专栏首页架构说c++系列之一构造函数

c++系列之一构造函数

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

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

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

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

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

一,需要初始化类成员

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

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

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

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

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

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

继承

先看看下面的代码

#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类型的变量,然后程序的输出是这样的:

	Construct A //继承
	Construct C //基础
	Construct B //自己
	Destruct B
	Destruct C
	Destruct A

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

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

列表初始化成员变量

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

	#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;
	}

类图

输出是这样的~

	Construct C 成员的声明顺序
	Construct A 成员变量的声明顺序
	Construct B //自己

可以看出,

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

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

继承与列表初始化

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

#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;
	}

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

	Construct A //继承顺序
	Construct C //继承顺序
	Construct C //成员顺序
	Construct A /成员顺序
	Construct B //自己
	Destruct B
	Destruct A
	Destruct C
	Destruct C
	Destruct A

也就是说,

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

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

#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;
}

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

	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

本文分享自微信公众号 - 架构说(JiaGouS),作者:程序员小王

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 无重复字符的最长子串

    同类:给一个字符串str,找到str中最长的连续子串(不区分大小写),返回其长度。

    程序员小王
  • leetcode第513题-找树左下角的值

    Given a binary tree, find the left most value in the last row of the tree.

    程序员小王
  • 从Pegasus看分布式系统设计

    程序员小王
  • 「漫画版」 小朋友都能看懂得 DevOops!不允许你有问号!

    DevOps消除了障碍,并减轻了开发人员和运营人员之间的紧张关系。革命性的DevOops!

    小小科
  • 持续代码质量管理-SonarQube-7.3简单使用 2.1. 查看配置2.2. 质量检测2.3. 浏览器查看

    安装了SonarQube以及Sonar Scanner之后,就需要那代码检测了。当然为了方便我们使用已有现成的demo,知道到对应的git地址下载...

    踏歌行
  • Java:那些鲜为人知的关键字volatile

    下面,我将详细讲解 volatile是如何保证 “共享变量 的可见性 & 有序性,但不保证原子性”的具体原理

    Carson.Ho
  • 本地图文直接复制到富文本编辑器中

    在使用 braft-editor 时,发现如果复制一段文字+图片的信息,在粘贴到富文本编辑器中时,只有文本被成功粘贴了,图片会丢失。但是单独复制一张图片是能够成...

    kmokidd
  • 什么是DevOps?

    DevOps概念已经被越来越多的人所熟知,本文将从不同职能与DevOps的联系,以及DevOps运动如何演变入手,希望可以帮助你对DevOps有更深刻的理解。

    APICloud
  • C++:二阶构造函数

    类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。最常见的操作是在构造函数中为类的成员变量进行赋值,如果还想再构造函数中进行一些其他操作,可...

    王强
  • [Objective-C Runtime] 成员变量与属性

    Jacklin

扫码关注云+社区

领取腾讯云代金券