printf 是 C 標準庫最複雜的函數之一,但其核心格式化邏輯可通過遞歸分解與位運算在百行代碼內實現。本文實現支持 %d(十進制)、%x/%p(十六進制)、%c(字符)、%s(字符串)的精簡版 my_printf,揭示格式化輸出的底層邏輯。
所有高級輸出最終歸歸於單字符輸出 putchar(或 write(1, &ch, 1))。
static void print_dec(unsigned int n) {
if (n >= 10) {
print_dec(n / 10); // 遞歸處理高位
n %= 10;
}
putchar('0' + n); // 輸出當前最低位
}
遞歸深度 = 位數(32位 int 最大 10 層),棧開銷極小。
static void print_hex(unsigned int n) {
putchar('0'); putchar('x'); // 0x 前綴
for (int i = 7; i >= 0; i--) { // 32位 = 8個十六進制位
unsigned char nibble = (n >> (i * 4)) & 0xF;
putchar(nibble < 10 ? '0' + nibble : 'a' + nibble - 10);
}
}
static void print_char(char ch) { putchar(ch); }
static void print_str(const char* s) {
while (*s) putchar(*s++);
}
void my_printf(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
for (const char* p = fmt; *p; p++) {
if (*p != '%') {
putchar(*p);
continue;
}
p++; // 解析格式字符
switch (*p) {
case 'd': {
int val = va_arg(ap, int);
if (val < 0) { putchar('-'); print_dec(-val); }
else print_dec(val);
break;
}
case 'x':
case 'p': {
unsigned int val = va_arg(ap, unsigned int);
print_hex(val);
break;
}
case 'c': {
char ch = (char)va_arg(ap, int); // char 提升為 int
print_char(ch);
break;
}
case 's': {
const char* s = va_arg(ap, const char*);
print_str(s);
break;
}
case '%':
putchar('%');
break;
default:
putchar('%'); putchar(*p);
}
}
va_end(ap);
}
int main() {
my_printf("dec: %d\n", 12345);
my_printf("hex: %x\n", 0xABCD);
my_printf("ptr: %p\n", (void*)0x7FFFABCD);
my_printf("char: %c\n", 'Z');
my_printf("str: %s\n", "Hello, World!");
my_printf("mix: %d %s %x\n", 42, "test", 0xFF);
return 0;
}
dec: 12345 hex: 0xabcd ptr: 0x7fffabcd char: Z str: Hello, World! mix: 42 test ff
| 特性 | 實現要點 |
|---|---|
寬度/精度 %10d %.2f | 解析 * 或數字,填充空格或 0 |
標志 %-+ #0 | 左對齊、強制符號、替代形式、零填充 |
長度修飾 hh/h/l/ll/j/z/t | 對應 char/short/long/long long/intmax_t/size_t/ptrdiff_t |
浮點數 %f/%e/%g/%a | 需實現浮點轉字符串算法(Dragon4/Grisu3) |
自定義類型 %n %m | %n 寫入已輸出字符數,%m 輸出 errno 字符串 |