Fromhttps://blog.csdn.net/zj510/article/details/8135556

通常我们对类成员进行“初始化”有两种方式:

  1. 构造函数后面跟冒号;

  2. 构造函数里面对成员进行赋值。

有些人不太注意这个小细节,或者根本不知道他们的区别,认为两种方式是一样的。这个误解有时可能会对程序带来影响,这里我来介绍一下这两种方式。

首先我们看这么一段代码:

  1. 1 class A
  2. 2 {
  3. 3 public:
  4. 4 A(int& c)
  5. 5 {
  6. 6 _a = 1;
  7. 7 }
  8. 8 protected:
  9. 9 int _a;
  10. 10 const int _b;
  11. 11 int& _c;
  12. 12 };

这段代码正确吗?答案是否定,这段代码无法通过编译。我们会看到下面的编译错误

  1> d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_b' : must be initialized in constructor base/member initializer list
  1> d:\study\myconsole\myconsole\myconsole.cpp(20) : see declaration of 'A::_b'
  1> d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_c' : must be initialized in constructor base/member initializer list
  1> d:\study\myconsole\myconsole\myconsole.cpp(21) : see declaration of 'A::_c'

意思是说成员_b和_c必须在构造函数的成员初始化列表里面初始化。那么_a为什么没有报错呢?看看成员的声明,我们看到_a是一个int类型,_b是一个const int类型,_c是一个int&类型。根据C++的规则,const类型和引用不可以被赋值,只能被初始化。这里我们先花一点点时间来看一下const类型和引用。

大家看看下面的这段代码是否正确:

  1. 1 int _tmain(int argc, _TCHAR* argv[])
  2. 2 {
  3. 3 int a;
  4. 4 const int b;
  5. 5 int& c;
  6. 6
  7. 7 return 0;
  8. 8 }

编译一下就会看到这2个错误:
  1> d:\study\myconsole\myconsole\myconsole.cpp(30) : error C2734: 'b' : const object must be initialized if not extern
  1> d:\study\myconsole\myconsole\myconsole.cpp(31) : error C2530: 'c' : references must be initialized

哦,原来const和引用必须在声明的时候就初始化(其实就是因为const和引用不可以在变量创建完成后再被赋值,所以编译器做了这个限制)。ok,把代码改一下:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. int a;
  4. const int b=5;
  5. int& c = a;
  6.  
  7. return 0;
  8. }

现在 编译就没有问题了。细心的朋友会发现我这里在b和c声明代码那里使用的=号而不是(),其实我们也可以这么做:

  1. 1 int _tmain(int argc, _TCHAR* argv[])
  2. 2 {
  3. 3 int a;
  4. 4 const int b(5);
  5. 5 int& c(a);
  6. 6
  7. 7 return 0;
  8. 8 }

在这种情况下用括号和等于号初始化,效果是一样的。具体就不细讲了。

OK,我们举这个小例子的目的就是想加深大家对const和引用的印象:const和引用必须在声明的时候就初始化,换句话说就是在给const和引用类型变量分配内存的时候就初始化。

好了,现在我们回到class A的问题,类A里面有const成员和引用成员,当系统要给类A的对象分配内存的时候,系统需要给A的对象的3个成员_a, _b, _c分配内存。_a没有问题,系统直接给它一块内存。_b和_c就出问题了,分配内存的时候没有初始化。所以编译就出问题了。其实C++给类成员初始化的唯一方式就是成员初始化列表,也就是构造函数后面跟冒号的那种形式。将class A的代码调整一下:

  1. 1 class A
  2. 2 {
  3. 3 public:
  4. 4 A(int& c): _b(2), _c(c)
  5. 5 {
  6. 6   _a = 1;
  7. 7 }
  8. 8 protected:
  9. 9 int _a;
  10. 10 const int _b;
  11. 11 int& _c;
  12. 12 };
  13. 13
  14. 14
  15. 15 int _tmain(int argc, _TCHAR* argv[])
  16. 16 {
  17. 17 int number = 3;
  18. 18 A a(number);
  19. 19
  20. 20 return 0;
  21. 21 }

我们在A的构造函数的后面用冒号来初始化_b和_c。现在可以通过编译了。因为系统可以在给_b和_c分配内存的时候就初始化了。那么假如我们把代码改成下面的形式:

  1. class A
  2. {
  3. public:
  4. A(int& c)
  5. {
  6. _a = 1;
  7. _b = 2;
  8. _c = c;
  9. }
  10. protected:
  11. int _a;
  12. const int _b;
  13. int& _c;
  14. };

