让我们看看编译器到底在哪些情况下会去做隐式转化
类型转换
在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
,short
和unsigned 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 部分
转化成布尔类型
存在一种从算术类型或指针类型向布尔类型自动转换的机制,如果指针或算数类型的值为 0
s, 转换结果为 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 语言风格的强制类型转换
旧式的结果和命名形式的效果是一样的,但是在表现形式上没有那么清晰明了,一旦出现问题,追踪起来可能更加困难
隐式转换相关的内容全部总结如上了,关于显式转换这里先介绍这么多,之后会有一片更详细的总结