简述
C++中有四个类型转换符,旨在严格限制允许的类型转换,使转换更规范。这四个转换符分别是:
- dynamic_cast
- static_cast
- const_cast
- reinterpret_cast
可以根据目的选择一个适合的运算符,而不是使用通用的类型转换。让编译器能够检查程序的行为是否与设计者想法吻合。
接下来看看每个类型转换符的用法以及它们之间的区别。
dynamic_cast
之前在 “C++之 RTTI”中讲到过dynamic_cast,dynamic_cast是动态转换,使用该转换符时会自动去检查转换类型,以及转换是否合法。它的用途是,使得能够在类层次结构中进行向上转换(由于是 is-a关系,这样的类型转换是安全的),而不允许其他转换。
语法为:
dynamic_cast<type-name> (expression)
举个栗子:
一个基类 Base(必须要是虚基类,带有虚函数),一个继承于Base 的派生类 A,那么
Base * pbase = new Base();
A * pA = new A();
Base * pB = dynamic_cast\<Base*>(pA); //成立
A * pA1 = dynamic_cast\<A*>(pbase); //不成立
也就是说,dynamic_cast能从多态性的虚基类强制到某个派生类或者兄弟类。dynamic_cast要求多态性的操作对象。
如果转换不合法,将会返回空指针,就比如上面那句不成立的转化,返回一个空指针。
需要注意的是:编译器不能读由 void所指向的存储提供任何保证。这也就意味着dynamic_cast不能从 void触发进行强制转换,因为它必须去查看对象,以便确定其类型,对于这种情况就需要 static_cast了。
缺点:这是为了保证转换的合法性,dynamic_cast会带来一点运行时的额外开销。
static_cast
静态转换static_cast,和dynamic_cast不同的是,它不需要检查被强制转换的对象。
语法为:
static_cast<type-name> (expression)
仅当 type_name 可被隐式转换为 expression 所属的类型或 expression 可被隐式转换为 type_name 所属的类型时,上述转换才是合法的,否则将会出错。
比如说:A 是 B 的基类,而 C 是一个无关的类,那么从 A 到 B 的转换,从 B 到 A 的转换都是合法的,而从A到 C 转换是不允许的。
A a;
B b;
A * pA = static_cast<A *>(&b); //允许
B * pB = static_cast<B *>(&a); //允许
C * pC = static_cast<C *>(&b); //不允许
第一种转换是合法的,因为向上转换可以显示地进行。第二种转换是从基类指针到派生类指针,在不进行显示类型转换的情况下,将无法进行。但是由于无需进行类型转换,便可以进行另一个方向的类型转换,因此使用 static_cast 来进行向下转换是合法的。
也就是说,可以从常规基类强制,不能从虚基类强制。比如 A 如果是 B 的常规基类,没有虚函数,这样是可以向下转换的,如果 A 是B 的虚基类,就不能转换。
const_cast
const_cast运算符用于执行只有一种用途的类型转换,即改变值为 const 或 volatile,其语法也是一样的:
const_cast<type-name> (expression)
注意:这里的type-name和expression类型必须相同,除了 const 和 volatile 特征(有或无)可以不同。
比如 A 和 B 两个类:
A bar;
const A * pBar = &bar;
A * pA = const_cast<A *>(pBar); //合法
const B * pB = const_cast<B *>(pBar); //不合法
第一个类型转换使得*pA成为一个可用于修改 bar 的对象值的指针,它删除 const 标签,第二个类型转换是非法的,因为它同时尝试将类型从 const A * 改为 const B *.
const_cast 的用途,有时候可能需要这样一个值,它在大多数时候是常量,而有时候又是可以修改的,在这种情况下,可以将这个值声明为 const,并在需要修改它的时候,使用 const_cast 。这也可以通过通用类型转换来实现,但通用转换也可能同时改变类型:
A bar;
const A * pBar = &bar;
A * pA = (A *)(pBar); //合法
B * pB = (B *)(pBar); //合法
由于编程时可能无意间同时改变类型和常量特征,因此使用 const_cast运算符更安全。
但是,const_cast并不是万能的,它可以修改指向一个值的指针,但是修改 const 值的结果是不确定的,来看个示例:
#include <iostream>
using namespace std;
void change(const int * pt,int n)
{
int * pc;
pc = const_cast<int *>(pt);
*pc += n;
}
int main()
{
int pop1 = 100;
const int pop2 = 50;
cout << "pop1 = " << pop1 << " pop2 = " << pop2 << endl;
change(&pop1,20);
change(&pop2,20);
cout << "pop1 = " << pop1 << " pop2 = " << pop2 << endl;
return 0;
}
输出结果:
pop1 = 100 pop2 = 50
pop1 = 120 pop2 = 50
可以看到,由于 pop2被声明为 const,因此编译器可能禁止修改它。所以 const_cast 可用来删除 const 的特征,但仅当指向的值不是 const 时才可行。
reinterpret_cast
reinterpret_cast用于天生危险的类型转换,reinterpret中文含义就是“重新诠释”,它可以用于不同类型之间的转换,也就是说可以将数据以二进制存在形式的重新解释。但并不是所有类型都可以转换的。
语法:reinterpret_cast<type-name> (expression)
示例:
struct dat{
short a;
short b;
};
long value = 0xA224B118;
dat * pd = reinterpret_cast<dat *>(&value);
cout << pd->a;
通常这样的转换适用于依赖于实现的底层编程技术,是不可移植的。例如,不同系统在存储多字节整型时,可能以不同顺序存储其中的字节。
然而,需要注意的是reinterpret_cast运算符并不支持所有的类型转换,例如,可以将指针类型转换为足以存储指针表示的整型,但不能将指针转换为更小的整型或浮点型。另一个限制是,不能将函数指针转换为数据指针,反之亦然。
什么情况下可以用到这个转换符:
- 从指针类型到一个足够大的整数类型
- 从整数类型或者枚举类型到指针类型
- 从一个指向函数的指针到另一个不同类型的指向函数的指针
- 从一个指向对象的指针到另一个不同类型的指向对象的指针
- 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
- 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
参考资料《c++程序设计语言》、《C++编程思想》