C++类型转换运算符(dynamic_cast, const_cast, static_cast,reinterpret_cast)

摘自以下图书:

  1. 《C++ Primer Plus》Stephen Prata

在 C++的创始人 Bjame Stroustrup 看来,C 语言中的类型转换运算符太过松散。例如,请看下面的代码:

struct Data
{
	double data[200];
};
struct Junk
{
	int junk[100];
};
Data d = {2.5e33, 3.5e-19, 20.2e32};
char* pch = (char*)(&d); //type cast #1 - convert to string
char pch = char (&d);    //type cast #2 - convert address to a char
Junk* pj = (Junk*)(&d);  //type cast #3 - convert to Junk pointer

首先,上述 3 3 3 种类型转换,哪一种是有意义的?除非不讲理,否则它们中没有一个是有意义的。其次,这 3 3 3 中类型转换中哪种是允许的呢?在C语言中都是允许的。

对于这种松散情况,Stroustrup 采取的措施是,更严格地限制允许的类型转换,并添加 4 4 4 个类型转换运算符,使转换过程更规范:

  • dynamic_cast
  • const_cast
  • static_cast
  • reinterpret_cast

其间 dynamic_caststatic_cast 功能几乎相同,但更加严谨(灵活性更差)。
可以根据目的选择一个适合的运算符,而不是使用通用的类型转换。这指出了类型转换的原因,并让编译器能够检査程序的行为是否与设计者想法吻合。

1. dynamic_cast

假设 High 和 Low 是两个类,而 ph 和 pl 类型分别为 High* 和 Low*,则仅当 Low 是 High 的可访问基类(直接或间接)时,下面的语句才将一个 Low* 指针赋给 pl

pl = dynamic_cast<Low*> ph;

否则,该语句将空指针赋给 pl。通常,该运算符的语法如下:
dynamic_cast< type-name > (expression)
该运算符的用途是,使得能够在类层次结构中进行向上转换(由于 is-a 关系,这样的类型转换是安全的),而不允许其他转换

2. const_cast

const_cast 运算符用于执行只有一种用途的类型转换,即改变值为 const 或 volatile,其语法与 dynamic_cast 运算符相同:
const_cast< type-name > (expression)
如果类型的其他方面也被修改,则上述类型转换将出错。也就是说,除了 const 或 volatile 特征(有或无)可以不同外,type_name 和 expression 的类型必须相同。再次假设 High 和 Low 是两个类:

High bar;
const High* pbar = &bar;
  - - - 
High* pb = const_cast<High*>(pbar);  		 // valid
const Low* pl = const_cast<const Low*>(pbar);// invalid,High->Low

第一个类型转换使得 *pb 成为一个可用于修改 bar 对象值的指针,它删除 const 标签。第二个类型转换是非法的,因为它同时尝试将类型从 const High* 改为 const Low*。

提供该运算符的原因是,有时候可能需要这样一个值,它在大多数时候是常量,而有时又是可以修改的。在这种情况下,可以将这个值声明为 const,并在需要修改它的时候,使用 const_cast。这也可以通过通用类型转换来实现,但通用类型转换也可能同时改变类型:

High bar;
const High* pbar = &bar;
  - - - 
High* pb = (High*)(pbar);// valid
Low* pl = (Low*)(pbar); // also valid

由于编程时可能无意间同时改变类型和常量特征,因此使用 const_cast 运算符更安全。

const_cast 不是万能的。它可以修改指向一个值的指针,但修改 const 值的结果是不确定的。以下程序阐明了这一点:

#include <iostream>
using std::cout;
using std::endl;
void change(const int* pt, int n);

int main()
{
	int pop1 = 38383;
	const int pop2 = 2000;

	cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
	change(&pop1, -103);
	change(&pop2, -103);
	cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
	return 0;
}

void change(const int* pt, int n)
{
	int* pc;
	
	pc = const_cast<int*>(pt);
	*pc += n;
} 

const_cast 运算符可以删除 const int* pt 中的 const,使得编译器能够接收 change() 中的语句:

*pc += n;

但由于 pop2 被声明为 const,因此编译器可能禁止修改它,如下面的输出所示:

