Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lab_stream.doc
Скачиваний:
13
Добавлен:
16.02.2016
Размер:
3.85 Mб
Скачать

Лабораторные работы №3,4

Тема: Потоковый ввод-вывод в языке Си.

Цель: Изучение принципов работы с файлами через механизм потокового ввода-вывода на языке Си, приобретение практических навыков работы с файлами в Си.

  1. Методические указания

Интерактивность программ является одним из основополагающих принципов работы: они взаимодействуют с пользователем, принимая от него данных и команды, а результаты выводят на экран монитора. Еще одним важным требованием к программам можно назвать необходимость сохранять результаты вычислений в энергонезависимой памяти (в файлах на дисках), чтобы с ним можно было обратиться в следующих сеансах работы с программой. В языке Си и работа с консолью, и работа с файлами на диске реализуется в едином программном интерфейсе, называемом потоковый ввод-вывод. Такой подход к взаимодействию программы с устройствами ввода-вывода характерен для работы в текстовом режиме в таких операционных системах, как Microsoft Windows (и более ранней MS DOS) и всех "клонах" UNIX .

В чем заключается принцип потокового ввода-вывода?

Вся работа с внешним устройством заменяется с точки зрения прикладной программы работой со специальной программной абстракцией – потоком.

  1. Операционная система создает в памяти поток, который аккумулирует все данные, перенаправляемые из программы в файл или на устройства, либо читаемые с файла или устройства.

  2. Программа для взаимодействия с файлом или устройством, обращается к связанному с ним потоку. Во внутренние буферы потока записывается вся передаваемая информация;

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

Подобный подход упрощает взаимодействие программиста с внешними устройствами, позволяя использовать одни и те же подходы как для вывода информации на экран монитора, так и для записи информации в файл. Поток выступает своеобразным информационным трубопроводом, соединяющим программу с внешним устройством. Программист ”заливает” информацию в свой конец этого трубопровода и может быть уверен, что она дойдет до адресата. Концепцию потока как информационного трубопровода иллюстрирует рис. 1.

Рисунок 1. Взаимодействие программы и внешних устройств с помощью потока.

В языке Си потоковый ввод-вывод реализуется с помощью средств библиотеки stdio.h. В ней определен целый ряд типов данных, констант, макросов и методов, которые позволяют создавать и закрывать потоки, читать и писать в/из них информацию, отслеживать и изменять состояние потока. Поток описывается в этой библиотеке структурой FILE. Структура FILE содержатся компоненты, с помощью которых ведется работа с потоком, в частности: указатель на буфер, указатель (индикатор) текущей позиции в потоке, дескриптор потока и другая информация. В среде MS Visual Studio структура FILE определена следующим образом:

struct _iobuf {

char *_ptr;

int _cnt;

char *_base;

int _flag;

int _file;

int _charbuf;

int _bufsiz;

char *_tmpfname;

};

typedef struct _iobuf FILE;

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

Переменная-указатель на структуру FILE называется потоковой переменной, и ее определение всегда предшествует работе с потоковыми функциями:

FILE * fp; //потоковая переменная

Прежде чем говорить о создании новых потоков, стоит упомянуть о стандартных потоках операционной системы. В операционной системе всегда существуют и никогда не удаляются следующие стандартные потоки:

  • Стандартный поток ввода (обозначение: stdin в языке Си, cin – в Си++) - используется для ввода символьных данных в программу. По-умолчанию этот поток связан с клавиатурой компьютера;

  • Стандартный поток вывода (обозначение:  stdout в языке Си, cout – в Си++ ) - используется для вывода символьной информации По-умолчанию этот поток связан экраном дисплея;

  • Стандартный поток ошибок (обозначение: stderr в языке Си, cerr – в Си++) - используется для вывода сообщений о возникших ошибках и предупреждениях при работе программы, По-умолчанию этот поток закреплён за экраном дисплея; Наличие двух потоков по умолчанию связанных с экраном дисплея связано с тем, что имеется возможность переопределить потоки, связав их с другим устройством (функцией freopen). Тогда, определив для потока stderr в качестве устройства вывода файл на диске, все данные, направляемых в поток, можно перенаправить с экрана в этот файл и не смешивать на экране полезную информацию с диагностической.

  • Стандартный поток печати (обозначение: stdprn в языке Си.) - используется для вывода результатов работы программы на печать. По-умолчанию этот поток закреплён за текущим принтером в системе, подключённым к порту LPT1.

Стандартные потоки имеют тип FILE*, что позволяет использовать их в качестве параметров функций работы с потоками, которые будут перечислены ниже.

