在C语言里打印二进制

标准的 C 语言(C23 之前)里没有类似 %b 这样的二进制格式化输出符。想要打印二进制,必须老老实实地利用右移 (>>)按位与 (&),把数字里面的每一位剥离出来,逐个打印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main() {
int n;
scanf_s("%d", &n);

for (int i = 0; i < (1 << n); i++) {
for (int j = n - 1; j >= 0; j--) {
printf("%d", (i >> j) & 1);
}
printf("\n");
}

return 0;
}

妈的,感觉C语言功力尽失。

下面是递归回溯法,现在我状态不是很好,所以不是我写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

char arr[16];
int n;

void generate_binary(int depth) {
if (depth == n) {
arr[n] = '\0';
printf("%s\n", arr);
return;
}

arr[depth] = '0';
generate_binary(depth + 1);

arr[depth] = '1';
generate_binary(depth + 1);
}

int main() {
scanf_s("%d", &n);
generate_binary(0);
return 0;
}

凭什么有 %d(十进制)、%o(八进制)、%x(十六进制),偏偏把最底层的二进制给排挤在外?是不是 C 语言的设计者偷懒了?

事实真相是:这还真是历史遗留问题,而且跟早期的计算机硬件息息相关。

咱们穿越回 1970 年代,看看 C 语言诞生时的环境,你就全明白了:

1. 二进制太长了,看着“辣眼睛”

你想想,在计算机底层,所有的东西确实都是二进制。但是二进制有一个致命缺点:太长了!

在调试 51 单片机或者给 STM32 写寄存器配置时,随便一个 32 位的寄存器,如果原样打印出来就是 11010010111101011010110001011111。在当年那种屏幕极小、甚至还在用打孔纸带和电传打字机的年代,满屏的 01 简直是程序员的噩梦,根本数不清位数。

2. 八进制和十六进制是完美的“压缩包”

C 语言的设计者(Dennis Ritchie 他们)非常聪明,他们发现:

  • 八进制(Octal):刚好逢 8 进 1,而 $2^3 = 8$。所以,1 位八进制数,完美等价于 3 位二进制数
  • 十六进制(Hexadecimal):逢 16 进 1,而 $2^4 = 16$。所以,1 位十六进制数,完美等价于 4 位二进制数

所以,与其加一个 %b 让屏幕塞满又长又臭的二进制串,不如直接强迫大家用 %o%x。原本 32 位的二进制串,用十六进制 %x 打印,只需要 8 个字符(比如 0xD2F5AC5F),又短又清晰。老一辈的极客们看十六进制和八进制,脑子里能自动把它“解压”成二进制。

3. 一个反直觉的真相:其实标准委员会也后悔了

你今天的吐槽,其实完全代表了现代程序员的心声。随着大家不用再死抠屏幕字符数,直接看二进制的需求越来越旺盛。

所以,这里有个振奋人心的“最新情报”:

在经过了长达几十年的固执之后,C 语言的官方标准委员会终于妥协了!在最新发布的 C23 标准中,官方终于正式把 %b(二进制格式化符)加进去了!

也就是说,在未来的 C 语言里,你真的可以用 printf("%b\n", i); 来打印二进制了。只不过,目前很多学校用的编译器(比如老版本的 VC++6.0、旧版 GCC、或者一些老单片机的 IDE)还不支持这个最新的 C23 标准,所以咱们现在为了求稳,还是得靠自己写右移和按位与(>>&)来提取。