在 C/C++ 開發中,字串格式化處理是核心基本功。sprintf 與 sscanf 作為標準庫中最強大的格式化工具,雖然用法看似簡單,但細節決定成敗。本文將從原理、常見用法、進階技巧到避坑指南,系統梳理這兩個函數的精髓。
int sprintf(char *buffer, const char *format, ...);
sprintf 是變參函數,前兩個參數固定:目標緩衝區與格式化字串。其精華在於格式化字串中的 % 佔位符,配合後續變參列表,將各類型資料「拼裝」成字串。與 printf 唯一不同:前者輸出到字串,後者輸出到標準輸出。
sprintf(s, "%d", 123); // "123"
sprintf(s, "%8d%8d", 123, 4567); // " 123 4567" 右對齊補空格
sprintf(s, "%-8d%8d", 123, 4567); // "123 4567" 左對齊
sprintf(s, "%8x", 4567); // 小寫十六進制,寬度 8,右對齊
sprintf(s, "%-8X", 4568); // 大寫十六進制,寬度 8,左對齊
sprintf(s, "%08X", 4567); // "000011D7" 寬度 8,前導補零(關鍵技巧)
變參函數無類型安全,short 會被整型提升為 int。若直接打印 short si = -1 配合 %04X,會得到 FFFFFFFF 而非預期的 FFFF。
short si = -1;
sprintf(s, "%04X", (unsigned short)si); // 正確:強制零擴展
// 或直接使用無符號類型
unsigned short usi = -1;
sprintf(s, "%04X", usi);
sprintf(s, "%f", 3.1415926); // "3.141593" 預設 6 位小數
sprintf(s, "%10.3f", 3.1415626); // " 3.142" 總寬 10,小數 3 位
sprintf(s, "%-10.3f", 3.1415626); // "3.142 " 左對齊
sprintf(s, "%.3f", 3.1415626); // "3.142" 僅限制小數位
血淚教訓:絕不可用 %f 對應整數變數,會導致位模式誤讀,結果不可預測。必須顯式轉型 (double)i。
char *who = "I", *whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); // "I love CSDN."
若字元陣列無 \0 結尾,%s 會越界。解法:用 %.ns 限制最大讀取長度。
char a1[] = {'A','B','C','D','E','F','G'};
char a2[] = {'H','I','J','K','L','M','N'};
sprintf(s, "%.7s%.7s", a1, a2); // "ABCDEFGHIJKLMN" 安全截取
* 佔位符)sprintf(s, "%.*s%.*s", 7, a1, 7, a2); // 等同 %.7s%.7s
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2); // 更通用
sprintf(s, "%-*d", 4, 'A'); // "65 " 動態寬度
sprintf(s, "%#0*X", 8, 128); // "0X000080" 動態寬度+前綴
sprintf(s, "%*.*f", 10, 2, 3.1415926); // " 3.14" 動態總寬+小數位
sprintf(s, "%p", &i); // 標準做法,自動適配架構(32/64 位)
// 等效於:sprintf(s, "%0*x", 2*sizeof(void*), &i);
sprintf 返回實際寫入字元數(不含結尾 \0)。
int len = sprintf(s, "%d", i); // 直接獲得字串長度
// 實戰:高效拼接隨機數
char buf[64];
int offset = 0;
for (int i = 0; i < 10; i++) {
offset += sprintf(buf + offset, "%d,", rand() % 100);
}
buf[offset - 1] = '\n'; // 替換最後逗號
此法比 strcat 高效:後者每次需掃描尋找 \0,前者直接記錄位置。
%.ns 限長。printf 易犯的低級錯誤。* 佔位的動態參數。int sscanf(const char *str, const char *format, ...);
成功返回成功轉換的參數個數;失敗返回 EOF(-1);若首個轉換即失敗返回 0。
%[] 掃描集比 %s 更強大,支援字元集合與排除集:
%[a-z]:僅讀取小寫字母,遇其他字元停止。%[^#]:讀取直到遇到 # 為止(不含 #)。%20[^#]:最多讀 20 字元,遇 # 停止。char buf[] = "Notebook,IBM,ThinkPad X32,6,12000";
char category[21], name[21], vendor[21], sku[21];
float price; int quantity;
sscanf(buf, "%20[^,],%20[^,],%20[^,],%d,%f",
name, vendor, sku, &quantity, &price);
#)// 格式:name#sku#price#quantity#category#vendor
sscanf(buf, "%20[^#]#%20[^#]#%f#%d#%20[^#]#%20[^\n]",
name, sku, &price, &quantity, category, vendor);
char log[] = "<14>2002-11-11 12:12:12 11.22.33.44 3 3 aaaa aaaaaa";
int a1, a5, a6; char str2[20], str3[20], str4[20], str7[20];
sscanf(log, "<%d>%s %s %s %d %d %s", &a1, str2, str3, str4, &a5, &a6, str7);
* 抑制賦值)sscanf(input, "%d %x %5[a-z] %*s %f", &i, &j, s, s);
// %*s 讀取並丟棄 "bbbbbbbb"
sscanf(str, "%[^ ] %[^ ] %[^$]", a, b, c); 以空格、$ 為界。sprintf,但需指定緩衝區上限,更安全。
time_t t = time(0);
char s[32];
strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t));
CString::Format、CTime::Format 提供物件導向介面,碼風更優雅。sprintf 與 sscanf 是 C 語言格式化 I/O 的雙子星。掌握格式字串語法(%[flags][width][.precision][length]specifier)、理解變參類型提升規則、善用動態寬度與掃描集,並時刻警惕緩衝區溢出與參數錯位,方能寫出健壯、高效的程式碼。實戰中,優先考慮 snprintf / sscanf_s 等安全版本,或使用 C++ std::format(C++20)等現代替代方案。