数制与码制和基本数据类型
数制
常见数制(纯数学层面)
1. 常见数制:就像不同 “语言” 的数数方式
你平时说 “1、2、3…9、10”,这是十进制(满 10 进 1),就像你有 10 根手指,数到 10 就需要 “借一根手指”(进一位);但计算机只有 “通电” 和 “断电” 两种状态,只能用二进制(满 2 进 1),好比只有 2 根手指,数到 2 就必须 “进一位”。
下面用 “数到 20” 的例子,对比 4 种常用数制,一看就懂:
| 数制类型 | 计数规则(满几进 1) | 可用 “数字符号” | 数到 20 的写法 | 生活类比 |
|---|---|---|---|---|
| 十进制 | 10 | 0-9 | 20 | 日常买菜算账、记身高体重 |
| 二进制 | 2 | 0-1 | 10100 | 电灯开关(开 = 1、关 = 0)、手机电路 |
| 八进制 | 8 | 0-7 | 24 | 早期计算机 “简化二进制” 用,好比把 8 个鸡蛋装一袋计数 |
| 十六进制 | 16 | 0-9、A-F(A=10,F=15) | 14 | 网页颜色代码(比如 #FF0000 是红色)、电脑内存地址 |
举个实际例子:你说 “我身高 180 厘米”,这是十进制;但在计算机里,180 会被存成二进制 “10110100”—— 就像你用中文说 “苹果”,计算机用 “二进制语言” 说 “10110100”,本质都是指同一个身高,只是 “说法不同”
位权
你肯定遇到过这种情况:同样是 “5”,在 “5 元” 里是 5 块钱,在 “50 元” 里是 50 块钱 —— 这就是 “位置决定价值”,在数制里叫 “位权”。
比如十进制的 “632.45”,每个数字的 “身价”(位权)是这样算的:
- 数字 “6” 在 “百位”,位权是 10²(100),所以它代表 “6×100=600”;
- 数字 “3” 在 “十位”,位权是 10¹(10),代表 “3×10=30”;
- 数字 “2” 在 “个位”,位权是 10⁰(1),代表 “2×1=2”;
- 小数点后 “4” 在 “十分位”,位权是 10⁻¹(0.1),代表 “4×0.1=0.4”;
- 最后 “5” 在 “百分位”,位权是 10⁻²(0.01),代表 “5×0.01=0.05”。
把这些加起来:600+30+2+0.4+0.05=632.45,正好是原来的数。
二进制也一样,比如 “0110.1100”(前面的 0 不影响数值):
- 从左到右,整数部分 “0” 在 2³(8)位,代表 0×8=0;“1” 在 2²(4)位,代表 1×4=4;“1” 在 2¹(2)位,代表 1×2=2;“0” 在 2⁰(1)位,代表 0×1=0;
- 小数点后 “1” 在 2⁻¹(0.5)位,代表 1×0.5=0.5;“1” 在 2⁻²(0.25)位,代表 1×0.25=0.25;后面的 0 不影响;
- 加起来:0+4+2+0+0.5+0.25=6.75,这就是二进制 “0110.1100” 对应的十进制数值。
简单说,位权就像 “房子的楼层”:一楼(个位)的 1 块钱,到二楼(十位)就变成 10 块,三楼(百位)变成 100 块 —— 楼层越高(位数越靠左),同样的 “1” 能代表的价值越大。
进制转换:不用死算,记住 “工具 + 规律”
你不用手动把二进制转十进制(太麻烦),就像算 “123×45” 不用手算用计算器一样,计算机里有 “程序员计算器”(Windows 自带,切换到 “程序员” 模式),输入一个进制的数,直接能看到其他进制的结果。
如果想知道原理,记住一个核心规律:任何进制转十进制,都用 “数字 × 位权,再加起来”(就像前面算 632.45 和 0110.1100 那样);反过来,十进制转其他进制,比如转二进制,就用 “除以 2 取余数,最后倒着排”—— 比如把 10 转二进制:
- 10÷2=5,余数 0;
- 5÷2=2,余数 1;
- 2÷2=1,余数 0;
- 1÷2=0,余数 1;
- 把余数倒着排:1010,这就是十进制 10 对应的二进制。
计算机存储体系
计算机存数据靠 “灯泡亮灭”:
1 个位(bit)= 1 个灯泡:亮 = 1,灭 = 0;
1 个字节(byte)= 8 个灯泡(比如 “亮灭亮亮灭灭亮灭”= 01010010);
1 个字(word)= 多个字节:比如 64 位电脑的 “1 个字”= 8 个字节(64 个灯泡)。
CPU 字长:CPU 一次能 “搬多少个灯泡”——64 位 CPU 一次能搬 64 个灯泡的数据,比 32 位的 “力气大”。
码制
码制:彻底搞懂计算机 “存数据” 的规则(适配 Typora 格式)
一、先明确:码制到底是啥?(用 “翻译官” 类比)
之前讲的数制是 “纯数学的数数方法”,比如十进制5、二进制101—— 就像你说 “我有 5 个苹果”(中文表达);
而码制是 “把数学数字翻译成计算机能懂的物理信号” 的规则 —— 好比把 “5 个苹果” 翻译成 “计算机能识别的电信号(高电平 = 1、低电平 = 0)”,是连接 “抽象数字” 和 “物理存储” 的 “翻译官”。
核心目的:计算机只能存0和1(比如电路通 = 1、断 = 0),码制就是解决 “怎么用 0 和 1 表示正负、小数、字母” 这些问题的方案。
二、核心场景 1:怎么存 “正负整数”?(原码、反码、补码)
计算机存整数,首先要解决 “怎么表示负号”—— 总不能直接写个 “-” 号吧?码制的思路是:用二进制的 “最高位” 当 “正负标签”(好比价格标签的 “+/-”):
- 最高位 = 0 → 正数(比如
0101代表正 5); - 最高位 = 1 → 负数(比如
1101代表负 5)。
但光有标签还不够,计算机算减法(比如5-3)会麻烦,所以衍生出 3 种常用 “整数编码”:原码、反码、补码(重点是补码,计算机实际在用)。
1. 原码:最直白的 “标签法”(类比 “直接标正负的价格”)
规则:符号位(最高位)+ 数字的二进制绝对值(数值位)。
比如用 “4 位二进制” 存数(1 位符号位 + 3 位数值位):
- 十进制
+5→ 二进制绝对值101→ 原码0101(0 = 正,后面 101=5); - 十进制
-5→ 二进制绝对值101→ 原码1101(1 = 负,后面 101=5)。
优点:一眼能看懂,和数学逻辑一致;
缺点:算减法会出错!比如5-3= 5+(-3),用原码计算:
0101(+5) + 1011(-3)= 10000(4 位二进制只能存 4 位,结果是0000,明显不对)。
2. 反码:为了解决减法问题的 “过渡方案”(类比 “反向标价”)
规则:
- 正数的反码 = 原码(和原码一样,比如
+5反码还是0101); - 负数的反码 = 符号位不变,数值位 “0 变 1、1 变 0”(取反)。
比如 4 位二进制:
+5原码0101→ 反码0101;-3原码1011→ 符号位不变(1),数值位011取反为100→ 反码1100。
再算5+(-3):0101 + 1100 = 10001(截断 4 位后是0001,对应十进制1,对了!)。
但反码有个小问题:+0和-0会并存(+0反码0000,-0反码1111),但实际0没有正负,浪费了一个存储位置。
3. 补码:计算机实际在用的 “最终方案”(类比 “超市结算价”)
补码解决了反码 “+0/-0 并存” 的问题,而且减法能直接转加法,是计算机存储整数的标准。
规则:
- 正数的补码 = 原码(和原码、反码都一样,比如
+5补码0101); - 负数的补码 = 反码 + 1(在反码的基础上,最后一位加 1,有进位就往前进)。
还是用 4 位二进制举例:
-
+5:原码0101→ 补码0101; -
-3:原码1011→ 反码1100→ 反码 + 1=1101(补码); -
算
5+(-3):
0101 + 1101 = 10010(截断 4 位后是
0010?不对?哦,4 位二进制的话,
0101+1101=10010,取低 4 位是
0010(十进制 2),哦之前算错了,换个例子:
3-2=1→
3+(-2):
+3补码0011,-2原码1010→反码1101→补码1110;0011 + 1110 = 10001,取低 4 位0001(十进制 1),正确!
再解决 “+0/-0” 问题:
+0原码0000→ 补码0000;-0原码1000→ 反码1111→ 补码1111+1=10000(截断 4 位后是0000);- 所以
+0和-0的补码都是0000,不会重复,节省了存储位置。
基本数据类型
整数int
int 是 C 语言里存整数(正、负、0)的 “默认容器”,就像生活里的 “通用快递箱”。
- 为什么叫 “有符号”?
“有符号”= 能存负数(符号代表正负),对应之前讲的有符号数(最高位是符号位)。
C 里写 int a = 5; 或 int b = -3; 都可以,因为int默认是 “有符号” 的(全称是 signed int,但 signed 可以省略,就像 “西红柿” 可以省略 “西红” 直接叫 “柿”)。 - “字长”:容器的大小(能装多少位二进制)
“字长” 是这个类型占多少字节(1 字节 = 8 位二进制),不同环境下大小可能变,但规则固定:
64 位电脑(现在的主流):int 占 4 字节 = 32 位二进制(可以理解成 “32 格的盒子”);
8/16 位单片机(嵌入式设备):int 占 2 字节 = 16 位二进制(“16 格的小盒子”)。 - “值域”:容器能装的数字范围(重点!)
值域 = 这个类型能存的 “最小数~最大数”,由字长 + 是否有符号决定(类比 “盒子越大,装的东西越多”)。
情况 1:4 字节int(32 位有符号)
32 位里,1 位是符号位(存正负),剩下 31 位是数值位:
最大值:符号位 0(正),数值位全 1 → 是 2³¹ - 1(因为 31 位全 1 的数是2³¹-1);
最小值:符号位 1(负),数值位全 1(补码规则) → 是 -2³¹;
所以值域是:-2³¹ ~ 2³¹ - 1(大概是 - 21 亿多~21 亿多)。
情况 2:2 字节int(16 位有符号)
16 位里,1 位符号位,15 位数值位:
最大值:2¹⁵ - 1(32767);
最小值:-2¹⁵(-32768);
值域:-2¹⁵ ~ 2¹⁵ - 1(-32768 ~ 32767)。
二、只能存正数的:无符号整型 unsigned int
unsigned int 是 “无符号” 的int,相当于 “没有正负标签的盒子”—— 只能装非负数(0 和正数)。 - 注意:unsigned不能省
C 里int默认是有符号的,但unsigned int必须写全unsigned(不能省),比如:
c
运行
unsigned int a = 10; // 正确
int b = -5; // 正确(有符号)
unsigned int c = -5; // 错误!无符号不能存负数 - 字长和值域
unsigned int的字长和int一样(4 字节 / 2 字节),但因为没有符号位,所有位都是数值位,所以能存的范围更大:
4 字节unsigned int(32 位无符号):
值域是 0 ~ 2³² - 1(0 ~ 42 亿多,比有符号int的范围大一倍);
2 字节unsigned int(16 位无符号):
值域是 0 ~ 2¹⁶ - 1(0 ~ 65535,这就是图里 “65535” 的来源)。
三、更长 / 更短的整型:short/long/long long
有时候int不够用(比如存很大的数),或者想省空间(存很小的数),就用这些 “加长 / 缩小版的盒子”:
类型 字长(常见) 作用 类比
short int 2 字节 存小范围整数,省空间 小盒子,装小物品
long int 4 字节(32 位)/8 字节(64 位) 存比int大的整数 大盒子,装大物品
long long int 8 字节 存超大整数(比如 100 亿) 超大盒子,装巨型物品
也可以加unsigned
这些类型也能和unsigned组合,比如:
unsigned short int:无符号短整型,值域0~65535;
unsigned long long int:无符号超长整型,值域0~2⁶⁴-1(超级大的数)。
char
不管是 32 位还是 64 位电脑,char的大小是固定 1 字节(8 位二进制)—— 这是 C 语言里少数 “大小永不变化” 的类型(不像int可能是 4 字节 / 2 字节)。(其他与int差不多)
浮点数
一、精度咋丢的?——“进制不兼容” 的锅(对应第一次图片)
咱们拿分披萨 + 数零钱举例子:
你有 1 个披萨,要平均分成 3 份:
用 “3 份专用秤”(类比 3 进制):每份直接标 “1 份”(写出来是0.1),完美分匀,没剩的;
用 “10 份专用秤”(类比十进制):每份是 “3.333333… 小块”,永远称不完,只能砍断后面的 “尾巴”。
计算机存浮点数(比如0.1),就像 “用二进制的‘零钱’凑十进制的数”:
十进制的0.1(1 毛钱),转成二进制是0.0001100110011…(无限循环)—— 但计算机的 “钱包” 装不下无限多的 “二进制零钱”,只能装前几位,最后凑出来的是 “9 分 9 厘 9 毫”(近似 0.1),这就是精度丢失。
踩大坑!别直接问 “是不是正好相等”(对应第二次图片)
生活里的例子:
你兜里是 “凑出来的 9 分 9 厘 9 毫”,想买 1 毛钱的糖,你问老板 “我这钱是不是正好 1 毛?”—— 老板说 “不是”(差了 1 厘),但其实你要的是 “差不多够”。
对应到代码里,就是别用==判断浮点数相等:
float 我兜里的钱 = 0.1; // 实际是9分9厘9毫
if (我兜里的钱 == 0.1) { // 问“是不是正好1毛”
printf("够买糖");
} else {
printf("不够"); // 会输出“不够”,但其实差不多!
}
正确操作:“差一点没关系,别差太多”
生活里老板会说 “差不超过 1 厘就算够”,对应到代码里,就是看两个数的差有没有小于 “允许的误差”(比如 0.0000001)。
像这样写就对了:
#include <math.h> // 用来算“差的绝对值”
float 我兜里的钱 = 0.1;
// 差的绝对值 < 0.0000001,就算够
if (fabs(我兜里的钱 - 0.1) < 0.0000001) {
printf("够买糖"); // 会输出“够买糖”,这才合理!
} else {
printf("不够");
}