揭秘整数内存密码:从原码到补码,读懂计算机 “数字语言”

1.1. int类型变量的声明、定义、赋值

C/C++语言中,有很多种类型,而整型是用的最多的

声明一个变量:

int num; // 声明一个整型变量

声明并初始化一个变量:

int cnt = 0; // 声明并初始化一个变量

先声明一个变量,再赋值:

	// 先声明一个变量 再赋值
	int exm; // 声明一个变量,它的值是随机的
	exm = 10;// 给这个变量赋值

上面这两行代码和int exm = 10;的作用是一样的,但是赋值和初始化是两个不同的操作,

赋值是给已经存在的变量进行操作,初始化是在变量定义时进行操作

1.2. 未初始化的变量

必须强调这里未初始化的危险。如果一个变量没有初始化,C/C++ 编译器不会报告错误,甚至不会发出警告。C/C++标准中未初始化变量的值也没有明确定义。你可能会从一个未初始化的变量中得到一个随机值。为了程序的健壮性,强烈建议初始化变量,哪怕不知道这个变量到底值为多少,初始化成0也是很好的

int main() {
	int num1;
	int num2 = 10;
	cout << num1 << endl << num2 << endl;
}

VS2022强制检查,报错了

在这里插入图片描述

使用C语法也一样报错

在这里插入图片描述

2. 其他的整型

int 的宽度(以位为单位)在 C 和 C++ 标准中并不是固定的。标准 int 应具有 32 位。除了 int,还有 short intlong intlong long int``short int 是 16 位。int 是 16 位或 32 位。long int 是 32 位或 64 位(取决于编译器)。long long int 是 64 位。char 也是一种常用的整数类型。有人可能会感到困惑,认为 char 仅用于字符。由于字符被编码为整数值,char 确实是一种整数类型,并且具有 8 位。char 对英语字符来说足够宽,但对中文和其他一些字符则不够。char16_tchar32_t 已在 C++11 中引入,表示 16 位和 32 位的范围。

char c = 'C'; // ASCII码为80
char c = 80;// 十进制80
char c = 0x50;// 十六进制50 转为十进制80

上面这三行代码是等效的,在我们编码时,看着每个变量的类型不同,其实到计算机底层,所有数据都被表示成01串了,机器只能识别01两个指令

以下是各个整型类型所占空间大小

类型所占字节大小
char8
short (short int)16
int16 or 32
long (long int)32 or 64
long long (long long int)64

我们可以用sizeof操作符来获取类型大小

int main() {
	cout << sizeof(char) << endl;
	cout << sizeof(short) << endl;
	cout << sizeof(int) << endl;
	cout << sizeof(long) << endl;
	cout << sizeof(long long) << endl;

	return 0;
}

在这里插入图片描述

在我的机器上long是四字节

有符号和无符号数

signedunsigned 可以在整数类型名称之前使用,以指示整数是有符号的还是无符号的。当整数是有符号时,关键字 signed 可以省略。这意味着 int 是用于 signed int,而 short 是用于 signed short。如果整数是有符号的,最高位(int 的第 32 位)将是其符号位。如果符号位为 1,则为负数;如果为 0,则为正数

有符号整数和无符号整数如下面的图所示:

在这里插入图片描述

3. 整型数据的存储

3.1. 原码表示法

数据在底层用0和1这两个数存储,一个字节(8bit)可以表达多少个数?————>每个位置两种选择,共八个位置: 2 8 2^8 28

在这里插入图片描述

对于无符号数来说,最大的数就是 ( 2 8 − 1 ) (2^8 - 1) (281)

那怎么表示负数呢?牺牲最高位作为符号位,符号位为 1,则为负数;如果为 0,则为正数。,这便是原码

这样就能得到以下分类:

在这里插入图片描述

符号位后两个极端,全0或者全1,正数可以表达 2 7 2^7 27个,负数也可以表达 2 7 2^7 27个,但是出现一个问题:0被重复表达了两次!+0和-0,所以,原码表示法出现了缺陷:

  • 0的表示并不唯一,计算机是机器,不是人,无法理解多义的表达
  • 加法、减法的运算方式并不统一,减法需要借位,逻辑复杂

所以,引入补码

3.2. 补码表示法

3.2.1 模运算的引入

对于一个钟表,假定指针指向了10点的位置,我现在要将其拨到6点位置,有两种方式:

  • 倒拨四格: 10 − 4 = 6 10 - 4 = 6 104=6
  • 顺拨八格: 10 + 8 ——— > 6 10 + 8 ———>6 10+8———>6,转完了一圈,18 % 12 = 6

在这里插入图片描述

钟表是一个模十二的系统:那么就可以理解为 10 − 4 = 10 + 8 = 10 + ( 12 − 4 ) 10-4 = 10+8 = 10+(12-4) 104=10+8=10+(124)

这里的-48是相等的关系,那么就可以说8的补码是-4,同样的,9的补码是-3

在模十二的系统中,都遵循着一个原则:互为补码的两个数,不管符号,相加和为12,8+4,9+3,10+2的结果都是12

