【C/C++基础进阶系列】C/C++ 对象模型 -- 类基础知识总结(三)
发布日期:2021-05-07 20:53:18 浏览次数:13 分类:原创文章

本文共 12515 字,大约阅读时间需要 41 分钟。

【C/C++基础进阶系列】C/C++ 对象模型 -- 类基础知识总结(三)

【1】new/delete

#include <iostream>#include <vector>using namespace std;class A{public:	A()	{		cout << "A" << endl;	}	int m_i;};/** * 知识点 * 1. new 一个对象实际上是在堆上分配内存 * 		new 出的对象必须用 delete 释放内存空间,否则会造成内存泄漏 */int main(){	{		/**		 * new 出的 int 对象没有名即无名对象		 * 		new 可以返回一个指向该对象的指针		 * 		从而通过该指针操作这个无名int对象		 * 		 * 内置类型对象在未赋初值时其初值是未定义的		 */		int *pointi = new int; // pointi指向一个int对象		/**		 * new 出的是 string 类类型的对象		 * 		此时会调用 string 类的构造函数进行初始化		 */		string *mystr = new string;		delete pointi;		// 请务必将指针置为空指针否则会产生悬空指针		pointi = nullptr;		delete mystr;		mystr = nullptr;	}	/**	 * 演示了 new 一个对象的同时对对象进行初始化的操作	 */	{		int *pointi = new int(100);							  // 跟踪调试,指针指向的值变成了100		string *mystr2 = new string(5, 'a');				  // 生成5个a的字符串,调用的是符合给进去的参数的string构造函数来构造出合适的字符串内容		vector<int> *pointv = new vector<int>{1, 2, 3, 4, 5}; // 一个容器对象,里面有5个元素,分别是1,2,3,4,5		delete pointi;		pointi = nullptr;		delete mystr2;		mystr2 = nullptr;		delete pointv;		pointv = nullptr;	}	/**	 * 值初始化	 */	{		string *mystr2 = new string(); // “值初始化”,感觉和string *mystr = new string;效果一样,总之最终字符串内容为空("")		int *pointi3 = new int();	   // 值被初始化为0,这个()加与不加确实不一样,只有加了()值才会被初始化为0,否则为未初始化的值		delete mystr2;		mystr2 = nullptr;		delete pointi3;		pointi3 = nullptr;	}	/**	 * 在 new 自定义类型对象时,以下代码效果一致	 * 即针对自定义类型,值初始化没有效果	 */	{		A *pa1 = new A;		A *pa2 = new A();		delete pa1;		pa1 = nullptr;		delete pa2;		pa2 = nullptr;	}	/**	 * auto 与 new 一起使用	 * 注意auto推断的结果,至于为何如此暂不深究(我也不懂)	 */	{		string *mystr2 = new string(5, 'a');		// 这里时获取 C 字符串		const char *p = mystr2->c_str();		auto mystr3 = new auto(mystr2); // 注意这种写法,mystr3会被推断成string ** 类型		//string** mystr3 = new (string *)(mystr2);		delete mystr2;		mystr2 = nullptr;		delete mystr3;		mystr3 = nullptr;	}	/**	 * const 对象的动态内存分配	 */	{		const int *pointci = new const int(200); // new后面这个const可以不写,似乎都差不多;当然const对象不能修改其值		//*pointci = 300;  						// 不合法		cout << "断点放到这里方便观察" << endl;		delete pointci; // new 的内存不能忘记释放		pointci = nullptr;	}	/**	 * delete 只能释放 new 出来的内存,并且只能释放一次	 * 当 p 为空指针时,此时虽然可以释放多次但没有意义	 */	{		char *p = nullptr;		delete p;		delete p;	}	{		int i;		int *p = &i;		//delete p; // 不是new出来的不能delete,否则编译不报错,但执行时会出现异常	}	{		int *p = new int();		int *p2 = p;		delete p2; // 没问题				   // delete p; // 异常,因为p和p2指向同一块内存,				   // 该内存已经通过delete p2释放了,				   // 所以两个指针指向同一块内存释放了p就不能再释放p2,释放了p2就不能再释放p		p2 = nullptr;		p = nullptr;	}	{		const int *pci = new const int(300);		delete pci; //可以delete const对象		pci = nullptr;	}	/**	 * const 对象可以 delete 但不能修改其值	 */	{		int *pci = new int(300);		delete pci; // 可以delete const对象		*pci = 900; // 异常,内存已经被释放了	}	return 0;}

