格式化输入输出:printf、scanf的格式控制符详解
说到C语言,我最先想聊的就是输入输出。你想想看,学编程第一件事儿是什么?不就是让程序跟你说话嘛。printf和scanf就是C语言里最基础、最常用的两个函数。我当年刚学的时候,被这些格式控制符搞得晕头转向,后来在项目里踩过几次坑,才算真正搞明白。
说白了,格式控制符就是告诉程序:「嘿,这个数据你要怎么显示,或者你要怎么读进来」。不同的数据类型,得用不同的控制符。用错了,轻则显示乱码,重则程序崩溃。嗯,咱们一个一个来看。
1. 格式控制符的基本结构
先看个最简单的例子:
printf("数字是:%d\n", 42);
这里的%d就是格式控制符。%是起始标记,d表示十进制整数。中间还可以加一些修饰符,比如宽度、精度什么的。我习惯把格式控制符拆成三部分来看:
- 起始符:
%,告诉编译器后面跟着的是格式控制 - 修饰符:可选,控制宽度、对齐、精度等
- 类型符:
d、f、c等,指定数据类型
2. 常用格式控制符详解
咱们先看一张表,把最常用的几个控制符列出来:
| 控制符 | 说明 | 示例 | 输出结果 |
|---|---|---|---|
%d |
有符号十进制整数 | printf("%d", -10); |
-10 |
%u |
无符号十进制整数 | printf("%u", 10); |
10 |
%f |
浮点数(小数形式) | printf("%f", 3.14); |
3.140000 |
%c |
单个字符 | printf("%c", 'A'); |
A |
%s |
字符串 | printf("%s", "hello"); |
hello |
%x |
十六进制整数(小写) | printf("%x", 255); |
ff |
%X |
十六进制整数(大写) | printf("%X", 255); |
FF |
%p |
指针地址 | printf("%p", &a); |
0x7ff... |
我在项目中遇到过一个问题:用%d打印无符号整数,结果负数显示成了很大的正数。为什么会这样?因为%d把内存里的二进制解释成了有符号数。所以,类型匹配很重要。
3. 格式修饰符
光有类型符还不够,有时候我们需要控制输出的宽度、精度、对齐方式。修饰符就是干这个的。
3.1 宽度控制
printf("%5d", 42); // 输出:" 42"(右对齐,占5位)
printf("%-5d", 42); // 输出:"42 "(左对齐,占5位)
宽度控制说白了就是「这个数最少占几个字符位置」。如果数字本身位数不够,就用空格补齐。我习惯用%-5d这种左对齐,打印表格时特别好用。
3.2 精度控制
printf("%.2f", 3.14159); // 输出:"3.14"
printf("%.5d", 42); // 输出:"00042"(前面补0)
精度控制对浮点数来说,就是小数点后保留几位。对整数来说,就是最少显示几位,不够前面补0。嗯,这里要注意:精度和宽度是两回事,别搞混了。
3.3 组合使用
printf("%8.2f", 3.14159); // 输出:" 3.14"(总宽8位,小数2位)
宽度和精度可以一起用。我个人习惯先写宽度,再写精度,中间用点隔开。
4. scanf的格式控制
scanf和printf的格式控制符基本一样,但有个关键区别:scanf需要传变量的地址。我见过太多新手在这里翻车了。
int a;
scanf("%d", &a); // 正确:传地址
scanf("%d", a); // 错误:传值,程序会崩溃
为什么会这样?因为scanf要把读到的数据写进变量里,必须知道变量在内存中的位置。传值的话,它不知道往哪儿写。
4.1 scanf的常见陷阱
我曾经在调试一个串口通信程序时,发现scanf读字符串总是少一个字符。后来才意识到,scanf读字符串时会在末尾自动加\0,所以缓冲区必须多留一个字节。
char buf[10];
scanf("%9s", buf); // 最多读9个字符,留一个给'\0'
scanf("%s", buf),结果用户输入了超长字符串,直接导致缓冲区溢出,程序崩溃。从那以后,我每次都用宽度限制,比如%9s。
4.2 scanf的返回值
很多人不知道,scanf其实有返回值。它返回成功读取的项数。我习惯用这个来判断输入是否合法:
int a, b;
if (scanf("%d %d", &a, &b) != 2) {
printf("输入格式错误!\n");
}
嗯,这个小技巧能帮你避免很多莫名其妙的bug。
5. 知识体系总览
下面这张图把本章的核心知识点串起来了,你可以对照着复习:
6. 实战建议
说了这么多,最后给你几个我自己的经验:
- 调试时多用
%p:打印指针地址,能帮你快速定位内存问题。我每次调试指针相关代码,第一件事就是打印地址看看。 - 浮点数输出用
%.2f:默认的%f会输出6位小数,太长了。我习惯保留2位,够用又清爽。 - 十六进制用
%#x:加个#前缀,输出会变成0xff这种格式,一眼就能看出是十六进制数。 - scanf别用
%s读字符串:除非你确定输入长度可控。否则用fgets更安全。
嗯,格式化输入输出看起来简单,但用好了能省不少事儿。你写代码的时候多留意这些细节,慢慢就形成肌肉记忆了。