C++14 之二进制字面量与数字分隔符
C++14 之二进制字面量与数字分隔符
系列文章: C++ 新特性系列 · C++14 篇
阅读时间: 约 10 分钟
适用标准: C++14 及以上
一、引言:数字也能"看不清"
在 C++14 之前,写一个十六进制的大数字,你可能需要这样:
unsigned int mask = 0xFF00FF00;
unsigned int flags = 0x0A0B0C0D;
long long bytes = 1073741824;
问题一目了然:
0xFF00FF00—— 位边界在哪?你得拿鼠标划半天才能数清楚这是 32 位还是 28 位。0x0A0B0C0D—— 每个字节的边界在哪?全挤在一起,肉眼分不清。1073741824—— 这是十亿?十亿零多少?没有分隔符的大数字就是一堵数字墙。
更别说二进制了。在 C++14 之前,你根本没有原生的二进制字面量。想表示一个二进制数?对不起,要么手算转换,要么用宏模拟:
// C++11 时代的痛苦写法
#define BIT(n) (1UL << (n))
#define B8(hi, lo) ((hi << 4) | lo)
unsigned int flags = BIT(3) | BIT(7) | BIT(15); // 还算能看
unsigned int mask = B8(1, 0); // 10000000... 这是在干啥?
C++14 给了我们两个简单却实用的解药:二进制字面量(Binary Literals) 和 数字分隔符(Digit Separators)。
二、二进制字面量:终于能直接写 0 和 1 了
语法
C++14 引入了 0b(或 0B)前缀,让整数字面量可以直接用二进制表示:
// 0b 前缀 + 二进制数字(0 和 1)
unsigned int value = 0b11110000;
// 0B 也可以(大小写不敏感)
unsigned int value2 = 0B11110000; // 和上面等价
就这么简单。0b11110000 就是十进制的 240,十六进制的 0xF0。
与其他进制的对比
| 进制 | 写法 | 值 | 可读性 |
|---|---|---|---|
| 二进制 | 0b11110000 |
240 | ⭐⭐⭐⭐⭐ 位模式一目了然 |
| 十六进制 | 0xF0 |
240 | ⭐⭐⭐⭐ 需要脑内转换位 |
| 八进制 | 0360 |
240 | ⭐⭐ 几乎没人用 |
| 十进制 | 240 |
240 | ⭐ 完全看不出位模式 |
使用场景
场景一:位掩码(Bitmask)
操作硬件寄存器或标志位时,二进制字面量是最自然的表达方式:
// 定义 LED 控制寄存器的各个位
constexpr unsigned char LED_POWER = 0b00000001; // bit 0: 电源
constexpr unsigned char LED_MODE = 0b00000010; // bit 1: 模式
constexpr unsigned char LED_BRIGHT = 0b00000100; // bit 2: 亮度
constexpr unsigned char LED_COLOR = 0b00001000; // bit 3: 颜色
constexpr unsigned char LED_BLINK = 0b00010000; // bit 4: 闪烁
// 设置寄存器
unsigned char reg = 0;
reg |= LED_POWER | LED_MODE | LED_BRIGHT; // 0b00000111
// 清除某一位
reg &= ~LED_MODE; // 0b00000101
// 检查某一位
if (reg & LED_BRIGHT) {
// 亮度已开启
}
比起 0x01、0x02、0x04……二进制字面量让每个位的含义显而易见。代码评审时,别人一眼就能看出每个标志占了哪些位。
场景二:位运算教学与调试
// 展示位操作的效果
unsigned char a = 0b10101010;
unsigned char b = 0b11001100;
unsigned char and_result = a & b; // 0b10001000
unsigned char or_result = a | b; // 0b11101110
unsigned char xor_result = a ^ b; // 0b01100110
unsigned char not_result = ~a; // 0b01010101(unsigned char 范围内)
场景三:硬件编程
// 模拟一个 8 位状态机的状态编码
constexpr unsigned char STATE_IDLE = 0b00000000;
constexpr unsigned char STATE_RUNNING = 0b00000001;
constexpr unsigned char STATE_PAUSED = 0b00000010;
constexpr unsigned char STATE_ERROR = 0b10000000;
unsigned char state = STATE_RUNNING;
// 转换到暂停状态
state = (state & ~0b00000011) | STATE_PAUSED;
// 检查是否处于错误状态
bool has_error = (state & STATE_ERROR) != 0;
三、数字分隔符:给大数字加上"千分位"
语法
C++14 允许在数字字面量中使用单引号 ' 作为分隔符,它纯粹是视觉辅助,编译器会直接忽略:
// 十进制:每三位一分,像金融数字一样
long long population = 7'800'000'000; // 78亿
long long distance = 149'597'870'700; // 地球到太阳的距离(米)
// 十六进制:按字节分隔
unsigned int addr = 0x1FFF'FFFC;
// 二进制:按位分组(后面会详细讲)
unsigned int mask = 0b1111'0000;
单引号 ' 会被编译器完全忽略,它不影响数值,只影响你的眼睛。
使用场景
场景一:大金额计算
// 财务系统中的金额(单位:分)
long long company_valuation = 1'000'000'000'000; // 1万亿分 = 100亿
long long annual_revenue = 36'500'000'000; // 365亿分 = 36.5亿
// 对比
long long without_separator = 1000000000000; // 数几个零?请开始你的表演
场景二:十六进制内存地址
// 内存地址按字节分隔
uintptr_t stack_base = 0x0000'7FFE'FBFF'0000;
uintptr_t heap_start = 0x0000'024A'1000'0000;
// 设备寄存器地址
constexpr uint32_t GPIO_BASE = 0x4002'0000;
constexpr uint32_t GPIO_BSRR = 0x4002'0018;
场景三:时间戳与常量
// 毫秒级时间戳
constexpr long long MILLISECONDS_PER_DAY = 86'400'000;
// 字节大小常量
constexpr size_t MB = 1'048'576; // 1 MB
constexpr size_t GB = 1'073'741'824; // 1 GB
constexpr size_t TB = 1'099'511'627'776; // 1 TB
// 对比:没有分隔符时
constexpr size_t TB_no_sep = 1099511627776; // 你确定这是 TB 不是 GB?
四、组合使用:二进制字面量 + 数字分隔符
两个特性单独用已经很好了,组合起来更是王炸。
按半字节(nibble)分组
二进制中,每 4 位对应一个十六进制数字。用分隔符按 4 位分组,可以直接对照十六进制值:
// 每 4 位一分隔,对应十六进制的每一"格"
unsigned char reg = 0b1111'0000; // 0xF0
unsigned int mask = 0b1010'1100'0101'1010; // 0xAC5A
// 对照表
// 0b1111'0000 = 0xF0
// 0b1010'1100 = 0xAC
// 0b0101'1010 = 0x5A
按字节分组
对于 32 位或 64 位的值,按 8 位(一个字节)分隔更直观:
// 按字节分隔,每个分隔符对应一个字节边界
constexpr uint32_t MAGIC = 0b0100'0011'0100'1011'0100'0001'0101'0000;
// ^--- C --^--- K --^--- A --^--- P --
// 实际上这就是 ASCII 码 "CKAP"
实战示例:网络协议标志位
// TCP/IP 协议中的一些标志位
constexpr uint16_t TCP_FIN = 0b0000'0000'0000'0001;
constexpr uint16_t TCP_SYN = 0b0000'0000'0000'0010;
constexpr uint16_t TCP_RST = 0b0000'0000'0000'0100;
constexpr uint16_t TCP_PSH = 0b0000'0000'0000'1000;
constexpr uint16_t TCP_ACK = 0b0000'0000'0001'0000;
constexpr uint16_t TCP_URG = 0b0000'0000'0010'0000;
// 构建一个 SYN+ACK 包的标志位
uint16_t flags = TCP_SYN | TCP_ACK;
// flags = 0b0000'0000'0001'0010
// 检查特定标志
if (flags & TCP_ACK) {
// 这是一个确认包
}
实战示例:位图操作
// 一个 8x8 的位图,用 8 个 unsigned char 表示
constexpr unsigned char bitmap[] = {
0b1111'1111, // 第 0 行:████████
0b1000'0001, // 第 1 行:█......█
0b1011'1101, // 第 2 行:█.████.█
0b1011'1101, // 第 3 行:█.████.█
0b1011'1101, // 第 4 行:█.████.█
0b1000'0001, // 第 5 行:█......█
0b1111'1111, // 第 6 行:████████
0b0000'0000, // 第 7 行:........
};
有了数字分隔符,每一行的位模式一眼就能看出是 1 还是 0,不需要在脑子里默默数位。
五、注意事项与限制
1. 分隔符不能放在开头或结尾
// ❌ 编译错误
int a = '123'456; // 分隔符不能在开头
int b = 123'456'; // ✅ 正确
int c = 123456'; // 分隔符不能在结尾
2. 不能连续使用分隔符
// ❌ 编译错误:两个分隔符之间必须有数字
int a = 123''456; // 连续分隔符不合法
int b = 123'4'56; // ✅ 正确,只要中间有数字就行
💡 提示: 这意味着
'0'这种写法没问题——中间有数字0。
3. 可以和任意进制前缀配合
数字分隔符不挑进制,所有进制都支持:
int dec = 1'000'000; // 十进制
int hex = 0xFF'FF'FF'FF; // 十六进制
int oct = 0'755; // 八进制
int bin = 0b1111'0000'1010'0101; // 二进制
4. 分隔符的位置很自由
单引号可以放在数字之间的任何位置,不要求三位一组或四位一组:
// 这些都是合法的
int a = 1'234'567; // 每三位(传统千分位风格)
int b = 1234'567; // 按需要分
int c = 1'2'3'4'5'6'7; // 每一位都分隔(合法,但有点极端)
// 推荐:根据数字的"语义"来分隔
int reg = 0b1111'0000; // 按半字节分
int addr = 0xDEAD'BEEF; // 按双字节分(更有意义)
5. 不影响类型推导
分隔符是纯粹的语法糖,不影响字面量的类型:
auto a = 1'000; // int
auto b = 1'000'000'000LL; // long long(LL 后缀照常生效)
auto c = 0b1111'0000U; // unsigned int(U 后缀照常生效)
6. 预处理阶段的行为
数字分隔符在预处理的数字扫描阶段就已经被识别。这意味着宏中也可以使用:
#define REG_CTRL 0b1111'0000
#define MASK_32 0xFFFF'FFFF
六、编译器支持
二进制字面量和数字分隔符都是 C++14 标准的一部分,主流编译器支持情况:
| 编译器 | 最低版本 | 备注 |
|---|---|---|
| GCC | 4.9+ | GCC 4.9 首次支持二进制字面量,分隔符在 5.0+ |
| Clang | 3.4+ | 完整支持 |
| MSVC | 2015 (19.0)+ | VS 2015 及以上 |
编译命令示例:
# GCC / Clang
g++ -std=c++14 -o main main.cpp
clang++ -std=c++14 -o main main.cpp
# MSVC (Developer Command Prompt)
cl /std:c++14 main.cpp
⚠️ 如果你的 GCC 版本低于 5.0,数字分隔符可能不被支持。建议升级到 GCC 5+ 以获得完整支持。
总结
| 特性 | 语法 | 核心价值 | 典型场景 |
|---|---|---|---|
| 二进制字面量 | 0b / 0B 前缀 |
直接表达位模式 | 位掩码、硬件编程、标志位 |
| 数字分隔符 | 单引号 ' |
提升大数字可读性 | 金额、地址、字节大小常量 |
这两个特性虽然简单,但带来的好处是实实在在的:
- ✅ 可读性提升:位模式一目了然,大数字不再需要数零
- ✅ 零运行时开销:纯编译期语法糖,不影响性能
- ✅ 广泛兼容:C++14 标准,所有现代编译器都支持
- ✅ 维护友好:代码评审时能快速理解位操作的意图
从 C++14 开始,再也不用手算二进制转换,再也不用在十六进制地址里默默数位数了。
下一篇预告
C++14 之变量模板(Variable Templates)
下一篇文章将介绍 C++14 引入的变量模板——让变量也能像函数和类一样使用模板参数。当你需要为不同数学常量定义统一接口时,变量模板将大显身手。告别
PI_FLOAT、PI_DOUBLE、PI_LONG_DOUBLE的手动定义时代!敬请关注 🚀
💬 觉得有帮助? 点赞 👍 + 收藏 ⭐,下次找得到!有问题欢迎评论区交流~
作者:林夕07 | 系列文章持续更新中…
更多推荐
所有评论(0)