當使用 `scanf("%d", &a)` 讀取整數時,使用者輸入 123\n,`scanf` 讀走 123,**換行符 `\n` 殘留在緩衝區**。下一次呼叫 `scanf("%c", &ch)` 或 `gets()` 時,**直接讀取殘留的 `\n`**,導致「跳過輸入」現象。
核心原則: 徹底讀取並丟棄緩衝區中殘留字元,直到遇到 `\n` 或 EOF。
scanf("%*[^\n]%*c");
fflush(stdin);
int c;
while ((c = getchar()) != '\n' && c != EOF);
void clear_stdin(void) {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
char buf[1024];
fgets(buf, sizeof(buf), stdin);
rewind(stdin);
| 方法 | 標準合規 | 跨平台 | 阻塞風險 | 程式碼簡潔度 | 推薦等級 |
|---|---|---|---|---|---|
| `scanf("%*[^\n]%*c")` | ✓ | ✓ | 高 (空緩衝區阻塞) | 高 (一行) | ★★★★☆ |
| `fflush(stdin)` | ✗ UB | ✗ (僅 Windows) | 無 | 高 | ★☆☆☆☆ 禁用 |
| `while(getchar()!='\n')` | ✓ | ✓ | 無 (遇 EOF 退出) | 中 (3 行) | ★★★★★ 最佳 |
| `fgets(buf, size, stdin)` | ✓ | ✓ | 無 | 中 | ★★★☆☆ |
| `rewind(stdin)` | ✗ UB | ✗ | 未定義 | 高 | ★☆☆☆☆ 禁用 |
不要在業務邏輯中散落清理代碼,封裝「讀取 + 自動清理」的安全包裝器:
// 安全讀取整數
int read_int(int *out) {
int rc = scanf("%d", out);
clear_stdin(); // 無論成功失敗都清理
return rc == 1;
}
// 安全讀取單字元(跳過空白)
int read_char(char *out) {
int c = getchar();
if (c == EOF) return 0;
*out = (char)c;
clear_stdin(); // 清理該字元後的殘留
return 1;
}
// 安全讀取一行字串(去除尾端 \n)
int read_line(char *buf, size_t sz) {
if (!fgets(buf, sz, stdin)) return 0;
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0';
else clear_stdin(); // 行過長被截斷,清理殘留
return 1;
}
| 場景 | 問題 | 解決方案 |
|---|---|---|
| `scanf("%d")` 後接 `scanf("%c")` | 讀到殘留 `\n` | 改用 `scanf(" %c")` (前置空白吃掉空白字元) 或中間插 `clear_stdin()` |
| `scanf("%d")` 後接 `fgets()` | 讀到空行 | `scanf` 後必呼 `clear_stdin()` |
| 混合 `scanf` / `getchar` / `fgets` | 緩衝區狀態混亂 | **統一改用 `fgets` + `sscanf` 解析**,完全避開 scanf 緩衝區陷阱 |
| 輸入驗證失敗需重新輸入 | 錯誤資料殘留 | 驗證失敗後呼 `clear_stdin()` 再提示重輸 |
總結: `scanf` 緩衝區問題的本質是**格式化掃描不消耗分隔符**。最穩健、可移植、無副作用的方案是 **`while ((c = getchar()) != '\n' && c != EOF);` 封裝為 `clear_stdin()`**。現代 C 程式設計建議**盡量避免直接使用 `scanf`**,改用 `fgets` 讀整行再用 `sscanf`/`strtol` 解析,從源頭消除緩衝區狀態管理的心智負擔。