C初始化列表与类型转换全解析
C++初始化列表与类型转换全解析
🔥个人主页: ****
📖个人专栏:************************************************************************************************************************************************************************************************************************************************************ ****************************************************************************************************************************************************************************************************************************************************************
⭐️人生格言:不试试怎么知道自己行不行
🎥胡萝卜3.0🌸的简介:
前沿:在类和对象(中)的学习中,我们学习了6个默认成员函数:构造函数、析构函数、拷贝构造、赋值运算符重载、const成员函数、取地址运算符重载。类和对象(中)我们差不多学习了70%~80%,类和对象(下)是学习类和对象的边边角角的内容。ok,话不多说,直接开始
一、再探构造函数
**我相信会有很多UU看到这个标题会感到很疑惑,在类和对象(中)中,我们不是已经学习了构造函数嘛,为什么还要再探构造函数呢?**其实是在前面并没有将构造函数的内容完全学完,还有一些内容没有学。
我们先来回顾一下前面的学习——
我们说默认构造函数是在对象实例化时,自动调用相应的构造函数,默认构造函数有三种:无参构造、全缺省构造、我们不写编译器自动生成的默认构造,并且如果我们不显示写,编译器自动生成的默认构造函数对内置成员变量不做处理,对自定义类型成员会去调用它的构造函数,如果没有构造函数,就编译错误。
1.1 初始化列表
之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表
1.1.1 使用方式
初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。
示例:
1.1.2 细节部分
每个成员变量在初始化列表中只能出现一次。(这是什么意思?)
上面这种写法是错误的,在初始化列表中,每个成员变量只能出现一次!!!
接下来,我们来看一个比较难理解的一句话——
语法理解上初始化列表可以认为是每个成员变量定义初始化的地方!!!
这是啥意思?ok,我们先不看这句话。
我们先来想一想为什么祖师爷在创建C++语言时,既有函数体赋值,又有初始化列表呢?
其实是因为有些成员变量必须在初始化列表中初始化,引用成员变量,const成员变量,没有默认构造的自定义类型成员变量,必须放在初始化列表位置进行初始化,否则编译报错。
这里有个问题:
上图中的代码是对对象整体定义,开空间
**我们必须给每个成员变量找个定义的地方,为什么?**因为有些成员变量必须在定义时初始化,所以,基于以上原因,就有了初始化列表,普通成员可以在函数体内赋值,也可以在初始化列表中初始化,但引用成员变量,const成员变量,没有默认构造的自定义类型成员变量,必须放在初始化列表位置进行初始化。
建议:在初始化列表中一次性初始化,并且优先使用初始化列表。
其实上面的初始化列表中,有一个成员变量的初始化有些问题,那就是引用成员变量,_ref引用的x为局部变量,出了作用域就销毁了,如果d1中访问引用,就会有些问题,所以在****初始化引用成员,不能引用普通的形参,最好引用外面的变量,将成员变量的引用域外面的一个变量关联起来。
我们再来看一种错误写法:
记住:引用成员变量,const成员变量,没有默认构造的自定义类型成员变量,必须放在初始化列表位置进行初始化,其他成员变量都可以。
ok,既然有了上图中写法,那我们是不是解决前面的一些问题:
在前面的学习中,我们写过一个MyQueue类中有Stack类的自定义类型
上面的这种是理想化,若Stack类中无默认构造呢?编译器会报错,MyQueue类中就必须写构造函数
那我们是按照下面的写法吗?
ok,通过上面的学习,我们已经知道在函数体内的是赋值,那我们直接将n赋值给_PushSt和_PopSt,这~对吗?这很明显不是正确的初始化。
ok,通过上面对Time类的初始化,我们就知道如何书写了:
现在我们再回过头来看一下为什么说初始化列表是每个成员变量定义初始化的地方?
我们必须给每个成员变量找个定义的地方,为什么?
因为有些成员变量必须在定义时初始化,所以,基于以上原因,就有了初始化列表,普通成员可以在函数体内赋值,也可以在初始化列表中初始化,但引用成员变量,const成员变量,没有默认构造的自定义类型成员变量,必须放在初始化列表位置进行初始化
引用成员变量,const成员变量,没有默认构造的自定义类型成员变量为什么必须在初始化列表初始化?
**因为它们的特点是必须在定义时初始化,必须在定义时初始化,**那谁是它们定义的地方呢?初始化列表是它们定义的地方,所以它们必须在初始化列表中初始化
1.1.3 缺省值
C++11支持在声明的位置给缺省值
当我写出上面的代码,就会有人认为在声明的时候,_year,_month,_day被初始化了,确实是被初始化了,但是这里不是初始化,只有在定义时的才是初始化。
缺省值就像前面的缺省参数一样
这时候就会有同学想问了,那上面所说的引用成员变量,const成员变量,没有默认构造的成员变量是怎么样的?
总结:
- 一般情况下,建议尽量使用初始化列表显示初始化。
- 如果没有初始化列表的值,尽量给缺省值,不建议既无初始化列表,又无缺省值
只有下面的这种情况可以不写:
那什么时候使用函数体呢?
初始化列表完了,还会做一些检查,例如还会做更深层次的初始化——
总结:
- 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致
下⾯程序的运行结果是什么(D)
A. 输出 1 1 B. 输出 2 2 C. 编译报错 D. 输出 1 随机值 E. 输出 1 2 F. 输出 2 1
二、类型转换
C++支持内置类型隐式类型转换为类类型对象(自定义类型),需要有相关内置类型为参数的构造函数。
在前面的学习中,我们学习过内置类型转换成内置类型
其实这里的转换不是随便转的,而是任何类型之间有一定关联才能转换
ok,接下来我们来看内置类型如何转换成自定义类型:
class A
{
public:
A(int a)
:_a1(a)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
//构造
A a1(1);
//隐式类型转换
A a2 = 1;
return 0;
}
那这里为什么可以进行隐式类型转换呢?
既然有了上面的操作,那么下面的操作就可以实现了:
应用场景:
通过上面的学习,我们知道临时类型转换会经历两个步骤:
- 内置类型为参数构造临时对象
- 临时对象拷贝构造。
但是编译器会对上面的步骤进行优化:
但是,下图中的不能优化:
ref3不能直接引用整型1
ref3
的类型是const A&
(A对象的引用)1
的类型是int
- 编译器不能违反类型系统的基本规则
所以还是走两个步骤:1、内置类型为参数构造临时对象,2、临时对象拷贝构造。
如果我们不想让构造函数支持隐式类型转换,我们可以在构造函数前面加 explicit 就不再支持隐式类型转换。
上面的都是一个参数进行转换,如果是多个参数可不可以?
其实是可以的:
既然内置类型可以转换成自定义类型,那自定义类型可以转换成自定义类型吗?
当然是可以的,自定义类型的对象之间可以隐式转换,需要相应的构造函数支持。默认情况下,自定义类型之间是不能直接转换的,但是若有相应的构造函数也就构成了关联,就可以转换了