知识点:
- 全局变量:程序启动时分配,结束时销毁;局部自动对象:进入其定义所在块时分配,离开块时销毁;局部static对象:第一次使用前分配,程序结束时销毁
- 静态内存:保存static对象、类static数据成员、定义在函数之外的变量;栈内存:保存定义在函数内的非static对象;
- 内存池:被称作自由空间或堆。程序用堆来存储动态分配的对象
- 动态对象的生存期由程序控制,也就是说,当动态对象不再使用时,我们的代码必须显示地销毁它
1.智能指针:
- 定义在头文件memory中,shared_ptr、unique_ptr两种智能指针;
- shared_ptr:允许多个指针指向同一个对象
- unique_ptr:“独占”所指向的对象
- 标准库定义的名为weak_ptr的伴随类,是一种弱引用,指向shared_ptr所管理的对象
- shared_ptr和unique_ptr都支持的操作:
shared_ptr<T> sp //空智能指针,可以指向类型为T的对象
uniwue_ptr<T> up
p //将p用作一个条件判断,若p指向一个对象,则为true
*p //解引用一个p,获得它所指向的对象
p->mem //等价于(*p).mem
p.get() //返回p中保存的指针
swap(p,q) //交换p和q中的指针
p.swap(q)
2.shared_ptr智能指针:
- shared_ptr独有的操作:
shared_ptr<T> (args) //返回一个shared_ptr,指向一个动态分配的类型为T的对象,用args初始化此对象
shared_ptr<T> p(q) //p是shared_ptr q的拷贝;此操作会递增q的引用计数
p = q //p和q都是shared_ptr,此操作会递减p的引用计数,递增q的引用计数;若p的引用 计数变为0,则将其管理的原内存释放
p.unique() //若p.use_count()为1,返回true;否则返回false
p.use_count //返回与p共享对象的智能指针数量;
- make_shared函数:此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr
shared_ptr<int> p = make_shared<int>(42);
auto p1 =make_shared<vector<string>> ();
- 每个shared_ptr都有一个关联的计数器,通常称其为引用计数
/*1.当我们拷贝一个shared_ptr时,计数器都会递增
2.当我们给shared_ptr赋予一个新值或是sahred_ptr被销毁,计数器就会递减
3.一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象*/
- shared_ptr自动销毁所管理的对象,还会自动释放相关联的内存
shared_ptr<Foo> factory(T arg)
{
//恰当处理arg
//shared_ptr负责释放内存
return make_shared<Foo>(arg);
}
//例:下面的函数将factory返回的shared_ptr保存在局部变量中:
void use_factory(T arg)
{
shared_ptr<Foo> p = factory(arg);
//使用p
} //p离开了作用域,它指向的内存会被自动释放掉
3.直接管理内存:
- 运算符new分配动态内存,delete释放new分配的内存
- 使用new动态分配和初始化对象:
string *ps1 = new string; //默认初始化为空string
int *pi = new int(1024); //直接初始化,pi所指向的对象的值为1024
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6}; //列表初始化
//当括号中仅有单一初始化器时才可以使用auto
auto p1 = new auto(obj);
auto p2 = new auto{a,b,c}; //错误:括号中只能有单个初始化器
//动态分配const对象:
const int *pci = new const int(1024); //分配并初始化一个const int
//内存耗尽
int *p1 = new int; //如果分配失败,new抛出bad_alloc
int *p2 = new (nothrow) int; //如果分配失败,new返回一个空指针;将nothrow传递给new,告诉它不能抛出异常
- 指针值和delete:释放一块非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的
- const对象的值不能被改变,但本身可被销毁
const int *pci = new const int(1024);
delete pci;
- 返回指向动态内存的指针(而不是智能指针)的函数调用者必须记得释放内存;由内置指针管理的动态内存在被显示释放前一直都会存在
- delete之后,指针变成 空悬指针:指向一块曾经保存数据对象但现在已经无效的内存的指针
//1.delete之后重置指针值,只是提供了有限的保护
//2.在delete内存之后重置指针的方法只对这个指针有效,对其他任何扔指向内存的指针是没有作用的
int *p(new int(42)); //p指向动态内存
auto q = p; //p和q指向相同的内存
delete p; //p和q均变为无效
p = nullptr; //指出p不再绑定任何对象
4.shared_ptr和new结合使用:
- 可以用new返回的指针来初始化智能指针;接受指针参数的智能指针构造函数是explicit的,因此我们不能将一个内置指针类型转换为一个智能指针,必须使用直接初始化形式来初始化智能指针
shared_ptr<int> p(new int(1024)); //直接初始化形式
- 定义和改变shared_ptr的其他方法:
shared_ptr<T> p(q) //p管理内置类型q所指向的对象
shared_ptr<T> p(u) //p从unqiue_ptr u那里接管了对象的所以权
shared_ptr<T> p(q,d) //p接管了内置类型q所指的对象的所有权;p使用可调用对象d来代替delete
shared_ptr<T> p(p2,d) //p是shared_ptr p2的拷贝,唯一区别是p使用可调用对象d来代替delete
p.reset() //若p是唯一指向其对象的shared_ptr,reset会释放此对象;
p.reset(q) //会令p指向q
p.reset(q,d) //使用可调用对象d来代替delete
- 智能指针陷阱:
/*1.不使用相同的内置指针初始化(或reset)多个智能指针
2.不delete get()返回的指针
3.不使用get()初始化或reset另一个智能指针
4.如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
5.如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器*/
5.unique_ptr智能指针:
- 一个unique_ptr“拥有”它所指向的对象,因此不支持普通的拷贝或赋值的操作
- unique_ptr操作:
unique_ptr<T> u1 //空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放它的指针
unique_ptr<T, D> u2 //u2会调用一个类型为D的可调用对象来释放它的指针
unique_ptr<T, D> u(d) //空unique_ptr,可以指向类型为T的对象;用一个类型为D的对象d来代替delete
u = nullptr //释放u所指向的对象,将u置为空
u.release() //u放弃对指针的控制权,返回指针,并将u置为空
//release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值
u.reset() //释放u所指的对象
u.reset(q) //如果提供了内置指针q,令u指向这个对象
u.reset(nullptr)
- 虽然不能拷贝或赋值unqiue_ptr,但可以通过调用release或reset将指针的所有权从一个unique_ptr转移到另一个unique
6.weak_ptr:
- weak_ptr操作:
weak_ptr<T> w //空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) //与shared_ptr sp指向相同对象的weak_ptr
w = p //p可以是一个shared_ptr或一个weak_ptr,赋值后w与p共享对象
w.reset() //将w置为空
w.use_count() //与w共享对象的shared_ptr的数量
w.expired() //若w.use_count()为0,返回true,否则返回false
w.lock() //如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr
- 当创建一个weak_ptr时,要用一个shared_ptr来初始化它:
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp弱共享p;p的引用计数为改变
- 不能使用weak_ptr直接访问对象,必须调用lock
7.动态数组:
- new和数组:在类型名后跟一队方括号,在其中指明要分配的对象的数目
int *p = new int[42]; //分配一个数组得到一个数组元素类型的指针
//初始化动态分配对象的数组
int *pia = new int[10]; //10个未初始化的int
int *pia2 = new int[10](); //10个值初始化为0的int
string *psa = new string[10]; //10个空string
string *psa2 = new string[10](); //10个空string
//10个int分别用列表中对应的初始化器初始化
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
/*1.如果初始化器数目小于元素数目,剩余元素将进行值初始化
2.如果初始化器数目大于元素数目,则new表达式失败,不会分配任何内存*/
//释放动态数组
delete [] pa; //pa必须指向一个动态分配的数组或为空
- 智能指针与动态数组:
//指向数组的unique_ptr
unique_ptr<T[]> u //u可以指向一个动态分配的数组
unique_ptr<T[]> u(p) //u指向内置指针p所指向的动态分配的数组
u[i] //返回u拥有的数组中的位置i处的对象;u必须指向一个数组
//如果使用shared_ptr管理数组,必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int *p){delete [] p;});
8.allocator类:
- allocator类定义在头文件memory中,帮助我们将内存分配和对象构造分离开来;分配的内存是原始的、未构造的
- 当allocator对象分配内存时,会根据给定的对象类型来确定恰当的内存大小和对齐位置
- 标准库allocator类及其算法:
allocator<T> a //定义了一个名为a的allocator对象,可以为类型为T的对象分配内存
a.allocate(n) //分配一段原始的、未构造的内存,保存n个类型为T的对象
a.deallocate(p,n) /*释放从T*指针p中地址开始的内存,这块内存中保存了n个类型为T的对象;
p必须是一个先前由allocate返回的指针,且n必须是p创建时要求的大小。
在调用该函数之前,用户必须对每个在这块内存中创建的对象调用destroy*/
a.construct(p, args) //p必须是一个类型为T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象
a.destroy(p) //此算法对p指向的对象执行析构函数
- 为了使用allocate返回的内存,必须用construct构造对象。使用未构造的内存,其行为是未定义的
- 拷贝和填充未初始化内存的算法。。。。。
说点题外话吧,说实话在看到本章主要讲解指针与内存的时候,心里很慌(本来之前学习c的时候就对指针有恐惧感);但渐渐学习下来,发现并没有想象中的那么难,只是需要静下心来认真理解每一句话的含义,尽管不能立马吃透,但起码能做到了解大部分知识点,能够理解书中指针的应用;也许有些东西以后并不会用到,但在这学习过程中充实自己我觉得是很有必要的。继续加油吧!