C++中new和delete匹配使用过程详解

 更新时间:2023年02月14日 10:58:47   作者:[Pokemon]大猫猫  
关于 new 和 delete 的使用相信大家并不陌生,可是为什么使用 new 的时候要用 delete,使用 new[] 的时候又要用 delete[]呢?本文就来和大家详细说说

C语言的动态内存管理函数(malloc、calloc、realloc、free) 虽然可以继续在 C++ 使用,但是对于自定义类型成员而言,这些函数不会自动调用构造函数和析构函数,于是 C++ 增加了 new 和 delete 关键字

一、new和delete的使用

new 和 delete 用于在堆上申请或释放一个元素的空间,new[] 和 delete[] 用于在堆上申请或释放一块连续的空间,对于自定义类型空间的开辟,new 和 delete 还会调用构造函数和析构函数

#include <iostream>
using namespace std;
class Demo
{
public:
	Demo(int a1 = 10, int a2 = 20)
		: _a1(a1)
		, _a2(a2)
	{
		cout << "Demo()" << endl;
	}
	void print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
	~Demo()
	{
		cout << "~Demo()" << endl;
	}
private:
	int _a1;
	int _a2;
};
void printIntArr(int* arr, int len)
{
	for (int i = 0; i < len; ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
void printDemoArr(Demo* arr, int len)
{
	for (int i = 0; i < len; ++i)
	{
		arr[i].print();
	}
	cout << endl;
}
int main()
{
	//用 new 申请一个内置类型变量的空间
	int* pint1 = new int;
	cout << *pint1 << endl; //输出 -842150451
	//使用括号中的值初始化变量
	int* pint2 = new int(5);
	cout << *pint2 << endl;	//输出 5
	//用 delete 释放一个变量的空间
	delete pint1;
	delete pint2;
	//用 new 申请一个自定义类型对象的空间,申请后会自动调用构造函数
	Demo* pd1 = new Demo;	//输出 Demo()
	pd1->print();	//输出 10 20
	//自定义类型会根据括号中的参数调用对应的构造函数
	Demo* pd2 = new Demo(5, 5);	//输出 Demo()
	pd2->print();	//输出 5 5
	//用 delete 释放一个变量的空间,释放前会自动调用析构函数
	delete pd1;	//输出 ~Demo()
	delete pd2;	//输出 ~Demo()
	//对内置类型用 new[] 开辟一块连续的空间
	int* pint3 = new int[5];	//[]中表示开辟整形的个数
	printIntArr(pint3, 5);	//输出 -842150451 -842150451 -842150451 -842150451 -842150451
	//用花括号中的值初始化开辟的连续空间,未给值的为 0
	int* pint4 = new int[5]{ 1, 2, 3, 4 };	
	printIntArr(pint4, 5);	//输出 1 2 3 4 0
	//对内置类型用 delete[] 释放一块连续的空间
	delete[] pint3;
	delete[] pint4;
	//对自定义类型用 new[] 开辟一块连续的空间
	//申请后会对空间自动调用构造函数 5 次
	Demo* pd3 = new Demo[5];	//输出 5 行 Demo()
	printDemoArr(pd3, 5);	//输出 5 行 10 20
	//用花括号中的值初始化开辟的连续空间
	//花括号中如果用小括号会被认为是逗号表达式,会去调用单参的构造函数
	//调用多参构造函数应在花括号中使用花括号,未给的值根据构造函数决定
	Demo* pd4 = new Demo[5]{ (1, 2), {5}, {5, 10}};	//输出 5 行 Demo()
	printDemoArr(pd4, 5);	//输出 第一行 2 20,第二行 5 10 第三行 5 10,两行 10 20
	//对自定义类型用 delete[] 释放一块连续的空间
	//释放之前会对空间自动调用析构函数 5 次
	delete[] pd3;	//输出 5 行 ~Demo
	delete[] pd4;	//输出 5 行 ~Demo
	return 0;
}

二、operator new和operator delete函数

operator new 和 operator delete 是系统提供的全局函数,不是 new 和 delete 的运算符重载函数

  • operator new 底层是通过 malloc 函数来申请空间,当空间申请成功时直接返回,失败时抛出异常(不会返回 nullptr),operator new 函数可以像 malloc 一样使用,只是失败时的处理不同
  • operator delete 和 free 底层都是是通过 _free_dbg 函数释放空间,只不过 operator delete 会对释放前后进行一些检查
#include <iostream>
using namespace std;
int main()
{
	//operator new 和 malloc 使用方法一样
	//operator new 申请空间失败时抛异常
	int* pi = (int*)operator new(sizeof(int) * 4);
	//operator delete 和 free 使用方法一样,都会调用 _free_dbg
	//operator delete 在释放空间时会做一些检查
	operator delete(pi);
	return 0;
}

operator new[] 和 operator delete[] 也是系统提供的全局函数,内部是通过调用 operator new 和 operator delete 函数

三、new和delete的实现原理

如果是内置类型,new 和 delete 调用 operator new 和 operator delete,new[] 和 delete[] 调用 operator new[] 和 operator delete[]

如果是自定义类型:

#include <iostream>
using namespace std;
class Demo
{
public:
	Demo(int a1 = 10, int a2 = 20);
	~Demo();
private:
	int _a1;
	int _a2;
};
Demo::Demo(int a1, int a2)
	: _a1(a1)
	, _a2(a2)
{
	cout << "Demo()" << endl;
}
Demo::~Demo()
{
	cout << "~Demo()" << endl;
}
int main()
{
	Demo* pd1 = new Demo(5, 5);
	delete pd1;
	Demo* pd2 = new Demo[2]{ {1, 2}, {2, 3} };
	delete[] pd2;
	return 0;
}

new:

1. 调用 operator new 函数申请空间

2. 在申请的空间上执行构造函数,完成对象的构造

delete:

1. 在空间上执行析构函数,完成对象中资源的清理工作

2. 调用operator delete函数释放对象的空间

new 类型[N]:

1. 调用operator new[] 函数,实际上是在 operator new[] 中调用 operator new 函数完成 N 个对象空间的申请

2. 在申请的空间上执行 N 次构造函数

delete[]:

1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理

2. 调用 operator delete[] 释放空间,实际上时在 operator delete[] 中调用 operator delete 来释放空间

四、申请空间和释放空间应配套使用

malloc/free、new/delete、new[]/delete[] 需要配套使用,否则总会有出问题的时候

下述代码不会报错,会产生内存泄漏

#include <iostream>
using namespace std;
class Stack
{
public:
	Stack(int capacity = 4)
		: _a(new int[capacity])
		, _top(0)
		, _capacity(capacity)
	{
	}
	~Stack()
	{
		if (_a)
		{
			delete[] _a;
			_top = _capacity = 0;
		}
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack* ps = new Stack;
	//free(ps);	//内存泄漏
	//delete 释放内存之前会调用析构函数
	delete ps;	//正确写法
	return 0;
}

下述代码在 vs2022 下会崩溃

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << endl;
	}
	~A()
	{
		cout << "~A():" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = new A[10];
	//free(p1); 	//崩溃
	delete[] p1;	//正确写法
	A* p2 = new A[10];
	//delete p2;	 //崩溃
	delete[] p2;	//正确写法
	return 0;
}

注意:不同的编译器处理可能不同,这里只代表在 vs2022 编译器中

五、定位new表达式

定位 new 表达式是在已开辟好的原始内存空间上调用构造函数初始化一个对象,使用格式:

new(place_address)type 或者 new(place_address)type(initializer-list)
place_address 必须是一个指针,initializer-list 是类型的初始化列表

定位 new 表达式在实际中一般是配合 内存池 使用,因为内存池分配出的内存没有初始化,并且构造函数不可以显示调用,所以如果是自定义类型的对象,需要使用定位 new 以进行显示调用构造函数进行初始化

#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
//定位 new 又叫 replacement new
int main()
{
	//p1 现在指向的只是与 A 对象相同大小的一段空间,并不是一个对象,因为没有调用构造函数
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;	//调用无参的构造函数 输出 A()
	//可以手动调用析构函数,然后释放空间
	p1->~A();	//输出 ~A()
	free(p1);
	//p2 现在指向的只是与 A 对象相同大小的一段空间,并不是一个对象,因为没有调用构造函数
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);	//10 是参数,可以根据参数调用对应的构造函数 输出 A()
	p2->~A();	//输出 ~A()
	operator delete(p2);
	return 0;
}

六、malloc/free和new/delete的区别

malloc/free 和 new/delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放

不同的地方是:

  • malloc 和 free 是函数,new 和 delete 是运算符
  • malloc 申请的空间不会初始化,new 可以初始化
  • malloc 申请空间时,需要手动计算空间大小并传递,new 只需在其后跟上空间的类型,如果是多个对象,[] 中指定对象个数即可
  • malloc 的返回值为 void*,接收时必须强制类型转换,new 不需要,因为 new 后跟的是空间的类型
  • malloc 申请空间失败时,返回的是NULL,因此使用时必须判空,new 不需要,但是 new 需要捕获异常
  • 申请自定义类型对象时,malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成空间中资源的清理

到此这篇关于C++中new和delete匹配使用过程详解的文章就介绍到这了,更多相关C++ new与delete内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C/C++ Qt实现文章小说人物关系分析

    C/C++ Qt实现文章小说人物关系分析

    这篇文章主要为大家详细介绍了C/C++ Qt如何实现文章小说人物关系分析功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-01-01
  • C++中map 字典的基本使用教程

    C++中map 字典的基本使用教程

    Map是字典一样的数据结构,它是(键,值)对的关联数组,其中每个唯一键仅与单个值相关联,下面这篇文章主要给大家介绍了关于C++中map 字典的基本使用的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • C++中String的语法及常用接口的底层实现详解

    C++中String的语法及常用接口的底层实现详解

    在C语言中,string是一个标准库类(class),用于处理字符串,它提供了一种更高级、更便捷的字符串操作方式,string 类提供了一系列成员函数和重载运算符,以便于对字符串进行操作和处理,本编文章会对C++中的 string 进行详解,希望本篇文章会对你有所帮助
    2023-06-06
  • c++11之std::async 和std::thread的区别小结

    c++11之std::async 和std::thread的区别小结

    std::async和std::thread都是C++11中提供的线程库,它们都可以用于创建新线程,本文主要介绍了c++11之std::async 和std::thread的区别小结,感兴趣的可以了解一下
    2024-02-02
  • C++中spdlog的简单使用示例

    C++中spdlog的简单使用示例

    spdlog是一个开源、跨平台、无依赖、只有头文件的C++11日志库,所以这篇文章主要来和大家介绍一下一个简单的spdlog使用示例,感兴趣的小伙伴可以了解一下
    2023-08-08
  • MFC串口通信发送16进制数据的方法

    MFC串口通信发送16进制数据的方法

    这篇文章主要为大家详细介绍了MFC串口通信发送16进制数据,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • C语言实现俄罗斯方块小游戏

    C语言实现俄罗斯方块小游戏

    这篇文章主要为大家详细介绍了Linux下C语言实现俄罗斯方块小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • C/C++模拟实现烟花效果的示例代码

    C/C++模拟实现烟花效果的示例代码

    这篇文章主要为大家详细介绍了C/C++模拟实现烟花效果的两种简单方法,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以了解下
    2024-01-01
  • Qt5.14.2使用虚拟键盘的关键代码

    Qt5.14.2使用虚拟键盘的关键代码

    对于Qwidget程序,使用qtvirtualkeyboard弹出键盘之后,键盘会浮于表面。使用VirtualkeyboardPushView模块,自动根据情况把输入视图往上面推移,这篇文章主要介绍了Qt5.14.2使用虚拟键盘的关键代码,需要的朋友可以参考下
    2022-09-09
  • 详解Dev C++使用教程(使用Dev C++编写C语言程序)

    详解Dev C++使用教程(使用Dev C++编写C语言程序)

    这篇文章主要介绍了详解Dev C++使用教程(使用Dev C++编写C语言程序),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03

最新评论