前言
C语言的类型转换比较自由,但也带来了一些问题,这些问题大多由程序员自行控制和解决。对于庞大的C++语言机制而言,这种简单粗暴的类型转换方式显然是个巨大的负担,因此C++引入4种类型转换运算符,更加严格的限制允许的类型转换,使转换过程更加规范:
dynamic_cast
用于多态类型的转换static_cast
用于非多态类型的转换const_cast
用于删除const ,volatile 和 __unaligned 属性reinterpret_cast
用于位的简单重新解释
其中,const_cast
和reinterpret_cast
的使用会带来更大的风险,因此不到万不得已,不推荐使用。
dynamic_cast:
//将expression转换为type类型.
dynamic_cast<type>(expression);
备注:
① 转换类型必须是一个指针、引用或者void*,用于将基类的指针或引用安全地转换成派生类的指针或引用;
② dynamic_cast在运行期间强制转换,运行时进行类型转换检查;
③ 对指针进行转换,失败返回null,成功返回type类型的对象指针,对于引用的转换,失败抛出一个bad_cast
,成功返回type类型的引用;
④ dynamic_cast不能用于内置类型的转换;
⑤ 用于类的转换,基类中一定要有virtual定义的虚函数(保证多态性),不然会编译错误。
举个例子:
dynamic_cast
最常用的场景就是将原本“指向派生对象的基类指针或引用”升级为“派生类指针或引用”,比如:
#include<iostream>
using namespace std;
// 基类
class Base {
public:
virtual void show() {
cout << "void Base::show();" << endl;
};
virtual ~Base() {};
};
//派生类
class Derived :public Base {
public:
void show() {
cout << "void Derived::show();" << endl;
};
};
int main() {
Base* base = new Derived;
if (Derived* derived = dynamic_cast<Derived*>(base)) {
cout << "基类指针转换派生类指针成功" << endl;
derived->show();
}
else {
cout << "基类指针转换派生类指针失败" << endl;
}
base = new Base;
if (Derived* derived = dynamic_cast<Derived*>(base)) {
cout << "基类指针转换派生类指针成功" << endl;
derived->show();
}
else {
cout << "基类指针转换派生类指针失败" << endl;
}
cin.get();
return 0;
}
输出结果:
基类指针转换派生类指针成功
void Derived::show()
基类指针转换派生类指针失败
第一种情况,先让基类的指针指向派生类对象,这是一种“向上转换”(upcasting),可以直接隐式转换,合乎语法。然后令基类指针“升级为”派生类指针,原则上,这是一种“向下转换”(downcasting),需要显示强制转换,但指向的内存是派生类的,派生类指针指向派生类的内存,理论上不存在安全隐患,所以用dynamic_cast进行强转,没任何问题。
第二种情况,令派生类指针指向基类对象,显然是存在安全隐患,dynamic_cast会返回一个null指针,告诉开发者转换失败了。但这个操作发生在运行时。
dynamic_cast和传统的(type)(expression)强制转换的最大区别在于提供了运行时的类型检查,保证了类型安全,使用强制转换,会跳过编译器的类型检查,但可能会造成运行时异常,导致程序直接崩溃
static_cast:
//与dynamic_cast作用类似,将expression转换为type类型,区别在于,static发生于编译时,dynamic发生于运行时。
static_cast<type>(expression);
① 用于类层次结构中基类和派生类之间指针或引用的转换,其中——向上转换是安全的,向下转换是不安全的,但两者均可以通过编译,也就是说开发者要负责强转的运行时安全性,这一点,不如dynamic_cast安全;
#include<iostream>
using namespace std;
class Another {};
class Base {};
class Derived :public Base {};
int main() {
// 通过编译,且是安全的
Base*base = static_cast<Base*>(new Derived);
// 通过编译,但是存在安全隐患
Derived*d = static_cast<Derived*>(new Base);
// 没有任何关系的两个类,无法转换,static_cast执行编译时类型检查
Another*a = static_cast<Base*>(new Base);
return 0;
}
② 可以用于内置类型的转换。
C语言与自己的内置类型隐式转换规则的,比如说,算术运算,低类型自动往高类型转换(如图),但转换的精度损失由开发者负责,编译器往往会提出警告。使用了static_cast运算符之后,等于告诉编译器,“我知道这里发生了类型转换,我会为转换的安全性负责,你不用管了”,编译器不会发出编译警告,除非你类型转换完全非法(比如 int a = static_cast(“Hello world!”); ),static_cast才会报编译错误;
③ 把void*转换成目标类型的指针;
④ 把任意类型转换成void类型;
⑤ static_cast无法转换expression的const/volitale/__unaligned属性(会报编译时错误)。
const_cast:
//弥补了static_cast无法转换const/volitale的不足,将expression的const/volitale属性移除,仅限于底层const属性.
const_cast<type>(expression);
① 顶层const:表示指针变量是const的,比如int *const pointer
; 底层const: 表示指针所指向的变量是const的,比如const int *pointer
;。理解记忆:所谓底层const就是指我这个变量“底子”就是const,改不了,天生丽质难自弃。反之,则是顶层const。
② const_cast
不能执行其他任何类型转换,只能用于同类型之间不同const/ volitale
属性的移除。否则会报编译时错误。
③ 需要注意的是,const_cast通常对指针和引用进行转换,而无法直接移除内置类型的const/volitale
属性,换言之,这种语法直接提供了一个具有写权限的指针或引用,可以通过间接访问的方式,修改常量。
举个例子:
#include<iostream>
using namespace std;
int main() {
const int a = 10;
const int*pointer_const = &a;
int*b = const_cast<int*>(pointer_const);
*b = 20;
cout << "b = " << *b << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "&a = " << &a << endl;
// a = 50; // 指向a的指针b可以修改a,但是a自身不能修改,因为a是常量
return 0;
}
测试结果:
#include<iostream>
using namespace std;
int main() {
cout<<"请输入一个数:"<<endl;
int input;
cin>>input;
const int a = input;
const int*pointer_const = &a;
int*b = const_cast<int*>(pointer_const);
*b = 20;
cout << "b = " << *b << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "&a = " << &a << endl;
return 0;
}
实验结果:
这时会发现,常量a也能改变了(但仍然无法使用a = 30这样的赋值语句来改变a)。
reinterpret_cast:
//reinterpret_cast 允许将任何指针转换为任何其他指针类型。 也允许将任何整数类型转换为任何指针类型以及反向转换。
reinterpret_cast<type>(expression);
① reinterpret_cast
运算符可用于 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,这本身并不安全,但可以通过编译;
② reinterpret_cast
的本质作用是重新定义内存数据的解释方式,而不进行任何二进制转换。
总结
C++提供了这四种类型强制转换符,主要作用是应对更高级的语法,以及更复杂的情况,以保证更好的安全性。毕竟C++的高级语法,还是蛮复杂的,如果类型转换像C语言一样自由而没有限制,必然会带来一连串问题。这种机制,一定程度上限定了类型转换的“规则”。
但对于比较简单的类型转换,大可不必这么复杂,直接用强制转换(而不是强制转换运算符)即可。
参考链接:
C++的类型转换运算符总结