C++内存管理方式
C语言内存管理方式(malloc, realloc, calloc, free等函数)在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦
我们先来看一段代码, 创建自定义对象:
class A{
public:
// 无参构造和全缺省的构造都是默认构造
A(int a = 1, int b = 2, int c = 3)
//A(int a, int b, int c)
: _a(a)
, _b(b)
, _c(c){
cout << "A(int, int, int)" << endl;
}
private:
int _a;
int _b;
int _c;
};
void test2(){
// 第一种
A* pa = (A*)malloc(sizeof(A));// 堆上 值都为随机值
// 第二种
A obj(1, 2, 3);// 栈上 掉用了构造函数
// pa->_a = 1; // 不行
// pa(4, 5 , 6);// 也不行
// A* pa = (A*)malloc(sizeof(A))(4, 5, 6);// 也不行
第一种:
- 对于第一种用malloc创建对象来说, 他只是在堆上为对象开辟了空间, 而里面的内容并没有初始化, 也没有调构造函数来创建对象.
- 当类中的成员变量为private时, 不能对成员变量进行赋值, 也不能调用构造函数, 相当于创建的这个对象是一个不能使用的对象
第二种
- 对于第二种调构造函数创建对象来说, 却是在栈上创建的对象, 并且完成了初始化
那么有没有一种方式既可以在堆上创建对象, 也可以调构造函数呢??? 所以C++给了新的操作符来完成这一任务.
1 new操作
我们先来看看这种新的操作符堆上申请内存空间的使用方式:
单个空间: 相当于申请一个值的空间
- new + 数据类型(初始值)
连续空间 : 相当于申请一个数组的空间
- new + 数据类型[元素个数]
void Test(){
// 在堆上动态申请一个int类型的空间
int* p = new int;
// 在堆上动态申请一个int类型的空间并初始化为2
int* p2 = new int(2);
// 在堆上动态申请10个连续的int类型的整形空间
int* p3 = new int[3];
// 自定义类型: new --> 申请空间 + 调用构造函数初始化
A* pa2 = new A(4, 5, 6); // 相比于前面的pa, 这里的值已经赋好了, 自动调用了构造函数
}
- 由此也可以看出, 在创建自定义类型的对象时, malloc只是申请空间, 并不会调用构造函数, 而new则是既申请了空间, 同样也调了构造函数, 还可以对对象进行初始化操作,
- new创建对象和在栈上创建对象都可以一步完成, 都可以开好空间并初始化好, 区别在于在栈上开辟空间是系统帮我们自动完成的, 而堆上需要我们手动去开空间 并且堆上也需要手动释放空间
2. delete操作
跟new对应的释放就是delete操作符, 我们来看一下delete释放
void Test(){
// 内置类型
int* p = new int;
int* p2 = new int(2);
int* p3 = new int[3];
// 自定义类型对象
A* pa2 = new A(4, 5, 6);
// 释放内置类型空间
delete p; // 释放单个元素的空间
delete p2;
delete[] p3; // 释放连续空间
// 释放自定义类型对象空间
delete pa2;
注意: new 和 delete 的使用必须一一对应,
- 申请和释放单个元素的空间,使用new和delete操作符, new —> delete
- 申请和释放连续的空间,使用new[]和delete[] , new [ ] —> delete[ ]+指针
我们都知道对象销毁会调析构函数资源, 空间还给系统, 如果是构造了n个对象, 那就会调n次析构函数来完成资源的清理, 在用new创建自定义类型的对象时, 会调构造函数; 而用delete释放new所开辟的空间时, 同样也调了析构函数来完成自定义类型对象的销毁以及清理资源, 现在再去看释放自定义类型对象时, delete就起到了既释放空间 又调用析构清理资源
A* pa2 = new A(4, 5, 6); // 申请空间 + 调构造函数初始化
delete pa2; // 释放空间 + 清理资源
当用new[]创建了n个自定义对象时, 用delete[]释放空间时, 就会调n次析构函数
class A{
public:
// 如果这里不是默认构造 new是调不了构造函数的,
A(int a = 1, int b = 2, int c = 3)
: _a(a)
, _b(b)
, _c(c){}
A(const A& A){
cout << "A(const A&)" << endl;
}
~A(){
cout << "~A()" << endl;
}
private:
int _a;
int _b;
int _c;
};
void test(){
// 栈上创建对象数组
A array[10]; 构造100个A 类型的对象,
// 堆上动态创建
cout << "heap creat: " << endl;
// 动态申请自定义类型的连续空间,需要有默认构造函数(包括无参构造和全缺省构造)
A* array2 = new A[10];
// delete array2; // 若用这个来释放, 就会导致程序崩掉, 调用不匹配, 没有与new[]对应
delete[] array2; // 销毁了10个A类型对象
}
那来个问题, 清理资源是在释放空间之前还是之后呢???
我们想一下, 空间释放了还能不能继续操作这个空间的资源呢, 答案是不行了, 因为空间释放后可能会被别人使用, 已经不属于我门的空间了, 我们也不能进行操作, 所以 我们只能在释放空间之前, 先调用析构函数 进行资源清理, 清理完之后再去释放空间, 这就是delete做的事情
从上面的new和delete操作, 我们可以明显发现, 这两个属于C++的操作符, 已经不再是函数操作了, 所以并不属于库函数, 而malloc和free是属于库函数的, 也可以发现, C++的内存管理的新方式写起来更简单一点 也不容易出错, 因为我们不需要去计算数据的大小, 也不需要做类型的转换,
new和delete只是在堆上的操作, 不管栈上的空间, 和malloc和free一样 只管堆上的空间, 所以不要用delete去释放栈上的空间, 就跟free去释放栈上的空间是一个道理,
需要注意的是动态申请自定义类型的连续空间时,必须要有默认构造函数(包括无参构造和全缺省构造); 申请一个对象的空间则没有要求 直接把参数传进去即可
A* pa = new A(10, 9, 8);// 调带参构造
A* pa1 = new A; // 调默认构造
A* pcopy = new A(*pa);// 也可以调拷贝构造
总结:
1 内置类型:
- new: 申请空间 , 也可以初始化(new 类型(初始值))
- delete: 空间释放
- new[] : 申请连续空间, 不可以初始化
- delete[]: 释放连续空间
2 自定义类型:
-
new: 申请空间 + 调用构造函数 (可以是默认构造, 也可以是带参构造, 用户指定, 如果参数列表是一对象, 则调的就是拷贝构造)
-
delete: 调用析构 + 空间释放
-
new[] : 申请连续空间 + 调用默认构造
-
delete[] : 调用析构 + 释放连续空间
-
new/delete, new []/ delete [] 配套使用
-
对于内置类型,此时delete就相当于free,不会造成内存泄漏
-
如果是自定义类型, 不使用方括号就会运行时错误
-
针对数组释放应使用delete[], delete如果没有使用[],只会调用一次析构函数,往往就会引发程序崩溃
-
在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。