隐式类型转换到底是个啥

让我们看看编译器到底在哪些情况下会去做隐式转化

类型转换

在C++语言中,某些类型之间有关联,如果两种类型有关联,那么当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代。

举个例子就是:

int a = 3;
double b = 3.14;
int c = a + b;

这里的会先将 a 转换成 double 类型,再和 b 相加,因为最后需要构造 c, 再从 double 类型转换成 int 类型, 而这些类型转换的过程,程序员是不需要介入的,都是编译器自己完成的,而类似这样的操作就是 隐式转换

当然 类型转换 除了 隐式转换, 还有 显式转换,这篇文章主要介绍 隐式转换,而 显式转换 先简单知道有哪些,之后会有具体介绍

何时发生隐式类型转化

在下面这些情况下,编译器会自动地转换运算对象的类型:

  • 在大多数表达式中,比 int 类型小的整数值首先提升为较大的整数类型
  • 在条件中, 非布尔值转换成布尔类型
  • 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象类型
  • 如果算数运算或关系运算的运算对象有多种类型,需要转换成同一种类型
  • 函数调用时也会发生类型转换

算术转换

算术转换的含义是把一种算数类型转换成另一种算数类型。

类型 含义 最小尺寸
bool 布尔类型 未定义
char 字符 8位
wchar_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位
int 整形 16位
long 长整型 32位
long long 长整型 64位
float 单精度浮点数 6位有效数字
doubel 双精度浮点数 10位有效数字
long doubel 扩展精度浮点数 10位有效数字

算术转换的规则:运算符的运算对象将转换成更宽的类型,这些转换的前提是转换后的类型要能容纳原类型所有可能的值

最优先的规则:

  • 小整数类型(bool, char ,short)转化为较大整数类型 对于 bool, char, signed char, unsigned char, shortunsigned short 等类型只要能存进 int ,都会提升为 int, 否则提升为 unsigned int
  • 较大的char类型( wchar_t, char16_t, char32_t)提升为 int, unsigned int, long, unsigned long, long long, unsigned long long 中最小的一种类型

其次:

  • 当不同类型的数据进行操作时,首先转换成相同的数据类型,然后进行操作,根据类型的 尺寸 大小来转换

举个例子:

3.14159L + 'a';   // 'a' 提升为 int, 然后该 int 值转换成 long double

上面是基础规则,但是如果某个运算对象的类型时无符号类型,可以分为下面2种情况:

  • 无符号整型+无符号整型 (带符号整型+带符号整型)
    小类型的运算对象转换成较大的类型
  • 无符号整型+带符号整型
    若无符号整型不小于带符号整型,那么带符号的运算对象转换成无符号的
    带符号的类型大于无符号的类型时,此时转换的结果依赖于机器。如果无符号类型的所有值都能存在该带符号类型中,则无符号转换成带符号类型,如果不能,带符号类型转换成无符号类型

其他隐式类型转化

数组转换成指针

数组自动转换成指向数组首元素的指针

int ia[10];   // 含有10个整数的数组
int *p = ia;  // ia 转换成指向数组首元素的指针

指针的转换

  • 常量整数值 0 或者字面值 nullptr 能转换成任意指针类型
  • 指向任意非常量的指针都能转换成 void*
  • 指向任意对象的指针都能转换成 const void*
  • 派生类到基类的类型转换,因为在派生类对象中含有于其基类对应的组成部分,所以我们能把生类的对象当成基类的对象来使用,而且我们也能将基类的指针或引用绑定到派生类对象中的基部分中
BaseClass A;            // 基类对象
Subclass B;             // 派生类对象
BaseClass *p = &A;      // p 指向基类对象
p = &B;                 // p 指向 B 的 BaseClass 部分
BaseClass &r= B;        // r 绑定到 B 的 BaseClass 部分

转化成布尔类型

存在一种从算术类型或指针类型向布尔类型自动转换的机制,如果指针或算数类型的值为 0s, 转换结果为 false, 否则转换结果为 true

转换成常量

允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也适用,但是不允许const 转换成非常量

int i;
const int &j = i;         // 正确
const int *p = &i;        // 正确
int &r = j;               // 错误,不允许const 转换成非常量

类类型定义的转换(用户定义的类型转换)

定义类型转换的规则,编译器自动执行转化,注意 编译器每次只能执行一次类类型的转换

具体有2种

  • 转换构造函数 : 一个将其他对象转换成类类型的构造函数
  • 类型转换运算符: 一个是将类类型转换成其他类型的特殊成员函数

转换构造函数

如果构造函数只接受一个实参,则它实际上定义了转化为此类型的隐式转换机制,而这种构造函数被称为 转换构造函数, 简单看一下例子

 class SmallInt
 {
 private:
     int m_value;
 public:
     SmallInt(int value)
     {
         m_value = value < 0? 0 : (value > 255? 255: value);
     }
     int getValue() {return m_value;}
 };

我这里定义了一个 SmallInt 的类, 并提供了一个只接受一个实参的构造函数,如果出现下面的代码

SmallInt si = 200;

编译器会把 200 这个值转换成一个临时的 SmallInt 对象,然后赋值给 si,这个转换就是类类型转换的转换构造

类型转换运算符

类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型,类型转换函数的一般形式如下所示:

operator type() const;  // type 表示某种类型

类型转换运算符可以面向任意类型(除了void之外)进行定义, 只要该类型能作为函数的返回类型

可作为函数返回类型:
函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针

注意

而这2种转换的函数都可以用 explicit 修饰,从而禁止编译器偷偷调用,隐式转换,从而产生一些意外的结果

显式转换

命名的强制类型转换

一个命名的强制类型转换代码具体如下形式:

cast_name<type>(expression);

而C++中提供4种强制类型转换的方式

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast

旧式的强制类型转化

旧式的代码格式如下:

type (expr);   // 函数形式的强制类型转换
(type) expr;   // C 语言风格的强制类型转换

旧式的结果和命名形式的效果是一样的,但是在表现形式上没有那么清晰明了,一旦出现问题,追踪起来可能更加困难

隐式转换相关的内容全部总结如上了,关于显式转换这里先介绍这么多,之后会有一片更详细的总结