来源:http://blog.csdn.net/theprinceofelf/article/details/20057359

前段时间被人问及“初始化列表和构造有什么区别?”我竟一时语塞,只好回头

拿起几本C++的大部头书,打开VS2012和vim开始倒腾。最后总结出如下几点,希望

对大家理解C++能有些帮助。(题外话:我认为好的技术书籍和师者对人最大的帮助

就是:帮助学者节省时间。)

综合而言,C++中类的初始化操作有四个部分组成:

1.初始化列表:所有类非静态数据成员都可以在这里初始化,

所有类静态数据成员都不能在这里初始化

2.构造函数体:对于类非静态数据成员:

const型成员不能在这里初始化

引用型成员不能在这里初始化

没有默认构造函数的成员不能在这里初始化

对于类静态数据成员:

可以在这里修改可修改的静态成员,但静态成员必须已经在类外部初始化

3.类外初始化:除一个特例外,所有类static数据成员必须在这里初始化,

特例是类static const int数据成员可以在这里初始化,也可以在成员的声明处初始化

4.类中声明时直接赋值:类static const int数据成员可以选在这里初始化。

直接罗列这样的规则,是我国大多数教科书的展开方式,记得经典的三部曲吗?

(1)定义

(2)定理

(3)例题

至于来龙去脉就只能靠我们这些学子的悟性了。何其苦载!事实证明需要理清

一些定理和思想的来龙去脉往往需要比这个定理更加广阔的知识和视野,让学生拿

着空洞的课本靠领悟?(不要意思,又吐槽了)

让我们从一段简单的代码开始:

  1. class A {
  2. const int x;
  3. public:
  4. A() {
  5. this->x = 1; /* Error! */
  6. }
  7. };

对很多人而言,这是什么直观写法,为什么就错了呢?其实这本质上相当于写:

  1. const int x;
  2. x = 1;

所以我们只能按如下方式声明其初始化:

  1. class A {
  2. const int x;
  3. public:
  4. A() : x(1) {
  5. }
  6. };

再来看一段简单的代码:

  1. class A {
  2. int &x;
  3. public:
  4. A(int k) {
  5. this->x = k; /* Error! */
  6. }
  7. };

同理这这本质上相当于写:

  1. int &x;
  2. x = k;

所以我们只能按如下方式声明其初始化:

  1. class A {
  2. const int x;
  3. public:
  4. A(int k) : x(k) {
  5. }
  6. };

有了上面两个简单例子作为引子,我们开始进一步讨论C++初始化的全过程。

其实我相信很多人还是怀着这样一些疑问“写在初始化列表里就相当于int &x=k;吗?”

且让我们来看看C++类的初始化的全过程:

(1)静态成员初始化阶段:所有类的静态成员应该都是在这个阶段初始化的。

注意初始化的顺序,就是操作语句的顺序,例如你有一个Test类:

  1. int Test::x = 2;
  2. int Test::y = 3;

需要注意的是2点,一是初始化语句不再需要static关键字,二是执行顺序就是

语句的顺序,这里是先初始化t1,再初始化t2。执行顺序的问题在静态成员是类的时候

就关系到构造函数的调用顺序了。另外需要注意的是,这些静态成员的初始化在任何具

体实例被创建前就已经完成了。

(2)实例初始化列表工作阶段:
       需要说的是,在用户使用new或者其他方法开始构建实例的时候,第一步首先是向

操作系统申请内存,初始化列表是在申请成功后才开始工作的。

然后,根据非静态成员的声明顺序开始执行如下操作:

1.如果该成员没有出现在初始化列表中:

1)如果是内置非const且非引用类型,不设定初值

2)如果是const类型,报错,必须在这里给定初值

3)如果是引用类型,报错,必须在这里给定初值

4)如果是class类型,就调用默认构造函数,进行初始化操作

2.如果该成员出现在初始化列表中:

1)如果是内置类型,就按初始化列表指定的值设定初值
      2)如果是const类型,就按初始化列表指定的值设定初值

3)如果是引用类型,就按初始化列表指定的值设定初值

4)如果是class类型,就调用初始化列表指定的构造函数进行初始化操作

(3)计算阶段:

根据构造函数的函数体进行赋值操作,或者修改操作,

在这里,静态和非静态数据都可以赋值和修改