【2】new/delete VS malloc/free

#include <iostream>#include <vector>using namespace std;class A{public:	A()	{		cout << "A()构造函数被调用" << endl;	}	~A()	{		cout << "~A()析构函数被调用" << endl;	}};/** * new/delete 的高级话题 * new 作用,分配内存并调用A的构造函数 *//** * new 的调用关系 * A *pa = new A();			// 操作符 * 		operator new();		// 函数 * 			malloc();		// C 风格函数分配内存 * 		A::A();				// 有构造函数便调用构造函数 *//** * delete 的调用关系 * delete pa;				// 操作符 * 		A::~A();			// 若存在析构函数则调用析构函数 * 		operator delete();	// 函数 * 			free();			// C 风格函数释放内存 *//** * new 和 malloc 的区别 * 1. new 是关键字/操作符,malloc 是函数; * 2. new 一个对象不但会分配内存还会调用对象的构造函数 * 3. new 可以将对象的成员变量的值初始化为0 *  * delete 和 free 的区别 * 1. delete 是关键字/操作符,free 是函数; * 2. delete 一个对象不但会释放内存还会调用对象的析构函数 */int main(){	/**	 * new/delete 与 malloc/free 的区别	 * 		使用new生成一个类对象时系统会调用该类的构造函数	 * 		使用delete删除一个类对象时系统会调用该类的析构函数	 */	{		A *pa = new A(); // 类A的构造函数被调用		delete pa;		 // 类A的析构函数被调用		pa = nullptr;	}	/**	 * new 运算符功能 : 1. 分配内存;2. 调用构造函数初始化该内存	 * 		分配内存是通过operator new() 函数进行的	 * delete 运算符功能 : 1. 调用析构函数;2. 释放内存	 */	{		int *pi = new int; // 分配出去4字节		delete pi;		   // 回收内存的时候,编译器怎样知道要回收4字节?						   // 这就是new内部有记录机制,它分配出去多少,会找个地方记录下来,回收的时候就按这个字节数回收	}	{		// 直接使用 operator new 分配内存空间		void *myorgpoint = operator new(100); // 分配100字节内存,一般没人这样做	}	/**	 * 动态申请和释放一个数组	 * 1. 此处 delete p; 存在弊端	 * 		C++ 针对具有析构函数类型的数组会多分配4个字节,	 * 		此时调用 delete p; 可能无法准确获取待释放的内存空间	 * 2. 因此,delete p; 可行的条件为对象类型是内置类型或没有自定义析构函数	 */	{		int *p = new int[2]; // 动态分配一个整型数组,有2个元素,如果不释放,会产生8个字节泄漏,2个int=8字节		//delete p; // 没有使用[]释放内存,似乎也可以直接释放p这个int数组,并没有内存泄漏		delete[] p; // 这种释放方法是规范的,没有问题的	}	/**	 * 1. A *pA = new A[2](); 为什么会多出 4 个字节?	 * 		C++ 在分配数组空间时会多分配4个字节用于保存数组的大小	 * 2. delete[] pA; 调用了2次析构函数?	 * 		C++ 在分配数组空间时会多分配4个字节用于保存数组的大小	 * 		delete 便可以知道数组中元素的个数从而知道该调用多少次析构函数	 * 3. 注意事项	 * 		new/delete 与 new[]/delete[] 必须成对使用	 */	{		int ilen = sizeof(A); // 1字节,为什么不是0字节?类A里明明没有任何成员,为什么不是0字节?							  // 因为一个类对象,肯定有地址,一个内存地址,至少能保存1个字节,所以这里是1字节不会是0字节							  // A* pA = new A(); 		// 不delete,发现泄漏了1个字节内存		A *pA = new A[2]();	  // 给对象数组分配内存但不delete,发现泄漏了6个字节内存,这里为什么是6字节,而不是2字节?							  // 多出的4字节是用来干嘛的,为什么int* p = new int[2];不多泄漏4字节呢?							  // 		这就是int内置类型和A类类型的差别;							  // delete  pA;    		// 系统报异常,为什么系统报异常呢		delete[] pA;		  // 这种释放方法是规范的,没有问题的,调用了2次析构函数(因为分配内存时调用了两次构造函数)		cout << "测试" << endl;	}	return 0;}

