C++的强制类型转换对类型转换做了细分,提供了四种不同类型转换,以支持不同需求的转换;类型转换有了统一的标示符,利于代码排查和检视。这四种类型转换是:static_case(静态转换)、dynamic_cast(动态转换)、const_cast(常量转换)和reinterpret_cast(不相关类型的转换)。
下面分别来介绍这四种转换:static_cast、dynamic_cast、const_cast、reinterpret_cast,它们都是类模板。
格式均为:xx_cast(expression) ,其中type-id-要转换成什么类型,expression-被转换类型的目标变量
从图中可知,派生类不仅会继承基类中可继承的方法和属性,还有自身的方法和属性。
当我们从派生类向基类转换时,也就是上行转换,static_case和dynamic_cast这两种方法都是能安全转换的。但是当向下转换类型,也就是我们从基类向派生类转换,当我们采用传统的C语言和c++转换时,就会出现意想不到的情况,因为转换后派生类自己的方法和属性丢失了,一旦我们去调用派生类的方法和属性那就糟糕了,而dynamic_cast这种强制转换内部提供了安全的转换,解决了这种问题。其内部实现可参考passion_wu128 的这篇博客。
以下则主要讲static_case和dynamic_cast的用法和特点。
(1)使用场景
1 在基本数据类型之间转换,如把 int 转换为 char,这种带来安全性问题由程序员来保证;
2 把空指针转换成目标类型的空指针。
3 用于类层次结构中基类和派生类之间指针或引用的转换。
上行转换(派生类---->基类)是安全的;
下行转换(基类---->派生类)由于没有动态类型检查,所以是不安全的。
4 把任何类型的表达式转换成void类型
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性
(2)使用特点
主要执行非多态的转换操作,用于代替C中通常的转换操作。
隐式转换都建议使用 static_cast 进行标明和替换。
不能使用 static_cast 在有类型指针内转换。
类上行和下行转换举例:
class Base
{};
class Derived : public Base
{};
Base* pB = new Base();
//用static_cast的下行转换,不安全
if(Derived* pD = static_cast(pB)){}
Derived* pD = new Derived();
//上行转换,安全
if(Base* pB = static_cast (pD)){}
(1)使用场景
主要用于将一个父类的指针/引用转化为子类的指针/引用(下行转换)。
注:由于这个dynamic_cast要耗费重大的运行成本,应避免频繁使用。
(2)使用特点
1 基类必须要有虚函数,因为 dynamic_cast 是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中。
2 对于下行转换,dynamic_cast 是安全的(当类型不一致时,转换过来的是空指针),而 static_cast 是不安全的。
3 对指针进行 dynamic_cast,失败返回 NULL,成功返回正常 cast 后的对象指针;对引用进行 dynamic_cast,失败抛出一个异常,成功返回正常 cast 后的对象引用。
(3)转换方式:
dynamic_cast< type* >(e)//type必须是一个类类型且必须是一个有效的指针
dynamic_cast< type& >(e)//type必须是一个类类型且必须是一个左值
dynamic_cast< type&& >(e)//type必须是一个类类型且必须是一个右值
e的类型必须符合以下三个条件中的任何一个:
1、e的类型是目标类型type的公有派生类
2、e的类型是目标type的共有基类
3、e的类型就是目标type的类型。
如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。e也可以是一个空指针,结果是所需类型的空指针。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
代码举例:
#include
using namespace std;
class CBase // 基类(父类)
{
public:
// dynamic_cast在将父类cast到子类时,父类必须要有虚函数
virtual int test() { return 0; } // 一定要是 virtual
};
class CDerived : public CBase // 派生类(子类)
{
public:
int test() { return 1; }
};
int main()
{
CBase *p_CBase = new CBase; // 基类对象指针
CDerived *p_CDerived = dynamic_cast(p_CBase); // 将基类对象指针类型转换为派生类对象指针
CBase i_CBase; // 创建基类对象
CBase &r_CBase = i_CBase; // 基类对象的引用
CDerived &r_CDerived = dynamic_cast(r_CBase); // 将基类对象的引用转换派生类对象的引用
}
表达式const_cast
class A { };
void f()
{
const A *pa = new A;//const对象
A pb;//非const对象
//pb = pa; // 这里将出错,不能将const对象指针赋值给非const对象
pb = const_cast>(pa); // 现在OK了
…
}
对于本身定义时为const的类型,即使你去掉const性,在你操作这片内容时候也要小心,只能读不能写操作,否则还是会出错。
const_cast操作不能在不同的种类间转换。相反,它仅仅把一个它作用的表达式转换成常量。它可以使一个本来不是const类型的数据转换成const类型的,或者把const属性去掉。
reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。
static_cast 和 reinterpret_cast 的用法和区别: static_cast 运算符完成相关类型之间的转换. 而 reinterpret_cast 处理互不相关的类型之间的转换.
所谓"相关类型"指的是从逻辑上来说,多多少少还有那么一点联系的类型,比如从 double 到 int,我们知道它们之间还是有联系的,只是精度差异而已,使用 static_cast 就是告诉编译器:我知道会引起精度损失,但是我不在乎. 又如从 void* 到 具体类型指针像 char*,从语义上我们知道 void* 可以是任意类型的指针,当然也有可能是 char* 型的指针,这就是所谓的"多多少少还有那么一点联系"的意思. 又如从派生类层次中的上行转换(即从派生类指针到基类指针,因为是安全的,所以可以用隐式类型转换)或者下行转换(不安全,应该用 dynamic_cast 代替).对于static_cast操作符,如果需要截断,补齐或者指针偏移编译器都会自动完成.注意这一点,是和 reinterpret_cast 的一个根本区别.reinterpret_cast做转换时编译器不会做任何检查,截断,补齐的操作,只是把比特位拷贝过去.所以 reinterpret_cast 常常被用作不同类型指针间的相互转换,因为所有类型的指针的长度都是一致的,按比特位拷贝后不会损失数据.