C++类成员初始化列表

  • C++类型定义
  • C++构造函数的初始化列表定义
  • C++构造函数执行的两个阶段:初始化阶段和计算阶段
  • 为什么使用初始化列表
  • C++里面哪些东西必须放在初始化列表里面
  • 成员变量的初始化顺序
  • 总结:C++构造函数逻辑执行顺序

这边文章主要学习C++里面的成员初始化列表。然后分析为什么需要成员初始化列表,C++构造器调用机制与Java区别。

C++类型定义

在C++里面定义一个类型(比如有默认构造器的Test类)的方式一般是:

Test t1;

下面这个方式定义一个类变量会报错:

Test t1();

为什么呢?因为C++为了兼容C语言,对于上面的定义在C语言里面会被认为是一个函数声明。

所以我们定义一个类变量,然后调用默认构造函数,直接使用类似于Test t1 这种方式就行。

这里与Java有区别,在Java里面对于Test t1 代码只是做声明,并不会调用构造器做初始化。但是在C++里面会调用构造函数。

C++构造函数的初始化列表定义

C++的构造函数与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。

比如下面的例子:

class Foo
{private:string name ;int id ;
public:Foo(string s, int i):name(s), id(i){} ; // 初始化列表
};

C++构造函数执行的两个阶段:初始化阶段和计算阶段

构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段执行。

初始化阶段

在定义的类中的所有的类类型(class type)的成员都会在初始化阶段初始化,也就是调用默认的无参数构造函数,即使该成员没有出现在构造函数的初始化列表中。

所以初始化阶段分为两个步骤:
1)调用初始化列表
2)未初始化的类成员(不在初始化列表)调用默认构造函数

比如对于下面的代码:

class BaseTest1{public:BaseTest1();BaseTest1(const BaseTest1& t1);BaseTest1& operator=(const BaseTest1& t1);
private:int a;
};BaseTest1::BaseTest1()
{cout << "BaseTest1 Constructor" << endl;this->a = 0;
}BaseTest1::BaseTest1(const BaseTest1 &t1)
{cout << "BaseTest1 Copy Constructor" << endl;this->a = t1.a;
}BaseTest1& BaseTest1::operator=(const BaseTest1 &t1)
{cout << "BaseTest1 assignment" << endl;this->a = t1.a;return *this;
}class BaseTest2
{public:BaseTest2(){cout << "BaseTest2 Constructor" << endl;this->a = 0;}BaseTest2(const BaseTest2& t){cout << "BaseTest2 Copy Constructor" << endl;this->a = t.a;}BaseTest2& operator=(const BaseTest2& t){cout << "BaseTest2 assignment" << endl;this->a = t.a;return *this;}
private:int a;
};class Test{public:Test(BaseTest1& t1, BaseTest2& t2):test1(t1){this->test2 = t2;}public:BaseTest2 test2;BaseTest1 test1;
};int main(int argc, char* args[])
{BaseTest1 test1;BaseTest2 test2;Test test(test1, test2);
}

执行结果是:

//执行main函数的第一和第二行,调用BaseTest1和BaseTest2的构造函数;
BaseTest1 Constructor
BaseTest2 Constructor
// 因为Test类中test2变量不在初始化列表中,所以会先初始化Test类成员test2,然后调用初始化列表,也就是通过BaseTest1 Copy Constructor, 最后执行在Test的构造函数的逻辑,也就是test2的赋操作
BaseTest2 Constructor
BaseTest1 Copy Constructor
BaseTest2 assignment

但是如果我们更改Test的声明如下:

class Test{public:Test(BaseTest1& t1, BaseTest2& t2):test1(t1),test2(t2) {}
public:BaseTest2 test2;BaseTest1 test1;
};int main(int argc, char* args[])
{BaseTest1 test1;BaseTest2 test2;Test test(test1, test2);
}

那么执行结果如下:

BaseTest1 Constructor
BaseTest2 Constructor
BaseTest2 Copy Constructor
BaseTest1 Copy Constructor

