scanf 是 C 語言最基礎也最危險的輸入函數。看似簡單的格式字串背後,隱藏著掃描集、返回值語義、緩衝區處理、未定義行為等深層機制。本文系統梳理 scanf 核心特性與避坑指南,助你寫出健壯的輸入處理代碼。
scanf 格式字串可包含普通字符,要求輸入流中逐字匹配:
int num;
scanf("hello %d", &num);
語義: 忽略 hello 與數字間的空白字符(空格、製表、換行),要求輸入以 hello 開頭,隨後為整數。
// 合法輸入
hello 1234
hello1234
// 非法輸入
hi 1234 // 字面不匹配
hello abc // %d 匹配失敗
陷阱: 格式字串 "%d %d" 中的空格會「吃掉」任意數量空白字符,而 "%d,%d" 則要求輸入確實包含逗號。
scanf 返回成功賦值的輸入項數,而非字符數。EOF 時返回 EOF(通常 -1)。
int num, result = 0;
printf("Please input score: ");
while (result != 1) {
fflush(stdin); // 清空輸入緩衝區 (非標準,見下文)
if (result == 0) // 首次或上次失敗
printf("Please input a digital score: ");
result = scanf("%d", &num);
}
關鍵點:
result == 1:成功讀取 1 個整數result == 0:匹配失敗(如輸入 "abc"),錯誤字符留在緩衝區result == EOF:輸入流結束fflush(stdin) 陷阱標準 C 規定 fflush 僅適用於輸出流,對輸入流行為未定義。MSVC 允許清空輸入緩衝區,但 GCC/Clang 可能無效或崩潰。
可移植清空方案:
int c;
while ((c = getchar()) != '\n' && c != EOF); // 讀取並丟棄至換行或 EOF
%[]:比 %s 強大得多的字串讀取%s 停止於空白,%[] 可自定義字符集:
%[charset] // 讀取 charset 內字符,遇非集合字符停止
%[^charset] // 讀取非 charset 字符,遇集合字符停止
// 只讀小寫字母
char str[100];
scanf("%[a-z]", str); // 輸入 "hello123" → 讀取 "hello"
// 讀取直到句點
scanf("%[^.]", str); // 輸入 "hello.world" → 讀取 "hello"
// 讀取整行(含空格)
scanf("%[^\n]", str); // 遇換行停止
// 需後續處理殘留換行:getchar() 或 scanf("%*c")
char buf[10];
scanf("%9[^\n]", buf); // 最多讀 9 字符 + '\0'
scanf 為何被詬病?三大核心痛點fgets + sscanf/strto*char line[256];
if (fgets(line, sizeof(line), stdin)) {
int val;
if (sscanf(line, "%d", &val) == 1) { /* 成功 */ }
// 或用 strtol 更精確控制
}
優勢:行緩衝區隔離,避免跨行殘留汙染。
std::cin / std::getline + 字串流std::string line;
std::getline(std::cin, line);
std::istringstream iss(line);
int val;
if (iss >> val) { /* 成功 */ }
優勢:類型安全、異常處理、可擴展性。
scanf 返回值 每次調用%s/%[] 必須指定寬度 (%99s)fflush(stdin),使用可移植清空循環fgets/std::getline 讀行再解析scanf 是把雙刃劍:功能強大(掃描集、格式匹配),但語義陷阱密布(返回值、緩衝區殘留、非標準擴展)。掌握其掃描集語法、返回值驗證模式、緩衝區管理,能應付維護舊代碼場景;但新代碼請優先選擇 fgets/std::getline + 解析,這是現代 C/C++ 工程的基本職業素養。