printf 與 scanf 是 C 語言最常用、功能最豐富的 I/O 函數。掌握格式字串語法、轉換規格、對應參數規則,是避免「格式化字串漏洞」與輸出錯誤的關鍵。
printf 格式化輸出
int printf(const char *format, ...);
// 返回輸出字元數,錯誤返回負值
format = 普通字元 (原樣輸出) + 轉換規格 (以 % 開頭)
%[旗標][寬度][.精度][長度]類型
| 類型 | 對應參數 | 輸出形式 |
|---|---|---|
d, i | int | 有號十進制整數 |
u | unsigned int | 無號十進制整數 |
o | unsigned int | 八進制 (不帶前綴) |
x, X | unsigned int | 十六進制 (小寫/大寫) |
f, F | double | 定點小數 |
e, E | double | 科學記號 |
g, G | double | 自動選擇 f 或 e (短者) |
c | int (字元) | 單一字元 |
s | char* | 字串 (至 '\0') |
p | void* | 指標位址 (實作定義格式) |
% | 無 | 輸出 '%' |
| 類別 | 選項 | 效果 |
|---|---|---|
| 旗標 | - | 左對齊 (預設右對齊) |
+ | 有號數值強制顯示正負號 | |
(空格) | 正數前加空格,負數加負號 | |
# | 替代形式: 0x/0X/0 前綴, 強制小數點 | |
0 | 左側填充 0 (而非空格),旗標 - 覆蓋 | |
| 寬度 | n 或 * | 最小欄寬,* 從參數取得 |
| 精度 | .n 或 .* | 浮點數小數位數 / 字串最大字元數 / 整數最小位數 |
| 長度 | hh | char (提升為 int) |
h | short | |
l (ell) | long / double (針對 f 等) / wchar_t* | |
ll | long long | |
L | long double |
printf("%-10s %5d %8.2f\n", "Apple", 100, 12.5); // 左對齊字串、寬度5整數、寬度8保留2位小數
printf("0x%08X\n", 0x123ABC); // 8位十六進制前導0、大寫
printf("%p\n", (void*)&var); // 列印位址
scanf 格式化輸入
int scanf(const char *format, ...);
// 返回成功轉換賦值的參數個數,EOF 表示輸入失敗/結束
scanf 參數必須是位址
int a; float b; char c; char str[20];
scanf("%d %f %c %19s", &a, &b, &c, str); // str 已是位址
%[...] 進階用法
char line[100];
scanf("%[^\n]", line); // 讀取整行 (不含 \n)
scanf("%[abc]", set); // 僅讀取 a/b/c 字元
scanf("%[^abc]", set); // 讀取非 a/b/c 字元
| 陷阱 | 錯誤代碼 | 問題 | 修正 |
|---|---|---|---|
忘記 & | scanf("%d", a) | 傳值而非位址,導致亂寫記憶體 | &a |
%s 無寬度限制 | scanf("%s", str) | 緩衝區溢位風險 | %19s (陣列大小-1) |
殘留 \n 干擾後續讀取 | scanf("%d", &n); gets(s) | gets 讀到空行 | 中間加 getchar() 吃掉 \n,或改用 fgets |
| 格式字串不匹配參數 | printf("%d", 1.5) | 未定義行為 | 嚴格對應類型 |
| 忽略返回值 | 不檢查 scanf 返回 | 輸入失敗時變數未初始化 | if (scanf(...) != N) handle_error() |
snprintf/vsnprintf:指定緩衝區大小,防止溢位fgets + sscanf:先安全讀行,再解析,分離 I/O 與解析邏輯scanf_s (C11 Annex K / MSVC 擴展):強制寬度參數printf/scanf 功能強大但「格式字串與參數不匹配」是 C 語言第一大 Bug 來源。熟記「類型字元、旗標/寬度/精度/長度修飾、scanf 必須傳位址、%s 必須限寬度、檢查返回值」五大鐵律,並善用 snprintf/fgets+sscanf 替代不安全用法,能寫出既彈性又安全的格式化 I/O 代碼。