На этом шаге мы рассмотрим средства произвольного доступа к файлам.
В таблице 1 перечислены функции позиционирования в потоках данных C++.
Класс | Функция | Описание |
---|---|---|
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.
Константа | Описание |
---|---|
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 при достижении конца файла.
Управление позициями чтения и записи осуществляется разными функциями, но для стандартных потоков данных поддерживается общая позиция чтения/записи в одном потоковом буфере. Это важно, если буфер используется несколькими потоками данных.
На следующем шаге мы рассмотрим файловые дескрипторы.