在之前C语言的学习中,我们学过了C是如何进行动态内存管理的,也简单的了解过C/C++程序的内存开辟。
这篇文章呢,我们重点来学习一下C++的内存管理方式。
1. C/C++内存分布
C/C++的内存分布我们在之前也是了解过的,那我们这里再简单复习一下。
我们先来看下面的一段代码和一些相关的问题:
int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }
问:
我们来分析一下:
首先globalVar是一个全局变量,staticGlobalVar是一个静态全局变量,staticVar是静态局部变量,都在静态区(数据段)。
然后localVar是个局部变量,num1是个整型数组,那它们是在栈上的。
接着看
char2应该在哪?🆗,这里涉及到我们之前C语言讲过的一个关于常量字符串的知识,我们先来复习一下:
相信现在大家就知道了,char2这个字符数组还是在栈上的,只是拿代码段(常量区)的一个常量字符串去初始化它了,然后*char2,char2是数组首元素地址,那*char2就是数组首元素,还是在栈上。
再看pChar3是一个局部指针变量,在栈上,但是pChar3指向常量区的一个常量字符串,所以*pChar3是在常量区。
然后ptr1还是局部指针变量,在栈上,ptr1指向的空间是malloc出来的,在堆上。
所以,答案是这样的:
再看几个填空题:
sizeof(num1),数组名放到sizeof里面代表整个数组,num1是10个元素的整型数组,所以答案是40;sizeof(char2),char2里面有5个字符(字符串隐藏结束标志\0),所以是5;strlen(char2)求字符串长度,是4 ;sizeof(pChar3),指针变量,大家为4或8字节;strlen(pChar3),同样求长度是4;sizeof(ptr1),指针变量,4或8 字节。
最后,再来复习一下C/C++的内存区域划分:
2. C的动态内存管理方式
那我们再来简单复习一下C语言的内存管理方式:
void Test() { int* p1 = (int*)malloc(sizeof(int)); free(p1); // 1.malloc/calloc/realloc的区别是什么? int* p2 = (int*)calloc(4, sizeof(int)); int* p3 = (int*)realloc(p2, sizeof(int) * 10); // 这里需要free(p2)吗? free(p3); }
大家回忆一下,malloc和calloc的区别是什么?
它们都可以在堆上开辟内存空间,除了传参不一样之外,两者的区别在于calloc会把开辟出来的空间全部初始化为0,而malloc不会去初始化。
然后realloc是用来扩容的,有两种方式,原地扩和异地扩。
这里就不细说了,如果大家遗忘了,可以看一下之前的文章链接: link
3. C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:
通过new和delete操作符进行动态内存管理。
3.1 new/delete操作内置类型
在C语言中:
我们使用
malloc/calloc
去申请空间,是不是需要自己计算需要开辟空间的大小,然后传参,返回值呢是void*
,还需要我们自己强转。
int* pp1 = (int*)malloc(sizeof(int));
那在C++中,我们就可以这样:
int* p1 = new int;
直接用一个操作符叫做
new
,我们也不需要sizeof
计算大小,直接给类型就行了,而且也不需要强制类型转换。
如果要动态申请10个整型大小的空间:
直接这样就可以了。🆗,那大家思考一下:
C++搞出来这样新的动态内存管理的方式,仅仅是为了用起来比C语言方便,简洁一点吗?
那他会不会进行一些优化,比如,C++里这样搞会对空间进行初始化?
好的,并没有初始化。
那这样看的话,C++搞出new这些东西和C语言的malloc这些对于内置类型的操作好像除了用法之外也没有什么很大的区别。
那所以呢?
C++搞出这些东西更多的是为了自定义类型,那new和delete操作自定义类型我们后面也会专门讲解,先不急。
那另外:
我们
malloc
的时候由于可能会开辟失败的缘故,所以我们一般malloc
之后会进行一个检查,如果返回的是空指针,就代表开辟失败。那我们的
new
有没有可能失败呢?当然也是有可能的,但是
new
失败不是返回空指针,而是抛异常,那关于异常我们后面也会讲到。
然后呢:
我们看到上面对于内置类型
new
出来的空间并没有被初始化,但是C++其实有方法去对new
出来的空间进行初始化。
怎么做呢?
直接在后面加圆括号然后放上我们要初始化的值就行了
要注意与这样写的区别:
那对于我们使用
new
动态开辟的数组,我们可以初始化吗?也是可以的:
直接在后面跟大括号进行初始化。
那除了new这个操作符之外呢,我们再来学一个操作符叫做delete:
我们C语言阶段使用malloc/calloc在堆上开辟出来的空间使用完是不是要使用free释放啊。
那如果是我们new出来的空间呢,我们说new和delete操作符也是进行动态内存管理的,所以new出来的空间也是堆上的,那new出来的空间使用完我们要怎么释放呢?
🆗,用一个操作符叫做delete:
不用加括号,因为我们今天学的new和delete是操作符,而malloc/calloc是库里面的函数。
那使用new动态开辟的数组怎么销毁呢?
注意delete后面加一个方括号。
那大家可能会想:
既然都是申请空间和释放空间,那可以不可以混起来用呢?
就是malloc的空间可不可以不 free ,而使用delete ,new 出来的空间去free,或者是其它方式的混用。
🆗,那想告诉大家的是:
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]
注意:匹配起来使用。
我们不要去不匹配的用,不匹配的话,有些情况可能没事,但是有些情况下可能就出错了。
至于原因我们后面也会浅浅的给大家解释一下。
3.2 new和delete操作自定义类型
那我们上面提到:C++搞出new和delete这些东西更多的是为了自定义类型,那接下来我们就来看一下new和delete操作自定义类型有什么特别的地方。
那现在有这样一个自定义类型类A:
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; };
那大家看:
这两种写法有什么区别吗?
我们看到用malloc
呢就只是开辟了空间。
但是用new
呢?
除了开辟空间还自动调用了构造函数进行初始化。
free只是释放了空间;
delete除了释放指针指向的空间还会调用析构函数对自定义类型进行析构。
当然:
如果对应的构造函数有参数,我们new
的同时也可以传参:
所以:
在申请和释放自定义类型的空间时,new会自动调用构造函数,delete会自动调用析构函数,而malloc与free不会。