C++学习之路-编译器自动生成的构造函数

本文探讨了C++中编译器何时会为类自动生成无参构造函数。通过汇编代码分析,我们发现编译器在成员变量初始化、存在虚函数、虚继承、包含其他类的对象成员或父类有构造函数时,会默认生成构造函数。这些情况表明,只有在对象创建时有额外操作需要执行时,编译器才会生成构造函数。理解这一机制有助于深入掌握C++的构造函数和对象初始化。
摘要由CSDN通过智能技术生成

回顾

在前面构造函数的学习中,我们提到了一个广为流传的错误:

默认情况下,编译器会为每一个类生成空的、无参的构造函数。 这句话在C++里是错的,不严谨

我们在此节中详细说明一下,这句话不严谨在哪。即,C++在某些特定的情况下,会给类自动生成无参的构造函数。

通过汇编检查是否调用了构造函数

我们声明一个类,声明一个无参的构造函数,通过创建对象,查看汇编函数调用情况

class Person
{
	int m_age;
public:
	Person() {}
};

int main()
{
	Person person;
	return 0;
}

查看汇编代码,可以看到call Person::Person,说明调用了该构造函数

在这里插入图片描述

然后,我们将构造函数删除,再次创建对象,查看是否调用构造函数(即查看编译器是否生成了默认的无参构造函数)

class Person
{
	int m_age;
public:

};

int main()
{
	Person person;
	return 0;
}

可以看出,创建对象的时候,什么也没做,并没有调用构造函数。这也就说明编译器并没有为该类生成了默认的无参构造函数。

在这里插入图片描述

因此,我们证明了那个广为流传的错误:默认情况下,编译器会为每一个类生成空的、无参的构造函数。既然,我们给出了结论:C++在某些特定的情况下,会给类自动生成无参的构造函数。那我们接下来就探索一下,在哪些特定情况下,即使不写构造函数,编译器也会默认生成无参的构造函数。

哪些情况下,会默认生成无参的构造函数?

成员变量在声明的时候,就进行初始化

class Person
{
	int m_age = 10;
public:

};

int main()
{
	Person person;
	return 0;
}

通过汇编也可以看出,竟然调用了Person构造函数

在这里插入图片描述

我们进入构造函数的内部,查看汇编代码。可以看到存在传递值10的操作,也就是给age赋值10

在这里插入图片描述

到这里其实我们可以得出一个结论了:编译器为该类默认的生成了无参的构造函数,且在构造函数内部完成了成员变量的初始化。别以为代码写的时候是在创建变量时初始化的,初始化操作就在外面完成。我们通过汇编分析,得出结论:即使在外部初始化,依然会在构造函数内部完成初始化操作。

因此,下面两种写法是等价的

class Person
{
	int m_age = 10;
public:

};
class Person
{
	int m_age;
public:
	Person()
	{
		m_age = 10;
	}
};

好了言归正传,回到标题:成员变量在声明的时候,就进行初始化,那么编译器就会为该类生成一个无参的构造函数,来完成成员变量的初始化

类中存在定义的虚函数

在该类中,我声明一个空的虚函数。创建对象时,也会调用构造函数。意味着,编译器也会默认的创建一个无参的构造函数

class Person
{
	int m_age;
public:
	virtual void run()
	{

	}
};

我们通过查看构造函数内部,发现存在这样的一个操作:将虚表地址放在对象内存的最前面,用于查找虚表(细节可以查看我之前的博文

博文中:一旦类中有虚函数,创建对象时,对象最前面会多出四个字节存放虚表地址

在这里插入图片描述

然后就可以得出一个结论:类中存在虚函数时,即使不写构造函数,编译器也会默认的生成一个无参的构造函数,从而在函数内部完成虚表地址的存放

虚继承了其它类

虚继承是怎么回事,看之前的博文

class Person
{
public:

};

class Student : virtual public Person
{

};

int main()
{
	Student student;
	return 0;
}

即使两个类什么都不写,但是Student虚继承了Person类,创建Student对象时,依然会调用构造函数,也就是说编译器生成了一个无参的构造函数。

在这里插入图片描述

可以看到,构造函数里依然有虚表地址的传递,也就说创建对象时,依然有事干。

在这里插入图片描述

类中包含其他类的对象成员,同时其它类存在构造函数

这句话什么意思,下面代码直接看懂。在Person类里面,声明一个Car类型的成员,且Car中是有构造函数的(无论这个构造函数是自定义的还是编译器默认生成的)

class Car
{
public:
	Car(){}
};

class Person
{
public:
	Car car; 
};

int main()
{
	Person person;
	return 0;
}

此时,可以看到Person内部生成了默认的构造函数。

在这里插入图片描述

我们可以在Person的构造函数里看到,调用了Car的构造函数。

在这里插入图片描述

我们梳理一下这个过程:Person创建对象时,有事做,所以编译器给生成无参构造函数。做了什么事:对象类型成员变量car需要调用Car的构造函数,就像普通成员变量需要初始化值一样,都叫有事要做。

父类有构造函数(无论是自定义的还是编译器默认生成的)

父类中有构造函数,子类在创建对象时,会优先调用父类的构造函数完成父类成员变量的初始化(在我的这篇博文里有讲解)

class Person
{
public:
	 Person(){}
};

class Student : public Person
{
public:

};

int main()
{
	Student student;
	return 0;
}

所以,子类在创建对象时,会调用父类构造函数,这叫有事可做,所以编译器会为子类生成一个无参的构造函数

在这里插入图片描述

总结

通过几个例子我们似乎可以摸清规律了。

什么特定情况下编译器会默认的创建一个无参的构造函数,通俗点说就是:当对象创建的那一刻,有事情要做!

对象创建完毕,需要做一些额外操作(比如内存操作:存放虚表地址,初始化成员变量;函数调用:调用父类构造函数)时,编译器一般都会为类生成一个无参的构造函数。

切记,理解对象创建完毕,需要做一些额外操作这句话,指的是对象创建完毕那一刻,而不是对象创建完成之后的事。

person对象创建完毕之后,调用了run函数。这不叫对象创建时有事要做,必须是创建完毕那一刻有事要做,而不是过一会才有事要做。要理解程序运行的时序。

class Person
{
public:
	void run()
	{
		cout << "do something" << endl;
	}
};

int main()
{
	Person person;
	person.run();

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值