C++语言定义了两个运算符来分配和释放动态内存:运算符new分配内存,运算符delete释放new分配的内存。

运算符new和delete

  • 使用new动态分配和初始化对象
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:
int* p = new int;

默认情况下,用new分配的对象进行默认初始化,如果动态分配的对象是一个类,则使用类的默认构造函数。而对于内置类型,它的值将是未定义的。

string* str = new string; // 初始化为空的string
int* pi= new int; // pi指向一个未初始化的int,它的值未知

我们可以使用直接初始化的方式来初始化一个new分配的对象,我们可以使用传统的圆括号形式进行构造,在新标准下,我们可以使用花括号列表来进行值初始化。

int* pi = new int(1024); // pi指向一个值为1024的int类型对象
string* ps = new string(10, '9'); // ps指向一个包含10个'9'的string对象 vector<int>* v = new vector<int> {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // v指向一个vector对象,包含从0到9的10个元素
也可以给new分配的对象进行值初始化:只需在类型后面加一对空括号即可:
int* pi = new int(); // pi指向一个int类型对象,值为0
string* ps = new string(); // ps指向一个string对象,值为空字符串
● 对于定义了自己的构造函数的类来说,要求值初始化是没有意义的。不管采用什么形式,对象都会通过默认构造函数来进行初始化。 但对于内置类型来说,值初始化的类型对象有着良好定义的值,而默认初始化的对线的值是未定义的。
我们提供了一个括号包围的初始化器后,就可以使用auto从初始化器来推断我们想要分配内存的对象类型。如果我们要用初始化器来推断类型,只有当括号中的初始化器为单一的参数时才可以使用auto:
auto p1 = new auto(obj); // p1 指向一个类型与obj类型一样的,用obj初始化的对象
auto p2 = new auto(a, b, c); // 错误,括号中只能有单一的参数
  • 动态分配的const对象
用new来分配一个const对象是合法的:
const int* pci = new const int(1024); // 分配一个const int对象并初始化为1024
cosnt string* pcs = new const string; // 使用string的默认构造函数来初始化一个const string
分配一个const对象必须进行初始化,对于一个定义了默认构造函数的类来说,它的动态const对象可以默认初始化,但对于其他类型的对象来说,必须进行显示初始化。
由于分配的对象时const的,因此new一个const对象返回指向const的指针。
  • 内存耗尽
默认情况下,如果new不能分配所要求的内存空间,就会抛出一个bad_alloc异常,我们可以改变使用new的方式来阻止它抛出异常:
int* p1 = new int; // 如果分配失败,抛出一个std::bad_alloc异常
int* p2 = new (nothrow) int; // 如果分配失败,new返回一个空指针

这种阻止new抛出异常的方式我们叫做定位new, 定位new允许我们向new传递额外的参数,我们在此例中传递给new的是标准库定义的nothrow对象,它告诉编译器,如果new操作失败,不能抛出异常,在这种情况下,new会返回一个空指针。
bad_alloc和nothrow都定义在头文件 <new>中。

  • 指针值和delete
当动态内存使用结束后,必须将内存归还给系统。我们通过delete表达式来释放。delete接受一个指针,指针必须指向一个动态分配的对象或一个空指针。
释放一块非new分配的内存,或者将相同的指针释放多次,其行为是未定义的。
int i, *pi1 = &i, *pi2 = nullptr;
double* pd = new double(33), *pd2 = pd;
delete i; // 错误,i不是一个指针
delete pi1; // 未定义,pi1指向一个局部变量
delete pi2; // 正确,释放一个空指针总是没有错误的
delete pd; // 正确,pd由new分配而来
delete pd2; // 未定义,pd2指向的内存已经被释放了

虽然一个const对象的值不能被改变,但是它本身是可以被销毁的。和其他任何动态对象一样,销毁一个const动态对象,只要delete指向它的指针即可:

const int* pci = new const int(1024);
delete pci;
  • 动态对象的生存期直到被释放时为止
shared_ptr管理的内存在最后一个shared_ptr销毁时会被自动释放,对于内置指针来说,我们必须手动释放它所管理的动态对象,直到被显示释放之前都是存在的。
对于返回动态内存的指针(不是智能指针)的函数来说,这给其调用者增加了一个负担:调用者必须记得释放内存。
Foo* factory(T arg) {
/*
*/ return new Foo(arg); // 调用者负责释放此内存
}

这个函数负责分配内存,但并不负责释放它所分配的内存。factory的调用者负责在不需要此对象的时候释放它。

