, поэтому мы используем стандартный механизм ввода-вывода языка С, определенный в заголовочном файле
<stdio.h>
. Эквивалентами потоков ввода и вывода
cin
и
cout
из языка С++ в языке С являются потоки
stdin
и
stdout
. Стандартные средства ввода-вывода языка С и потоки
iostream
могут одновременно использоваться в одной и той же программе (для одних и тех же потоков ввода-вывода),
но мы не рекомендуем это делать. Если вам необходимо совместно использовать эти механизмы, хорошенько разберитесь в них (обратите особое внимание на функцию
ios_base::sync_with_stdio
), используя хороший учебник. См. также раздел Б.10.
27.6.1. Вывод
Наиболее популярной и полезной функцией библиотеки
stdio
является функция
printf
. Основным предназначением функции
printf
является вывод С-строки.
#include<stdio.h>
void f(const char* p)
{
printf("Hello, World!\n");
printf(p);
}
Это не очень интересно. Намного интереснее то, что функция
printf
может получать любое количество аргументов и начальную управляющую строку, которая определяет, как вывести дополнительные аргументы. Объявление функции
printf
в языке C выглядит следующим образом:
int printf(const char* format, ...);
Многоточие (
...
) означает “и, возможно, остальные аргументы”. Мы можем вызвать функцию
printf
так:
void f1(double d, char* s, int i, char ch)
{
printf("double %g string %s int %d char %c\n", d, s, i, ch);
}
где символы
%g
означают: “Напечатать число с плавающей точкой, используя универсальный формат”, символы
%s
означают: “Напечатать С-строку”, символы
%d
означают: “Напечатать целое число, используя десятичные цифры,” а символы
%c
означают: “Напечатать символ”. Каждый такой спецификатор формата связан со следующим, до поры до времени не используемым аргументом, так что спецификатор
%g
выводит на экран значение переменной
d
;
%s
— значение переменной
s
,
%d
— значение переменной
i
, а
%c
— значение переменной
ch
. Полный список форматов функции
printf
приведен в разделе Б.10.2.
К сожалению, функция
printf
не является безопасной с точки зрения типов. Рассмотрим пример.
Интересен эффект последнего вызова функции printf: она выводит на экран каждый байт участка памяти, следующего за элементом a[1], пока не встретится нуль. Такой вывод может состоять из довольно большого количества символов.
Недостаток проверки типов является одной из причин, по которым мы предпочитаем потоки
iostream
, несмотря на то, что стандартный механизм ввода-вывода, описанный в библиотеке
stdio
языков C и C++, работает одинаково. Другой причиной является то, что функции из библиотеки
stdio
не допускают расширения: мы не можем расширить функцию
printf
так, чтобы она выводила на экран значения переменных вашего собственного типа. Для этого можно использовать потоки
iostream
. Например, нет никакого способа, который позволил бы вам определить свой собственный спецификатор формата
%Y
для вывода структуры
struct Y
.
Существует полезная версия функции
printf
, принимающая в качестве первого аргумента дескриптор файла.
int fprintf(FILE* stream, const char* format, ...);
fprintf(ff,"Hello, World!\n"); // запись "Hello,World!\n"
// в файл My_file
Дескрипторы файлов описаны в разделе 27.6.3.
27.6.2. Ввод
Ниже перечислены наиболее популярные функции из библиотеки
stdio
.
int scanf(const char* format, ...); /* форматный ввод из потока stdin */
int getchar(void); /* ввод символа из потока stdin */
int getc(FILE* stream); /* ввод символа из потока stream*/
char* gets(char* s); /* ввод символов из потока stdin */
Простейший способ считывания строки символов — использовать функцию
gets
. Рассмотрим пример.
char a[12];
gets(a); /* ввод данных в массив символов a вплоть до символа '\n' */
Никогда не делайте этого! Считайте, что функция
gets
отравлена. Вместе со своей ближайшей “родственницей” — функцией
scanf("%s")
— функция
gets
является мишенью для примерно четверти успешных хакерских атак. Она порождает много проблем, связанных с безопасностью. Как в тривиальном примере, приведенном выше, вы можете знать, что до следующей новой строки будет введено не более 11 символов? Вы не можете этого знать. Следовательно, функция
gets
почти наверное приведет к повреждению памяти (байтов, находящихся за буфером), а повреждение памяти является основным инструментом для хакерских атак. Не считайте, что можете угадать максимальный размер буфера, достаточный на все случаи жизни. Возможно, что “субъект” на другом конце потока ввода — это программа, не соответствующая вашим критериям разумности.
Функция
scanf
считывает данные с помощью формата точно так же, как и функция
printf
. Как и функция
printf
, она может быть очень удобной.
void f
{
int i;
char c;
double d;
char* s = (char*)malloc(100);
/* считываем данные в переменные, передаваемые как указатели: */
scanf("%i %c %g %s", &i, &c, &d, s);
/* спецификатор %s пропускает первый пробел и прекращает
действие на следующем пробеле */
}
Как и функция
printf
, функция
scanf
не является безопасной с точки зрения типов. Форматные символы и аргументы (все указатели) должны точно соответствовать друг другу, иначе во время выполнения программы будут происходить странные вещи. Обратите также внимание на то, что считывание данных в строку
s
с помощью спецификатора
%s
может привести к переполнению. Никогда не используйте вызовы
gets
или
scanf("%s")
!
Итак, как же безопасно ввести символы? Мы можем использовать вид формата %s, устанавливающий предел количества считываемых символов. Рассмотрим пример.
char buf[20];
scanf("%19s",buf);
Нам требуется участок памяти, заканчивающийся нулем (содержание которого вводится функцией
scanf
), поэтому 19 — это максимальное количество символов, которое можно считать в массив
buf
. Однако этот способ не отвечает на вопрос, что делать, если некто введет больше 19 символов. Лишние символы останутся в потоке ввода и будут обнаружены при следующей попытке ввода.