很久以前。
写 C 的年代。
你想表达“某一位是 1”。
文档上画的是一格一格的二进制。
代码里却只能写十六进制。
于是大家开始背。
0x80 是第 7 位。
0x40 是第 6 位。
0x20 是第 5 位。
背熟了。
你就像多了超能力。
背错了。
你就会在凌晨被叫醒。
当年:没有 0b,只有十六进制
那时候写位掩码,最常见的就是十六进制。
先别怕这些前缀。
0x 的意思就是。
“这个数按十六进制读”。
这套写法很早就有了。 很多老代码也就一直这么写。
因为它短。 也因为它“看起来很底层”。
还有个更现实的原因。 十六进制跟二进制对得特别齐。
一位十六进制。 刚好是 4 位二进制。
constexpr unsigned kRead = 0x01;
constexpr unsigned kWrite = 0x02;
constexpr unsigned kExec = 0x04;
你不用懂什么硬件。 你只要记住这个换算关系就够了。
constexpr unsigned x = 0xA;
0xA 就是十进制的 10。
也等于二进制的 0b1010。
这段你看得懂。
但它靠的不是眼睛。 靠的是你脑子里那本“0x 到二进制的对照表”。
坑:你写的是位,眼里看到的却是数
当位数一多,十六进制就开始像谜语。
因为你真正关心的是。 “哪一位是 1”。
但十六进制给你的却是。 “每 4 位打包后的结果”。
比如你想表达。 “第 7 位和第 3 位为 1”。
constexpr unsigned m = 0x88;
0x88 为啥对应那两位?
因为 8 这个十六进制数字。
等于二进制的 1000。
两个 8 连起来。
就是 1000 1000。
你得把 0x88 翻译成 10001000。
翻译这件事,本身就是 bug 温床。
大家怎么爬出来:用移位,但还是要数
后来很多人会改用移位。
至少不用背 0x20、0x08 这些了。
constexpr unsigned kCompressed = 1u << 7;
constexpr unsigned kEncrypted = 1u << 3;
1u << 7 的意思就是。
把二进制的 1 往左挪 7 格。
这里的 u 也别怕。
它就是在说“这是无符号数”。
位运算这类东西。 用无符号更省心。
这比十六进制诚实。 但你仍然在“数位”。
而且一旦合并成一个值,你又回到了翻译题。
unsigned flags = kCompressed | kEncrypted;
看到结果,你还是得在脑子里跑一遍二进制。
我的小项目:线上啪一下的那种
我有次写了个很小的协议解析。
头里有个 flags。
flags 这词听起来玄。
其实就是“把很多开关塞进一个整数里”。
按文档说,第 5 位表示“需要 ACK”。
ACK 也不神秘。 就是对方希望你“回个确认”。
我当时写的是十六进制。
constexpr unsigned kNeedAck = 0x20;
后来改需求。
我手一滑,把它改成了 0x02。
constexpr unsigned kNeedAck = 0x02;
代码还能跑。 单测也可能过。
但线上就会出现一种很烦的现象。 对方明明要求 ACK,你却像没看见一样。
if (flags & kNeedAck) {
SendAck();
}
这里的 & 是按位与。
你可以把它理解成。
“这一位是不是同时为 1”。
写在 if (...) 里。
意思就是。
“只要结果不是 0,就算真”。
0x20 和 0x02。
长得太像。
你写错的那一刻。 肉眼其实很难发现。
C++14:终于能直接写二进制
C++14 加了二进制字面量。
也就是 0b 这个前缀。
“字面量”你可以先理解成。 你在代码里直接写出来的那个常量。
顺便说句历史。
不少语言早就支持 0b 了。
比如 Python、Java。
C/C++ 这边一直拖着。 后来 C++14 才把它写进标准。
constexpr unsigned kNeedAck = 0b00100000;
你不需要翻译。 你看到的,就是文档里的那一格一格。
如果你嫌它太长。 C++14 还允许你用单引号分组。
constexpr unsigned kNeedAck = 0b0010'0000;
分组不影响数值。 只影响你看得累不累。
再写一个“第 7 位和第 3 位”。 也不用脑补了。
constexpr unsigned m = 0b10001000;
你甚至可以顺手写个解析。 把“某几位”抽出来。
unsigned bit3 = (m >> 3) & 0b1;
先右移 3 位。
再用 & 0b1 留下最后一位。
顺手再补一个老坑:前导 0
有些新同学会喜欢在前面补 0。 看起来整齐。
但在 C/C++ 里。
0 开头的整数,默认是八进制。
int x = 010;
它不是十。 它是八。
所以写位的时候。
0b0010'0000 这种反而更稳。
一句话的结论
0b 没给你新能力。
它只是把“人脑翻译”这件事,从流程里删掉了。
十六进制。 短。 但你要翻译。
移位。 不需要背。 但你要数。
二进制字面量。 长一点。 但它更像你真正想表达的东西。
最后留个亮点
我现在判断一段代码危险不危险,有个土标准。
只要它需要你在脑子里做翻译。 它就迟早会逼你交学费。
二进制字面量的价值,不在于更底层。 在于更像人话。