【C++】类和对象(九):再谈构造函数
大家好,我是苏貝,本篇博客带大家了解C++的构造函数2,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
目录
- 1 构造函数体赋值
- 2 初始化列表
- (A) 特性
- (B) 必须在初始化列表里显示的3种情况
- a. Const成员变量
- b. 引用成员变量
- c. 自定义类型的成员变量(且该类没有默认构造函数时)
- (C) 单参数的构造函数支持隐式类型的转换
- a. 非引用
- b. 引用
- c. 作用
- 3 新增:非单参数的构造函数支持隐式类型转换(C++11)
- 4 explicit关键字
1 构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值,如下图:_year被赋值2次
2 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。位置:放在构造函数中间
初始化列表是将每一个成员变量的定义初始化的位置
(A) 特性
- 所有的成员变量在定义对象时都会走初始化列表
现在修改初始化列表,问:_month和_day有没有走初始化列表?
有,只要是成员变量,在定义对象时都要初始化,即都要走初始化列表,即使我们没有显示写出来。但是,如果有成员变量(内置类型)没有显示写,虽然也初始化了,但是是用随机值初始化的。
- 缺省值
我们之前有说过,C++98里,编译器生成的无参的构造函数对内置类型不做处理,对自定义类型调用它的构造函数。但是对内置类型不做处理也不好,因此在C++11中,新增一个语法:可以在类的成员变量后加缺省值。
问:为什么叫缺省值?
因为这些值就是给初始化列表的缺省值
上图中,初始化列表里,将_year初始化为2020,将_month用缺省值初始化为1,将_day初始化为随机值
- 初始化列表中成员变量后面的括号里可以是malloc,因为malloc的返回值是地址(如果成功)
既然可以在括号里写malloc,那么也可以在成员变量声明时的缺省值这样写
-
尽量使用初始化列表初始化,因为不管是否使用初始化列表,对于成员变量,一定会先使用初始化列表初始化,再进入构造函数体内。
-
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
问:下图的结果是什么?
A.输出1 1 B. 程序崩溃 C. 编译不通过 D. 输出1 随机值
答案:D
因为成员变量在初始化列表中的初始化顺序是和成员变量声明顺序相同,所以先初始化a2,再为a1。初始化a2时,a1还没被初始化,为随机值,因此a2是随机值。再用a = =1初始化a1,所以a1==1
(B) 必须在初始化列表里显示的3种情况
a. Const成员变量
对于const修饰的成员变量,因为不能被修改,所以一定要初始化给值。它可以在声明的时候就给缺省值,也可以在初始化列表里给值
b. 引用成员变量
引用必须在定义时初始化
c. 自定义类型的成员变量(且该类没有默认构造函数时)
我们知道,编译器生成的无参的构造函数对内置类型不做处理,对自定义类型会调用它的默认构造函数。上图中的Date类里我们写了带参的构造函数。问:会不会调用类A的默认构造函数?会不会编译错误?
会调用类A的默认构造函数,不会编译错误。
因为定义对象时一定要对成员变量初始化,即成员变量都要走初始化列表。所以对自定义类型的成员变量,要调用它的默认构造函数。A类中确实有默认构造函数,因此不会编译错误
也就是说如果类A中没有默认构造函数,那么会编译错误。
除了给类A加默认构造函数外,还有没有什么别的方法能处理这个错误?
在初始化列表里显示,下图的aa(2)中2是实参,这里的使用aa和我们在main函数里新建一个A类的对象(A aa1(1))一样
© 单参数的构造函数支持隐式类型的转换
这里的单参数的构造函数有2种含义:只有一个参数的构造函数,和除第一个参数无默认值外,其余均有默认值的构造函数
单参数的构造函数支持隐式类型转换,这是什么意思?
如:类A中的构造函数是单参的,该参数的类型是type(int/double等),那么在类外可以用type类型的变量/字面量来构造一个类A 的临时变量。没有理解的话,看看下面的例子吧
a. 非引用
先来看只有一个参数的构造函数
对aa1我们没有疑问,这就是普通的定义对象。可是aa2很奇怪,为什么可以将3赋值给aa2?
其实不是将3赋值给aa2,而是用3构造了一个类型为类A的临时对象(为什么支持?因为单参数的构造函数支持隐式类型转换),再将这个临时对象拷贝给aa2
我们想证明,A aa2=3;这条语句真的是先新建了一个临时对象,再将临时对象拷贝给aa2。所以下图的结果应该打印了“拷贝”才对
结果为空,为什么?是因为编译器优化了,同一个表达式连续步骤的构造,一般会合二为一。即直接用3构造了aa2,省略了将2先构造一个类A的临时变量,再将临时变量拷贝给aa2的过程。
那是说,我们不能证明先用3构造一个类A 的临时对象,再将临时对象拷贝给aa2吗?不是的,在后面的引用里我们可以证明
除第一个参数无默认值外,其余均有默认值的构造函数,也支持隐式类型转换
b. 引用
上面我们想证明A aa2=3;这条语句是先构造再拷贝,但是由于编译器的优化,失败了。下面我们来用引用证明一下
上图中,为什么aa1报错,aa2不报错?
因为类A的构造函数是单参的,支持隐式类型转换,所以用3构造了一个类A的临时对象,由于临时对象具有常性,aa1没有被const修饰,从const到非const,权限放大,报错。aa2是从const到const,权限平移,成功。
上面就能证明确实是产生了临时对象,既然产生了临时对象,那么自然的,就是用临时对象拷贝
c. 作用
我们想给类A的自定义类型的成员变量_b一个初始值2,如何给?看下图
可是这蛮费劲的,要先定义一个全局对象,再将全局对象作为初始化列表中_b的缺省值。我们学了单参数的构造函数支持隐式类型转换后,可以直接将2给_b
如果我们想建立一个栈,栈里面存的是类A(构造函数是单参)的对象,我们以前需要先新建对象,再用Push函数将对象插入栈中。可现在我们只需要将与类A的构造函数参数的类型相同的变量/字面量当作Push函数的实参,即可实现将类A的对象存入栈中。这就方便许多了
总结:
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
3 新增:非单参数的构造函数支持隐式类型转换(C++11)
在C++98规定中,只有单参数的构造函数能支持隐式类型转换。但到C++11中,非单参数的构造函数也能支持隐式类型转换。不过需要使用{ }
4 explicit关键字
用explicit修饰构造函数,那么即使构造函数是单参的,也禁止隐式类型转换
好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️