printf — 格式化輸出
完整格式字符串語法
%[標誌][寬度][.精度][長度]類型
| 組件 | 選項 | 說明 |
| 標誌 | - + 0 空格 # | 左對齊、強制符號、零填充、正數前空格、替代形式 (0x/0/0X) |
| 寬度 | m 或 * | 最小字符寬度,不足左/右填充;* 從參數獲取 |
| 精度 | .n 或 .* | 整數:最小位數 (前導 0);浮點:小數位數;字符串:最大字符數 |
| 長度 | hh h l ll j z t L | hh=char, h=short, l=long/double, ll=long long, L=long double |
| 類型 | 見下表 | 決定參數解讀方式 |
常用類型字符對照表
| 類型 | 對應參數 | 輸出示例 | 備註 |
d i | int | 42 -7 | 有符號十進制 |
u | unsigned int | 42 | 無符號十進制 |
o | unsigned int | 52 | 八進制 (無前綴) |
x X | unsigned int | 2a 2A | 十六進制 (小寫/大寫) |
#x #X | unsigned int | 0x2a 0X2A | # 標誌添加前綴 |
f F | double | 3.140000 | 定點小數,默認 6 位小數 |
e E | double | 3.140000e+00 | 科學計數法 |
g G | double | 3.14 | 自動選 f/e 短者,去尾隨 0 |
c | int (字符) | A | 單字符 |
s | const char* | hello | 字符串 (至 '\0') |
p | void* | 0x7ffe... | 指針地址 (實現定義格式) |
n | int* | (無輸出) | **寫入已輸出字符數** — 安全風險,慎用 |
% | - | % | 輸出字面 '%' (寫 %%) |
精度與寬度實例
printf("%10d", 42); // " 42" (右對齊,寬度 10)
printf("%-10d", 42); // "42 " (左對齊)
printf("%010d", 42); // "0000000042" (零填充)
printf("%.4d", 42); // "0042" (精度 4:最小位數)
printf("%10.2f", 3.14159); // " 3.14" (寬度 10,小數 2 位)
printf("%.5s", "hello world"); // "hello" (最大 5 字符)
printf("%-10.5s", "hi"); // "hi " (左對齊,寬度 10,最多 5 字符)
長度修飾符關鍵對應
%hd / %hhd — short / signed char。
%ld / %lld — long / long long。
%lf — double (printf 中 %f 也可接 double,因默認提升;但 scanf **必須** %lf 讀 double)。
%Lf — long double。
%zu — size_t;%td — ptrdiff_t。
scanf — 格式化輸入
核心差異
- 參數**必須是地址** (指針):
scanf("%d", &a);。
- 格式字符串中**空白字符 (空格、換行、Tab) 匹配輸入中任意長度的空白** (包括 0 個)。
- 非格式字符 (如
scanf("%d,%d", &a, &b);) **必須原樣匹配輸入**,否則匹配失敗停止。
- 返回值:成功匹配並賦值的項目數;EOF 表示輸入結束/錯誤。
常見格式字符 (與 printf 類似,但無精度/標誌)
| 類型 | 參數類型 | 說明 |
d i | int* | 有符號十進制 (i 也支持 0x/0 開頭的八/十六進制) |
u | unsigned* | 無符號十進制 |
o x X | unsigned* | 八/十六進制 |
f e g | float* | 單精度浮點 |
lf le lg | double* | 雙精度浮點 (**必須 l**) |
Lf | long double* | 長雙精度 |
c | char* | 讀單字符 (含空白),%1c 讀 1 字符 |
s | char* | 讀字符串 (跳過前導空白,遇空白停止,**不安全**,易溢出) |
[...] | char* | 掃描集:%[^\n] 讀整行 (含空格),%[0-9] 只讀數字 |
p | void** | 讀指針地址 |
n | int* | 寫入已讀字符數 |
高質量輸入輸出實踐
1. printf 最佳實踐
- **格式字符串不接受用戶輸入** — 防格式化字符串攻擊:
printf(user_str); → printf("%s", user_str);。
- **參數數量類型嚴格匹配** — 開啟編譯器警告 (
-Wformat),C++ 用 std::format (C++20) 或 iostream 類型安全。
- **輸出指針用
%p 配合 (void*)** — printf("%p", (void*)ptr);。
- **浮點輸出控制精度** —
printf("%.2f", val); 避免 3.140000。
2. scanf 避坑指南 (極其重要)
- 必須取地址:
scanf("%d", &a); 而非 a。
- 避免在格式串放非格式字符,特別是
\n:scanf("%d\n", &a); 會導致需輸入非數字字符才能結束。
- 字符串輸入防溢出:
char buf[20]; scanf("%19s", buf); (寬度 = size-1)。或用 fgets(buf, sizeof(buf), stdin); 讀整行再解析。
- 處理殘留換行符:連續
scanf("%d", &a); scanf("%c", &ch); 第二次讀到 '\n'。解決:scanf(" %c", &ch); (格式串前加空格跳過空白) 或循環清理 while (getchar() != '\n');。
- 檢查返回值:
if (scanf("%d", &a) != 1) { /* 輸入錯誤處理 */ }。
- 掃描集讀整行:
char line[100]; scanf("%99[^\n]", line); getchar(); // 吃掉換行。
3. 進階:scanf 返回值驅動循環
int x;
while (scanf("%d", &x) == 1) { // 成功讀 1 個整數
process(x);
}
// 輸入非數字或 EOF 退出
總結對比表
| 特性 | printf | scanf |
| 方向 | 輸出 | 輸入 |
| 參數 | 值 | 地址 (指針) |
| 空白處理 | 按格式輸出 | 格式串空白匹配任意輸入空白 |
| 非格式字符 | 原樣輸出 | 必須原樣匹配輸入 |
| 返回值 | 輸出字符數 | 成功賦值項目數 / EOF |
| 安全風險 | 格式化字符串攻擊 | 緩衝區溢出、殘留換行、匹配失敗 |
精通 printf/scanf 格式控制、參數匹配、返回值檢查、殘留字符處理,是 C 語言工程師從「能跑」到「健壯」的分水嶺。現代 C++ 優先選擇 iostream 或 std::format (C++20) 獲得類型安全與擴展性。