C++基础——C++风格的类型转换(static_cast、const_cast、dynamic_cast、reinterpret_cast)


类型转换是一个笼统、模糊的词,对应着C语言类型转换的粗粒度特性。

C++的观点,类型转换之间存在着巨大的不同,例如把一个指向常量const的指针(pointer-to-const-object,const int* pa = &a,const int* != int* const,前者是指向)转换成指向非常量对象的指针(pointer-to-non-const-object),也即去除常量性,再比如,把一个指向基类的指针转换成指向子类的指针(转换的是对象类型)。

这顺便说一下常量指针指针常量之间的区别:

int a = 10;
int b = 20;

const int* pa = &a;     // 常量指针,指向的内容不可改变
*pa = 15;               // 错误,此时*pa不是可修改的左值

int* const pa = &a;     // 指针常量(类比int常量),指针本身不可发生变话
pa = &b;                // 错误,此时pa不是可修改的左值

传统的C-style的类型转换不对上述两种转换(消除常量性,基类指针转换为子类指针)进行区分。

这是C-style风格转换的一种缺陷,它的另外一个问题是其转换形式在程序中难以识别,容易与其他C++语法特性发生混淆。语法上,C-style的类型转换由圆括号和标识符组成,即(type)expression,这种语法格式可以发生在C++的任何地方,尤其是C++还支持括号运算符重载。

doubel val = 10.;
int a = (int)val;

为了解决C-style类型转换的上述问题,C++引入了C++风格的类型转换操作符:static_cast、const_cast、dynamic_cast,和reinterpret_cast。语法形式上:

(new-type)expression

转换为:

static_cast<new-type>(expression)

例如把一个int转换成double,以便让包含int类型变量的表达式产生浮点数值的结果(也就是纯int类型的变量如果不经历类型转换,四则运算之后的结果仍是int类型)。

int numerator, denominator;

// C-style
double result = (double)numerator/denominator;

// C++-style
double result = static_cast<double>(numerator)/denominator;

static_cast

static_cast功能上基本与C-style的类型转换一样强大,含义也一样,也有转换能力上的限制。例如,不能用static_cast像用C-style的类型转换一样,把struct转换呢为int类型或者把double转换为指针类型。另外static_cast不能去除表达式中的常量性(C-style具备这样的去除常量性),const_cast具备这样的去除常量性。

int a = 10, b = 20;
int* const pa = &a;
pa = &b;        // 错误,pa不是可修改的左值
const_cast<int*>(pa) = &b;      // 通过
(int*)pa = &b;                  // c-style也是可以的,但不推荐 

const_cast

const_cast用于类型转换掉表达式的常量性(或者volatileness性)。更重要的一点是,通过使用const_cast,你向人们或编译器传达了通过类型转换想做的仅仅只是消除表达式的常量性,除此之外,没有非分之想。这个讯号被编译器所接受,如果你试图使用const_cast完成消除常量性之外的事,会被编译器拒绝。

class Widget {};
class SpecialWidget: public Widget {};
void update(SpecialWidget* psw){}

SpecialWidget sw;       // sw是一个非const变量
const SpecialWidget& csw = sw;  // csw是sw的常量引用

update(&csw);       // 错误!无法将(const SpecialWidget*)转换为(SpecialWidget* )

update(const_static<SpecialWidget*>(&csw)); // 正确,csw的常量性被转换掉,csw和sw在update函数中都将被更新

update((SpecialWidget*)&csw);       // 同上,但用了一个更难识别的C-style类型转换

Widget* pw = new Widget;    
update(pw);                 // 错误!无法将(Widget*)转换为(SpecialWidget*)

update(const_cast<SpecialWidget*>(pw)); // 错误,const_cast仅能用在消除常量性上,不能用在基类向子类进行转换。

dynamic_cast

dynamic_cast 的类型必须是指向完整类类型(完整类类型(一般为子类)的指针)或void* 类型的指针或引用。

dynamic_cast被用于安全地沿着类的继承关系向下进行类型转换,失败的转换将返回空指针(当对指针进行转换时),或者抛出异常(当对引用进行类型转换时)。

Widget* pw = new Widget;

update(dynamic_cast<SpecialWidget*>(pw)); 
                    // 可能有的编译器会提示出错,即运行dynamic_cast的操作数必须包含多态类类类型,即存在virtual函数
                    // 添加一个虚函数之后,正确。
                    // 如果pw确实指向一个对象,否则传递过去的将是空指针。
void updateViaRef(SpecialWidget& rsw){}

updateViaRef(dynamic_cast<SpecialWidget&>(*pw));

dynamic_cast在继承关系上进行类型转换是有限制的,它不能用于缺乏虚函数的类型,也不能用来转换掉constness。

int numerator, denominator;
double result = dynamic_cast<double>(numerator)/denominator;
                    // 错误!不存在继承关系
const Widget cw;
update(dynamic_cast<SpecialWidget*>(&cw));
                // 错误,dynamic_cast不能消除常量性

update(const_cast<SpecialWidget*>(dynamic_cast<SpecialWidget*>(&cw)));
                // 错误,dynamic_cast不能转换一个被const修饰的表达式

update(dynamic_cast<SpecialWidget*>(const_cast<Widget*>(&cw)));
                // 通过,所以正确顺序是先消除常量性,再沿着继承体系向下转换

reinterpret_cast

使用reinterpret_cast进行的转换,其转换结果几乎都是执行期语义(implementation-defined),使用reinterpret_cast的代码很难移植。

reinterpret_cast最普通的用途是在函数指针类型之间进行转换,例如,假设你有一个函数指针数组,

typedef void(*FuncPtr)();
FuncPtr funcPtrArray[10];

如果我们希望把一个指向下面函数声明的指针存入funcPtrArray数组:

int doSomething();

它们的返回类型不同,

funcPtrArray[0] = &doSomething; // 错误,类型不匹配
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);
    // 通过,reinterpret_cast迫使编译器以你的方法看待它们

转换函数指针的代码不可移植,慎用。

References

[1] More Effective C++

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五道口纳什

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值