C++语法学习笔记

Posted Shi Peng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++语法学习笔记相关的知识,希望对你有一定的参考价值。

一、智能指针 unique_ptr

unique_ptr是C++ 11提供的用于防止内存泄漏的智能指针。

1.1、unique_ptr的实现原理

实现方式:
unique_ptr对象封装了一个原始指针,并管理了他的生命周期,即当该对象被销毁时,会自动在其析构函数中删除关联的原始指针。

例子:

#include <iostream>
#include <memory>

struct Task 
    int mId;
    Task(int id ) :mId(id) 
        std::cout << "Task::Constructor" << std::endl;
    
    ~Task() 
        std::cout << "Task::Destructor" << std::endl;
    
;

int main()

    // 通过原始指针创建 unique_ptr 实例
    std::unique_ptr<Task> taskPtr(new Task(23));

    //通过 unique_ptr 访问其成员
    int id = taskPtr->mId;
    std::cout << id << std::endl;

    return 0;

输出:

Task::Constructor
23
Task::Destructor

可以看到,在main函数退出时,unique_ptr taskPtr的生命周期也结束了,这时,在unique_ptr对象taskPtr的析构函数中,会删除关联的原始指针,这样就不需要专门delete task对象了。

这样,无论函数是否正常退出(包括异常退出),都会调用taskPtr的析构函数,来删除原始指针,从而防止了内存泄漏。

1.2、unique_ptr独享对象所有权

unique_ptr对象始终是其原始指针的唯一所有者,即我们无法赋值unique_ptr对象,他只能移动。

正因为每个unique_ptr对象都是其原始指针的唯一所有者,因此在其析构函数中直接删除其原始指针,不需要任何计数。

1.3、unique_ptr的一些操作

1.3.1、创建一个空的unique_ptr对象

std::unique_ptr<int> ptr1;

因为没有与之关联的原始指针,所以他是空的。

1.3.2、检查unique_ptr对象是否为空

// 方法1
if(!ptr1)
	std::cout<<"ptr1 is empty"<<std::endl;
// 方法2
if(ptr1 == nullptr)
	std::cout<<"ptr1 is empty"<<std::endl;

1.3.3、使用原始指针创建unique_ptr对象

方法1:

std::unique_ptr<Task> taskPtr(new Task(22));

方法2:

std::unique_ptr<Task> taskPtr(new std::unique_ptr<Task>::element_type(23));

1.3.4、使用std::make_unique 创建 unique_ptr 对象

std::make_unique<>() 是C++ 14 引入的新函数

std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34);

1.3.5、获取被管理对象的指针

通过get()函数

Task *p1 = taskPtr.get();

1.3.6、重置 unique_ptr 对象

reset()函数将释放delete关联的原始指针,并使unique_ptr对象为空

taskPtr.reset();

1.3.7、unique_ptr 对象不可复制

由于 unique_ptr 不可复制,只能移动。因此,我们无法通过复制构造函数或赋值运算符创建unique_ptr对象的副本。

// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error

// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; //compile error

1.3.8、转移 unique_ptr 对象的所有权 – 这做dir切换时可用到

我们无法复制 unique_ptr 对象,但我们可以转移它们。这意味着 unique_ptr 对象可以将关联的原始指针的所有权转移到另一个 unique_ptr 对象。

例子:

// 通过原始指针创建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));

// 把taskPtr2中关联指针的所有权转移给taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);

// 现在taskPtr2关联的指针为空
if(taskPtr2 == nullptr) 
	std::cout<<"taskPtr2 is  empty"<<std::endl;


// taskPtr2关联指针的所有权现在转移到了taskPtr4中
if(taskPtr4 != nullptr)
	std::cout<<"taskPtr4 is not empty"<<std::endl;

// 会输出55
std::cout<< taskPtr4->mId << std::endl;

说明:std::move() 将把 taskPtr2 的所有权转移给taskPtr4, 转移后,taskPtr2变为空。

1.3.9、释放关联的原始指针

在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。

std::unique_ptr<Task> taskPtr5(new Task(55));
// 不为空
if(taskPtr5 != nullptr)
	std::cout<<"taskPtr5 is not empty"<<std::endl;
// 释放关联指针的所有权
Task * ptr = taskPtr5.release();
// 现在为空
if(taskPtr5 == nullptr)
	std::cout<<"taskPtr5 is empty"<<std::endl;

说明:release()只是释放了所有权,并没有delete原始指针,指向的对象还在。

1.4、完整示例

#include <iostream>
#include <memory>

struct Task 
    int mId;
    Task(int id ) :mId(id) 
        std::cout<<"Task::Constructor"<<std::endl;
    
    ~Task() 
        std::cout<<"Task::Destructor"<<std::endl;
    
;