【3】new 对象 VS new 对象()

#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <windows.h>using namespace std;class A{};/** * new/delete 的高级话题 */int main(){	/**	 * new 对象时加括号与不加括号的区别	 * 		1. 若 A 是一个空类则两者没有区别	 */	{		A *pa = new A();		A *pa2 = new A;		delete pa;		delete pa2;	}	return 0;}
#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <windows.h>using namespace std;class A{public:	int m_i;};/** * new/delete 的高级话题 */int main(){	/**	 * new 对象时加括号与不加括号的区别	 * 		2. 若类 A 仅有一个公共成员变量 m_i	 */	{		A *pa = new A(); // m_i 为 0,会将与变量相关的内存置为 0		A *pa2 = new A;	 // m_i 为随机值		delete pa;		delete pa2;	}	return 0;}
#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <windows.h>using namespace std;class A{public:	int m_i;	A() // 构造函数	{	}public:	~A()	{	}};int main(){	/**	 * new 对象时加括号与不加括号的区别	 * 		3. 若类 A 中存在 public 的构造函数	 * 		则两者都会调用类 A 的构造函数	 */	{		A *pa = new A(); // m_i 为随机值		A *pa2 = new A;	 // m_i 为随机值		delete pa;		delete pa2;	}	return 0;}
#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <windows.h>using namespace std;/** * new/delete 的高级话题 */int main(){	{		int *p1 = new int;		// 初始化为随机值		int *p2 = new int();	// 初始化为0		int *p3 = new int(100); // 初始化为100		cout << "断点调试" << endl;		delete p1;		delete p2;		delete p3;	}	{		operator new(12);	}	return 0;}

【4】new/delete 调用过程探讨

