实在不好意思,到这里才给大家分享new和delete。
对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
new和delete是运算符不是函数
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。new/delete不是库函数,而是运算符。
一、new关键字
- new用来在内存中分配一块内存,new分配的对象是没有名称的,而是返回一个指向该对象的指针
- int *p1=new int(1); //pi指向一个动态分配的、初始化值为1的无名对象
- int *p2(new int(1)); //同上
二、new的值初始化规则
通用规则
- 如果类型名后无括号:内置类型或组合类型的对象的值是未定义的,而类类型对象将用默认构造函数进行初始化
- 如果类型名后有括号:则内置类型/组合类型/类类型都进行默认的初始化
br
C++11标准下的初始化规则
- 允许使用花括号来指定初始化列表
- 注意:花括号{}本质上是用来当一个列表的
- vector<int> *vec = new vector<int> { 1, 2, 3, 4, 5 };
- int *arr = new int[3] {1, 2, 3};
- int *p = new int { 1 };
auto初始化
- 如果使用圆括号初始化器,就可以使用auto来推断我们想要分配的对象类型
br
const初始化
- 因为const对象为常量,初始化之后就不可以修改值了
- 类类型初始化时可以不给出值,此时使用默认的构造函数值
- 其它类型必须显示地给出初始化值(注意:编译器本质允许不给出值,但是定义之后就不能改变值了)
br
三、bad_alloc异常处理
- bad_alloc异常出现的情景:如果一个程序可用的内存消耗完,那么new表达式就会失败。默认情况下,此时会抛出bad_alloc异常
- bad_alloc异常的处理:如果不处理此异常那么程序就会中断。但是我们可以使用定位new的nothrow关键字来处理此异常
- nothrow关键字:如果在new后面加一个圆括号并且加上“nothrow”,那么捕获到bad_alloc异常时,new返回空指针,而不抛出异常。我们称这种形式的new为“定位new”
- bad_alloc和nothrow都定义在头文件new中
br
四、delete关键字
- 用来释放一块动态申请的内存,解除指针与该指针所指向的内存之间的关系
- 如果new的动态内存没有被释放(销毁),那么该动态内存就一直存在,会造成浪费
五、delete的使用规则
规则如下
- 不能用来释放一块静态内存(栈区)
- 用来释放动态申请的内存(new申请的堆区)
- 允许释放一个空指针,不会出错
- 释放一块已经释放的内存是错误的
- 虽然const对象的值不能被改变,但是可以使用一个const动态对象
br
六、内存泄漏问题
- 当我们使用new申请一块动态内存后,如果没有delete掉内存,那么就会造成内存泄漏
案例:
- 定义一个factory函数,返回一个指向与Foo类型的动态内存指针
- Foo* factory(T arg)
- {
- ...
- return new Foo(arg);
- }
- 错误使用:下面的函数,调用了factory函数申请了一块动态内存,但是函数结束之后,没有释放p所指向的内存,于是就造成了内存的浪费
- void use_factory(T arg)
- {
- Foo *p=factory(arg);
- }
- 正确使用:下面对use_factory函数进行了改造,在函数的最后delete掉了p所指向的动态内存,这样就不会导致内存的泄漏了
- void use_factory(Foo arg)
- {
- Foo *p=factoyr(arg);
- ...
- delete p;
- }
七、delete指针之后的置空问题
- 规则:当我们释放一个指针之后,该指针指向的是一个不确定的内存空间。因此,当释放指针之后,建议将指针值为空,来指示该指针不指向任何对象了
- int *p=new int(30); //申请
- ......
- delete p; //释放
- p=nullptr; //置位空
- 实际上,作为初级程序员容易犯这样的错误,认为只要delete之后,
- 自己就不负责任了(实际上你这种行为和渣男有什么区别,明明已经
- 和你的前女友分手了,你还留着人家的各种信息和人家藕断丝连,
- 偶尔还用保存着前女友的某一个把柄来威胁前女友。)
- 我曾经犯过这样错误,导致游戏服务器的一个全球跨服战的宕机,原
- 因就是我在delete之后,没有将指针指向的内容没有置为NULL,导致我后
- 面又对指针指向的成员进行非法访问,宕机,我半夜两天起来远程连接公
- 司电脑修BUG,到现在记忆犹新,心脏瞬间不好了。
八、多个指针同指一块内存的使用
特点
- ①多个指针指向同一内存时,释放其中一个指针,其他指针均变为无效
- ②将一个指针值为空,只与该指针有关,与其他指针无关
br
九、shared_ptr与new的使用
使用规则
- ①我们可以使用将shared_ptr类对象指向一个new所申请的动态内存
- ②new申请的动态内存的使用、释放等规则仍然符合shared_ptr类的使用规则
使用语法
- 因为智能指针的构造函数是explicit的。因此:我们不能将一个内置指针隐式地转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针
- shared_ptr<int> p=new int(1024); //错误
- shared_ptr<int> p2(new int(1024)); //正确:使用直接初始化
- 动态内存作为返回值时的使用手法:限于上面的使用语法:一个返回shared_ptr的函数不能在其返回语句中隐式转换为一个普通指针
- shared_ptr<int> clone(int p)
- {
- return new int(p); //错误
- }
- shared_ptr<int> clone(int p)
- {
- return shared_ptr<int>(new int(p)); //正确
- }
十、new传参时与shared_ptr的关系
- 当一个函数的参数是shared_ptr类时,有以下规则:
- 函数的调用是传值调用
- 调用函数时,该shared_ptr类所指向的对象引用计数加1
- 但是函数调用完成之后,shared_ptr类自动释放,对象的引用计数又减1
- void process(shared_ptr<int> ptr){ ... }
- shared_ptr<int> p(new int(42)); //初始化一个智能指针对象p
- process(p); //p所指的对象引用计数加1
- //process函数调用之后,p所指的引用计数减1
- int i=*p; //正确
函数参数使用时与new的关系
- 因为shared_ptr类会在生存周期结束之后,将引用计数减1,当引用计数为0时,会释放内存空间
- 下面是一个特殊的应用场景,需要注意
- void process(shared_ptr<int> ptr){ ... }
- int *x(new int(1024));
- process(x); //错误,不能将int*转换为一个shared_ptr<int>
- process(shared_ptr<int>(x)); //合法的,但是process函数返回之后内存会被释放
- int j = *x; //错误,x所指的内存已经被释放了
十一、异常处理
- 当程序发生异常时,我们可以捕获异常来将资源被正确的释放
- 但是如果没有对异常进行处理,则有以下规则:
- shared_ptr的异常处理:如果程序发生异常,并且过早的结束了,那么智能指针也能确保在内存不再需要时将其释放
- new的异常处理:如果释放内存在异常终止之后,那么就造成内存浪费
br
十二: 动态数组的初始化
- 默认情况下,new分配的对象都有默认初始化
br
十三、动态数组的一些注意事项
- ①因为new申请的数组是动态地,因此不能使用begin()和end()函数对动态数组进行操作
- ②不能使用for each对数组进行遍历,但是可以使用for循环操作动态数组
- int *p1 = new int[3]{1,2,3}; //动态数组
- int p2[3]= { 1,2,3 }; //普通数组
- for (int i = 0; i < 3; i++) //正确
- cout <<p1[i] << endl;
- for each (int var in p2)//错误
- {
- }
十四、动态分配一个空数组是合法的
- 动态分配一个数组是合法的,new返回一个合法的非空指针,此指针保证与new返回的其它任何指针都不相同。但是不能对此指针解引用
- 普通数组定义一个空数组是不允许的
- int arr[0]; //错误
- int *p = new int[0]; //正确
十五、动态数组的释放
- 使用delete释放,并且在数组名前需要加上“[]”
- 释放的顺序:数组中的元素按照逆序销毁,即最后一个元素首先被销毁,然后倒数第二个....以此类推
- typedef int arrT[10];
- int *a=new arrT;
- delete []a;
- 实际上我要解释下:
- delete a;
- 仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数
- 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不
- 能释放 从而造成内存泄漏。
- delete [] a;
- 调用使用类对象的析构函数释放用户自己分配内存空间并且释放了a指针
- 指向的全部内存空间所以总结下就是,如果ptr代表一个用new申请的内
- 存返回的内存空间地址,即所谓的指针,那么:
- delete ptr 代表用来释放内存,且只用来释放ptr指向的内存。
delete[] rg 用来释放rg指向的内存,!!还逐一调用数组中每个对象 - 的destructor 对于像int/char/long/int*/struct等等简单数据类型,
- 由于对象没有destructor,所以用delete 和delete [] 是一样的!
- 但是如果是C++对象数组就不同了!我将会在接下来的第28节说到这块