Главная > Уроки > Форматированный вывод. Функция printf

Записывайся на этот курс на Stepike!

Форматированный вывод. Функция printf

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

Общий синтаксис функции printf следующий:

Общий синтаксис функции printf

Рис.1. Общий синтаксис функции printf.

У функции printf есть один обязательный параметр – строка, заключенная в двойные кавычки. Эту строку еще называют формат-строкой.

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

Кроме обязательной строки форматирования есть и необязательные параметры. Они пишутся через запятую после формат-строки.

Формат-строка.

Любой символ в формат-строке относится к одной из следующих групп:

  • символы, которые выводятся на экран без изменений
  • escape-последовательности
  • спецификаторы формата

Еscape-последовательности

С этой группой символов мы уже встречались в первом уроке. Символ \n. Он, как вы наверное помните, переносит выводимый текст на новую строку. Есть и другие эскейп-последовательности (иногда можно встретить название управляющие последовательности). Любая такая последовательность начинается с символа обратный слеш \.

Часто используемые escape-последовательности:

\n     - новая строка
\t     - горизонтальная табуляция. Сдвигает выводимые данные вправо на ближайшую позицию табуляции. Обычно используется для выравнивания текста внутри строки.
\'     - вывод символа '
\"     - вывод символа "
\\     - вывод символа \

Как видите, последние три последовательности нужны лишь для того, чтобы вывести на экран символы «"», «'» и «\». Дело в том, что если эти символы просто записать в формат-строку, то они не отобразятся на экране, а в некоторых случаях программа и вовсе не скомпилируется.

Интерес представляет символ ?. Вообще говоря, для него также имеется escape-последовательность \?. При этом, вывести данный символ можно и без добавления обратного слеша.

Следующая программа иллюстрирует работу escape-последовательностей.

Листинг 1.

#include <stdio.h> 
int main(void){
	printf("What is your name\?\n\tMy name\'s Bond! James Bond!\n");
	return 0;
}

Хотя escape-последовательности состоят из нескольких символов, но в потоке вывода они воспринимаются как цельный символ, который имеет своё собственное значение.

Итого, используя управляющие последовательности мы можем влиять на то, как данные будут выводиться на экране.

Спецификаторы формата.

Спецификаторы формата всегда начинаются с символа %, и предназначены для вывода на экран значений переменных и выражений.

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

Основные спецификаторы формата:

%d, %i  - целые числа
%f, %g  - вещественные числа
%c         - символы

Есть и другие спецификаторы формата. Мы познакомимся с ними тогда, когда они нам понадобятся.

Сами спецификаторы формата на экран не выводятся. Вместо них выводятся данные, которые передаются в функцию printf после строки форматирования.

Функция printf работает следующим образом. Все символы, заключенные в двойные кавычки, кроме управляющих последовательностей и спецификаторов формата, выводятся на экран. Спецификаторы формата во время вывода заменяются на значения, указанные после формат-строки. Причем, если используется несколько спецификаторов формата, то первый спецификатор заменяется на первое значение, расположенное после формат строки, второй – на второе, и т.д.

Посмотрим на примерах.

Листинг 2.

printf("%d\t%d\n%d",10,20,30); 
Вывод Листинг 2.

Рис.2 Вывод Листинг 2.

Листинг 3.

printf("pervoe slagaemoe: %d\nvtoroe slagaemoe:%d\nsumma: %d\n",10,20,30); 
Вывод Листинг 3.

Рис.3 Вывод Листинг 3.

Листинг 4.

printf("%d + %d = %d\n", 20, 10, 20+10);
Вывод Листинг 4.

Рис.4 Вывод Листинг 4.

Листинг 5.

printf("%d + %d %c %d\n", 10, 20, '=', 20+10);
Вывод Листинг 5.

Рис.5 Вывод Листинг 5.

На следующей картинке показан принцип работы функции printf.

Принцип работы функции printf.

