Функция printf() для форматированного вывода

Практический курс по C/C++: https://stepik.org/course/193691

На этом занятии речь пойдет о функции printf(), с которой нам ранее уже приходилось сталкиваться, начиная с самой первой программы. Как я отмечал, название функции – это сокращение от английских слов:

print formatted (форматированный вывод)

А само определение (прототип) функции следующее:

int printf(const char* format, …);

Первый параметр format – это указатель на строку. Пока его можно воспринимать просто как строку, в которой определен формат для вывода информации в стандартный поток stdout. А троеточие определяет произвольное число дополнительных параметров. Обычно, это переменные или выражения, значения которых следует выводить в заданном формате. В качестве возвращаемого типа указан int, то есть функция возвращает целое число. Это число соответствует количеству выведенных символов в стандартный поток stdout (в нашем случае на экран). Обычно, на практике, этим значением пренебрегают и вызывают функцию printf(), как говорят, ради побочного эффекта, т.е. ради передачи данных в выходной поток.

В самом простом варианте мы с вами использовали эту функцию для вывода строки на экран:

#include <stdio.h>
 
int main(void)
{
    printf("Hello, World!\n");
    return 0;
}

Но, как вы уже догадались, функция printf() способна на гораздо большее! На этом занятии мы рассмотрим лишь основные ее возможности, которые наиболее часто применяются на практике. Если вам понадобится больше информации, то справочные руководства всегда к вашим услугам.

Итак, первый параметр format функции printf() определяет не просто выводимую строку, а формат выдаваемых данных. И для этого в нашем распоряжении имеются, так называемые, спецификаторы преобразования. Основные из них следующие.

Спецификатор

Описание

%d или %i

Целое число со знаком в десятичной форме.

%u

Целое беззнаковое (unsigned) число в десятичной форме.

%o

Целое беззнаковое (unsigned) число в восьмеричной форме.

%x или %X

Целое беззнаковое (unsigned) число в шестнадцатеричной форме.

%f или %F

Вещественное число в виде десятичной дроби.

%e или %E

Вещественное число в экспоненциальной форме.

%c

Символ в соответствии с текущей кодовой таблицей.

%s

Строка (последовательность символов).

%%

Запись символа % в форматной строке.

В действительности существуют и другие спецификаторы, но мы остановимся только на этих. Также спецификатор %s для вывода строк рассмотрим позже, когда будем рассматривать строки.

Итак, давайте посмотрим, как можно воспользоваться этими спецификаторами преобразования. Пусть у нас в программе определена целочисленная переменная var_i с начальным значением 1208:

int var_i = 1208;

И мы бы хотели вывести ее значение на экран в виде десятичного числа с помощью функции printf(). Из таблицы видим, что для этого подходит спецификатор %d. Им и воспользуемся, следующим образом:

printf("value = %d\n", var_i);

После запуска программы на экран будет выведено:

value = 1208

Почему так и как это работает? Сморите, вначале функция printf() выполняет анализ форматной строки. В некотором смысле она работает как ее интерпретатор. Как только в строке встречается символ %, скорее всего, это некоторый спецификатор. И, действительно, дальше идет символ d, функция «понимает», что это спецификатор %d, на место которого нужно подставить значение переменной в виде целого десятичного числа со знаком (если он есть; знак + не ставится). При этом берется первая переменная, записанная после форматной строки. В нашем случае – это var_i со значением 1208. В результате получаем итоговую строку «value = 1208».

А теперь, давайте, вместо спецификатора %d запишем, например, спецификатор %x:

printf("value = %x\n", var_i);

В результате будет выведена строка:

value = 4b8

То есть, спецификатор %x преобразовывает данные из переменной var_i в шестнадцатеричный формат и подставляет на свое место. И так работает каждый спецификатор. Даже если прописать:

printf("value = %f\n", var_i);

для вывода значение переменной var_i как вещественное число, то ошибки никакой не будет, но отображаемое значение будет, конечно же, некорректным:

value = 0.000000

Но, если изменить тип переменной var_i на вещественный:

double var_i = 1208;

то увидим правильное значение:

value = 1208.000000

Суффиксы типов для спецификаторов

Этот пример показывает, что спецификаторы должны быть согласованы с типами данных переменных или выражений. Причем, целочисленные типы char и short, при передаче значений функции printf(), автоматически приводятся к типу int. Поэтому спецификатор %d охватывает все три типа: char, short, int. Если же переменная имеет тип long или long long, то перед спецификаторами допустимо прописывать малые буквы l и ll соответственно:

  • l – суффикс для типов long или unsigned long;
  • ll – суффикс для типов long long или unsigned long long.

Так как в моей версии компилятора типы long и int по размеру совпадают и имеют 4 байта (32 бита), то приведу пример с типом long long, который занимает 8 байтов (64 бита):

