成员变量初始化方式有两种:列表初始化和赋值初始化。如下代码。但是这两种初始化表面上看着相同,但是用法和原理却并不相同。本篇博客主要讨论这两种初始化的使用方法和基本原理。

class Test
{public:Test(int a, int b, int c):_a(a)//初始化列表初始化{_b = b;//赋值初始化_c = c;//赋值初始化}
private:int _a;int _b;int _c;
};

首先,必须是本类的本身的成员变量或者是基类才允许用列表初始化。也就是说从基类继承的成员变量不允许利用初始化列表进行初始化。

一、类本身包含的变量(非继承而来的变量)和基类才可以使用初始化列表

1.类本身的变量(非继承而来的变量)用赋值初始化或者初始化列表都行

类本身的变量用列表初始化或者赋值初始化都可以,可以掺杂着用,也可以都用赋值初始化也可以都用列表初始化。如下代码:

class Test
{public:Test(int a, int b, int c):_a(a)//初始化列表初始化{_b = b;//赋值初始化_c = c;//赋值初始化}
private:int _a;int _b;int _c;
};

二、从基类继承而来的变量的初始化

首先直接向类本身的变量一样利用列表初始化是错误的,如下代码:

class Base
{public:int _num1;int _num2;
};class Son1 :public Base
{public:Son1(int num1, int num2):_num1(num1),_num2(num2){}
};

编译器会报错,提醒说_num1和_num2既不是类的(本身的)非静态成员变量也不是基类,无法使用列表初始化。
下面看看以赋值初始化的方式对继承而来的变量进行初始化。
(1)用赋值初始化

