函數族對比
| 函數 | 輸出目標 | 輸入源 | 返回值 |
printf | 標準輸出 | — | 輸出字符數 |
sprintf | 字符串緩衝區 | — | 寫入字符數 |
scanf | — | 標準輸入 | 成功賦值項數 |
sscanf | — | 字符串緩衝區 | 成功賦值項數 |
sprintf 核心功能與實例
1. 數字格式化
sprintf(s, "%-8X", 12345); // "12345 " 左對齊 16 進制大寫
sprintf(s, "%08X", 4567); // "000011D7" 左補零
sprintf(s, "%10.3f", 3.14159); // " 3.142" 寬度 10,保留 3 位小數
sprintf(s, "%-10.3f", 3.14159);// "3.142 " 左對齊
2. 字符串連接與截取
// 直接連接
sprintf(dest, "%.5s%.4s", src1, src2); // "abcde1234"
// 指定長度截取連接
sprintf(dest, "%.*s%.*s", 2, src1, 3, src2); // "ab123"
// 動態長度
sprintf(dest, "%.*s%.*s", len1, src1, len2, src2);
3. 字符/ASCII 碼對照
for (int i=32; i<127; i++)
printf("[ %c ]: %3d 0x%#04X\n", i, i, i); // # 標誌產生 0X 前綴
4. 地址打印
sprintf(s, "%p", &i); // 標準指針格式
sprintf(s, "%08X", (unsigned)&i); // 手動 16 進制
sprintf(s, "%0*x", 2*sizeof(void*), &i); // 等價寫法
5. 利用返回值避免 strlen
int len = sprintf(s, "%d", i); // 直接獲得字符串長度
6. 類型安全陷阱
short si = -1;
sprintf(s, "%04X", si); // 錯誤:輸出 "FFFFFFFF"(符號擴展)
sprintf(s, "%04X", (unsigned short)si); // 正確:輸出 "FFFF"
int i = 100;
sprintf(s, "%.2f", i); // 錯誤:整數按浮點解讀
sprintf(s, "%.2f", (double)i); // 正確:輸出 "100.00"
sscanf 進階掃描集用法
掃描集 %[] 語法
%[a-z]:讀取 a-z 字符,遇其他停止
%[^a-z]:讀取非 a-z 字符,遇 a-z 停止
%[a-z,A-Z,0-9]:多範圍組合
%*[^=]:* 忽略賦值,跳過匹配內容
實戰案例:URL 解析
const char *url = "https://www.baidu.com:1234";
char protocol[32], host[128], port[8];
sscanf(url, "%[^:]://%[^:]:%[1-9]", protocol, host, port);
// protocol="http", host="www.baidu.com", port="1234"
其他常用模式
// 讀取固定長度
sscanf(s, "%40c", buf);
// 讀取到指定字符
char s[] = "notepad=1.0.0.1001";
char name[32];
sscanf(s, "%[^=]", name); // "notepad"
sscanf(s, "%*[^=]=%s", name); // "1.0.0.1001" (跳過前綴)
// 多字符分隔符
sscanf(s, "%[^=:]", name); // 支持 = 或 : 分隔
scanf/sscanf 返回值處理
int cnt = sscanf(str, "%d %s", &a, buf);
if (cnt == 2) { /* 成功 */ }
else if (cnt == EOF) { /* 輸入結束 */ }
else { /* 格式不匹配 */ }
安全版本:snprintf
// 防止緩衝區溢出
int snprintf(char *buf, size_t n, const char *fmt, ...);
// 最多寫入 n-1 字符,自動添加 '\0'
綜合實戰:IP/MAC 地址轉換
// IP 字符串 → 整數
uint32_t ipstr2int(const char *ipstr) {
uint a,b,c,d;
sscanf(ipstr, "%u.%u.%u.%u", &a,&b,&c,&d);
return (a<<24) | (b<<16) | (c<<8) | d;
}
// 整數 → IP 字符串(處理大小端)
char* int2ipstr(uint32_t ip, char *buf) {
if (is_little_endian()) {
sprintf(buf, "%u.%u.%u.%u",
(uchar)*((uchar*)&ip+3),
(uchar)*((uchar*)&ip+2),
(uchar)*((uchar*)&ip+1),
(uchar)*((uchar*)&ip+0));
} else {
sprintf(buf, "%u.%u.%u.%u",
(uchar)*((uchar*)&ip+0),
(uchar)*((uchar*)&ip+1),
(uchar)*((uchar*)&ip+2),
(uchar)*((uchar*)&ip+3));
}
return buf;
}
// MAC 字節數組 → 字符串
char* mac2str(const uchar *mac, char *buf) {
sprintf(buf, "%02X-%02X-%02X-%02X-%02X-%02X",
mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
return buf;
}
最佳實踐總結
- 優先用 snprintf:防止緩衝區溢出
- 類型匹配嚴格:格式符與實參類型必須一致
- 善用掃描集:
%[] 比手動解析高效安全
- 檢查返回值:確認解析/格式化成功項數
- 避免格式化字符串漏洞:
printf(user_input) → printf("%s", user_input)
- 大小端敏感操作:網絡序用
htonl/ntohl