这是一个在 C 语言初学者中非常常见,但又充满争议和陷阱的话题,我会从以下几个方面为你彻底讲清楚:

fflush(stdin)的意图是什么?- 为什么它在标准 C 中是未定义行为?
- 它为什么“看起来好像”在很多编译器上有效?
- 如何正确地清空输入缓冲区? (这才是最重要的部分)
fflush(stdin) 的意图是什么?
fflush 函数的官方定义是“刷新一个输出流”或“更新一个流”,它的作用是强制将缓冲区中尚未写入(对于输出流)或尚未读取(对于输入流)的数据立即处理掉。
- 对于 输出流(如
stdout,FILE*指针以 "w" 模式打开的文件),fflush(fp)会把缓冲区里的内容立刻写入到文件或屏幕上。 - 对于 输入流(如
stdin,FILE*指针以 "r" 模式打开的文件),fflush(stdin)的“意图”是清空输入缓冲区,丢弃其中尚未被scanf或getchar等函数读取的字符。
一个典型的场景:
假设你写了如下代码:
#include <stdio.h>
int main() {
int age;
char name[50];
printf("请输入您的年龄: ");
scanf("%d", &age);
// 如果用户输入 "25" 然后按回车,输入缓冲区里剩下 "\n"
printf("请输入您的姓名: ");
// 如果直接用 gets,它会读取缓冲区里的 "\n",然后立即结束,导致 name 为空
gets(name); // 危险函数,这里仅作演示
printf("年龄: %d, 姓名: %s\n", age, name);
return 0;
}
用户输入 25 后按回车,scanf 读取了数字 25,但回车符 \n 还留在输入缓冲区,接着执行 gets(name),它会立刻读取这个 \n,认为输入已经结束,导致 name 数组没有被正确赋值。

这时候,一个初学者可能会想:“我需要在 scanf 之后清空一下输入缓冲区,把那个 \n 丢掉”,于是他写下了 fflush(stdin);。
为什么它在标准 C 中是未定义行为?
关键点就在这里:fflush 函数在标准 C (C89/C90, C99, C11) 中,只被定义为用于输出流,或者用于更新流(既能读也能写的流,如 stdout 通常也是更新流)。
对于纯输入流(如 stdin),调用 fflush 是未定义行为。
- 什么是未定义行为?
它意味着标准委员会没有规定在这种情况下会发生什么,编译器可以自由地决定如何处理它,你的程序可能会:
- 看起来正常工作(这是最危险的)。
- 完全没有任何效果。
- 导致程序崩溃。
- 做任何奇怪的事情(比如破坏内存)。
从标准 C fflush(stdin) 是一个错误的用法,是不应该出现在你的代码中的。

