%d(有符號十進制)、%s(字符串)格式。<stdarg.h> 處理可變參數。itoa(整數轉字符串),不依賴庫函數。
#include <stdio.h>
#include <stdarg.h>
/* 簡易 itoa:支援 2-36 進制,處理負數 */
char* my_itoa(int val, char* buf, int radix) {
char* p = buf;
char* first = buf;
char temp;
int digit;
if (val < 0 && radix == 10) {
*p++ = '-';
val = -val;
first = p;
}
do {
digit = val % radix;
val /= radix;
*p++ = (digit > 9) ? (char)(digit - 10 + 'A') : (char)(digit + '0');
} while (val > 0);
*p-- = '\0';
// 反轉字符串(first 到 p)
while (first < p) {
temp = *p; *p = *first; *first = temp;
--p; ++first;
}
return buf;
}
/* 簡易 printf */
void my_printf(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
while (*fmt) {
if (*fmt != '%') {
putchar(*fmt++);
continue;
}
switch (*++fmt) {
case 's': {
const char* s = va_arg(ap, const char*);
while (*s) putchar(*s++);
break;
}
case 'd': {
int d = va_arg(ap, int);
char buf[16];
my_itoa(d, buf, 10);
for (char* s = buf; *s; ++s) putchar(*s);
break;
}
default:
putchar(*fmt); // 未知格式原樣輸出
break;
}
++fmt;
}
va_end(ap);
}
int main() {
int a = 154;
char* p = "adasd";
my_printf("%d\t%s\n", a, p);
my_printf("%d\t%s\t%s\n", 12, "abcde", "a");
return 0;
}
<stdarg.h>)va_list ap:參數列表指針。va_start(ap, last_fixed):初始化,定位到第一個可變參數。va_arg(ap, type):按類型取參數並移動指針。va_end(ap):清理(某些平台需調用)。%c 則根據 c 分派。d/s,易擴展 x/o/c/f 等。printf 還支援寬度、精度、標誌(%-10.5d),實現複雜度指數級上升。'-',並取絕對值(注意 INT_MIN 絕對值溢出,實際應用需用 unsigned 處理)。%、/ 得到低位到高位,寫入緩衝區。radix 參數化,2-36 進制通用。%x(十六進制)、%o(八進制)、%c(字符)、%p(指針)。%10d、精度 %.5d、標誌 %-+0。printf 行為),需在每次 putchar 計數。vsprintf / snprintf 將結果寫入緩衝區而非 stdout。%f 需自行實現 dtoa(Gruson/Ryu 算法),難度較大。| 特性 | 本簡易版 | glibc/MSVC printf |
|---|---|---|
| 格式支援 | %d %s | 完整 C99/C11 |
| 線程安全 | 否 | 是(鎖/線程局部存儲) |
| 浮點數 | 無 | Ryu/Gruson 高性能算法 |
| 本地化 | 無 | 支援 locale 十進制點等 |
| 返回值 | 無 | 輸出字符數 |
手寫 printf 是理解可變參數、格式解析、數字轉字符串、字符輸出等底層機制的最佳練習。雖然生產代碼直接用標準庫,但掌握其原理有助於調試格式化漏洞、實現嵌入式無標準庫環境下的輸出、編寫自定義日誌框架等。