可知Test的构造过程少了BaseTest2的默认构造函数调用,直接通过拷贝构造函数赋值到Test的成员属性。

此外我们还能知道,类成员的初始化顺序与初始化列表的顺序无关,与类里面属性定义的顺序有关。

计算阶段

计算阶段指:执行构造函数体内的赋值操作。

前面我们已经讲过了计算阶段的主要操作,主要是构造函数体里面的逻辑执行,一般来说我们都尽量通过初始化列表初始化成员属性,无法通过初始化列表初始化的操作,就通过构造函数计算阶段执行。

为什么使用初始化列表

初始化类的成员有两种方式:一是使用初始化列表,二是在构造函数体内进行赋值操作。使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由上面的测试可知,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。同样看上面的例子,我们使用初始化列表来实现Test2的构造函数:

class Test2{public:Test2(Test1& t1);
private:Test1 test1;
};
// copy constructor
Test2::Test2(Test1 &t1):test1(t1){}

执行同样的main函数:结果如下:

Test1 Constructor
Test1 Copy Constructor

第一行输出对应调用代码的第一行。第二行输出对应Test2的初始化列表,直接调用拷贝构造函数初始化test1,省去了调用默认构造函数的过程。所以一个好的原则是,能使用初始化列表的时候尽量使用初始化列表。

C++里面哪些东西必须放在初始化列表里面

除了性能问题之外,有些场景初始化列表是不可或缺的,以下几种情况时必须使用初始化列表:

  • 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
  • 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
  • 没有默认构造函数的class type,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

比如:

class Test1
{public:Test1(int a):i(a){}int i ;
};class Test2
{public:Test1 test1 ;Test2(Test1 &t1){test1 = t1 ;}
};

以上代码无法通过编译,因为Test2类中Test1 test1;需要调用默认的构造函数,但是Test1类没有无参的构造函数,但是由于Test1没有默认的构造函数,故而编译错误。正确的代码如下,使用初始化列表代替赋值操作。

class Test2{Test1 test1 ;Test2(Test1 &t1):test1(t1){}
}

成员变量的初始化顺序

成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的,看代码。

class foo{int i ;int j ;foo(int x):i(x), j(i){}; // ok, 先初始化i,后初始化j
};

再看下面的代码:

class foo{int i ;int j ;foo(int x):j(x), i(j){} // i值未定义
};

这里i的值是未定义的,虽然j在初始化列表里面出现在i前面,但是i先于j定义,所以先初始化i,但i由j初始化,此时j尚未初始化,所以导致i的值未定义。所以,一个好的习惯是,按照成员定义的顺序进行初始化。

总结:C++构造函数逻辑执行顺序

  1. 创建派生类的对象,基类的构造函数优先被调用,(基类的构造函数也优先于派生类里的成员属性类);

  2. 如果类里面有成员属性类,成员属性类的构造函数优先被调用;(成员属性类的构造函数也优先于该类本身的构造函数)

  3. 基类构造函数如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序而不是它们在成员初始化表中的顺序;

  4. 成员类对象构造函数如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;

  5. 派生类构造函数,作为一般规则,派生类构造函数应该不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数,否则两个类的实现变成紧耦合的(tightly coupled)将更加难于正确地修改或扩展基类的实现。(基类设计者的责任是提供一组适当的基类构造函数)

综上可以得出,初始化顺序:

父类构造函数–>成员类对象构造函数(按照类定义顺序而不是初始化列表的顺序)–>类自身构造函数

析构顺序和构造顺序相反。