class Base
{public:int _num1;int _num2;
};class Son1 :public Base
{public:Son1(int num1, int num2){//从基类继承而来的成员变量在函数体内用赋值的方式初始化_num1 = num1;_num2 = num2;}
};

上面的代码是正确的,类本身的成员变量和继承而来的成员变量都可以使用赋值初始化的方式进行初始化。

(2)继承而来的成员变量利用列表初始化和基类的构造函数进行初始化
前面说了,只有本类的本身的非静态成员变量和基类才可以使用列表初始化。本身的非静态成员变量好理解,即在本类的非静态成员变量,非继承而来的非静态成员变量。那么基类用列表初始化怎么理解呢?即当子类继承基类,基类的成员变量也被继承,可以通过基类的构造函数对子类从基类继承而来的变量进行初始化。如下代码:

class Base
{public:Base(int num1, int num2) {_num1 = num1;_num2 = num2;cout << "Base 的非缺省构造函数" << endl;}Base(){cout << "Base 的缺省构造函数" << endl;}
public:int _num1;int _num2;
};class Son1 :public Base
{public:Son1(int num1, int num2) :Base(num1, num2){cout << "Son1的构造函数" << endl;}
};int main()
{Son1 so(1, 2);cout << so._num1 << "   " << so._num2 << endl;return 0;
}

运行结果:

发现Son1从Base继承而来的成员变量通过列表初始化的Base(num1,num2)调用了一次构造函数,将Son1继承而来的成员变量进行了初始化。
注意: Base的非缺省构造函数在Son1构造函数之前执行。

那么如果将Base(num1,num2)这句代码放到函数体中呢?如下代码:

class Base
{public:Base(int num1, int num2) {_num1 = num1;_num2 = num2;cout << "Base 的非缺省构造函数" << endl;}Base(){cout << "Base 的缺省构造函数" << endl;}
public:int _num1;int _num2;
};class Son1 :public Base
{public:Son1(int num1, int num2) {Base(num1, num2);cout << "Son1的构造函数" << endl;}
};int main()
{Son1 so(1, 2);cout << so._num1 << "   " << so._num2 << endl;return 0;
}

运行结果:

发现Son1创建一个so对象经过了一下三个步骤:
①调用基类的缺省构造函数
②调用Son1类的构造函数
③调用基类的非缺省构造函数
第三步其实是由于在Son1函数体内我们留下了这样一句代码:Base(num1,num2);这句代码就是简单的执行Base的构造函数,没有特别意义,只是创造了一个匿名对象。这也是为什么_num1和_num2依然为随机值的缘故。

将以上两段代码合起来分析一下:
在创建子类对象so之前,一定会先调用基类的构造函数,因为有继承关系,子类对象需要包含从基类继承下来的成员变量,而编译器会先调用基类的构造函数构造一个无名对象,这样基类的成员变量才可以被子类的对象给包含。这一段总结就是说,子类创建对象之前,先创建个基类的无名对象,然后子类再将基类的无名对象的成员变量包含在其中。
当我们在子类后面用列表初始化写这一句代码Base(num1,num2)的时候,子类创建对象时,在进入到子类的构造函数体之前,就先执行了这句代码,创建一个无名对象,且用num1和num2分别初始化了无名对象的成员变量_num1和_num2(注意,子类继承的_num1和_num2这两个变量就是这个时候被定义并且用num1和num2初始化的),然后进入到子类的构造函数的函数体内,创建了子类对象,包含了在这之前调用父类的构造函数创建的无名对象的内容,即继承了基类的这些成员变量。
那么再来看第二段代码,由于在创建子类对象之前,得先创建一个基类的对象,然后子类的对象包含了基类对象的内容,即继承基类的成员变量。由于我们没有写调用基类的哪一个构造函数,那么编译器就会调用基类的默认构造函数,构造出来一个无名对象,之后,子类对象才创建出来,包含这个基类对象,即继承基类的成员变量。
那么对于第二段代码,如果我们将默认构造函数注释掉或者将默认构造函数设置成私有的话,那么编译器将无法调用基类的默认构造函数,那么子类的子对象将无法创建。如下代码:

class Base
{public:Base(int num1, int num2) {_num1 = num1;_num2 = num2;cout << "Base 的非缺省构造函数" << endl;}public:int _num1;int _num2;
private:Base(){cout << "Base 的缺省构造函数" << endl;}
};class Son1 :public Base
{public:Son1(int num1, int num2) {cout << "Son1的构造函数" << endl;}
};int main()
{Son1 so(1, 2);cout << so._num1 << "   " << so._num2 << endl;return 0;
}

运行结果:

发现编译器报错,说Base::Base无法访问,就是说无法访问Base的默认构造函数,运行到第28行停止的,原因就是子类创建子对象之前,“一定”(注意,是相对于我们这个代码来说是一定)要调用基类的默认构造函数,基类的默认构造函数无法访问,就报错。
上面的“一定”打了引号,是因为可以不让编译器调用基类的默认构造函数,做法就是我们告诉编译器调用基类的哪个构造函数创建基类的对象给子类的对象继承。如下代码:

class Son1 :public Base
{public:Son1(int num1, int num2) :Base(num1,num2){cout << "Son1的构造函数" << endl;}
};


红线部分就是列表初始化,可以让编译器知道,我们让它调用基类的Base(int num1,int num2)构造函数来创建无名对象给子类创建的对象继承。这个构造函数里面对_num1和_num2初始化了,所以子类对象将基类创建的无名对象继承过来以后,继承过来的成员变量已经被调用的Base(int,int)构造函数用num1和num2初始化过了。

至于是用列表初始化效率高还是赋值初始化效率高,应该答案已经出来了。用列表初始化,我们可以让基类在构建无名对象产生变量的时候就已经对成员变量进行初始化。而如果用赋值初始化,编译器得先调用基类的默认构造函数,然后再在子类构造函数体内对基类的成员变量进行赋值初始化。
再来看以下代码,类中的东西都只是声明,包括_a,_b,_c,_d这四个变量。由于_b和_c和_d都是常量和引用,由于常量和引用在定义的时候必须要有初始值,而当进入到子类的构造函数内部,这些成员变量都被编译器自动定义了,但是没初始值,编译器会报错,所以需要我们在编译器执行构造函数之前就对这些常量和引用进行定义,那么,就只能使用列表初始化,因为在执行构造函数之前,编译器会先执行初始化列表,而列表初始化就可以看成是定义(当然,只能是对以在类内声明的成员变量的定义),()里面的是给的初始值。

class Test
{public:Test(int num1, int num2, int num3) : _b(num1), _c(num1),_d(_a){_a = 10;//可列表初始化,可赋值初始化}
public:int _a;const int _b;//定义的时候必须有初始值,所以必须使用列表初始化int& _c;       //定义的时候必须有初始值,所以必须使用列表初始化const int& _d;
};int main()
{Test so(1, 2,3);cout << so._a << "   " << so._b << "   " << so._c << "   " << so._d << endl;return 0;
}

以上代码,列表初始化可以看成这些代码:
const int _b = num1;
int& _c = num1;
const int& _d = _a;
注意这个列表初始化是有问题的,首先是进入到构造函数之前(这时函数参数是已经绑定过确定的)执行的,_b没问题,就是保存num1的值,_c有问题,它是num1的引用,num1是形参,构造函数结束后形参生存期就到了,num1的空间里就是随机值了,所以主函数打印的时候_c是随机值,_d没问题,执行const int& _d = _a;的时候,_a还是随机值,但是执行完构造函数,_a就是10,所以主函数里打印的_d就是10。
运行结果:

总结:
不同成员变量初始化使用:
(1)类本身的成员变量(非继承而来的成员变量)既可以使用列表初始化又可以使用赋值初始化。
(2)继承而来的成员变量,要么用基类的构造函数和列表初始化告诉编译器调用基类的哪个构造函数,可以在基类的构造函数中对将要被创建的子类对象继承的成员变量进行初始化,要么就编译器默认的调用基类的默认构造函数,在子类的函数体中以赋值初始化的方式对继承的成员变量进行初始化。
必须使用列表初始化的三个情况:
由于列表初始化就是相当于定义,所以那些定义的时候必须需要初始值的,就用列表初始化。
(1)成员变量是const类型
(2)成员变量是引用类型
(3)如果基类没有缺省构造函数(必须使用列表初始化,告诉编译器调用其他基类构造函数)。或者说类本身有一个自定义类型变量,但是没有缺省构造函数(因为自定义类型的变量定义的时候,就是类的实例化,编译器默认调用这个类型的默认构造函数创建这个对象,但是没有缺省构造啊函数的话,就得用列表初始化告诉编译器用其他的构造函数创建这个自定义类型的变量)。

列表初始化和赋值初始化的使用注意事项相关推荐

  1. C语言中指针的初始化和赋值

    1.指针的初始化 指针初始化时,"="的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值(但是int*p=0;除外,该语句表示指针为空).此时,*p只是表示定 ...

  2. C语言指针的初始化和赋值

    1.指针的初始化 指针初始化时,"="的右操作数必须为内存中数据的地址,不可以是变量,也不可以直接用整型地址值(但是int*p=0;除外,该语句表示指针为空).此时,*p只是表示定 ...

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

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

  4. 初始化、赋值、默认初始化、列表初始化、类内初始值、直接初始化与拷贝初始化

    文章目录 初始化和赋值的区别 什么是默认初始化? 列表初始化 列表初始化的使用场景 不适合使用列表初始化的场景 类内初始值 混用string对象和C风格字符串 数组与vector对象 关于vector ...

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

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

  6. c++容器定义、初始化、赋值

    令C表示六个顺序容器类型期中之一(vector,deque,list,forward,string,array),以下详细说明定义和初始化以及赋值. 1.容器定义和初始化 (1)C  c;默认构造函数 ...

  7. 《C++ Primer 第五版》第二章(1-4小节)------基本内置类型,初始化和赋值及声明和定义,指针和引用,const和constexpr

    C++ Primer第二章的内容主要介绍了变量和基础类型,包括C++语言定义的基础内置类型.变量的定义及声明.符合类型如指针及引用的介绍和const及常量表达式constexpr的介绍,本次博客也从这 ...

  8. c++初始化成员列表_C++ 类构造函数初始化列表

    关注我们更多精彩等你发现! 构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式.例如: 上面的例子中两个构造函数的结果是一样的.上面的构造函数 ...

  9. 【C++】对象的定义、初始化与赋值

    定义 int a; 编译器将在栈上为变量a分配一块内存空间,但并不会对其进行写入,也就是说,当前x内的值是未知的. 读取未被初始化的值将引发不确定的行为:可能会直接让程序崩溃,也可能让系统的逻辑出现错 ...

最新文章

  1. Android 双目 单usb,【android9.0】无法打开usb uvc camera
  2. 150页书籍《PyTorch 深度学习快速入门指南》附PDF电子版
  3. Jmeter启动错误及解决方案
  4. 《Humans vs Computers》作者访谈录
  5. Exchange 2007 接收zip附件邮件时退信
  6. java解析纯真IP数据库
  7. 厉害了!这里藏着通关学霸的秘籍
  8. [记录] --- linux安装redis
  9. rfid阅读器的主要任务_RFID阅读器(读写器)的应用领域及其如何使用?
  10. 万兆网卡实际吞吐量_案例探索 | 千兆/万兆网卡每秒转发包数处理能力上限到底有多大?...
  11. java joda 获取utc时间_java – JodaTime – 如何获取UTC的当前时间
  12. STM32工作笔记0064---输入捕获实验
  13. AI芯片最重要的是什么?Arm中国:背后的软件生态
  14. Oracle集成基础安装包+补丁包
  15. PACS管理系统源码 PACS源码
  16. STM8S003F3控制LED
  17. 【怎样制作ppt】Focusky教程 | 调节音乐的音量(插入的音乐、背景音乐、录音的音量)
  18. mininet sflow 资料和经验
  19. canvas制作简单表格
  20. docker环境下mysql镜像启动后权限更改问题的解决

热门文章

  1. [Python图像处理] 五.图像融合、加法运算及图像类型转换
  2. [python知识] 爬虫知识之BeautifulSoup库安装及简单介绍
  3. iOS之深入解析类加载的底层原理:类如何加载到内存中
  4. Codeforces Round #552 (Div. 3) —— A. Restoring Three Numbers
  5. 百练2815 城堡问题
  6. Java 利用InetAddress类确定特殊Ip地址
  7. 【STM32】通用定时器(TIM2到TIM5)
  8. 【Tools】VMware Workstation 15.5 Pro安装详解
  9. 【数据库】PLSQL Developer出现ORA-12541TNS no listener错误解决方法
  10. 【Linux】一步一步学Linux——tr命令(55)