C++强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast



文章目录

        1. c强制转换与c++强制转换
        2. static_cast 详解
        3. dynamic_cast 详解
        4. const_cast 详解
        5. reinterpret_cast 详解
        6. 归纳总结

1. c强制转换与c++强制转换

C语言强制类型转换主要用于基础的数据类型间的转换,语法为:

(type-id)expression   //转换格式1
type-id(expression)   //转换格式2


C++除了能使用C语言的强制类型转换外,还新增了四种强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast,主要运用于继承关系类间的强制转换,语法为:

static_cast<new_type>      (expression)
dynamic_cast<new_type>     (expression)
const_cast<new_type>       (expression)
reinterpret_cast<new_type> (expression)


备注:new_type为目标数据类型,expression为原始数据类型变量或者表达式。《Effective C++》中将c语言强制类型转换称为旧式转型,c++强制类型转换称为新式转型。
类继承关系图

注:从图中可以看出,派生类不仅有自己的方法和属性,同时它还包括从父类继承来的方法和属性。当我们从派生类向基类转换时,不管用传统的C语言还是C++转换方式都可以百分百转换成功。但是可怕是向下转换类型,也就是我们从基类向派生类转换,当我们采用传统的C语言和C++转换时,就会出现意想不到的情况,因为转换后派生类自己的方法和属性丢失了,一旦我们去调用派生类的方法和属性那就糟糕了,这就是对类继承关系和内存分配理解不清晰导致的。好在C++增加了 static_cast 和 dynamic_cast 运用于继承关系类间的强制转化。

对于C++类继承后,派生类的内存结构和类的大小可参见 C++类空间大小
2. static_cast 详解

static_cast相当于传统的C语言里的强制转换,该运算符把 expression 转换为 new_type 类型,用来强迫隐式转换如 non-const 对象转为 const 对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

    用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换:
      ①进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
      ②进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有
      动态类型检查,所以是不安全的;
    用于基本数据类型之间的转换,如把int转换成char,把int转换成enum;
    把空指针转换成目标类型的空指针;
    把任何类型的表达式转换成void类型;

注意:static_cast 不能转换掉 expression 的const(使用cast_const来解常量性)、volatile、或者__unaligned属性

//-------------------------------------------------------------------------------
//基本类型数据转换举例如下:
char a = 'a';
int b = static_cast<int>(a);//正确,将char型数据隐式转换成int型数据

double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针

int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据

const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
//-------------------------------------------------------------------------------
//类上行和下行转换:
class Base {};

class Derived : public Base {}

Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB)) {}//下行转换是不安全的(坚决抵制这种方法)

Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD)) {}//上行转换是安全的
//-------------------------------------------------------------------------------

3. dynamic_cast 详解

转换方式:

dynamic_cast< type* >(e)       //type必须是一个类类型且必须是一个有效的指针
dynamic_cast< type& >(e)       //type必须是一个类类型且必须是一个左值
dynamic_cast< type&& >(e)      //type必须是一个类类型且必须是一个右值



e的类型必须符合以下三个条件中的任何一个:

    e的类型是目标类型type的公有派生类,向上转换;
    e的类型是目标type的共有基类,向下转换,有安全性检查,较耗时;
    e的类型就是目标type的类型;

如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。e也可以是一个空指针,结果是所需类型的空指针。

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。

//-----------------------------------------------------------------------------
/*(1)指针类型
举例,Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,
如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,
代码如下:*/
if(Derived *dp = dynamic_cast<Derived *>(bp)) {
  //使用dp指向的Derived对象  
} else {
  //使用bp指向的Base对象  
}
/*值得注意的是,在上述代码中,if语句中定义了dp,这样做的好处是可以在一
个操作中同时完成类型转换和条件检查两项任务。*/
//-----------------------------------------------------------------------------
/*(2)引用类型
因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,
在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。*/
void f(const Base &b) {
    try {
        const Derived &d = dynamic_cast<const Base &>(b);  
        //使用b引用的Derived对象
    } catch(std::bad_cast) {
        //处理类型转换失败的情况
    }
}
//-----------------------------------------------------------------------------



