19.2 补充:进制的转换与详解
在日常生活中,我们使用的是十进制(Decimal),逢十进一。因为人类有十根手指,这是最符合直觉的计数方式。但在计算机底层的硬件世界里,只有高电平和低电平(或者说开和关),所以计算机内部完全使用二进制(Binary),也就是逢二进一。
我们写的任何数字、任何代码、任何图片甚至视频,最终存储在硬盘和内存里的,全都是 0 和 1 的组合。
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 个字母数字组合。
2. C++ 中各类进制的书写规范
Section titled “2. C++ 中各类进制的书写规范”在 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::hex 和 std::oct):
int my_num = 255;std::cout << my_num << "\n"; // 输出十进制:255std::cout << std::hex << my_num << "\n"; // 输出十六进制:ffstd::cout << std::oct << my_num << "\n"; // 输出八进制:377std::cout << std::dec << my_num << "\n"; // 切回十进制输出:2553. 进制之间的相互转换规则
Section titled “3. 进制之间的相互转换规则”掌握手工转换不仅是为了应付考试,更是为了在调试底层 Bug 时,你能拿着内存地址或者报错代码一眼看出其中隐藏的原理。
原理篇:按权展开法(其他进制 —> 回十进制)
Section titled “原理篇:按权展开法(其他进制 —> 回十进制)”无论是什么进制转回我们熟悉的十进制,方法高度统一:把每一位上的数字,乘以它的“当前位置权重”,然后全部相加。 权重都是从最右边(个位)开始,从 X^0 开始往左递增(X 代表进制)。
示例 A:十六进制 0x2A5 转十进制
- 5 处在第 0 位(最右),权重是 16^0 = 1。计算:5 \times 1 = 5
- A 代表十进制 10,处在第 1 位,权重是 16^1 = 16。计算:10 \times 16 = 160
- 2 处在第 2 位(最左),权重是 16^2 = 256。计算:2 \times 256 = 512
- 汇总相加: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 转二进制
- 11 \div 2 = 5 … 余 1 (这是产生的第一位,放在最右端,叫最低位)
- 5 \div 2 = 2 … 余 1
- 2 \div 2 = 1 … 余 0
- 1 \div 2 = 0 … 余 1 (商终于为 0,运算结束。这是最高位)
倒序抄写结果:1011 (0b1011)。
4. 终极奥义:二进制与十六进制的快速互转
Section titled “4. 终极奥义:二进制与十六进制的快速互转”这是真正的重点。熟练的 C++ 程序员在底层开发时,脑海里是没有“十进制”这个中间商的。
因为 1 个十六进制位刚好能撑满 4 个二进制位(从 0000 到 1111 正好对应十六进制的 0 到 F)。这种无缝对接意味着你可以像“查字典”一样进行文本级别的替换。
牢记这张核心映射表(必须熟背):
| 十六进制 | 二进制 (4位) | 十进制意义 |
|---|---|---|
| 0 | 0000 | 0 |
| 1 | 0001 | 1 |
| 2 | 0010 | 2 |
| 3 | 0011 | 3 |
| … | … | … |
| 8 | 1000 | 8 |
| 9 | 1001 | 9 |
| A | 1010 | 10 |
| B | 1011 | 11 |
| C | 1100 | 12 |
| D | 1101 | 13 |
| E | 1110 | 14 |
| F | 1111 | 15 |
二进制快速转十六进制:四位分组法
Section titled “二进制快速转十六进制:四位分组法”拿出一长串二进制数,从右向左,每 4 位切成一段(最左边如果不够 4 位,就在前面自动补 0)。然后对着查表,一节一节翻译过去。
实战:转换二进制 10111010
- 划分小组:
1011和1010 - 逐个翻译:
1011查表是 B,1010查表是 A - 最终得出十六进制结果:
0xBA
十六进制快速转二进制:直接展开法
Section titled “十六进制快速转二进制:直接展开法”反向操作更加简单。看到十六进制数字,把上面的字母和数字逐个拉长成 4 位一行的二进制小队,然后拼接起来就是完整的二进制结构了。
实战:转换十六进制 0x2F
- 逐个拉伸:
2对应的是0010,F对应的是1111 - 最终得出二进制结果:
0b00101111
这种完全无缝的直译能力,极大地简化了计算机内部数据的表达。你在开发中见到的所有内存地址(如 0x7ffee21b8c0 )、颜色代码(如纯白色 #FFFFFF 其实就是 0xFF 的 Red, 0xFF 的 Green, 0xFF 的 Blue),包括字符编码的标志,其核心根基都源自于此。