技术交流QQ群:1027579432,欢迎你的加入!
1.Cpp中的内存分配
- 了解动态内存在C++中是如何工作的是成为一名合格的C++程序员必不可少的。C++程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
- 很多时候,在无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。在C++中,可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即new运算符。如果不再需要动态分配的内存空间,可以使用delete运算符,删除之前由new运算符分配的内存。
2.new和delete运算符
- 下面是使用new运算符来为任意的数据类型动态分配内存的通用语法:
new 数据类型;
- 上面的数据类型可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。先来看下内置的数据类型。例如,可以定义一个指向double类型的指针,然后请求内存,该内存在执行时被分配。可以按照下面的语句使用new运算符来完成这点:
double *ptr = NULL; // 初始化为null的指针 ptr = new double; // 为变量请求内存
- 如果自由存储区已被用完,可能无法成功分配内存。所以建议检查new运算符是否返回NULL指针,并采取以下适当的操作:
double *ptr = NULL; if(!(ptr == new double){ cout << "Error: out of memory.\n"; exit(1); }
- malloc()函数在C语言中就出现了,在C++中仍然存在,但建议尽量不要使用malloc()函数。new与malloc()函数相比,主要的优点是:new不只是分配了内存,还创建了对象!在任何时候,当觉得某个已经动态分配内存的变量不再需要使用时,可以使用delete操作符释放它所占用的内存,如下所示:
delete ptr; // 释放ptr所指向的内存
- 下面的实例使用了上面的概念,演示了如何使用new和delete的运算符
#include "iostream" using namespace std; int main(){ double *ptr = NULL; // 初始化为null的指针 ptr = new double; // 为变量请求内存 *ptr = 3.14; // 在分配的地址存储值 cout << "*ptr = " << *ptr << endl; delete ptr; // 释放内存 return 0; }
3.数组的动态内存分配
- 假设要为一个字符数组(一个有20个字符的字符串)分配内存,可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char *ptr = NULL; // 初始化为 null 的指针 ptr = new char[20]; // 为变量请求内存
- 删除刚创建的数组,语句如下:
delete [] ptr; // 删除 pvalue 所指向的数组
- 下面是new操作符的通用语法,可以为***数组分配内存,如下所示:
// 一维数组如下 int *array = new int [m]; // 动态分配,数组长度为 m delete [] array; //释放内存 ------------------------------------------------------------------------------- // 二维数组如下 int **array; // 假定数组第一维长度为m, 第二维长度为n // 动态分配空间 array = new int *[m]; for(int i = 0; i < m; i++) array[i] = new int [n]; // 释放 for(int i = 0; i < m; i++) delete [] array[i]; delete [] array;
- 二维数组实例测试
// 二维数组 int **p; int i, j; // p[4][8] // 开始分配4行8列的二维数组数据 p = new int *[4]; for(i = 0; i < 4; i++) p[i] = new int [8]; for(i = 0; i < 4; i++){ for(j = 0; j < 8; j++) p[i][j] = j * i; } // 打印数据 for(i = 0; i < 4; i++){ for(j = 0; j < 8; j++){ if(j == 0) cout << endl; cout << p[i][j] << "\t"; } } cout << endl; // 开始释放申请的堆 for(i = 0; i < 4; i++) delete [] p[i]; delete p; cout << "-------------------------------\n";
- 三维数组测试实例
// 三维数组 int i1, j1, k1; //p[2][3][4] int ***pt; pt = new int **[2]; for(i1 =0; i1 < 2; i1++){ pt[i1] = new int *[3]; for(j1 = 0;j1<3;j1++) pt[i1][j1] = new int[4]; } // 输出pt[i][j][k]三维数据 for(i1 = 0;i1 <2;i1++){ for(j1=0;j1 <3;j1++){ for(k1=0; k1 < 4; k1++){ pt[i1][j1][k1] = i1 + j1 + k1; cout << pt[i1][j1][k1] << " "; } cout << endl; } cout << endl; } // 释放内存 for(i1 = 0; i1 < 2; i1++){ for(j1 = 0; j1 < 3; j1++) delete [] pt[i1][j1]; } for(i1 = 0; i1 < 2; i1++) delete [] pt[i1]; delete [] pt; cout << "-------------------------------\n";
4.对象的动态内存分配
- 对象与简单的数据类型没有什么不同,例如,请看下面的代码,将使用一个对象数组来理清这一概念,如果要为一个包含4个Box对象的数组分配内存,构造函数将被调用4次.同样地,当删除这些对象时,析构函数也将被调用相同的次数(4次)。
// 对象的动态内存分配 class Box{ public: Box(){ cout << "调用构造函数\n"; } ~Box(){ cout << "调用析构函数\n"; } }; Box *boxarray = new Box[4]; delete [] boxarray; // 删除数组
5.动态内存分配中的细节知识点
- delete与delete []区别
- 针对简单类型,使用new分配后的不管是数组还是非数组形式内存空间用两种方式均可,此种情况中的释放效果相同。原因在于:分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理。在析构时,系统并不会调用析构函数,它直接通过指针可以获取实际分配的内存空间,哪怕是一个数组内存空间(在分配过程中 系统会记录分配内存的大小等信息,此信息保存在结构体_CrtMemBlockHeader中,具体情况可参看VC安装目录下CRT\SRC\DBGDEL.cpp)。如:
int *a = new int a[10]; delete a; delete [] a;
- 针对类class,两种方式体现出具体差异
- delete a: 仅释放了a指针指向的全部内存空间,但是只调用了a[0]对象的析构函数,剩下的从a[1]到a[9]这9个用户自行分配的m_buffer对应内存空间将不能释放,从而造成内存泄漏。
- delete [] a: 调用使用类对象的析构函数释放用户自己分配内存空间并且释放了a指针指向的全部内存空间。
class A{ private: char *m_buffer; int m_len; public: A(){ m_buffer = new char[m_len]; } ~A(){ delete [] m_buffer; } }; A *a = new A[10]; delete a; delete [] a;
- 总结:如果ptr代表一个用new申请的内存返回的内存空间地址,即所谓的指针,则:
- delete ptr: 代表用来释放内存,且只用来释放ptr指向的内存
- delete [] ptr: 用来释放ptr指向的内存,还逐一调用数组中每个对象的destructor!!
- 对于像int/char/long/int*/struct等简单数据类型,由于对象没有destructor,所以用delete和delete []是一样的!但是如果是C++对象数组就不同了!
- 针对简单类型,使用new分配后的不管是数组还是非数组形式内存空间用两种方式均可,此种情况中的释放效果相同。原因在于:分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理。在析构时,系统并不会调用析构函数,它直接通过指针可以获取实际分配的内存空间,哪怕是一个数组内存空间(在分配过程中 系统会记录分配内存的大小等信息,此信息保存在结构体_CrtMemBlockHeader中,具体情况可参看VC安装目录下CRT\SRC\DBGDEL.cpp)。如:
- new和malloc内部的实现方式上的区别
- new的功能是在堆区新建一个对象,并返回该对象的指针。所谓的新建对象的意思就是,将调用该类的构造函数,因为如果不构造的话,就不能称之为一个对象;而malloc只是机械的分配一块内存,如果用mallco在堆区创建一个对象的话,是不会调用构造函数的。严格来说,用malloc不能算是新建了一个对象,只能说是分配了一块与该类对象匹配的内存而已,然后强行把它解释为这是一个对象,按这个逻辑来,也不存在构造函数什么事。
- 用delete去释放一个堆区的对象,会调用该对象的析构函数;用free去释放一个堆区的对象,不会调用该对象的析构函数
// new和malloc的区别 class TEST{ private: int num1; int num2; public: TEST(){ num1 = 10; num2 = 20; } void Print(){ cout << num1 << " " << num2 << endl; } }; /* 用malloc()函数在堆区分配一块内存空间,然后对该块内存空间进行强制类型(TEST*)转换 解释为是一个TEST类对象,这不会调用TEST的默认构造函数 */ TEST *t_obj1 = (TEST *)malloc(sizeof(TEST)); t_obj1->Print(); // 用new在堆区创建一个TEST类的对象,这会调用TEST类的默认构造函数 TEST *t_obj2 = new TEST; t_obj2->Print();