C语言札记之数据类型(二)
C语言笔记之数据类型(二)
再看一个:
2、扩展
不过至少,我们知道低容量的char向高容量的short扩展时,如果其二进制最高位是1,那么扩展后的高位用1来填充,这就是所谓的符号扩展。
总结上面printf函数的这个小“bug”让我不禁这样猜想:是不是在做类型转换时,无论是截断还是扩展,先统一变成四个字节,若不足四个字节,则采用相应的扩展。
接下来是不同类型之间的强制转换。当把一个高容量的类型强制转换为低容量的类型时,会发生截断:丢弃二进制的高位,只保留低位(二进制的左边为高位,右边为低位);而把低容量类型强制转换成高容量类型时,会发生扩展:在二进制的高位左边继续填充数字。扩展分为两类:零扩展和符号扩展。下面看代码示例。
1、截断
#include <stdio.h> int main(void) { short a = 257; char b = (char)a; printf("a = %u, b = %d\n", a, b); printf("a = %#x, b = %#x\n", a, b); return 0; }结果:
a = 257, b = 1 a = 0x101, b = 0x10x0101被分成两部分,高位的01和低位的01,char类型只能容纳一个字节,于是取低位的一个字节后,结果是0x01。
再看一个:
#include <stdio.h> int main(void) { short a = -24368; char b = (char)a; printf("a = %d, b = %d\n", a, b); printf("a = %#x, b = %#x\n", a, b); printf("a: %d, b: %d\n", sizeof a, sizeof b); return 0; }结果:
a = -24368, b = -48 a = 0xffffa0d0, b = 0xffffffd0 a: 2, b: 1虽然截断后的结果是一致的,但是这里的printf似乎是按四个字节来打印负数的十六进制。
2、扩展
#include <stdio.h> int main(void) { unsigned short a = 63222u; unsigned int b = (unsigned int)a; printf("a = %d, b = %d\n", a, b); printf("a = %#x, b = %#x\n", a, b); printf("a: %d, b: %d\n", sizeof a, sizeof b); return 0; }结果:
a = 63222, b = 63222 a = 0xf6f6, b = 0xf6f6 a: 2, b: 4显然,虽然打印出来的a和b的二进制表示相同,但是用sizeof运算符计算长度时,发现b占用了4个字节,所以b的实际十六进制为:0x0000f6f6。这表明,无符号数的扩展采用0来填充高位,这就是所谓的零扩展。
另外,我发现printf函数打印十六进制时,自动忽略高位的0。
有符号数呢?
#include <stdio.h> int main(void) { char a = -68; short b = (short)a; printf("a = %d, b = %d\n", a, b); printf("a = %#x, b = %#x\n", a, b); printf("a: %d, b: %d\n", sizeof a, sizeof b); return 0; }结果:
a = -68, b = -68 a = 0xffffffbc, b = 0xffffffbc a: 1, b: 2呃,又被吓到了,我不禁怀疑printf函数打印十六进制时存在bug:sizeof显示a的确只占一个字节,所以十六进制只打印0xbc就够了,为毛还要加上前面一堆f?同理,b的话,0xffbc就够了。。
不过至少,我们知道低容量的char向高容量的short扩展时,如果其二进制最高位是1,那么扩展后的高位用1来填充,这就是所谓的符号扩展。
我再验证一下有符号的正数:
#include <stdio.h> int main(void) { char a = 68; int b = (int)a; printf("a = %d, b = %d\n", a, b); printf("a = %#x, b = %#x\n", a, b); printf("a: %d, b: %d\n", sizeof a, sizeof b); return 0; }结果:
a = 68, b = 68 a = 0x44, b = 0x44 a: 1, b: 4恩,虽然也是用0来填充高位,但此时0应被视为正号,仍是符号扩展。
总结上面printf函数的这个小“bug”让我不禁这样猜想:是不是在做类型转换时,无论是截断还是扩展,先统一变成四个字节,若不足四个字节,则采用相应的扩展。
若打印十进制,则从右到左读取相应的类型长度,然后停止读取并将读取的数据打印成十进制;但是打印十六进制时,在读取了足够的类型长度后,若读到0则停止读入,若读到1则继续读入,直到读够4个字节。
那么最后,符号转换和类型转换一起上会怎样?
#include <stdio.h> int main(void) { short a = -1; int b = (int)(unsigned short)a; unsigned int c = (unsigned)(int)a; printf("a = %d, a = %#x\n", a, a); printf("b = %d, b = %#x\n", b, b); printf("c = %u, c = %#x\n", c, c); //printf("a: %d, b: %d\n", sizeof a, sizeof b); return 0; }结果:
a = -1, a = 0xffffffff b = 65535, b = 0xffff c = 4294967295, c = 0xffffffff对于b,是先把a由signed short转换成unsigned short,再转换成int;而c的话,先做类型转换,再由signed转换成unsigned。我们看到结果是不同的,理由在上面说过了。另外,unsigned等价于unsigned int。
现在我们来研究一下有符号数和无符号数运算的问题。
C语言规定,如果参与运算的两个数一个是有符号的另一个是无符号的,那么有符号数会被隐式的强制转换成无符号数,并假设两个数都是非负数。
这条规则对于算数运算来说,不会导致什么区别,也就是没什么负面作用。然而对于逻辑运算可能会产生意外。
如:-1 < 0u
表面上看没什么错,但这个运算的结果是0,即false。。因为-1被转换成无符号数后就是4294967295u,它当然不可能小于0u。。。
在比如:2147483647u > -2147483647 - 1
结果也是错的,因为-2147483648转换成无符号数后是2147483648。