目录
前言
什么是隐式类型转换?
借用标准里的话来说,就是当你只有一个类型T1,但是当前表达式需要类型为T2的值,如果这时候T1自动转换为了T2那么这就是隐式类型转换。
int a = 0;
long b = a + 1; // int 转换为 long
if (a == b) {
// 默认的operator==需要a的类型和b相同,因此也发生转换
}
一、隐式类型转换规则
在C语言中,自动类型转换遵循以下规则:
- 若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
- 转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
- a、若两种类型的字节数不同,转换成字节数高的类型
- b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
- 所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
- char型和short型参与运算时,必须先转换成int型。
- 在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。
注意:隐式类型转换只会发生在低精度->高精度这一过程中。如果反过来高精度->低精度,编译器一般会报错,但也可以通过强制类型转换完成转换(会发生截断)。
double a = 2.2;
int b = (int)a;
二、类类型构造时的隐式转换
std::string s = "hello c++";
Q:请问创建了几个string呢?
A:是1个或者2个
下面我们来详细了解这句代码执行时发生了什么。
对于C++11之前:
- 首先
"hello c++"
是const char[N]
类型的,不过它在表达式中于是退化成const char *
- 然后因为s实际上是处于“声明即定义”的表达式中,因此适用的只有复制构造函数,而不是重载的=
- 因此等号的右半边必须也是
string
类型- 因为正好有从
const char *
到string
的转换规则,因此把它转换成合适的类型- 转换完会返回一个新的
string
的临时量,它会作为参数调用复制构造函数- 复制构造函数调用完成后s也就创建完毕了。
整个过程创建了s和一个临时量,一共两个string。
对于C++11:
- 前面步骤相同,字符串字面量隐式转换成string,创建了一个临时量
- 临时量是个右值,所以绑定给右值引用,因此移动构造函数被选择
- 临时量里的数据移动到s里,s创建完成
移动语义减少了不必要的内部数据的复制,但是临时量还是会被创建的。
对于C++17:
- 编译器发现表达式是string的复制初始化
- 右侧是表达式会隐式转换产生一个string的纯右值用于初始化同一类型的s
- 判断复制构造函数是否可用,然后发现符合复制省略的条件
- 寻找string里是否有符合要求的构造函数
- 找到了
string::string(const char *)
,于是直接调用- s初始化完成
在c++17下只会创建一个string对象,这比移动语义更加高效。这也是为什么题目的答案既可以是1也可以是2的原因。
三、explicit关键字
隐式转换存在许多风险,那如何能够禁止隐式转换的发生呢。C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。
explicit关键字作用如下:
struct A
{
A(int) { } // 转换构造函数
A(int, int) { } // 转换构造函数(C++11)
operator bool() const { return true; }
};
struct B
{
explicit B(int) { }
explicit B(int, int) { }
explicit operator bool() const { return true; }
};
int main()
{
A a1 = 1; // OK:复制初始化选择 A::A(int)
A a2(2); // OK:直接初始化选择 A::A(int)
A a3 {4, 5}; // OK:直接列表初始化选择 A::A(int, int)
A a4 = {4, 5}; // OK:复制列表初始化选择 A::A(int, int)
A a5 = (A)1; // OK:显式转型进行 static_cast
if (a1) ; // OK:A::operator bool()
bool na1 = a1; // OK:复制初始化选择 A::operator bool()
bool na2 = static_cast<bool>(a1); // OK:static_cast 进行直接初始化
// B b1 = 1; // 错误:复制初始化不考虑 B::B(int)
B b2(2); // OK:直接初始化选择 B::B(int)
B b3 {4, 5}; // OK:直接列表初始化选择 B::B(int, int)
// B b4 = {4, 5}; // 错误:复制列表初始化不考虑 B::B(int,int)
B b5 = (B)1; // OK:显式转型进行 static_cast
if (b2) ; // OK:B::operator bool()
// bool nb1 = b2; // 错误:复制初始化不考虑 B::operator bool()
bool nb2 = static_cast<bool>(b2); // OK:static_cast 进行直接初始化
}