编写类似 printf 的函数
可变参数
编写类似 printf 的函数 是 CoddyKit 上的免费 C Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C Academy 课程共包含 4 节课。
接收格式字符串的函数
可变参数最强大的用途,是构建能够解析格式字符串的 printf 风格函数。
格式字符串会告诉函数后面有多少个参数,以及每个参数的类型。
#include <stdio.h>
#include <stdarg.h>
void myprint(const char *fmt, ...) {
printf("format was: %s\n", fmt);
}
int main(void) {
myprint("%d items\n", 5);
return 0;
}遍历格式字符串
逐个字符扫描格式字符串。当看到 % 时,下一个字符就是转换说明符,它会告诉您要读取什么类型。
其他所有内容都按字面原样打印。
#include <stdio.h>
#include <stdarg.h>
void scan(const char *fmt) {
for (const char *p = fmt; *p; p++) {
if (*p == '%') printf("[spec:%c]", *(p+1));
else putchar(*p);
}
putchar('\n');
}
int main(void) {
scan("x=%d y=%s");
return 0;
}读取匹配的类型
当说明符为 %d 时,读取一个 int。对于 %s,读取一个 char *。格式必须与实际参数完全匹配。
#include <stdio.h>
#include <stdarg.h>
void mini(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p; p++) {
if (*p == '%' && *(p+1) == 'd') {
printf("%d", va_arg(args, int));
p++;
} else putchar(*p);
}
va_end(args);
}
int main(void) {
mini("count=%d\n", 42);
return 0;
}处理多个说明符
扩展扫描器,使其能够处理多种转换。为 %d、%s 和 %c 添加分支,每次读取正确的类型。
#include <stdio.h>
#include <stdarg.h>
void fmt_print(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p; p++) {
if (*p != '%') { putchar(*p); continue; }
p++;
if (*p == 'd') printf("%d", va_arg(args, int));
else if (*p == 's') printf("%s", va_arg(args, char *));
else if (*p == 'c') putchar(va_arg(args, int));
}
va_end(args);
}
int main(void) {
fmt_print("%s is %d\n", "age", 30);
return 0;
}转义百分号
真正的格式化函数会将 %% 视为字面百分号。添加相应分支,以便用户打印实际的 %。
#include <stdio.h>
#include <stdarg.h>
void fp(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
for (const char *p = fmt; *p; p++) {
if (*p == '%') {
p++;
if (*p == '%') putchar('%');
else if (*p == 'd') printf("%d", va_arg(args, int));
} else putchar(*p);
}
va_end(args);
}
int main(void) {
fp("%d%% done\n", 75);
return 0;
}转发给 vprintf
很多时候,您并不想重新实现格式化功能。标准库提供了 vprintf,它可以直接接收 va_list。
您的包装函数只需将参数继续传递下去即可。
#include <stdio.h>
#include <stdarg.h>
void logline(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
int main(void) {
logline("value = %d, name = %s\n", 7, "box");
return 0;
}添加前缀
使用 vprintf 方法,可以轻松地在标准格式化功能周围添加行为,例如在每条消息前打印一个标签。
#include <stdio.h>
#include <stdarg.h>
void info(const char *fmt, ...) {
printf("[INFO] ");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
int main(void) {
info("server started on port %d\n", 8080);
return 0;
}使用 vsnprintf 写入缓冲区
如果要将内容格式化到字符串中而不是直接打印,请使用 vsnprintf(buf, size, fmt, args)。
它受 size 限制,可以防止溢出,并且始终会添加空终止符。
#include <stdio.h>
#include <stdarg.h>
void format_into(char *buf, size_t n, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vsnprintf(buf, n, fmt, args);
va_end(args);
}
int main(void) {
char out[64];
format_into(out, sizeof(out), "id-%d", 123);
printf("%s\n", out);
return 0;
}返回长度
vsnprintf 返回本来会写入的字符数。您可以利用这一点检测截断情况或确定缓冲区大小。
#include <stdio.h>
#include <stdarg.h>
int build(char *buf, size_t n, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int len = vsnprintf(buf, n, fmt, args);
va_end(args);
return len;
}
int main(void) {
char b[10];
int needed = build(b, sizeof(b), "%d-%d", 11, 22);
printf("%s (needed %d)\n", b, needed);
return 0;
}选择实现方式
有两种策略:自行解析格式字符串以获得完全控制,或者转发给 vprintf/vsnprintf,以获得正确性并减少代码量。
对于大多数实际代码,转发更加安全、简单。
#include <stdio.h>
#include <stdarg.h>
void say(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stdout, fmt, args);
va_end(args);
}
int main(void) {
say("%s = %.1f\n", "pi", 3.14);
return 0;
}不匹配会导致未定义行为
如果格式字符串指定的类型与实际参数不匹配,行为就是未定义的。
printf("%d", "text") 可能会崩溃。请始终确保格式和参数保持同步。
#include <stdio.h>
int main(void) {
int n = 5;
printf("%d\n", n);
return 0;
}快速检查
测试您对 printf 风格包装函数的理解。
回顾
您学习了如何构建 printf 风格函数:
- 将格式字符串作为命名参数,然后使用
...。 - 您可以自行解析
%说明符并读取匹配的类型,或者 - 将
va_list转发给 vprintf / vsnprintf,以进行安全、正确的格式化。 - 保持格式与参数类型同步,避免未定义行为。
常见问题解答
「编写类似 printf 的函数」课时是免费的吗?
是的 — 「编写类似 printf 的函数」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C Academy 课程的其余内容,请升级到 CoddyKit PRO。 C Academy 课程共包含 4 节课。
「编写类似 printf 的函数」这节课中我会学到什么?
可变参数 你通过在浏览器中直接运行的动手代码来练习 C Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「编写类似 printf 的函数」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C Academy 课中编写并运行代码吗?
能。每节 C Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。