printf("value = %lld\n", -12345678901234LL);
printf("value = %llu\n", 12345678901234LL);

Если при выводе в спецификаторах убрать буквы ll, то значение будет отображено некорректно:

printf("value = %d\n", -12345678901234LL);

Увидим:

value = -1942892530

Аналогично и с вещественными типами: float автоматически преобразовывается в double, поэтому спецификаторы %f, %F, %e и %E корректно обрабатывают оба из них. А вот для типа long double следует прописывать суффикс в виде заглавной буквы L:

  • L – суффикс для типа long double.

Например:

long double var_ld = 0.5;
printf("value = %Lf\n", var_ld);

Увидим строку:

value = 0.500000

Но, при использовании спецификатора без суффикса L, вывод окажется некорректным:

printf("value = %f\n", var_ld);

На выходе:

value = -0.000000

Так с помощью суффиксов учитываются все базовые типы переменных языка Си.

Вывод нескольких переменных

Конечно, с помощью функции printf() можно выводить сразу несколько переменных в одной форматной строке. Делается это очевидным образом. Достаточно указать нужное количество спецификаторов, а затем, перечислить такое же количество переменных или выражений. Например, для вывода трех переменных разного типа:

short var_h = 100;
int var_i = 1024;
long double var_ld = 0.5;

Функцию printf() можно записать в виде:

printf("var_h = %d, var_i = %d, %Lf\n", var_h, var_i, var_ld);

Так как тип short преобразуется в тип int, то для переменных var_h и var_i допустимо использовать спецификатор %d. А вот для переменной var_ld типа long double обязательно следует использовать суффикс L перед спецификатором вещественного типа. В результате обработки этой форматной строки слева-направо, функция printf() вместо первого спецификатора %d подставит значение из переменной var_h; вместо второго %d – значение из переменной var_i; вместо третьего %Lf – значение из переменной var_ld. И этот порядок всегда такой: в n-й по счету спецификатор подставляется n-я по счету переменная или выражение. В результате, получим вывод в виде строки:

var_h = 100, var_i = 1024, 0.500000

Флаги спецификаторов

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

Флаг

Описание

Десятичное число

Задает минимальную ширину поля выводимой информации.

Знак «-»

Задает выравнивание по левому краю в пределах спецификатора.

Знак «+»

Задает отображение чисел со знаками «+» и «-».

Цифра 0

Заполнение нулями свободной области в пределах спецификатора.

Пробел

Задает отображение положительных чисел с пробелом в начале и со знаком «-» для отрицательных.

Символ «#»

Задает отображение префикса числа (0x – для шестнадцатеричных; 0 – для восьмеричных).

Давайте посмотрим на конкретных примерах, как можно использовать эти флаги по отдельности и в комбинации с другими флагами. Объявим в программе две переменные var_i и var_d с некоторыми начальными значениями, а затем выведем их в столбик с шириной поля 10:

#include <stdio.h>
 
int main(void)
{
    int var_i = -1283;
    double var_d = 54.34675;
 
    printf("[%10d]\n", var_i);
    printf("[%10f]\n", var_d);
    return 0;
}

Я здесь для наглядности прописал две функции printf(), хотя, можно было бы обойтись и одним вызовом. Квадратные скобки приведены для обозначения границ полей спецификаторов. После запуска программы увидим:

[     -1283]
[ 54.346750]

В обоих случаях ширина равна 10 символов и выводимые данные выровнены по правому краю. Можно изменить это поведение и сделать выравнивание по левому краю следующим образом:

printf("[%-10d]\n", var_i);
printf("[%-10f]\n", var_d);

Увидим строчки:

[-1283     ]
[54.346750 ]

Если указанная ширина поля будет меньше размера выводимых данных, то она автоматически будет увеличена до нужных размеров. Например:

printf("[%-5d]\n", var_i);
printf("[%-5f]\n", var_d);

Увидим строчки:

[-1283]
[54.346750]

Если вместо целого числа указывать дробное, то в случае с целыми числами число до точки (12) будет задавать общую ширину вывода, а число после точки (7) – ширину отображаемых данных, которая дополняется нулями:

printf("[%12.7d]\n", var_i);
printf("[%12.2f]\n", var_d);

В случае с вещественными числами, значение 12 определяет общую минимальную ширину вывода, а число после точки (2) – точность выводимого значения. После запуска программы увидим:

[    -0001283]
[       54.35]

Или в таком виде:

printf("[%.7d]\n", var_i);
printf("[%.2f]\n", var_d);

Получим вывод:

[-0001283]
[54.35]

Следующие два флага # и + работают следующим образом:

printf("[%#X]\n", var_i);
printf("[%+.2f]\n", var_d);

Получим:

[0XFFFFFAFD]
[+54.35]

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

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме