C printf 格式化输出
更新时间:2026-08-26。本文是
languages/c/主题入门层第 7 篇。
前面几篇的代码里全是 printf,这一篇把它彻底讲透。printf 是 C 里最常用的函数,也是新手报错重灾区——因为它不像别的语言会抛异常,格式不对时它静默地打出错误的值,甚至直接崩溃。
本文要回答的问题
%d、%f、%s、%p、%zu各对应什么类型?- 怎么控制输出的宽度、小数位数?
- 为什么
printf("%d", 3.14)不报错,却打出一堆垃圾?
一、最常用的格式符
| 格式符 | 对应类型 | 示例 | 输出 |
|---|---|---|---|
%d | int(有符号十进制) | printf("%d", 42) | 42 |
%u | unsigned int | printf("%u", 42U) | 42 |
%f | double | printf("%f", 3.14) | 3.140000 |
%c | char | printf("%c", 'A') | A |
%s | 字符串(char*) | printf("%s", "hi") | hi |
%p | 指针地址 | printf("%p", &x) | 0x7ffc... |
%zu | size_t | printf("%zu", n) | 10 |
%x / %X | 十六进制(int) | printf("%x", 255) | ff / FF |
%ld / %lld | long / long long | printf("%ld", 42L) | 42 |
要点:float 传给 printf 时会被自动提升成 double,所以 %f 对应的是 double。%d 只认有符号 int,size_t 必须 %zu——类型对应错了,轻则打错值,重则段错误。
二、宽度、精度与对齐
2.1 数字宽度
在 % 和格式符之间夹数字控制宽度,宽度不足时默认用空格右对齐补位:
printf("[%5d]", 42); // 占 5 格,右对齐,输出 [ 42]
printf("[%-5d]", 42); // 负号表示左对齐,输出 [42 ]
printf("[%05d]", 42); // 前导 0 补位,输出 [00042]2.2 浮点精度
点号加数字控制小数位数:
printf("%.2f", 3.14159); // 保留 2 位小数:3.14
printf("%.0f", 3.6); // 四舍五入到整数:4精度还常和宽度组合使用(如 %8.2f:宽度 8、精度 2)。%.2f 是四舍五入,不是截断——做金额显示时这是好帮手,但别用 printf 的结果去反推精确值。
三、转义字符
在字符串里用反斜杠开头的转义序列表达特殊字符,常见的有:\n(换行)、\t(制表符)、\\(反斜杠本身)、\0(空字符,字符串结尾标志)。
注意 \n 是一个字符(换行符),不是两个字符。字符串 "a\nb" 的长度是 3,不是 4——\n 算一个。printf 的输出缓冲是按行刷新的,\n 不只是排版,还影响输出时机(缓冲区细节见下一篇 scanf 的输入缓冲,是同一套机制)。
四、格式与类型不匹配:静默的灾难
这是 printf 最大的坑。printf 无法检查参数类型——它靠格式串里的 % 去"猜"参数类型,猜错了就出问题:
printf("%d", 3.14); // 打出垃圾值
printf("%f", 42); // 打出天文数字
printf("%zu", (int)5); // 可能打错
printf("%s", 42); // 大概率段错误!| 错误 | 后果 |
|---|---|
%d 配 double | 打出不可预测的垃圾值 |
%d 配 size_t | 64 位下截断,大数打错 |
%s 配数字 | 把数字当地址解引用,段错误 |
%c 配字符串 | 打第一个字节,行为混乱 |
为什么这么危险:printf 是可变参数函数,参数按"寄存器 + 栈"传递,格式串里的 % 决定了怎么解读这些字节。%d 会去读"应该放 int 的地方",结果读到的是 double 的一半字节…… 编译器开 -Wall 时能警告常见的格式不匹配,所以:编译带 -Wall,这个坑能拦下一大半。
五、snprintf:写到缓冲区
printf 是打印到屏幕,snprintf 是格式化到字符串缓冲区,日志、拼消息的标配:
char buf[64];
int n = snprintf(buf, sizeof(buf), "value = %d", 42);
// buf 现在是 "value = 42"第二个参数 sizeof(buf) 是缓冲区上限,防止溢出——这是安全编程的基本功,忘了写就是缓冲区溢出漏洞的起点。返回值 n 是"本应写入的字符数"(不含结尾的空字符),可以拿它检查是否截断。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
%f 配 float 之外的类型 | 垃圾输出 | 记住 %f 对应 double |
%d 打印 size_t | 大数错乱 | 用 %zu |
%s 配非字符串 | 段错误 | 检查类型 |
| 反斜杠转义写错 | 编译警告 / 输出异常 | 转义要写全 |
| snprintf 忘缓冲区大小 | 溢出风险 | 永远写 sizeof(buf) |
七、与本站主线衔接
- printf 背后的可变参数机制(参数怎么进栈、
%怎么解析),见高手层 可变参数函数; - 输入与缓冲区的机制,见scanf 与输入缓冲区陷阱(下一篇);
- 格式串被恶意控制时的攻击(格式化字符串漏洞),见缓冲区溢出与安全。
一句话总结
printf 的每个 % 都是"按这个类型读下一个参数"的指令——%d 配 int、%f 配 double、%zu 配 size_t,格式与类型必须一一对应;-Wall 能拦下大部分格式错误,snprintf 写缓冲区永远带大小,这两条是 printf 家族的安全底线。