前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >c++系列之一构造函数

c++系列之一构造函数

作者头像
程序员小王
发布2019-05-05 16:51:48
6460
发布2019-05-05 16:51:48
举报
文章被收录于专栏:架构说架构说

构造函数的执行过程会分成两个阶段:

1 初始化阶段(如果是成员变量,类成员的声明顺序,如果是继承的类 ,根据继承顺序 ) 2 赋值阶段 (构造函数中代码顺序)

旁白:变量或者类在 定义是,分配空间后,第一次 赋予初值即为初始化,

默认初始化和显示初始化之分,

C++何时要使用成员初始化列表? 初始化数据成员与对数据成员赋值的含义是什么?有什么区别?

一,需要初始化类成员

首先把数据成员按类型分类并分情况说明:

  • 1.内置数据类型,复合类型(指针,引用)- 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的【这里没有什么区别】
  • 2.用户定义类型(类类型)- 结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)【减少对象传递过程中的拷贝】

二,需要初始化const数据成员;

三,需要初始化引用数据成员;

对于第一种情况,大家知道,对于类对象,初始化和赋值是不同的,赋值会带来了很大的性能开销(类的关系-组合) 对于第二种以及第三种情况,const 成员引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。(初始化只有一次)

析构函数与构造函数的顺序

继承

先看看下面的代码

代码语言:javascript
复制
#include <iostream>
#include <cmath>
using namespace std;

class A
{
public:
	A(){cout << "Construct A" << endl;}
	~A(){cout << "Destruct A" << endl;}
};

class C
{
public:
	C(){cout << "Construct C" << endl;}
	~C(){cout << "Destruct C" << endl;}
	
};

class B: public A, public C
{
public:
	B(){cout << "Construct B" << endl;}
	~B(){cout << "Destruct B" << endl;}
};

int main(int argc, char const *argv[])
{
	B b;
	return 0;
}

类图

这里有三个类,其中A,C类是B的父类,然后在Main函数中声明一个B类型的变量,然后程序的输出是这样的:

代码语言:javascript
复制
	Construct A //继承
	Construct C //基础
	Construct B //自己
	Destruct B
	Destruct C
	Destruct A

这里涉及到构造以及析构的顺序,首先看看构造。构造函数执行的顺序是,先构造父类,再构造子类,其中父类的构造顺序是从左到右。然后析构函数执行的顺序是跟构造函数正好相反的,先执行自身的析构函数,然后再依次从右到左执行父类的析构函数。

上面说的是不涉及列表初始化,以及虚拟继承这些的。那接下来看看加上列表初始化会怎么样。

列表初始化成员变量

现在ABC之间不再是继承关系,而是组合关系。B类中有A和C两个类型的变量。

代码语言:javascript
复制
	#include <iostream>
	#include <cmath>
	using namespace std;

	class A
	{
	public:
		A(){cout << "Construct A" << endl;}
		~A(){cout << "Destruct A" << endl;}
	};

	class C
	{
	public:
		C(){cout << "Construct C" << endl;}
		~C(){cout << "Destruct C" << endl;}
		
	};

	class B
	{
	public:
		// Notice
		B(): a(A()), c(C()) {cout << "Construct B" << endl;}
		~B(){cout << "Destruct B" << endl;}
		C c;
		A a;
	};

	int main(int argc, char const *argv[])
	{
		B b;
		return 0;
	}

类图

输出是这样的~

代码语言:javascript
复制
	Construct C 成员的声明顺序
	Construct A 成员变量的声明顺序
	Construct B //自己

可以看出,

列表初始化是先于构造函数的调用的,而且列表初始化是与初始化顺序无关,只与数据成员定义的顺序有关。

在上面的例子中C类型的变量定义在A类型的变量前面,因此会先构造C,之后构造A。

继承与列表初始化

下面的例子中B类继承了A和C,然后又拥有一个A和C类型的成员变量,虽然不符合设计模式,但是就将就看了。

代码语言:javascript
复制
#include <iostream>
	#include <cmath>
	using namespace std;

	class A
	{
	public:
		A(){cout << "Construct A" << endl;}
		~A(){cout << "Destruct A" << endl;}
	};

	class C
	{
	public:
		C(){cout << "Construct C" << endl;}
		~C(){cout << "Destruct C" << endl;}
		
	};

	class B: public A, public C
	{
	public:
		//Notice: List initialize
		B(): a(A()), c(C()) {cout << "Construct B" << endl;}
		~B(){cout << "Destruct B" << endl;}
		C c;
		A a;
	};

	int main(int argc, char const *argv[])
	{
		B b;
		return 0;
	}

在这样的例子中输出是这样的~

代码语言:javascript
复制
	Construct A //继承顺序
	Construct C //继承顺序
	Construct C //成员顺序
	Construct A /成员顺序
	Construct B //自己
	Destruct B
	Destruct A
	Destruct C
	Destruct C
	Destruct A

也就是说,

  1. 类在构造的时候会先从左到右调用父类的构造函数,
  2. 然后根据类中数据成员的定义依次构造/*注意:是与列表初始化顺序无关*/
  3. 然后才会调用自身的构造函数,而析构函数则正好相反。

virtual 继承,继承,与列表初始化

代码语言:javascript
复制
#include <iostream>
#include <cmath>
using namespace std;

class A
{
public:
	A(){cout << "Construct A" << endl;}
	~A(){cout << "Destruct A" << endl;}
};

class C
{
public:
	C(){cout << "Construct C" << endl;}
	~C(){cout << "Destruct C" << endl;}
	
};

//Notice: C is a virtual public
class B: public A, public virtual C
{
public:
	B(): a(A()), c(C()) {cout << "Construct B" << endl;}
	~B(){cout << "Destruct B" << endl;}
	C c;
	A a;
};

int main(int argc, char const *argv[])
{
	B b;
	return 0;
}

继承跟虚拟继承初始化的顺序是不一样的,在这个例子中,输出顺序是这样的:

代码语言:javascript
复制
	Construct C //virutal继承优先
	Construct A//普通继承其次
	Construct C //成员构造
	Construct A //成员构造
	Construct B //自己

跟之前相比,父类的构造顺序发生了改变,C的构造函数先被执行,然后是AC的构造函数。所以最后可以得到结论:先执行虚拟继承的父类的构造函数,然后从左到右执行普通继承的父类的构造函数,然后按照定义的顺序执行数据成员的初始化,最后是自身的构造函数的调用。

结论

在类被构造的时候(有继承和组合),

  1. 先执行虚拟继承的父类的构造函数,
  2. 然后从左到右执行普通继承的父类的构造函数,
  3. 然后按照定义的顺序执行数据成员的初始化,
  4. 最后是自身的构造函数的调用。析构函数与之完全相反,互成镜

旁白:类与类关系 分为2中继承和组合

参考

1 http://gaocegege.com/Blog/cpp/cppclass

2 http://www.runoob.com/w3cnote/cpp-construct-function-initial-list.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 析构函数与构造函数的顺序
    • 继承
      • 列表初始化成员变量
        • 继承与列表初始化
          • virtual 继承,继承,与列表初始化
          • 结论
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档