打印

static_cast 和 reinterpret_cast <<static_cast 和 reinterpret_cast>>

[复制链接]
241|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
keer_zu|  楼主 | 2022-11-14 19:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
C/C++是强类型语言,不同类型之间的相互转换是比较麻烦的.但是在编程实践中,不可避免的要用到类型转换.有2中类型转换:隐式类型转换和强制类型转换.

使用特权

评论回复
沙发
keer_zu|  楼主 | 2022-11-14 19:50 | 只看该作者
1.隐式类型转换
1.1 提升精度,此种是编译器自动完成的,安全的.所以编译的时候不会有任何错误或者警告信息提示.
示例: <<C++ Primer (第三版)>> P147
int ival = 3;
double dval = 3.14159;

// ival 被提升为 double 类型: 3.0
ival + dval;

1.2 降低精度,也是有编译器自动完成,会造成精度丢失,所以编译时得到一个警告信息提示.
示例:
double dval = 3.14159;
// dval的值被截取为 int 值3
int ival = dval;

使用特权

评论回复
板凳
keer_zu|  楼主 | 2022-11-14 19:51 | 只看该作者
2.显式类型转换
2.1 C风格的强制转换(包括旧式C++风格的强制转换)

格式:
类型(表达式); // 旧的C++风格
或者
(类型)表达式 // C风格

示例: int(dval) 或者 (int)dval

此种强制转换是比较粗暴直接的,有可能导致精度丢失(如从 double 转换为 int)或者一些莫名其妙的错误(如把 int 转换为 函数指针),一旦使用了强制转换,编译器将不提示任何警告.这也往往成为错误的源泉.而且这种错误非常难找.我想这也是C++要使用新的强制转换操作符的原因之一吧.

使用特权

评论回复
地板
keer_zu|  楼主 | 2022-11-14 19:51 | 只看该作者
2.2 C++强制转换操作符
C++增加了4个关键字用于强制类型转换:
static_cast, reinterpret_cast, const_cast 和 dynamic_cast.

const_cast 用来移除 const,这个没什么好说的.
dynamic_cast 需要 RTTI 支持, 主要用于把基类指针转换为派生类指针.这里的基类指针其实是指向一个派生类实例,只是类型为基类.
示例:
// 前提假设: class B 由 class A 派生
A *ptrA = new class B;
B *ptrB = dynamic_cast<B*>(ptrA);

使用特权

评论回复
5
keer_zu|  楼主 | 2022-11-14 19:56 | 只看该作者
本文主要谈谈 static_cast 和 reinterpret_cast 的用法和区别.
<<C++程序程序设计语言>>里有一句话我认为说到点子上了: static_cast 运算符完成*相关类型*之间的转换. 而 reinterpret_cast 处理*互不相关的类型*之间的转换.

所谓"相关类型"指的是从逻辑上来说,多多少少还有那么一点联系的类型,比如从 double 到 int,我们知道它们之间还是有联系的,只是精度差异而已,使用 static_cast 就是告诉编译器:我知道会引起精度损失,但是我不在乎. 又如从 void* 到 具体类型指针像 char*,从语义上我们知道 void* 可以是任意类型的指针,当然也有可能是 char* 型的指针,这就是所谓的"多多少少还有那么一点联系"的意思. 又如从派生类层次中的上行转换(即从派生类指针到基类指针,因为是安全的,所以可以用隐式类型转换)或者下行转换(不安全,应该用 dynamic_cast 代替).
对于static_cast操作符,如果需要截断,补齐或者指针偏移编译器都会自动完成.注意这一点,是和 reinterpret_cast 的一个根本区别.

"互不相关的类型"指的是两种完全不同的类型,如从整型到指针类型,或者从一个指针到另一个毫不相干的指针.
示例:
int ival = 1;
double *dptr = reinterpret_cast<double*>(ival);

或者
int *iptr = NULL;
double *dptr = reinterpret_cast<double*>(iptr);

reinterpret_cast 操作执行的是比特位拷贝,就好像用 memcpy() 一样.

int *iptr = reinterpret_cast<int*>(1);
double *dptr = reinterpret_cast<double*>(2);
memcpy(&dptr, &iptr, sizeof(double*)); // 等效于 dptr = reinterpret_cast<double*>(iptr); 结果 dptr 的值为1;

上面这个示例也说明了 reinterpret_cast 的意思:编译器不会做任何检查,截断,补齐的操作,只是把比特位拷贝过去.
所以 reinterpret_cast 常常被用作不同类型指针间的相互转换,因为所有类型的指针的长度都是一致的(32位系统上都是4字节),按比特位拷贝后不会损失数据.

使用特权

评论回复
6
keer_zu|  楼主 | 2022-11-14 19:57 | 只看该作者
3. 编程实践中几种典型的应用场景

3.1 数值精度提示或者降低,包括把无符号型转换为带符号型(也是精度损失的一种),用 static_cast 可以消除编译器的警告信息,前面提到好几次了.

3.2 任意类型指针到 void*, 隐式类型转换,自动完成. 看看 memcpy 的原型
void *memcpy(
   void *dest,
   const void *src,
   size_t count
);
参数定义为 void* 是有道理的,不管我们传入什么类型的指针都符合语义,并且不会有编译器警告.

3.3 void* 到任意类型指针, 用 static_cast 和 reinterpret_cast 都可以,这是由 void* 是通用指针这个语义决定的.我个人倾向用 reinterpret_cast,表达要"重新解释"指针的语义.

3.4 不同类型指针间的相互转换用 reinterpret_cast.

3.5 int 型和指针类型间的相互转换用 reinterpret_cast.
比如我写代码的时候经常这样做: new 一个 struct,然后把指针返回给外部函数作为一个"句柄",我不希望外部函数知道这是一个指针,只需要外部函数在调用相关函数时把这个"句柄"重新传回来.这时,就可以把指针转换为一个 int 型返回. 这是 reinterpret_cast 存在的绝佳理由.

struct car
{
    int doors;
    int height;
    int length;
    float weight;
};

int create_car()
{
    car *c = new car;
    return reinterpret_cast<int>(c);
}

int get_car_doors(int car_id)
{
    car *c = reinterpret_cast<car*>(car_id);
    return c->doors;
}

void destroy_car(int car_id)
{
    car *c = reinterpret_cast<car*>(car_id);
    delete c;
}

如上,外部函数不需要知道 struct car 的具体定义,只需要调用 create_car() 得到一个 car id,然后用此 car_id 调用其他相关函数即可,至于 car_id 是什么,根本没必要关心.

3.6 派生类指针和基类指针间的相互转换.
3.6.1 派生类指针到基类指针用隐式类型转换(直接赋值)或者用 static_cast. 显然不应该也没必要用 reinterpret_cast.
3.6.2 基类指针到派生类指针用 dynamic_cast (运行期检查)或者 static_cast (运行期不检查,由程序员保证正确性). 考虑到C++对象模型的内存分布可能引起的指针偏移问题,绝对不能用 reinterpret_cast.

使用特权

评论回复
7
keer_zu|  楼主 | 2022-11-14 19:57 | 只看该作者
后记
几乎所有提到 reinterpret_cast 的书籍都要附带说什么"不可移植","危险"之类的词,好像 reinterpret_cast 是洪水猛兽,碰不得摸不得.其实理解了之后就知道没什么神秘的,存在即是理由,该用的时候就要大胆的用,否则C++保留这个关键字干什么? 关键是程序员应该清楚的知道自己要的结果是什么,如此,就是用C风格的强制转换又有何妨?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:qq群:49734243 Email:zukeqiang@gmail.com

1314

主题

12271

帖子

53

粉丝