Рис.6 Принцип работы функции printf.

По сути, формат строка задаёт некоторый трафарет(шаблон), в который подставляются данные для вывода, в том порядке, в котором они указаны.

Два основных правила, которые нужно соблюдать при работе с функцией printf:

  • количество спецификаторов формата должно совпадать с количеством данных для вывода
  • спецификаторы формата должны точно соответствовать типам выводимых данных

Пара примеров неправильного использования функции printf.

Листинг 6.

#include <stdio.h> 
int main(void){
	int z = 4;
	float b = 5.4;
	printf("%f\n",z); // нарушено  2 правило
	// переменная z целого типа, а команда форматирования %f  предназначена для 
	// вывода переменных типа float
	printf("%d\n",z, b); //нарушено 1 правило
	// нет команды форматирования для переменной b.
	return 0;
}

Напишем небольшую программу, которая иллюстрирует использование спецификаторов формата.

Листинг 7.

#include <stdio.h> 
int main(void){
  int a = 25;
  float b = 23.4;
  double c = 217.876;
  char d = 's';
  
  printf("Cheloe %d\n",a);
  printf("Odin simvol %c\n",d);
  printf("Vewestvennoe %f %g\n",b,c);

  return 0;
}

Модификаторы формата.

Обратите внимание, что вещественные числа через спецификатор %f выведены с шестью знаками после запятой. У вас на компьютере может быть выведено как больше, так и меньше знаков. Обычно такая точность не нужна. К счастью, этим можно управлять. Для этого предназначены модификаторы формата.

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

Модификатор формата

Рис.7 Модификатор формата

Первое число обозначает ширину поля, выделяемого для записи числа. Второе число обозначает точность, с которой мы хотим вывести данное вещественное число.

В примере на картинке под вещественное число мы выделяем 8 символов и хотим видеть 3 знака после запятой.

Если указанного в ширине количества позиций нам не хватает для вывода числа, то ширина поля увеличивается автоматически, до минимально-возможного количества позиций.

У первого числа может спереди еще стоять знак минус, например %-8.3f. Этот минус говорит о том, что необходимо выровнять число по левому краю используемой области.

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

Листинг 8.

#include <stdio.h> 
int main(void){
  int a=777; 
// никаких модификаторов нет, 
//вывод использует минимальную ширину поля
  printf("|%d|\n", a);
	
// ширина 8 позиций, выравнивание по левому краю     
  printf("|%-8d|\n", a);
	
// ширина 8 позиций, выравнивание по правому краю    
  printf("|%8d|\n", a);
	
// число позиций 2, но в числе 777 больше двух цифр 
// поэтому область расширяется до минимально-возможной ширины
  printf("|%2d|\n", a);

  float b=150.5;   
// никаких модификаторов нет, выведет используя минимальную ширину поля
// и стандартную точность 6 знаков
  printf("|%f|\n", b);
	
// ширина 8 позиций, выравнивание по левому краю, 3 знака после запятой
  printf("|%-8.3f|\n", b);
	
// ширина 8 позиций, 2 знака после запятой, выравнивание по правому краю    
  printf("|%8.2f|\n", b);
	
// число позиций 4,точность 3 знака после запятой, но этого мало
// поэтому ширина поля увеличивается до минимально-возможного значения
  printf("|%4.3f|\n", b);
	return 0;
}

Результат работы данной программы представлен на рисунке ниже.

Вывод Листинг 8.

Рис.8 Вывод Листинг 8.

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

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

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

Листинг 9.

#include <stdio.h>
int main(void) {
  int a, b, res;

  a = 10;
  b = 7;

  res = a + b;  
  printf("%d + %d = %d\n", a, b, res);
  return 0;
}

Сохрани в закладки или поддержи проект.

Практика

Решите предложенные задачи. Для удобства работы сразу переходите в полноэкранный режим

