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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值