c++:四大类型转换


 

前言

C语言的类型转换比较自由,但也带来了一些问题,这些问题大多由程序员自行控制和解决。对于庞大的C++语言机制而言,这种简单粗暴的类型转换方式显然是个巨大的负担,因此C++引入4种类型转换运算符,更加严格的限制允许的类型转换,使转换过程更加规范:

  • dynamic_cast 用于多态类型的转换
  • static_cast 用于非多态类型的转换
  • const_cast 用于删除const ,volatile 和 __unaligned 属性
  • reinterpret_cast 用于位的简单重新解释

其中,const_castreinterpret_cast的使用会带来更大的风险,因此不到万不得已,不推荐使用。

dynamic_cast:

//将expression转换为type类型.
dynamic_cast<type>(expression);

备注:
  ① 转换类型必须是一个指针、引用或者void*,用于将基类的指针或引用安全地转换成派生类的指针或引用;
  ② dynamic_cast在运行期间强制转换,运行时进行类型转换检查;
  ③ 对指针进行转换,失败返回null,成功返回type类型的对象指针,对于引用的转换,失败抛出一个bad_cast ,成功返回type类型的引用;
  ④ dynamic_cast不能用于内置类型的转换;
  ⑤ 用于类的转换,基类中一定要有virtual定义的虚函数(保证多态性),不然会编译错误。

举个例子:

dynamic_cast最常用的场景就是将原本“指向派生对象的基类指针或引用”升级为“派生类指针或引用”,比如:

#include<iostream>
using namespace std;
// 基类
class Base {
public:
    virtual void show() {
        cout << "void Base::show();" << endl;
    };
    virtual ~Base() {};
};
//派生类
class Derived :public Base {
public:
    void show() {
        cout << "void Derived::show();" << endl;
    };
};
int main() {
    Base* base = new Derived;
    if (Derived* derived = dynamic_cast<Derived*>(base)) {
        cout << "基类指针转换派生类指针成功" << endl;
        derived->show();
    }
    else {
        cout << "基类指针转换派生类指针失败" << endl;
    }
    base = new Base;
    if (Derived* derived = dynamic_cast<Derived*>(base)) {
        cout << "基类指针转换派生类指针成功" << endl;
        derived->show();
    }
    else {
        cout << "基类指针转换派生类指针失败" << endl;
    }

    cin.get();
    return 0;
}

输出结果:

基类指针转换派生类指针成功
void Derived::show()
基类指针转换派生类指针失败

       第一种情况,先让基类的指针指向派生类对象,这是一种“向上转换”(upcasting),可以直接隐式转换,合乎语法。然后令基类指针“升级为”派生类指针,原则上,这是一种“向下转换”(downcasting),需要显示强制转换,但指向的内存是派生类的,派生类指针指向派生类的内存理论上不存在安全隐患,所以用dynamic_cast进行强转,没任何问题。
  第二种情况,令派生类指针指向基类对象,显然是存在安全隐患,dynamic_cast会返回一个null指针,告诉开发者转换失败了。但这个操作发生在运行时。
  dynamic_cast和传统的(type)(expression)强制转换的最大区别在于提供了运行时的类型检查,保证了类型安全,使用强制转换,会跳过编译器的类型检查,但可能会造成运行时异常,导致程序直接崩溃

static_cast:

//与dynamic_cast作用类似,将expression转换为type类型,区别在于,static发生于编译时,dynamic发生于运行时。 
static_cast<type>(expression);

       ① 用于类层次结构中基类和派生类之间指针或引用的转换,其中——向上转换是安全的,向下转换是不安全的,但两者均可以通过编译,也就是说开发者要负责强转的运行时安全性,这一点,不如dynamic_cast安全;

#include<iostream>
using namespace std;

class Another {};
class Base {};
class Derived :public Base {};