Исследовательские задачи для хакеров:

  1. Теперь вы можете увидеть, как выглядит мусор, который хранится в переменной после её объявления. Для этого объявите переменную любого типа и выведете её значение на экран использую функцию printf.
  2. Найдите в стандарте языка, в справочной системе компилятора полный список escape-последовательностей. Попробуйте использовать их в вашей программе. Разберитесь как они работают.
  3. Выясните, что произойдет, если в формат-строку функции printf вставить \c, где c — любой символ, не входящий в список escape-последовательностей. [K&R]
  4. Разберитесь с тем, как работает модификатор точности для целых чисел.
  5. Попробуйте вывести символы «"», «'», «\» и «?» без использования escape-последовательностей.
  6. Изучите как работает спецификатор %e
  7. Скомпилируйте и выполните сделующую программу:
    #include <stdio.h>
    #include <string.h>
    
    int main(void) {
    	char one = '?';
    	char two = '\?';
    
    	printf("Compare chars: %d - %d\n", one, two);
    	if (one == two){
    		printf("It's equal!\n\n");
    	} else {
    		printf("It isn't equal!\n\n");
    	}
    
    	char tree[] = "?";
    	char four[] = "\?";
    	
    	printf("Compare strings: %s - %s\n", tree, four);
    	if (strcmp(tree, four) == 0){
    		printf("It's equal!\n");
    	} else {
    		printf("It isn't equal!\n");
    	}
      return 0;
    }

Дополнительные материалы

  1. Ранее я говорил, что тип double называется типом двойной точности, и что он в некотором смысле лучше, чем тип float. Запустите следующую программу, чтобы убедиться в этом.

    Программа

    #include <stdio.h>
    int main(void){
      int z = 1/10;
      float x = 1.0/10.0;
      double y = 1.0/10.0;
      
      printf("int:\n%d\n\n", z);
      printf("float:\n4\t--\t%.4f\n10\t--\t%.12f\n16\t--\t%.16f\n\n", x,x,x);
      printf("double:\n4\t--\t%.4f\n10\t--\t%.12f\n16\t--\t%.16f\n", y,y,y);
      
      return(0);
    }

    Причина подобного результата в том, что количество памяти выделенное для хранения любой переменной ограничено Из-за этого вещественные числа хранятся в памяти компьютера приближенно. Для типа float памяти выделяется обычно меньше, чем для типа double. Поэтому возникают такие забавные моменты. Отдельно обратите внимание на результат деления целых чисел, записанный в переменную z. Об этом мы поговорим в следующем уроке.

Оставить комментарий

Чтобы код красиво отображался на странице заключайте его в теги [code] здесь писать код [/code]

Комментарии

IamNotARobot
ННо даже этих возможностей вполне хватит тут НН опечатка
KaDeaT
Спасибо. Поправил.
vellkira
Разве в листинге 6 не должна быть переменная b вместо f?
В комментарии говорится о нарушении 2-го правила, но мы не присваивали никаких данный переменной f, верно?
KaDeaT
В листинге 6 вовсе нет переменной f
Levon
Очень хороший сайт
KaDeaT
Спасибо!
Misha
"относится к одной ИЗ следующих групп: "
KaDeaT
Поправил! Спасибо.
Misha
Вопрос по первой хакерской задаче: я знаю как вывести содержимое переменной через амперсанд. Есть ли ещё какие-то решения?
Вячеслав
Попробуйте поставить 100 знаков, а не 16 после запятой.
Неточность будет всегда! Если только сразу в вводе данных не указывать их полноценными. В программах для военных целей это очень важно, а и в любой другой науке.
CindyTig
Hi, a have one question.
What all people doing here?
Why we dont living with real life?
KaDeaT
We study programming language and computer science to make the world a better place!
Ivan
Для того чтобы вывести символ '?', не обязательно использовать escape-последовательность '?'. У меня и без этого выводится: printf("Your name?"). В чем подвох?
KaDeaT
Так ведь об этом и написано. Не обязательно использовать / для знака вопроса.
LRusseBesuhof
Задание для хакеров:
При стандартной программе
#include <stdio.h>
int main(void) {
int a;
printf("%d", a);
return 0;
}
Visual Studio выдаёт ошибку, а если в Stepik-e скомпилировать и запустить, то будет 0. Там действительно 0 или как-то по-другому выводить нужно?
KaDeaT
В степике компилятор настроен так, что всем неинициализированным переменным устанавливает значение 0.

