> 文章列表 > C++ 中 static_cast , dynamic_cast , const_cast , reinterpret_cast 类型转换使用详解

C++ 中 static_cast , dynamic_cast , const_cast , reinterpret_cast 类型转换使用详解

C++ 中 static_cast , dynamic_cast , const_cast , reinterpret_cast 类型转换使用详解

文章目录

    • 前言
    • const_cast
      • 原生数据类型
      • 自定义数据类型
    • static_cast
    • reinterpret_cast
    • dynamic_cast
      • 向上转型(Upcasting)
      • 向下转型(Downcasting)

前言

隐式类型转换是安全的,显式类型转换是有风险的,C语言之所以增加强制类型转换的语法,就是为了强调风险,让程序员意识到自己在做什么。但是这种强调风险的方式还是比较粗放,粒度比较大,它并没有表明存在什么风险,风险程度如何。为了使潜在风险更加细化,使问题追溯更加方便,使书写格式更加规范,C++ 对类型转换进行了分类,新增了四个关键字来予以支持。

  1. const_cast : 用于 const 与非 const、volatile 与非 volatile 之间的转换。
  2. static_cast :用于良性转换,一般不会导致意外发生,风险很低。
  3. reinterpret_cast : 高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。
  4. dynamic_cast : 借助 RTTI,用于类型安全的向下转型(Downcasting)。

语法格式:newType data_new = xxx_cast<newType>(data)
newType 是要转换成的新数据类型,data 是被转换的数据。

const_cast

用来去掉表达式的 const 修饰或 volatile 修饰。换句话说 const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。需要注意的是:变量本身的 const 属性是不能去除的,要想修改变量的值,一般是去除指针(或引用)的 const 属性,再进行间接修改。也就是说源类型和目标类型除了 const 属性不同,其他地方完全相同。

原生数据类型