那么,在一个模 x x x的运算系统中,有两个数 1 1 1 − n -n n,一定满足 1 + n = x 1+n=x 1+n=x

那么就引出了补码的运算:加法和减法的运算逻辑相同

如果做减法,我们只需要加上这个数的补码即可,将减法转换成了加法,这样加减法共用一套逻辑

3.2. 补码表示法

**”8位2进制数“模运算系统(mod 2 8 2^8 28)**中,有两个数 1 1 1 − n -n n,一定满足 1 + n = 2 8 1+n=2^8 1+n=28

现在用 2 8 2^8 28减去一个1,即是

1 0000 0000 - 0000 0001————> 得到1111 1111,这个数,是-1的表达,所以-1的表达,就是 2 8 2^8 28减去1,相当于把原来的0000 0001各位取反再加1————>1111 1111,现在使得1111 1111 + 0000 0001————>1 0000 0000即是 2 8 2^8 28,这便是负数的补码

正数的补码是正数本身,负数的补码是符号位不变,其他位取反,末位加一

例如:-1:原码1000 0001 补码:1111 1111

现在回到之前的分类:

在这里插入图片描述

这样原来的-0:1000 0000就可以表示一个更小的数了

我们再将1000 0000转换成原码检查一下:

除了符号位全部取反->1111 1111,末尾+1->1 0000 0000,

八位存不下,最高位截断:0000 0000 ->0 0 + 2 8 = 2 8 0 + 2^8 = 2^8 0+28=28,满足“8位2进制数“模运算系统(mod 2 8 2^8 28)的规则

以下是常见整型的取值范围:

在这里插入图片描述

我们可以继续用一个圆来理解:
在这里插入图片描述

short int等其他类型同理

4. 小案例

4.1. 规定:若运算中同时有无符号和带符号整数,则按无符号整数运算

关系运算符类型结果说明
0 == 0U无符号1同为零
-1 < 0有符号1有符号类型-1<0
-1 < 0U无符号011…11( 2 32 − 1 2^{32}-1 2321) > 0
2147483647 > -2147483647 -1有符号1正数 >负数
2147483647U > -2147483647 - 1无符号001…1( 2 31 − 1 2^{31}-1 2311)<10…0( 2 31 2^{31} 231)
2147483647 >(int)2147483647U有符号101…1( 2 31 − 1 2^{31}-1 2311)>10…0( − 2 31 -2^{31} 231)
-1 >-2有符号1有符号类型-1>-2
(unsigned)-1 > -2无符号111…11( 2 32 − 1 2^{32}-1 2321) > 11…10( 2 32 − 2 2^{32}-2 2322)

对于intunsigned int 2 31 − 1 > − 2 31 2^{31}-1 > -2^{31} 2311>231

在这里插入图片描述

4.2. 计算机的世界只有0和1,具体怎么解读靠人的定义

int main() {
	int x = -1;
	unsigned u = 2147483648;
	printf("x = %u = %d\n", x, x);
	printf("u = %u = %d\n", u, u);

	return 0;
}
  1. 对于变量 x = -1
    • 在内存中,-1 作为 32 位有符号整数(补码)存储为全 1:11111111 11111111 11111111 11111111
    • 当用 %u(无符号整数格式)输出时,这个二进制被解读为无符号数,值为 4294967295(2³²-1)
    • 当用 %d(有符号整数格式)输出时,正确解读为 -1
  2. 对于变量 u = 2147483648
    • 作为无符号整数,它的二进制是 10000000 00000000 00000000 00000000
    • 当用 %u 输出时,正确显示为 2147483648
    • 当用 %d 输出时,这个二进制被解读为有符号整数(补码),由于最高位是 1(符号位),表示这是一个负数。在 32 位有符号整数中,这个值恰好是最小的负数 -2147483648

在这里插入图片描述

这个例子展示了C/C++整数的二进制存储本质:相同的二进制序列,根据解释方式(有符号 / 无符号)的不同,会呈现出完全不同的数值。格式化字符 %d%u 决定了如何解读内存中的二进制数据,而不是改变数据本身。

int main() {
	int b = -1;
	char c = -1;
	printf("%u,%u\n",b, c);

	return 0;
}
  1. 变量存储分析
    • int b = -1:作为 32 位有符号整数,-1的补码表示为全 1(11111111 11111111 11111111 11111111
    • char c = -1:在大多数系统中char是 8 位,-1的补码表示为11111111
  2. printf输出规则
    • 当使用%u(无符号整数格式)输出时,所有整数类型都会被提升为unsigned int
    • b作为 int 直接转换为 unsigned int,全 1 的 32 位二进制对应4294967295
    • c作为 char 首先进行符号扩展(因为是有符号 char),8 位的11111111扩展为 32 位的11111111 11111111 11111111 11111111,再转换为 unsigned int 也得到4294967295
    • 在这里插入图片描述

这个例子展示了C/C++整数提升符号扩展的规则:当小整数类型参与表达式运算或格式化输出时,会先提升为 int (有符号扩展),再根据格式控制符进行相应转换。

评论 28
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值