下面用一段代码来测试这个过程:

  1. class Test1 { /*用于测试Test2中含有没有默认构造函数的成员时的情况*/
  2. public:
  3. int i;
  4. Test1(int a): i(a){} /*这就取消了Test1的默认构造函数*/
  5. };
  6. class Test2 {
  7. public:
  8. int a;    //int a = 1;Error:不允许数据成员初始值设定项
  9. const int b;
  10. static int c;
  11. static const int d = 4;//正确,这样赋值也是可以的,也可以选在类声明外进行赋值
  12. //但是如果不赋值,则程序中没有使用d不出错,使用了就会有link error
  13. //无法解析的外部命令
  14. //static const float ff = 4.0; Error:只有静态常量整形数据成员才可以在类中初始化
  15. int &e;
  16. const int &f;
  17. static int &g;
  18. static const int &h;
  19. //static const int &h = x_h; Error:只有静态常量整形数据成员才可以在类中初始化
  20. Test1 t1;
  21. const Test1 t2;
  22. static Test1 t3;
  23. const static Test1 t4;
  24. Test2(int b, int e, int f, Test1 t1, Test1 t2)
  25. : b(b),
  26. //d(4), Error: d不是类的非静态成员或基类
  27. e(e),//如果没有这句,Error:Test2:e没有提供初始化值
  28. f(f),
  29. t1(t1),//如果没有这句,Error:Test1没有默认构造函数
  30. t2(t2)
  31. {
  32. a = 1;
  33. //b = 2; //Error:表达式必须是可修改的左值,b是左值,不能修改
  34. c = 3;
  35. //d = 4;  //Error:表达式必须是可修改的左值,d是左值,但不能修改
  36. }
  37. };
  38. //int Test2::a = 1; //Error:非静态数据成员不能在其类的外部定义
  39. //int Test2::b = 2; //Error:非静态数据成员不能在其类的外部定义
  40. int Test2::c = 3; //如果没有这句,会出现无法解析的外部符号public:static int A::c
  41. //int Test2::d = 4; //Error: int与声明const int不兼容
  42. //int const Test2::d = 4; //和在类声明里面直接写赋值等价
  43. int x_g = 5;  /*这个全局变量主要用户后续的静态成员赋值*/
  44. int x_h = 6;  /*这个全局变量主要用户后续的静态成员赋值*/
  45. Test1 x_t3(7);/*这个全局变量主要用户后续的静态成员赋值*/
  46. Test1 x_t4(8);/*这个全局变量主要用户后续的静态成员赋值*/
  47. int& Test2::g = x_g;
  48. const int& Test2::h = x_h;
  49. Test1 Test2::t3 = x_t3;
  50. const Test1 Test2::t4 = x_t4;

前面讲了这么多具体的细节,我个人建议按如下简化规则来记忆:

(1)所有static成员变量在类外初始化(不管它是const,是引用,还是没默认构造函数的对象)

(2)普通成员变量,是const,是引用,是没默认构造函数的,必须在初始化列表初始化

(3)普通成员变量,需要复杂运算的初始化变量,应该在构造函数内初始化,否则尽量在

初始化列表中初始化。

另外补充2个小点:

(1)初始化列表的使用可能提高性能

  1. class Test3 {
  2. public:
  3. int a;
  4. Test3() {
  5. a = 0;
  6. puts("Test3 constructor");
  7. }
  8. Test3(Test3 &t3) {
  9. this->a = t3.a;
  10. puts("Test3 copy constructor");
  11. }
  12. Test3& operator=(Test3 &t) {
  13. puts("Test3 assign operator");
  14. this->a = t.a;
  15. return *this;
  16. }
  17. ~Test3() { }
  18. };
  19. class Test4 {
  20. public:
  21. Test3 t3;
  22. //Test4( Test3 &t3) : t3(t3) { //这种方式和下面的方式有相同的效果,不同的效率
  23. //}
  24. Test4( Test3 &t3) {
  25. this->t3 = t3;
  26. };
  27. };

(2)成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的

参考如下代码

  1. struct foo
  2. {
  3. int i ;
  4. int j ;
  5. foo(int x):i(x), j(i){}; // ok, 先初始化i,后初始化j
  6. };

再看下面的代码

  1. struct foo
  2. {
  3. int i ;
  4. int j ;
  5. foo(int x):j(x), i(j){} // i值未定义
  6. };

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

i(j);

j(x);

所以会出现错误。

