
写 C 的人,都见过 1、077、0xFF 这类“看起来像数字”的东西。它们只是“字面量”——编译期的常量,但背后有一套严格的记法规则和类型推断机制。理解这些细节,能帮你躲过许多肉眼难识的坑,列如“前导 0 变八进制”“位运算越界”“printf 打印错位”等。
一、整型常量的“长相”:四种进制前缀
十进制:123(无前缀)
八进制:077(前缀 0)
十六进制:0xFF 或 0XFF(前缀 0x/0X)
二进制:0b1010 或 0B1010(C23 标准引入;GNU/Clang 早有扩展)
- 前导 0 陷阱:08 在标准 C 中是非法的(由于八进制没有 8),许多线上事故就栽在“填充位数时加了个 0”。
- 数字分隔符(C23):可用单引号分隔数字提高可读性:1'000'000、0xFF'FF、0b1010'1100。编译器未启用 C23 时不要用。
- 习惯上,位掩码更推荐用十六进制或二进制表明:0x80000000u、0b1u << 31,语义一眼能懂。
二、类型后缀:U / L / LL(可以组合)
- u 或 U:无符号(unsigned)
- l 或 L:long
- ll 或 LL:long long(C99 引入)
组合写法(顺序不拘,推荐大写更清晰):
- 123U、123L、123UL、123LU、123LL、123ULL、123LLU……
- 不要用小写 l:1l 容易和 11 混淆,统一用 L/LL。
后缀不是“装饰品”,它直接决定常量的类型,从而影响整数提升、重载决议(在 C++)、位移/比较/打印等行为。
三、编译器如何推断整型常量的类型
C 有两套推断路径,取能容纳该值的第一个类型。
1)十进制常量(有符号优先)
int → long int → long long int
也就是不思考无符号,除非你显式写了 U。
2)八/十六/二进制常量(有符号与无符号交替)
int → unsigned int → long int → unsigned long int → long long int → unsigned long long int
这解释了为什么 0x80000000 往往是 unsigned int,而 2147483648(十进制)则会走到 long/long long。
不同平台的数据模型影响结论
- LP64(大多数 64 位 Linux/Unix/macOS):int32 / long64 / long long64
- LLP64(Windows 64 位):int32 / long32 / long long64
同一个字面量,在不同数据模型上得到的具体类型可能不同,但规则不变:能装下就选最“短”的那个(遵循上面的顺序)。
四、常见坑 & 典型案例
1)前导 0 变八进制
int a = 010; // 8
int b = 8; // 8
// a == b,但 010 的语义完全不同。写固定宽度时不要用前导 0。
2)位移越界与符号扩展
// 想要得到最高位的掩码
unsigned m1 = 1u << 31; // 正确:结果是 0x80000000(在 32 位 unsigned 上)
int m2 = 1 << 31; // 未定义/实现相关:有符号位移到符号位,行为不可靠
结论:凡是位运算的“高位”构造,先加 u 再位移。
3)十六进制的“无符号”倾向
int a = 2147483648; // 十进制,类型需能容纳:一般是 long/long long
unsigned int b = 0x80000000; // 十六进制可优先匹配 unsigned int
这俩常量长得像,但默认类型不同,参与运算时的整数提升也会不同。
4)printf 与后缀/类型不匹配
printf("%d
", 0x80000000); // 在许多平台是未定义行为
printf("%u
", 0x80000000u); // 正确
printf("%ld
", 2147483648L); // long 用 %ld
printf("%lld
", 1LL << 60); // long long 用 %lld
口诀:int→%d、unsigned→%u、long→%ld/%lu、long long→%lld/%llu。
别偷懒:先加后缀,再配格式。
5)比较陷阱:有符号 vs 无符号
int x = -1;
unsigned y = 1U;
printf("%d
", x < y); // 0?1?——实现会把 x 转成 unsigned 比较,结果是 0
要点:只要表达式里有无符号类型参与,提升/转换就会发生。对边界值的比较,统一后缀与类型,别混用。
五、最佳实践清单(拿去即用)
- 位相关常量一律写无符号:1u << 31、0x80000000u。
- 边界/容量常量明确后缀:SIZE_MAX、UINT32_MAX,自写用 U/UL/ULL。
- 需要固定宽度?用 <stdint.h> 的宏:
- INT32_C(100)、UINT64_C(1) << 40(这些宏会自动加合适的后缀,最可移植)。
- 表达位掩码优先用十六进制或二进制,并分组:0xFF00'FF00u(C23)或 0b1111'0000u。
- 禁止前导 0 表明十进制。需要填充位数交给格式化/字符串,不要写在字面量上。
- printf/scanf 的格式串和类型成对出现;宁可啰嗦,不要赌编译器。
- 审阅代码时看到小写 l 后缀,一律改成 L/LL。
- 跨平台库里,面向接口写字面量:用宏(UINT32_C 等)或 static_assert 校验 sizeof 假设。
六、速查:不同进制 + 后缀示例
// 十进制(有符号优先)
123 // int
2147483648L // long
9223372036854775807LL // long long
// 八/十六/二进制(交替匹配无符号)
077u // unsigned int(八进制)
0x80000000u // unsigned int(十六进制)
0b1010'1100U // C23 二进制 + 分隔符 + 无符号
// 固定宽度(最推荐)
uint32_t mask = UINT32_C(0x80000000);
int64_t big = INT64_C(9'223'372'036'854'775'807); // C23 分隔符
七、三道“读代码辨类型”小练习(含答案)
Q1:
printf("%zu
", sizeof(0xFFFFFFFF));
它的类型是什么?为什么 sizeof 的结果在不同平台可能不同?
Q2:
uint32_t m = 1 << 31;
这行有风险吗?应该怎么改?
Q3:
long long a = 0x7FFFFFFF;
long long b = 2147483648;
printf("%lld %lld
", a, b);
a 与 b 的字面量类型分别是什么?赋值有没有隐式转换?
参考答案
- A1:0xFFFFFFFF 走“八/十六/二进制路径”,会在 unsigned int/unsigned long/… 中择一。LP64 上可能是 unsigned int(4 字节),LLP64 上也可能是 unsigned long(4 字节),甚至 unsigned long long(8 字节),因此 sizeof 结果非固定。
- A2:有风险。1 为 int,1 << 31 可能触及符号位导致未定义/实现相关。应写 1u << 31 或 UINT32_C(1) << 31。
- A3:0x7FFFFFFF 按规则一般为 int(能装下即选),再提升赋给 long long;2147483648(十进制)需要更大类型,一般直接是 long 或 long long。两者赋值都发生了隐式转换,但前者是从 int 转,后者可能无需再扩大。
结语
整型常量不是“随意写个数字”那么简单:进制前缀决定它的“长相”,后缀决定它的“身份”,而推断规则决定它在不同平台的落点。把这三件事讲清楚、写规范,你的位运算、边界判断、IO 打印和跨平台行为都会更稳。下一次敲下 0x80000000u 或 INT64_C(1) << 40,你就已经避开了半打常见 bug。





![[C++探索之旅] 第一部分第十一课:小练习,猜单词 - 鹿快](https://img.lukuai.com/blogimg/20251015/da217e2245754101b3d2ef80869e9de2.jpg)










暂无评论内容