【从成员对象的角度看析构函数】析构函数的调用顺序

系列文章目录

座右铭:人的一生这么长、你凭什么用短短的几年去衡量自己的一生!

个人主页:

个人主页清灵白羽 漾情天殇清灵白羽 漾情天殇擅长计算机底层原理,深度解析C++,自顶向下看Java,等方面的知识,清灵白羽 漾情天殇关注c++,java,c语言,linux领域.https://blog.csdn.net/weixin_59658448


系列文章目录

文章目录

前言

一、栈上对象

1、无动态分配内存

        1.类内成员对象本身

        2.类内成员对象指针

        3.类内成员对象指针(包含成员变量)

        1.成员对象本身

        2.成员对象指针

2、有动态分配内存

        1.成员对象本身

        2.成员对象指针

 二、堆上对象

补充:

直接对象 vs. 指针对象:

 总结


前言

        这是一篇通过讲解成员对象来进一步研究析构函数的调用的文章,这篇文章将会详细地剖析成员对象的定义方式以及各种用法,希望能够帮助到大家!


一、栈上对象

1、无动态分配内存

        1.类内成员对象本身

        这里的代码主要为大家展示栈上创建对象并且没有任何动态分配的内存,也就意味着不需要析构函数释放额外的资源,并且类内的成员对象要在类内直接构造。

