fscanf 是一个“格式化”输入函数,它严格按照你提供的格式字符串来读取数据,它不会自动跳过空白字符(包括换行符 \n),除非你在格式字符串中明确指示它这样做。

fscanf 的工作原理
fscanf 的原型是:
int fscanf(FILE *stream, const char *format, ...);
它会从 stream (文件流,如 stdin 标准输入) 中读取字符,并根据 format (格式字符串) 进行解释。
format 字符串中的普通字符(除了格式说明符,如 %d, %s, %c)必须与输入中的字符完全匹配。
常见问题场景与代码示例
%d 后直接跟 %s (最经典的陷阱)
问题代码:

#include <stdio.h>
int main() {
int age;
char name[50];
printf("请输入年龄: ");
scanf("%d", &age); // 假设用户输入: 25
printf("请输入姓名: ");
fscanf(stdin, "%s", name); // 假设用户输入: Alice
printf("年龄: %d, 姓名: %s\n", age, name);
return 0;
}
执行过程分析:
scanf("%d", &age)读取数字25,输入缓冲区中剩下的内容是:25\nAlice。fscanf(stdin, "%s", name)开始读取。%s格式说明符会读取一个字符串,直到遇到空白字符(空格、制表符\t、换行符\n)为止。- 它会立刻遇到缓冲区里的
\n,因为\n是空白字符,%s认为它已经成功读取了一个空字符串 。 name被赋值为空字符串 。- 程序会立即阻塞,等待用户在下一行输入新的非空白字符。
用户实际感受:
请输入年龄: 25
请输入姓名: Alice
年龄: 25, 姓名:
(然后程序会等待你再次输入,直到你输入一个不带空格的字符串)
如何解决?
在读取字符串之前,需要手动消耗掉那个多余的换行符,最简单的方法是使用一个 %c 格式说明符,并将其赋值一个不需要的变量。
修正后的代码:

