别让数字“方言”坑了你:从一道送分题看进制转换的门道
你有没有在调试程序时,盯着一个 0x2A 看了半天,愣是没反应过来它其实就是 42?或者更惨——写了个 int x = 08;,编译器直接报错,你还在想“8 不是挺正常的吗”?
别笑,这些都是进制“方言”惹的祸。今天我们就来把二进制、八进制、十进制、十六进制这四位“兄弟”彻底捋清楚,顺便看一道经典的进制转换题,看看它到底在考什么。
数字的“方言”:同一种价值,不同的写法
先抛个问题:为什么计算机不用十进制?
答案很简单——因为硬件太“笨”了。CPU 里的晶体管只有两个稳定状态:导通(1)和截止(0)。你让它用一个电压表示 0~9 的十个数字,它得精确区分十种电平,成本高、易出错。但只区分“有电”和“没电”,那就稳如老狗。
所以计算机的母语是二进制,逢二进一。但二进制写起来实在太长,比如十进制 255,二进制是 11111111,写 8 位还行,要是 65535 呢?16 位。人眼扫过去就花了。
这时候就轮到八进制和十六进制登场了。它们不是新语言,而是二进制的“缩写”:
- 3 位二进制正好能表示 0~7,对应 1 位八进制。
- 4 位二进制正好能表示 0~15,对应 1 位十六进制。
所以 0b101101 你可以直接按 4 位一组拆开:0010 1101,第一位是 2,第二位是 13(即 D),合起来就是 0x2D。多清爽。
C++ 里的“方言”切换:前缀就是身份证
在 C++ 里,你写一个数字字面量,编译器靠前缀来识别它的进制:
| 进制 | 前缀 | 例子 |
|---|---|---|
| 十进制 | 无 | 42 |
| 二进制 | 0b(C++14+) | 0b101010 |
| 八进制 | 0(数字零) | 052 |
| 十六进制 | 0x 或 0X | 0x2A |
注意那个八进制的前缀是数字 0,不是字母 o。很多人在这里栽跟头——包括我曾经带过的学生,他写 int a = 0o52; 然后编译器报错,他还一脸无辜地说“我加了前缀啊”。
记住:C++ 里没有 0o 这种写法,那是 Python 的规矩。 在 C++ 里,看见 0 开头就是八进制,看见 0x 就是十六进制,其余全是十进制。
关键点:cout 的“方言”设置是全局开关
很多初学者在 C++ 里用 cout 输出十六进制时,会遇到一个诡异现象:
int a = 100;
cout << hex << a << endl; // 输出 64
cout << a << endl; // 还是 64!不是 100!
为什么?因为 hex 是一个流操控符,它一旦被设置,就会一直生效,直到你改回 dec。这就像你手机切换了语言,整个界面都变了,不是只改一个按钮。
所以如果你用 hex 输出完,想恢复十进制,记得手动 cout << dec;。这个细节在很多题里会坑人,尤其是那些需要多次输出的程序。
一道“送分题”背后的考点
来看一道经典的进制转换题:二进制数 101101 转换为十进制是多少?
这题看着简单,但错误率不低。有人选 53,有人选 43,还有人选 55。为什么?
核心考点就一个:按权展开求和。
从最低位(最右边)开始,权重是 2⁰ = 1,向左依次是 2¹ = 2、2² = 4、2³ = 8、2⁴ = 16、2⁵ = 32。
所以:
1×2⁵ + 0×2⁴ + 1×2³ + 1×2² + 0×2¹ + 1×2⁰
= 32 + 0 + 8 + 4 + 0 + 1
= 45
选 53 的人,多半是把 2⁵ 当成了 48(即 2⁴×3),或者把 2⁴ 当成了 16 却忘了还有 2⁵ 这一位。选 43 的人,可能是漏了最低位的 1(2⁰)。选 55 的人,可能是把 2⁵ 当成了 64。
这道题真正的考点不是计算,而是你是否理解“位权”这个概念。 很多学生背口诀“从右往左 1、2、4、8、16……”,但一遇到中间有 0 的二进制数就慌了,不知道 0 也要乘权重。记住:每一位都要乘,不管它是 0 还是 1。
进阶:回文数那道题,才是真正的试金石
如果你觉得上面那道题太简单,来看这道更综合的:给定一个 N 进制数 M,求最少经过多少步(每一步把 M 和它的逆序相加)能得到回文数?
这道题考察的就不是单纯的进制转换了,而是:
- 进制加法——在 N 进制下做加法,逢 N 进一,不是逢十进一。
- 进制表示——16 进制里 A-F 代表 10-15,需要处理字符和数字的转换。
- 回文判断——一个数从左读和从右读一样。
解题思路是这样的:
- 把输入的字符串 M 转换成数值数组(或者直接用字符串操作)。
- 写一个“逆序相加”的函数,注意进位规则是 N 而不是 10。
- 每加一次,判断结果是否是回文数,如果是就输出步数,超过 30 步就输出
Impossible!。
关键代码片段:
// 在 N 进制下,把数组 a 和它的逆序相加,结果存回 a
void add_reverse(vector<int>& a, int N) {
int n = a.size();
vector<int> b(n);
for (int i = 0; i < n; i++) b[i] = a[n - 1 - i]; // 逆序
int carry = 0;
for (int i = n - 1; i >= 0; i--) {
int sum = a[i] + b[i] + carry;
a[i] = sum % N;
carry = sum / N;
}
if (carry) a.insert(a.begin(), carry);
}
注意这里的 sum % N 和 sum / N,就是 N 进制加法的核心——逢 N 进一。很多学生在这里会惯性写成 % 10 和 / 10,那就是用十进制思维做 N 进制题,必错。
进制转换的本质:换汤不换药
说到底,进制只是数字的“表示法”,不是数字本身。就像同一个苹果,中文叫“苹果”,英文叫“apple”,法文叫“pomme”——苹果还是那个苹果,只是叫法不同。
理解进制转换的关键,是跳出“十进制思维”的舒适区。 当你看到一个二进制数 101101 时,不要试图把它“翻译”成十进制再去理解,而是直接看它的二进制结构:32 + 8 + 4 + 1 = 45。这样你才能真正理解位运算、内存地址、颜色值这些底层概念。
给你的下一步建议
学完进制转换,你可以试着:
- 写一个通用的进制转换器——输入一个数和目标进制,输出转换结果,自己实现短除法,不依赖
cout的操控符。 - 研究位运算——
&、|、^、<<、>>这些操作都是直接对二进制位进行的,理解了进制,位运算就是小菜一碟。 - 看内存地址——调试器里显示的
0x7ffeefbff5a8这种地址,用十六进制看是习惯,但你能一眼看出它大概是多少吗?练多了就有感觉了。
进制是计算机世界的“通用语言”,掌握了它,你就拿到了读懂计算机底层逻辑的钥匙。别怕它,它只是数字的另一种“方言”而已。
关于作者
我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。
这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。
如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)