scanf 是C語言最常用的輸入函數,也是Bug高發區。本文從參數為何傳指針、掃描流程、返回值三個維度,徹底講透 scanf 的工作原理與正確用法。
int age; float height; char addr[30];
scanf("%d", &age); // &age: int* 指向age的地址
scanf("%f", &height); // &height: float* 指向height的地址
scanf("%s", addr); // addr: char* 數組名退化為首元素地址
核心原因:C語言參數傳遞是**值傳遞**。scanf 需要在函數內部**修改調用者的變量值**,只能通過指針間接修改。這與「編寫swap函數交換兩數」同理。
錯誤後果:傳非指針(如 scanf("%d", age))將變量值當地址,導致寫入隨機內存,**極大概率崩潰 (Segmentation Fault)**。
%d 為例)\t)、換行(\n)、垂直製表(\v)、換頁(\f)、回車(\r) —— **直到遇到第一個非空白字符**。+ 或 -。0-9,**直到遇到非數字字符**(空白、字母、標點、EOF)。int,經指針存入目標變量。scanf("%d%d", &a, &b) 輸入 123 456、123\n456、123\t456 均可正確讀取。123abc:a=123,下次讀取從 'a' 開始,若下一個也是 %d 會匹配失敗(返回0),'a' 留在流中導致後續讀取死循環。%c **不跳過空白**!讀取下一個字符(含空白)。跳過空白讀字符用 " %c" (格式串前加空格)。%s 跳過空白,讀取非空白序列,**遇到空白停止**,自動加 \0。**不限制長度,極易緩衝區溢出**,實戰用 %29s (留1字節給\0)。
int scanf(const char *format, ...);
Ctrl+D/Ctrl+Z),返回 EOF (通常為 -1, 定義在 <stdio.h>)。
// 1. 循環讀取直到輸入非數字或EOF
while (scanf("%d", &num) == 1) {
process(num);
}
// 2. 跳過輸入中的整數(如忽略某欄)
while (scanf("%d", &dummy) == 1) ;
// 3. 讀取一行文本(含空格)—— 掃描集
char line[100];
scanf("%99[^\n]", line); // 讀取直到換行,最多99字符
getchar(); // 吃掉殘留的\n
// 4. 格式化輸入解析(如日期)
int y, m, d;
if (scanf("%d-%d-%d", &y, &m, &d) == 3) { ... }
| 陷阱 | 後果 | 修正 |
|---|---|---|
忘記 & | 崩潰/靜默破壞內存 | 非數組參數必加 & |
%s 無寬度限制 | 緩衝區溢出,安全漏洞 | % (N為數組大小) |
%c 讀到殘留換行符 | 邏輯錯誤 | " %c" 跳過空白 |
| 不檢查返回值 | 使用未初始化/錯誤數據 | **必須檢查返回值 == 期望項目數** |
| 輸入錯誤後不清理流 | 無限循環 | 匹配失敗時用 scanf("%*s") 吃掉錯誤token或手動清理 |
混用 scanf 和 getchar/fgets | 殘留換行符干擾 | 統一風格,或轉換點顯式處理換行 |
%[...]%[abc]:只讀取 a/b/c 組成的序列。%[^abc]:讀取**非** a/b/c 的序列(如 %[^\n] 讀整行)。%[a-z]:範圍(實現定義,通常支持)。%99[^\n]。scanf 本質是**格式化輸入解析器**,靠指針回寫結果。%s/%[...] **必加寬度**,%c **注意空白**。fgets 讀整行 + sscanf 解析,分離輸入獲取與解析,更健壯。搞懂 scanf 的「指針參數、掃描流程、返回值」三角鐵律,輸入處理不再是噩夢,而是可控、可驗證的工程環節。