这样 能行吗?编译一下就得到下面的错误:

  1> d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_b' : must be initialized in constructor base/member initializer list
  1> d:\study\myconsole\myconsole\myconsole.cpp(22) : see declaration of 'A::_b'
  1> d:\study\myconsole\myconsole\myconsole.cpp(14) : error C2758: 'A::_c' : must be initialized in constructor base/member initializer list
  1> d:\study\myconsole\myconsole\myconsole.cpp(23) : see declaration of 'A::_c'
  1> d:\study\myconsole\myconsole\myconsole.cpp(17) : error C2166: l-value specifies const object

这3个错误包含2个意思:

  1. const和引用变量没有初始化;

  2. 不可以对const变量_b进行赋值,也可以说const变量不可以当作左值(error C2166: l-value specifies const object)。

现在我们就可以知道了,其实在构造函数里面调用等于号并不是真正意义上的“初始化”。这个过程相当于:

  1. 系统创建成员变量;

  2. 创建完后再进行赋值操作。

而在构造函数后面跟冒号,就相当于:

  1. 系统创建成员变量并且初始化。也就是系统为成员变量分配了一块内存并且把相应的数据给填了进去。而构造函数里面调用等于号的方式是分配好后再进行赋值,多了一个步骤。

下面我们再来做一个实验:

  1. 1 class A
  2. 2 {
  3. 3 public:
  4. 4 A(int& c): _b(2), _c(c)
  5. 5 {
  6. 6 _a = 1;
  7. 7 }
  8. 8 protected:
  9. 9 int _a;
  10. 10 const int _b;
  11. 11 int& _c;
  12. 12 };
  13. 13
  14. 14 class B
  15. 15 {
  16. 16 public:
  17. 17 B(int& c):_objA(c)
  18. 18 {
  19. 19 printf("B constructor\n");
  20. 20 }
  21. 21
  22. 22 protected:
  23. 23 A _objA;
  24. 24 };
  25. 25
  26. 26
  27. 27 int _tmain(int argc, _TCHAR* argv[])
  28. 28 {
  29. 29 int number = 3;
  30. 30 B obj2(number);
  31. 31
  32. 32 return 0;
  33. 33 }

类B里面有个一个类A的对象,在类B的构造函数里面用冒号来初始化成员_objA。那么_objA是什么时候被初始化的呢?有图有真相:

从callstack里面可以清楚的看到:

  1. 进入B的构造函数;

  2. 进入A的构造函数。

也就是说冒号后面的代码是在一进入构造函数的时候就被调用了。

然后从左下角的Watch里面也可以看到,在系统调用构造函数括号里面的第一行代码之前,_a,_b, _c就已经分配好了。我们可以看到_a是个没有初始化过的值(系统自己生成了一个),_b和_c都是我们初始化的。那么我可以得出一个结论:

构造函数后面跟的冒号代码是在进入构造函数并且在括号里面的第一行代码之前被执行。

假如在B的构造函数里面不显式初始化_objA,会发生什么事呢?用代码模拟一下就知道了,系统会调用A的默认构造函数来初始化_objA。

好了,讲完了。通俗的讲,构造函数后面的冒号就是初始化,而括号里面的等于号并不是初始化,而是变量生成以后的赋值而已(永远都是2个步骤)。

附:

本文前面我提到一句话:const和引用不可以被赋值,只能被初始化。可能会有些朋友对这句话有意见,看下面的代码,这段代码是正确的,没有问题。那么怎么说不能被赋值呢?其实b=12只是把a的内容给改掉了(a和b的值都是12),而不是把引用b指向另外一个变量。换句话说:引用b初始化完成后,就永远指向初始化时候的那个变量,无法再改变了。我这里的“引用不可以被赋值”是指不能给引用本身赋值来改变它的指向,并不是说不可以改变引用指向的内存的内容。可能言语上面会有不同的理解,但是只要知道是这么回事情就可以了。

  1. 1 int _tmain(int argc, _TCHAR* argv[])
  2. 2 {
  3. 3 int a = 1;
  4. 4 int& b = a;
  5. 5 b = 12;
  6. 6
  7. 7 return 0;
  8. 8 }

