那会儿我们写 C/C++。
配置常常就写在源码里。
数字也就跟着一起写进去。
一长串。
一坨 0。
你当时不会把它当风险。
毕竟它“只是个常量”。
直到有一天。
你少写了一个 0。
线上啪一下。
服务开始挨打。
你盯着那一行数字。
第一次认真怀疑:人是不是不该靠肉眼数位数。
当年:大数字就是一坨 0
当时写超时、容量、阈值,基本靠手敲。 配置也常常就写在源码里。 于是这些数字就跟着一起进仓库。
这些写在代码里的“现成数字”,C++ 里叫字面量。
就是你直接写出来的 60000、1000000 这种。
constexpr int timeout_ms = 60000;
constexpr int max_items = 1000000;
这里的 constexpr 你可以先当成“编译期常量”。
意思是这个数字在编译时就确定,不会在运行时变来变去。
顺手再补一句新手容易卡的。 字面量不是“字符串”。 它就是数字本尊,编译器会按它的规则去解析。
短的时候没啥。 问题是它会越来越长。 一长,你就开始干一件很不靠谱的事:数 0。
constexpr int page_size = 1048576; // 1024 * 1024
你会先数位数。 再数一次。 然后心里还没底。
我一个老同事当年吐槽得很准。 “人不是用来数 0 的。”
坑:少一个 0,逻辑就完全变了
我见过最离谱的一次,不是算法写错。 也不是锁写错。 是限流阈值少了一个 0。
那是个很小的项目。 就一个接口,一个简单的限流。 上线那天风平浪静,第二天活动一开,机器直接被打满。
constexpr int max_requests_per_minute = 6000; // 本来想写 60000
if (requests_last_minute > max_requests_per_minute) {
reject();
}
这段代码“能编译”。
也“能跑”。
但阈值从 60000 变成 6000 的那一刻,你的保护就变成了摆设。
更阴险的是,它不会立刻报错。 它只会在你最不想出事的时候,让你出事。
当时我们怎么爬坑
最常见的自救方式,是把数字拆开。 让你读到的是“含义”,不是“位数”。 这样你就不用再数 0。
constexpr int max_requests_per_minute = 60 * 1000; // 60 秒 * 1000 次
这比 60000 好。
因为你脑子里记的是“60 秒 * 1000 次”,而不是“到底几个 0”。
但它也不是万能。
有些数字就是拆不开,比如 1,048,576 这种。
它背后是 1024 * 1024,但你也不可能每次都写乘法。
在 C++ 里,数字还会长出“尾巴”
到这里,新手还有一个常见困惑: 为什么有的数字后面会多几个字母。
int n1 = 1000000; // 普通整数
unsigned long long n2 = 1000000ULL; // ULL = unsigned long long
这些字母叫“后缀”。 它在告诉编译器:这个数字用什么类型装。
你不需要现在就背全。 你只要记住:数字不光是“值”,它还有“类型”。
当数字变大,或者你不想要符号位这些麻烦事时,这个“尾巴”就会救命。 它至少能让你少掉一类“装不下还不报错”的烂账。
这个点子不是 C++ 独创
“别让人数 0”这件事,别的语言很早就做了。
很多语言选的是下划线 _。
看起来像分组线,也不容易跟别的符号混。
比如 Python 可以这样写。
它用 _ 把位数切开,让你用“看”的。
limit = 50_000_000
这不是格式化输出。 这是你写在源码里的原始数字。
比如 Java 也可以这样写。
编译器同样把 _ 当成“忽略符号”。
int limit = 50_000_000;
你读起来会更像“50 百万”。 而不是“这到底几个 0”。
你会发现它们的直觉很一致。 分隔符不改变数值,只改变人的阅读体验。
这种写法不是最近两年才冒出来的。
像 Java 很早就支持在数字里插 _(后来很多语言也跟上了)。
Python、Rust、Go、C# 这些语言里,也基本是同一个思路。
它们共同点就一个。 让你在“读数字”这件事上,尽量少靠数。
C++14:给数字加“分隔符”
C++14 做了个很小的改动。
它允许你在数字中间插单引号 ' 当分隔符。
编译器会忽略它,把整个东西当成同一个数字。
你可能会问,为啥不是下划线 _。
因为 C++11 以后有了用户自定义字面量(你可以自己做个 10_ms 这种)。
在词法层面,1_000 会更像“数字 1 + 后缀 _000”。
所以 _ 这条路在 C++ 里走不通。
int x = 1_000; // 这在 C++ 里会被当成用户自定义字面量,从而报错
所以最后选了 '。
你可以把它理解成一个妥协。
别的语言用 _ 很顺。
C++ 这边为了不把语法弄打架,只能换个符号。
constexpr int timeout_ms = 60'000;
constexpr int max_items = 1'000'000;
constexpr int page_size = 1'048'576;
注意这个 ' 不是字符串里的引号。
它只是在源码里让你更好读。
而且这是纯语法糖,运行时不会多付出一分钱。
它到底能插在哪
规则你可以先记一个粗暴版本。 只要在“数字的中间”,你想怎么分都行。
int a = 1'234'567;
int b = 12'34'567; // 也行,只是人看着别扭
分隔符和“后缀”也能一起用。 写大整数常量时很常见。
unsigned long long big = 1'000'000ULL;
ULL 的意思是 unsigned long long。
你可以把它理解成:我想要一个“更大的整数桶”。
它不仅能用在十进制。 十六进制、二进制也能用。
unsigned int mask = 0xFF00'00FF;
unsigned int bits = 0b1010'0110;
0x 表示十六进制。
每一位十六进制数字,刚好是 4 个二进制位。
0b 表示二进制。
这个写法也是 C++14 进来的,所以你会经常在同一代代码里见到它。
浮点数也行。 写表格常量时,眼睛会轻松很多。
double pi = 3.141'592'6;
但它不是随便哪都能塞。
别放在开头或结尾,也别连续写两个。
小数点两边别紧贴着塞,3.'141 这种不行。
0x、0b 这种前缀后面,也别立刻来一个分隔符。
你别被这些规则吓到。 它们的核心其实就一句:别让编译器误会你在写什么。
一个实用的小习惯
分隔符怎么分组,标准不管。 你按“你想检查的单位”来分就行。
写十进制金额、字节数,一般按三位一组。 你可以把它当成“手动加了千分位”。
int money_cents = 12'345'678;
写十六进制掩码,一般按 4 位(一个十六进制位)或者按 8 位(一个字节)来切。 因为你常常就是按字节去理解它的。
unsigned int color = 0xFF00'00FF; // 4 个字节,一眼能看出每个通道
横向对比:你到底该用哪种写法
如果你只是想让数字“更不容易写错”。 数字分隔符是最快的。 改一行就见效。
如果你想让数字“带着含义”。 拆成乘法表达式更好读。 代价是代码会更啰嗦一点。
如果你想把坑彻底封死。 那就让单位变成类型。 代价是你要引入库,或者写一点封装。
把它放进一个“真的会出事”的小项目
比如你写了个小上传服务。 你想做个很朴素的限制:每个用户每天最多传 50,000,000 字节。
你把数字写进代码。 过了两周风平浪静。 某天突然开始有人投诉:“怎么传不了”。
constexpr int daily_limit_bytes = 50000000;
if (uploaded_today + chunk_bytes > daily_limit_bytes) {
reject();
}
这里有两个坑。 第一,你很难一眼确认 0 的个数。 第二,你脑子里想的“50MB”到底是哪种 MB。
很多人口头说 MB,其实想的是 MiB,也就是 1024 * 1024 字节那种。
(MiB 读作“米比”,就是为了把 1024 这一套和 1000 这一套分开。)
用分隔符以后,你至少不会在第一步就翻车。 你不用数 0,你用“看”。 这种改动很便宜,但很值。
constexpr int daily_limit_bytes = 50'000'000;
顺手再提醒一句。
如果你真的在意“单位别搞错”,更好的路子其实是让单位变成类型。
比如用一个单位库,或者自己封装个 Bytes。
但那是下一层的事。 数字分隔符属于“今天就能做、立刻少踩坑”的那一层。
一句话的结论
让人不用数 0,就是在减少 bug。
最后留个亮点
我后来把这种东西当成“代码的排版”。
你不是在讨好编译器。
你是在救未来的自己。