На такое поведение надеяться не стоит. В общем случае в переменной будет не нуль, а непонятно что (мусор).
KotS
Всем привет! Наткнулся тут на интересную штуку (мне, как новичку, тут всё интересно ????): если необъявленную переменную вывести на экран то отобразится произвольное число, так называемый "мусор". Но, если эту переменную заключить в одинарные кавычки, например вот так:
#include <stdio.h>
int main(void){
int d;
printf("%d",'d');
}
то число становится определённым и, возрастает в соответствии с алфавитом, например 'а'=97, 'b'=98 и так далее по возрастанию. Во!????
KaDeaT
Тут всё просто. Вы выводите не значение переменной, а символьную константу 'd'. А символы и целые числа связаны между собой. Вот, например, посмотрите: https://youngcoder.ru/lessons/9/tip_dannyh_char.php
Yupi
В дополнительных материалах несоответствие небольшое есть в printf. В том месте, где вы хотите показать отображение точности в 10 символов, фактически показываете 12. Надо уравнять их=)
Artgeos
В этих строках нет ошибок?
 printf("float:n4t--t%.4fn10t--t%.12fn16t--t%.16fnn", x,x,x); 

 printf("double:n4t--t%.4fn10t--t%.12fn16t--t%.16fn", y,y,y); 

Почему 10 -- %.12f а не %.10f?
Почему и в float и в double использован спецификатор формата %f? А когда нужно использовать %g, ведь и float и double вещественные?
Димитрий

#include <stdio.h>

int main(void)
{
int i;
float f;
double d;
char c;

printf("int = %dn", i);
printf("float = %fn", f);
printf("double = %lfn", d);
printf("char = %cn", c);

return 0;
}


Компилирую через gcc версия 11.3.0, и вместо мусора выводятся нули для числовых типов и пустота для char, что является более разумным поведением, на мой взгляд.
Serega-ne@
После заголовка МОДИФИКАТОРЫ ФОРМАТА.
Вы пишите интересную вещь о том что вещественные числа через спецификатор %f выведены с шестью знаками после запятой. Незамедлительно возникает вопрос, а где? покажите... и начинаешь ломать голову! Может быть пример который объясняет данное правило, что описан в листинге под номером 8, указать перед текстом? я прям потратил много времени что бы разобраться и свести данную инфо воедино.
Антон
что будет если количество спецификаторов формата не совпадает с количеством данных для вывода, или тип формата другой, а не как у переменной?
Владимир
советую исправить
либо так:
printf("float:n4t--t%.4fn12t--t%.12fn16t--t%.16fnn", x,x,x);
printf("double:n4t--t%.4fn12t--t%.12fn16t--t%.16fn", y,y,y);
либо так:
printf("float:n4t--t%.4fn10t--t%.10fn16t--t%.16fnn", x,x,x);
printf("double:n4t--t%.4fn10t--t%.10fn16t--t%.16fn", y,y,y);
Гриша
Написал код для задания про Шерлока:
#include <stdio.h>

int main() {
printf("S.Holmes:n");
printf("%dgrad %d'%5.2f" Nn", 51, 31, 25.48);
printf("%dgrad %d'%5.2f" W", 0, 9, 29.93);
return 0;
}

результат полностью соответствует условию задачи:
S.Holmes:
51grad 31'25.48" N
0grad 9'29.93" W

но при отправке задания вылезает ошибка, в чем дело?