旧式的强制类型转换
在早期C/C++中,显式地进行强制类型的转换有以下两种形式:
type (expr) ; //函数形式的强制类型转换
(type) expr; //C语言风格的强制类型转换
比如:
char c = '12';
int b = (int)c;
float f = float(b);
C++的新式强制类型转换
命名的强制类型转换具有如下形式:
cast-name (expr);
cast-name可以是:static_cast、const_cast、dynamic_cast、reinterpret_cast
type是转换的目标类型
避免强制类型转换
强制类型转换干扰了正常的类型检查,所以强烈建议程序员避免使用强制类型转换
这个建议对于reinterpret_cast尤其使用,因为此类类型转换总是充满了风险
一、static_cast
功能:用来强迫隐式类型转换,或称为显式的类型转换
static_cast还可以将一个左值转换为右值引用
例如:
将非const对象转换为const对象(但是不能将底层const对象转换为非const对象,这个只有const_cast才能做到)
将int转换为double,反之亦然
也可以将void*指针转换为其他类型指针,将pointer-to-base转换为pointer-to-derived
注意事项:
使用static_cast会去除编译器的警告,但是我们必须自己清楚转换有效,否则转换无效可能会产生未定义的结果
演示案例
我们将一个整型对象转换为double类型
int i = 10, j = 1;
double slope1 = i / j; //一般的强制类型转换,编译器可能会报出警告
double slope2 = static_cast<double>(j) / j; //显式地强制类型
转换,编译器无警告
当我们把较大的算术类型赋值给较小的类型时,一般的强制类型转换编译器会发出警告
但是当我们使用static_cast后,编译器就不会报出警告
演示案例
static_cast对于编译器无法自动执行的类型转换也非常有用
例如我们可以使用static_cast找回存在于void*指针中的值:
double num = 3.14;
void *p = # //任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p); //将void*转换回初始的指针类型
二、const_cast
功能:用来将对象的常量性移除
注意事项:
只能改变运算对象的底层const
const_cast只能改变表达式的常量属性,而不能改变表达式的数据类型
演示案例
将底层const对象转换为非const对象
const char *pc;
//正确,但是通过p写值是未定义的行为
char *p = const_cast<char*>(pc);
当我们去掉某个对象的const性质之后,编译器就不再阻止我们对该对象进行写操作了,因此写操作会产生未定义的后果
演示案例
const_cast只能改变表达式的常量属性,而不能改变表达式的数据类型
const char* cp;
//错误,static_cast不能去除const性质
char*q = static_cast<char*>(cp);
//正确,字符串常量值可以转换为string类型
static_cast<string>(cp);
//错误,const只能去除const性质,但是不能进行数据类型的转换
const_cast<string>(cp);
三、reinterpret_cast
功能:通常为运算对象的位模式提供较低层次上的重新解释
例如将一个pointer-to-int转换成一个int
使用reinterpret_cast是非常危险的,我们必须自己编写正确的代码
reinterpret_cast本质上依赖于机器。要想安全地使用reinterpret_cast必须对设计的类型和编译器实现转换的过程都非常了解,
演示案例
例如有下面的转换
int *ip;
char *pc = reinterpret_cast(ip);
我们必须牢记pc所指的对象是一个int而不是字符
如果把pc当成普通的字符指针使用那么就会产生未定义的后果。例如:
int *ip;
char *pc = reinterpret_cast(ip);
//编译器虽然不报错,但是后果未定义
string str(pc);
四、dynamic_cast
dynamic_cast支持运行时类型识别,使用形式如下所示:
在第一种形式下:e必须是一个有效的指针
在第二种形式下:e必须是一个左值
在第三种形式下:e不能使左值
type:必须是一个类类型,并且通常情况下该类型应该含有虚函数
比如:
dynamic_ cast(e)
dynamic_ cast (e)
dynamic_ cast (e)
在上面的所有形式中,e的类型必须符合以下三个条件中的任意一个:
e的类型是目标type的公有派生类
e的类型是目标type的公有基类
e的类型就是目标type的类型
出错时的返回值:
如果一条dynamic_cast语句的转换目标是指针类型且失败了,则结果为0
如果一条dynamic_cast语句的转换目标是引用类型且失败了,则dynamic_cast运算符抛出一个bad_cast异常
运行时类型识别(RTTI)的功能由两个运算符实现:
typeid运算符:用于返回表达式的类型
dynamic_cast运算符:用于将基类的指针或引用安全地转换成派生类的指针或引用
当我们将这两个运算符用于某种类型的指针或引用,并且该类含有虚函数时,运算符将使用指针或引用所绑定对象的动态类型
这两个运算符适用于以下的情况:
我们想使用基类对象的指针或引用执行某个派生类操作,并且该操作不是虚函数
一般来说,只要有可能我们应该尽量使用虚函数。当操作被定义为虚函数时,编译器将根据对象的动态类型自动地选择正确的函数版本。然而,并非任何时候都能定义一个虚函数,假设我们无法使用虚函数,则可以使用一个RTTI运算符
另一方面,与虚函数相比,使用RTTI运算符蕴含着更多潜在的风险:程序员必须清楚地知道转换的目标类型并且必须检查类型转换是否被成功执行。
指针类型的dynamic_cast
假设Base含有虚函数,且Derived是Base的公有派生类
如果有一个指向Base的指针bp,则我们可以在运行时将它转换成指向Derived的指针。代码如下:
class Base {
public:
virtual void foo() {} //必须含有虚函数,否则不能执行dynamic_cast
};
class Derived :public Base {};
int main()
{
Base *bp = new Base;
//成功返回Derived指针,失败返回0
if (Derived *dp = dynamic_cast(bp))
{
//使用dp指向的Derived对象
}
else
{
//使用bp指向的Base对象
}
return 0;
}
如果bp成功指向Derived对象,则上述的类型转换初始化dp并令其指向bp所指的Derived对象。此时:
if语句内部使用Derived操作的代码是安全的
否则,类型转换的结果为0,dp为0意味着if语句的条件失败,此时else子句指向相应的Base对象
值得注意的是,我们在条件部分定义了dp,这样做的好处是:
可以在一个操作中同时完成类型转换和条件检查两项任务
而且,指针dp在if语句外部是不可访问的。一旦转换失败,即使后续的代码忘了做相应判断,也不会接触到这个未绑定的指针,从而程序时安全的。
引用类型的dynamic_cast
引用类型的dynamic_cast与指针类型的dynamic_cast使用起来类似,只是两者在出错时的返回值不同:
引用类型的dynamic_cast在出错时会抛出异常
指针类型的dynamic_cast在出错时返回0
当对引用的类型转换失败时,程序抛出一个名为std::bad_cast的异常,该异常定义在typeinfo头文件中
例如:
class Base {
public:
virtual void foo() {} //必须含有虚函数,否则不能执行dynamic_cast
};
class Derived :public Base {};
void f(const Base &b)
{
try {
//如果出错,将抛出bad_cast异常
const Derived &d = dynamic_cast<const Derived&>(b);
}
catch (bad_cast) {
}
}