pop1, pop2: 38383, 2000
pop1, pop2: 38280, 2000

正如您看到的,调用 change() 时,修改了 pop1,但没有修改 pop2。在 change() 中,指针被声明为 const int*,因此不能用来修改指向的 int。指针 pc 删除了 const 特征,因此可用来修改指向的值,但仅当指向的值不是 const 时才行。因此,pc 可用于修改 pop1,但不能用于修改 pop2。

3. static_cast

static_cast 运算符的语法与其他类型转换符相同:
static_cast< type-name > (expression)
仅当 type_name 可被隐式转换为 expression 所属的类型或 expression 可被隐式转换为 type_name 所属的类型时,上述转换才是合法的,否则将出错。假设 High 是 Low 的基类,而 Pond 是一个无关的类,则从 High 到 Low 的转换、从 Low 到 High 的转换都是合法的,而从 Low 到 Pond 的转换是不允许的:

High bar;
Low blow;
  - - - 
High* pb = static_cast<High*>(&blow); // valid upcast
Low* pl = static_cast<Low*>(&bar); // valid downcast
Pond* pmer = static_cast<Pond*>(&blow); // invalid, Pond unrelated

第一种转换是合法的,因为向上转换可以显式地进行。第二种转换是从基类指针到派生类指针,在不进行显式类型转换的情况下,将无法进行。但由于无需进行类型转换,便可以进行另一个方向的类型转换,因此使用 static_cast 来进行向下转换是合法的。

同理,由于无需进行类型转换,枚举值就可以被转换为整型,所以可以用 static_cast 将整型转换为枚举值。同样,可以使用 static_castdouble 转换为 int、将 float 转换为 long 以及其他各种数值转换。

4. reinterpret_cast

interpret 从字面意思理解,是“解释、诠释”的意思,加上前缀 re,就是“重新诠释”的意思。整个词顺下来就是“重新诠释的转型”。reinterpret_cast 运算符并不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释”。比如下面示例:

int main(int argc, char** argv)
{
	int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
	int * pnum = &num;
	char * pstr = reinterpret_cast<char *>(pnum);
	cout<<"pnum指针的值: "<<pnum<<endl;
	cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
	cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
	cout<<"pstr指向的内容: "<<pstr<<endl;
	return 0;
}

输出:
在这里插入图片描述可以发现两个指针的值是完全相同的。但是输出内容不同。

reinterpret_cast 运算符用于天生危险的类型转换。它不允许删除 const, 但会执行其他令人生厌的操作。有时程序员必须做一些依赖于实现的、令人生厌的操作,使用 reinterpret_cast 运算符可以简化对这种行为的跟踪工作。该运算符的语法与另外 3 3 3 个相同:
reinterpret_cast< type-name > (expression)
下面是一个使用示例:

struct dat {short a; short b;};
long value = 0xA224B118;
dat* pd = reinterpret_cast<dat*> (&value);
cout << hex << pd->a; // display first 2 bytes of value

通常,这样的转换适用于依赖于实现的底层编程程序,是不可移植的。例如,不同系统在存储多字节整型时,可能以不同的顺序存储其中的字节。

然而,reinterpret_cast 运算符并不支持所有的类型转换。例如,可以将指针类型转换为足以存储指针表示的整型,但不能将指针转换为更小的整型或浮点型。另一个限制是,不能将函数指针转换为数据指针,反之亦然。

在 C++ 中,普通类型转换也受到限制。基本上,可以执行其他类型转换可执行的操作,加上一些组合,如 static_castreinterpret_cast 后跟 const_cast,但不能执行其他转换。因此,下面的类型转换在 C 语言是允许的,但在 C++ 中通常不允许,因为对于大多数 C++ 实现,char 类型都太小,不能存储指针:

char ch = char (&d); // type cast #2 - convert address to a char

这些限制是合理的,如果您觉得这种限制难以忍受,可以使用 C 语言。

(这玩意在SLAM的序列化保存中出现的比较平凡,因为好多格式可以用 reinterpret_cast<const char*> 方式来写入和读取)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泠山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值