类型转换
c++它允许将一块内存看作多种类型,如可以直接将整型变量与字符变量相加。但在语法上,它是强类型的,如不允许整型与字符串直接运算。
在实际编程中,经常需要变换变量的类型,以完成必要的操作。但转换操作是有风险的,使用不当可能导致程序崩溃,所以要使用合适的转换方式尽量避免错误。
c++支持传统c的强制类型转换,主要用于基础的数据类型间的转换,语法为:(type)expression//转换格式
,即把expression转换为type指定的类型。
这种强制转换使用简单,但也容易导致错误,主要缺点如下:
- 没有从形式上体现转换功能和风险的不同,如将 int 强制转换成 double 是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的
- 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象
- 难以在程序中寻找到底什么地方进行了强制类型转换,不便代码review和排错
针对这些问题,c++提供了4种功能更强大灵活的类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast,主要运用于继承关系类间的强制转化,语法为:
static_cast<new_type> (expression)
dynamic_cast<new_type> (expression)
const_cast<new_type> (expression)
reinterpret_cast<new_type> (expression)
static_cast
static_cast相当于传统的C语言里的强制转换,用于进行比较自然和低风险的转换,如整型和浮点型、字符型之间的互相转换。该运算符把expression转换为new_type类型,用来强迫隐式转换。
- 编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证
- 把空指针转换成目标类型的空指针
- 把任何类型的表达式转换成void类型
举例:
char a = 'a';
int b = static_cast<char>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
dynamic_cast
dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回nullptr,如果转换的是引用,则抛出异常。
- dynamic_cast 是通过“运行时类型检查”来保证安全性的
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的,在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全
- 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用,这种转换没法保证安全性,只好用 reinterpret_cast 来完成
- dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作
举例:
// Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,bp是指向Base的指针,在运行时将它转换成指向Derived的指针
if(Derived *dp = dynamic_cast<Derived *>(bp)){
//使用dp指向的Derived对象
}
else{
//使用bp指向的Base对象
}
const_cast
const_cast 用于修改类型的const或volatile属性,常用于进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。
举例:
const int g = 20;
int *h = const_cast<int*>(&g);//去掉const常量const属性
const int g = 20;
int &h = const_cast<int &>(g);//去掉const引用const属性
const char *g = "hello";
char *h = const_cast<char *>(g);//去掉const指针const属性
reinterpret_cast
reinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
- reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编辑器,这也就表示它不可移植
- 错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式
- 这种转换提供了很强的灵活性,但转换的安全性只能由程序员来保证(用户可以做任何操作,但要为自己的行为负责)
举例:
#include <iostream>
using namespace std;
class A
{
public:
int i;
int j;
A(int n):i(n),j(n) { }
};
int main()
{
A a(100);
int &r = reinterpret_cast<int&>(a); //强行让 r 引用 a
r = 200; //把 a.i 变成了 200
cout << a.i << "," << a.j << endl; // 输出 200,100
int n = 300;
A *pa = reinterpret_cast<A*> ( & n); //强行让 pa 指向 n
pa->i = 400; // n 变成 400
pa->j = 500; //此条语句不安全,很可能导致程序崩溃
cout << n << endl; // 输出 400
long long la = 0x12345678abcdLL;
pa = reinterpret_cast<A*>(la); //la太长,只取低32位0x5678abcd拷贝给pa
unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐个比特拷贝到u
cout << hex << u << endl; //输出 5678abcd
typedef void (* PF1) (int);
typedef int (* PF2) (int,char *);
PF1 pf1; PF2 pf2;
pf2 = reinterpret_cast<PF2>(pf1); //两个不同类型的函数指针之间可以互相转换
}
小结
- 尽量使用c++提供的新式转换
- 尽量使用编译器能够检测出异常错误的转换,优先考虑使用 static_cast
- const_cast 在重载const 修饰的成员函数时非常有用
- reinterpret_cast最危险,必须谨慎使用
- 尽量少进行类型转换,尤其是dynamic_cast,耗时较高,会导致性能的下降,尽量使用其他方法替代
参考资料
C++强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
C++强制类型转换运算符(static_cast、reinterpret_cast、const_cast和dynamic_cast)
When should static_cast, dynamic_cast, const_cast and reinterpret_cast be used?