При чтении любого (включая пробельные) символа, кроме конца файла, мы увеличиваем переменную cnt на 1 и копируем прочитанный символ на терминал, чтобы вовремя заметить ошибки в работе программы.
Встретив символ новой строки, мы записываем текущее значение cnt в inOut. Как только будет достигнут конец файла, цикл прекращается. Окончательное значение cnt выводится в файл и на экран.
Программа компилируется без ошибок и кажется правильной. Но если подать на вход несколько фраз из романа "Моби Дик" Германа Мелвилла:
Call me Ishmael. Some years ago, never mind
how long precisely, having little or no money
in my purse, and nothing particular to interest
me on shore, I thought I would sail about a little
and see the watery part of the world. It is a
way I have of driving off the spleen, and
regulating the circulation.
то получим такой результат:
[ 0 ]
Программа не вывела ни одного символа, видимо, полагая, что файл пуст. Проблема в том, что файл открыт для дозаписи и потому позиционирован на конец. При выполнении инструкции
inOut.get( ch );
мы читаем конец файла, цикл while завершается и выводится значение 0.
Хотя мы допустили серьезную ошибку, исправить ее совсем несложно, поскольку причина понятна. Надо лишь перед чтением переустановить файл на начало. Это делается с помощью обращения:
inOut.seekg( 0 );
Запустим программу заново. На этот раз она печатает:
Call me Ishmael. Some years ago, never mind
[ 45 ]
Как видим, выводится лишь первая строка текста и счетчик для нее, а оставшиеся шесть строк проигнорированы. Ну что ж, исправление ошибок – неотъемлемая часть профессии программиста. А проблема опять в том, что файл открыт в режиме дозаписи. Как только мы в первый раз вывели cnt, файл оказался позиционирован на конец. При следующем обращении к get() читается конец файла, и цикл while снова завершается преждевременно.
Нам необходимо встать на ту позицию в файле, где мы были перед выводом cnt. Для этого понадобятся еще две инструкции:
// запомнить текущую позицию
ios_base::pos_type mark = inOut.tellg();
inOut cnt sp;
inOut.seekg( mark ); // восстановить позицию
После повторной компиляции программа выводит на экран ожидаемый результат. Но посмотрев на выходной файл, мы обнаружим, что она все еще не вполне правильна: окончательное значение счетчика есть на экране, но не в файле. Оператор вывода, следующий за циклом while, не был выполнен.
Дело в том, что inOut находится в состоянии "конец файла", в котором операции ввода и вывода не выполняются. Для решения проблемы необходимо сбросить это состояние с помощью функции-члена clear():
inOut.clear(); // обнулить флаги состояния
Окончательный вариант программы выглядит так:
#include iostream
#include fstream
int main()
{
fstream inOut( "copy.out", ios_base::in|ios_base::app );
int cnt=0;
char ch;
inOut.seekg(0);
while ( inOut.get( ch ))
{
cout.put( ch );
cnt++;
if ( ch == '\n' )
{
// запомнить текущую позицию
ios_base::pos_type mark = inOut.tellg();
inOut cnt ' ';
inOut.seekg( mark ); // восстановить позицию
}
}
inOut.clear();
inOut cnt endl;
cout "[ " cnt " ]\n";
return 0;
}
Вот теперь – наконец-то! – все правильно. При реализации этой программы было необходимо явно сформулировать поведение, которое мы собирались поддержать. А каждое наше исправление было реакцией на выявившуюся ошибку вместо анализа проблемы в целом.
Упражнение 20.12
Пользуясь операторами вывода для класса Date, которые вы определили в упражнении 20.7, или для класса CheckoutRecord из упражнения 20.8 (см. раздел 20.4), напишите программу, позволяющую создать файл и писать в него.
Упражнение 20.13
Напишите программу для открытия и чтения файла, созданного в упражнении 20.12. Выведите содержимое файла на стандартный вывод.
Упражнение 20.14
Напишите программу для открытия файла, созданного в упражнении 20.12, для чтения и дозаписи. Выведите экземпляр класса Date или CheckoutRecord:
(a) в начало файла
(b) после второго из существующих объектов
(c) в конец файла
20.7. Состояния потока