Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

MS-DOS для программиста

© Александр Фролов, Григорий Фролов
Том 19, М.: Диалог-МИФИ, 1995, 253 стр.

[Назад] [Содеожание] [Дальше]

3.9. Потоки ввода и вывода

Стандартная библиотека Borland C++ содержит многочисленные функции, использующие собственный механизм буферизации при работе с файлами. Их часто называют функциями потокового ввода/вывода . Такую буферизацию не следует путать с буферизацией, выполняемой операционной системой. Имена всех этих функций начинаются на f - fopen , fclose , fprintf и т. д.

Функции потокового ввода/вывода хорошо описаны во многих учебных пособиях по языку программирования С, поэтому мы приведем лишь краткий обзор, делая акцент на особенностях их применения.

Существуют потоки , соответствующие стандартным устройствам ввода, вывода, вывода сообщений об ошибках, стандартному устройству последовательного ввода/вывода и стандартному устройство печати:

Поток Описание
stdin Стандартное устройство ввода
stdout Стандартное устройство вывода
stderr Стандартное устройство для вывода сообщений об ошибках
stdaux Стандартное последовательное устройство ввода/вывода
stdprn Стандартное печатающее устройство

Для использования этих потоков не требуются выполнять процедуру открытия и закрытия.

Для работы со стандартными устройствами ввода/вывода в библиотеках трансляторов языка программирования С имеется соответствующий набор функций, которые должны быть вам хорошо известны - printf, scanf , putchar и т. д. Мы не будем их описывать, так как объем книги ограничен.

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

При использовании функций потокового ввода/вывода файлы открываются функцией fopen , а закрываются функцией fclose . Эти функции не только открывают и закрывают файлы (получают и освобождают их идентификаторы), но и, соответственно, создают и уничтожают переменную типа FILE , описанную в файле stdio.h и связанную с данным файлом.

fopen

Для организации потокового ввода/вывода вначале необходимо при помощи функции fopen открыть файл. Функция fopen имеет следующий прототип:

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

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

Режим Операция, для выполнения которой открывается файл
"r" Чтение
"w" Запись
"a" Запись, данные будут добавляться в конец файла

К буквам r, w, a справа могут добавляться буквы t и b.

Буква t означает, что файл будет открыт в текстовом режиме, b - в двоичном. Для двоичного режима не выполняется обработка таких символов, как конец строки, конец файла и т. д.

Строка режима открытия файла может дополнительно содержать символ '+'. Этот символ означает, что для файла разрешены операции чтения и записи одновременно.

fclose

Для закрытия файлов, открытых для ввода или вывода потоком, должна использоваться функция fclose :

int fclose (FILE  *stream);

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

fdopen

Если вы открыли файл с помощью функции open , вы можете создать поток для этого файла, используя функцию fdopen :

FILE  *fdopen (int handle, char *mode);

В качестве первого параметра используется идентификатор файла, полученный от функции open . Второй параметр аналогичен параметру mode для функции fopen .

Для того чтобы закрыть поток, созданный функцией fdopen , необходимо использовать функцию fclose , а не close .

fileno

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

int fileno (FILE  *stream);

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

fwrite

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

size_t fwrite (void *buffer, size_t size,
  size_t count, FILE  *stream);

Эта функция записывает в файл stream блоки информации, каждый из которых имеет длину size байт. Количество блоков - count. Данные для записи расположены по адресу buffer.

Если файл открыт в текстовом режиме, каждый символ возврата каретки CR заменяется на два символа - возврата каретки CR и перевода строки LF.

Функция возвращает количество действительно записанных блоков информации без учета замены символа CR в текстовом режиме.

fread

Чтение данных потоком можно выполнить с помощью функции fread :

size_t fread (void *buffer, size_t size,
  size_t count, FILE  *stream);

Эта функция используется аналогично предыдущей. Для распознавания конца файла и обнаружения ошибок после вызова этой функции необходимо использовать функции feof и ferror.

Если при использовании функции fread вы задали значения параметров size или count, равные нулю, функция fread не изменяет содержимое буфера buffer.

fseek

Для позиционирования внутри файла, открытого потоком с помощью функции fopen, предназначена функция fseek :

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

В этой функции параметр offset задает новое содержимое указателя текущей позиции в файле stream, а параметр origin определяет способ задания новой позиции. Этот оператор может иметь значения, аналогичные используемым в функции lseek :

