C++ 的四种转型(casting)动作

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++的描述:这个转换在低级代码(和硬件相关)以外很少见。 那就等有需要的时候再深入探索吧!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值