摘至《LinuxC编程一站式学习》
3.1 Integer Promotion
在一个表达式中,凡是可以使用int 或unsigned int 类型做右值的地方也都可以使用有符号或无符号的char 型、short 型和Bit-field。如果原始类型的取值范围都能用int 型表示,则其值被提升为int 型,如果表示不了就提升为unsigned int 型,这称为Integer Promotion。做Integer Promotion只影响上述几种类型的值,对其它类型无影响。C99规定Integer Promotion适用于以下几种情况:1、如果一个函数的形参类型未知,例如使用了Old Style C风格的函数声明,或者函数的参数列表中有... ,那么调用函数时要对相应的实参做Integer Promotion,此外,相应的实参如果是float 型的也要被提升为double型,这条规则称为Default Argument Promotion。我们知道printf的参数列表中有... ,除了第一个形参之外,其它形参的类型都是未知的,因此我们在调用printf("%c", 'A') 时,'A' 其实被提升为int 型之后才传给了printf。
2、算术运算中的类型转换。两个算术类型的操作数做算术运算,比如a * b,如果两边操作数的类型不同,编译器会自动做类型转换,使两边类型相同之后才做运算,这称为Usual
Arithmetic Conversion,转换过程中有一步就是Integer Promotion,我们先举个例子来理解这一步,至于Usual Arithmetic Conversion的完整规则将在下一小节详细解释。
unsigned char c1 = 255, c2 = 2;
int n = c1 + c2;
计算表达式c1 + c2的过程其实是先把c1和c2提升为int 类型然后相加(unsigned char 的取值范围是0~255,完全可以用int 表示,所以不需要提升为unsigned int ),整个表达式的值也是int 型,最后的结果是257。假如没有这个提升的过程,c1 + c2就溢出了,最后的结果应该是1。显然,+-*/%这些算术运算以及> < >= <= == !=这些比较运算都需要做Usual Arithmetic Conversion,因为都要求两边操作数的类型一致,此外还有哪些运算也需要做Usual Arithmetic Conversion呢?我们将在下一章做个总结。
3、单目运算符+、-、~只有一个操作数,移位运算符<<、>>两边的操作数类型不要求一致,因此这些运算不需要做Usual Arithmetic Conversion,但也需要做Integer Promotion。运算符~、<<、>>将在下一章介绍。
3.2. Usual Arithmetic Conversion
现在详细解释一下Usual Arithmetic Conversion的规则:
1. 如果有一边的类型是long double,则把另一边也转成long double。
2. 否则,如果有一边的类型是double,则把另一边也转成double。
3. 否则,如果有一边的类型是float ,则把另一边也转成float 。
4. 否则,两边应该都是整数类型,首先按上一小节讲过的规则对a和b做Integer Promotion,然后如果类型仍不相同,则需要继续转换。首先规定char 、short 、int 、long 、long long 的转换级别(Integer Conversion Rank)一个比一个高,同一类型的有符号和无符号数具有相同的Rank,然后有如下转换规则:
a. 如果两边都是有符号数,或者都是无符号数,那么较低Rank的类型转换成较高Rank的类型。例如unsigned int 和unsigned long 做算术运算时都转成unsigned long 。
b. 否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如unsigned long 和int 做算术运算时都转成unsigned long ,unsigned long 和long 做算术运算时也都转成unsigned long 。
c. 剩下的情况就是:一边是无符号数另一边是有符号数,并且无符号数的Rank低于有符号数的Rank。这时又分为两种情况,如果这个有符号数类型能够覆盖这个无符号数类型的取值范围,则把无符号数转成另一边的有符号类型。例如遵循LP64的平台上unsigned int 和long 在做算术运算时都转成long 。
d. 否则,也就是这个符号数类型不足以覆盖这个无符号数类型的取值范围,则把两边都转成两者之中较高Rank的无符号类型。例如遵循ILP32的平台上unsigned int 和long 在做算术运算时都转成unsigned long 。
3.3. 由赋值产生的类型转换
如果赋值或初始化时等号两边的类型不相同,则编译器会把等号右边的类型转换成等号左边的类型再做赋值。例如int c = 3.14; ,编译器会把右边的double型转成int 型再赋给变量c。我们知道,函数调用传参的过程相当于定义形参并且用实参对其做初始化,函数返回的过程相当于定义一个临时变量并且用return的表达式对其做初始化,所以由赋值产生的类型转换也适用于这两种情况。例如一个函数的原型是int foo(int, int); ,则调用foo(3.1, 4.2) 时会自动
把两个double型的实参转成int 型赋给形参,如果这个函数定义中有返回语句return 1.2; ,则返回值1.2 会自动转成int 型再返回。在函数调用和返回过程中发生的类型转换往往容易被忽视,因为函数原型和函数调用并没有写在一起。例如char c = getchar(); ,看到这一句,往往想当然地认为getchar的返回值是char 型的,而事实上getchar的返回值是int 型的,这样赋值会引起一个类型转换,我们以后会详细解释使用这个函数需要注意的问题。