#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <windows.h>using namespace std;/** * 编译器若要有效的管理内存的分配和回收,在分配一块内存之外需要额外多分配许多空间保存更多的信息 * 这些信息包括如 1. 分配了多少字节;2. Debug 模式下的调试信息;3. 提高效率进行的边界调整的字节填充;4. 尾信息等 * ppoint 实质指向 malloc 分配出的内存空间中间的某个位置 */int main(){	{		char *ppoint = new char[10];		memset(ppoint, 0, 10);		delete[] ppoint;	}	{		char *ppoint = new char[55];		memset(ppoint, 0, 10);		delete[] ppoint;	}	return 0;}
#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <windows.h>using namespace std;class A{public:	// 应该为静态函数,但不写static似乎也行,估计是编译器内部有处理,	// 因为new一个对象时还没对象,静态成员函数跟着类走和对象无关	static void *operator new(size_t size);	static void operator delete(void *phead);	static void *operator new[](size_t size);	static void operator delete[](void *phead);public:	A()	{		cout << "类A的构造函数执行了" << endl;	}	~A()	{		cout << "类A的析构函数执行了" << endl;	}};void *A::operator new(size_t size){	cout << "A::operator new被调用了" << endl;	A *ppoint = (A *)malloc(size);	return ppoint;}void A::operator delete(void *phead){	cout << "A::operator delete被调用了" << endl;	free(phead);}void *A::operator new[](size_t size){	cout << "A::operator new[]被调用了" << endl;	A *ppoint = (A *)malloc(size);	return ppoint;}void A::operator delete[](void *phead){	cout << "A::operator delete[]被调用了" << endl;	free(phead);}/** * 重载类中的 operator new 和 operator delete 操作符 *//** * new 的调用关系 * A *pa = new A();			// 操作符 * 		operator new();		// 函数 * 			malloc();		// C 风格函数分配内存 * 		A::A();				// 有构造函数便调用构造函数 *//** * delete 的调用关系 * delete pa;				// 操作符 * 		A::~A();			// 若存在析构函数则调用析构函数 * 		operator delete();	// 函数 * 			free();			// C 风格函数释放内存 *//** * 编译器角度的理解 * void * temp = operator new(sizeof(A)); * A *pa = static_cast<A *>(temp); * pa->A::A(); *  * pa->A::~A(); * operator delete(pa); */int main(){	/**	 * 调用用户重载的 new/delete 运算符	 */	{		A *pa = new A();		delete pa;	}	/**	 * 调用全局的 new/delete 关键字	 * :: 作用域运算符	 */	{		A *pa2 = ::new A();		::delete pa2;	}	return 0;}
#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <ctime>using namespace std;void *operator new(size_t size) // 重载全局operator new{	return malloc(size);}void *operator new[](size_t size) // 重载全局operator new[]{	return malloc(size);}void operator delete(void *phead) // 重载全局operator delete{	free(phead);}void operator delete[](void *phead) // 重载全局operator delete[]{	free(phead);}class A{public:	A() //构造函数	{		cout << "A::A()" << endl;	}	~A() //析构函数	{		cout << "A::~A()" << endl;	}	void *operator new(size_t size)	{		A *ppoint = (A *)malloc(size);		return ppoint;	}	void operator delete(void *phead)	{		free(phead);	}	void *operator new[](size_t size)	{		A *ppoint = (A *)malloc(size);		return ppoint;	}	void operator delete[](void *phead)	{		free(phead);	}};/** * 演示重载全局 new/delete 运算符 * 演示重载特定类的 new/delete 运算符 */int main(){	{		int *pint = new int(12);   // 调用重载的operator new		delete pint;			   // 调用重载的operator delete		char *parr = new char[10]; // 调用重载的operator new[]		delete[] parr;			   // 调用重载的operator delete[]		A *p = new A();		// 调用重载的operator new,之后也执行了类A的构造函数		delete p;			// 执行了类A的析构函数,之后也调用了重载的operator delete		A *pa = new A[3](); // 调用一次重载的operator new[],之后执行了三次类A的构造函数		delete[] pa;		// 执行了三次类A的析构函数,之后也调用了重载的operator delete[]	}	return 0;}

【5】定位 new

