频道栏目
读书频道 > 软件开发 > C++ > C++ Primer中文版(第5版)
4.11.3 显式转换
2013-09-09 13:48:58     我来说两句
收藏   我要投稿

本文所属图书 > C++ Primer中文版(第5版)

这本久负盛名的 C++经典教程,时隔八年之久,终迎来史无前例的重大升级。除令全球无数程序员从中受益,甚至为之迷醉的——C++ 大师 Stanley B. Lippman 的丰富实践经验,C++标准委员会原负责人 Jos&eacut...  立即去当当网订购

有时我们希望显式地将对象强制转换成另外一种类型。例如,如果想在下面的代码中执行浮点数除法:

int i, j;

double slope = i/j;

就要使用某种方法将i和/或j显式地转换成double,这种方法称作强制类型转换(cast)。

虽然有时不得不使用强制类型转换,但这种方法本质上是非常危险的。

命名的强制类型转换
一个命名的强制类型转换具有如下形式:

cast-name<type>(expression);

其中,type是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值。cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。dynamic_cast支持运行时类型识别,我们将在19.2节(第825页)对其做更详细的介绍。cast-name指定了执行的是哪种转换。

static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。例如,通过将一个运算对象强制转换成double类型就能使表达式执行浮点数除法:

// 进行强制类型转换以便执行浮点数除法

double slope = static_cast<double>(j) / i;

当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。一般来说,如果编译器发现一个较大的算术类型试图赋值给较小的类型,就会给出警告信息;但是当我们执行了显式的类型转换后,警告信息就会被关闭了。

static_cast对于编译器无法自动执行的类型转换也非常有用。例如,我们可以使用static_cast找回存在于void*指针(参见2.3.2节,第56页)中的值:

void* p = &d;   // 正确:任何非常量对象的地址都能存入void*

//正确:将void*转换回初始的指针类型

double *dp = static_cast<double*>(p);

当我们把指针存放在void*中,并且使用static_cast将其强制转换回原来的类型时,应该确保指针的值保持不变。也就是说,强制转换的结果将与原始的地址值相等,因此我们必须确保转换后所得的类型就是指针所指的类型。类型一旦不符,将产生未定义的后果。

const_cast
const_cast只能改变运算对象的底层const(参见2.4.3节,第63页):

const char *pc;

char *p = const_cast<char*>(pc); // 正确:但是通过p写值是未定义的行为

对于将常量对象转换成非常量对象的行为,我们一般称其为“去掉const性质(cast away the const)”。一旦我们去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。

只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。同样的,也不能用const_cast改变表达式的类型:

const char *cp;

// 错误:static_cast不能转换掉const性质

char *q = static_cast<char*>(cp);

static_cast<string>(cp);    // 正确:字符串字面值转换成string类型

const_cast<string>(cp);     // 错误:const_cast只改变常量属性

const_cast常常用于有函数重载的上下文中,关于函数重载将在6.4节(第232页)进行详细介绍。

reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。举个例子,假设有如下的转换

int *ip;

char *pc = reinterpret_cast<char*>(ip);

我们必须牢记pc所指的真实对象是一个int而非字符,如果把pc当成普通的字符指针使用就可能在运行时发生错误。例如:

string str(pc);

可能导致异常的运行时行为。

使用reinterpret_cast是非常危险的,用pc初始化str的例子很好地证明了这一点。其中的关键问题是类型改变了,但编译器没有给出任何警告或者错误的提示信息。当我们用一个int的地址初始化pc时,由于显式地声称这种转换合法,所以编译器不会发出任何警告或错误信息。接下来再使用pc时就会认定它的值是char*类型,编译器没法知道它实际存放的是指向int的指针。最终的结果就是,在上面的例子中虽然用pc初始化str没什么实际意义,甚至还可能引发更糟糕的后果,但仅从语法上而言这种操作无可指摘。查找这类问题的原因非常困难,如果将ip强制转换成pc的语句和用pc初始化string对象的语句分属不同文件就更是如此。

reinterpret_cast本质上依赖于机器。要想安全地使用reinterpret_cast必须对涉及的类型和编译器实现转换的过程都非常了解。

建议:避免强制类型转换                                                     

强制类型转换干扰了正常的类型检查(参见2.2.2节,第46页),因此我们强烈建议程序员避免使用强制类型转换。这个建议对于reinterpret_cast尤其适用,因为此类类型转换总是充满了风险。在有重载函数的上下文中使用const_cast无可厚非,关于这一点将在6.4节(第232页)中详细介绍;但是在其他情况下使用const_cast也就意味着程序存在某种设计缺陷。其他强制类型转换,比如static_cast和dynamic_cast,都不应该频繁使用。每次书写了一条强制类型转换语句,都应该反复斟酌能否以其他方式实现相同的目标。就算实在无法避免,也应该尽量限制类型转换值的作用域,并且记录对相关类型的所有假定,这样可以减少错误发生的机会。

旧式的强制类型转换
在早期版本的C++语言中,显式地进行强制类型转换包含两种形式:

type (expr);       // 函数形式的强制类型转换

(type) expr;       // C语言风格的强制类型转换

根据所涉及的类型不同,旧式的强制类型转换分别具有与const_cast、static_cast或reinterpret_cast相似的行为。当我们在某处执行旧式的强制类型转换时,如果换成const_cast和static_cast也合法,则其行为与对应的命名转换一致。如果替换后不合法,则旧式强制类型转换执行与reinterpret_cast类似的功能:

char *pc = (char*) ip; // ip是指向整数的指针

的效果与使用reinterpret_cast一样。

与命名的强制类型转换相比,旧式的强制类型转换从表现形式上来说不那么清晰明了,容易被看漏,所以一旦转换过程出现问题,追踪起来也更加困难。

您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:4.11.2 其他隐式类型转换
下一篇:4.11.3节练习
相关文章
图文推荐
排行
热门
最新书评
特别推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站