从初始化列表和构造函数谈C++的初始化机制的更多相关文章

  1. C++构造函数初始化列表与构造函数中的赋值的区别

    C++类中成员变量的初始化有两种方式:构造函数初始化列表和构造函数体内赋值. 一.内部数据类型(char,int……指针等) class Animal { public: Animal(int wei ...

  2. C++基础 (3) 第三天 构造函数 构造函数初始化列表 拷贝构造函数 析构函数 静态成员变量

    // 同类之间无私处 2构造函数 3析构函数 4构造函数的种类和析构函数的顺序 结论:析构函数的调用顺序,跟对象的构造顺序相反,谁先构造,谁最后一个被析构. 拷贝构造函数: 注意: 等号写在下面和写在 ...

  3. c++ 初始化列表和构造函数初始化区别

    先上代码 #include <iostream> class MyContruct { public: MyContruct() { std::cout << "My ...

  4. C++ 构造函数_初始化列表

    构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式.例如: class Student { public: //构造函数初始化列表 Stude ...

  5. C++类够做函数初始化列表

    构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式.例如: class CExample{ public: int a; float b; C ...

  6. C++类构造函数初始化列表

    C++类构造函数初始化列表 构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式.例如: class CExample {public:     ...

  7. 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数

    一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 构造函数的执行分为两个阶段 初始化段 普通计算段 (一).对象成员及其初始化  C++ Code  1 2 3 4 5 6 7 8 9 1 ...

  8. C++构造函数初始化列表与赋值

    C++中类的初始化操作一般有四个部分组成: 1.构造函数初始化列表 2.构造函数体内赋值 3.类外部初始化 4.类声明时直接赋值 对于内部数据类型(char,int,float...),构造函数初始化 ...

  9. 10.C++-构造函数初始化列表、类const成员、对象构造顺序、析构函数

    首先回忆下,以前学的const 单独使用const修饰变量时,是定义的常量,比如:const int i=1; 使用volatile const修饰变量时,定义的是只读变量 使用const & ...

随机推荐

  1. [UE4]蓝图比C++慢10倍,是吗?

    首先,蓝图肯定是比C++慢. 任何脚本语言(需要解释执行的语言),和C++相比可能达到十倍甚至百倍的差距.比如Java.Python.Lua,JS. 脚本语言是运行在虚拟机上的,所以它们比起直接运行的 ...

  2. [UE4]实例化材质

    在虚幻引擎 4 中,材质实例化用来更改材质的外观,而不会引起成本高昂的材质重新编译. 实例化材质官方文档

  3. [UE4]虚幻4链接独立服务器

    如果虚幻4只做客户端的话,应该怎么连接服务器呢? 官方并没有提供蓝图的网络函数,C++里面有. 一.自己实现,提供接口给蓝图使用. 二.第三方插件.插件下载地址:https://github.com/ ...

  4. 在线学习和在线凸优化(online learning and online convex optimization)—在线分类问题2

    紧接上文,我们讲述在线分类问题 令,为0-1损失,我们做出如下的简化假设: 学习者的目标是相对于hypotheses set: H具有low regret,其中H中的每个函数是从到{0,1}的映射,并 ...

  5. VS2008自定义快捷键设置

    点[Keyboard..]

  6. python多进程与服务器并发

    进程 什么是进程 进程:正在进行的一个过程或者说一个任务.而负责执行任务则是cpu. 进程与程序的区别 程序仅仅只是一堆代码而已,而进程指的是程序的运行过程. 并发与并行 无论是并行还是并发,在用户看 ...

  7. 图片路径中含有中文在jsp下不能正常显示的问题

    图片路径中含有中文在jsp下不能正常显示的问题~ 这里其实涉及到get请求编码和url编码的问题. jsp页面: 当路径中存在中文的时候,最简单的解决办法是改变tomcat的编码: 在conf/ser ...

  8. Linux TCP/IP调优-Linux内核参数注释

    固定文件的内核参数 下列文件所在目录: /proc/sys/net/ipv4/ 名称 默认值 建议值 描述 tcpsyn_retries 5 1 对于一个新建连接,内核要发送多少个SYN连接请求才决定 ...

  9. String.prototype.normalize()

    normalize() 方法会按照指定的一种 Unicode 正规形式将当前字符串正规化. 这是一个ES6方法. 许多欧洲语言有语调符号和重音符号.为了表示它们,Unicode 提供了两种方法.一种是 ...

  10. alias with parameter,linux

    alias demoAlias1='_(){ git checkout -b $1; command2;}; _'