【动态内存2】:C++中的内存开辟与释放&&底层实现

new与delete是用于开辟内存与释放内存,用于堆内存管理的两个关键字(标识符),与C语言中的malloc/free对应,new与delete相较于malloc和free实现机制更为复杂,new申请空间时还会调用构造函数,delete释放空间时还会析构函数。

注意:new/delete ;malloc 与free都要成对使用哦!!!以免内存泄漏

一、new的用法

   new是关键字,返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的。

(一)开辟单个变量

1、开辟方式 

  • 指针变量名 = new + 类型标识符 ; int * p = new int // 开辟一个存放整数的存储空间,返回一个指向该存储空间的地址。
  • 指针变量名  = new + 类型标识符 (做初始化的值);int *q = new int(5) //开辟一个存放整数的存储空间,并初始化赋值为5;

 2、简单应用:

int main()
{
    int *p = new int;   //未初始化
    cout<<"*p未作初始化为:"<< *p <<endl;

    int *q = new int(5); //初始化为5
    cout<<"*q初始化为:"<< *q <<endl;
    return 0;
}

运行结果:

【动态内存2】:C++中的内存开辟与释放&&底层实现

(二)开辟数组

1、开辟方法:

  • 指针变量名 = new +类型标识符[开辟的数组个数] ;int *p = new int [6] // 开辟一个包含6 个单元格的int 型的数组,并且指针指向的是第一个int;
  • 指针变量名  = new +类型标识符[开辟的数组个数]()小括号表示做0初始化
  1. int *q = new  int[20]() //20个值初始化为0的int
  2. int *q = new int[20] //20个未初始化的int
  3. string *pp = new string[10] //10个空string
  4. string *pp = new string[10]() //10个空string,字符类不能用0做初始化,因此仍然表现为未初始化

 简单应用:

int main()

{

    int *p = new int[5]; //未作初始化,为随机值
    for(int i = 0;i<5;i++)
    {
        cout<<"  "<<p[i];
    }
    cout<<endl;
    cout<<"------------------------------------------"<<endl;
    int *q = new int[5](); //初始化为0
    for(int i = 0;i<5;i++)
    {
        cout<<"   " <<q[i];
    }
    return 0;
}

运行结果:

【动态内存2】:C++中的内存开辟与释放&&底层实现

(三)开辟失败时,则会抛出异常。

(四)可以开辟常量内存块:

const int *p = new const int(10); //一定要做初始化,否则开辟的空间就没有意义

delete p;

const int *p2 = new const int[10]();

delete [ ] p3;

二、delete的用法

(1)释放单个变量内存块

int *p = new int(5);

delete p; //释放单个空间

delete [ ] p; //错误 ,释放单个变量内存时,不用加 [ ];

(2)释放数组

int *q = new int[5]();

delete [] q ;//释放数组时,空括号必须有。

(3)别名定义

typedef int arrT[20]; //arrT是一个包含20个ine的数组的类型别名

int *p = new arrT;//分配一个包含20个int型数据的数组,p指向第一个元素

delete [ ] p; // 方括号必须加,因为我们当初分配的是一个数组

      delete释放数组时,空方括号是必须的:它指示编译器此指针指向一个对象数组的第一个元素。如果我们在delete一个指向数组的指针时忽略了方括号或者是在delete一个指向单一对象的指针时使用了方括号,其行为是未定义的。

 

三、二维数组的开辟与释放

  开辟:  

    int **p = new int *[10]; // 相当于int *p = new int[10][10];
    for(int i = 0;i < 10; i++)
    {
        p[i] = new int [10];
    }

释放:
    for(int i =0;i< 10;i++)
    {
        delete [ ]p;
    }

 四、delete与free的底层实现

class B
{
public:
	B()
	{
		cout << "构造函数" <<endl;
	}


	~B()
	{
		cout << "析构函数" <<endl;
	}


	void *operator new(size_t)
	{
		B *t =(B*)malloc(sizeof(B));
		cout << "内存开辟" <<endl;
		return t;
	}

	void operator delete(void *p)
	{
		free(p);
		cout << "内存释放"<< endl;
		return ;
	}

};
int main()
{
	B * t = new B();//开辟空间时,先调用new运算符的重载函数,开辟内存,再调用构造函数
	delete t;//释放内存时,先调用析构函数,再调用delete的重载函数
	return 0;
}

运行结果:

【动态内存2】:C++中的内存开辟与释放&&底层实现

【动态内存2】:C++中的内存开辟与释放&&底层实现

因为delete释放内存时,要调用析构函数,所以delete在释放内存时,先开辟的后释放。

TIPS:

free怎么知道释放多少内存的???

与free怎么知道要释放多少内存问题优点类似(详细看上篇博客:【动态内存1】),在申请内存的时候,申请到的地址会比你实际的地址大一点点,它包含了一个存有申请空间大小的结构体。知道了开辟内存的大小,于是就可以准确释放了。(这点不是很正确,看到有误的可以补充或纠正,谢谢啦!)