C 语言中格式化打印
C 语言头文件 stdio.h 中包含 printf 系列函数,一直只了解其基本的用法,这次详细探究一番。
printf("str: %s, num: %d\n", s, n);
本文的目的在于了解这里 %s、 %d 等的语法。
语法
格式化项的语法如下,包含 6 个部分,方括号里面的是可忽略的,只有最后的 type 是必须的。
fmt ::= %[parameter][flags][width][.precision][length]type
parameter ::= <digit+>"$"
flags ::= "-" | "+" | " " | "0" | "'" | "#"
width ::= <digit+>
precision ::= <digit+> | "*"
length ::= "hh" | "h" | "l" | "ll" | "L" | "z" | "j" | "t"
type ::= "d" | "i" | "u" | "f" | "F" | "e" | "E" | "g" | "G" | "x" | "X" | "o" | "s" | "c" | "p" | "a" | "A" | "n"
parameter
这不是 C99 的标准,而是一个 POSIX 的扩展,它的形式很简单,下面是一个例子:
printf("%2$d %1$d \n", 1, 2); // 2 1
parameter 的形式为 n$,其中 n 是一个数字。%2$d 表示使用第二个参数来做格式化。如此格式的参数就不再是按顺序了。而且同一个参数可以被使用多次。但一旦其中一个格式化项使用了 parameter 其他的也都要使用。
flags
| Character | Description |
|---|---|
| - | 左对齐(默认是右对齐,需要配合 width 使用) |
| + | 在正数前面加上 + 号,默认正数是不加符号的。 |
| 空格 | 默认情况下,负数前面有一个 - 号,正数前面什么都没有。使用 % d 则会在正数前面加一个空格。 |
| 0 | 在整数前面使用 0 填充。 |
| # (hash) | Alternate form: For g and G types, trailing zeros are not removed. For f, F, e, E, g, G types, the output always contains a decimal point. For o, x, X types, the text 0, 0x, 0X, respectively, is prepended to non-zero numbers. |
width
一个数字,指出当前格式化项的最小宽度。
precision
如果格式化的是浮点数,precision 指出小数点的精度。如果格式化的是字符串,指出格式化的长度。
printf("%.2f %.6f", M_PI, M_PI); // 3.14 3.141593
精度也可以动态地指出:
printf("%.*f", 4, M_PI); // 3.1415
使用 .* 表示精度是动态获取的,这里第一个参数 4 就指出了精度,指出精度的哪一位需要是 int 型的。
const char *s = "hello world!";
printf("%.*s", 5, s); // hello
length
printf 系列函数是可变参函数,大致实现原理是这样的,在调用的时候多个参数被压入栈中,在格式化的时候,根据格式化字符串中指定的参数类型,去栈中对应偏移量取不同大小的数据,并进行格式化。传给 printf 系列函数的整数有很多中,比如 short、int、long 等等,因此需要有一种方法来说明待格式化的参数的字节长度。
| Character | Description |
|---|---|
| hh | char 型整数 |
| h | short 型整数 |
| l | long 型整数 |
| ll | long long 型整数 |
| L | long double |
| z | size_t |
| j | intmax_t |
| t | ptrdiff_t |
为了跨平台,你可能使用了 stdint.h 中定义的形如 int64_t 这样的类型,在 32 位和 64 位的系统上,格式化的 length 是不同的。ISO C99 标准引入了 inttypes.h 头文件,这里面定义了一系列的宏,它使用了条件编译,平台不同其内容不同,使用这些宏可以保证 printf 在格式化整数时的跨平台正确性。用法为: printf("%" PRId64 "\n", t);,可使用的宏如下:
| Macro | Description |
|---|---|
| PRId32 | Typically equivalent to d |
| PRId64 | Typically equivalent to lld (32-bit platforms) or ld (64-bit platforms) |
| PRIi32 | Typically equivalent to i |
| PRIi64 | Typically equivalent to lli (32-bit platforms) or li (64-bit platforms) |
| PRIu32 | Typically equivalent to u |
| PRIu64 | Typically equivalent to llu (32-bit platforms) or lu (64-bit platforms) |
| PRIx32 | Typically equivalent to x |
| PRIx64 | Typically equivalent to llx (32-bit platforms) or lx (64-bit platforms) |
type
type 支持待格式化的参数的类型,下表给出了说明:
| Character | Description |
|---|---|
| d, i | 十进制有符号整数 |
| o | 八进制的无符号整数 |
| x,X | 十六进制显示的无符号整数 |
| u | 十进制无符号整数 |
| f,F | 浮点数。(对浮点数的格式化并不区分 float 和 double,因为在可变参数中,float 都被提升为 double 了)f 和 F 的区别是只存在于格式化 nan 和 inf 的时候。f 和 F 的结果分别是 (inf, infinity and nan) 和 (INF, INFINITY and NAN) |
| e,E | 浮点数的指数表示 |
| a,A | 十六进制浮点数 |
| g,G | 自动选择浮点数的表示方式 |
| s | 字符串 |
| c | char |
| p | 指针 |
| n | 不打印任何内容,而是把当前已经格式的字符串的长度写入到对应的参数(int*)中。 |
下面是表中给出了 length 和 type 配合使用的时候表示的类型:
Modifier d, i o, u, x, X n
hh signed char unsigned char signed char *
h short unsigned short short *
l (ell) long unsigned long long *
ll (ell ell) long long unsigned long long long long *
j intmax_t uintmax_t intmax_t *
t ptrdiff_t (see note) ptrdiff_t *
z (see note) size_t (see note)
Modifier a, A, e, E, f, F, g, G
l (ell) double (ignored, same behavior as without it)
L long double