前言:首先,我不建议你看这篇blog学习。撰写本文的目的是想用简单易懂的语言加深自己对知识的理解,相当于重述一遍书籍的内容。但由于是用于自己学习,我不会写的很详细,因为我认为笔记的作用只是让自己不记得某些知识点的时候可以快速翻阅查看,而有些知识点已经记在自己脑子里的我就不会再重复。因此我不建议你跟着这篇blog学习,毕竟每个人掌握的知识都不一样,看原书还是很有必要的!
以下是正文
内存有很多种,比如
1、静态内存:保存局部static对象、类static数据成员、定义在函数之外的变量。
2、栈内存:保存定义在函数内的非static对象。在这里插入代码片
存这两块内存里面的对象,都由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
3、但是还有一种内存,称作 自由空间 或 堆 ,用来存储 动态分配 的对象。动态分配对象需要我们人为的创造和释放。
12.1 动态内存和智能指针
12.1.1 shared_ptr 类
#include<memory>
shared_ptr<T> sp; //要起名字,如sp
make_shared<T>(arg) //不起名字
shared_ptr 有一个关联的计数器,称其为 引用计数。
12.1.2 直接管理内存
new没办法给他分配的内存起名字,只能返回一个指向该内存的指针。
int *pi = new int;
int *pi = new int(1024);
int *pi(new int(1024));
vector<int> *pv = new vector<int> {0, 1, 2, 3, 4};
编译器不能分辨一个指针是指向静态的还是动态的,因此delete一个静态内存或多次delete一个动态内存,都是错误的,尽管编译器都能编译通过,但这是严重的错误。
自己new出来的内存,在自己销毁(释放)之前,都会一直存在,所以要记得删除,要不然就使用智能指针。
delete一块动态内存之后,记得重置指向这个动态内存的指针,要不然就变成野指针。
然而,这个办法有时候会出现问题,比如:
int *p(new int(1024));
auto q = p;
delete p;
p = nullptr; // 重置p这个指针
你会发现,虽然你重置了 p 这个指针,但 q 怎么办?他也是一个指向动态内存的指针,但现在他指向的内存被释放了,q变成野指针了!所以说,这个办法有时候有问题。
12.1.3 shared_ptr 和 new 结合使用
shared_ptr<double> p1; // 正确,如12.1.1所示,要起名字。
shared_ptr<int> p2 (new int(1024)) // 正确, 直接初始化。
shared_ptr<int> p3 = new int(1024); //错误,必须要直接初始化。
shared_ptr<int> p4 = make_shared<int>(1024); //正确
之所以 p3 会出错,是因为shared_ptr 的构造函数是 explicit 的,因此不能进行隐式转化。换句话说, new int(1024) 应该用一个 int* 类型去接收,而你用的却是shared_ptr 去接收他,又由于shared_ptr 的构造函数无法进行隐式转换,所以 p3 这样的定义会出错。
shared_ptr<T> p(q); // p 管理内置指针q所指向的对象;q必须指向new分配的内存;
p.reset(); // 若p是count=1的shared_ptr,则reset会释放此对象。
p.reset(q); // 若传递了可选的参数内置指针q, 会令p指向q;
不要混合使用智能指针和普通指针
举个例子
void process(shared_ptr<int> ptr)
{
// 使用ptr
}// ptr离开作用域,被销毁
int *x(new int(1024)); // x是一个普通指针
process(shared_ptr<int>(x)); // 合法的,但看看接下来会发生什么
int i = *x; // 出错!因为x是一个空指针
当将一个shared_ptr 绑定到一个普通指针时,我们就把内存的管理责任交给了这个shared_ptr。因此,在上面的例子中,shared_ptr在函数调用结束,离开作用域后,这块内存就被销毁了,x也就变成空指针。
也不要使用 get 初始化另一个智能指针或为智能指针赋值。
get 函数返回一个内置指针,指向智能指针管理的对象。
其实道理和上面一样,当将一个shared_ptr 绑定到一个普通指针时,我们就把内存的管理责任交给了这个shared_ptr。
智能指针使用陷阱!! 为了正确使用智能指针,我们必须遵守一些基本规范
- 不使用相同的内置指针初始化(或reset)多个智能指针。
- 不delete get() 返回的指针
- 不使用get() 初始化智能指针
- 如果使用get() 返回的指针,要记住这块内存的管理权实际上还在智能指针上。
12.2 动态数组
12.2.1 new和数组
new初始化, 返回的是数组元素类型的指针。
int *p = new int[10]; // new分配一个int数组,并返回第一个int指针
int *pia = new int[10]; // 10 个未初始化的int
int *pia2 = new int[10](); // 10个初始化为0的int
string *psa = new string[10]; // 10个空string
string *psa = new string[10](); // 10个空string
delete:在指针前加一个[ ]
delete []p; // 如果没加[], 其行为是未定义的。
智能指针和动态数组
unique_ptr<int[]> up (new int[10]);
up.release(); // 自动用delete[] 销毁其指针。
注意:指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符)
shared_ptr 不支持动态数组管理。
12.2.2 allocator类
一般我们希望将 内存分配 和 对象构造 绑定在一起。
但如果要分配一大块内存的时候,我们希望将内存分配和对象构造分离。
allocator 就可以帮我们,把内存分配和对象构造分离开来。
allocator<string> alloc; // 可以分配string的allocator对象
auto const p = alloc.allocate(n); // 分配n个未初始化的string
allocator 分配的内存是未构造的。我们按需要在此内存中构造对象。
auto q = p;
alloc.construct(q++); //*q 为空字符串
alloc.construct(q++, 10, 'c'); //*q 为 cccccccccc
alloc.construct(q++, "hi"); //*q 为hi
用完对象后, 必须用destroy来销毁他们。
while (q != p )
alloc.destroy(--q);
对象被destroy后,我们可以重新construct, 也可以将内存归还给系统。
alloc.deallocate(p,n);