Значение Описание
SEEK_SET Абсолютное смещение от начала файла
SEEK_CUR Смещение относительно текущей позиции
SEEK_END Смещение относительно конца файла

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

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

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

  • если поиск выполняется со смещением offset, равным нулю, при любом значении параметра origin;
  • если поиск выполняется относительно начала файла, причем в качестве смещения offset используется значение, полученное специальной функцией ftell .

ftell

Функция ftell возвращает текущее значение указателя позиции для файла, или -1 при ошибке:

long ftell (FILE  *stream);

Пара функций ftell и fseek позволит вам правильно организовать позиционирование для файлов, открытых в текстовом режиме.

fgetpos , fsetpos

Есть еще одна возможность организовать позиционирование внутри файлов, открытых потоком - вызов пары функций fgetpos и fsetpos :

int fgetpos (FILE  *stream, fpos_t *pos);
int fsetpos (FILE  *stream, fpos_t *pos);

Эти две функции используют для запоминания и установки позиции переменную с типом fpos_t, определенным в файле stdio.h. Функция fgetpos записывает в эту переменную текущую позицию в потоке stream. Содержимое переменной затем может быть использовано для установки позиции в потоке с помощью функции fsetpos .

Обе эти функции возвращают нулевое значение в случае успешного завершения работы, или ненулевое - при ошибке.

Форматный ввод и вывод

Среди потоковых функций можно выделить группу функций форматного ввода и вывода. Это такие функции, как fputc , fgetc , fputs , fgets , fprintf , fscanf .

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

fputc

Для записи в поток отдельных байт используется функция fputc :

int fputc (int c, FILE  *stream);

Байт c записывается в поток stream начиная с текущей позиции. После записи текущая позиция увеличивается на единицу. Функция возвращает записанный байт или значение EOF , которое служит признаком ошибки.

fgetc

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

int fgetc (FILE  *stream);

Эта функция возвращает байт, считанный из потока stream и преобразованный к типу int. После чтения байта текущая позиция в потоке увеличивается на единицу.

При достижении конца файла или в случае ошибок функция fgetc возвращает значение EOF . Однако для проверки на ошибку или конец файла лучше пользоваться специальными функциями ferror и feof. Если вы открыли файл в двоичном режиме, единственный способ определить момент достижения конца файла - использовать функцию feof, так как значение константы EOF может находиться в любом месте двоичного файла.

fputs и fgets

Для работы со строками предназначены функции fputs и fgets .

Функция fputs предназначена для вывода строки в файл, открытый потоком:

int fputs (char *string, FILE  *stream);

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

Для ввода строк из текстового файла удобна функция fgets :

int fgets (char *string, int n, FILE  *stream);

Функция читает байты из потока stream и записывает их в блок памяти, указатель на который задан параметром string, до тех пор, пока не произойдет одно из двух событий - будет прочитан символ новой строки '\n' или количество прочитанных символов не станет равно n-1.

После того, как байты будут прочитаны в блок памяти, в конец строки образованной из этих байт, функция запишет двоичный нуль. Если был прочитан символ новой строки '\n', он тоже будет записан.

Для анализа достижения конца файла или ошибок необходимо использовать функции feof и ferror.

fprintf

Для форматного вывода в файл содержимого переменных удобно использовать функцию fprintf :

int fprintf (FILE  *stream, char *format [,arg]...);

Эта функция аналогична хорошо известной вам функции форматного вывода на экран printf, с которой обычно начинают изучение языка программирования С. Вспомните такую программу:

#include <stdio.h>
main() 
{
  printf("Hello, world!");
}

Функция fprintf имеет дополнительно один параметр - stream, который определяет выходной поток.

После завершения работы функция возвращает количество записанных байт или отрицательную величину, если при записи произошла ошибка.

fscanf

Для форматного ввода информации из файла можно использовать функцию fscanf , аналогичную известной вам функции scanf :

int fscanf (FILE  *stream, char *format [,arg]...);

Эта функция читает данные, начиная с текущей позиции в потоке stream, в переменные, определенные аргументами arg. Каждый аргумент должен являться указателем на переменную, соответствующую типу, определенному в строке формата format.

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

При достижении конца файла функция возвращает значение EOF . Если функция возвратила нулевое значение, это означает, что преобразование полей не производилось.

Буферизация потоков

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

setbuf

Функция setbuf позволяет вам заменить системный буфер на свой собственный:

void setbuf (FILE  *stream, char *buffer);

Параметр buffer должен указывать на подготовленный пользователем массив, имеющий размер BUFSIZ байт. Константа BUFSIZ описана в файле stdio.h.