#include"Yangon.h"
using namespace std;
class Member {
public:
	Member() {
		cout << "Member()" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member member;
public:
	MyClass(Member member) 
		:member(member)
	{
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Member member;
	cout << "===============" << endl;
	MyClass myclass(member);
	cout << "===============" << endl;
	return 0;
}

这个是这段代码在VS2019上面的运行结果,大家可以观察一下他的析构函数调用的顺序,我用代码段的方式来为大家演示一下为什么会是这样的。

【从成员对象的角度看析构函数】析构函数的调用顺序_第1张图片

        首先在此之前我要说明一点,在栈上开辟的对象是不需要我们进行手动释放的,当这个对象的生命周期结束之后这个对象就会被自动释放,请注意我们这个对象是在主函数也就是main函数当中定义的,所以这个对象的生命周期等同于main函数的生命周期,也就意味着当主函数执行到return 0 之后这个对象的生命周期也就结束了,我们在调试代码的时候就会发现当程序执行到return 0之后程序才会开示调用析构函数。接下来我为大家详细地讲解一下析构函数的调用顺序。

Member()
===============
MyClass()
~Member()    

//这里由于我们采用传值传递所以调用了拷贝构造函数,
中间会生成一个临时对象,临时对象产生之后就会立刻调用析构
函数将这个临时对象清理掉,所以这里调用了Member的析构函数。

===============

//当程序执行到这里的时候其实主函数已经执行结束了,
这个时候会返回调用member对象和myclass对象的析构函数,
我们要知道myclass对象里面也有一个member对象,
一定要注意myclass里面的member和我们自己定义的member并不是一回事,
只是调用拷贝构造复制了一份,所以我们定义的member和
myclass里面的member都需要调用member的析构函数,
这就是为什么最后会调用两次member的析构函数。


~MyClass()
~Member()
~Member()

        这里涉及到了拷贝构造函数的部分知识我后续的文章会为大家详细讲到拷贝构造函数,这里就不做赘述了。

        2.类内成员对象指针

        在这里我们主要讨论当类内的成员对象是这个对象的指针而不是这个对象本身的时候,请看代码。

#include"Yangon.h"
using namespace std;
class Member {
public:
	Member() {
		cout << "Member()" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member* member;
public:
	MyClass(Member* member) :member(member)
	{
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};

int main() {
	Member* member = new Member();
	cout << "============" << endl;
	MyClass myclass(member);
	cout << "============" << endl;
	delete member;
	cout << "============" << endl;
	return 0;
}

        这个是代码运行的结果,从这里可以看到,当我们的对象是通过new关键字创建的时候,编译器就不会再为我们自动调用析构函数了,这个时候就必须我们手动调用delete关键字来对这个对象进行清理了。而且这个时候Myclass类里面不存在member对象了,存放的只是member对象的指针。而Myclass类对象仍然还是在栈上开辟,所以编译器会在主函数结束之后也就是这个栈上的对象生命周期结束之后自动调用析构函数来对对象进行清理,不需要程序员手动释放资源了。

【从成员对象的角度看析构函数】析构函数的调用顺序_第2张图片

        3.类内成员对象指针(包含成员变量)

        这个话题我主要是想要为大家展示一下当存在成员变量的时候对于成员对象本身和对于成员对象指针是如何初始化的问题。

        1.成员对象本身

#include"Yangon.h"
using namespace std;

class Member {
private:
	int age;
	string name;
public:
	Member(int age, string name) :age(age),name(name)
	{
		cout << "Member()" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	int age;
	string name;
	Member member;
public:
	MyClass(int age, string name,const Member& member) :age(age),name(name),member(member)
	{
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Member member(21, "Hello World");
	cout << "================" << endl;
	MyClass myclass(21, "Hello World!", member);
	cout << "================" << endl;
	return 0;
}

        这个是这段代码执行的最终结果,和上面的结果没有什么太大的区别,我就不多讲了。【从成员对象的角度看析构函数】析构函数的调用顺序_第3张图片

        我这里还是要提一句,有些小伙伴可能想问,如果member这个对象构造所需要的成员变量例如age和nameMyclass类里面没有怎么办呢?很简单可以直接在构造函数里面提供参数,代码如下:

#include"Yangon.h"
using namespace std;

class Member {
private:
	int age;
	string name;
public:
	Member(int age, string name) :age(age),name(name)
	{
		cout << "Member()" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member member;
public:
	MyClass(const Member& member) :member(member)
	{
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Member member(21, "Hello World");
	cout << "================" << endl;
	MyClass myclass(member);
	cout << "================" << endl;
	return 0;
}

       上面的代码不管myclass当中有没有成员对象构造所需要的变量,都不重要,因为myclass当中的成员对象的初始化靠的是拷贝构造,也就是先定义一个member对象然后直接给myclass赋值过去,当然还有另外一种初始化方法。

        请看代码:

#include"Yangon.h"
using namespace std;

class Member {
private:
	int age;
	string name;
public:
	Member(int age, string name) :age(age),name(name)
	{
		cout << "Member()" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member member;
public:
	MyClass(int age,string name) :member(age,name)
	{
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	MyClass myclass(21,"张三");
	cout << "================" << endl;
	return 0;
}

        2.成员对象指针

        这里主要为大家展示一下,当类当中是成员对象的指针的时候,注意成员对象的指针,这就意味着这个类不需要调用成员对象的构造函数那么自然也就不用调用他的析构函数了。

class Member {
private:
	int age;
	string name;
public:
	Member(int age, string name) :age(age), name(name) {
		cout << "Member()" << endl;
	}
	~Member() {
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member* member;
public:
	MyClass(Member* member) :member(member) {
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Member* member = new Member(21, "张三");
	cout << "=================" << endl;
	MyClass myClass(member);
	cout << "=================" << endl;
	MyClass* myClassobj = new MyClass(member);
	cout << "=================" << endl;
	delete member;
	cout << "=================" << endl;
	delete myClassobj;
	cout << "=================" << endl;
	return 0;
}

【从成员对象的角度看析构函数】析构函数的调用顺序_第4张图片


2、有动态分配内存

        1.成员对象本身

using namespace std;

class Member {
private:
	int age;
	string name;
	char* data;
public:
	Member(int age,string name,const char* str):age(age),name(name)
	{
		data = new char[strlen(str) + 1];
		strcpy(data, str);
		cout << "Member()" << endl;
	}
	~Member() {
		delete[] data;
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member member;
public:
	MyClass(int age, string name, const char* str) :member(age,name,str)
	{
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	MyClass myclass(21, "张三", "Hello World");
	cout << "===========" << endl;
	MyClass* myclassobj = new MyClass(22, "李四", "Hello 李四");
	cout << "===========" << endl;
	delete myclassobj;
	cout << "===========" << endl;
	return 0;
}

        这个是程序运行的结果,针对我上文当中讲到的内容,程序结束之后才释放的那么一定是栈上的对象,堆当中的对象一定是需要我们手动调用delete进行释放的。

【从成员对象的角度看析构函数】析构函数的调用顺序_第5张图片

        2.成员对象指针

        这里给大家演示栈上的对象内部拥有成员对象的指针的情况,析构函数是如何调用的。

#include"Yangon.h"
using namespace std;

class Member {
private:
	char* data;
	int age;
	string name;
public:
	Member(int age, string name, const char* str) :age(age), name(name) {
		data = new char[strlen(str) + 1];
		strcpy(data, str);
		cout << "Member()" << endl;
	}
	~Member() {
		delete[] data;
		cout << "~Member()" << endl;
	}
};
class MyClass {
private:
	Member* member;
public:
	MyClass(Member* member) :member(member) {
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Member* member = new Member(21, "张三", "Hello World!");
	cout << "==============" << endl;
	MyClass myclass(member);
	cout << "==============" << endl;
	delete member;
	cout << "==============" << endl;
	return 0;
}

        以下是这段代码的运行结果,成员对象的指针不需要调用构造函数也不需要调用析构函数,栈上的对象需要等到主函数结束以后也就是return 0执行过后才可以。这些内容我在上文当中都已经讲过了

【从成员对象的角度看析构函数】析构函数的调用顺序_第6张图片


 二、堆上对象


        为了方便大家一次性观看,我将上文当中提到的多种形式结合到一段代码里面去,这里面包括了成员对象本身、成员对象指针,以及栈上对象、对象对象,各位只需要记住,当我们将对象区分为堆上和栈上时区别在于栈上对象的释放不需要我们程序员手动释放,编译器会替我们做这个工作,这个过程发生在主函数结束以后,很好理解因为我们栈上对象定义在主函数里面所以主函数结束了这个对象的生命周期也就结束了,堆上的对象需要我们手动地释放,我们使用delete将这个对象释放。

        如果我们将成员对象区分为成员对象本身和成员对象指针的时候,要记住成员对象本身,是需要在类的内部调用成员对象的构造函数和析构函数的,而成员对象指针就不需要这些操作了,所以使用指针更方便一些。

#include"Yangon.h"
using namespace std;

class Base {
private:
	string gender;
	string name;
public:
	Base(string gender, string name) :gender(gender), name(name) {
		cout << "Base()" << endl;
	}
	~Base() {
		cout << "~Base()" << endl;
	}
};
class Member {
private:
	int age;
	string name;
	Base base;
	char* data;
public:
	Member(int age, string name, const char* str,string gender) :age(age), name(name),base(gender,name) {
		data = new char[strlen(str) + 1];
		strcpy(data, str);
		cout << "MyClass()" << endl;
	}
	~Member() {
		cout << "~MyClass()" << endl;
	}
};
class MyClass {
private:
	Member* member;
public:
	MyClass(Member* member) {
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
	}
};
int main() {
	Base base("男", "张三");
	cout << "=============" << endl;
	Member* member = new Member(21, "李四","Hello World!", "女");
	cout << "=============" << endl;
	MyClass* myClassObj = new MyClass(member);
	cout << "=============" << endl;
	delete member;
	cout << "=============" << endl;
	delete myClassObj;
	cout << "=============" << endl;
	return 0;
}

        这个是程序运行之后的结果,如果一个对象内部有其它成员对象的话需要调用其它成员对象的构造函数,调用这个对象的析构函数的时候也需要调用成员对象的析构函数,栈上对象就等到主函数结束之后自动调用,我已经用分隔符为大家整理好了,上文当中也已经说的很清楚了,就不一一为大家解释每一步的过程了。

【从成员对象的角度看析构函数】析构函数的调用顺序_第7张图片


补充:

class MyClass {
private:
	Member* member;
public:
	MyClass(Member* member) {
		cout << "MyClass()" << endl;
	}
	~MyClass() {
		cout << "~MyClass()" << endl;
		delete member;
	}
};

        当我们在类当中定义到了成员对象指针的时候必须在析构函数内部对这个指针进行清理,代码如上图所示。刚才我的代码里面没有体现出这一点,所以这里做出补充!

·        

  1. 直接对象 vs. 指针对象:

        当我们直接将一个对象作为成员而不是使用指针时,这个对象会在包含它的对象的构造函数中被直接构造。而当你使用指针时,你需要在构造函数中为指针分配内存,并在析构函数中手动释放内存。这导致了初始化和释放内存的方式的不同。


 总结

        这篇文章就为大家讲解到这里,这篇文章是基于成员对象对构造函数和析构函数的调用顺序做出了一个深入地了解!需要大家对构造函数和析构函数非常地熟悉,如果有对析构函数还不是很了解的话,可以去看我之前的文章,希望大家能够从我的文章当中有所收获!

 

你可能感兴趣的:(深度解析C++,c++)