Menu

C 语言格式说明符:printf 和 scanf 完整参考

C 语言所有格式说明符一页看全:%d、%s、%c、%f、%p 等各自的含义,长度修饰符 l、ll、h 和 z,宽度、精度和标志,以及哪些在 printf 和 scanf 中含义不同。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

格式说明符告诉 printf 如何把值变成文本,告诉 scanf 如何把文本变成值。这是 C 语言中编译器默认无法帮你的一个地方,因为函数看不到参数的类型,所以这一页就是供你对照检查的表格。

一个格式说明的一般形式是:

%[flags][width][.precision][length]conversion

只有 % 和转换字母是必需的。%-8.2lf 用到了全部五个部分。

转换说明符表

说明符含义printf 参数scanf 参数
%d有符号十进制整数intint *
%i有符号整数int(同 %d)int *(还接受 0x 和以 0 开头的八进制)
%u无符号十进制unsigned intunsigned int *
%f十进制浮点数doublefloat *
%F同 %f,INF/NAN 为大写double-
%e / %E科学计数法doublefloat *
%g / %G%f / %e 中较短的一种doublefloat *
%a / %A十六进制浮点数doublefloat *
%c单个字符int(提升后的 char)char *
%s字符串char *(以 '\0' 结尾)char *(缓冲区)
%p指针地址void *void **
%x / %X无符号十六进制unsigned intunsigned int *
%o无符号八进制unsigned intunsigned int *
%n存储目前已写入的字符数int *int *
%%字面的 %无匹配字面的 %
%[...]扫描集(仅 scanf)-char *

有两项需要特别提醒。%n 是写内存而不是打印,它正是格式化字符串漏洞的根源:永远不要让用户输入的文本进入格式字符串。%p 要求传入 void *;直接传 int * 是未定义行为,尽管所有实际的编译器都照样会打印出地址。

长度修饰符

长度修饰符位于精度和转换字母之间,说明参数实际有多大。

有符号转换(d i)和无符号转换(u x o)对应不同的类型:%lu 要的是 unsigned long,而不是 long。弄错了是未定义行为,不是无关紧要的小问题。

修饰符搭配 d i(printf / scanf)搭配 u x o(printf / scanf)
hhint(来自 signed char) / signed char *unsigned int(来自 unsigned char) / unsigned char *
hint(来自 short) / short *unsigned int(来自 unsigned short) / unsigned short *
(无)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
z有符号的 size 类型 / 指向它的指针size_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *ptrdiff_t 对应的无符号类型

浮点数和字符类的情况如下:

修饰符搭配printf 类型scanf 类型
(无)f e g adoublefloat *
lf e g adouble(可接受,无作用)double *
Lf e g along doublelong double *
lc swint_t / wchar_t *wchar_t *

你实际会用到的组合:

%zu 一定要记住。 sizeof、strlen 以及标准库中所有表示大小的返回值都是 size_t,它在 64 位系统上是 8 字节,而 int 是 4 字节。printf("%d", strlen(s)) 是一个真实的 bug,在某些平台上看起来能用,所以要用 %zu。

hh 和 h 修饰符主要是为 scanf 准备的,因为那里目标的大小很重要。在 printf 中,short 在调用前就被提升为 int,所以用 %d 也能正确打印;%hd 只是表明意图。

定宽类型

对于 <stdint.h> 中的 int32_t 这类类型,可移植的说明符是 <inttypes.h> 中的宏:

PRId32 会展开为适合你的平台的字母,而相邻的字符串字面量会自动拼接,所以格式字符串被拆成了三段。这种写法很难看;另一种做法是强制转换为 long long 再用 %lld,通常更易读,而且总是正确的。

标志

标志紧跟在 % 之后,用来改变输出的样式。它们只适用于 printf。

标志作用示例输出
-在值的后面填充,而不是前面对 42 使用 %-6d|42 |
+有符号值总是打印符号对 42 使用 %+d+42
空格在本该打印 + 的位置打印空格对 42 使用 % d 42
0用零而不是空格填充对 42 使用 %06d000042
#替代形式:%x 加 0x,%o 加 0,%g 保留小数点对 255 使用 %#x0xff

标志可以组合使用:%-+8.2f 表示最小宽度 8、两位小数、总是带符号、在数字后面填充。如果同时写了 - 和 0,以 - 为准。

