printf家族是C程序員每天打交道最多的函數,但真正掌握其所有轉換說明符、修飾符組合及邊界行為的並不多。本文系統整理printf格式化規範,配合實測示例,構建完整知識體系。
%[flags][width][.precision][length]specifier ↑ ↑ ↑ ↑ ↑ ↑ | | | | | └─ 必須:轉換說明符 | | | | └─ 可選:長度修飾符 | | | └─ 可選:精度 | | └─ 可寬:最小寬度 | └─ 可選:標誌位 └─ 固定:百分號起始
| 說明符 | 含義 | 示例輸入 | 輸出 | 備註 |
|---|---|---|---|---|
| %d, %i | 有符號十進制 | 10, -10 | 10, -10 | 完全等價 |
| %u | 無符號十進制 | -1 (32位) | 4294967295 | 二進制重新解讀 |
| %x, %X | 十六進制(小寫/大寫) | 255, -1 | ff, FFFFFFFF | 無符號解讀 |
| %o | 八進制 | 65 | 101 | 無符號 |
| 說明符 | 含義 | 示例 | 輸出 | 精度默認值 |
|---|---|---|---|---|
| %f, %F | 十進制記數法 | 12.62543219 | 12.625432 | 6位小數 |
| %e, %E | 科學記數法 | 0.625, 9000876.54 | 6.250000e-01, 9.000877e+06 | 6位小數 |
| %g, %G | 自動選擇%f/%e | 0.000089, 0.00089 | 8.9e-05, 0.00089 | 6位有效數字 |
| %a, %A | 十六進制浮點數 | 3.75, -3.75 | 0x1.ep+1, -0X1.EP+1 | 精確表示 |
%g/%G選擇規則:指數在[-4, 5]區間用%f,否則用%e。%G對應%E。
| 說明符 | 含義 | 示例 | 輸出 |
|---|---|---|---|
| %c | 單字符 | 65 (int) | A |
| %s | 字符串 | "hello" | hello |
| %p | 指針地址 | &var | 0x7ffee3a8b9c0 |
| %% | 字面百分號 | %% | % |
| %n | 寫入已輸出字符數 | int n; printf("%n", &n) | 無輸出,n被修改 |
| 標誌 | 效果 | 適用類型 | 示例 |
|---|---|---|---|
| - | 左對齊(默認右對齊) | 所有 | %-10d → "123 " |
| + | 強制顯示符號 | 有符號數 | %+d → +123, -123 |
| 空格 | 正數前加空格,負數顯示- | 有符號數 | % d → " 123", "-123" |
| 0 | 前導填充0(而非空格) | 數字 | %05d → 00123 |
| # | 替代形式 | o,x,X,a,A,e,E,f,F,g,G | %#x → 0x41, %#o → 0101 |
優先級:+高於空格;-或精度指定時0失效。
*表示從參數讀取:printf("%*d", 5, 10)f/e/g):小數點後位數s):最大字符數d/i/o/u/x/X):最小數字位數,前導補0.*從參數讀取:printf("%.*s", 3, "hello") → "hel"| 修飾符 | 對應類型 | 說明符示例 | 備註 |
|---|---|---|---|
| hh | signed char / unsigned char | %hhd, %hhu, %hhx | C99 |
| h | short / unsigned short | %hd, %hu, %hx | |
| l | long / unsigned long | %ld, %lu, %lx | 指針用%p |
| ll | long long / unsigned long long | %lld, %llu, %llx | C99 |
| L | long double | %Lf, %Le, %Lg | |
| j | intmax_t / uintmax_t | %jd, %ju | C99 |
| z | size_t / ssize_t | %zu, %zd | C99 |
| t | ptrdiff_t | %td, %tu | C99 |
printf("%.2f\n", 12.62543219); // 12.63 (四捨五入)
printf("%f\n", 12.62543219); // 12.625432 (默認6位)
printf("%.3s\n", "hello"); // "hel" (精度限制)
printf("%10s\n", "hi"); // " hi" (寬度填充)
printf("%-10.3s\n", "hello"); // "hel " (左對齊+精度)
printf("%u\n", -1); // 4294967295 (32位)
printf("%d\n", 4000000000u); // 未定義行為,可能輸出負數
%n將已輸出字符數寫入對應int*參數,可被利用進行格式化字符串攻擊。現代編譯器-Wformat-security會警告,建議禁用或極度謹慎使用。
float f = 3.14f;
printf("%f\n", f); // OK: float默認提升為double
printf("%d\n", f); // UB: 讀取錯誤棧位置
printf("%f\n", 10); // UB: int按double讀取
char fmt[32]; snprintf(fmt, sizeof(fmt), "%%.%df", precision); // 生成 "%.3f" printf(fmt, value);
printf("%2$d %1$d\n", 10, 20); // "20 10" 參數索引從1開始
/* 適用於國際化:格式字符串可調整參數順序 */
int len;
printf("Name: %s%n\n", name, &len);
printf("Value: %*s%d\n", 20 - len, "", value); // 對齊Value列
snprintf:防緩衝區溢出,返回本應寫入的字符數(不含\0),可用於預計算所需緩衝區大小asprintf (GNU/BSD):自動分配內存,char *p; asprintf(&p, "%s%d", s, n);vasprintf:va_list版本常用組合模式: "%-20s %10d %8.2f" // 左對齊字符串、右對齊整數、固定2位小數浮點數 "%04d-%02d-%02d" // 日期零填充 "0x%08lX" // 8位十六進制地址大寫 "%.3s" // 截取前3字符 "%zd bytes" // size_t打印 "%%" // 輸出字面%
精通printf格式化不僅是避免Bug,更是編寫清晰診斷輸出、構建文本報表、實現序列化協議的基礎技能。建議將上述速查卡打印備查,遇到疑難格式問題時對照驗證,養成「先看格式字符串再看參數」的代碼審查習慣。