1

Summary

0)在进行数据运算、使用变量的时候,一定要十分清楚变量的具体类型

1)对于整形数据,数据类型的最高位用于标识数据的符号:最高位为1表示负数,最高位为0表示整数

2)类型溢出时的运算总结:

  • 溢出的值为正的:实际值为:溢出后的值 - 该类型能表示的个数
  • 溢出的值为负的:实际值为:溢出后的值 + 该类型能表示的个数

3)十进制负数和补码的相互转换:
如何计算负数的补码:如十进制整数-7,8位

  1. 符号位:1
  2. 绝对值:7 <--> 0000 0111
  3. 取反:1111 1000
  4. 加1:1111 1001(补码)

如何从一个补码推算十进制负数:如已知(1111 1001)表示一个十进制负数
上述规则反向计算

  1. 减1:1111 1000
  2. 取反:0000 01111
  3. 绝对值:计算得7
  4. 符号位:-7

特殊的如1个字节的整数-128,它没有原码和反码,只有补码为1000 0000,即使用(-0)来表示(-128)。但仍然可以使用上述规则进行推算二进制补码。

4)C语言中的变量默认为有符号的类型signed;unsigned关键字将变量声明为无符号类型,注意:只有整数类型才可以声明为unsigned

5)当无符号数和有符号数进行数学运算时,有符号数会被转换为有符号数运算结果为无符号数

有符号与无符号数剖析

1、正数和负数

数据类型的最高位用于标识数据的符号

  • 最高位为1,标识这个数为负数
  • 最高位为0,标识这个书为正数

    int sign = 0;
    
    char i = -5;
    short j = 5;
    int k = -1;
    
    sign = (i & 0x80);      // i为负数,最高位为1 ==> sign != 0
    sign = (j & 0x8000);    // j为正数,最高位为0 ==> sign == 0
    sign = (k & 0x80000000);// k为负数,最高位为1 ==> sign != 0

2、原码和补码

2.1 类型溢出的运算

在计算机内部用原码表示无符号数

  • 无符号数默认为正数
  • 无符号数没有符号位

对于固定长度的无符号数

  • MAX_VALUE + 1 --> MIN_VALUE
  • MIN_VALUE - 1 --> MAX_VALUE

类型溢出时的运算总结:

  • 溢出的值为正的:实际值为:溢出后的值 - 该类型能表示的个数
  • 溢出的值为负的:实际值为:溢出后的值 + 该类型能表示的个数

eg1:char c = 256;

  • 分析:有符号char的表示范围为[-128, 127],256超过了能表示的最大值127,因此实际值为:溢出后的值 - 该类型能表示的个数,即256 - 28 = 0;

    char c = 256;
    printf("c = %d\n", c);  // c = 0;

eg2:char c = -200;

  • 分析:有符号char的表示范围为[-128, 127],256超过了能表示的最小值-128,因此实际值为:溢出后的值 + 该类型能表示的个数,即-200 + 28 = 56;

    char c = -200;
    printf("c = %d\n", c);  // c = 56;

eg3:unsigned char c = 256;

  • 分析:无符号char的表示范围为[0, 255],256超过了能表示的最大值255,因此实际值为:溢出后的值 - 该类型能表示的个数,即256- 28 = 0;

    unsigned char c = 256;
    printf("c = %d\n", c);  // c = 0;

eg2:usigned char c = -200;

  • 分析:无符号char的表示范围为[0, 255],-200超过了能表示的最小值0,因此实际值为:溢出后的值 + 该类型能表示的个数,即-200 + 28 = 56;

    char c = -200;
    printf("c = %d\n", c);  // c = 56;

2.2 负数如何用补码表示

在计算机内部用补码表示负数

  • 正数的补码就是正数本身
  • 负数的补码为负数的绝对值各位取反后加1

    • 如:8位整数 5 的补码为:0000 0101 (正数的原码、反码、补码都一样)
    • 如:8位整数 -7 的补码为:1111 1001

如何计算负数的补码:如十进制整数-7,8位

  1. 符号位:1
  2. 绝对值:7 <--> 0000 0111
  3. 取反:1111 1000
  4. 加1:1111 1001(补码)

如何从一个补码推算十进制负数:如已知(1111 1001)表示一个十进制负数
上述规则反向计算

  1. 减1:1111 1000
  2. 取反:0000 01111
  3. 绝对值:计算得7
  4. 符号位:-7

3、signed和unsigned关键字

  • C语言中的变量默认为有符号的类型signed
  • unsigned关键字将变量声明为无符号类型,注意:只有整数类型才可以声明为unsigned

    int i;          // 默认为有符号整形
    signed int j;   // 显示声明变量为带符号整形
    unsigned int k; // 声明变量为无符号整形

4、问题示例

eg.1 以下代码输出什么?

unsigned int i = 5;
int j = -10;

if((i+j) > 0)
{
    printf("i + j > 0 \n" );
}
else
{
    printf("i + j <= 0 \n");
}

实际输出:(i + j > 0)。按照数学运算-10 + 5 = -5,小于0,应该打印(i + j <= 0)才是,为什么?
解析:当无符号数和有符号数进行数学运算时,有符号数会被转换为有符号数运算结果为无符号数

  1. 计算i + j时,将j提升为unsigned int。
  2. -10是一个负数,在内存中以补码形式来存储,实际内存里的二进制值为0xFFFF FFF6(算法见上)。把0xFFFF FFF6以一个无符号数来解释,即此时的j是一个超大的数为4,294,967,286。
  3. 然后加上5即为0xFFFF FFFB,4,294,967,291。所以i + j > 0

eg.2 以下代码输出什么?

unsigned int i = 0;
for(i = 9; i>=0; i--)
{
    printf("i = %u\n", i);
}

实际输出:死循环。而不是只输出0 - 9。
解析:对于无符号整形i,当运算类型溢出时,实际值会向反方向的值靠拢。

  • 当i = 0时,此时执行i--,期望i的值为-1。但实际上i的类型是unsigned int,能表示的范围为[0, 4,294,967,295]。-1不在能表示的范围内,按照上面类型溢出时的算法,此时的实际值为:-1 + 232 = 4,294,967,295,仍然大于0。所以会不停的计算。

本文总结自“狄泰软件学院”唐佐林老师《C语言进阶课程》。
如有错漏之处,恳请指正。


bryson
169 声望12 粉丝