六一的部落格


关关难过关关过,前路漫漫亦灿灿。



使用内置指针直接管理动态内存


对象的生存期

  1. 到目前为止,我们编写程序时使用的对象都有着严格定义的生存期

    • 全局对象: 在程序启动时分配, 在程序结束时销毁

    • 局部自动对象: 当我们进入其定义所在的程序块时被创建, 离开块时被销毁

    • 局部静态对象: 在第一次使用前分配,在程序结束时销毁

  2. 接下来将介绍动态对象: 动态对象的生存期与创建地点无关; 由程序员控制

    当我们不再需要动态对象时,需在代码中显式销毁它们

  3. 使用动态对象的难点就在于如何正确地对其进行销毁

    为此, 标准库定义了两个智能指针类型来管理动态对象

    智能指针提供合适的机制供程序员使用

    当满足智能指针销毁对象的条件时, 指向动态对象的智能指针会自动销毁动态对象


存放对象的内存空间

到目前为止,我们编写的程序只用到静态内存和栈内存

  1. 静态内存: 用来存放全局对象,类静态成员,和局部静态对象

    静态对象在程序结束时销毁

  2. 栈内存: 用来存放定义在块内的自动对象

    栈对象仅在其定义所在的程序块运行时才存在

  3. 堆: 存放动态对象

    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}; // 错误

示例

  1. 创建string类型的动态对象
    1string *ps = new auto(string("hello"));
  2. 创建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的指针

  1. 从外向里理解

    具有顶层const的C风格字符类型为 const char * const

    指向具有顶层const的C风格字符类型对象的指针类型为 const char * const * , 即指向动态对象的内置指针类型

  2. 从里向外理解

    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运算符: 销毁动态对象并释放内存

  1. 如果忘记销毁动态对象, 会发生内存泄漏
  2. 如果在有多个指针指向同一动态对象的情况下对动态对象进行销毁, 而未对所有指针置空, 存在访问非法内存的风险
  3. 不要超过一次释放同一块内存
1delete p;

接受一个指针, 指向动态对象, 或者为空


接受一个指针

  1. 编译器能够判断参数是否为指针

    1int i;
    2delete i; // 错误
    
  2. 编译器无法判断指针是否指向动态对象

    1int i, *pi = &i;
    2delete pi; // 错误
    
  3. 编译器无法判断该动态对象是否有效

    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;

使用内置指针管理动态内存


使用内置指针直接管理动态内存


对象的生存期

  1. 到目前为止,我们编写程序时使用的对象都有着严格定义的生存期

    • 全局对象: 在程序启动时分配, 在程序结束时销毁

    • 局部自动对象: 当我们进入其定义所在的程序块时被创建, 离开块时被销毁

    • 局部静态对象: 在第一次使用前分配,在程序结束时销毁

  2. 接下来将介绍动态对象: 动态对象的生存期与创建地点无关; 由程序员控制

    当我们不再需要动态对象时,需在代码中显式销毁它们

  3. 使用动态对象的难点就在于如何正确地对其进行销毁

    为此, 标准库定义了两个智能指针类型来管理动态对象

    智能指针提供合适的机制供程序员使用

    当满足智能指针销毁对象的条件时, 指向动态对象的智能指针会自动销毁动态对象


存放对象的内存空间

到目前为止,我们编写的程序只用到静态内存和栈内存

  1. 静态内存: 用来存放全局对象,类静态成员,和局部静态对象

    静态对象在程序结束时销毁

  2. 栈内存: 用来存放定义在块内的自动对象

    栈对象仅在其定义所在的程序块运行时才存在

  3. 堆: 存放动态对象

    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}; // 错误

示例

  1. 创建string类型的动态对象
    1string *ps = new auto(string("hello"));
  2. 创建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的指针

  1. 从外向里理解

    具有顶层const的C风格字符类型为 const char * const

    指向具有顶层const的C风格字符类型对象的指针类型为 const char * const * , 即指向动态对象的内置指针类型

  2. 从里向外理解

    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运算符: 销毁动态对象并释放内存

  1. 如果忘记销毁动态对象, 会发生内存泄漏
  2. 如果在有多个指针指向同一动态对象的情况下对动态对象进行销毁, 而未对所有指针置空, 存在访问非法内存的风险
  3. 不要超过一次释放同一块内存
1delete p;

接受一个指针, 指向动态对象, 或者为空


接受一个指针

  1. 编译器能够判断参数是否为指针

    1int i;
    2delete i; // 错误
    
  2. 编译器无法判断指针是否指向动态对象

    1int i, *pi = &i;
    2delete pi; // 错误
    
  3. 编译器无法判断该动态对象是否有效

    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;