关于 unsigned 和 signed 类型相互转换的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

int main(int argc, char* argv[])
{
unsigned char a = -1; // 【补码】-1表示为 1111 1111(省略n个1,看机器是多少位)
char b = a; // signed char
printf("%d %d", a, b); // 从unsigned转换为signed,所以 a 前面填 0,表示为 255(00..0011111111),而 b 前面填符号位,即 1,表示为 -1(全部位为 1)
// 注意 printf 的时候也有一次转换

return 0;
}
// 结果:255 -1

int main(int argc, char* argv[])
{
unsigned short a = -1; // -1 表示为 1111 1111 1111 1111 1111 1111 1111 1111(32 位),给了 a 后变成 16 位,即 16 个 1,取右边 16 个 1。
short b = a; // 同理
printf("%d %d", a, b);

return 0;
}
// 结果:65535 -1

这是两段很简单的代码,我就以第二段代码为例。在计算机中,负数是以补码来存储的。

C 语言中常量整数 -1 的补码表示为 0xFFFFFFFF。截取后面 16 位 FFFF 赋值给变量 a(unsigned short)。此时 a = 0xFFFF。
(a 没有符号位,0xFFFF 转换为十进制为 65535)

a 又将 0xFFFF,直接赋值给 short b。 此时 b = 0xFFFF。
(但是要注意,b是有符号的,0xFFFF 转换为十进制为-1)。

执行 printf("%d %d", a, b) 的时候,要将 a 和 b 的值先转换为 int 型(相当于 signed int,有符号型):

  1. unsigned → signed:a 没有符号所以转为 int 型为 0x0000FFFF,即 65535
  2. signed → signed:b 有符号所以转为 int 型为 0xFFFFFFFF,即 -1

再看一个例子:

1
2
3
4
5
6
7
8
9
int main(int argc, char *argv[])
{
unsigned int a = -1; // 假设是32位计算机,-1表示为32个1
int b = a;
printf("%d %d", a, b); // -1 -1 这里都强制转换成 signed int 了,所以都是-1
printf("%u %u", a, b); // 4294967295 4294967295

return 0;
}

因为在 printf 中的 %d 会将其强制转换成有符号整形数 -1,所以如果用 cout 输出 a,结果应该是 4294967295,或是用 %u 格式输出。

只要记住:看原来被转换的是 signed 还是 unsigned。

  1. unsigned → signed 的时候,是直接复制到低位,高位填0。如果 signed 类型位数不够,只直接装载 unsigned 低位。

  2. signed → unsigned 的时候,也是将补码直接复制到低位,高位填符号位。如果 unsigned 位数不够,只直接装载 signed 低位。

编译器里面有标准的转换,这个是在整型运算的时候出现。标准转换的规则是:短的的向长的转;有符号的向无符号的转。如果被转换的数据比转换后的数据要长的话,转换可能会丢失高位数据。通常,编译器会给出警告。

如:

1
2
3
4
5
int a = -1;
unsigned int b = -1;
// a + b = 1111111111111111111111111111111110 = -2 但对于unsigned来说是一个很大的整数
printf("%d\n", a + b); // a向 unsigned int转换,但最终还是要看展示的格式
// 如果是 %u,我的电脑打印的是 4294967294

最终的展示结果还是要看 %u 还是 %d。无符号整数和有符号整数进行运算,是有符号整数向无符号整数靠齐。

从短到长依次为:short → unsigned short → int → unsigned int。