Режим — это строка, содержащая одну или несколько директив, определяющих, как именно должен быть открыт файл.
В конкретной операционной системе может быть (и, как правило, так и есть) больше возможностей. Некоторые режимы могут комбинироваться, например, инструкция
fopen("foo","rb")
пытается открыть файл
foo
для чтения в бинарном режиме. Режимы ввода-вывода для потоков из библиотек
stdio
и
iostream
должны быть одинаковыми (см. раздел Б.7.1)
Б.10.2. Семейство
функций printf
Наиболее популярными функциями в стандартной библиотеке языка С являются функции ввода-вывода. Тем не менее рекомендуем использовать библиотеку
iostream
, потому что она безопасна с точки зрения типов и допускает расширение. Функция форматированного вывода
printf
используется очень широко (в том числе и в программах на языке C++) и часто имитируется в других языках программирования. < image l:href="#"/>
В каждой версии число
n
— это количество записанных символов, а в случае неудачи — отрицательное число. На самом деле значение, возвращаемое функцией
printf
, практически всегда игнорируется.
Объявление функции
printf
имеет следующий вид:
int printf(const char* format ...);
Иначе говоря, эта функция получает строку в стиле языка С (как правило, строковый литерал), за которой следует список, состоящий из произвольного количества аргументов произвольного типа. Смысл этих дополнительных аргументов задается спецификаторами преобразования в форматной строке, например
%c
(вывести символ) и
%d
(вывести целое число). Рассмотрим пример.
int x = 5;
const char* p = "asdf";
printf("Значение x равно '%d', а значение p равно '%s'\n",x,s);
Символ, следующий за знаком
%
управляет обработкой аргументов. Первый знак
%
применяется к первому дополнительному аргументу (в данном примере спецификатор
%d
применяется к переменной
x
), второй знак
%
относится ко второму дополнительному аргументу (в данном примере спецификатор
%s
применяется к переменной
p
) и т.д. В частности, рассмотренный выше вызов функции
printf
приводит к следующему результату:
Значение x равно '5', а значение p равно 'asdf'
Затем происходит переход на новую строку.
В принципе соответствие между директивой преобразования
%
и типом, к которому она применяется, проверить невозможно. Рассмотрим пример.
printf("Значение x равно '%s', а значение p равно '%d'\n",x,p); // ой!
Набор спецификаторов преобразования довольно велик и обеспечивает большую гибкость (а также много возможностей сделать ошибку). За символом
%
могут следовать спецификаторы, описанные ниже.
Нулевая или слишком маленькая ширина поля никогда не приводит к усечению вывода; дополнение вывода нулями или пробелами производится только тогда, когда заданная ширина поля превышает реальную.
Поскольку в языке C нет пользовательских типов в смысле языка C++, в нем нет возможностей для определения форматов вывода для таких классов, как
complex
,
vector
или
string
.
Стандартный поток вывода
stdout
в языке C соответствует потоку
cout
. Стандартный поток ввода
stdin
в языке С соответствует потоку
cin
. Стандартный поток сообщений об ошибках
stderr
в языке С соответствует потоку
cerr
. Эти соответствия между стандартными потоками ввода-вывода в языке C и C++ настолько близки, что потоки ввода-вывода как в стиле языка С, так и стиле языка С++ могут использовать один и тот ж буфер. Например, для создания одного и того же потока вывода можно использовать комбинацию операций над объектами
cout
и
stdout
(такая ситуация часто встречается в смешанном коде, написанном на языка С и С++). Эта гибкость требует затрат. Для того чтобы получить более высокую производительность, не смешивайте операции с потоками из библиотек
stdio
и
iostream
при работе с одним и тем же потоком, вместо этого вызывайте функцию
ios_base::sync_with_stdio(false)
перед выполнением первой операции ввода-вывода. В библиотеке
stdio
определена функция
scanf
, т.е. операция ввода, похожая на функцию
printf
. Рассмотрим пример.
int x;
char s[buf_size];
int i = scanf("Значение x равно '%d', а значение s равно '%s'\n",&x,s);
Здесь функция
scanf
пытается считать целое число в переменную
x
и последовательность символов, не являющихся разделителями, в массив
s
. Неформатные символы указывают, что они должны содержаться в строке ввода. Рассмотрим пример.
"Значение x равно '123', а значение s равно 'string '\n"
Программа введет число
123
в переменную
x
и строку "
string
", за которой следует
0
, в массив
s
. Если вызов функции
scanf
завершает работу успешно, результирующее значение (
i
в предыдущем вызове) будет равно количеству присвоенных аргументов-указателей (в данном примере это число равно
2
); в противном случае оно равно
EOF
. Этот способ индикации ввода уязвим для ошибок (например, что произойдет, если вы забудете вставить пробел после строки "
string
" в строке ввода?). Все аргументы функции
scanf
должны быть указателями. Мы настоятельно рекомендуем не использовать эту функцию.
Как же ввести данные, если мы вынуждены использовать библиотеку
stdio
? Один и из распространенных ответов гласит: “Используйте стандартную библиотечную функцию
gets
”.
// очень опасный код:
char s[buf_size];
char* p = gets(s); // считывает строку в массив s
Вызов
p=gets(s)
будет вводить символы в массив
s
, пока не обнаружится символ перехода на новую строку или не будет достигнут конец файла. В этом случае в конец строки
s
после последнего символа будет вставлен
0
. Если обнаружен конец файла или возникла ошибка, то указатель p устанавливается равным
NULL
(т.е.
0
); в противном случае он устанавливается равным
s
. Никогда не используйте функцию
gets(s)
или ее эквивалент
scanf("%s",s))!
За прошедшие годы создатели вирусов облюбовали их слабые места: генерируя вводную строку, переполняющую буфер ввода (в данном примере строку
s
), они научились взламывать программы и атаковать компьютеры. Функция
sprintf
страдает от таких же проблем, связанных с переполнением буфера.
Библиотека
stdio
содержит также простые и полезные функции чтения и записи символов.
Обратите внимание на то, что результатом этих функций является число типа
int
(а не переменная типа
char
или макрос
EOF
). Рассмотрим типичный цикл ввода в программе на языке С.
int ch; /* но не char ch; */
while ((ch=getchar)!=EOF) { /* какие-то действия */ }
Не применяйте к потоку два последовательных вызова
ungetc
. Результат такого действия может оказаться непредсказуемым, а значит, программа не будет переносимой.
Мы описали не все функции из библиотеки
stdio
, более полную информацию можно найти в хороших учебниках по языку С, например в книге K&R.
Б.10.3. Строки в стиле языка С
Строки в стиле языка C представляют собой массивы элементов типа
char
, завершающиеся нулем. Эти строки обрабатываются функциями, описанными в заголовках