Skip to content

19.2 补充:进制的转换与详解

在日常生活中,我们使用的是十进制(Decimal),逢十进一。因为人类有十根手指,这是最符合直觉的计数方式。但在计算机底层的硬件世界里,只有高电平和低电平(或者说开和关),所以计算机内部完全使用二进制(Binary),也就是逢二进一。

我们写的任何数字、任何代码、任何图片甚至视频,最终存储在硬盘和内存里的,全都是 01 的组合。

1. 为什么我们需要八进制和十六进制?

Section titled “1. 为什么我们需要八进制和十六进制?”

既然计算机只懂二进制,为什么我们在写 C++ 代码时,经常需要学习和使用**八进制(Octal)十六进制(Hexadecimal)**呢?

根本原因:二进制太长了,反人类阅读。

假设我们要表示一个普通的十进制数字:9999。

  • 二进制下,它是:10011100001111(太长,数错一个0或1整个程序就挂了)
  • 八进制下,它是:23417
  • 十六进制下,它是:270F(极致简短)

为什么偏偏是八和十六进制?因为它们与二进制之间有着天衣无缝的数学对应关系,可以直接进行无脑的“文本替换”,完全不需要像十进制那样还要做运算。

  • 1 个八进制位,完美对应 3 个二进制位(因为 2^3 = 8)。
  • 1 个十六进制位,完美对应 4 个二进制位(因为 2^4 = 16)。

这就意味着,十六进制和八进制本质上就是二进制的高级缩写形式。在底层开发(如直接操作内存、位图图像、网络数据包抓取)中,我们使用十六进制既能看清单个“位(bit)”的状态,又能把 32 个 0 和 1 缩短成短短的 8 个字母数字组合。

在 C++ 中,你可以极其方便地直接将不同进制的数字赋值给整数变量。请注意,无论你用哪种进制写,变量在内存里存的永远是二进制。进制只是你写给编译器看的“阅读格式”而已。

你需要通过特殊的前缀来告诉编译器这个数字是几进制:

#include <iostream>
int main() {
// 【1. 十进制 (Decimal)】
// 无前缀,最普通常见的写法,包含数字 0-9
int dec_val = 42;
// 【2. 二进制 (Binary) - C++14 标准引入】
// 语法:以 0b 或 0B 开头,只能包含数字 0 和 1
// (计算:1*32 + 0*16 + 1*8 + 0*4 + 1*2 + 0*1 = 42)
int bin_val = 0b101010;
// 【3. 八进制 (Octal)】
// 语法:以数字 0 作为前缀,只能包含数字 0-7
// 极其容易犯的错:不要为了对齐在十进制前加0!052 并不等于 52!
// (计算:5*8 + 2*1 = 42)
int oct_val = 052;
// 【4. 十六进制 (Hexadecimal)】
// 语法:以 0x 或 0X 作为开头
// 包含数字 0-9 和字母 a-f (或 A-F 代表数值 10-15)
// (计算:2*16 + 10*1 = 42)
int hex_val = 0x2A;
// 无论你在代码里用什么进制定义,cout 默认都会直接输出十进制
std::cout << "十进制 42 的输出: " << dec_val << "\n";
std::cout << "二进制 0b101010 的输出: " << bin_val << "\n";
std::cout << "八进制 052 的输出: " << oct_val << "\n";
std::cout << "十六进制 0x2A 的输出: " << hex_val << "\n";
return 0;
}

补充技巧:如何让 cout 输出特定的进制?

Section titled “补充技巧:如何让 cout 输出特定的进制?”

你可以使用 C++ 标准库提供的格式化操作符(在 <iostream> 中包含的 std::hexstd::oct):

int my_num = 255;
std::cout << my_num << "\n"; // 输出十进制:255
std::cout << std::hex << my_num << "\n"; // 输出十六进制:ff
std::cout << std::oct << my_num << "\n"; // 输出八进制:377
std::cout << std::dec << my_num << "\n"; // 切回十进制输出:255