Все остальные потоки программист должен создавать сам. Они создаются уничтожаются с помощью функций открытия и закрытия файлов, на период чтения/записи/добавления информации в эти файлы.

Открытие потока

Для взаимодействия с устройством ввода/вывода программист должен создать новый поток и связать его с целевым устройством и осуществлять операции ввода/вывода работая с потоком.

Для создания потока предназначена функция fopen:

FILE*fopen(char*filename,char*mode);

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

Режим открытия потока mode задается в виде строки с предопределенным содержимым. Состоит из 2-х частей:

1)Режим доступа, определяющий, какие операции можно совершать с содержимым потока

2)Тип потока, определяющий, как интерпретировать содержимое потока: в виде совокупности текстовых строк (текстовый поток, символ 't') или как двоичную информацию (двоичный поток, символ 'b').

Для указания режима доступа используются английские буквы 'r’, ‘w’, ‘a’:

r–открыть для чтения(файл должен существовать)

w–режим открытия на запись(если файл не существует то создается, если существует, то обнуляется)

a–открытие потока на дозапись(файл должен существовать)

r+–открытие существующего файла на чтение и запись

w+–открытие файла на чтение и запись(если файл не существует то создается, если существует, то обнуляется)

a+ - открытие файла на чтение и добавление(если файл не существует то создается, если существует, то откроется с сохранением содержимого).

Примеры вызова функции fopen:

fopen(”file.dat”, “r”);

fopen(”c:\\tmp\\log.txt”, “w+”); //или так:

fopen(”c:/tmp/log.txt”, “w+”);

fopen(”..\\.my.sav”, “r+b”);

Функция fopen создает поток и возвращает указатель на связанную с ним структуру FILE как результат своей работы. Если открыть поток не удалось (указано неверное имя файла, невозможно открыть в указанном режиме, открыто слишком много файлов, у пользователя нет прав на открытие файла и другие причины), то fopen вернёт нулевой указатель NULL и работать с потоком нельзя. Код ошибки открытия можно получить вызовом макроса errno.

Пример открытия нового потока:

FILE*fp;

if((fp=fopen(“tmp.dat”,”w+”))==NULL)

printf(“Ошибка открытия”);

else

{//работа с потоком с использованием fp

fclose(fp);

}

Закрыть ранее открытый поток можно с использованием функции fclose, передав ей в качестве параметра потоковую переменную:

fclose(fp);

Закрыть все открытые потоки можно функцией fcloseall().

Функции для работы с потоком.

После открытия потока можно осуществлять операции ввода-вывода, считывая и записывая информацию в файл (устройства), связанное с потоком. Простейшим вариантом будет посимвольный ввод-вывод с использованием функций:

int fputc(int c, FILE *stream) - выводит символ с в поток stream.

int fgetc(FILE * stream ) – читает очередной символ из потока stream.

Рассмотрим пример посимвольного ввода-вывода информации из/в поток:

//Листинг 1. Пример посимвольного чтения/записи данных из/в поток

char str[6]=“hello”, ch;

FILE * fp;

if((fp = fopen(“file.dat”,”w+”))==NULL)

printf(“Ошибка открытия потока”);

else

{ int i=0;

while (str[i]) fputc(str[i++], fp);

fseek(fp, 0, SEEK_SET);

while(feof(fp)==0)

{ ch=fgetc(fp);

fputc(ch, stdout);

}

fclose (fp);

}

В примере, рассмотренном в листинге 1, содержимое строки str сначала посимвольно выводится в поток, связанный с файлом file.dat, а затем считывается содержимое потока и выводится на экран через поток stdin.

Помимо непосредственно функций обмена данными с потоками (fputc, fgetc), в примере использованы не встречавшиеся до сих пор функции (fseek, feof). Эти вспомогательные функции позволяют следить за состоянием потока и изменять его. Обсудим их подробнее.

Позиционирование в потоке

Одной из характеристик потока является позиция чтения (записи). Фактически, она представляет собой простой счетчик байтов относительно начала файла. Именно в эту позицию помещаются даны при очередной попытке записи, из позиции чтения извлекаются данные при чтения информации.

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

long int ftell (FILE *stream);

Функция ftell возвращает текущую позицию указателя в файле. Она может завершиться с ошибкой, если файл не поддерживает позиционирование или если позиция не может быть представлена (не помещается) как long. В случае ошибки функция ftell возвращает -1. Еще один способ получить значения указателя потока – использовать функцию

int fgetpos(FILE *fp, fpos_t *pos);