【C++】类成员冒号初始化以及构造函数内赋值的更多相关文章

  1. c++类 用冒号初始化对象(成员初始化列表)

    c++类 用冒号初始化对象(成员初始化列表) 成员初始化的顺序不同于它们在构造函数初始化列表中的顺序,而与它们在类定义中的顺序相同 #include<iostream> ; using n ...

  2. Java 类成员的初始化顺序

    Java 类成员的初始化顺序 前言:开发中碰到一个Java文件中有很多的成员变量,包括静态和非静态的,还有很多的初始化方法,很好奇这些成员的初始化顺序,在这里作个研究.   1  无继承情况下的Jav ...

  3. c++ 类成员变量初始化总结

    最近在学习c++,不同类型的c++成员变量在初始化的时候也有很有的区别,查了一些资料之后再此记录一下: #include<iostream> using namespace std; // ...

  4. c++类成员变量初始化相关问题

    对于内置变量的自动初始化 代码1 1 #include<stdio.h> 2 #define CONST 100 3 int *p1; 4 int a[2]; 5 int b; 6 sta ...

  5. 吐槽C++:C++ 类成员变量初始化 之 初始化带有参数的构造函数 的类成员变量。

    本来我想写这样的代码: class MatchManager{ public: MatchManager() { } class OnTimerRunFuncHelper{ public: OnTim ...

  6. c++11之二: 类成员变量初始化

    在C++11中, 1.允许非静态成员变量的初始化有多种形式:初始化列表; 使用等号=或花括号{}进行就地的初始化. 可以为同一成员变量既声明就地的列表初始化,又在初始化列表中进行初始化,只不过初始化列 ...

  7. c++ 类成员的初始化顺序

    class TestClass1 { public: TestClass1() { cout << "TestClass1()" << endl; } Te ...

  8. 关于初始化C++类成员

    在使用C++编程的过程当中,常常需要对类成员进行初始化,通常的方法有两种: 第一种方法: CMYClass::CSomeClass() { x=0; y=1; } 第二种方法: CSomeClass: ...

  9. VS C++工程类成员初始化检测脚本

    最近项目中出现由类成员未初始化而进行读写而造成的问题,于是想将项目中所有的为初始化的地方找出来,优化一下代码,维护了这么多年的程序已有百万余行且VS2015还尚未支持检查类成员初始化的方法.,于是想写 ...

  10. C#类成员初始化顺序

    这里直接给出C#类成员一般初始化顺序: 子类静态字段 子类静态构造 子类实例字段 父类静态字段 父类静态构造 父类实例字段 父类实例构造 子类实例构造 为什么说是"一般"初始化顺序 ...

随机推荐

  1. MyBatis高频面试题

    1.MyBatis中使用#和$书写占位符有什么区别? 2.Hibernate 与 Mybatis区别(MyBatis与Hibernate有什么不同). 3.持久层设计要考虑的问题有哪些? 4.你用过的 ...

  2. VSCode 中优雅地编写 Markdown

    VSCode 中优雅地编写 Markdown 在 VSCode 中编写 Markdown 有几个无法拒绝的优势,首先是顺手方便,常写代码的同学打开 VSCode 各项功能和快捷键使用的都比较熟练,可以 ...

  3. Rust 学习笔记

    rust 学习梳理 数据类型 基于已明确的类型,Rust会推断剩下大部分类型.基于类型推断Rust具备了与动态类型语言近似的易读性,并仍能在编译期捕获类型错误. 函数可以是泛型的:单个函数ujiu可以 ...

  4. C++中map,multimap和unordered_map的区别

    map.multimap容器 map的所有元素都是pair,同时拥有键值(key)和实值(value) pair的第一元素被视为键值,第二元素被视为实值 性质: 以rb_tree为底层结构,因此元素有 ...

  5. 质效提升 | 聊聊QA与业务测试

    上面一篇文章<质效提升 | QA不做业务需求测试,你怎么看>主要讨论的是QA 和业务需求测试相关的问题,文章发出后收到了很多小伙伴的反馈,这里把很多有意义的反馈放在下面,希望对你有用. 约 ...

  6. 几款Java开发者必备常用的工具,准点下班不在话下

    摘要:一问一答的形式轻松学习掌握java工具. 以一问一答的形式学习java工具 Q:检查内存泄露的工具有?A: jmap生成dump转储文件,jhat可视化查看. Q:某进程CPU使用率一直占满,用 ...

  7. 从零开始学习python | 实例讲解如何制作Python模式程序

    摘要:在本文中,我们将学习python中的各种模式程序. Python编程语言很容易学习.易于语法实现的各种库使其脱颖而出,这是它成为本世纪最流行的编程语言的众多原因之一.虽然学习很容易,但访问员通常 ...

  8. 云小课 | 华为云KYON之私网NAT网关

    摘要:本文介绍KYON独创的私网NAT网关服务,支持云上重叠组网,支持云上重叠组网,助您的业务敏捷上云. 本文分享自华为云社区<云小课 | 华为云KYON之私网NAT网关>,原文作者:云小 ...

  9. 教你如何在Spark Scala/Java应用中调用Python脚本

    摘要:本文将介绍如何在 Spark scala 程序中调用 Python 脚本,Spark java程序调用的过程也大体相同. 本文分享自华为云社区<[Spark]如何在Spark Scala/ ...

  10. Solon2 开发之插件,四、插件热插拔管理机制(H-Spi)

    插件热插拔管理机制,简称:H-Spi.是框架提供的生产时用的另一种高级扩展方案.相对E-Spi,H-Spi 更侧重隔离.热插热拔.及管理性. 应用时,是以一个业务模块为单位进行开发,且封装为一个独立插 ...