仔细看看putchar函数声明,它的参数(parameter)其实并不是char 类型,而是int 类型,这就很有意思了,看看cplusplus上的解释:
The int promotion of the character to be written.
The value is internally converted to an unsigned char when written.
“这个参数是将要被写的字符的int 形式,值在写入时会在内部转化为无符号char 类型”
这里的“promotion”是什么意思呢?
“promotion”?其实就是“type promotion”
在之前的博客里提到数据类型转换,其中提到:
…在不同类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。转换的规则如下:
转换按数据长度增加的方向进行,以保证数值不失真,或者精度不降低…
对于char 类型,其参与的类型转换如图:
这就是所谓的“integer promotion(整形提升)”,因为这些转换的方向都是从小的方向到大的方向,所以英文才用“promotion(提升,增加)”,只不过在中文中这种表达略显奇怪,所以我们在翻译的时候基本上都是翻译成“转换(conversion)”。事实上这两者是差不多的意思,可以通用。
数据类型转换规则
整型升级:
如果一个 int 可以表示原始类型的所有值,则小整数类型(char、有符号 char、无符号 char、short、无符号 short)可以晋升为 int。
如果 int 不能表示所有值(例如,在某些系统中,无符号 short 可能无法放入 int),则该值将晋升为无符号 int。
通常的算术转换:
当运算符应用于两个具有不同类型的操作数时,将按以下顺序应用以下规则:
如果其中一个操作数是 long double 类型,另一个操作数将转换为 long double。
否则,如果操作数之一是 double,则另一个操作数转换为 double。
否则,如果操作数之一是浮点数,则另一个操作数转换为浮点数。
否则,将对两个操作数进行整数运算。
在整数运算后,如果操作数的类型仍然不同,则会执行进一步的转换,将它们转换为共同的类型。
如果其中一个操作数是无符号 long,另一个操作数将转换为无符号 long。
否则,如果一个操作数是 long long,另一个操作数是无符号 long,那么如果无符号 long 可以表示无符号 long 的所有值,则将无符号 long 转换为 long long,否则两个操作数都转换为无符号 long long。
否则,如果一个操作数是 long long,另一个操作数将转换为 long long。
否则,如果其中一个操作数是无符号 long,另一个操作数将被转换为无符号 long。
否则,如果一个操作数为 long,另一个操作数将转换为 long。
否则,如果其中一个操作数是无符号 int,另一个操作数将转换为无符号 int。
函数调用:
在函数调用中,如果参数的类型需要进行整数升级,则参数会被升级并传递给函数。例如,如果函数参数的类型是 int,而你传递了一个 char 作为参数,那么在函数调用之前,该 char 将被升级为 int。
位操作:
对于位运算符(如 &、|、^ 和 ~),在运算之前会对操作数进行整数升级。
赋值操作:
在赋值操作中,右侧操作数会转换为左侧操作数的类型。如果左侧的类型不受整数运算影响,则不会发生运算。
[1]
需要注意的是,只要遇到了unsigned 类型,事情就会变得复杂。
上一篇的问题
所以,在上一篇中遇到的putchar 函数的参数其实要经历一个类型转换的过程,传入的char 类型要转换成int 类型。
char 的范围?char 的计算?
这似乎是一个简单的问题:
printf("%d", sizeof(char));
结果是1,那么char 类型的范围就是-128~127
,unsigned char 的范围就是0~255
,这是没有疑义的。
那么,接下来的这个实例就会引人深思:
char a = 30, b = 40, c = 10;
char d = (a * b) / c;
printf ("%d ", d);
结果是120,可是在第一步里,a * b
结果是1200,已经超出了char 的范围了。为什么没有报溢出呢?原来在char类型的计算中char已经被转换为int类型了,因此不会出问题。
接下来是一个关于unsigned char的示例:
char a = 128;
unsigned char b = 128;
printf("a = %c", a);
printf("\nb = %c", b);
if (a == b)
printf("\nSame");
else
printf("\nNot Same");
输出结果为:
128是在拓展的ASCII表里的字符,普通的ASCII表范围为0~128.
可以看到两者输出的字符是一样的,可是在接下来的比较却说它们两个不一样。如果换一个数字,比如把0xfb换成65或者0x41(65就是0x41),结果又变成了一样。经过测试,只有在0~127范围内才不会认为不一样,而无论如何,两者输出的字符都是一样的。
这两个变量在存储时都是以0x80(128的十六进制)存储,输出时也是以这个数字进行输出。但是在比较时,“比较”实际上是一种运算,“==”实际上是一种运算符,所以两个变量要进行类型转换。在这里,它们都将转换为int类型。
问题就在这里:char类型转换为int与unsigned char转换并不相同。只需要用这两行代码来验证一下就可以了:
printf("%d", a);
printf("\n%d", b);
结果分别为-128和128.也就是char的转换是有符号的,而unsigned char的转换则是无符号的。所以虽然可以把char看作int,但也别真的就认为它们两个等同了,如果是int和unsigned int两个来赋值128(或者说0x80),是不会达到这种效果的,人家4个字节就是4个字节,char就是1个字节。
运行以下代码:
char a = 0x80;
unsigned char b = 0x80;
printf("a:%x", a);
printf("\nb:%x", b);
结果:
也就是说,这两个变量在转换为int类型时分别为以下两种情况:
至于为什么这两种转换在前面填充的数字不同呢?由于char的最高有效位(MSB,most significant bit)有符号的含义,而unsigned char的最高有效位没有符号的含义。[2]