🌞引入—从C语言malloc、free到C++new、delete
各位请先看下面这段代码:
void test1()//C语言动态管理空间 { int* p = NULL; p = (int*)malloc(sizeof(int)); *p = 100; printf("%d\n", *p); free(p); } void test2()//C++动态管理空间 { int* p = NULL; p = new int(100); printf("%d\n", *p); delete p; }
这两段代码实现的功能是一样的。通过观察以及比对,我们很容易就能发现malloc和free分别对应着new和delete。乍一看,malloc、free和new、delete好像没什么区别?没错,他们的功能都是一样的,都是用来动态管理空间的。但是,如果细细观察,你会发现他们区别可大了!
注意:
1、new不需要强制类型转换。
2、new可以在开辟空间时,可以同时初始化空间内容。
\(^o^)/~ok,基本的引入就到这吧,现在我们进入正题,对于new、delete详解~
🌔 一、new和delete语法定义
new的语法定义
//动态分配一个空间时 指针 = new 指针对应的类型; //例1 int* ptr = new int;//在堆上分配一个整数的内存,并将其地址存储在指针ptr中 //申请多个空间时或者用于动态分配数组的内存时 指针 = new 指针对应的类型[申请的大小]; //例2 int* arr = new int[5];//在堆上分配一个包含5个整数的数组,并将其起始地址存储在指针arr中
delete的语法定义
//当只开辟了一个空间时 delete 所要释放开间的指针 //例1 delete ptr;//释放空间 //申请多个空间时或者用于动态分配数组的内存时 delete 所要释放开间的指针 delete[] 所要释放空间的指针 //此释放该指针开辟的所有空间 //例2 delete arr;//可能只释放首个空间->不同编译器所为不同 delete[] arr;//全部释放
特别注意:如果new和delete应当采用相同形式,详见本文末。
new的初始化
//动态分配一个空间时 指针 = new 指针对应的类型(对应类型数值); //例1 int* ptr = new int(100)//初始化空间值为100 //申请多个空间时或者用于动态分配数组的内存时 指针 = new 指针对应的类型[申请的大小]{数值(用,隔空)}; //例2 int* arr = new int[5]{1,2,3,4,5};//初始化5个空间值依次为1,2,3,4,5
栗子:
void test2()//C++动态管理空间 { int* p = NULL; p = new int(100); printf("%d\n", *p); delete p; int* q = NULL; int* z = NULL; q = new int[5] {10, 20, 30, 40, 50}; z = new int[5] {0}; for (int i = 0; i < 5; i++) { cout << q[i] << " "; } cout << endl; for (int i = 0; i < 5; i++) { cout << z[i] << " "; } delete[]z; delete[]q; }
🌕二、给类对象申请空间(为什么说C++中要用new和delete)
用malloc和用new给类申请空间的区别
请看下面这段代码~
class A { public: int num; public: A() { num = 200; cout << "构造函数" << endl; } ~A() { cout << "析构函数" << endl; } }; void test() { A* p = (A*)malloc(sizeof(A)); p->num = 100; cout << "malloc:num=" << p->num << endl; free(p); cout << endl; A* q = new A; cout << "new:num=" << q->num << endl; delete q; }
以下为该段代码的结果:
很明显的能看到,如果我们使用malloc开辟空间以及free来释放空间,类中最经典的构造函数和析构函数都是没有被调用的!然而,使用new来申请空间 如果申请成功 就会自动调用 对应类的构造函数,在用delete释放空间时会自动调用析构函数!因此,在C++中,我们使用new和delete会比malloc和free安全的多!
new申请对象数组
请看下面这段代码~
class A { public: int num; public: A() { num = 100; cout << "无参构造函数num="<<num << endl; } A(int n) { num = n; cout << "有参构造函数num="<<num << endl; } ~A() { cout << "析构函数num="<<num << endl; } }; void test() { cout << "无参:" << endl; A* arr1 = new A[5]; delete[]arr1; cout << endl; cout << "有参:" << endl; A* arr2 = new A[5]{A(1),A(2),A(3),A(4),A(5)}; delete[] arr2; }
以下为该段代码的结果:
以上的代码分别为利用无参构造函数和有参构造函数构造的对象数组,从以上例子我们也可总结出以下几点要点:
1、类对象数组本质是数组 只是数组的每个元素是类的对象。
2、如果想让对象数组中的元素调用有参构造 必须人为使用 有参构造初始化。
3、初始化的元素 调用有参构造 没有初始化的 调用无参构造。
4、当创建一个对象数组的时候, 必须对数组中的每一个对象调用构造函数, 除了在栈
上可以聚合初始化, 必须提供一个默认的构造函数。
这里建议回顾回顾类的知识: 构造函数与析构函数 (这是链接,快点!)
在对象数组中:
每个元素自动调用构造和析构函数,而他的构造顺序和析构顺序入下图所示:
构造按照入上文例子中有参构造,从左往右,也就是说谁先定义谁先构造,而析构则是相反,谁最后定义谁最先析构!
🌖三、一些注意事项
delete void*可能会出错
如果对一个 void*指针执行 delete 操作, 这将可能成为一个程序错误, 除非指针指
向的内容是非常简单的, 因为它将不执行析构函数.以下代码未调用析构函数, 导致可用内存减少。所以尽量不要用delete释放void *!
栗子:
class Person { public: Person(char* name, int age) { pName = (char*)malloc(sizeof(name)); strcpy(pName, name); mAge = age; } ~Person() { if (pName != NULL) { delete pName; } } public: char* pName; int mAge; }; void test() { char arr[] = "john"; void* A = new Person(arr, 22); delete A; }
特别注意:malloc、free和new、delete 不可以混搭使用!
使用 new 和 delete 采用相同形式
请仔细看下面这段代码~
Person* person = new Person[10]; delete person;
以上代码有什么问题吗?
这里使用了两个编译器来对该代码进行运行,分别出现了以下错误:vs 下直接中断、 qt 下析构函数调用一次。
使用了 new 也搭配使用了 delete, 问题在于 Person 有 10 个对象, 那么其他 9 个对象可能没有调用析构函数, 也就是说其他 9 个对象可能删除不完全, 因为它们的析构函数没有被调用。 我们现在清楚使用 new 的时候发生了两件事: 一、 分配内存; 二、 调用构造函数, 那么调用 delete 的时候也有两件事: 一、 析构函数; 二、 释放内存。 那么刚才我们那段代码最大的问题在于: person 指针指向的内存中到底有多少个对象, 因为这个决定应该有多少个析构函数应该被调用。 换句话说, person指针指向的是一个单一的对象还是一个数组对象, 由于单一对象和数组对象的内存布局是不同的。 更明确的说, 数组所用的内存通常还包括“数组大小记录”, 使delete 的时候知道应该调用几次析构函数。 单一对象的话就没有这个记录。
单一对象和数组对象的内存布局可理解为下图:
本图只是为了说明, 编译器不一定如此实现, 但是很多编译器是这样做的。 当我们使用一个 delete 的时候, 我们必须让 delete 知道指针指向的内存空间中是否存在一个“数组大小记录”的办法就是我们告诉它。 当我们使用 delete[ ], 那么 delete就知道是一个对象数组, 从而清楚应该调用几次析构函数。 结论: 如果在 new 表达式中使用[ ], 必须在相应delete 表达式中也使用[ ].如果在 new 表达式中不使用[], 一定不要在相应的 delete 表达式
中使用 [ ]。
感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o!