netlib.narod.ru< Назад | Оглавление | Далее >

7.5. Доступ к файлам

Во всех предыдущих примерах мы имели дело со стандартным вводом и стандартным выводом, которые для программы автоматически предопределены операционной системой конкретной машины.

Следующий шаг — научиться писать программы, которые имели бы доступ к файлам, заранее не подсоединенным к программам. Одна из программ, в которой возникает такая необходимость, — это программа cat, объединяющая несколько именованных файлов и направляющая результат в стандартный вывод. Функция cat часто применяется для выдачи файлов на экран, а также как универсальный «коллектор» файловой информации для тех программ, которые не имеют возможности обратиться к файлу по имени. например, команда

  cat x.c y.c

направит в стандартный вывод содержимое файлов x.c и y.c (и ничего более).

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

На этот счет имеются простые правила. Для того чтобы можно было читать из файла или писать в файл, он должен быть предварительно открыт с помощью библиотечной функции fopen. Функция fopen получает внешнее имя, например x.c или y.c, после чего осуществляет некоторые организационные действия и «переговоры» с операционной системой (технические детали которых здесь не рассматриваются) и возвращает указатель, используемый в дальнейшем для доступа к файлу.

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

Единственное, что требуется для определения указателя файла, — это задать описания такого, например, вида:

  FILE *fp;
  FILE *fopen(char *name, char *mode);

Это говорит, что fp — это указатель на FILE, а fopen возвращает указатель на FILE. Заметим, что FILE — это имя типа, наподобие int, а не тег структуры. Оно определено с помощью инструкции typedef. (Детали того, как можно реализовать функцию fopen в системе UNIX, приводятся в параграфе 8.5.)

Обращение к fopen в программе может выглядеть следующим образом:

  fp = fopen(name, mode);

Первый аргумент — строка, содержащая имя файла. Второй аргумент передает информацию о режиме доступа к файлу. Это тоже строка: в ней указывается, каким образом пользователь намерен использовать файл. Возможны следующие режимы: чтение (read — r), запись (write — w) и добавление (append — a), т.е. запись информации в конец уже существующего файла. В некоторых системах различаются текстовые и бинарные файлы; в случае последних в строку режима необходимо добавить букву b (binary — бинарный).

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

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

  int getc(FILE *fp)

Функция getc возвращает следующий символ из потока, на который указывает *fp; в случае исчерпания файла или ошибки она возвращает EOF.

Функция putc записывает символ c в файл fp

  int putc(int c, FILE *fp)

и возвращает записанный символ или EOF в случае ошибки. Аналогично getchar и putchar, реализация getc и putc может быть выполнена в виде макросов, а не функций.

При запуске Си-программы операционная система всегда открывает три файла и обеспечивает три файловые ссылки на них. Этими файлами являются: стандартный ввод, стандартный вывод и стандартный файл ошибок; соответствующие им указатели называются stdin, stdout и stderr; они описаны в <stdio.h>. Обычно stdin соотнесен с клавиатурой, а stdout и stderr — с экраном. Однако, stdin и stdout можно связать с файлами или, используя конвейерный механизм, соединить напрямую с другими программами, как это описывалось в параграфе 7.1.

С помощью getc, putc, stdin и stdout функции getchar и putchar теперь можно определить следующим образом:

  #define getchar()   getc(stdin)
  #define putchar(c)  putc((c), stdout)

Форматный ввод-вывод файлов можно построить на функциях fscanf и fprintf. Они идентичны scanf и printf с той лишь разницей, что первым их аргументом является указатель на файл, для которого осуществляется ввод-вывод, формат же указывается вторым аргументом.

  int fscanf(FILE *fp, char *format, ...)
  int fprintf(FILE *fp, char *format, ...)

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

  #include <stdio.h>

  /* cat: конкатенация файлов, версия 1 */
  main(int argc, char *argv[])
  {
      FILE *fp;
      void filecopy(FILE *, FILE *);

      if (argc == 1)  /* нет аргументов, копируем стандартный ввод */
          filecopy(stdin, stdout);
      else
          while (--argc > 0)
              if ((fp = fopen(*++argv, "r")) == NULL) {
                  printf("cat: не могу открыть файл %s\n", argv);
                  return 1;
              } else {
                  filecopy(fp, stdout);
                  fclose(fp);
              }
      return 0;
  }

  /* filecopy: копирует файл ifp в файл ofp */
  void filecopy(FILE *ifp, FILE *ofp)
  {
      int c;

      while ((c = getc(ifp)) != EOF)
          putc (c, ofp);
  }

Файловые указатели stdin и stdout представляют собой объекты типа FILE*. Это константы, а не переменные, следовательно, им нельзя ничего присваивать.

Функция

  int fclose(FILE *fp)

обратная по отношению к fopen; она разрывает связь между файловым указателем и внешним именем (которая раньше была установлена с помощью fopen), освобождая тем самым этот указатель для других файлов. Так как в большинстве операционных систем количество одновременно открытых одной программой файлов ограничено, то файловые указатели, если они больше не нужны, лучше освобождать, как это и делается в программе cat. Есть еще одна причина применить fclose к файлу вывода, — это необходимость «опорожнить» буфер, в котором putc накопила предназначенные для вывода данные. При нормальном завершении работы программы для каждого открытого файла fclose вызывается автоматически. (Вы можете закрыть stdin и stdout, если они вам не нужны. Воспользовавшись библиотечной функцией freopen их можно восстановить.)


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz