C++的四种_cast运算符

前言

在C语言中,使用强制类型转换进行变量之间的类型转换,主要形式有两种,如下:

//整形和浮点型之间的转换
int a(10);
double b=(double)a;

//指针类型之间的转换
char str[] = "hello!";
func((void*)str);

对于进行简单的类型转换,这种方法简单有效,C++也保留了这种强制类型转换,但是这种强制类型转换无法适用于类和类的指针等一些特殊的场景;
ANSI-C++标准定义了四个新的转换符:reinterpret_cast, static_cast, dynamic_cast和const_cast,目的在于提供类之间的类型转换;


1、static_cast

从名字上可以看出,他是一种静态转换的策略,在编译器就能确定转换:

使用方法:
static_cast <newType> ( expression )

它所允许的转换类型较多,允许执行任意的隐式转换,主要有如下几种用法:
1)用于基本数据类型之间的转换,隐式转换就能完成的工作
2)用于任何类型的表达式转换成void类型

int val = 100;
static_cast<void>(val);

3)用于void*和其他指针类型的转换,但不能用于两个有类型指针直接的转换

int *p = new int;
void *p1 = static_cast<void*>(p);

char *p2 = static_cast<char*>(p);//error:invalid static_cast from type ‘int*’ to type ‘char*’

4)将子类指针转换为父类指针upercasting,反过来是不安全的

class Base;
class Derive;
Derive d;
Base* pb = &d;

Derive *pd = static_cast<Derive*>(pb);

2、dynamic_cast

从名字上看,这个关键字与 static_cast 的静态转换是对立的,只用于类继承结构中基类和派生类之间指针或引用的转换,可以进行向上、向下,或者横向的转换。
但是其并不是无条件的进行向上向下,只是会进行类型检查,转换的条件也比较苛刻,必须有继承关系的类之间才能转换,并且在基类中有虚函数才可以。

  1. 错误的类型
//类定义
class Base { virtual fun() {} };
class Derived : public Base {};

Base* b1 = new Base;

//将指向父类对象的指针转换为子类指针会进行检查返回NULL, fails: returns NULL
Derived* d1 = dynamic_cast<Derived *>(b1);
//引用类型执行了类型转换,并且这个转换是不可能的,会抛出bad_cast异常而不是返回NULL
Derived &d2 = dynamic_cast<Derived &>(*b1);  

Why???
为什么指向父类对象的指针 不能转换为 子类指针呢?

这就是因为C++的多态,基类写了虚函数,生成虚函数表,子类继承虚函数,重写虚函数修改虚函数表。这时使用父类指针指向子类对象调用时就会有多态。
但是将指向 父类对象的指针 转换为 子类对象指针 时也会导致虚函数表从父类虚函数表被修改为子类虚函数表,这就有问题。因此C++在使用dynamic_cast时会进行安全检查。

  1. 一个正常转换的例子
   struct B { virtual void test() {} };
   struct D1 : virtual B { };
   struct D2 : virtual B { };
   struct MD : D1, D2 { };


   D1* pd1 = new MD();
   std::cout << pd1 << std::endl;

   // 向上转型
   B* pb = dynamic_cast<B*>(pd1);
   std::cout << pb << std::endl;

   // 向下转型
   MD* pmd = dynamic_cast<MD*>(pd1);
   std::cout << pmd << std::endl;

   // 横向转型
   D2* pd2 = dynamic_cast<D2*>(pd1);
   std::cout << pd2 << std::endl;

3、reinterpret_cast

用法:reinpreter_cast<newType> (expression)

它被用于不同类型指针或引用之间的转换,或者指针和整数之间的转换,比如前面提到的一个例子,static_cast 不能将 int* 直接强转成 char*,使用reinterpret_cast就可以办到。

   int *p = new int;

   // 编译失败 //error: invalid static_cast from type ‘int*’ to type ‘char*’
   char* p1 =  static_cast<char*>(p);

   // 编译成功
   char* p2 =  reinterpret_cast<char*>(p1);

4. const_cast

用法:const_cast<type_id> (expression)

在C/C++中,const限定符通常被用来限定变量,用于表示该变量的值不能被修改,这种限定可以避免程序员犯一些初级错误,但同时也造成了一些不便,比如一些已有函数要求非常量指针,但是掉用这些函数的接口函数中都传递了常量指针,这时候就要对指针类型去常量化。

但需要特别注意的是 const_cast 不能去除变量的常量性,只能用来去除指向常数对象的指针或引用的常量性,且去除常量性的对象必须为指针或引用。

  1. 尝试去除非指针和引用的类型的常量性会编译失败
   const int i = 6;
   // 编译错误 //
   int j = const_cast<int>(i);
  1. 修改一个指针的常量性
   const int val = 6;
   std::cout << "&val=" << &val << ", val=" << val << std::endl;

   const int* cp = &val;
   int *p = const_cast<int*>(cp);
   *p = 2;

   std::cout << "&val=" << &val << ", val=" << val << std::endl;
   std::cout << "p=" << p << ", *p=" << *p << std::endl;
&val=0x7ffff7446bd4, val=6
&val=0x7ffff7446bd4, val=6
p=0x7ffff7446bd4, *p=2

运行之后,变量 p 指向了变量val地址,并改变了地址所指向的内存数据,但是打印 val 的值并没有发生变化,这是因为 val 作为常量在编译期使用它的地方就进行了替换。

volatile关键字

C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量;用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。
    当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

volatile int i=10;
int a = i;
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

    volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

实际上这是操作系统自己会优化的,即使我们这样做可能也无济于事。

强转关键字的选择

好几个关键字,并且有些功能还是重复的,那么究竟该选哪一个呢?这个真得按照经验来选,我建议使用排除法,按照 const_cast -> reinterpret_cast -> dynamic_cast -> static_cast 的顺序带入选择。

  1. 先看是不是要去掉指针或引用的常量属性,如果是只能选择 const_cast
  2. 接着看是不是偏底层的代码,需要将无关类型指针进行转换,或者指针与整数之间进行转换,如果是则选择 reinterpret_cast
  3. 再看转换的是不是继承体系下的多态结构,如果是这种结构下的指针和引用的转换最好使用 dynamic_cast
  4. 前三种情况都不满足,那就只能使用 static_cast
reinterpret_castC++中的一种类型转换运算符,用于在不同类型之间进行强制转换。它可以将一个指针类型转换为另一个指针类型,或者将一个整数类型转换为指针类型。这种转换是非常危险的,因为它会绕过编译器对类型的检查,并且可能导致未定义行为。因此,在使用reinterpret_cast时需要非常小心并确保转换是安全和合法的。 举个例子,引用中的代码演示了reinterpret_cast的使用。在这个例子中,将一个char指针转换为float指针,然后打印出float指针所指向的值。由于char和float的内存布局不同,这种转换可能会导致错误的结果,因此需要谨慎使用reinterpret_cast。 另外,引用中的代码演示了使用const_cast将常量对象的const属性去除的过程。在这个例子中,将一个const int对象的指针转换为int指针,并修改所指向的值。需要注意的是,修改常量对象的值是一种未定义行为,因此使用const_cast需要慎重考虑。 总之,reinterpret_cast是一种强制类型转换运算符,可以在不同类型之间进行转换,但需要谨慎使用,并确保转换是安全和合法的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++ static_cast、dynamic_cast、const_cast和reinterpret_cast四种类型转换运算符)](https://blog.youkuaiyun.com/ccc369639963/article/details/122905438)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值