#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <ctime>using namespace std;class PLA{public:	int m_a;	PLA() : m_a(0) // 构造函数	{		cout << "PLA::PLA()构造函数执行" << endl;	}	PLA(int tempvalue) : m_a(tempvalue) // 构造函数	{		cout << "PLA::PLA(int tempvalue)构造函数执行" << endl;	}	~PLA() //析构函数	{		cout << "PLA::~PLA()析构函数执行" << endl;	}};int main(){	/**	 * 定位new	 * 		定位new与new处于同一个层次	 * 		定位new的功能,在已分配的原始空间中初始化一个对象	 * 			已分配,表明定位new不在分配内存	 * 			初始化对象,即调用对象的构造函数	 * 定位new格式	 * new(地址)类类型(参数)	 */	/**	 * 定位new的调用关系	 * 必须已经分配完毕内存空间	 * PLA *pmyAobj1 = new (mymemPoint) PLA();	// 定位new操作符	 * 		operator new();						// 函数并没有调用malloc	 * 		PLA::PLA();							// 调用构造函数	 */	{		void *mymemPoint = (void *)new char[sizeof(PLA)]; // 内存必须事先分配出来,为了内存分配通用性,这里返回void *类型		// 开始用这个返回的void*指针		PLA *pmyAobj1 = new (mymemPoint) PLA(); // 定位new,调用无参构造函数,这里并不额外分配内存		void *mymemPoint2 = (void *)new char[sizeof(PLA)];		PLA *pmyAobj2 = new (mymemPoint2) PLA(12); // 定位new,调用带一个参数的构造函数,这里并不额外分配内存		// 释放		pmyAobj1->~PLA(); // 根据需要,有析构函数就可以调用析构函数		pmyAobj2->~PLA();		delete[](void *) pmyAobj1; // 分配时用char[],释放时用delete[],本行等价于delete[](void*)mymemPoint;		delete[](void *) pmyAobj2; // 本行等价于delete[](void*)mymemPoint2;	}	return 0;}
#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>#include <atomic>#include <ctime>using namespace std;class PLA{public:	int m_a;	PLA() : m_a(0) // 构造函数	{		cout << "PLA::PLA()构造函数执行" << endl;	}	PLA(int tempvalue) : m_a(tempvalue) // 构造函数	{		cout << "PLA::PLA(int tempvalue)构造函数执行" << endl;	}	~PLA() // 析构函数	{		cout << "PLA::~PLA()析构函数执行" << endl;	}	// 定位new操作符的重载,注意参数是比传统new多一个size参数	void *operator new(size_t size, void *phead)	{		// 这里增加一些自己的额外代码,用于统计之类的,但不要分配内存		return phead; // 收到内存开始地址也只返回内存开始地址即可	}};int main(){	/**	 * 定位new	 * 		定位new与new处于同一个层次	 * 		定位new的功能,在已分配的原始空间中初始化一个对象	 * 			已分配,表明定位new不在分配内存	 * 			初始化对象,即调用对象的构造函数	 * 定位new格式	 * new(地址)类类型(参数)	 */	/**	 * 定位new的调用关系	 * 必须已经分配完毕内存空间	 * PLA *pmyAobj1 = new (mymemPoint) PLA();	// 定位new操作符	 * 		operator new();						// 函数并没有调用malloc	 * 		PLA::PLA();							// 调用构造函数	 */	{		void *mymemPoint = (void *)new char[sizeof(PLA)]; // 内存必须事先分配出来,为了内存分配通用性,这里返回void *类型		// 开始用这个返回的void*指针		PLA *pmyAobj1 = new (mymemPoint) PLA(); // 定位new,调用无参构造函数,这里并不额外分配内存		void *mymemPoint2 = (void *)new char[sizeof(PLA)];		PLA *pmyAobj2 = new (mymemPoint2) PLA(12); // 定位new,调用带一个参数的构造函数,这里并不额外分配内存		// 释放		pmyAobj1->~PLA(); // 根据需要,有析构函数就可以调用析构函数		pmyAobj2->~PLA();		delete[](void *) pmyAobj1; // 分配时用char[],释放时用delete[],本行等价于delete[](void*)mymemPoint;		delete[](void *) pmyAobj2; // 本行等价于delete[](void*)mymemPoint2;	}	return 0;}

参考致谢

本博客为博主学习笔记,同时参考了网上众博主的博文以及相关专业书籍,在此表示感谢,本文若存在不足之处,请批评指正。

【1】C++ 新经典

【2】C++11/14 高级编程 Boost 程序库探秘

上一篇:【C/C++基础进阶系列】C/C++ 对象模型 -- 对象语义
下一篇:【网络通信 -- 直播】SRS -- SRS 部署与直播效果测试

发表评论

最新留言

不错!
[***.144.177.141]2025年03月22日 22时28分51秒