前言:超越 %s 的輸入掌控力
scanf 族函式的 %[] (掃描集合) 與 %n (字元計數) 是 C89/C99 標準規定、但教科書鮮少提及的進階功能。它們能解決 %s 無法處理的「含空白輸入」、「特定分隔符」、「精確位置追蹤」等問題。
一、%[] 掃描集合 — 定製你的輸入規則
語法
%[*][width][size] [scanset]
// 範例: %[abc] %[^abc] %10[^\n] %*[^;]
*:指派抑制 (不儲存結果,只跳過)
width:最大讀取字元數 (不含結束 \0)
[scanset]:掃描字符集定義
兩種模式
| 模式 | 語法 | 掃描字符集 (接受的字元) | 分隔符 (停止讀取的字元) |
| 正向 | %[abc] | a, b, c | 除 a,b,c 外的所有字元 |
| 反向 | %[^abc] | 除 a,b,c 外的所有可輸入字元 | a, b, c |
經典用法範例
char buf[100];
// 1. 讀取整行 (含空白),遇換行停止
scanf("%[^\n]", buf); // 相當於 fgets 但不保留 \n
scanf("%*[^\n]"); scanf("%*c"); // 清除緩衝區剩餘換行符
// 2. 讀取 CSV 欄位:遇逗號停止
scanf("%[^,]", field); // 讀取到 ',' 為止
scanf("%*c"); // 跳過 ','
// 3. 只接受數字與小數點
scanf("%[0-9.]", num_str);
// 4. 跳過 HTML 標籤內容
fscanf(fp, "%*[^>]>"); // 讀取並丟棄直到 '>'
// 5. 限制長度防緩衝區溢出
scanf("%99[^\n]", buf); // 最多讀 99 字元
掃描集合特殊字元處理
]:若要包含 ],放在第一個位置 (緊接 [ 或 [^):%[]abc]、%[^]abc]
-:表示範圍 (如 a-z);若要包含字面 -,放在第一個、最後一個、或緊接 ^ 後:%-abc]、%[abc-]、%[^-abc]
^:僅在緊接 [ 時表示反向;其他位置視為字面字元
二、%n 字元計數 — 追蹤已讀取/輸出字元數
特性
- 對應參數:
int * (指向整數的指標)
- 不消耗輸入,只記錄「目前為止已成功讀取/輸出的字元數」
- 不影響函式回傳值 (
scanf 回傳成功指派的項目數,printf 回傳輸出總字元數)
- 可用於
scanf、printf、fscanf、fprintf 等
scanf 範例
int i, j, k;
scanf("%d %d%n", &i, &j, &k);
// 輸入: "434 6434"
// i=434, j=6434, k=8 (含空白共 8 字元: '4','3','4',' ','6','4','3','4')
// scanf 回傳 2 (成功指派 2 個 %d)
char ch;
int pos;
scanf("%c%n", &ch, &pos);
// 輸入: "sbcdefg"
// ch='s', pos=1 (只讀 1 字元), 非 7!
printf 範例
int i = 343, j = 123, k;
printf("i=%d, j=%d%n", i, j, &k);
// 輸出: "i=343, j=123" (12 字元)
// k = 12
// printf 回傳 12 (不含 %n 計數)
三、實戰組合:解析固定格式文字
// 解析: "key=value;" 格式
char key[50], val[100];
int n;
while (fscanf(fp, "%49[^=]=%99[^;];%n", key, val, &n) == 2) {
printf("Key: %s, Value: %s (讀取 %d 字元)\n", key, val, n);
// 繼續下一組
}
四、常見陷阱與注意事項
| 陷阱 | 說明 | 對策 |
| %[] 不跳過前導空白 | %s/%d 會自動跳過空白,%[] 不會 | 格式字串前加空白: " %[^\n]" |
| 緩衝區溢出 | 未指定 width 時,輸入過長會溢出 | 永遠加 width: %99[^\n] |
| %n 不計入回傳值 | scanf 回傳不包含 %n 的指派數 | 預期回傳值 = 格式說明符數 - %n 數 |
| %[] 遇 EOF 行為 | 若未讀到任何符合字元即遇 EOF,指派失敗 | 檢查回傳值 == 項目數 |
| 掃描集合中的 - 歧義 | 部分編譯器將中間 - 視為範圍,部分視為字面 | 明確放在首尾或跳脫: %-az] 或 %[a-z-] |
五、%[] vs %s vs fgets 對比
| 功能 | %s | %[^\n] | fgets |
| 讀取空白 | ❌ 停止 | ✅ 讀取 | ✅ 讀取 |
| 保留換行符 | ❌ | ❌ | ✅ |
指定最大長度 | %99s | %99[^\n] | fgets(buf, 100, fp) |
| 自訂分隔符 | ❌ 固定空白 | ✅ 任意字元集 | ❌ 固定 \n/EOF |
| 安全性 | 需加 width | 需加 width | ✅ 內建長度限制 |
| 推薦場景 | 單詞讀取 | 整行/自訂分隔 | 通用整行讀取 (最安全) |
總結
- %[]:輸入解析的瑞士刀,支援自訂接受字元集、分隔符、長度限制,解決
%s 無法讀空白、fgets 無法自訂分隔符的痛點。
- %n:無副作用的位置追蹤器,適用於解析器實作、格式驗證、進度顯示。
- 兩者結合,可構建健壯的文字協定解析器 (CSV、INI、簡易 JSON、Log 格式等)。
- **永遠指定 width**,檢查回傳值,搭配
fgets/sscanf 組合使用更安全。