5.4 隐式转换

正如我们在示例中所看到的,操作数的类型会影响运算符表达式的类型,如1-1U:而第一个是signed int,第二个是unsigned int。对于初学者来说,后者可能尤其令人惊讶,因为unsigned int没有负值,所以-1U是一个很大的正整数。

要点5.29 一元-+有它们提升的参数的类型。

因此,这些运算符是类型通常不变的示例。在它们确实发生变化的情况下,我们必须依赖C语言的策略来执行隐式转换,也就是说,将具有特定类型的值变成另一个所需类型的值。再来考虑一下下面的例子,同样,我们假设-2147483648和2147483647分别是一个signed int数的最小值和最大值:

067-01

这里,ab的初始化是无害的。相应的值完全在所需类型的范围内,因此C编译器可以悄悄地转换它们。

下面两个cd的转换是有问题的。正如我们所看到的,0x80000000unsigned int类型,不适合signed int,所以c接收一个由实现定义的值,我们必须知道我们的平台在这种情况下决定要做什么。它可以重用右边值的位模式,也可以终止程序。对于所有实现定义的特性,应该根据平台的文档说明来选择哪种解决方案,但是要注意,这可能会随着编译器的新版本而改变,或者可能会由编译器参数来进行切换。

对于d的情况,情况更加复杂:0x80000000的值是2147483648,我们可能会认为-0x80000000就是-2147483648。但是由于实际上-0x80000000又是2147483648,所以对于c来说出现了同样的问题[练习4]

那么,e也是无害的。这是因为我们使用了一个负的十进制字面量-2147483648,它的类型为signed long,其值实际上是-2147483648(如前面所示)。因为这个值适合signed int类型,所以可以毫无问题地完成转换。

最后一个例子g的结果是含糊不清的。对于无符号类型来说太大的值将根据模数进行转换。特别地,如果我们假定unsigned short的最大值是216-1,那么结果值为0。这种“缩小”的转换是否是期望的结果往往很难判断。

要点5.30 避免缩小转换。

要点5.31 不要在运算中使用窄类型。

对于像加法和乘法这样有两个操作数的运算符,类型规则会变得更加复杂,因为它们可能具有不同的类型。下面是一些涉及浮点类型运算的示例:这里,前两个示例是无害的:整型常量1的值非常适合double类型或complex float类型。实际上,对于大多数这样的混合运算,只要一种类型的范围适合另一种类型的范围,结果类型就具有更大的范围。

068-01

后面两个是有问题的,因为signed int的最大值INT_MAX,通常不适用于floatcomplex float。例如,在我的机器上,INT_MAX + 0.0FINT_MAX + 1.0F相同,其值为2147483648。最后一行显示的是使用double的运算,这在大多数平台上都可以正常运行。然而,在现有或将来的平台上,如果int是64位的,可能会出现类似的精度问题。

因为对于整型没有严格的包含值范围,所以推断混合了有符号值和无符号值的运算类型会很麻烦:

068-02

前三个比较是无害的,因为即使它们混合了不同类型的操作数,它们也不会混合有符号。因为在这些情况下,可能值的范围很好地相互包含,C只是将一种类型转换为更宽的类型,并在那里进行比较。

接下来的两种情况是明确的,但可能不是一个新手程序员所期望的。事实上,对于这两种情况,所有的操作数都被转换为unsigned int,因此,两个负值都被转换成很大的无符号值,比较的结果为false

最后两个比较甚至更有问题。在UINT_MAXLONG_MAX的平台上,0U被转换为0L,因此第一个结果为true。在其他LONG_MAX < UINT_MAX的平台上,-1L被转换为-1U(即UINT_MAX),因此第一个比较是false。类似的观察结果适用于后两者的第二次比较,但是要注意,这两种方法的结果很可能不一样。

类似于最后两个比较的例子可能会分别引起支持或反对有符号类型或无符号类型的无休止的争论。但是它们只显示了一件事:混合有符号和无符号操作数的语义并不总是清晰的。在某些情况下,任何一种可能的隐式转换的选择都是有问题的。

要点5.32 避免使用不同符号类型的操作数进行操作。

要点5.33 尽可能使用无符号类型。

要点5.34 选择那些隐式转换无害的算术类型。


[练习4]假设unsigned int的最大值是0xFFFFFFFF的情况下,证明-0x80000000 == 0x80000000