Что касается файловой системы языка С, то в начале выполнения программы автоматически открываются три потока. Это stdin (стандартный поток ввода), stdout (стандартный поток вывода) и stderr (стандартный поток ошибок). Обычно эти потоки направляются к консоли, но в средах, которые поддерживают перенаправление ввода/вывода, они могут быть перенаправлены операционной системой на другое устройство. (Перенаправление ввода/вывода поддерживается, например, такими операционными системами, как Windows, DOS, UNIX и OS/2.)
Так как стандартные потоки являются указателями файлов, то они могут использоваться системой ввода/вывода языка С также для выполнения операций ввода/вывода на консоль. Например, putchar() может быть определена таким образом:
int putchar(char c) { return putc(c, stdout); }
Вообще говоря, stdin используется для считывания с консоли, a stdout и stderr — для записи на консоль.
В роли указателей файлов потоки stdin, stdout и stderr можно применять в любой функции, где используется переменная типа FILE *. Например, для ввода строки с консоли можно написать примерно такой вызов fgets():
char str[255]; fgets(str, 80, stdin);
И действительно, такое применение fgets() может оказаться достаточно полезным. Как уже говорилось в этой книге, при использовании gets() не исключена возможность, что массив, который используется для приема вводимых пользователем символов, будет переполнен. Это возможно потому, что gets() не проводит проверку на отсутствие нарушения границ. Полезной альтернативой gets() является функция fgets() с аргументом stdin, так как эта функция может ограничивать число читаемых символов и таким образом не допустить переполнения массива. Единственная проблема, связанная с fgets(), состоит в том, что она не удаляет символ новой строки (в то время как gets() удаляет!), поэтому его приходится удалять "вручную", как показано в следующей программе:
#include <stdio.h> #include <string.h> int main(void) { char str[80]; int i; printf("Введите строку: "); fgets(str, 10, stdin); /* удалить символ новой строки, если он есть */ i = strlen(str)-1; if(str[i]=='\n') str[i] = '\0'; printf("Это Ваша строка: %s", str); return 0; }
He забывайте, что stdin, stdout и stderr — это не переменные в обычном смысле, и им нельзя присваивать значение с помощью fopen(). Кроме того, именно потому, что в начале работы программы эти указатели файлов создаются автоматически, в конце работы они и закрываются автоматически. Так что и не пытайтесь самостоятельно их закрыть.
В языке С консольный и файловый ввод/вывод не слишком отличаются друг от друга. Функции консольного ввода/вывода, описанные в главе 8, на самом деле направляют результаты своих операций на один из потоков — stdin или stdout, и по сути, каждая из них является специальной версией соответствующей файловой функции. Функции консольного ввода/вывода для того и существуют, чтобы было удобно именно программисту.
Как говорилось в предыдущем разделе, ввод/вывод на консоль можно выполнять с помощью любой файловой функции языка С. Однако для вас может быть сюрпризом, что, оказывается, операции ввода/вывода на дисковых файлах можно выполнять с помощью функции консольного ввода/вывода, например, printf()! Дело в том, что все функции консольного ввода/вывода, о которых говорилось в главе 8, выполняют свои операции с потоками stdin и stdout. В средах, поддерживающих перенаправление ввода/вывода, это равносильно тому, что stdin или stdout могут быть перенаправлены на устройство, отличное от клавиатуры или экрана. Проанализируйте, например, следующую программу:
#include <stdio.h> int main(void) { char str[80]; printf("Введите строку: "); gets(str); printf(str); return 0; }
Предположим, что эта программа называется TEST. При ее нормальном выполнении на экран выводится подсказка, затем читается строка, введенная с клавиатуры, и, наконец, эта строка выводится на экран. Однако в средах, в которых поддерживается перенаправление ввода/вывода, один из потоков stdin или stdout (или оба одновременно) можно перенаправить в файл. Например, в среде DOS или Windows следующий запуск TEST
TEST > OUTPUT
приводит к тому, что вывод этой программы будет записан в файл по имени OUTPUT. А следующий запуск TEST
TEST < INPUT > OUTPUT
направляет поток stdin в файл по имени INPUT, а поток стандартного вывода — в файл по имени OUTPUT.
Когда С-программа завершается, то все перенаправленные потоки возвращаются в состояния, которые были установлены по умолчанию.
Для перенаправления стандартных потоков можно воспользоваться функцией freopen(). Эта функция связывает имеющийся поток с новым файлом. Так что она вполне может связать с новым файлом и стандартный поток. Вот прототип этой функции:
FILE *freopen(const char *имя_файла, const char *режим, FILE *поток);
где имя_файла — это указатель на имя файла, который требуется связать с потоком, на который указывает указатель поток. Файл открывается в режиме режим; этот параметр может принимать те же значения, что и соответствующий параметр функции fopen(). Если функция freopen() выполнилась успешно, то она возвращает поток, а если встретились ошибки, — то NULL.
В следующей программе показано использование функции freopen() для перенаправления стандартного потока вывода stdout в файл с именем OUTPUT.
#include <stdio.h> int main(void) { char str[80]; freopen("OUTPUT", "w", stdout); printf("Введите строку: "); gets(str); printf(str); return 0; }
Вообще говоря, перенаправление стандартных потоков с помощью freopen() в некоторых случаях может быть полезно, например, при отладке. Однако выполнение дисковых операций ввода/вывода на перенаправленных потоках stdin и stdout не настолько эффективно, как использование таких функций, как fread() или fwrite().