Переместить позицию текущего указателя можно с использованием функции:

int fseek (FILE *stream, long int offset, int base);

Здесь stream – потоковая переменная. а параметры offset и base задают новое значение указателя: он устанавливается по смещению offset байт относительно позиции base, в качестве которой можно задать:

SEEK_SET-начало потока

SEEK_END-конец потока

SEEK_CUR-текущее положение.

Тогда вызов

fseek(fp, 0, SEEK_SET);

переместит указатель в начало потока, а вызов

fseek(fp, -4, SEEK_CUR);

возвратит его на 4 байта назад относительно текущей позиции.

На позицию указателя могут повлиять функции:

int fsetpos(FILE*fp, const fpos_t *pos);

void rewind(FILE *fp); //перемещает указатель к началу потока

Мониторинг состояния потока.

Размер потока (файла) не всегда известен заранее, поэтому встает проблема идентификации того момента, когда в процессе чтения данных будет достигнет конец потока. Для решения этой проблемы лучше всего использовать макрос

int feof(FILE *stream)

Он возвращает ненулевое значение в тот момент, когда при чтении данных из потока будет прочитан символ конца потока (EOF).

Такой принцип работы макроса делает очень популярным следующий цикл чтения данных из потока:

while( !feof(fp) ) //цикл пока не достигнут конец потока

{ // чтение из потока fp }

Так, например, можно посимвольно прочитать и вывести на экран все содержимое потока:

int c;

while ( (c = fgetc(fp)) != EOF )

{ putchar(c); }

В структуре FILE фиксируются ошибки, произошедшие при взаимодействии с потоком. Определить, что операции с потоком привели к ошибке можно с использованием макроса ferror(FILE*stream). Она возвращает ненулевое значение если возникла ошибка при работе с потоком. Код ошибки можно получить с помощью макроса errno.

FILE *pFile; char ch=’!’;

pFile = fopen ("d:\\myfile.txt","r");

fwrite(&ch, 1,1, pFile);

if(ferror(pFile))

printf("Произошла ошибка. Её код: %d", errno);

В рассмотренном пример на экране появится сообщение:

Произошла ошибка. Её код: 9

Если посмотреть содержимое файла errno.h, то можно убедиться, что код 9 соответствует макроопределению

#define EBADF 9

В данном случае, ошибка, очевидно, возникла из-за нарушения режима доступа: была предпринята попытка записать информацию в поток, открытый только на чтение.

Необходимо также отметить, что функция ferror реагирует на индикатор ошибки, устанавливающийся в структуре FILE в момент ее возникновения. Сама функция ferror этот индикатор не сбрасывает и повторный вызов ferror также обнаружит ошибку. Чтобы сбросить индикатор ошибки, необходимо вызвать функцию clearerr(FILE * stream).

Осуществлять ввод-вывод посимвольно зачастую будет нерационально, эффективнее считывать информацию из потока или помещать ее в поток более крупными блоками. Для текстовой информации можно оперировать строками с использованием функций:

сhar * fgets ( char *s, int n, FILE * stream) для чтения данных (где s – строка, в которую считываются данные из потока stream, n – максимальное количество считываемых символов). Данные считываются в строку начиная с позиции указателя потока и до конца строки в файле (первого символа '\n'). Параметр n необходим для предотвращения случаев переполнения строки s, когда объем считанных из файла данных превысит выделенный под строку участок памяти. Функция возвращает указатель на считанную строку.

int fputs ( char *s, FILE * fp) для записи содержимого строки s в поток. Функция возвращает количество записанных в поток символов.

Пример в листинге 2 иллюстрирует использование функций чтения и записи строки в поток.

//Листинг 2. Удалить из файла каждую вторую строку

FILE * fp, *fp2;

int i=0;

char filename[30], str[256];

fgets(filename, 30, stdin);

fp=fopen(filename,“r+”);

if(fp!=NULL)

{ fp2=fopen(“temp.txt”, “w+”);

while(!feof (fp) )

{ fgets(str, 256, fp); //Программа обрабатывает файлы

//со строками длиной до 255 символов

i++;

if(i==2)

{fputs(str, fp2); i=0;}

}

fclose(fp);fclose(fp2);

remove(filename);

rename(“temp.txt”, filename);

}

Для вывода в поток информации произвольного типа используется функция fprintf.

int fprintf( FILE * stream, const char * format, ... );

Эта функция выполняет форматированный вывод в поток. Она записывает в указанный поток stream последовательность символов в формате, указанном аргументом format. После параметра format функции необходимо указать список объектов, значения которых будут выведены в поток.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]