17世纪,德国数学家莱布尼茨(Gottfried Wilhelm Leibniz)发明了现代二进制系统。
他坚信:"万物皆数",而所有数都可以仅用 0 和 1 两个符号来表示。
这个看似简单的想法,在200年后成为了整个计算机科学的基石!
今天,你手机里的每一张照片、每一段视频、每一条消息,在最底层都只是无数个 0 和 1 的组合。
但人类习惯使用十进制(因为有10根手指),计算机却只认识二进制。如何在两种"语言"之间自由翻译?这就是本讲要解决的核心问题。
从自然数到实数,从十进制到二进制——掌握计算机世界的"数字语言",避开整数运算的隐形陷阱!
用故事建立直觉,用类比理解抽象概念。
17世纪,德国数学家莱布尼茨(Gottfried Wilhelm Leibniz)发明了现代二进制系统。
他坚信:"万物皆数",而所有数都可以仅用 0 和 1 两个符号来表示。
这个看似简单的想法,在200年后成为了整个计算机科学的基石!
今天,你手机里的每一张照片、每一段视频、每一条消息,在最底层都只是无数个 0 和 1 的组合。
但人类习惯使用十进制(因为有10根手指),计算机却只认识二进制。如何在两种"语言"之间自由翻译?这就是本讲要解决的核心问题。
1小时=60分钟,1分钟=60秒。满60进1。
→ 非十进制的日常例子周一到周日,满7天回到周一。
→ 模运算的生活原型人类有10根手指,所以发明了十进制。
→ 进制源于计数工具灯只有"开"和"关"两种状态,天然对应0和1。
→ 计算机选择二进制的物理原因数是信息学竞赛中最基本的操作对象。不同种类的数,在C++中有不同的表示方式和运算规则。
📌 本讲核心知识地图
| 板块 | 核心内容 | 对应大纲 |
|---|---|---|
| 数的分类 | 自然数、整数、有理数、实数 | 数及其运算 |
| 算术运算 | 加减乘除、整除、取余、溢出 | 数及其运算 |
| 进制概念 | 基数、位权、数码 | 进制与进制转换 |
| 通用转换 | R↔10 按权展开法 / 除基取余法 | 进制与进制转换 |
| 特殊转换 | 二↔八↔十六 分组对应法 | 进制与进制转换 |
按权展开求和,每位乘以基数的幂
从右往左数,最低位是零次方
除基取余倒排,商为零时停下来
先得余数是低位,后得余数高位摆
三位一组转八进,四位一组转十六
从小数点向两边分,不足补零凑整齐
系统掌握数的分类、运算规则与进制转换三大核心板块。
| 类别 | 符号 | 定义 | C++类型 | 示例 |
|---|---|---|---|---|
| 自然数 | ℕ | 非负整数(含0) | unsigned int | 0, 1, 2, 3... |
| 整数 | ℤ | 正整数、零、负整数 | int / long long | -3, 0, 42 |
| 有理数 | ℚ | 能表示为 p/q (q≠0) 的数 | double (近似) | 1/3, -0.5, 7 |
| 实数 | ℝ | 有理数 + 无理数 | double / long double | π, √2, 3.14 |
int / int = 整除(截断小数)int / double = 浮点除法% 只能用于整数double 有精度误差,不能直接判等intlong long| 运算符 | 名称 | 规则 | ⚠️ 陷阱 |
|---|---|---|---|
| + | 加法 | 正常相加 | 可能溢出 |
| - | 减法 | 正常相减 | 无符号数下溢变巨大正数 |
| * | 乘法 | 正常相乘 | 最容易溢出!先判断再乘 |
| / | 除法 | 整数÷整数=整除 | 7/2=3,不是3.5! |
| % | 取余 | a % b = a - (a/b)*b | 负数取余结果符号同被除数 |
-7 % 3 = -1(不是2!)7 % -3 = 1((a % b) + b) % b① 整除截断:7/2=3,-7/2=-3(向零取整)
② 溢出致命:int 最大约 2×10⁹,两数相乘极易超界 → 用 long long
③ 负余同号:-7%3=-1,想要正余数需额外处理
R进制:逢R进1,每一位上的数码范围是 0 ~ R-1。
| 进制 | 基数(R) | 可用数码 | 前缀/后缀 | 特点 |
|---|---|---|---|---|
| 二进制 | 2 | 0, 1 | 0b / B | 计算机内部存储 |
| 八进制 | 8 | 0~7 | 0 / O | Linux文件权限 |
| 十进制 | 10 | 0~9 | 无 | 人类日常使用 |
| 十六进制 | 16 | 0~9, A~F | 0x / H | 颜色代码、内存地址 |
任何一个R进制数都可以写成按权展开式:
(dₙdₙ₋₁...d₁d₀)ᵣ = dₙ×Rⁿ + dₙ₋₁×Rⁿ⁻¹ + ... + d₁×R¹ + d₀×R⁰
例如:(1A3)₁₆ = 1×16² + 10×16¹ + 3×16⁰ = 256 + 160 + 3 = 419
方法:将每一位数码乘以对应的位权(R的幂次),然后求和。
🖐 手推示例1:(1011)₂ → (?)₁₀
🖐 手推示例2:(2F)₁₆ → (?)₁₀
int toDecimal(string s, int base) {
int result = 0;
for (char c : s) {
int digit;
if (c >= '0' && c <= '9') digit = c - '0';
else digit = c - 'A' + 10; // A=10, B=11...
result = result * base + digit; // 秦九韶算法优化
}
return result;
}
方法:不断除以R取余数,直到商为0,然后将余数逆序排列。
🖐 手推示例:(25)₁₀ → (?)₂
| 步骤 | 除法 | 商 | 余数 | 说明 |
|---|---|---|---|---|
| ① | 25 ÷ 2 | 12 | 1 | 最低位(LSB) |
| ② | 12 ÷ 2 | 6 | 0 | |
| ③ | 6 ÷ 2 | 3 | 0 | |
| ④ | 3 ÷ 2 | 1 | 1 | |
| ⑤ | 1 ÷ 2 | 0 | 1 | 最高位(MSB) |
逆序读取余数:11001,即 (25)₁₀ = (11001)₂ ✅
string toBase(int n, int base) {
if (n == 0) return "0";
string result = "";
while (n > 0) {
int r = n % base;
char c = (r < 10) ? ('0' + r) : ('A' + r - 10);
result = c + result; // 余数插到前面
n /= base;
}
return result;
}
因为 8 = 2³,16 = 2⁴,所以可以直接分组对应,无需经过十进制中转!
110 101 → 65₈0011 1010 → 3A₁₆🖐 手推:(11010110)₂ → (?)₈ → (?)₁₆
0000=0 0001=1 0010=2 0011=3
0100=4 0101=5 0110=6 0111=7
1000=8 1001=9 1010=A 1011=B
1100=C 1101=D 1110=E 1111=F
通过详细的手推追踪,巩固运算规则和转换方法。
题目:在C++中,以下表达式的值分别是多少?
(A) 17 / 5
(B) 17 % 5
(C) -17 / 5
(D) -17 % 5
(E) 17 / 5 * 5 + 17 % 5
a/b*b + a%b == a 始终成立题目:将十六进制数 (3B7)₁₆ 分别转换为二进制、八进制和十进制。
① 转二进制(每位展开4位):
3 → 0011,B → 1011,7 → 0111
结果:(1110110111)₂
② 转八进制(从二进制3位一组):
分组:001 110 110 111
对应:1 6 6 7
结果:(1667)₈
③ 转十进制(按权展开):
3×16² + 11×16¹ + 7×16⁰
= 3×256 + 11×16 + 7
= 768 + 176 + 7 = 951
题目:不写完整过程,快速心算 (10000)₂ 等于十进制多少?
技巧:二进制中,1后面跟n个0 = 2ⁿ
(10000)₂ 中1后面有4个0 → 2⁴ = 16
同理:(100000000)₂ = 2⁸ = 256
(1111)₂ = 2⁴ - 1 = 15(全1 = 2ⁿ-1)
| 知识板块 | 必须记住的核心 |
|---|---|
| 数的分类 | ℕ⊂ℤ⊂ℚ⊂ℝ;C++中int整除、double有精度误差 |
| 整除与取余 | 向零截断;负余同号;a/b*b+a%b==a |
| R→10 | 按权展开求和(秦九韶算法优化) |
| 10→R | 除基取余,逆序排列 |
| 2↔8↔16 | 3位一组转八,4位一组转十六,无需经十进制 |
| 溢出防范 | 乘法前先判断;大范围用long long |
计算机使用补码(Two's Complement)表示负数:
① 正数的补码 = 原码本身
② 负数的补码 = 绝对值的二进制取反+1
例:-5 的8位补码 → 5=(00000101)₂ → 取反=11111010 → +1 = 11111011
好处:加减法统一处理,无需单独的减法电路!
1. 区分自然数、整数、有理数、实数,知道C++中对应类型。
2. 熟练进行四种进制之间的任意转换。
3. 掌握按权展开法和除基取余法两大通用算法。
4. 能用分组法快速完成二↔八↔十六互转。
5. 警惕整除截断、负数取余、溢出三大运算陷阱。
5 个初赛高频易错点,每个都有口诀帮你记住。
7 / 2 = 3.57 / 2 = 3(截断)7.0 / 2 或 (double)7 / 2-7 % 3 = 2-7 % 3 = -1| 转换方向 | 方法 | 关键步骤 |
|---|---|---|
| R → 10 | 按权展开法 | 每位 × R^位序,求和 |
| 10 → R | 除基取余法 | 反复÷R取余,逆序排列 |
| 2 → 8 | 3位分组 | 每3位二进制 = 1位八进制 |
| 2 → 16 | 4位分组 | 每4位二进制 = 1位十六进制 |
| 8 → 2 | 逐位展开 | 每位八进制 → 3位二进制 |
| 16 → 2 | 逐位展开 | 每位十六进制 → 4位二进制 |
| 8 ↔ 16 | 以2为桥 | 先转二进制,再转目标进制 |
ℕ ⊂ ℤ ⊂ ℚ ⊂ ℝ
C++: int / long long / double
int/int = 整除(截断)
整除截断:7/2=3
负余同号:-7%3=-1
溢出:乘法先用long long
按权展开求和
秦九韶:result = result×R + d
从高位到低位迭代
除基取余,逆序排列
先得低位,后得高位
商为0时停止
2↔8:3位一组
2↔16:4位一组
从右向左分组,左补零
正数:原码本身
负数:取反+1
统一加减法运算
A基础 / B提高 / C挑战,难度递增,点击展开答案与解析。
A. 11 B. 13 C. 15 D. 12
答案:B. 13
1×8 + 1×4 + 0×2 + 1×1 = 8+4+0+1 = 13
考点:二进制转十进制的通用方法是「按位权展开求和」:从右往左第 i 位(i 从 0 起)的权是 2i,把该位数字乘上权再累加。
易错:从左往右把最高位当成 20 起算,或直接把 1101 看成 1+1+0+1=3。
A. (72)₈ B. (62)₈ C. (74)₈ D. (58)₈
答案:A. (72)₈
58÷8=7余2,7÷8=0余7 → 逆序:72
考点:十进制转 R 进制的通用方法是「除 R 取余、结果逆序」,本题 R=8,每步留下的是低位。
易错:把余数按计算先后顺序正着连起来写成 27,忘记最后要逆序。
-11 % 4 的值是()A. 1 B. -3 C. 3 D. -1
答案:B. -3
-11 ÷ 4 = -2(向零截断),余数 = -11 - (-2)×4 = -11+8 = -3
考点:C++ 的整数除法是「向零截断」,取模满足恒等式 a % b = a - (a / b) * b,因此余数的符号跟随被除数 a。
易错:套用数学上「余数非负」的模定义算成 1,或与 Python 中 -11 % 4 == 1 的结果混淆。
A. 0.333... B. √2 C. -7/3 D. 0
答案:B. √2
√2 是无理数,不能表示为两个整数之比。0.333...=1/3是有理数。
考点:有理数就是能写成两个整数之比的数(含整数、有限小数、无限循环小数),只有无限不循环小数才是无理数。
易错:看到 0.333... 后面带省略号就误判为无理数,其实它是循环小数、等于 1/3。
A. 1010101111 B. 001010101111 C. 101010111100 D. 001010101110
答案:B
2→0010, A→1010, F→1111 → 001010101111
考点:十六进制与二进制可以「一位换四位」直接转换:每个十六进制数字对应恰好 4 位二进制,逐位展开后拼接即可。
易错:把 A 记成 1011、F 记成 1110,或每位只写 3 位二进制导致位数错位。
#include <iostream>
#include <string>
using namespace std;
int main() {
string s = "356"; // 待转换的八进制数
int k = 8; // 原进制
const char* digit = "0123456789ABCDEF"; // 16 进制数字表,10~15 写作 A~F
// 第一步:k 进制 → 十进制,按权展开
int dec = 0;
for (char c : s)
dec = dec * k + (c - '0'); // 每读一位就 3
cout << "(" << s << ")" << k << " = (" << dec << ")10" << endl;
// 第二步:十进制 → 十六进制, 8
char buf[64]; // 从低位到高位临时存放每一位
int len = 0;
int tmp = dec;
while (tmp > 0) {
buf[len++] = digit[tmp % 16]; // 取最低位
tmp /= 16; // 去掉最低位
}
cout << "(";
for (int i = len - 1; i >= 0; i--) // 倒序输出,先出来的低位移到最后
cout << buf[i];
cout << ")16" << endl; // 输出 (EE)16
return 0;
}
答案:(356)8 = (EE)16。
思路:两种做法都对。① 快速法(八、十六都是二的幂):八进制 1 位拆 3 位二进制,3→011、5→101、6→110,拼成 011101110;再从右往左每 4 位分一组:1110 1110 → E E。② 通用法(程序里的做法):先按权展开把八进制变成十进制——(356)8 = 3×64+5×8+6 = 238,再用"除 16 取余、结果倒序"把 238 变成十六进制,238÷16 余 14(E) → 商 14 ÷16 余 14(E) → 倒着读就是 EE。
考点:任意进制转换的两条路:k 进制 → 十进制用"按权展开",十进制 → k 进制用"while 取余 + 数组倒序输出";以及 2/8/16 之间"3 位 / 4 位分组"的快捷法。
易错:① 分组一定要从右往左,左边不够就补 0(这里刚好 9 位,补 1 个 0 成 011101110);② 十六进制里 10~15 要写成 A~F,别直接写成 10、11;③ 别忘了倒序——除基取余先得到的是最低位。
a = 17, b = 5,则 a - a/b*b 等价于()A. a / b B. a % b C. b % a D. a * b
答案:B. a % b
由恒等式 a/b*b + a%b == a,可得 a - a/b*b == a%b
考点:本质是整数除法的恒等式 a = (a / b) * b + a % b,移项即得 a - a / b * b 就是 a 除以 b 的余数。
易错:以为 a / b * b 等于 a 而选 0,忽略了整数除法会把余数直接舍掉。
A. 128 B. 255 C. 256 D. 127
答案:B. 255
8位全1 = (11111111)₂ = 2⁸-1 = 255
考点:n 位二进制共有 2n 个不同编码,无符号时从 0 开始编号,最大值是 2n-1。
易错:直接记成 28=256,忘了编码从 0 起算,最大值应是 255。
25 / 3 * 3 + 25 % 3 的值是()A. 24 B. 25 C. 27 D. 不确定
答案:B. 25
25/3=8, 8*3=24, 25%3=1, 24+1=25。这就是恒等式 a/b*b+a%b==a。
考点:考查 / 与 % 的优先级相同、从左到右求值,以及恒等式 a / b * b + a % b == a 这一整数除法的基本性质。
易错:把 25 / 3 当作 8.33 带入浮点运算,或先算 25 % 3 改变求值顺序。
A. (7E8)₁₆ B. (7D8)₁₆ C. (8E8)₁₆ D. (7F8)₁₆
答案:A. (7E8)₁₆
2024÷16=126余8,126÷16=7余14(E),7÷16=0余7 → 7E8
考点:十进制转十六进制同样用「除 16 取余、逆序排列」,只是余数 10~15 要写成 A~F。
易错:把余数 14 直接写成两位数字 14 而不是 E,或忘记把三组余数逆序。
A. 5 B. 6 C. 7 D. 8
答案:D. 8
设进制为R,则 (14)ᵣ = 1×R + 4 = R+4。又3×4=12,所以R+4=12,R=8。
考点:设进制为 R,把结果 (14)R 按位权展开成 R + 4,与十进制下的乘积 12 建立等式解出 R。
易错:直接用 14 - 4 = 10 或 14 ÷ 4 得答案,忽略了结果本身也是 R 进制数。
A. 正数的补码等于其原码
B. 负数的补码是其绝对值取反加1
C. 补码的最高位为符号位,0表示负
D. 使用补码可以将减法转化为加法
答案:C
补码最高位为符号位,0表示正,1表示负。C说反了。
考点:补码规则:正数的补码与原码相同,负数的补码是「反码加 1」;最高位始终是符号位,0 表示正、1 表示负。
易错:把符号位约定记反(以为 1 表示正),或误认为补码最高位只是普通数据位。