使用内置指针管理动态内存
2023年12月28日 2023年12月29日
使用内置指针直接管理动态内存
对象的生存期
-
到目前为止,我们编写程序时使用的对象都有着严格定义的生存期
-
全局对象: 在程序启动时分配, 在程序结束时销毁
-
局部自动对象: 当我们进入其定义所在的程序块时被创建, 离开块时被销毁
-
局部静态对象: 在第一次使用前分配,在程序结束时销毁
-
-
接下来将介绍动态对象: 动态对象的生存期与创建地点无关; 由程序员控制
当我们不再需要动态对象时,需在代码中显式销毁它们 -
使用动态对象的难点就在于如何正确地对其进行销毁
为此, 标准库定义了两个智能指针类型来管理动态对象智能指针提供合适的机制供程序员使用
当满足智能指针销毁对象的条件时, 指向动态对象的智能指针会自动销毁动态对象
存放对象的内存空间
到目前为止,我们编写的程序只用到静态内存和栈内存
-
静态内存: 用来存放全局对象,类静态成员,和局部静态对象
静态对象在程序结束时销毁 -
栈内存: 用来存放定义在块内的自动对象
栈对象仅在其定义所在的程序块运行时才存在 -
堆: 存放动态对象
heap
也称作内存池/自由空间
free store
静态内存和栈内存中的对象由编译器自动创建和销毁
new运算符: 为对象申请动态内存并初始化
成功则返回指向动态对象的内置指针;抛出bad_alloc异常
头文件
1#include <new>
默认初始化或直接初始化对象
- | |
---|---|
默认初始化 | new + 类型 |
直接初始化 | new + 类型 + ( + 匹配构造函数的形参 + ) |
直接初始化形参列表为空时, 值初始化动态对象
1T *p = new T; 2T *p = new T(args);
示例
1int *pi = new int; 2string *ps = new string; 3cout << "*pi = " << *pi << endl 4cout << "*ps = " << *ps << endl;
1int *pi = new int(); 2int *pi2 = new int(1024); 3string *ps = new string(); 4string *ps2 = new string(10, '9'); 5 6cout << "*pi = " << *pi << endl; 7cout << "*pi2 = " << *pi << endl; 8cout << "*ps = " << *pi << endl; 9cout << "*ps2 = " << *pi << endl;
创建动态容器对象
对容器进行列表初始化,或者匹配构造函数
1vector<int> *pv = new vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 2 3vector<int> *pv2 = new vector<int>(10, 1); 4 5cout << "Elements of pv: "; 6for (auto &i : *pv) 7 cout << i << '\t'; 8cout << endl; 9 10cout << "Elements of pv2: "; 11for (auto &i : *pv2) 12 cout << i << '\t'; 13cout << endl;
使用auto指示类型
对象类型由auto根据单一初始化器推断得出
1T *p = new auto(one_arg); 2T *p = new auto{one_arg};
1int *pi = new auto(10); 2auto pi2 = new auto(10); 3int *pi3 = new auto{10};
只支持单一初始化器
1string *ps2 = new auto(10, 'd'); // 错误
等号两侧的类型要一致
1unsigned *pui = new auto(10); // 错误
无法推断容器类型
1auto pv = new auto{0, 1, 2, 3}; // 错误
无法推断内置数组类型
1auto pia = new auto[10]{1, 2, 3}; // 错误
示例
- 创建string类型的动态对象
1string *ps = new auto(string("hello"));
- 创建C风格字符串类型的动态对象
1const char **ppc = new auto("hello"); 2cout << *ppc << endl;
创建具有顶层const的动态对象
- |
---|
new + const + 类型 |
new + const + 类型 + ( + 匹配构造函数的参数 + ) |
1const T *pi = new const T; 2const T *pi = new const T(args);
内置类型必须初始化
1const int *pi = new const int; // 错误 2const int *pi2 = new const int(); 3const int *pi3 = new const int(1024);
类类型支持默认初始化
1const string *psc = new const string;
示例: 创建具有顶层const的C风格字符串类型的动态对象
C风格字符串类型为 const char *
, 是具有底层const的指针
-
从外向里理解
具有顶层const的C风格字符类型为const char * const
指向具有顶层const的C风格字符类型对象的指针类型为
const char * const *
, 即指向动态对象的内置指针类型 -
从里向外理解
new运算符返回一个具有底层const指针, 类型为const *
指针指向C风格字符串, 类型为
const char * const *
1const char * const *pc = new const auto("word");
定位new
placement new
通过向new传递参数,来控制new的行为
nothrow
标准库定义
向new传递nothrow对象,若申请内存失败,new不抛出异常,返回空指针
nothrow包含在头文件new中
- |
---|
new + ( + nothrow + ) + … |
1auto p = new(nothrow) T; 2auto p = new(nothrow) T(args);
1int *p = new(nothrow) int;
delete运算符: 销毁动态对象并释放内存
- 如果忘记销毁动态对象, 会发生内存泄漏
- 如果在有多个指针指向同一动态对象的情况下对动态对象进行销毁, 而未对所有指针置空, 存在访问非法内存的风险
- 不要超过一次释放同一块内存
1delete p;
接受一个指针, 指向动态对象, 或者为空
接受一个指针
-
编译器能够判断参数是否为指针
1int i; 2delete i; // 错误
-
编译器无法判断指针是否指向动态对象
1int i, *pi = &i; 2delete pi; // 错误
-
编译器无法判断该动态对象是否有效
1double *pd = new double(33); 2delete pd; 3delete pd; // 错误
接受空指针
1int *pi = nullptr; 2delete pi;
delete动态对象后,将指针置空
建议
1int *p(new int(42)); 2auto q = p; 3delete p; 4p = nullptr;