打印
[牛人杂谈]

C语言种负数的存储方式

[复制链接]
635|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
在数学中,数字有正负之分。在C语言中也是一样,short、int、long 都可以带上正负号,例如:
//负数
short a1 = -10;
short a2 = -0x2dc9;  //十六进制
//正数
int b1 = +10;
int b2 = +0174;  //八进制
int b3 = 22910;
//负数和正数相加
long c = (-9) + (+12);
如果不带正负号,默认就是正数。
符号也是数字的一部分,也要在内存中体现出来。符号只有正负两种情况,用1位(Bit)就足以表示;C语言规定,把内存的最高位作为符号位。以 int 为例,它占用 32 位的内存,0~30 位表示数值,31 位表示正负号。如下图所示:


C语言规定,在符号位中,用 0 表示正数,用 1 表示负数。例如 int 类型的 -10 和 +16 在内存中的表示如下:

short、int 和 long 类型默认都是带符号位的,符号位以外的内存才是数值位。如果只考虑正数,那么各种类型能表示的数值范围(取值范围)就比原来小了一半。

但是在很多情况下,我们非常确定某个数字只能是正数,比如班级学生的人数、字符串的长度、内存地址等,这个时候符号位就是多余的了,就不如删掉符号位,把所有的位都用来存储数值,这样能表示的数值范围更大(大一倍)。
C语言允许我们这样做,如果不希望设置符号位,可以在数据类型前面加上 unsigned 关键字,例如:

unsigned short a = 12;
unsigned int b = 1002;
unsigned long c = 9892320;
这样,short、int、long 中就没有符号位了,所有的位都用来表示数值,正数的取值范围更大了。这也意味着,使用了 unsigned 后只能表示正数,不能再表示负数了。
如果将一个数字分为符号和数值两部分,那么不加 unsigned 的数字称为有符号数,能表示正数和负数,加了 unsigned 的数字称为无符号数,只能表示正数。
请读者注意一个小细节,如果是unsigned int类型,那么可以省略 int ,只写 unsigned,例如:

unsigned n = 100;
它等价于:
unsigned int n = 100;
无符号数的输出
无符号数可以以八进制、十进制和十六进制的形式输出,它们对应的格式控制符分别为:


严格来说,格式控制符和整数的符号是紧密相关的,具体就是:
  • %d 以十进制形式输出有符号数;
  • %u 以十进制形式输出无符号数;
  • %o 以八进制形式输出无符号数;
  • %x 以十六进制形式输出无符号数。
那么,如何以八进制和十六进制形式输出有符号数呢?很遗憾,printf 并不支持,也没有对应的格式控制符。在实际开发中,也基本没有“输出负的八进制数或者十六进制数”这样的需求,我想可能正是因为这一点,printf 才没有提供对应的格式控制符。

下表全面地总结了不同类型的整数,以不同进制的形式输出时对应的格式控制符(--表示没有对应的格式控制符)。




使用特权

评论回复
沙发
玛尼玛尼哄|  楼主 | 2023-2-13 22:04 | 只看该作者

当以有符号数的形式输出时,printf 会读取数字所占用的内存,并把最高位作为符号位,把剩下的内存作为数值位;
当以无符号数的形式输出时,printf 也会读取数字所占用的内存,并把所有的内存都作为数值位对待。

对于一个有符号的正数,它的符号位是 0,当按照无符号数的形式读取时,符号位就变成了数值位,但是该位恰好是 0 而不是 1,所以对数值不会产生影响,这就好比在一个数字前面加 0,有多少个 0 都不会影响数字的值。

如果对一个有符号的负数使用 %o 或者 %x 输出,那么结果就会大相径庭,读者可以亲试。

可以说,“有符号正数的最高位是 0”这个巧合才使得 %o 和 %x 输出有符号数时不会出错。

再次强调,不管是以 %o、%u、%x 输出有符号数,还是以 %d 输出无符号数,编译器都不会报错,只是对内存的解释不同了。%o、%d、%u、%x 这些格式控制符不会关心数字在定义时到底是有符号的还是无符号的:
你让我输出无符号数,那我在读取内存时就不区分符号位和数值位了,我会把所有的内存都看做数值位;
你让我输出有符号数,那我在读取内存时会把最高位作为符号位,把剩下的内存作为数值位。

说得再直接一些,我管你在定义时是有符号数还是无符号数呢,我只关心内存,有符号数也可以按照无符号数输出,无符号数也可以按照有符号数输出,至于输出结果对不对,那我就不管了,你自己承担风险。
#include <stdio.h>
int main()
{
    short a = 0100;  //八进制
    int b = -0x1;  //十六进制
    long c = 720;  //十进制
   
    unsigned short m = 0xffff;  //十六进制
    unsigned int n = 0x80000000;  //十六进制
    unsigned long p = 100;  //十进制
   
    //以无符号的形式输出有符号数
    printf("a=%#ho, b=%#x, c=%ld\n", a, b, c);
    //以有符号数的形式输出无符号类型(只能以十进制形式输出)
    printf("m=%hd, n=%d, p=%ld\n", m, n, p);
    return 0;
}

运行结果:
a=0100, b=0xffffffff, c=720
m=-1, n=-2147483648, p=100

使用特权

评论回复
板凳
小夏天的大西瓜| | 2023-2-14 08:33 | 只看该作者
负数的存储形式是其绝对值的存储形式按位取反,再加一

使用特权

评论回复
地板
tpgf| | 2023-3-7 16:49 | 只看该作者
负数和正数在机器中好像都不是一种编码吧

使用特权

评论回复
5
晓伍| | 2023-3-8 08:03 | 只看该作者
C语言中负数用补码标识,正数的补码就是其本身,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)。

使用特权

评论回复
6
八层楼| | 2023-3-8 08:21 | 只看该作者
因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值

使用特权

评论回复
7
观海| | 2023-3-8 09:12 | 只看该作者
最高位为1时,对于有符号来说,这个数是负数,而对于无符号数而言,由于最高位也是有效存储位,所以可以存储更大范围的数

使用特权

评论回复
8
guanjiaer| | 2023-3-8 09:27 | 只看该作者
我们平时写代码比较关心的就是数据的溢出

使用特权

评论回复
9
keaibukelian| | 2023-3-8 09:51 | 只看该作者
玛尼玛尼哄 发表于 2023-2-13 22:04
当以有符号数的形式输出时,printf 会读取数字所占用的内存,并把最高位作为符号位,把剩下的内存作为数值 ...

如果我不关心是不是有无符号那么我从内存读出来的数据如何识别是不是有符号无符号呢  数据的范围是不一样的啊

使用特权

评论回复
10
负数的存储形式是其绝对值的二进制形式 按位取反再加一

使用特权

评论回复
11
豌豆爹| | 2023-3-22 15:35 | 只看该作者
学习了,感谢各路大神的精彩讲解

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

168

主题

3045

帖子

2

粉丝