类型转换
在 C++ 语言中,某些类型之间有关联。如果两种类型有关联,那么当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代。
隐式转换
隐式转换是指编译器会自动地转换运算对象的类型:
在大多数表达式中,比 int 类型小的整型值首先提升为较大的整数类型。
在条件中,非布尔值转换成布尔值。
初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。
函数调用时也会发生类型转换。
算数转换
整型提升:负责把小整数类型转换成较大的整数类型。对于 bool、char、signed char、 unsigned char、short 和 unsigned short 等类型来说,只要它们所有可能的值都能存在 int 里,它们就会提升成 int 类型。
无符号类型的运算对象:如果一个运算对象时无符号类型、另外一个运算对象时带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的,
其他隐式类型转换
数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。当数组被用作 decltype 关键字的参数,或者作为取地址符(&)、sizeof 及 typeid 等运算符的运算对象时,上述转换不会发生。同样,如果一个引用来初始化数组,上述转换也不会发生。
指针的转换:C++ 还规定了几种其他的指针转换方法,包括常量整数值 0 或者字面值 nullptr 能转换成任意指针类型;指向任意非常量的指针能转换成 void*;指向任意对象的指针能转换成 const void*。
转换成布尔类型:存在一种从算术类型或指针类型向布尔类型自动转换的机制。如果指针或算术类型的值为0,转换结果是 false;否则转换结果是 true。
转换成常量:允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样。也就是说,如果 T 是一种类型,我们就能将指向 T 的指针或引用分别转换成指向 const T 的指针或引用。
类类型定义的转换:类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。
C++ 中 explicit 的作用
在 C++ 中,explicit 通常用于构造函数的声明中,用于防止隐士转换。当将一个参数传递给构造函数时,如果构造函数声明中使用了 explicit 关键字,则只能使用显示转换进行转换,而不能进行隐式转换。
这种机制可以防止编译器自动执行预期外的类型转换,提高代码的安全性。
例如,考虑下面的代码:
class MyInt{
public:
MyInt(int n): num(n) {}
private:
int num;
}
MyInt fun(MyInt n)
{
return n;
}
int main()
{
MyInt a = 100;
fun(100);
}
注意 MyInt a = 100;这段代码有两个步骤:
1. int 类型的 100 先隐式类型转换为 MyInt的一个临时对象
2. 将隐式类型转换后的临时对象再通过复制构造函数生成 a。
但这样有时候会出现问题,例如在外面是使用fun(100)调用fun函数时,不出出错,但是我们有时候不期望int类型的变量可以调用fun函数。如果我们希望只接受 MyInt 类型的参数,就可以将构造函数声明加上 explicit。
显示转换
static_cast
用法:static_cast <new_type>(expression)
任何具有明确定义的类型转换,只要不包含底层 const,都可以使用 static_cast。
例如通过将一个运算对象强制转换成 double 类型就能使表达式执行浮点数除法:
int j;
double s;
double s = static_cast<double> (j);
这种方法没有运行时类型检查来保证转换的安全性:
进行上转换是安全的:把派生类的指针或引用转换成基类表示
进行下行转换,由于没有动态类型检查,所以是不安全的:把基类的指针或引用转换位派生类表示。
const_cast
const_cast 只能改变运算对象的底层 const
常量指针转换为非常量指针,并且仍然指向原来的对象。常量引用被转换为非常量引用,并且仍然指向原来的对象。可以去掉类型 const 或 volatile 属性。
const char *pc;
char *p = const_cast<char *>(pc);
//正确:但通过 p 写值是未定义的行为
对于将常量对象转换成非常量对象的行为,我们一般称其为“去掉 const 性质”。
一旦我们去掉了某个对象的 const 性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量,使用强制类型转换获取写权限是合法的行为。然而如果对象是一个常量,再使用 const_cast 执行写操作就会产生未定义的后果。
const_cast 常常用于有函数重载的上下文中。
reinterpret_cast
reinterpret_cast 通常为运算对象的位模式提供较低层次上的重新编译。
可以将整型转换位指针,也可以把指针转换为数组;可以在指针和引用里进行肆无忌惮的转换,但是平台移植性比较差。
int *p;
char *pc = reinterpret_cast<char *>(p);
dynamic_cast
dynamic_cast 在 C++ 中主要应用于父子类层次结构中的安全类型转换,它在运行时执行类型检查,因此相比于 static_cast,它更加安全。
在进行下行转换时,dynamic_cast 具有类型检查(信息在虚函数中)的功能,比 static_cast 更安全。
此外,转换后必须是类的指针、引用或者 void*, 基类要有虚函数,可以交叉转换。
要使用 dynamic_cast 有效,基类至少需要一个虚函数。因为 dynamic_cast 只有在基类存在虚函数表的情况下才有可能将基类指针转化为子类。
dynamic_cast 的底层原理
dynamic_cast 的底层原理依赖于运行时类型信息(RTTI,Runtime Type Information)。
C++ 编译器在编译时为支持多态的类生成 RTTI,它包含了类的类型信息和类层次信息。
我们都知道当使用虚函数时,编译器会为每个类生成一个虚函数表(vtable),并在其中存储指向虚函数的指针。伴随虚函数表的还有 RTTI,这些辅助的信息可以用来帮助我们运行时识别对象的类型信息。
每个多态对象都有一个指向其 vtable 的指针,称为vptr,vtable表中通常关联着 RTTI
dynamic_cast 就是利用 RTTI 来指向运行时类型检查和安全类型转换,执行过程大概是:
首先,dynamic_cast 通过查询对象的 vptr 来获取其 RTTI。
然后,dynamic_cast 比较请求的目标类型与从 RTTI 获得的实际类型。如果目标时实际类型或其基类,则转换成功。
如果目标类型是派生类,dynamic_cast 会检查类层次结构,以确定转换是合法的。如果在类层次结构中找到了目标类型,则转换成功;否则,转换失败。
当转换成功时,dynamic_cast 返回转换后的指针或引用
如果转换失败,对于指针类型,dynamic_cast 返回空指针;对于引用类型,dynamic_cast 会抛出一个 std::bad_cast 异常。
最后,因为 dynamic_cast 依赖于运行时类型信息,它的性能低于其他类型转换操作,而static_cast 时编译器静态转换,编译时期就完成了。