setvbuf

Функция setvbuf позволяет программе не только указать свой буфер, но и задать его размер:

int setvbuf (FILE  *stream, char *buffer, int mode,
  size_t size);

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

Параметр buffer должен указывать на подготовленный программой буфер размером size байт. Этот буфер будет использоваться для работы с потоком stream.

Параметр mode может принимать значения _IOFBF , _IOLBF , _IONBF . Если mode равен _IOFBF или _IOLBF, параметр size указывает размер буфера. Если параметр mode равен _IONBF, буферизация не используется, а параметры buffer и size игнорируются.

Параметры _IOFBF и _IOLBF эквивалентны друг другу.

Если в качестве адреса буфера buffer задать значение NULL, функция автоматически закажет буфер размером size.

Функция setvbuf возвращает нуль при успешном завершении и ненулевую величину, если указан неправильный параметр mode или неправильный размер буфера size.

Для чего может понадобиться изменение размера буфера?

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

fflush

При закрытии потока функцией fclose содержимое буфера записывается на диск. Если программе необходимо выполнить запись содержимого буфера на диск без закрытия файла, она может воспользоваться функцией fflush :

int fflush (FILE  *stream);

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

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

Программа BUFCOPY

В качестве примера приведем текст программы BUFCOPY (листинг 3.7), копирующей содержимое текстового файла.

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


Листинг 3.7. Файл bufcopy\ bufcopy.cpp


#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void filecpy(FILE  *stream_from, FILE *stream_to);

// Буферы для файлов
char buf1[BUFSIZ * 10];
char buf2[BUFSIZ * 10];

int main(int argc, char *argv[])
{
  time_t start, end;
  FILE  *stream_from, *stream_to;

  if(argc < 3)
  {
    printf("Задайте имена файлов!\n");
    return(-1);
  }

  // Открываем файлы и используем для копирования
  // буфер стандартного размера
  if((stream_from = fopen (argv[1], "rt")) == NULL)
  {
    printf("Задайте имя входного файла!\n");
    return(-1);
  }

  stream_to = fopen (argv[2], "wt+");

  // Определяем время начала копирования
  start = clock();

  // Выполняем копирование файла
  filecpy(stream_from,stream_to);

  // Определяем время завершения копирования
  end = clock();

  // Выводим время копирования при использовании
  // буферов стандартного размера
  printf("Время копирования: %5.1f "
    "Размер буфера, байтов: %d\n",
    ((float)end - start) / CLK_TCK, BUFSIZ);

  // Задаем свой буфер большего размера
  if((stream_from = fopen (argv[1], "rt")) == NULL)
    return(-1);

  stream_to = fopen (argv[2], "wt+");

  // Устанавливаем буферы как для входного,
  // так и для выходного файлов
  setvbuf (stream_from, buf1, _IOFBF , sizeof(buf1));
  setvbuf (stream_to,   buf2, _IOFBF , sizeof(buf2));

  // Копируем файл и измеряем продолжительность
  // копирования
  start = clock();
  filecpy(stream_from,stream_to);
  end = clock();

  printf("Время копирования: %5.1f "
    "Размер буфера: %d\n",
    ((float)end - start) / CLK_TCK, BUFSIZ * 10);

  // Копируем без использования буферизации
  if((stream_from = fopen (argv[1], "rt")) == NULL)
    return(-1);

  stream_to = fopen (argv[2], "wt+");
  setvbuf (stream_from, NULL, _IONBF , 0);
  setvbuf (stream_to,   NULL, _IONBF , 0);

  start = clock();
  filecpy(stream_from,stream_to);
  end = clock();

  printf("Время копирования: %5.1f "
    "Буферизация  не используется\n",
    ((float)end - start) / CLK_TCK);

  return(0);
}

// Функция для копирования файлов
void filecpy(FILE  *stream_from, FILE *stream_to)
{
  char linebuf[256];

  // Цикл копирования. Условие выхода из цикла -
  // достижение кнеца входного файла
  while(!feof(stream_from))
  {
    // Читаем в буфер linebuf одну строку
    if(fgets (linebuf, 255, stream_from) == NULL)
      break;

    // Записываем содержимое буфера linebuf
    // в выходной файл
    if(fputs (linebuf, stream_to) != 0)
      break;
  }

  // Закрываем входной и выходной файлы
  fclose (stream_from);
  fclose (stream_to);
}

[Назад] [Содеожание] [Дальше]