Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник
Скачать 1.57 Mb.
|
Глава 7 ВВОД И ВЫВОД В языке Си отсутствуют средства ввода-вывода. Все операции ввода- вывода реализуются с помощью функций, находящихся в библиотеке языка Си, поставляемой в составе конкретной системы программирования Си. Особенностью языка Си, который впервые был применен при разработке операционной системы UNIX, является отсутствие заранее спланированных структур файлов. Все файлы рассматриваются как неструктурированная последовательность байтов. При таком подходе удалось распространить понятие файла на различные устройства. В UNIX конкретному устройству соответствует так называемый «специальный файл», а одни и те же функции библиотеки языка Си используются как для обмена данными с файлами, так и для обмена с устройствами. Примечание В UNIX эти же функции используются также для обмена данными между процессами (выполняющимися программами). Библиотека языка Си поддерживает два уровня ввода-вывода: потоковый ввод-вывод и ввод-вывод нижнего уровня. 7.1. Потоковый ввод-вывод На уровне потокового ввода-вывода обмен данными производится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней памяти, строго говоря, являются устройствами поблочного обмена, то есть за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 512 байт. При вводе с диска (при чтении из файла) данные помещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они накапливаются в буфере, а при заполнении буфера записываются в виде единого блока на диск за одно обращение к последнему. Буферы операционной системы реализуются в виде участков основной памяти. Поэтому пересылки между буферами ввода-вывода и выполняемой программой происходят достаточно быстро, в отличие от реальных обменов с физическими устройствами. Функции библиотеки ввода-вывода языка Си, поддерживающие обмен данными с файлами на уровне потока, позволяют обрабатывать данные различных размеров и форматов, обеспечивая при этом буферизованный ввод и вывод. Таким образом, поток - это файл либо внешнее устройство вместе с предоставляемыми средствами буферизации. При работе с потоком можно производить следующие действия: открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами); вводить и выводить: символ, строку, форматированные данные, порцию данных произвольной длины; анализировать ошибки потокового ввода-вывода и условие достижения конца потока (конца файла); управлять буферизацией потока и размером буфера; получать и устанавливать указатель (индикатор) текущей позиции в потоке. Для того чтобы можно было использовать функции библиотеки ввода-вывода языка Си, в программу необходимо включить заголовочный файл stdio.h (#include <stdio.h>), который содержит прототипы функций ввода-вывода, а также определения констант, типов и структур, необходимых для работы функций обмена с потоком. На рис. 7.1 показаны возможные информационные обмены исполняемой программы на локальной (не сетевой) ЭВМ. Открытие и закрытие потока Прежде чем начать работать с потоком, его необходимо инициализировать, то есть открыть. При этом поток связывается в исполняемой программе со структурой предопределенного типа FILE. Рис. 7.1. Информационные обмены исполняемой программы на локальной ЭВМ Определение структурного типа FILE находится в заголовочном файле stdio.h. В структуре FILE содержатся компоненты, с помощью которых ведется работа с потоком, в частности указатель на буфер, указатель (индикатор) текущей позиции в потоке и другая информация. При открытии потока в программу возвращается указатель на поток, являющийся указателем на объект структурного типа FILE. Этот указатель идентифицирует поток во всех последующих операциях. Указатель на поток, например fp, должен быть объявлен в программе следующим образом: #include Указатель на поток приобретает значение в результате выполнения функции открытия потока: fp = fopen (имя_файла, режим_открытия); Параметры имя_файла и режим_открытия являются указателями на массивы символов, содержащих соответственно имя файла, связанного с потоком, и строку режимов открытия. Однако эти параметры могут задаваться и непосредственно в виде строк при вызове функции открытия файла: fp = fopen("t.txt", "r"); где t.txt - имя некоторого файла, связанного с потоком; r - обозначение одного из режимов работы с файлом (тип доступа к потоку). Поток можно открыть в текстовом либо двоичном (бинарном) режиме. Стандартно файл, связанный с потоком, можно открыть в одном из следующих шести текстовых режимов: «w» - новый текстовый (см. ниже) файл открывается для записи. Если файл уже существовал, то предыдущее содержимое стирается, файл создается заново; «г» - существующий текстовый файл открывается только для чтения; «a» - текстовый файл открывается (или создается, если файла нет) для добавления в него новой порции информации (добавление в конец файла). В отличие от режима «w», режим «a» позволяет открывать уже существующий файл, не уничтожая его предыдущей версии, и писать в продолжение файла; «w+» - новый текстовый файл открывается для записи и последующих многократных исправлений. Если файл уже существует, то предыдущее содержимое стирается. Последующие после открытия файла запись и чтение из него допустимы в любом месте файла, в том числе запись разрешена и в конец файла, то есть файл может увеличиваться («расти»); «r+» - существующий текстовый файл открывается как для чтения, так и для записи в любом месте файла; однако в этом режиме невозможна запись в конец файла, то есть недопустимо увеличение размеров файла; «a+» - текстовый файл открывается или создается (если файла нет) и становится доступным для изменений, то есть для записи и для чтения в любом месте; при этом, в отличие от режима «w+», можно открыть существующий файл и не уничтожать его содержимого; в отличие от режима «r+», в режиме «а+» можно вести запись в конец файла, то есть увеличивать его размеры. В текстовом режиме прочитанная из потока комбинация символов CR (значение 13) и LF (значение 10), то есть управляющие коды «возврат каретки» и «перевод строки», преобразуется в один символ новой строки '\n' (значение 10, совпадающее с LF). При записи в поток в текстовом режиме осуществляется обратное преобразование, то есть символ новой строки '\n' (LF) заменяется последовательностью CR и LF. Если файл, связанный с потоком, хранит не текстовую, а произвольную двоичную информацию, то указанные преобразования не нужны и могут быть даже вредными. Обмен без такого преобразования выполняется при выборе двоичного или бинарного режима, который обозначается буквой b. Например, «r+b» или «wb». В некоторых компиляторах текстовый режим обмена обозначается буквой t, то есть записывают «a+t» или «rt». Если поток открыт для изменений, то есть в параметре режима присутствует символ «+», то разрешен как вывод в поток, так и чтение из него. Однако смена режима (переход от записи к чтению и обратно) должна происходить лишь после установки указателя потока в нужную позицию (см. §7.1.3). При открытии потока могут возникнуть следующие ошибки: указанный файл, связанный с потоком, не найден (для режима «чтение»); диск заполнен или диск защищен от записи и т. п. Необходимо также отметить, что при выполнении функции fopen( ) происходит выделение динамической памяти. При ее отсутствии устанавливается признак ошибки «Not enough memory» (Недостаточно памяти). В перечисленных случаях указатель на поток приобретает значение NULL. Заметим, что указатель на поток в любом режиме, отличном от аварийного, никогда не бывает равным NULL. Приведем типичную последовательность операторов, которая используется при открытии файла, связанного с потоком: if ((fp = fopen("t.txt", "w")) == NULL) { реггог("ошибка при открытии файла t.txt \n"); exit(0); } где NULL - нулевой указатель, определенный в файле stdio.h. Для вывода на экран дисплея сообщения об ошибке при открытии потока используется стандартная библиотечная функция perror( ), прототип которой в stdio.h имеет вид: void perror(const char * s); Функция perror( ) выводит строку символов, адресуемую аргументом, за которой размещается сообщение об ошибке. Содержимое и формат сообщения определяются реализацией системы программирования. Текст сообщения об ошибке выбирается функцией perror( ) на основании номера ошибки. Номер ошибки заносится в переменную int errno (определенную в заголовочном файле errno.h) рядом функций библиотеки языка Си, в том числе и функциями ввода-вывода. После того как файл открыт, с ним можно работать, записывая в него информацию или считывая ее (в зависимости от режима). Открытые на диске файлы после окончания работы с ними рекомендуется закрыть явно. Для этого используется библиотечная функция int fclose ( указатель_на_поток ); Открытый файл можно открыть повторно (например, для изменения режима работы с ним) только после того, как файл будет закрыт с помощью функции fclose( ). Стандартные потоки и функции для работы с ними Когда программа начинает выполняться, автоматически открываются пять потоков, из которых основными являются: стандартный поток ввода (на него ссылаются, используя предопределенный указатель на поток stdin); стандартный поток вывода (stdout); стандартный поток вывода сообщений об ошибках (stderr). По умолчанию стандартному потоку ввода stdin ставится в соответствие клавиатура, а потокам stdout и stderr соответствует экран дисплея. Для ввода-вывода данных с помощью стандартных потоков в библиотеке языка Си определены следующие функции: getchar( )/putchar( ) - ввод-вывод отдельного символа; gets( )/puts( ) - ввод-вывод строки; scanf( )/printf( ) - ввод-вывод в режиме форматирования данных. Ввод-вывод отдельных символов. Одним из наиболее эффективных способов осуществления ввода-вывода одного символа является использование библиотечных функций getchar( ) и putchar( ). Прототипы функций getchar( ) и putchar( ) имеют следующий вид: int getchar(void); int putchar(int c); Функция getchar( ) считывает из входного потока один символ и возвращает этот символ в виде значения типа int. Функция putchar( ) выводит в стандартный поток один символ и возвращает этот символ в точку вызова в виде значения типа int. Обратите внимание на то, что функция getchar( ) вводит очередной символ в виде значения типа int. Это сделано для того, чтобы гарантировать успешность распознавания ситуации «достигнут конец файла». Дело в том, что при чтении из файла с помощью функции getchar( ) может быть достигнут конец файла. В этом случае операционная система в ответ на попытку чтения символа передает функции getchar( ) значение EOF (End of File). Константа EOF определена в заголовочном файле stdio.h и в разных операционных системах имеет значение 0 или -1. Таким образом, функция getchar( ) должна иметь возможность прочитать из входного потока не только символ, но и целое значение. Именно с этой целью функция getchar( ) всегда возвращает значение типа int. Применение в программах константы EOF вместо конкретных целых значений, возвращаемых при достижении конца файла, делает программу мобильной (независимой от конкретной операционной системы). В случае ошибки при вводе функция getchar( ) также возвращает EOF. Строго говоря, функции getchar( ) и putchar( ) функциями не являются, а представляют собой макросы, определенные в заголовочном файле stdio.h следующим образом: #define getchar() getc(stdin) #define putchar(c) putc ((с), stdout) Здесь переменная c определена как int c. Функции getc( ) и putc( ), с помощью которых определены функции getchar( ) и putchar( ), имеют следующие прототипы: int getc (FILE *stream); int putc (int c, FILE *stream); Указатели на поток, задаваемые в этих функциях в качестве параметра, при определении макросов getchar( ) и putchar( ) имеют соответственно предопределенные значения stdin (стандартный ввод) и stdout (стандартный вывод). Интересно отметить, что функции getc( ) и putc( ), в свою очередь, также реализуются как макросы. Соответствующие строки легко найти в заголовочном файле stdio.h. Не разбирая подробно этих макросов, заметим, что вызов библиотечной функции getc( ) приводит к чтению очередного байта информации из стандартного ввода с помощью системной функции (ее имя зависит от операционной системы) лишь в том случае, если буфер операционной системы окажется пустым. Если буфер непуст, то в программу пользователя будет передан очередной символ из буфера. Это необходимо учитывать при работе с программой, которая использует функцию getchar( ) для ввода символов. При наборе текста на клавиатуре коды символов записываются во внутренний буфер операционной системы. Одновременно они отображаются (для визуального контроля) на экране дисплея. Набранные на клавиатуре символы можно редактировать (удалять и набирать новые). Фактический перенос символов из внутреннего буфера в программу происходит при нажатии клавиши Об этом необходимо помнить, если вы рассчитываете на ввод функцией getchar( ) одиночного символа. Приведем фрагмент программы, в которой функция getchar( ) используется для временной приостановки работы программы с целью анализа промежуточных значений контролируемых переменных. #include printf("а"); getchar(); /* #1 */ printf("b"); getchar(); /* #2 */ printf("c"); return 0; } Сначала на экран выводится символ 'а', и работа программы приостанавливается до ввода очередного (в данном случае - любого) символа. Если нажать, например, клавишу и затем клавишу |