printf族函數是C語言最核心的格式化輸出工具。本文系統梳理格式字符串語法、所有格式轉換符、標志字符、寬度精度控制、長度修飾符,並通過示例演示輸出效果與常見陷阱。
%[標志][最小寬度][.精度][長度]類型
方括號內為可選項,類型為必選。
| 符號 | 輸出類型 | 說明 |
|---|---|---|
| d / i | 有符號十進制整數 | d為十進制,i兼容八/十六進制輸入(scanf),輸出同d |
| u | 無符號十進制整數 | |
| o | 八進制無符號整數 | 不輸出前綴0 |
| x / X | 十六進制無符號整數 | x輸出a-f,X輸出A-F,不輸出前綴0x |
| f / F | 十進制浮點數 | 固定小數點記法,f輸出nan/inf,F輸出NAN/INF |
| e / E | 科學記數法浮點數 | e輸出e+nn,E輸出E+NN,總有一位小數點前數字 |
| g / G | 自動選擇f或e | 按較短輸出,去除尾部0,精度指有效數字總數 |
| a / A | 十六進制浮點數 | C99,精確表示二進制浮點值,如0x1.921fb54442d18p+1 |
| c | 單個字符 | 對應int參數,輸出對應字符 |
| s | 字符串 | 對應char*,輸出至\0,精度限最大字符數 |
| p | 指針地址 | 實現定義格式,通常0x前綴十六進制 |
| n | 不輸出 | 將已輸出字符數寫入對應int*參數,極少用 |
| % | 字面% | %%輸出單個% |
| 標志 | 作用 | 適用類型 |
|---|---|---|
| - | 左對齊(默認右對齊),右側填空格 | 所有 |
| + | 有符號類型總顯示符號(+或-) | d,i,f,e,g等 |
| 空格 | 正數前加空格,負數前加-(+優先) | 有符號數值 |
| # | 替代形式:o加前綴0,x/X加0x/0X,f/e/g強制顯示小數點,g/G保留尾部0 | o,x,X,f,e,g |
| 0 | 右對齊時左側填0而非空格(-優先,精度指定時忽略) | 數值類型 |
0標志填0)*動態指定:printf("%*d", width, val).開頭後跟十進制整數或*%.4d對12輸出0012f/e為6,g為1,s無限制| 修飾符 | 含義 | 適用類型 |
|---|---|---|
| hh | signed char / unsigned char | d,i,o,u,x,X,n |
| h | short / unsigned short | d,i,o,u,x,X,n |
| l (ell) | long / unsigned long / wchar_t / double(for f) | d,i,o,u,x,X,n / c,s / f,e,g |
| ll (ell-ell) | long long / unsigned long long | d,i,o,u,x,X,n |
| j | intmax_t / uintmax_t | d,i,o,u,x,X,n |
| z | size_t / ssize_t | d,i,o,u,x,X,n |
| t | ptrdiff_t | d,i,o,u,x,X,n |
| L | long double | f,e,g,a |
關鍵:%lf在printf中等同%f(float傳參自動提升為double),但在scanf中%lf對應double*,%f對應float*。
#include <stdio.h>
int main() {
int a = 15;
float b = 138.3576278f;
double c = 35648256.3645687;
char d = 'p';
printf("a=%d,%5d,%o,%x\n", a, a, a, a);
// a=15, 15,17,f
printf("b=%f,%.4f,%e,%10.4f\n", b, b, b, b);
// b=138.357628,138.3576,1.383576e+02, 138.3576
printf("c=%lf,%f,%8.4lf\n", c, c, c);
// c=35648256.364569,35648256.364569,35648256.3646
printf("d=%c,%8c\n", d, d);
// d=p, p
return 0;
}
C標準未規定函數參數求值順序。printf的可變參數求值順序由編譯器決定(通常右到左,但不可依賴)。
int i = 8;
printf("%d\n%d\n%d\n%d\n%d\n", ++i, --i, i--, i++, -i--);
// 輸出不可預期!屬於未定義行為(同一表達式多次修改i且無序列點)
鐵律:絕不在同一printf調用中對同一變量既修改又讀取。若需連續輸出變化值,分多條語句:
int i = 8;
printf("%d\n", ++i); // 9
printf("%d\n", i++); // 9
printf("%d\n", i); // 10
| 問題 | 風險 | 對策 |
|---|---|---|
| 格式字符串漏洞 | 用戶輸入作格式串 → 內存讀寫攻擊 | 永遠用字面量格式串:printf(fmt, ...)禁用,改printf("%s", fmt) |
| 參數類型不匹配 | 未定義行為,崩潰或輸出亂碼 | 啟用-Wformat -Wformat-security,C++用iostreams或fmt庫 |
| 緩衝區溢出 | sprintf無長度限制 | 永遠用snprintf |
| %n 攻擊 | 配合格式串漏洞任意寫內存 | 禁用%n,編譯器可-Wno-format-nonliteral排查 |
fmt::print("{:0>5}", 42) → 00042,Python風格語法、類型安全、高性能、編譯期檢查std::format("{:.2f}", 3.14159)精通printf格式化語法是C程序員必備基本功,但現代項目應優先遷移至類型安全的格式化庫,從根源消除格式字符串漏洞與類型不匹配風險。