1、四类类型转换
《Effective C++》中将 c 语言强制类型转换称为旧式转型,c++ 强制类型转换称为新式转型
static_cast<new_type> (expression)
dynamic_cast<new_type> (expression)
const_cast<new_type> (expression)
reinterpret_cast<new_type> (expression)
备注:new_type 为目标数据类型,expression 为原始数据类型变量或者表达式。
C++ 方式的强制类型转换以 C++ 关键字的方式进行转换,同时将所有转换类型分为4中类型,如下表所示:
类型 | 特点 |
---|---|
static_cast | 用于基本类型之间的转换 不能同于基本类型指针间的转换 可以用于有继承关系的类对象之间的转换和类指针之间的转换 |
const_cast | 用于去除变量的只读属性 强制类型转换的目标类型必须是指针或引用 |
dynamic_cast | 用于有继承关系的类指针间的转换 用于有交叉关系的类指针间的转换 具有类型检查的功能 需要有虚函数的支持 |
reinterpret_cast | 用于指针类型之间的转换 用于整数和指针类型之间的转换 |
C++ 方式的强制类型转换具有如下特点:
- 编译器能够帮助检查潜在的问题
- 可以快速的在代码中进行定位
- 支持动态类型识别
相比C的类型转换,任意类型之间都可以进行转换,编译器很难判断其正确性。
C的类型转换太过随意,可以在任意类型之间转换。你可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成一个派生类对象的指针,这些转换之间的差距是非常巨大的,但是传统的C语言风格的类型转换没有区分这些。
C风格的转换没有统一的关键字和标示符。对于大型系统,做代码排查时容易遗漏和忽略。
C++风格完美的解决了上面两个问题。
1.对类型转换做了细分,提供了四种不同类型转换,以支持不同需求的转换;
2.类型转换有了统一的标示符,利于代码排查和检视。
下面分别来介绍这四种转换:static_cast、dynamic_cast、const_cast和reinterpre_cast.
2、static_cast转换
1.基本用法:static_cast<type-id> expression
2.使用场景:
a、用于类层次结构中基类和派生类之间指针或引用的转换
上行转换(派生类---->基类)是安全的;
下行转换(基类---->派生类)由于没有动态类型检查,所以是不安全的。
b、用于基本数据类型之间的转换,如把int转换为char,这种带来安全性问题由程序员来保证
c、把空指针转换成目标类型的空指针
d、把任何类型的表达式转为void类型
3.使用特点
a、主要执行非多态的转换操作,用于代替C中通常的转换操作
b、隐式转换都建议使用static_cast进行标明和替换
另外,static_cast转换需要两个类有继承关系。
3、dynamic_cast转换
1.基本用法:dynamic_cast<type-id> expression
2.使用场景:只有在派生类之间转换时才使用dynamic_cast,type-id必须是类指针,类引用或者void*。
3.使用特点:
a、基类必须要有虚函数,因为dynamic_cast是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表(如果一个类没有虚函数,那么一般意义上,这个类的设计者也不想它成为一个基类)。
b、对于下行转换,dynamic_cast是安全的(当类型不一致时,转换过来的是空指针),而static_cast是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
c、dynamic_cast还可以进行交叉转换
dynamic_cast
提供了运行时的检查。对于指针类型,在运行时会检查 expression
是否真正的指向一个 type-id
类型的对象,如果是,则能进行正确的转换;否则返回 nullptr
。对于引用类型,若是无效转换,则在运行时会抛出异常 std::bad_cast
。
T1 obj;
T2* pObj = dynamic_cast<T2*>(&obj); // 无效转换返回 nullptr
T2& refObj = dynamic_cast<T2&>(obj); // 无效转换抛出 bad_cast 异常
上行转换:其实和 static_cast 是一样的,一般肯定能成功。例如前面用到的例子:
// A->B
B* pb = new B();
A* pa = static_cast<A*>(pa);
但是,下面这种继承关系会转换失败:
#include <iostream>
/* A
/ \
V V
B C
\/
v
D */
class A {
virtual void func(){}
};
class B : public A {
void func(){}
};
class C : public A {
void func(){}
};
class D : public B, public C {
void func(){}
};
int main(){
D* pd = new D();
A* pa = dynamic_cast<A*>(pd);
return 0;
}
上面这个例子,虽然也是上行转换,但是存在两条路径,在 B 和 C 都继承于 A,并且有虚函数实现,上行转换不知道从哪条路径进行转换。下面的写法则没问题:
D* pd = new D();
B* pb = dynamic_cast<B*>(pd);
A* pa = dynamic_cast<A*>(pb);
4、const_cast转换
1.基本用法:const_cast<type-id>expression
2.使用场景:
a、常量指针转换为非常量指针,并且仍然指向原来的对象
b、常量引用被转换为非常量引用,并且仍然指向原来的对象
3.使用特点:
a、cosnt_cast是四种类型转换符中唯一可以对常量进行操作的转换符
b、去除常量性是一个危险的动作,尽量避免使用。一个特定的场景是:类通过const提供重载时,一般都是非常量函数调用const_cast<const T>将参数转换为常量,然后调用常量函数,然后得到结果再调用const_cast <T>去除常量性。
const_cast
用来去掉表达式的 const
修饰或 volatile
修饰,也就是将 const
或 volatile
类型转换为非 const
或 非 volatile
类型。
#include <iostream>
int main(){
const int n = 111;
int *p = const_cast<int*>(&n);
*p = 222;
std::cout<< "n = " << n << std::endl;
std::cout<< "*p = " << *p << std::endl;
return 0;
}
5、reinterpret_cast转换
1.基本用法:reinterpret_cast<type-id>expression
2.使用场景:不到万不得已,不用使用这个转换符,高危操作
3.使用特点:
a、reinterpret_cast是从底层对数据进行重新解释,依赖具体的平台,可移植性差
b、reinterpret_cast可以将整型转换为指针,也可以把指针转换为数组
c、reinterpret_cast可以在指针和引用里进行肆无忌惮的转换
#include <iostream>
int main(){
char str[]="hello world!";
float *p = reinterpret_cast<float*>(str);
std::cout << *p << std::endl; // 1.14314e+27
return 0;
}
6、各种转换之间的比较
1.static_cast和dynamic_cast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
在基类派生类互相转换时,虽然static_cast是在编译期完成,效率更高,但是不安全,上例中就示范了一个踩内存的例子。相比之下因为dynamic_cast可以查看运行时信息,上例如果Base含有虚函数,那么drvPtrB就是一个空指针(这可比踩内存什么的好多了),不能操作Derived中_d的数据从而保证安全性,所以应该优先使用dynamic_cast。
2.static_cast和reinterpret_cast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
static_cast虽然也不是一种绝对安全的转换,但是它在转换时,还是会进行必要的检测(诸如指针越界计算,类型检查)。reinterpret_cast完全是肆无忌惮,直接从二进制开始重新映射解释,是极度不安全的,再次提醒,不到万不得已,不要使用。