C++成员初始化列表相关推荐

  1. C++:用成员初始化列表对数据成员初始化

    1.在声明类时,对数据成员的初始化工作一般在构造函数中用赋值语句进行.  例如: class Complex{private:double real;double imag;public:Comple ...

  2. Cpp 对象模型探索 / 成员初始化列表

    目录 一.何时必须使用? 二.优势 三.细节探究 一.何时必须使用? 成员变量是 const 类型. 成员变量是引用. 基类中含有带形参的构造函数且不存在默认的构造函数. 成员变量是类对象,该对象含有 ...

  3. C++中成员初始化列表的使用

    C++在类的构造函数中,可以两种方式初始化成员数据(data member). 1,在构造函数的实现中,初始类的成员数据.诸如: class point { private:  int x,y; pu ...

  4. lt;转载自刘佳ID:freedom0203和waretgt; C++中成员初始化列表的使用

    刘佳: C++在类的构造函数中,可以两种方式初始化成员数据(data member). 1,在构造函数的实现中,初始类的成员数据.诸如: class point { private:  int x,y ...

  5. C++ 语法之【成员初始化列表】

    C++ 语法之[成员初始化列表] 类对象构造过程 定义[成员初始化列表] 必须使用[成员初始化列表]的情况 情况一:const类成员或者被声明为引用的类成员 情况二:初始化成员是对象(包含继承) 情况 ...

  6. C++ 类(构造函数的成员初始化列表)

    文章概述 构造函数的成员初始化列表 构造函数的成员初始化列表 下面的代码分析了什么时候使用成员初始化列表: class A {private:int a;public:A(int a){this-&g ...

  7. C++学习笔记:成员初始化列表【Cherno】

    先带着几个问题来看文章: 为什么要有初始化列表.怎么写初始化列表,初始化列表的好处是什么: 1:为什么要有初始化列表: 很简单,为了提高效率和C++风格化,先说C++风格化,如果我们要在一个构造函数中 ...

  8. c++构造函数成员初始化中赋值和初始化列表两种方式的区别

    先总结下: 由于类成员初始化总在构造函数执行之前 1)从必要性: a. 成员是类或结构,且构造函数带参数:成员初始化时无法调用缺省(无参)构造函数 b. 成员是常量或引用:成员无法赋值,只能被初始化 ...

  9. 1.c++中初始化列表和构造函数初始化的区别是什么?2.类的成员变量的初始化顺序是按照声明顺序吗?

    初始化列表和构造函数初始化的区别是什么? 初始化和赋值对内置类型的成员没有太大的区别,在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的.只有一些需要注意的事项 初始化列表一般情况如下: D ...

最新文章

  1. PanoNet3D:一个基于激光雷达点云语义和几何理解的3D目标检测方法
  2. dedeCMS修改文章更新发布时间问题
  3. c语言寻找James,[semi-tutorial]某亚里亚写在JamesM边上的OS笔记
  4. MonoDevelop笔记
  5. 计算机基础应用模拟试题,计算机基础应用模拟试题5
  6. nginx缓存服务器
  7. 编写 Servlet 2.3 Filter
  8. 网站底部运行时间的php代码,网站底部运行时间统计代码
  9. 开发好能重构的代码,都是这么干的
  10. 大数据可视化有什么优点
  11. ajax实现form表单提交
  12. 中国农村统计年鉴合集(1985-2019年)
  13. VS2019创建COM组件
  14. ceph集群安装报错解决方法
  15. 安卓模拟器安装教程_雷电模拟器4.0去广告清爽版,详细安装教程,超简单!
  16. 从算法学起C语言--费氏数列
  17. php mds函数,MDSRank类解析 - linux_hunter的个人页面 - OSCHINA - 中文开源技术交流社区...
  18. Word2vec And Doc2vec - 文本向量化
  19. 邻接矩阵的定义和例子
  20. 部分ADSL猫的默认密码

热门文章

  1. 如何关闭谷歌浏览器的自动更新?
  2. 《计算机网络常见问题》
  3. 计算机网络 数据链路层 要解决的三个问题 差错检测
  4. postgres 保存报错duplicate key value violates unique constraint...解决方案_亲测有效
  5. 阿里巴巴开发者工具盘点
  6. 存放在mysql数据库的表_下列选项中,存放在mysql数据库的表是
  7. 云尚办公OA系统学习笔记(6.审批设置-管理端)
  8. JetBrains学生认证过期,提示“No suitable licenses associated with account ”
  9. 8.vim(vi)自定义环境变量
  10. 集群所有节点加载时报错--main class information unavailable