C++的四种类型转换运算符

  • static_cast关键字
    static_cast 只能用于良性转换,这样的转换风险较低,一般不会发生什么意外,例如:
    原有的自动类型转换,例如 short 转 int、int 转 double、const 转非 const、向上转型等;void 指针和具体类型指针之间的转换,例如void *转int *、char *转void *等;有转换构造函数或者类型转换函数的类与其它类型之间的转换,例如 double 转 Complex(调用转换构造函数)、Complex 转 double(调用类型转换函数)。需要注意的是,static_cast 不能用于无关类型之间的转换,因为这些转换都是有风险的,例如:两个具体类型指针之间的转换,例如int *转double *、Student *转int *等。不同类型的数据存储格式不一样,长度也不一样,用 A 类型的指针指向 B 类型的数据后,会按照 A 类型的方式来处理数据:如果是读取操作,可能会得到一堆没有意义的值;如果是写入操作,可能会使 B 类型的数据遭到破坏,当再次以 B 类型的方式读取数据时会得到一堆没有意义的值。int 和指针之间的转换。将一个具体的地址赋值给指针变量是非常危险的,因为该地址上的内存可能没有分配,也可能没有读写权限,恰好是可用内存反而是小概率事件。
    static_cast 也不能用来去掉表达式的 const 修饰和 volatile 修饰。换句话说,不能将 const/volatile 类型转换为非 const/volatile 类型。
    static_cast 是“静态转换”的意思,也就是在编译期间转换,转换失败的话会抛出一个编译错误。
    下面的代码演示了 static_cast 的正确用法和错误用法:
#include <iostream>
#include <cstdlib>
using namespace std;
class Complex{
public:
Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public:
operator double() const { return m_real; } //类型转换函数
private:
double m_real;
double m_imag;
};
int main(){
//下面是正确的用法
int m = 100;
Complex c(12.5, 23.8);
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指针
double real= static_cast<double>(c); //调用类型转换函数
//下面的用法是错误的
float *p3 = static_cast<float*>(p1); //不能在两个具体类型的指针之间进行转换
p3 = static_cast<float*>(0X2DF9); //不能将整数转换为指针类型
return 0;
}
  • const_cast关键字
    const_cast 比较好理解,它用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。
    实例:
#include <iostream>
using namespace std;
int main(){
const int n = 100;
int *p = const_cast<int*>(&n);
*p = 234;
cout<<"n = "<<n<<endl;
cout<<"*p = "<<*p<<endl;
return 0;
}
  • reinterpret_cast关键字
    reinterpret 是“重新解释”的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。
    reinterpret_cast 可以认为是 static_cast 的一种补充,一些 static_cast 不能完成的转换,就可以用 reinterpret_cast 来完成,例如两个具体类型指针之间的转换、int 和指针之间的转换(有些编译器只允许 int 转指针,不允许反过来)。
    实例:
#include <iostream>
using namespace std;
class A
{
public:
    A(int a = 0, int b = 0)
        : m_a(a), m_b(b)
    {}
private:
    int m_a;
    int m_b;
};
int main()
{
//将 char* 转换为 float*
    char str[] = "http://c.biancheng.net";
    float *p1 = reinterpret_cast<float *>(str);
    cout << *p1 << endl;
//将 int 转换为 int*
    int *p = reinterpret_cast<int *>(100);
//将 A* 转换为 int*
    p = reinterpret_cast<int *>(new A(25, 96));
    cout << *p << endl;
    return 0;
}

可以想象,用一个 float 指针来操作一个 char 数组是一件多么荒诞和危险的事情,这样的转换方式不到万不得已的时候不要使用。将A转换为int,使用指针直接访问 private 成员刺穿了一个类的封装性,更好的办法是让类提供 get/set 函数,间接地访问成员变量。

  • dynamic_cast关键字
    dynamic_cast 的语法格式为:dynamic_cast < newType > (expression)
    newType 和 expression 必须同时是指针类型或者引用类型。换句话说,dynamic_cast 只能转换指针类型和引用类型,其它类型(int、double、数组、类、结构体等)都不行。
    对于指针,如果转换失败将返回 NULL;对于引用,如果转换失败将抛出std::bad_cast异常。
    向上转型时,只要待转换的两个类型之间存在继承关系,并且基类包含了虚函数(这些信息在编译期间就能确定),就一定能转换成功。因为向上转型始终是安全的,所以 dynamic_cast 不会进行任何运行期间的检查,这个时候的 dynamic_cast 和 static_cast 就没有什么区别了。「向上转型时不执行运行期检测」虽然提高了效率,但也留下了安全隐患
#include <iostream>
#include <iomanip>
using namespace std;
class Base
{
public:
    Base(int a = 0)
        : m_a(a)
    {}
    int get_a() const
    { return m_a; }
    virtual void func() const
    {}
protected:
    int m_a;
};
class Derived: public Base
{
public:
    Derived(int a = 0, int b = 0)
        : Base(a), m_b(b)
    {}
    int get_b() const
    { return m_b; }
private:
    int m_b;
};
int main()
{
//情况①,正确用法
    Derived *pd1 = new Derived(35, 78);
    Base *pb1 = dynamic_cast<Derived *>(pd1);
    cout << "pd1 = " << pd1 << ", pb1 = " << pb1 << endl;
    cout << pb1->get_a() << endl;
    pb1->func();
//情况②
    int n = 100;
    Derived *pd2 = reinterpret_cast<Derived *>(&n);
    Base *pb2 = dynamic_cast<Base *>(pd2);//向上转换不会进行类型检查
    cout << "pd2 = " << pd2 << ", pb2 = " << pb2 << endl;
    cout << pb2->get_a() << endl; //输出一个垃圾值
    pb2->func(); //内存错误
    return 0;
}

向下转型时是有风险的,dynamic_cast 会借助 RTTI 信息进行检测,确定安全的才能转换成功,否则就转换失败。那么,哪些向下转型是安全地呢,哪些又是不安全的呢?

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

从表面上看起来 dynamic_cast 确实能够向下转型,本例也很好地证明了这一点:B 和 C 都是 A 的派生类,我们成功地将 pa 从 A 类型指针转换成了 B 和 C 类型指针。但是从本质上讲,dynamic_cast 还是只允许向上转型,因为它只会向上遍历继承链。造成这种假象的根本原因在于,派生类对象可以用任何一个基类的指针指向它,这样做始终是安全的。本例中的情况②,pa 指向的对象是 D 类型的,pa、pb、pc 都是 D 的基类的指针,所以它们都可以指向 D 类型的对象,dynamic_cast 只是让不同的基类指针指向同一个派生类对象罢了

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值