Написание функций, подобных printf
Переменное число аргументов
«Написание функций, подобных printf» — бесплатный урок C Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения 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» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C Academy, подпишись на CoddyKit PRO. Курс C Academy содержит 4 уроков всего.
Чему я научусь в уроке «Написание функций, подобных printf»?
Переменное число аргументов Ты практикуешь C Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C Academy?
Предыдущий опыт не требуется. C Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Написание функций, подобных printf»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C Academy?
Да. Каждый урок C Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Макросы stdarg
- Написание функций, подобных printf
- Проблемы безопасности типов
- Практические примеры