概述
C语言中允许对不同类型的数据进行混合运算,由于不同的数据类型表示的范围和精度都不同,需要语言进行数据类型转换后再进行运算。在C语言中,提供了两种数据类型转换的方法:隐式类型转换和显式类型转换,前者由编译器完成类型转换的工作,后者则由开发人员显式地进行处理。
隐式类型转换
隐式类型转换是系统进行的自动转换,其转换的基本原则是向表达数据能力更强的方向进行转换。系统自动数据类型转换规则如下图所示:
当在表达式进行混合数据类型的运算时,系统会自动按上图中的规则将参加运算的数据转换成同一类型的数据后再进行运算。
显式类型转换
在必要时,可以对数据进行显式类型转换,即强制类型转换。显式类型转换的语法形式是:
(类型名)(<表达式>) /* 如 int int_val = (int)short_val; */
考虑到显式类型转换是一种强制性规则,因此在使用时需要清楚在使用显式类型转换后的结果,包括数据符号变化、数据丢失等情况。
类型转换处理
类型转换需要考虑有符号数与无符号数之间、短整型数与长整型数之间以及它们的相互组合等各种情况。C语言标准要求先进行数据大小的转换,之后再进行有符号与无符号的转换。
截断
将一个大的数据类型转换为小的数据类型时,不管是无符号数还是有符号数都是简单地进行位截断。
无符号数的数值大小可能因截断而变化,而有符号数不仅数值大小可能变化,符号位也可能发生改变,如8位二进制数00011001(25)转换为4位有符号数截断的结果是1001(-7).
扩展
扩展规则适用于将小的类型转换为更大的数据类型,并且根据要转换的数据类型为有符号数还是无符号数,扩展的规则存在一些差异:
- 将有符号数转换为更大的数据类型(包括无符号类型)需要执行符号扩展,规则是将符号位扩展至所需要的位数
- 将无符号数转换为更大的数据类型时, 只需简单地在开头添加0,这种运算称为0扩展
混合类型运算
在进行整数的运算时,会先进行数据类型的转换(转换规则如上所示),然后再进行运算,运算的结果也会根据存储的容器大小进行相应的扩展或截断操作。这里要特别强调的是,数据类型完全是软件层面的概念,对于机器来说,它不区分有符号数、无符号数等概念,完全视为普通的数字运算,对于运算的结果,由软件层面进行解释。
溢出
在进行整数的算术运算时,当结果变量的位数不足以存放实际实际结果的位数时,运算的结果就会因截断而产生溢出,如4位二进制数运算1011(-5)+1011(-5)=10110(-10), 但如果结果也采用4位二进制存放就会截断为0110(6),产生溢出。
典型的案例
无符号数作循环变量
一个比较常见的错误就是在循环语句中进行无符号数和有符号数的比较,示例如下:
for (unsigned int loop = 10; loop >= 0; loop--) {
printf("loop val:%u\n", loop);
}
这里,循环变量是无符号整型数,其值是恒大于或等于0的,因此循环条件loop>=0
永远满足,这段程序也就成了死循环。
不同类型大小比较
下面的程序
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
unsigned long long_val = ULONG_MAX;
if (long_val > -1) {
printf("0x%lx > -1.", long_val);
} else if (long_val == -1) {
printf("0x%lx == -1.", long_val);
} else {
printf("0x%lx < -1.", long_val);
}
return 0;
}
上述程序的实际运行结果为:
Aspiresky ~/Workspace/CCode/test # ./a.out
0xffffffffffffffff == -1.
这个结果相对来说比较显而易见,但是也体现了对于数据比较来说确实隐藏着一些暗坑,有两个点在这个程序中需要考虑:系统中数据的表示(一般都是使用补码)和语言的数据类型转换规则。C语言中常量默认为int整型,当与unsigned long整型进行对比时,编译器隐式转换规则生效:-1(系统中补码表示为0xFFFFFFFF)会先转换为long整型数,有符号数在扩展时会补符号位;然后long整型数进一步提升为unsigned long整型,其最终结果的16进制表示为0xFFFFFFFFFFFFFFFF,对应于无符号整型数的最大值。
相关参考
- 《C语言深度剖析》
- 《深入理解计算机系统》