它为什么“看起来好像”在很多编译器上有效?
你可能会说:“不对啊,我用 fflush(stdin) 在 Dev-C++ (GCC)、Visual Studio (MSVC) 上都清空了缓冲区,很好用!”
这是因为,虽然标准没有规定,但一些编译器厂商(尤其是微软)为了方便开发者,对 fflush(stdin) 提供了“扩展功能”或“非标准实现”。
- 在 Windows 下的 MSVC (Visual Studio) 编译器中,
fflush(stdin)确实可以清空输入缓冲区,这是微软的特有行为,不属于 C 标准。 - 在 GCC/Clang (Linux, macOS, MinGW) 等编译器中,
fflush(stdin)通常是无效的,它不会做任何事情,符合标准中“未定义行为”可能产生的一种结果。
为什么这很危险?
你的代码不具备可移植性,在 MSVC 上运行良好的代码,换到 GCC 环境下就可能失效,导致程序逻辑错误,一个专业的程序员应该编写遵循标准的、可移植的代码,而不是依赖特定编译器的“扩展功能”。
如何正确地清空输入缓冲区?
这才是我们应该学习的核心内容,既然 fflush(stdin) 不可靠,那么有哪些标准、安全、可移植的方法呢?
这里推荐两种最常用和最有效的方法。
读取并丢弃所有字符(最通用、最安全)
这种方法的思想是:既然缓冲区里有“垃圾”,那就一个一个地把它们“吃掉”,直到缓冲区为空。
// 循环读取并丢弃字符,直到遇到换行符或文件结尾
int c;
while ((c = getchar()) != '\n' && c != EOF) {
// 丢弃字符,空循环体即可
}
如何使用:
#include <stdio.h>
int main() {
int age;
char name[50];
printf("请输入您的年龄: ");
scanf("%d", &age);
// 在这里调用清空函数
clear_input_buffer();
printf("请输入您的姓名: ");
// gets 是安全的(虽然 gets 本身还是不推荐,应该用 fgets)
fgets(name, sizeof(name), stdin); // fgets 会读取换行符,所以需要处理
// 去掉 fgets 读取的换行符
for (int i = 0; name[i] != '\0'; i++) {
if (name[i] == '\n') {
name[i] = '\0';
break;
}
}
printf("年龄: %d, 姓名: %s\n", age, name);
return 0;
}
// 定义一个清空输入缓冲区的函数
void clear_input_buffer() {
int c;
while ((c = getchar()) != '\n' && c != EOF) {
// do nothing
}
}
优点:
- 100% 标准 C,可移植性极强,在任何编译器上都能正常工作。
- 逻辑清晰,明确表达了“清空缓冲区”的意图。
缺点:
- 代码比
fflush(stdin)稍长一点。
使用 scanf 的返回值(更简洁)
scanf 函数会返回成功匹配并赋值的变量个数,我们可以利用这一点来“消耗”掉缓冲区中多余的字符。
// 假设我们已经用 scanf 读取了一个整数
int num;
scanf("%d", &num);
// 如果用户输入了 "123abc",scanf 只会读取 "123","abc" 会留在缓冲区
// 我们可以这样消耗掉剩余部分:
scanf("%*[^\n]"); // 读取并丢弃所有字符,直到遇到换行符
getchar(); // 再消耗掉那个换行符
*解释 `scanf("%[^\n]");`:**
- 表示“赋值抑制符”,即成功匹配的字符不会被赋值给任何变量。
[^\n]是一个字符集,表示“匹配除了换行符以外的所有字符”。- 这行代码的意思是:读取并丢弃所有非换行符的字符。
如何使用:
#include <stdio.h>
int main() {
int age;
char name[50];
printf("请输入您的年龄: ");
scanf("%d", &age);
// 使用 scanf 的方式清空
// 1. 丢弃输入行中(当前行)的所有剩余字符
scanf("%*[^\n]");
// 2. 丢弃换行符本身
getchar();
printf("请输入您的姓名: ");
fgets(name, sizeof(name), stdin);
// ... (同上,处理 fgets 的换行符)
printf("年龄: %d, 姓名: %s\n", age, name);
return 0;
}
优点:
- 代码非常简洁。
缺点:
- 不如第一种方法直观。
scanf本身就失败了(比如用户输入的不是数字),%*[^\n]可能不会被正确执行,导致缓冲区未被清空,所以它通常紧跟在一个成功的scanf调用之后。
总结与最佳实践
| 方法 | 代码示例 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
fflush(stdin) |
fflush(stdin); |
在特定编译器(如MSVC)上代码最短。 | 标准未定义行为,不可移植,是坏习惯。 | ⭐ (不推荐) |
getchar() 循环 |
while((c=getchar())!='\n' && c!=EOF); |
标准、安全、可移植,逻辑清晰。 | 代码稍长。 | ⭐⭐⭐⭐⭐ (强烈推荐) |
scanf 返回值 |
scanf("%*[^\n]"); getchar(); |
代码简洁。 | 不够直观,依赖前一个 scanf 成功。 |
⭐⭐⭐⭐ (推荐) |
最终建议:
- 永远不要在你的 C 代码中使用
fflush(stdin)。 即使它现在在你的电脑上能用,也不要养成这个坏习惯。 - 养成封装一个好习惯: 编写一个
clear_input_buffer()函数,使用getchar()循环实现,然后在任何需要清空输入缓冲区的地方调用这个函数。 - 从根本上解决问题: 尽量避免使用
scanf和gets这类容易出问题的函数,对于字符串输入,fgets是更安全、更现代的选择,虽然fgets会读取换行符,但这比gets直接导致缓冲区溢出要好得多,你只需要简单地处理掉这个换行符即可。