int main() {    
    // 通过编译,且是安全的
    Base*base = static_cast<Base*>(new Derived);
    // 通过编译,但是存在安全隐患
    Derived*d = static_cast<Derived*>(new Base);
    // 没有任何关系的两个类,无法转换,static_cast执行编译时类型检查
    Another*a = static_cast<Base*>(new Base);
    return 0;
}

       ② 可以用于内置类型的转换。
  C语言与自己的内置类型隐式转换规则的,比如说,算术运算,低类型自动往高类型转换(如图),但转换的精度损失由开发者负责,编译器往往会提出警告。使用了static_cast运算符之后,等于告诉编译器,“我知道这里发生了类型转换,我会为转换的安全性负责,你不用管了”,编译器不会发出编译警告,除非你类型转换完全非法(比如 int a = static_cast(“Hello world!”); ),static_cast才会报编译错误;

       ③ 把void*转换成目标类型的指针;
  ④ 把任意类型转换成void类型;
  ⑤ static_cast无法转换expression的const/volitale/__unaligned属性(会报编译时错误)。

const_cast:

//弥补了static_cast无法转换const/volitale的不足,将expression的const/volitale属性移除,仅限于底层const属性. 
const_cast<type>(expression);

       ① 顶层const:表示指针变量是const的,比如int *const pointer底层const: 表示指针所指向的变量是const的,比如const int *pointer;。理解记忆:所谓底层const就是指我这个变量“底子”就是const,改不了,天生丽质难自弃。反之,则是顶层const。
  ② const_cast不能执行其他任何类型转换,只能用于同类型之间不同const/ volitale属性的移除。否则会报编译时错误。
  ③ 需要注意的是,const_cast通常对指针和引用进行转换,而无法直接移除内置类型的const/volitale属性,换言之,这种语法直接提供了一个具有写权限的指针或引用,可以通过间接访问的方式,修改常量。

举个例子:

#include<iostream>
using namespace std;

int main() {

    const int a = 10;
    const int*pointer_const = &a;
    int*b = const_cast<int*>(pointer_const);
    *b = 20;
    cout << "b = " << *b << endl;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "&a = " << &a << endl;
    // a = 50; // 指向a的指针b可以修改a,但是a自身不能修改,因为a是常量

    return 0;
}

测试结果:

       通过`const_cast` 常量a被修改。但是这里有个有趣的现象,就是,指向a的 指针b被修改了,但是a本身却还是没变,从地址上看,指针b确实指向了常量a,但是*b和a却不一样,**这是为什么呢?**   推测可能是编译器(这里用的Visual Studio 2017)对字面型常量的引用,有自己的优化,所以a的值没有发生更改,但实际上已经改了,比如换种方式——
#include<iostream>
using namespace std;

int main() {
    cout<<"请输入一个数:"<<endl;
    int input;
    cin>>input;
    const int a = input;
    const int*pointer_const = &a;
    int*b = const_cast<int*>(pointer_const);
    *b = 20;
    cout << "b = " << *b << endl;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "&a = " << &a << endl;

    return 0;
}

实验结果:

这时会发现,常量a也能改变了(但仍然无法使用a = 30这样的赋值语句来改变a)。

reinterpret_cast:

//reinterpret_cast 允许将任何指针转换为任何其他指针类型。 也允许将任何整数类型转换为任何指针类型以及反向转换。 
reinterpret_cast<type>(expression);

       ① reinterpret_cast 运算符可用于 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,这本身并不安全,但可以通过编译;
  ② reinterpret_cast 的本质作用是重新定义内存数据的解释方式,而不进行任何二进制转换。

总结

       C++提供了这四种类型强制转换符,主要作用是应对更高级的语法,以及更复杂的情况,以保证更好的安全性。毕竟C++的高级语法,还是蛮复杂的,如果类型转换像C语言一样自由而没有限制,必然会带来一连串问题。这种机制,一定程度上限定了类型转换的“规则”。
  但对于比较简单的类型转换,大可不必这么复杂,直接用强制转换(而不是强制转换运算符)即可。

参考链接:
C++的类型转换运算符总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值