Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Вполне логично, что для класса istream, а также производных от него классов ifstream и istringstream (см. раздел 8.1) можно использовать только версии g, а для классов ostream и классов ofstream и ostringstream, производных от него, можно использовать только версии p. Классы iostream, fstream и stringstream способны читать и записывать данные в поток, поэтому для них можно использовать обе версии, g и p.
Существует только один маркерТот факт, что библиотека различает версии функций seek() и tell() для чтения и записи, может ввести в заблуждение. Хотя библиотека и различает эти функции, в файле существует только один маркер, т.е. нет разных маркеров для чтения и записи.
Когда речь идет о потоке только ввода или вывода, различие не столь очевидно. В таких потоках можно использовать версии только g или p. Если попытаться вызвать функцию tellp() для объекта класса ifstream, компилятор сообщит об ошибке. Аналогично он поступит при попытке вызвать функцию seekg() для объекта класса ostringstream.
Типы fstream и stringstream допускают чтение и запись в тот же поток. У них есть один буфер для хранения подлежащих чтению и записи данных, а также один маркер, обозначающий текущую позицию в буфере. Библиотечные функции версий g и p используют тот же маркер позиции.
Поскольку существует только один маркер, для переустановки маркера при каждом переключении между чтением и записью следует применять функцию seek().
Перемещение маркераИмеются две версии функции установки позиции: одна обеспечивает переход к указанной позиции в файле, а другая осуществляет смещение от текущей позиции.
// установка маркера в заданную позицию
seekg(new_position); // установить маркер чтения в позицию pos_type
seekp(new_position); // установить маркер записи в позицию pos_type
// смещение позиции на указанную дистанцию от текущей
seekg(offset, from); // установить дистанцию смещения маркера чтения
seekp(offset, from); // от from; offset имеет тип off_type
Возможные значения параметра from перечислены в табл. 17.21.
Аргументы new_position и offset этих функций имеют машинно-зависимые типы pos_type и off_type соответственно. Они определены в классах istream и ostream. Тип pos_type представляет позицию файла, а тип off_type — смещение от этой позиции. Значение типа off_type может быть положительным или отрицательным, что соответствует смещению вперед или назад.
Доступ к маркеруФункции tellg() и tellp() возвращают значение типа pos_type, обозначающее текущую позицию в потоке. Эти функции обычно используются для того, чтобы запомнить позицию и впоследствии вернуться к ней:
// запомнить текущую позицию записи в переменную mark
ostringstream writeStr; // поток вывода в строку
ostringstream::pos_type mark = writeStr.tellp();
// ...
if (cancelEntry)
// возврат к отмеченной позиции
writeStr.seekp(mark);
Чтение и запись в тот же файлРассмотрим пример программы, которая читает файл и записывает в его конец новую строку, содержащую относительную позицию начала каждой строки. Предположим, например, что работать придется со следующим файлом.
Abcd
efg
hi
j
Модифицированный программой файл должен выглядеть следующим образом.
Abcd
efg
hi
j
5 9 12 14
Обратите внимание: программа не записывает смещение для первой строки, она всегда начинается с позиции 0. Обратите также внимание на то, что смещения должны также учитывать невидимый символ новой строки, завершающий каждую строку. И наконец, последнее число в выводе — смещение для строки, с которой начинается вывод. При включении этого смещения в вывод можно отличить свой вывод от первоначального содержимого файла. Можно прочитать последнее число в полученном файле и установить смещение так, чтобы получить позицию начала вывода.
Наша программа будет читать файл построчно. Для каждой строки значение счетчика будет увеличиваться на размер только что прочитанной строки. Этот счетчик содержит смещение, с которого начинается следующая строка:
int main() {
// открыть файл для ввода и вывода, а затем перейти в его конец
// аргументы режима файла приведены в табл. 8.4
fstream inOut("copyOut",
fstream::ate | fstream::in | fstream::out);
if (!inOut) {
cerr << "Unable to open file!" << endl;
return EXIT_FAILURE; // EXIT_FAILURE см. p. 6.3.2
}
// inOut открыт в режиме ate, поэтому исходной позицией файла будет
// его конец
auto end_mark = inOut.tellg(); // запомнить позицию первоначального
// конца файла
inOut.seekg(0, fstream::beg); // перейти к началу файла
size_t cnt = 0; // счетчик количества байтов
string line; // содержит каждую строку ввода
// пока нет ошибки и исходные данные читаются
while (inOut && inOut.tellg() != end_mark
&& getline(inOut, line)) { // и можно получить следующую строку
cnt += line.size() + 1; // добавить 1 для новой строки
auto mark = inOut.tellg(); // запомнить позицию чтения
inOut.seekp(0, fstream::end); // установить маркер записи в конец
inOut << cnt; // записать общую длину
// вывести разделитель, если это не последняя строка
if (mark != end_mark) inOut << " ";
inOut.seekg(mark); // восстановить позицию чтения
}
inOut.seekp(0, fstream::end); // перейти к концу
inOut << "n"; // вывести символ новой строки в конце файла
return 0;
}
Эта программа открывает поток fstream в режимах in, out и ate (см. табл. 8.4). Первые два режима означают, что предполагается чтение и запись в тот же файл. Режим ate означает, что начальной позицией открытого файла будет его конец. Как обычно, необходимо удостовериться, что файл открыт корректно, если это не так, следует выйти из программы (см. раздел 6.3.2).
Поскольку программа пишет в свой исходный файл, нельзя использовать конец файла как признак прекращения чтения. Цикл должен закончиться по достижении конца первоначального ввода. В результате сначала следует запомнить первоначальную позицию конца файла. Так как файл открыт в режиме ate, поток inOut уже установлен в конец. Сохраним текущую (т.е. первоначальную) позицию конца файла в переменной end_mark. Запомнив конечную позицию, маркер чтения следует установить в начало файла, чтобы можно было приступить к чтению данных.
Цикл while имеет три условия выхода: сначала проверяется допустимость потока; если это так, то проверяется, не достигнут ли конец исходных данных. Для этого текущая позиция чтения, возвращаемая функцией tellg(), сравнивается с позицией, заранее сохраненной в переменной end_mark. И наконец, если обе проверки пройдены успешно, происходит вызов функции getline(), которая читает следующую строку из файла. Если вызов функции getline() успешен, выполняется тело цикла.
Тело цикла начинается с запоминания текущей позиции в переменной mark. Она сохраняется для возвращения после записи следующего относительного смещения. Вызов функции seekp() переводит маркер записи в конец файла. Выводится значение счетчика, а затем функция seekg() возвращается к позиции, сохраненной в переменной mark. Восстановив положение маркера, можно снова проверить условие выхода из цикла while.
Каждая итерация цикла выводит смещение следующей строки. Поэтому последняя итерация цикла заботится о записи смещения последней строки. Однако в конец файла следует еще записать символ новой строки. Как и в других случаях записи, для позиционирования в конец файла перед выводом новой строки происходит вызов функции seekp().
Упражнения раздела 17.5.3Упражнение 17.39. Напишите собственную версию программы, представленной в этом разделе.
Резюме
В этой главе рассматривались дополнительные операции ввода-вывода и четыре библиотечных типа: кортеж, набор битов, регулярные выражения и случайные числа.