4. const_cast 详解

const_cast,用于修改类型的 const(唯一有此能力的C++ style转型操作符) 或 volatile 属性

除了 const 或 volatile 修饰之外, new_type 和 expression 的类型是一样的。

    常量指针被转化成非常量的指针,并且仍然指向原来的对象;
    常量引用被转换成非常量的引用,并且仍然指向原来的对象;
    const_cast 一般用于修改底指针。如 const char *p形式;

//-----------------------------------------------------------------------------
//转换举例如下:
const int g = 20;
int *h = const_cast<int*>(&g);//去掉const常量const属性

const int g = 20;
int &h = const_cast<int &>(g);//去掉const引用const属性

 const char *g = "hello";
char *h = const_cast<char *>(g);//去掉const指针const属性
//-----------------------------------------------------------------------------

 

5. reinterpret_cast 详解

new_type 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

reinterpret_cast 意图执行低级转型,实际动作(及结果)可能取决于编辑器,这也就表示它不可移植。

举一个错误使用reintepret_cast例子,将整数类型转换成函数指针后,vc++在执行过程中会报"…中的 0xxxxxxxxx 处有未经处理的异常: 0xC0000005: Access violation"错误:

#include <iostream>
using namespace std;
int output(int p){
    cout << p <<endl;  return 0;
}

typedef int (*test_func)(int );//定义函数指针test_func
int main(){
    int p = 10;
    test_func fun1 = output;
    fun1(p);//正确
    test_func fun2 = reinterpret_cast<test_func>(&p);
    fun2(p);//...处有未经处理的异常: 0xC0000005: Access violation
    return 0;
}

  

IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。

MSDN中也提到了,实际中可将reinterpret_cast应用到哈希函数中,如下(64位系统中需将unsigned int修改为unsigned long):

// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>

// Returns a hash code based on an address
unsigned short Hash( void *p ) {
   unsigned int val = reinterpret_cast<unsigned int>( p );
   return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
   int a[20];
   for ( int i = 0; i < 20; i++ )
      cout << Hash( a + i ) << endl;
}

  
另外,static_cast和reinterpret_cast的区别主要在于多重继承,比如:

class A {
public:
    int m_a;
};
 
class B {
public:
    int m_b;
};
 
class C : public A, public B {};

//那么对于以下代码:
int main() {
    C c;
    printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast<B*>(&c));
}

 

前两个的输出值是相同的,最后一个则会在原基础上偏移4个字节,这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换。因此, 你需要谨慎使用 reinterpret_cast。
6. 归纳总结

    const_cast 通常被用来将对象的常量性转除(cast away the constness)。它也是唯一有此能力的c+±style转型操作符;
    dynamic_cast 主要用来执行"安全向下转型(safe downcasting, 从基类转为派生类)",也就是用来决定某对象是否归属继承体系中的某个类型。其是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作;
    reinterpret_cast 意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植;
    static_cast 用来强迫隐式转换(implicit conversions),如将non-const转为const对象,但无法将const转换为non-const的;

    新式转换较旧式转换更受欢迎。原因有二,一是新式转型较易辨别,能简化“找出类型系统在哪个地方被破坏”的过程;二是各转型动作的目标愈窄化,编译器愈能诊断出错误的运用;
    尽量少使用转型操作,尤其是dynamic_cast,耗时较高,会导致性能的下降,尽量使用其他方法替代;
    dynamic_cast支持从子类到父类的转化,转换不成功的话会把想要赋值的指针置为null,应该说更安全. static_cast是既支持从父类到子类的指针转换, 这在正常情况下是不允许的, 同样也支持子类到父类. 所以综合来说的话,想要从子类指针转化为父类指针,使用dynamic_cast会更安全, 如果想要从父类指针转化为子类指针的话,最好使用static_cast完成;
————————————————


原文链接:https://blog.csdn.net/yueguangmuyu/article/details/114652055

posted on 2021-04-01 17:14  回形针的迷宫  阅读(248)  评论(0编辑  收藏  举报

导航