| 修飾 | printf 對應類型 | scanf 對應類型 |
| `hh` | `signed char` / `unsigned char` | `signed char*` / `unsigned char*` |
| `h` | `short` / `unsigned short` | `short*` / `unsigned short*` |
| `l` | `long` / `unsigned long` / `double` (對 `%f`) / `wchar_t*` (對 `%c/%s`) | `long*` / `unsigned long*` / `double*` / `wchar_t*` |
| `ll` | `long long` / `unsigned long long` | `long long*` / `unsigned long long*` |
| `j` | `intmax_t` / `uintmax_t` | `intmax_t*` / `uintmax_t*` |
| `z` | `size_t` | `size_t*` |
| `t` | `ptrdiff_t` | `ptrdiff_t*` |
| `L` | `long double` | `long double*` |
四、scanf 獨有高級特性:掃描集 `[...]` / `[^...]`
**唯一能讀取含空白/特定分隔符字符串的機制**。
%[chars] → 只讀取 chars 集合內字符,遇非集合字符停止
%[^chars] → 讀取「非 chars 集合」字符,遇集合內字符停止
// 兩者自動在末尾添加 '\0',需確保緩衝區足夠大
// 經典用例:
scanf("%[^\n]", line); // 讀整行 (含空格),遇 \n 停
scanf("%[^,]", field); // 讀 CSV 字段,遇 , 停
scanf("%[0-9]", digits); // 只讀數字
scanf("%[^;,]", token); // 讀至 ; 或 ,
scanf("\"%[^\"]\"", str); // 讀雙引號內容
安全必加寬度限制:`%31[^\n]` 防止緩衝區溢出 (留 1 字節給 `\0`)。
五、動態寬度/精度:`*` 占位符
// 寬度/精度由參數傳入,而非寫死格式串
printf("%*.*s\n", width, precision, str); // 等價 "%10.5s" 若 width=10, precision=5
scanf("%*d %d", &x); // `%*d` 讀取並**丟棄**一個整數,不賦值
// * 表示「抑制賦值」,不計入返回值,用於跳過輸入字段
六、特殊返回值 `%n`:記錄已處理字符數
int n;
printf("hello%n world", &n); // 輸出 "hello world",n = 5 (hello 長度)
scanf("abc%n def", &n); // 輸入 "abc def",n = 3 (abc 長度)
// 應用:解析定長協議、計算字段偏移
// 風險:格式化字符串攻擊利用 %n 任意寫內存,現代編譯器默認禁用或需特定標志啟用
七、printf 返回值與錯誤處理
- 返回**輸出字符總數** (不含結束 `\0`),負值表示輸出錯誤 (如磁盤滿、管道破裂)。
- `snprintf` 返回**本應輸出的字符數** (可能大於實際寫入),負值表示編碼錯誤。
八、常見陷阱速查
| 陷阱 | 錯誤代碼 | 正確代碼 | 原因 |
| `scanf` 讀字符吃掉換行 | `scanf("%c", &c);` | `scanf(" %c", &c);` | `%c` 不跳過空白,前加空格跳過 `\n` |
| `%s` 緩衝區溢出 | `scanf("%s", buf);` | `scanf("%31s", buf);` | 必須限制寬度 = buf_size - 1 |
| `printf` 格式串漏洞 | `printf(user_input);` | `printf("%s", user_input);` | 用戶輸入含 `%n` `%s` 導致任意讀寫 |
| 浮點 `printf` 傳 `float` | `printf("%f", f);` | `printf("%f", f);` ✅ | 可變參自動提升 `float→double`,`%f` 兼容兩者 |
| `scanf` 浮點傳 `float` | `scanf("%f", &f);` ✅ | `scanf("%f", &f);` ✅ | `%f` 對應 `float*`,`double` 需 `%lf` |
| 優先級:`==` 高於 `&` | `if (x & y == 0)` | `if ((x & y) == 0)` | `==` 先結合,必須加括號 |
九、C99/C11 新增格式 (需 `` ``)
// 定寬整數打印 (可移植)
printf("int32: %" PRId32 ", uint64: %" PRIu64 "\n", i32, u64);
// 掃描
scanf("%" SCNd32 "%" SCNu64, &i32, &u64);
// bool 類型 (C99 _Bool, C23 bool)
printf("bool: %d\n", flag); // 0/1
scanf("%d", &flag); // 讀 0/1 非零視為真
總結
掌握格式化 I/O 的核心在於:**區分輸出與輸入語義差異、精確控制寬度精度長度、善用掃描集處理複雜輸入、防範緩衝區溢出與格式串漏洞**。建議打印此表貼近顯示器,或配置編輯器 Snippet 快速插入常用模式。
- 上一篇文章: C語言嵌入式實戰:動態內存碎片掃描與可用塊統計函數
- 下一篇文章: 最大子序列和問題:O(N) 線性掃描法與 O(N log N) 分治法完整實現
Copyright ©
程式碼之家 All Rights Reserved