#include <stdio.h>
int main() {
int age;
char name[50];
char temp; // 用于消耗换行符的临时变量
printf("请输入年龄: ");
scanf("%d", &age);
// 关键:在这里消耗掉输入缓冲区中的换行符
scanf("%c", &temp);
printf("请输入姓名: ");
fscanf(stdin, "%s", name);
printf("年龄: %d, 姓名: %s\n", age, name);
return 0;
}
scanf("%c", &temp) 会读取并丢弃 25 后面的 \n,fscanf("%s", name) 就能正确读取下一行的 Alice 了。
%c 与换行符
%c 格式说明符的作用是读取单个字符,并且它会读取所有字符,包括空白字符(如空格、换行符 \n)。
问题代码:
#include <stdio.h>
int main() {
char c1, c2;
printf("请输入两个字符,用回车分隔: ");
scanf("%c", &c1); // 读取第一个字符
scanf("%c", &c2); // 读取第二个字符
printf("c1: '%c', c2: '%c'\n", c1, c2);
return 0;
}
执行过程分析:
假设用户输入 A 然后按回车。
scanf("%c", &c1)读取A,缓冲区中剩下:\n。scanf("%c", &c2)立刻读取缓冲区中的\n。- 输出结果:
c1: 'A', c2: '\n'。
如何解决?
如果你希望跳过空白字符(包括换行符),可以在 %c 前面加一个空格。
修正后的代码:
#include <stdio.h>
int main() {
char c1, c2;
printf("请输入两个字符,用空格或回车分隔: ");
scanf(" %c", &c1); // 注意 %c 前面的空格
scanf(" %c", &c2); // 这个空格会告诉 fscanf 跳过所有前导的空白字符
printf("c1: '%c', c2: '%c'\n", c1, c2);
return 0;
}
空格的作用:在 fscanf 的格式字符串中,一个空格(或制表符 \t)会匹配输入中的任意数量的空白字符,直到遇到一个非空白字符为止,它起到了“跳过所有空白”的作用。
%s 与换行符
%s 会读取直到遇到空白字符为止,这意味着 fgets 和 scanf("%s", ...) 的行为不同。
fgets会读取包括换行符在内的整行,直到缓冲区满或遇到换行符。scanf("%s", ...)会在遇到换行符时停止,并且不会消耗换行符,换行符会留在缓冲区里。
示例:
#include <stdio.h>
int main() {
char line1[20];
char line2[20];
printf("请输入第一行文本: ");
fscanf(stdin, "%s", line1); // 用户输入 "Hello"
printf("请输入第二行文本: ");
fscanf(stdin, "%s", line2); // 用户输入 "World"
printf("line1: %s\n", line1);
printf("line2: %s\n", line2);
return 0;
}
如果用户输入:
Hello World
fscanf读取Hello,遇到空格,停止,缓冲区剩下:World\n。- 下一个
fscanf立即从W开始读取,读取World,遇到\n,停止。 - 输出:
line1: Hello line2: World但如果用户分两行输入:
Hello World fscanf读取Hello,遇到\n,停止,缓冲区剩下:\nWorld。- 下一个
fscanf立即读取\n,因为它不是空白字符(对于%s\n是分隔符,但%s不会把它读入字符串),它会等待下一个非空白字符,所以它会读取World。 - 输出结果和上面一样。
最佳实践:fgets + sscanf
处理用户输入时,fscanf 和 scanf 经常会因为缓冲区问题(尤其是换行符)变得非常棘手,一个更稳健、更推荐的方法是:
- 使用
fgets读取一整行输入(包括换行符,或者直到缓冲区满)。 - 使用
sscanf从这个字符串中解析出你需要的数据。
优点:
- 分离输入和解析:
fgets负责安全地获取整行输入,sscanf负责精确地解析,互不干扰。 - 避免缓冲区陷阱:你不再需要担心
scanf或fscanf会留下未处理的换行符在缓冲区里,影响下一次读取。
示例:
#include <stdio.h>
#include <string.h>
int main() {
int age;
char name[50];
char input_buffer[100]; // 用于存储整行输入的缓冲区
printf("请输入年龄和姓名,用空格隔开: ");
// 1. 使用 fgets 读取整行
if (fgets(input_buffer, sizeof(input_buffer), stdin) == NULL) {
printf("输入错误,\n");
return 1;
}
// 2. 使用 sscanf 从缓冲区中解析数据
// sscanf 会从 input_buffer 的开头开始扫描
if (sscanf(input_buffer, "%d %49s", &age, name) == 2) {
// %49s 是一个好习惯,防止 name 数组溢出,最多读取49个字符,留1个给'\0'
printf("解析成功!年龄: %d, 姓名: %s\n", age, name);
} else {
printf("输入格式不正确,\n");
}
return 0;
}
这个方法几乎可以解决所有与 scanf/fscanf 和换行符相关的烦恼。
| 函数/场景 | 行为 | 如何处理换行符 |
|---|---|---|
fscanf("%d", ...) |
读取整数,遇到空白符(包括 \n)停止。 |
不消耗 \n,\n 留在缓冲区中。 |
fscanf("%s", ...) |
读取字符串,遇到空白符(包括 \n)停止。 |
不消耗 \n,\n 留在缓冲区中。 |
fscanf("%c", ...) |
读取任何单个字符,包括 \n。 |
会读取并消耗 \n。 |
fscanf(" %c", ...) |
读取一个字符,但会先跳过所有前导的空白符(包括 \n)。 |
会跳过并消耗 前导的 \n。 |
fgets |
读取一行,直到遇到 \n 或缓冲区满。会把 \n 也读入字符串。 |
fgets 自己处理了换行符,把它作为字符串的一部分。 |
sscanf |
从一个字符串中读取,不涉及文件流或输入缓冲区。 | 不涉及外部输入,因此没有换行符问题。 |
核心要点:
fscanf是“严格匹配”的:格式字符串里的普通字符必须和输入里的字符一模一样。- 空白字符是分隔符:
%d,%s等会因遇到空白符而停止,但通常不会“吃掉”它们。 %c是个例外:它会吃掉任何字符,包括\n。- 最佳实践是
fgets+sscanf:它能让你完全掌控输入和解析过程,避免绝大多数与缓冲区相关的问题。