void use_factory(T arg) {
Foo* p = factory(arg);
// 使用p但是不delete它 } // p离开了作用域,但是它指向的内存并没有被释放

内置类型的对象被销毁时什么也不会发生,特别时,当一个指针离开其作用域时,它所指向的对象什么也不会发生。如果这个指针指向的是动态内存,那么内存不会被释放。

正确的做法是在use_factory中记得释放内存:
void use_fatory(T arg) {
Foo* p = factory(arg);
// 使用p
delete p;
}

如果还有其他代码块需要用到此指针,我们就需要将它返回给它的调用者:

 
Foo* use_fatory(T arg) {
Foo* p = factory(arg);
// 使用p
return p; // 调用者必须释放内存
}
***************************************************************************************************
动态内存的管理容易出错:使用new和delete管理动态内存存在三个常见问题:
1. 忘记delete内存, 忘记释放动态内存会导致我们所说的“内存泄露”问题,这种内存永远不可能归还给系统,查找内存泄露错误是非常困难的,因为应用程序通常运行很长时间,直到内存耗尽时,才会发现这种错误。
2. 使用已经释放掉的动态对象, 通过在释放内存后将指针置为空,有时可以避免这种错误。
3. 同一块内存释放多次,有时候存在这种情况,两个指针可能指向同一块动态内存,当一个指针不再使用后,将其delete,它所管理的内存就归还给自由空间了,而在之后delete另一个指针时,自由空间就可能被破坏。
                                                                                                                *********************************************************************************************************************
  • delete之后重置指针值
当我们delete一个指针之后,指针值就变得无效了,虽然这样,但是在很多机器上指针仍然保留着(已经释放了的)动态内存的地址。在delete之后,指针就变为一个空悬指针,
未初始化的指针的缺点空悬指针也都有,我们可以在指针要离开作用域之前释放它所关联的内存,这样就没有机会继续使用指针了。如果我们需要保留指针,我们可以将这个指针置为nullptr,这样就清楚明了的指出这个指针不再指向任何对象。

C++ Primer : 第十二章 : 动态内存之动态内存管理(new和delete)的更多相关文章

  1. C++ Primer : 第十二章 : 动态内存之动态数组

    动态数组的分配和释放 new和数组 C++语言和标准库提供了一次分配一个对象数组的方法,定义了另一种new表达式语法.我们需要在类型名后跟一对方括号,在其中指明要分配的对象的数目. int* arr ...

  2. C++ Primer : 第十二章 : 动态内存之shared_ptr与new的结合使用、智能指针异常

    shared_ptr和new结合使用 一个shared_ptr默认初始化为一个空指针.我们也可以使用new返回的指针来初始化一个shared_ptr: shared_ptr<double> ...

  3. C++ Primer : 第十二章 : 动态内存之shared_ptr类

    在C++中,动态内存是的管理是通过一对运算符来完成的:new  ,在动态内存中为对象分配空间并返回一个指向该对象的指针,delete接受一个动态对象的指针,销毁该对象,并释放该对象关联的内存. 动态内 ...

  4. C++ Primer : 第十二章 : 动态内存之allocator类

    标准库allocator类定义在头文件 <memory>中.它帮助我们将内存分配和构造分离开来,它分配的内存是原始的.未构造的. 类似vector,allocator也是一个模板类,我们在 ...

  5. C++ Primer : 第十二章 : 动态内存之shared_ptr类实例:StrBlob类

    StrBlob是一个管理string的类,借助标准库容器vector,以及动态内存管理类shared_ptr,我们将vector保存在动态内存里,这样就能在多个对象之间共享内存. 定义StrBlob类 ...

  6. C++ Primer : 第十二章 : 动态内存之unique_ptr和weak_ptr

    unique_ptr 一个unique_ptr拥有它所管理的对象,与shared_ptr不同,unique_ptr指向的对象只能有一个用户.当unique_ptr被销毁后,它所指向的对象也被销毁. 定 ...

  7. C++Primer 第十二章

    //1.标准库提供了两种智能指针类型来管理动态对象,均定义在头文件memory中,声明在std命名空间. // shared_ptr:允许多个指针指向同一个对象. // unique_ptr:独占所指 ...

  8. C++ Primer : 第十二章 : 文本查询程序

    C++ Primer书上这个例子讲的很不错,写写帮助自己理解标准库和智能指针. .h 文件内容 #include <fstream> #include <iostream> # ...

  9. C++ Primer之 十二章 类

    1.关于类的const对象 const对象只能调用声明为const的成员函数,在这篇csdn博客中也讨论了这个问题. 究其原因是因为 const 对象A 调用了非const函数F, F因为没有cons ...

随机推荐

  1. C#遍历窗体所有控件或某类型所有控件

    //遍历窗体所有控件, foreach (Control control in this.Controls) { //遍历后的操作... control.Enabled = false; } 遍历某个 ...

  2. display:flex

    元素在x方向走,元素y不一样[高度].可以用对齐.align-items. align-self 自身调节元素在x方向走,元素在x方向距离.justify-content .   元素在x方向走,x方 ...

  3. 站在K2角度审视流程--任务的独占与释放

    应用场景一:某件事情由A.B两人(或者更多人)完成,任务开始后,两人随时可以处理任务,只需有一人处理完成,此事情即可结束. 应用场景二:某件事情由A.B两人(或者更多人)完成,任务开始后,两人随时可以 ...

  4. Android 时间戳的转换

    在Android应用中,经常会碰到后台的时间是时间戳而现实的需要今天什么时候,昨天什么时候,就像微博的时间显示一样.现在我上一个把时间戳转换的代码: public static String getT ...

  5. idea使用generator自动生成model、mapper、mapper.xml(转)

    原文链接:http://www.mamicode.com/info-detail-445217.html TEP 0.在Intellij IDEA创建maven项目(本过程比较简单,略) STEP 1 ...

  6. joinfetch之意义

    既然被join的对象早晚都要用到,为什么要先从A表取这边的独享,再根据关联关系取B表中的对象,分两次或者多次进行,增加数据库的负载呢? 为什么不把A表和B表join成一张表,从这个组合表中把要取的对象 ...

  7. 匹配IP地址的正则表达式 (转)

    正则表达式 ^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[ ...

  8. linux下文件系统的介绍

    一.linux文件系统的目录结构 目录 描述 / 根目录 /bin 做为基础系统所需要的最基础的命令就是放在这里.比如 ls.cp.mkdir等命令:功能和/usr/bin类似,这个目录中的文件都是可 ...

  9. lightoj1038

    //Accepted 2860 KB 16 ms //概率 //对于n,假设n变成1的期望步数为p(n) //则p(n)=1/t*sum((1+p(d))) d|n //解得:p(n)=1/(t-1) ...

  10. PHP、C++的重载

    首先明确一点:PHP重载是用在面向对象的类当中,而不支持函数重载. 这点与C++不一样,在C++当中,重载可以用于面向过程和面向对象,而且方法也不一样. 在C++中,重载适用于当函数名相同时,函数所需 ...