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) {
    // 亮度已开启
}

比起 0x010x020x04……二进制字面量让每个位的含义显而易见。代码评审时,别人一眼就能看出每个标志占了哪些位。

场景二:位运算教学与调试
// 展示位操作的效果
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_FLOATPI_DOUBLEPI_LONG_DOUBLE 的手动定义时代!

敬请关注 🚀


💬 觉得有帮助? 点赞 👍 + 收藏 ⭐,下次找得到!有问题欢迎评论区交流~

作者:林夕07 | 系列文章持续更新中…

更多推荐