Шаг 496.
Библиотека STL.
Ввод-вывод с использованием потоковых классов. Произвольный доступ к файлам

    На этом шаге мы рассмотрим средства произвольного доступа к файлам.

    В таблице 1 перечислены функции позиционирования в потоках данных C++.

Таблица 1. Функции позиционирования в потоках данных
Класс Функция Описание
basic_istream<> tellg() Возвращает текущую позицию чтения
seekg(pos) Устанавливает абсолютную позицию чтения
seekg(offset, rpos) Устанавливает относительную позицию чтения
basic_ostream<> tellp() Возвращает текущую позицию записи
seekp(pos) Устанавливает абсолютную позицию записи
seekp(offset, rpos) Устанавливает относительную позицию записи

    Позиционирование чтения и записи выполняется отдельными функциями (суффикс "g" означает "get", а суффикс "р" - "put"). Функции чтения определяются в классе basic_istream, а функции записи - в классе basic_ostream. Тем не менее не все потоковые классы поддерживают позиционирование. Например, для потоков данных cin, cout и cerr позиционирование не определено. Операции файлового позиционирования определяются в базовых классах, потому что обычно используются ссылки на объекты типов istream и ostream.

    Функции seekg() и seekp() могут вызываться для абсолютных или относительных позиций. Функции tellg() и tellp() возвращают абсолютную позицию в виде значения типа pos_type. Это значение не является целым числом или индексом, задающим позицию символа, поскольку логическая позиция может отличаться от фактической. Например, в текстовых файлах MS-DOS символы новой строки хранятся в файлах в виде двух символов, хотя логически они соответствуют только одному символу. Кроме того, ситуация дополнительно усложняется при многобайтовой кодировке символов.

    Разобраться в точном определении типа pos_type непросто: стандартная библиотека C++ определяет глобальный класс шаблона fpos<> для представления позиций в файлах. На базе класса fpos<> определяются типы streampos (для потоков данных char) и wstreampos (для потоков даииых wchar_t). Эти типы используются для определения pos_type соответствующих классов трактовок. Наконец, переменная типа pos_type класса трактовок требуется для определения типа pos_type соответствующих потоковых классов. Следовательно, позиции в потоке данных также могут представляться типом streampos, ио использовать типы long и unsigned long было бы неправильно, потому что streampos не является целочисленным типом (а точнее, перестал им быть). Пример:

// Сохранение текущей позиции
std::ios::pos_type pos = file.tellg();
// Переход к позиции, хранящейся в pos 
file.seekg(pos);

    Следующие объявления эквивалентны:

std::ios::pos_type pos; 
std::streampos pos;

    В версиях с относительным позиционированием смещение задается по отношению к трем позициям, для которых определены соответствующие константы (таблица 2). Константы определяются в классе ios_base и относятся к типу seekdir.

Таблица 2. Константы относительных позиций
Константа Описание
beg Смещение задается относительно начала файла
cur Смещение задается относительно текущей позиции
end Смещение задается относительно конца файла

    Смещение относится к типу off_type, который представляет собой косвенное определение для streamoff. По аналогии с pos_type тип streamoff используется для определения off_type в классе трактовок и в потоковых классах. Тем не менее streamoff является целым знаковым типом, поэтому смещение в потоке данных может задаваться целым числом. Пример:

// Позиционирование в начало файла 
file.seekg (0, std::ios::beg);
// Позиционирование на 20 символов вперед 
file.seekg (20, std::ios::cur);
// Позиционирование на 10 символов от конца файла 
file.seekg (-10, std::ios::end);

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

    Следующий пример демонстрирует использование функции seekg(). В ием определена функция, которая дважды выводит содержимое файла:

//---------------------------------------------------------------------------

#include <vcl.h>
// Заголовочные файлы для файлового ввода-вывода
#include <fstream>
#include <iostream>

#include <conio.h>   //необходимо для getch()

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;

std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

void printFileTwice (const char* filename)
{
  // Открытие файла
  std::ifstream file(filename);

  // Первый вывод содержимого
  std::cout << file.rdbuf();

  // Возврат к началу файла
  file.seekg(0);

  // Второй вывод содержимого
  std::cout << file.rdbuf();
}


int main (int argc, char* argv[])
{
  // Двукратный вывод всех файлов, переданных в командной строке
  for (int i=1; i<argc; ++i) {
      printFileTwice(argv[i]);
  }
  cout << ToRus("Работа выполнена");

  getch();
  return 0;
}

//---------------------------------------------------------------------------
Текст этого примера можно взять здесь.

    Результат работы программы выглядит так:


Рис.1. Результат работы приложения

    Обратите внимание на вывод содержимого файла функцией file.rdbuf(). Операция выполняется прямо с потоковым буфером и не изменяет состояния потока данных. Если содержимое file выводится фуикциями потокового интерфейса, такими как getline() (смотри 479 шаг), вам придется сбросить состояние файла file функцией clear() перед тем, как выполнять с ним любые операции, включая изменение позиции чтения, поскольку эти функции устанавливают флаги ios::eofbit и ios::failbit при достижении конца файла.

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

    На следующем шаге мы рассмотрим файловые дескрипторы.




Предыдущий шаг Содержание Следующий шаг