宽度和精度

宽度是字段的最小尺寸:输出不足时会填充到这个宽度,但超出时永远不会被截断。精度(写在点号之后)对不同类别的含义各不相同:

转换精度的含义
%f %e小数点后的位数(默认 6)
%g有效数字的总位数(默认 6)
%s最多打印的字符数
%d %i %u %x %o最少位数,不足时补零

用 * 代替其中任一个数字,就会从一个 int 参数中取值,这个参数在值本身之前读取。这样就能构建列宽在运行时才计算出来的表格。

在 scanf 中,宽度承担着一个不同而且重要得多的职责:它限制一次转换最多读取多少输入,这是 %s 和缓冲区溢出之间唯一的屏障。

printf 和 scanf 的不同之处

它们看起来很像,但有四个地方不一致。每一处都是常见的 bug 来源:

情况printfscanf
double 值%f(float 会被提升为 double)%lf,%f 表示 float *
宽度最小字段尺寸,会填充最多读取的输入量,会截断
参数值指针:&x,或者数组名
格式中的空格打印一个空格跳过任意数量的连续空白

试着输入 2.5 hello。读取它的格式和打印它的格式对同一个 double 使用了不同的字母,而 %15s 中的 15 把输入限制在 15 个字符加上结束符,正好是 word 的大小。

scanf 扫描集

%[...] 读取的是一组字符,而不是以空白分隔的单词。这是用 scanf 读取一整行的方法,不过 fgets 仍然是更好的工具:

[^\n] 表示“除换行符之外的任意字符”。%[0-9] 只读取数字,%[abc] 只读取这三个字母。和 %s 不同,扫描集不会跳过开头的空白,所以缓冲区里残留的一个换行符会让它什么也匹配不到。

用错就是未定义行为

printf 看不到参数的真实类型。它按说明符暗示的字节数读取,并按说明符的说法去解释。所以类型不匹配不是格式上的小问题,而是把内存当成了错误的东西来读:

printf("%d\n", 3.5);        /* prints garbage: reads a double's bits as int */
printf("%s\n", 42);         /* treats 42 as an address - crash */
printf("%d\n", strlen(s));  /* size_t read as int - wrong on 64-bit */
printf("%f\n", 3);          /* int read as double - garbage */

防御只需要一个编译选项。用 -Wall 编译,GCC 就会对照参数检查每一个字面量格式字符串:

gcc -Wall -Wextra program.c -o program

上面四行都会在编译时被报告出来。这是 C 语言中最有价值的警告,上面的编辑器默认就开启了它。

关于这些说明符的实际用法,比如对齐表格、让浮点数打印得更易读、读取输入时避开经典的换行符 bug,参见 printf 和 scanf。

常见问题

C 语言中 %d 是什么意思?

%d 是有符号十进制整数(int)的转换说明符。在 printf 中,它把参数打印成十进制数字;在 scanf 中,它把十进制数字读入一个 int *。在 printf 中 %i 与它含义相同,但在 scanf 中 %i 还能接受 0x 开头的十六进制和以 0 开头的八进制。

C 语言中 %f 和 %lf 有什么区别?

在 printf 中实际上没有区别:float 参数会被提升为 double,所以 %f 两者都能打印,C99 也接受 %lf 作为同义写法。在 scanf 中区别至关重要:%f 写入 float *(4 字节),%lf 写入 double *(8 字节)。对 double 使用 %f 会破坏内存。

C 语言中怎么打印 size_t 或 long long?

size_t(sizeof 和 strlen 的返回类型)用 %zu,long long / unsigned long long 用 %lld / %llu。对它们使用 %d 是未定义行为:在 32 位系统上碰巧能用,在 64 位系统上就会出错,这也是这个 bug 能长期潜伏的原因。

C 语言中用错格式说明符会怎样?

这是未定义行为,而不是舍入误差。printf 会按说明符声称的类型来读取参数,所以 printf("%d", 3.5) 会打印出垃圾值,printf("%s", 42) 会把数字 42 当作地址去访问,通常会崩溃。用 -Wall 编译,GCC 就会对照参数检查格式字符串。

Coddy 编程语言插图

和 Coddy 一起学编程

开始