int main()

    // 空对象 unique_ptr
    std::unique_ptr<int> ptr1;

    // 检查 ptr1 是否为空
    if(!ptr1)
        std::cout<<"ptr1 is empty"<<std::endl;

    // 检查 ptr1 是否为空
    if(ptr1 == nullptr)
        std::cout<<"ptr1 is empty"<<std::endl;

    // 不能通过赋值初始化unique_ptr
    // std::unique_ptr<Task> taskPtr2 = new Task(); // Compile Error

    // 通过原始指针创建 unique_ptr
    std::unique_ptr<Task> taskPtr(new Task(23));

    // 检查 taskPtr 是否为空
    if(taskPtr != nullptr)
        std::cout<<"taskPtr is  not empty"<<std::endl;

    // 访问 unique_ptr关联指针的成员
    std::cout<<taskPtr->mId<<std::endl;

    std::cout<<"Reset the taskPtr"<<std::endl;
    // 重置 unique_ptr 为空,将删除关联的原始指针
    taskPtr.reset();

    // 检查是否为空 / 检查有没有关联的原始指针
    if(taskPtr == nullptr)
        std::cout<<"taskPtr is  empty"<<std::endl;

    // 通过原始指针创建 unique_ptr
    std::unique_ptr<Task> taskPtr2(new Task(55));

    if(taskPtr2 != nullptr)
        std::cout<<"taskPtr2 is  not empty"<<std::endl;

    // unique_ptr 对象不能复制
    //taskPtr = taskPtr2; //compile error
    //std::unique_ptr<Task> taskPtr3 = taskPtr2;

    
        // 转移所有权(把unique_ptr中的指针转移到另一个unique_ptr中)
        std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
        // 转移后为空
        if(taskPtr2 == nullptr)
            std::cout << "taskPtr2 is  empty" << std::endl;
        // 转进来后非空
        if(taskPtr4 != nullptr)
            std::cout<<"taskPtr4 is not empty"<<std::endl;

        std::cout << taskPtr4->mId << std::endl;

        //taskPtr4 超出下面这个括号的作用于将delete其关联的指针
    

    std::unique_ptr<Task> taskPtr5(new Task(66));

    if(taskPtr5 != nullptr)
        std::cout << "taskPtr5 is not empty" << std::endl;

    // 释放所有权
    Task * ptr = taskPtr5.release();

    if(taskPtr5 == nullptr)
        std::cout << "taskPtr5 is empty" << std::endl;

    std::cout << ptr->mId << std::endl;

    delete ptr;

    return 0;

输出结果:

ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is  not empty
23
Reset the taskPtr
Task::Destructor
taskPtr is  empty
Task::Constructor
taskPtr2 is  not empty
taskPtr2 is  empty
taskPtr4 is not empty
55
Task::Destructor
Task::Constructor
taskPtr5 is not empty
taskPtr5 is empty
66
Task::Destructor

1.5、总结

1、new出来的对象是位于堆内存上的,必须调用delete才能释放其内存。
2、unique_ptr 是一个装指针的容器,且拥有关联指针的唯一所有权,作为普通变量使用时系统分配对象到栈内存上,超出作用域时会自动析构,unique_ptr对象的析构函数中会delete其关联指针,这样就相当于替我们执行了delete堆内存上的对象。

3、常用函数

成员函数作用
reset()重置unique_ptr为空,delete其关联的指针。
release()不delete关联指针,并返回关联指针。释放关联指针的所有权,unique_ptr为空。
get()仅仅返回关联指针

4、unique_ptr不能直接复制,必须使用std::move()转移其管理的指针,转移后原 unique_ptr 为空。std::unique_ptr taskPtr4 = std::move(taskPtr2);

二、new/delete用法

在C++中,new用于创建对象,delete用于销毁对象:
new对象时,会在堆中动态分配内存,而delete可把对象在堆中分配的内存释放掉。

在C++中,new和delete都不是函数,而是C++定义的关键字。

对于下面的语句:

string *ps = new string("hello world");

C++中的new和C中的malloc还是有点不同:
malloc申请空间后,不会对内存进行必要的初始化,而new会。

2.1、操作符new和delete

下面是C++标准库函数:

void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array

这两个函数和C语言中的malloc和free函数有点像,都是用来申请和释放内存的,并且operator new申请内存后,不会对内存进行初始化,而直接返回申请内存的指针。

下面通过一个例子来看new和delete背后的机制:

class A

public:
    A(int v) : var(v)
    
        fopen_s(&file, "test", "r");
    
    ~A()
    
        fclose(file);
    

private:
    int var;
    FILE *file;
;

在此例子中,类A:
1)有两个私有变量:int类型的var 和 一个文件类型的变量
2)有一个构造函数,一个析构函数:
在构造函数中,初始化int变量,并打开一个文件;
在析构函数中,关闭打开的文件。

我们使用

class A *pA = new A(10);

来创建类A的实例,返回其指针pA。

此时,new所做的工作如下:

接着,我们看delete做了什么?

delete pA;


delete操作做了两件事:
1)调用A类的析构函数,对打开的文件进行关闭
2)通过标准库函数operator delete来释放该对象的内存。

以上是关于C++语法学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

C++基础学习笔记C++语法之引用

C++基础学习笔记C++语法之引用

C++语法学习笔记

js学习笔记03-ES6语法

C++入门<一> (跑路人笔记)

C++学习笔记