C++ 的四种转型(casting)动作
1. const_cast
const_cast 是用来将const变量转化为非 const 的一种手段。而且,在四种 casting 手段中,只有 const_cast 有这种能耐!。其最常见于函数的匹配上,比如:
void fun(int* ptr);
函数 fun 要求传入的参数是一个指向 int 类型的指针,也就是这个指针指向的内容可能是可以改变的。当我们手里只有一个const int 类型的变量 a 时,是没办法传给这个函数的:
const int a = 10;
fun(&a); //错误
这是因为 fun 函数没办法保证它不对参数 ptr 所指向的内容进行修改。这时候我们可以把 a 的const属性给去掉:
int * b= const_cast<int*> (&a) // 尖括号内表示想转化的类型
fun(b) //没错误
但是有人要问了:你这不是扯犊子?fun 函数把 a 改变了怎么办?比如:
void fun(int* ptr) {
*ptr = -1;
}
其实当我们把 a 和 指针b的内容打印出来,又会发现 a 的值没有被改变!
void fun(int* ptr)
{
*ptr = -1;
}
int main() {
const int a = 10;
int* b = const_cast<int*>(&a);
fun(b);
cout << "a = " << a << endl;
cout << "*b = " << *b << endl;
}
// 打印内容
a = 10
*b = -1
哈哈,没想到吧。这时有人会问,a和*b可能不是同一个东西?很简单,我们把地址打印出来即可:
void fun(int* ptr) {
*ptr = -1;
}
int main() {
const int a = 10;
int* b = const_cast<int*>(&a);
fun(b);
cout << "adress a=" << &a << " a =" << a << endl;
cout << "adress b=" << b << " *b =" << *b << endl;
}
a和 *b 确实是同一个东西,因为他们呆在同一个地址中。那为什么 a 和 *b的值不一样呢?
因为“对于一个常量,编译器会将所有用到该变量的地方用初始值替换。也就是当编译器遇到 const常量时,会直接转成立即数,而不是去内存里取值。” 所以,a 和 *b 确实是同一个东西,只不过在用的时候,编译器 把看见 a 的地方都换成了10,而用到 b 的时候,就去内存里面取值。
2. dynamic_cast
dynamic_cast 主要用来将执行 “安全的向下转型”(当然也可以向上转换,而且是安全的,放到 static_cast 一起论述)。
向下转型指的是将基类的对象转化成子类;而安全是相对于 static_cast 来说的,因为 dynamic_cast 在转型的时候做了类型的检查。好多人看到这儿可能直接懵逼了:说人话!
在C++中,由于多态的存在,父类的指针可以指向子类的对象。换句话说,现在给你一个父类的指针,你能确定它是指向父类还是子类的吗?如果你确定它是指向子类,那就可以把这个父类的指针用 dynamic_cast 来向下转型成为子类。那为什么要转成子类,维持父类的状态不香?答案是子类通常具有父类没有的特性,因此,子类的“权限更高”,比如:
#include <iostream>
#include <assert.h>
using namespace std;
// 我是父类
class Tfather
{
public:
virtual void f() { cout << "father's f()" << endl; }
};
// 我是子类
class Tson : public Tfather
{
public:
void f() { cout << "son's f()" << endl; }
int data; // 我是子类独有成员
};
int main()
{
Tfather father;
Tson son;
son.data = 123;
Tfather *pf;
/* 父类指针指向了子类对象 */
pf = &son;
pf->data;
system("pause");
}
编译器直接报错。用父类的指针没法取出data啊!这时候 dynamic_cast 派上用场了:我把它转成子类就行!如:
#include <iostream>
#include <assert.h>
using namespace std;
// 我是父类
class Tfather
{
public:
virtual void f() { cout << "father's f()" << endl; }
};
// 我是子类
class Tson : public Tfather
{
public:
void f() { cout << "son's f()" << endl; }
int data; // 我是子类独有成员
};
int main()
{
Tfather father;
Tson son;
son.data = 123;
Tfather *pf;
/* 父类指针指向了子类对象 */
pf = &son;
/* 将父类的指针向下转成子类 */
Tson *ps = dynamic_cast<Tson*>(pf);
cout << ps->data << endl;
system("pause");
}
这下终于不会报错了!
那么问题来了,假设我的先验知识错了,也就是父类的指针原来确实是指向了父类的对象的,但是我搞错啦(还以为是子类)。因此我想用 dynamic_cast 来把父类转化成子类。对于这种情况: static_cast 对于这种转化不会返回一个NULL的指针,而 dynamic_cast 会返回一个NULL指针来警告这种错误!因此,这也是“安全”与“不安全”的由来。
例子来自 weixin_44212574的博客 :
#include <iostream>
#include <assert.h>
using namespace std;
// 我是父类
class Tfather
{
public:
virtual void f() { cout << "father's f()" << endl; }
};
// 我是子类
class Tson : public Tfather
{
public:
void f() { cout << "son's f()" << endl; }
int data; // 我是子类独有成员
};
int main()
{
Tfather father;
Tson son;
son.data = 123;
Tfather *pf;
Tson *ps;
/* 上行转换:没有问题,多态有效 */
ps = &son;
pf = dynamic_cast<Tfather *>(ps);
pf->f();
/* 下行转换(pf实际指向子类对象):没有问题 */
pf = &son;
ps = dynamic_cast<Tson *>(pf);
ps->f();
cout << ps->data << endl; // 访问子类独有成员有效
/* 下行转换(pf实际指向父类对象):含有不安全操作,dynamic_cast发挥作用返回NULL */
pf = &father;
ps = dynamic_cast<Tson *>(pf);
assert(ps != NULL); // 违背断言,阻止以下不安全操作
ps->f();
cout << ps->data << endl; // 不安全操作,对象实例根本没有data成员
/* 下行转换(pf实际指向父类对象):含有不安全操作,static_cast无视 */
pf = &father;
ps = static_cast<Tson *>(pf);
assert(ps != NULL);
ps->f();
cout << ps->data << endl; // 不安全操作,对象实例根本没有data成员
system("pause");
}
最后值得注意的是:dynamic_cast 在将父类 cast 到子类时,父类必须要有虚函数,否则编译器会报错。
3.static_cast
static_cast 的主要用途有:
- 内置类型的转换,比如把double类型的数据转换成int类型的数据。
- 将空指针(nulptr)转化成目标类型的指针。
- 将各种类型的指针转化成void* 类型的指针。
- 进行对象的上行转换(子类到父类,安全)和下行(父类到子类,不安全)转换。
注意: 对于最后一点,上行转换的功能 dynamic_cast 和 static_cast 都可以完成,而且是安全的。
原因就是子类的对象中往往含有父类的成分,也就是父类其实是子类的子集。因此向上转换的过程是安全的。而向下转换的过程中,由于父类可能不含有子类的一些东西,因此往往是不安全的,除非你能保证需要转换的这个东西是 “父类的指针指向了子类的对象”。
而 dynamic_cast是在运行的时候转化的,static_cast 是在编译的时候转化的,前者在转化的过程中做了类型的检查,因此比较安全。
4.reinterpret_cast
reinterpret_cast 用来进行无关类型的转化,转化之后的对象在内存中的比特位与原始对象相同。比如:
#include <iostream>
#include <vector>
using namespace std;
class A {
public:
int a;
double b;
string c;
};
int main() {
A objA;
objA.a = 120;
objA.b = 1.2;
objA.c = "hello";
int* r = reinterpret_cast<int*>(&objA); // 直接天马行空乱转,程序正常
cout << &objA << " " << r << endl;
return 0;
}
输出:
总的来说, reinterpret_cast 能完成:
- 任意类型指针的转化(如上面的例子,无安全检查)。
- 指针到整型的转化(没试过)。
- 整型到指针的转化(没试过)。
根据 effective c++的描述:这个转换在低级代码(和硬件相关)以外很少见。 那就等有需要的时候再深入探索吧!