#include <iostream>
using namespace std;int main()
{const int data = 1024;int *data_new = const_cast<int*>(&data);*data_new = 521;cout<<"data = "<<data<<endl;  // (*)cout<<"*data_new = "<<*data_new<<endl;return 0;}

程序输出:

data = 1024
*data_new = 521

  1. &data 用来获取 data 的地址,它的类型为 const int *,必须使用 const_cast 转换为 int * 类型后才能赋值给 data_new。由于 data_new 指向了 data,并且 data 占用的是栈内存,有写入权限,所以可以通过 data_new 修改 data 的值。
  2. 至于 data 和 *data_new 输出的值不一样,是因为 C++ 对常量的处理更像是编译时期的 #define,是一个值替换的过程,代码中所有使用 data 的地方在编译期间就被替换成了 1024。换句话说,第 (*) 行代码被修改成了下面的形式:
    cout<<"data = "<<1024<<endl;
  3. const 的机制,就是在编译期间,用一个常量代替了 data。这种方式叫做常量折叠。常量折叠与编译器的工作原理有关,是编译器的一种编译优化。在编译器进行语法分析的时候,将常量表达式计算求值,并用求得的值来替换表达式,放入常量表。所以在上面的例子中,编译器在优化的过程中,会把碰到的 data(为 const 常量)全部以内容 1024 替换掉,跟宏的替换有点类似。
    *注 :常量折叠只对原生数据类型起作用,对我们自定义的数据类型,是不会起作用的。

自定义数据类型

#include <iostream>
using namespace std;class Test 
{public :Test():_data(1024) {}const int getData() const {return _data;}void setData(const int data) { _data = data;}private :int _data;
};int main()
{const Test t;Test * new_t = const_cast<Test *>(&t);cout << "t = " << t.getData() << endl;new_t->setData(521);cout << "t._data = " << t.getData() << endl;cout << "new—_t._data = " << new_t->getData() << endl;return 0;
} 

程序输出:
t = 1024
t._data = 521
new_t._data = 521

static_cast

static_cast 一般用来将枚举类型转换成整型,或者整型转换成浮点型。也可以用来将指向父类的指针转换成指向子类的指针。做这些转换前,你必须确定要转换的数据确实是目标类型的数据,因为 static_cast 不做运行时的类型检查以保证转换的安全性。也因此,static_cast不如 dynamic_cast 安全。对含有二义性的指针,dynamic_cast 会转换失败,而 static_cast 却直接且粗暴地进行转换。这是非常危险的。等价于隐式转换的一种类型转换运算符,以前是编译器自动隐式转换,static_cast可使用于需要明确隐式转换的地方。c++中用static_cast用来表示明确的转换。static_cast用于基类与派生类的转换过程中,但是没有运行时类型检查。

#include <iostream>
#include <cstdlib>
using namespace std;class Complex
{public:Complex(double real = 0.0, double imag = 0.0): _real(real), _imag(imag){}public:operator double() const { return _real; }  //类型转换函数private:double _real;double _imag;
};class Father
{public:Father(const int data = 0): _data(data) {}public:void foo() { std::cout << "Father()::void foo()" << std::endl;}private:int _data;
};class Son : public Father
{public:void foo() { std::cout << "Son::void foo()" << std::endl;}
};int main()
{int m = 1024;double fd = 0.0;fd = m; // c 语言中的隐式转换 cout<<"fd = "<<fd<<endl;//正确的用法long n = static_cast<long>(m);  //宽转换,没有信息丢失char ch = static_cast<char>(m);  //窄转换,可能会丢失信息int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) );  //将void指针转换为具体类型指针void *p2 = static_cast<void*>(p1);  //将具体类型指针,转换为void指针Complex c(12.5, 23.8);double real= static_cast<double>(c);  //调用类型转换函数Father* pFather = nullptr;Son* pSon = nullptr;pFather = static_cast<Father*>(pSon); pFather->foo();pSon->foo(); pSon = static_cast<Son*>(pFather); //是不安全的pFather->foo();pSon->foo(); //错误的用法 //float *p3 = static_cast<float*>(p1);  //不能在两个具体类型的指针之间进行转换//p3 = static_cast<float*>(0X2DF9);  //不能将整数转换为指针类型return 0;
}

程序输出:
fd = 1024
Father()::void foo()
Son::void foo()
Father()::void foo()
Son::void foo()

reinterpret_cast

reinterpret 是“重新解释”的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。

dynamic_cast

dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。dynamic_cast 是动态类型转换,运行时检查类型安全(转换失败返回 NULL )。
dynamic_cast 与 static_cast 是相对的,dynamic_cast 是“动态转换”的意思,static_cast 是“静态转换”的意思。dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换,这就要求基类必须包含虚函数;static_cast 在编译期间完成类型转换,能够更加及时地发现错误。
注:dynamic_cast 只能转换指针类型和引用类型,其它类型(int、double、数组、类、结构体等)都不行。

向上转型(Upcasting)

向上转型时,只要待转换的两个类型之间存在继承关系,并且基类包含了虚函数(这些信息在编译期间就能确定),就一定能转换成功。因为向上转型始终是安全的,所以 dynamic_cast 不会进行任何运行期间的检查,这个时候的 dynamic_cast 和 static_cast 就没有什么区别了。向上转型时不执行运行期检测,虽然提高了效率,但也留下了安全隐患。
注:虚基类(或称抽象类)可以使用dynamic_cast,但是,非虚基类不可以。在dynamic_cast被设计之前,C++无法实现从一个虚基类到派生类的强制转换。dynamic_cast就是为解决虚基类到派生类的转换而设计的。

#include <iostream>using namespace std;class Base
{public:Base(const int a = 0): _a(a){}int get_a() const { return _a; }virtual void func() const { cout<<"Base::func"<<endl;}private:int _a;
};class Derived : public Base
{public:Derived(const int a = 0, const int b = 0) : Base(a), _b(b){}int get_b() const { return _b; }virtual void func() const { cout<<"Derived::func"<<endl;}private:int _b;
};int main()
{// case 1Derived * pder = new Derived(35, 78);pder->func();Base * pbase = dynamic_cast<Base*>(pder);pbase->func();Base * pbase2 = pder;pbase2->func(); // 向上转换,无论是否用 dynamic_cast,C++总是能够正确识别,即将派生类的指针赋值给基类指针。cout<<"pder = "<<pder<<", pbase = "<<pbase<<", pbase2 = "<<pbase2<<endl;cout<<pbase->get_a()<<endl;return 0;
}

程序输出:
Derived::func
Derived::func
Derived::func
pder = 0x2cb1510, pbase = 0x2cb1510, pbase2 = 0x2cb1510
35

向下转型(Downcasting)

向下转型是有风险的,dynamic_cast 会借助 RTTI 信息进行检测,确定安全的才能转换成功,否则就转换失败,转换失败返回 NULL 。
当使用 dynamic_cast 对指针进行类型转换时,程序会先找到该指针指向的对象,再根据对象找到当前类(指针指向的对象所属的类)的类型信息,并从此节点开始沿着继承链向上遍历,如果找到了要转化的目标类型,那么说明这种转换是安全的,就能够转换成功,如果没有找到要转换的目标类型,那么说明这种转换存在较大的风险,就不能转换。

#include <iostream>
using namespace std;class A
{public:virtual void func() const { cout<<"Class A"<<endl; }
};class B: public A
{public:virtual void func() const { cout<<"Class B"<<endl; }
};class C: public B
{public:virtual void func() const { cout<<"Class C"<<endl; }
};class D: public C
{public:virtual void func() const { cout<<"Class D"<<endl; }
};int main()
{A *pa = new A();B *pb = nullptr;C *pc = nullptr;// case 1pb = dynamic_cast<B*>(pa);  //向下转型失败if(pb == nullptr) {cout<<"Downcasting failed: A* to B*"<<endl;} else {cout<<"Downcasting successfully: A* to B*"<<endl;pb -> func();}pc = dynamic_cast<C*>(pa);  //向下转型失败if(pc == nullptr) {cout<<"Downcasting failed: A* to C*"<<endl;} else {cout<<"Downcasting successfully: A* to C*"<<endl;pc -> func();}//case 2pa = new D();  //向上转型都是允许的pb = dynamic_cast<B*>(pa);  //向下转型成功if(pb == nullptr) {cout<<"Downcasting failed: A* to B*"<<endl;} else {cout<<"Downcasting successfully: A* to B*"<<endl;pb -> func();}pc = dynamic_cast<C*>(pa);  //向下转型成功if(pc == nullptr) {cout<<"Downcasting failed: A* to C*"<<endl;} else {cout<<"Downcasting successfully: A* to C*"<<endl;pc -> func();}return 0;
}

程序输出:
Downcasting failed: A* to B*
Downcasting failed: A* to C*
Downcasting successfully: A* to B*
Class D
Downcasting successfully: A* to C*
Class D

代码中类的继承顺序为:A --> B --> C --> D。pa 是 A* 类型的指针,当 pa 指向 A 类型的对象时,向下转型失败,pa 不能转换为B或C类型。当 pa 指向 D 类型的对象时,向下转型成功,pa 可以转换为B或C类型。都是向下转型, pa 指向的对象不同,转换的结果不同。

对于本例中的 case 1,pa 指向 A 类对象,根据该对象找到的就是 A 的类型信息,当程序从这个节点开始向上遍历时,发现 A 的上方没有要转换的 B 类型或 C 类型(实际上 A 的上方没有任何类型了),所以就转换败了。对于 case 2,pa 指向 D 类对象,根据该对象找到的就是 D 的类型信息,程序从这个节点向上遍历的过程中,发现了 C 类型和 B 类型,所以就转换成功了。

总起来说,dynamic_cast 会在程序运行过程中遍历继承链,如果途中遇到了要转换的目标类型,那么就能够转换成功,如果直到继承链的顶点(最顶层的基类)还没有遇到要转换的目标类型,那么就转换失败。对于同一个指针(例如 pa),它指向的对象不同,会导致遍历继承链的起点不一样,途中能够匹配到的类型也不一样,所以相同的类型转换产生了不同的结果。从本质上讲,dynamic_cast 还是只允许向上转型,因为它只会向上遍历继承链。