Главная страница

Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


Скачать 1.57 Mb.
НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
АнкорКурс на Си
Дата18.02.2023
Размер1.57 Mb.
Формат файлаdocx
Имя файлаПодбельский. Курс программирования на Си.docx
ТипУчебник
#943863
страница22 из 42
1   ...   18   19   20   21   22   23   24   25   ...   42
Глава 7

ВВОД И ВЫВОД

В языке Си отсутствуют средства ввода-вывода. Все операции ввода- вывода реализуются с помощью функций, находящихся в библио­теке языка Си, поставляемой в составе конкретной системы про­граммирования Си.

Особенностью языка Си, который впервые был применен при разработке операционной системы UNIX, является отсутствие за­ранее спланированных структур файлов. Все файлы рассматрива­ются как неструктурированная последовательность байтов. При та­ком подходе удалось распространить понятие файла на различные устройства. В UNIX конкретному устройству соответствует так на­зываемый «специальный файл», а одни и те же функции библиотеки языка Си используются как для обмена данными с файлами, так и для обмена с устройствами.

Примечание

В UNIX эти же функции используются также для обмена данными между процессами (выполняющимися программами).

Библиотека языка Си поддерживает два уровня ввода-вывода: по­токовый ввод-вывод и ввод-вывод нижнего уровня.

7.1. Потоковый ввод-вывод

На уровне потокового ввода-вывода обмен данными произво­дится побайтно. Такой ввод-вывод возможен как для собственно устройств побайтового обмена (печатающее устройство, дисплей), так и для файлов на диске, хотя устройства внешней памяти, стро­го говоря, являются устройствами поблочного обмена, то есть за одно обращение к устройству производится считывание или запись фиксированной порции данных. Чаще всего минимальной порцией данных, участвующей в обмене с внешней памятью, являются блоки в 512 байт. При вводе с диска (при чтении из файла) данные по­мещаются в буфер операционной системы, а затем побайтно или определенными порциями передаются программе пользователя. При выводе данных в файл они накапливаются в буфере, а при заполнении буфера записываются в виде единого блока на диск за одно обращение к последнему. Буферы операционной системы реа­лизуются в виде участков основной памяти. Поэтому пересылки между буферами ввода-вывода и выполняемой программой проис­ходят достаточно быстро, в отличие от реальных обменов с физи­ческими устройствами.

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

При работе с потоком можно производить следующие действия:

  • открывать и закрывать потоки (связывать указатели на потоки с конкретными файлами);

  • вводить и выводить: символ, строку, форматированные дан­ные, порцию данных произвольной длины;

  • анализировать ошибки потокового ввода-вывода и условие до­стижения конца потока (конца файла);

  • управлять буферизацией потока и размером буфера;

  • получать и устанавливать указатель (индикатор) текущей по­зиции в потоке.

Для того чтобы можно было использовать функции библиотеки ввода-вывода языка Си, в программу необходимо включить заголо­вочный файл stdio.h (#include <stdio.h>), который содержит прото­типы функций ввода-вывода, а также определения констант, типов и структур, необходимых для работы функций обмена с потоком.

На рис. 7.1 показаны возможные информационные обмены ис­полняемой программы на локальной (не сетевой) ЭВМ.

      1. Открытие и закрытие потока

Прежде чем начать работать с потоком, его необходимо инициа­лизировать, то есть открыть. При этом поток связывается в испол­няемой программе со структурой предопределенного типа FILE.



Рис. 7.1. Информационные обмены исполняемой программы на локальной ЭВМ

Определение структурного типа FILE находится в заголовочном файле stdio.h. В структуре FILE содержатся компоненты, с помощью которых ведется работа с потоком, в частности указатель на буфер, указатель (индикатор) текущей позиции в потоке и другая инфор­мация.

При открытии потока в программу возвращается указатель на поток, являющийся указателем на объект структурного типа FILE. Этот указатель идентифицирует поток во всех последующих опе­рациях.

Указатель на поток, например fp, должен быть объявлен в про­грамме следующим образом:

#include FILE *fp;

Указатель на поток приобретает значение в результате выполне­ния функции открытия потока:

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( ).

      1. Стандартные потоки и функции

для работы с ними

Когда программа начинает выполняться, автоматически открыва­ются пять потоков, из которых основными являются:

  • стандартный поток ввода (на него ссылаются, используя предопределенный указатель на поток 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 int main() {

printf("а");

getchar(); /* #1 */

printf("b");

getchar(); /* #2 */

printf("c"); return 0;

}

Сначала на экран выводится символ 'а', и работа программы при­останавливается до ввода очередного (в данном случае - любого) символа. Если нажать, например, клавишу и затем клавишу (для завершения ввода), то на следующей строке появятся символы bc, и программа завершит свою работу. Первая (в програм­ме) функция getchar( ) (#1) прочитала из внутреннего буфера код символа 'q', и следующая за ней функция printf( ) вывела на экран символ 'b'. Остановки работы программы не произошло, потому что вторая функция getchar( ) (#2) прочитала код клавиши из внутреннего буфера, а не очередной символ с клавиатуры. Про­изошло это потому, что к моменту вызова функции getchar( ) внут­ренний буфер не был пуст.

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

Функция putchar( ) служит для вывода на устройство стандарт­ного вывода одного символа, заданного в качестве параметра. Ниже приведены примеры:

int c;

1 c = getchar( );

2 putchar(c);

3 putchar('A');

4 putchar('\007');

5 putchar('\t');

В строке 2 фрагмента программы на экран дисплея выводит­ся символ, введенный в предыдущей строке функцией getchar( ). В строке 3 выводится символ 'А', в строке 4 выводится управляю­щий символ, заданный кодом 007 (звуковой сигнал). В последней строке выводится неотображаемый (управляющий) символ табуля­ции, перемещающий курсор в следующую позицию табуляции.

Приведем в качестве примера применения функций getchar( ) и putchar( ) программу копирования данных из стандартного ввода в стандартный вывод, которую можно найти практически в любом пособии по программированию на Си:

#include int main()

{

int c;

while ((c = getchar()) != EOF)

putchar(c);

return 0;

}

Эта же программа с использованием функций getc( ) и putc( ) будет выглядеть так:

#include

int main()

{

int c;

while ((с = getc(stdin)) != EOF) putc(c, stdout);

return 0;

}

Для завершения приведенной выше программы копирования не­обходимо ввести с клавиатуры сигнал прерывания Ctrl+C (одно­временно нажать клавиши и ).

В [1, §1.5] приведены примеры использования функций getchar( ) и putchar( ) для подсчета во входном потоке числа отдельных вве­денных символов, строк и слов.
1   ...   18   19   20   21   22   23   24   25   ...   42


написать администратору сайта