2.8 整数常量、浮点常量和字符常量
常量写法不仅影响可读性,也会直接影响类型推导、溢出风险和可移植性。写对字面量,是“表达式正确求值”的第一步。
1. 整数常量
整数常量支持十进制、八进制、十六进制和(C23 起)二进制形式。前缀分别是无前缀、0、0x/0X、0b/0B。后缀 u、l、ll 及其组合用于指定无符号或更宽类型。
- 十进制常量包含
0~9的数字,且不以0开头:1,123,50000000000000; - 八进制常量包含
0~7的数字,以0开头:017,0777777777; - 十六进制常量包含
0~9中的数字和a~f中的字母(或者大写A~F,可以混用,但写代码时别混用),以0x开头:0x23,0x7E7,0xFf,0x0d000721。 - 二进制常量包含
0和1,以0b开头:0b101,0b11110000。
int a = 42; /* 十进制 */
int b = 052; /* 八进制,值为 42 */
int c = 0x2A; /* 十六进制 */
int d = 0b101010; /* 二进制,C23 */
unsigned long long e = 42ull;2
3
4
5
标准会根据常量形式和后缀选择“能容纳该值的首个类型”。如果希望类型明确可读,直接写后缀或使用 <stdint.h> 的宏常量;关于 <stdint.h>,见 19.1。
若你的编译器尚未切到 C23,二进制前缀 0b 可能不可用,这时可用十六进制常量做下位替代。
1.1 整数常量类型的推导规则
1.1.1 十进制常量
从小到大尝试:int → long int → long long int。
1.1.2 二进制、八进制和十六进制常量
从小到大尝试:int → unsigned int → long int → unsigned long int → long long int → unsigned long long int
1.1.3 如果更大?
可能会尝试寻找扩展类型(比如 __int128 等);如果超出所有类型的表示范围,可能报错。这类行为由编译器实现决定,不在此讨论。
1.1.4 为什么两种常量的规则不同?
人类写十进制数的时候,会更关注数的大小,自然认为他是有符号的(比如 2 大于 -1)。
如果十进制整数常量被按照 int → unsigned int 的次序推导(假设 int 最大值为 2147483647),那么以下代码就会发生严重错误:
if (3000000000 > -1) {
// 三十亿大于 -1,所以这块代码应该被执行
}2
3
然而,3000000000 被推导为 unsigned int,-1 会被隐式提升为无符号数 4294967295,导致左边小于右边。
程序员写下十六进制(如 0x80000000)或八进制(如 020000000000)时,通常用他们表示位的模式。如果强制推导成有符号类型,会扰乱位运算。
2. 浮点常量
浮点常量可写成十进制科学计数法,也可写成十六进制浮点形式。后缀 f/F 指定 float,l/L 指定 long double,无后缀默认 double。
double x = 3.1415926;
double y = 1.0e-3;
double z = 0x1.8p+2; /* 十六进制浮点,值为 6.0 */
float w = 6.02e23f;2
3
4
十六进制浮点在描述二进制可精确表示值时很有优势,适合写底层测试和数值边界用例。
3. 字符常量
普通字符常量使用单引号包围,例如 'A'。其类型通常是 int,值为执行字符集中对应字符编码。转义序列如 '\n'、'\0'、'\x41' 可表达不可见字符或指定码位。
#include <stdio.h>
int main(void) {
int a = 'A';
int b = '\n';
int c = '\x41';
printf("%d %d %d\n", a, b, c);
return 0;
}2
3
4
5
6
7
8
9
10
在执行字符集采用 ASCII、换行字符编码值为 10 的常见实现中,输出为:
65 10 65
其中 'A' 和 '\n' 的值由执行字符集决定,'\x41' 的值为十六进制 41,即十进制 65。 多字符常量(如 'AB')是实现定义行为,不建议用于可移植代码。若要表达文本,请优先使用字符串字面量。