掌握手工转换不仅是为了应付考试,更是为了在调试底层 Bug 时,你能拿着内存地址或者报错代码一眼看出其中隐藏的原理。

原理篇:按权展开法(其他进制 —> 回十进制)

Section titled “原理篇:按权展开法(其他进制 —> 回十进制)”

无论是什么进制转回我们熟悉的十进制,方法高度统一:把每一位上的数字,乘以它的“当前位置权重”,然后全部相加。 权重都是从最右边(个位)开始,从 X^0 开始往左递增(X 代表进制)。

示例 A:十六进制 0x2A5 转十进制

  1. 5 处在第 0 位(最右),权重是 16^0 = 1。计算:5 \times 1 = 5
  2. A 代表十进制 10,处在第 1 位,权重是 16^1 = 16。计算:10 \times 16 = 160
  3. 2 处在第 2 位(最左),权重是 16^2 = 256。计算:2 \times 256 = 512
  4. 汇总相加:512 + 160 + 5 = 677

示例 B:二进制 0b1011 转十进制

  • 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0
  • = 8 + 0 + 2 + 1 = 11

原理篇:除基取余法(十进制 —> 到其他进制)

Section titled “原理篇:除基取余法(十进制 —> 到其他进制)”

要把某个十进制数变成别的进制,就去不断除以那个目标进制(转二进制就除以 2,转十六进制就除以 16)。 每次记录下得到的“余数”。拿新的“商”继续除,直到商变为 0。 最后,把所有的余数**从后往前(倒序)**拼装起来。

示例:十进制 11 转二进制

  1. 11 \div 2 = 5 … 余 1 (这是产生的第一位,放在最右端,叫最低位)
  2. 5 \div 2 = 2 … 余 1
  3. 2 \div 2 = 1 … 余 0
  4. 1 \div 2 = 0 … 余 1 (商终于为 0,运算结束。这是最高位)

倒序抄写结果:1011 (0b1011)。


4. 终极奥义:二进制与十六进制的快速互转

Section titled “4. 终极奥义:二进制与十六进制的快速互转”

这是真正的重点。熟练的 C++ 程序员在底层开发时,脑海里是没有“十进制”这个中间商的。

因为 1 个十六进制位刚好能撑满 4 个二进制位(从 00001111 正好对应十六进制的 0F)。这种无缝对接意味着你可以像“查字典”一样进行文本级别的替换

牢记这张核心映射表(必须熟背):

十六进制二进制 (4位)十进制意义
000000
100011
200102
300113
810008
910019
A101010
B101111
C110012
D110113
E111014
F111115

二进制快速转十六进制:四位分组法

Section titled “二进制快速转十六进制:四位分组法”

拿出一长串二进制数,从右向左,每 4 位切成一段(最左边如果不够 4 位,就在前面自动补 0)。然后对着查表,一节一节翻译过去。

实战:转换二进制 10111010

  • 划分小组: 10111010
  • 逐个翻译:1011 查表是 B1010 查表是 A
  • 最终得出十六进制结果:0xBA

十六进制快速转二进制:直接展开法

Section titled “十六进制快速转二进制:直接展开法”

反向操作更加简单。看到十六进制数字,把上面的字母和数字逐个拉长成 4 位一行的二进制小队,然后拼接起来就是完整的二进制结构了。

实战:转换十六进制 0x2F

  • 逐个拉伸:2 对应的是 0010F 对应的是 1111
  • 最终得出二进制结果:0b00101111

这种完全无缝的直译能力,极大地简化了计算机内部数据的表达。你在开发中见到的所有内存地址(如 0x7ffee21b8c0 )、颜色代码(如纯白色 #FFFFFF 其实就是 0xFF 的 Red, 0xFF 的 Green, 0xFF 的 Blue),包括字符编码的标志,其核心根基都源自于此。