scanf 是 C 語言最基礎也最「反直覺」的輸入函數。本文系統拆解其四大經典坑點,給出標準解法與最佳實踐代碼模板。
\n、制表符 \t)%10s 讀 10 字符)%d 讀到 'a')**關鍵**:scanf 只有在**所有格式匹配成功**並讀到換行後才返回,否則留在緩衝區等待下次調用。
char str[80];
scanf("%s", str); // 輸入 "I love you!" → 只讀 "I"
printf("%s", str); // 輸出 "I"
**原因**:%s 遇空格停止,"love you!" 殘留 stdin。
**解法**:用掃描集 %[^\n] 讀整行(含空格):
scanf("%[^\n]", str); // 讀到換行為止
// 或 fgets(str, sizeof(str), stdin); // 更安全,自帶換行處理
int a; char c;
scanf("%d", &a); // 輸入 "10\n",a=10,\n 殘留
scanf("%c", &c); // 直接讀到 \n,c='\n' (ASCII 10)!
**五大清理殘留方案對比**:
| 方案 | 代碼 | 優點 | 缺點 |
|---|---|---|---|
| 1. 格式串前加空格 | scanf(" %c", &c); | 最簡潔,自動跳過所有空白 | 不適用 %s/%[ ] |
| 2. getchar() 吃單字符 | getchar(); | 精確吃一個 | 若無殘留會吃掉下一個有效字符 |
| 3. %*c 抑制賦值 | scanf("%d%*c", &a); | 同步吃掉 | 格式固化,靈活性差 |
| 4. fflush(stdin) | fflush(stdin); | 清空整個輸入緩衝區 | **C 標準未定義行為**,僅 Windows/MSVC 支持 |
| 5. 讀整行再解析 | fgets(buf,..); sscanf(buf,..); | 最健壯,徹底隔離輸入與解析 | 稍繁瑣 |
**推薦**:方案 1(scanf(" %c", &c))或方案 5(fgets + sscanf)。
int a, b;
while (1) {
scanf("%d,%d", &a, &b); // 輸入 "a,b" → 匹配失敗,a,b 不變,緩衝區不變
// 下次循環同樣失敗 → 死循環
}
**標準解法**:檢查返回值(成功匹配個數),失敗時清理緩衝區:
while (scanf("%d,%d", &a, &b) != 2) {
// 清理殘留:讀取並丟棄直到換行
int c;
while ((c = getchar()) != '\n' && c != EOF);
printf("輸入格式錯誤,請重新輸入:");
}
for (int i=0; i<10; i++) {
scanf("%c", &c); // 無空格:第 1 次讀有效字符,第 2~10 次讀殘留 \n/空格
}
**對比**:
scanf("%c", &c); // 連續讀 10 次:第 1 次有效,其餘全是 \n
scanf(" %c", &c); // 前加空格:每次自動跳過空白,正常讀 10 個有效字符
// 讀整行並解析,彻底避開 scanf 緩衝區陷阱
bool read_int(const char *prompt, int *out) {
char buf[256];
printf("%s", prompt);
if (!fgets(buf, sizeof(buf), stdin)) return false; // EOF
buf[strcspn(buf, "\n")] = 0; // 去掉尾部 \n
return sscanf(buf, "%d", out) == 1;
}
// 用法
int age;
while (!read_int("請輸入年齡: ", &age)) {
printf("無效輸入,請重試。\n");
}


char b; int a; → scanf("%d", &a) 後 b 被覆蓋int a; char b; 或 char b[2]; scanf("%1s", b) 可規避(但本質是棧佈局問題)scanf 直接讀字符,改用 fgets/sscanf 模式徹底搞懂 scanf 的「格式匹配、緩衝區殘留、返回值語義」三機制,並建立「fgets 讀行 → sscanf 解析」的肌肉記憶,才能從輸入地獄中解脫。