【C\C++】隐式类型转换

目录

前言

一、隐式类型转换规则

二、类类型构造时的隐式转换

三、explicit关键字

参考资料
 


前言

什么是隐式类型转换?

借用标准里的话来说,就是当你只有一个类型T1,但是当前表达式需要类型为T2的值,如果这时候T1自动转换为了T2那么这就是隐式类型转换。

int a = 0;
long b = a + 1; // int 转换为 long
 
if (a == b) {
    // 默认的operator==需要a的类型和b相同,因此也发生转换
}

一、隐式类型转换规则

在C语言中,自动类型转换遵循以下规则:

  1. 若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
  2. 转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
  3. a、若两种类型的字节数不同,转换成字节数高的类型
  4. b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
  5. 所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
  6. char型和short型参与运算时,必须先转换成int型。
  7. 在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。

注意:隐式类型转换只会发生在低精度->高精度这一过程中。如果反过来高精度->低精度,编译器一般会报错,但也可以通过强制类型转换完成转换(会发生截断)。

double a = 2.2;
int b = (int)a;

二、类类型构造时的隐式转换

std::string s = "hello c++";

Q:请问创建了几个string呢?

A:是1个或者2个

下面我们来详细了解这句代码执行时发生了什么。

对于C++11之前:

  1. 首先"hello c++"const char[N]类型的,不过它在表达式中于是退化成const char *
  2. 然后因为s实际上是处于“声明即定义”的表达式中,因此适用的只有复制构造函数,而不是重载的=
  3. 因此等号的右半边必须也是string类型
  4. 因为正好有从const char *string的转换规则,因此把它转换成合适的类型
  5. 转换完会返回一个新的string的临时量,它会作为参数调用复制构造函数
  6. 复制构造函数调用完成后s也就创建完毕了。

整个过程创建了s和一个临时量,一共两个string。

对于C++11:

  1. 前面步骤相同,字符串字面量隐式转换成string,创建了一个临时量
  2. 临时量是个右值,所以绑定给右值引用,因此移动构造函数被选择
  3. 临时量里的数据移动到s里,s创建完成

移动语义减少了不必要的内部数据的复制,但是临时量还是会被创建的。

对于C++17:

  1. 编译器发现表达式是string的复制初始化
  2. 右侧是表达式会隐式转换产生一个string的纯右值用于初始化同一类型的s
  3. 判断复制构造函数是否可用,然后发现符合复制省略的条件
  4. 寻找string里是否有符合要求的构造函数
  5. 找到了string::string(const char *),于是直接调用
  6. s初始化完成

在c++17下只会创建一个string对象,这比移动语义更加高效。这也是为什么题目的答案既可以是1也可以是2的原因。

三、explicit关键字

隐式转换存在许多风险,那如何能够禁止隐式转换的发生呢。C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。

explicit关键字作用如下:

  1. 指定构造函数或转换函数 (C++11起)为显式, 即它不能用于隐式转换复制初始化.
  2. explicit 指定符可以与常量表达式一同使用. 函数若且唯若该常量表达式求值为 true 才为显式. (C++20起)
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 进行直接初始化
}

参考资料

  1. 彻底理解c++的隐式类型转换 - apocelipes - 博客园 (cnblogs.com)
  2. 深入理解隐式类型转换_隐式转换-CSDN博客
  3. explicit 说明符 - cppreference.com
  • 30
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值