关于编译器生成默认构造函数的理解

构造函数

构造函数是创建类类型对象时,由编译器自动调用,用来初始化对象的函数,它保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。函数名与类名相同,无返回值。构造函数可以重载。


  • 什么是默认构造函数?
  1. 无参的构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以称之为默认构造函数;
  2. 默认构造函数只能有一个

例1:验证默认构造函数只能有一个

class Date
{
public:
	// 无参构造函数
	Date()
	{
		_year = 2021;
		_month = 4;
		_day = 18;
	}

	// 全缺省构造函数
	Date(int year = 2000, int month = 4, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d; 
	
	system("pause");
	return 0;
}

创建d对象时会出错,因为编译器不知道要调用哪个默认构造函数,存在二义性这里是引用

1. 如何理解 “ 如果用户不显示定义构造函数,编译器会生成默认构造函数 ” ?

例2:不显式定义构造函数

class Date
{
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;

	system("pause");
	return 0;
}

在这里插入图片描述
从监视窗口可以看到,成员变量被初始化了,只不过是随机值而已。说明如果我们不显式定义构造函数,编译器可能生成了一个默认构造函数
为什么说可能呢?接着往下看
在这里插入图片描述
从反汇编看,创建对象d时,并没有调用Date的构造函数的指令,而是直接跳到了system(“pause”)

结论:
从语法来说,如果一个类没有显式定义任何构造函数,编译器会生成一个默认构造函数;
实际上,编译器对于是否生成构造函数会进行优化,只有编译器觉得需要才会生成

2、编译器会生成默认构造函数的情况

1)A类中包含B类的对象,B类定义了默认构造函数(无参构造函数 或 全缺省构造函数)

原因:因为类成员对象有默认构造函数,那么编译器就需要显式的来调用这个类成员对象的构造函数。要想显式调用,那编译器必须自己合成一些代码来调用,所以就会生成默认构造函数了。
注意:编译器产生的这个默认构造函数仅仅调用类成员对象的构造函数,而不对类中其他变量做任何初始化操作。

例3:Time类显式定义默认构造函数,Date类不显式定义默认构造函数;Date类中包含一个Time类对象

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_min = 0;
		_sec = 0;
	}
private:
	int _hour;
	int _min;
	int _sec;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;

	Time _t; // 另一个类的类对象
};

int main()
{
	Date d;

	system("pause");
	return 0;
}

在这里插入图片描述
这次从汇编代码可以看到,创建Date d时,调用了编译器生成的默认构造函数
在这里插入图片描述
需要注意的是,初始化Time _t的时候不是编译器直接去调用Time类中定义的构造函数,而是先给Date类生成默认构造函数,然后在Date类的默认构造函数中再去调用Time类中我们自己显式定义的默认构造函数

例4:验证如果被包含类对象的类中没有定义默认构造函数,则编译器不会生成默认构造函数
对例3的代码稍稍改动一下下;这次Time类中也不显式定义默认构造函数

class Time
{
private:
	int _hour;
	int _min;
	int _sec;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;

	Time _t; // 自定义类型
};

int main()
{
	Date d;

	system("pause");
	return 0;
}

在这里插入图片描述在这里插入图片描述
汇编代码中没有调用编译器没有生成默认构造函数的指令,也就是说,编译器没有给Date类生成默认构造函数

2)基类带有默认构造函数(无参构造函数 或 全缺省构造函数),创建派生类对象时
(类A是基类,类B是派生类,类B继承自类A,类A中带有默认构造函数)

例5:

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
};

class Derived :public Base
{
public:
	int _d;
};

int main()
{
	Derived d;
	
	system("pause");
	return 0;
}

在这里插入图片描述
3)类中含有虚函数,编译器一定会给该类生成默认的构造函数

① 类本身定义了自己的虚函数
② 类从继承体系中继承了虚函数
这两种情况都使一个类成为带有虚函数的类。因为含有虚函数的类对象都含有一个虚表指针vptr,编译器需要对vptr设置初值,以满足虚函数机制的正确运行,编译器会把这个设置初值的操作放在默认构造函数中。如果设计者没有定义任何一个默认构造函数,则编译器会生成一个默认构造函数完成上述操作,否则,编译器将在每一个构造函数中插入代码来完成相同的事情。

4)在虚拟继承中,编译器一定会给子类生成构造函数

虚继承是用于解决多继承条件下的菱形继承问题
底层实现原理与编译器有关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基表(不占用类对象的存储空间) (需要注意的是,虚基类依旧会在其子类中存在拷贝,但是只有一份);当虚基类的子类被当作父类继承时,虚基类指针也会被继承
实际上,vbptr指的是虚基表指针,该指针指向了一个虚基表,虚基表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基表)的两份同样的拷贝,节省了存储空间

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ 默认构造函数是在没有显式定义构造函数的情况下自动生成的特殊成员函数。它通常用于在创建对象时进行初始化操作。默认构造函数无参数,不接受任何实参。当我们通过调用类的构造函数来创建对象时,如果没有提供实参,则编译器会自动调用默认构造函数默认构造函数的作用是确保对象的所有成员变量都被正确初始化。例如,如果一个类有一个int类型的成员变量,那么在默认构造函数中,可以将该成员变量初始化为0。如果没有默认构造函数,当我们创建对象时,该成员变量可能会未被初始化,导致程序运行时出现意外结果。 另一个重要的地方是,当我们定义了类的其他构造函数时(比如有参数的构造函数),默认构造函数依然会被生成。这是因为在某些情况下,我们可能只想使用默认构造函数来创建对象,而不希望传递实参。此时,默认构造函数就能满足需求。当我们重载构造函数时,可以使用默认参数来实现默认构造函数的功能。 需要注意的是,默认构造函数在一些特殊情况下可能不会被生成。例如,如果我们显式定义了有参数的构造函数,但没有提供默认构造函数,那么编译器将不会自动生成默认构造函数,这意味着我们不能再使用无参的方式来创建对象。 总之,理解C++默认构造函数的作用和用法对于编写高质量的代码至关重要。它可以帮助我们确保对象的正确初始化,并且在一些特殊情况下可以提供方便的使用方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值