0Pricing
C Academy · Урок

Написание функций, подобных 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 — локальная установка не требуется.

Все уроки этого курса

  1. Макросы stdarg
  2. Написание функций, подобных printf
  3. Проблемы безопасности типов
  4. Практические примеры
← Назад к C Academy