Функции форматированного ввода/вывода: printf и scanf

Очень многие не знают о существовании в языке C стандартных функций форматированного ввода, и особенно вывода данных. Простейший вывод числа в строчку порой превращается в по-настоящему адовые функции с кромсанием числа на разряды (обычно неэффективно реализованным), выделением памяти под строку и прочими ночными кошмарами.

Но всё это реализовано уже очень давно. Страшно сказать, но функция printf празднует в следующем году 50-летний юбилей! Да этих функций и не могло не быть — на заре развития компьютеров и компиляторов единственным средством общения программы с человеком был текстовый ввод-вывод.

Эти функции лежат в библиотеке stdio, подключим её:

#include "stdio.h"

Итак, функция которая понадобится вам чаще всего — sprintf. Она принимает:

  1. указатель на строку, в которую будет помещён результат (char *s)
  2. строку форматирования, например «напряжение: %d, ток: %d»
  3. перечень параметров, в этом примере — два целых числа. Здесь используются variable arguments.

sprintf работает как шаблонизатор: она читает строку форматирования, находит в ней идентификаторы, подставляет на их место значения переданных параметров и выводит результат в *s.

В строке форматирования может быть несколько идентификаторов, тогда в качестве параметров должно быть передано такое же количество аргументов, и конечно они должны чётко соответствовать типу, указанному в идентификаторе.

Самые частые случаи

Чаще всего вам понадобятся:

  • %d — вывод целого числа: sprintf(s, «масса: %d грамм», 2358) => «масса: 2358 грамм»
  • %f — вывод дробного числа: sprintf(s, «пинг: %f секунды», 1.432) => «пинг: 1.432 секунды»
  • %s — вывод строки: sprintf(s, «json: {\»name\»: \»%s\»}», «joe») => «json: {«name»: «joe»}»
  • %02X — вывод байта в виде hex: sprintf(s, «CRC: %02X %02X», 0x43, 0xfe) => «CRC: 43 FE»

Некоторые символы, такие как «, нужно экранировать слешом: \»

Можно добавлять управляющие последовательности, например \n — перевод строки.

Описание

Теперь подробнее.

Каждый идентификатор начинается с символа «%», и сообщает всё о переменной, значение которой нужно подставить: её тип и модификаторы (ширина, точность, размер). Формат идентификатора:

%[флаги][ширина][.точность][размер]тип

Ни один из элементов не является обязательным, кроме типа. Часть элементов относятся только к некоторым типам, например ширина, точность и размер — только к числовым типам.

Между элементами могут стоять пробелы, но делать так нельзя — резко увеличивается вероятность ошибки.

Числовые целые типы

%d/%i, %u, %o, %x/%X — всё это типы целых чисел int (т.е. оно имеет длину равную разрядности платформы).

%d и %i — десятичное целое знаковое число, минус ставится только у отрицательных чисел.

%u — десятичное целое беззнаковое число.

%o — восьмеричное представление int.

%x — шестнадцатиричное представление int в строчном формате: 0..9, a..f.

%X — шестнадцатиричное представление int в прописном формате: 0..9, A..F.

К ним можно применить модификаторы размера:

%l — число интерпретируется как long int = int16_t

%ll — long long int = int32_t

%h — short int

%hh — char

Флаги:

%0d — вывод ведущих нулей, дополняя число до длины, указанной в поле «ширина»

%-d — выравнивание числа по левому краю

И размерность:

%2d — длина числа минимум 2 символа. Если меньше — оно будет дополнено слева пробелами (либо нулями, если есть флаг 0)

Я очень часто использую идентификатор %02X для вывода шестнадцатиричных чисел в пакетах данных, например:

sprintf(s, «%02X %02X %02X», 0xb3, 0x54, 0xaf) => «B3 54 AF»

Типы с плавающей точкой

%f, %e

%f — float

%e — экспоненциальная запись

Модификатор размера только один:

%lf — double

К ним можно применить те же самые флаги 0 и -, а также можно указать ширину и точность:

%.2f — выведет float с двумя знаками после запятой. Если они равны 0 — они всё равно будут выведены, поэтому так можно делать красиво выровненные таблички.

%10.2f — как и в прошлой главе, дополнит число нулями или пробелами до длины 10 символов.

Символы

%c выводит символ с кодом, переданным в качестве параметра.

Строки

%s выводит переданную строку, вплоть до её терминирующего нуля.

Особые типы

%n записывает в переданную переменную счётчик символов — количество символов, которое было выведено на момент появления этого идентификатора.

%p выводит адрес указателя.

%% выводит символ «%»

Варианты функций printf

Мы уже рассмотрели функцию sprintf, как наиболее полезную при программировании под МК — она выводит текст в переданный указатель на строку. Но это не единственная функция из семейства:

  • printf(char *format, …) — выводит текст в стандартный вывод (STDIO). Больше применима при программировании для компьютера, но в принципе вы можете переопределить встроенную функцию putc, чтобы она выводила символ как-то по-другому — и сможете, например, выводить через printf напрямую на ЖК-экран.
  • snprintf(char *s, int n, char *format, …) — ограничивает количество выведенных символов параметром n, непоместившиеся символы будут потеряны. Очень полезно, если у вас есть какой-то накопительный буфер, который нельзя переполнять.
  • fprintf(FILE *f, char *format, …) — выводит текст в файл. Опять же, в основном используется на ПК, но опять же вы можете переопределить её и, например, выводить данные в SD-карту.
  • vprintf(char *format, va_list va) — использует перечень аргументов variable arguments. Это просто незаменимая фича при написании своей обёртки вокруг printf.

Все функции вида printf возвращают количество записанных символов.

scanf

Функция, обратная sprintf — это sscanf. Она принимает строку с данными и текстом, пытается её разобрать в соответствии со строкой форматирования, и записывает найденные данные по переданным адресам. Простейший пример:

float lat, lon;
sscanf("GPS lat: 63.321, lon: 37.867", "GPS lat: %f, lon: %f", &lat, &lon);

Здесь действуют все те же самые правила составления идентификаторов, что и в printf, и есть все те же самые функции вроде scanf, fscanf, sscanf.

Ссылка на основную публикацию