目录
一、内存的分布:
内存主要分为三个部分:
二、普通的动态内存申请
c语言的动态内存管理。
void *malloc( size_t size );
可以看到申请出来的值未初始化,都是随机值
calloc(): void *calloc( size_t num, size_t size );
在申请空间的时候会初始化,可以看到确实都为0;
void *realloc( void *memblock, size_t size );
当然,在申请空间以后,肯定要用到free函数,去将申请的空间给释放了(有始有终)
那么问题来了,既然c++是兼容c语言的,既然malloc,realloc,calloc函数已经能够做到内存的管理了,为什么c++还要用别的方法:定义new 和delete 运算符呢?
new 和delete运算符肯定有着别样的优点,接下来就一起来看看吧:
c++的申请方式:
1、new int(10)
后面的(10)代表着初始化为10
2、new int [10]
后面的【10】表示的是申请一个数组,大小为10
当然,这种形式也可以进行初始化,方式是在后面加个{1,2,3,4,5。。。}
了解了new和delete的简单使用方法之后,
我们都知道c++和类是紧密联系的,我们一起来看下类中的内存管理:
看看new和delete在类中有什么优点
三、在类中的内存管理
1、malloc型在类中:
静态分配的时候,可以看到,可以自动调用构造函数和析构函数。
那如果动态分配呢?
我们可以发现,在动态分配对象的时候,并没有自动调用构造和析构(静静的来,静静的走)
在使用c语言的方法去动态开辟的时候,仅仅是把对象所需占用的空间去申请出来了,但和构造、析构没有关系
这时候申请出来的对象自然也就没有初始化,是一堆随机值
形式如下:
这时候要是想要去初始化对象成员就要去额外定义函数:
void InitTest()
{
m_data = 0;
ptr = (int*)malloc(sizeof(int) * 10);
assert(ptr != NULL);
}
void DestroyTest()
{
free(ptr);
}
使用这一套流程,可以看到是很复杂的
那么我们再来看看new在类中的表现:
2、new型在类中的表现
可以看到,使用new的时候,不仅能够开辟空间,还会自动去调用构造函数去初始化对象。(这就体现了方便性)
过程如图:
1、对于new函数: 先申请对象空间,在调用构造函数去进行对象的初始化
2、对于delete,那就是先调用析构函数摧毁对象,在释放对象空间0
四、异常不安全问题
异常不安全的产生
之前在运算符重载的篇章中学习了对于string这种对象成员带有指针的情况,拷贝,赋值函数都要深层次的进行
在这里时,我们有新的要注意的地方
对于赋值函数之前的写法如下:
但实际上这样写是有一定不妥的 (异常不安全)
假如s占据的空间非常大,那么在赋给s2的时候,对于new来说,有可能会申请空间失败
那么这个时候就会发生问题了:因为在执行new操作之前,就已经把把m_data的空间给delete了,
那么一旦申请空间失败,不仅不能进行赋值,就连原来的数据也没有了。这就会造成异常不安全(指发生异常的时候,
程序运行不安全)
我们想要的是假如new申请不了空间的时候,能够将原本s2中的数据不变,即做到“能够回去”。
有两种办法可以解决这个问题:
解决异常不安全:
1、先申请成功空间再进行delete
先申请一个new_data空间,申请成功以后再将m_data空间给释放掉:
2、先创建一个临时对象,再交换临时对象和原来对象的值
代码示意图如下:
在代码中我们发现没有用delete去操作s2的空间,那是不是忘记了呢?
不是的,高明之处就在这了:因为tmp是一个对象,当脱离他的生成作业域的时候,便会自动调用析构函数,释放他所指向的空间,而这时候他指向的正好是原来s2中m_data的空间,所以就省去写了delete这一步
这里有个重要的思想:借助临时变量去释放原有的空间
其实赋值函数中的交换也可以使用系统提供的交换函数swap去实现
这样的话就会保证异常安全性,因为在实例化对象的时候肯定会申请空间,如果没有空间的话就会实例化失败
那么就没有接下来的操作了,也就不会造成不安全了。
new 和 delete的三种形式
1、 new operator new操作符
平时所使用的new就是new操作符,使用的时候会执行两步操作:
1、申请空间
2、调用构造函数去初始化对象
这两个工作必须一全部都执行
2、operator new 操作符new
//操作符new,只能申请空间,不用调用构造函数
void* operator new(size_t sz)
{
void* ptr = malloc(sz);
return ptr;
}
可以发现,当调用的时候,并没有自动调用构造函数,只是开辟了空间
3、palcement new 定位new (重要)
先来看看定位new的解释:
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
再来看看下面这个问题
我们来看下这段代码,因为使用的是malloc,所以对象只是有了空间,但值还是随机值,
这时候要进行*ps=s的时候,就会引发空间的异常,
那要怎样实现呢?
可以使用定位new
这里的new并没有去开辟新的空间,而是调用了拷贝构造函数,构造完成之后,把所构造的对象放到了ps所指的对象空间
定位new在数组中
这样使用的时候可以看到ar【0】被赋值为了8,但这样去使用的话永远只能把数组0下标定位,
这不是真正的定位,要想真正实现定位肯定离不开c++中的重要方法--------》重载
这时候写重载的时候多了个参数 pos相当于把位置传了进去,如果不单独定义的话就相当于默认为了0位置