Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

1052

 

 

#include <iostream>

 

 

 

 

 

 

#include "WordCount.h"

 

 

 

/* необходимо модифицировать определение класса WordCount, чтобы

 

 

 

оператор ввода был другом

 

 

 

class WordCount {

 

 

 

friend ostream& operator<<( ostream&, const WordCount& );

 

 

 

friend istream& operator>>( istream&, const WordCount& );

 

 

 

*/

 

 

 

istream&

 

 

 

operator >>( istream &is, WordCount &wd )

 

 

 

{

 

 

 

/* формат хранения объекта WordCount:

 

 

 

* <2> строка

 

 

 

* <7,3> <12,36>

 

 

 

*/

 

 

 

int ch;

 

 

 

/* прочитать знак '<'. Если его нет,

 

 

 

* перевести поток в ошибочное состояние и выйти

 

 

 

*/

 

 

 

if ((ch = is.get()) != '<' )

 

 

 

{

 

 

 

// is.setstate( ios_base::badbit );

 

 

 

return is;

 

 

 

}

 

 

 

// прочитать длину

 

 

 

int occurs;

 

 

 

is >> occurs;

 

 

 

// читать до обнаружения >; ошибки не контролируются

 

 

 

while ( is && (ch = is.get()) != '>' ) ;

 

 

 

is >> wd._word;

 

 

 

// прочитать позиции вхождений;

 

 

 

// каждая позиция имеет формат: < строка, колонка >

 

 

 

for ( int ix = 0; ix < occurs; ++ix )

 

 

 

{

 

 

 

int line, col;

 

 

 

// извлечь значения

 

 

 

while (is && (ch = is.get())!= '<' ) ;

 

 

 

is >> line;

 

 

 

while (is && (ch = is.get())!= ',' ) ;

 

 

 

is >> col;

 

 

 

while (is && (ch = is.get())!= '>' ) ;

 

 

 

wd._occurList.push_back( Location( line, col ));

 

 

 

}

 

 

 

return is;

 

 

 

}

 

 

 

 

 

 

 

На этом примере показан целый ряд проблем, имеющих отношение к возможным

 

ошибочным состояниям входного потока:

 

С++ для начинающих

1053

 

поток, чтение из которого невозможно из-за

неправильного формата,

 

 

переводится в состояние fail:

 

 

is.setstate( ios_base::failbit );

 

 

операции вставки и извлечения из потока, находящегося в ошибочном

 

 

 

состоянии, не работают:

 

while (( ch = is.get() ) != lbrace)

Инструкция зациклится, если объект istream будет находиться в ошибочном состоянии. Поэтому перед каждым обращением к get() проверяется отсутствие

// проверить, находится ли поток "is" в "хорошем" состоянии

ошибки:

while ( is && ( ch = is.get() ) != lbrace)

Если объект istream не в хорошемсостоянии, то его значение будет равно false. (О состояниях потока мы расскажем в разделе 20.7.)

Данная программа считывает объект класса WordCount, сохраненный оператором вывода

#include <iostream> #include "WordCount.h"

int main()

{

WordCount readIn;

// operator>>( cin, readIn ) cin >> readIn;

if ( !cin ) {

cerr << "Ошибка ввода WordCount" << endl; return -1;

}

// operator<<( cout, readIn ) cout << readIn << endl;

из предыдущего раздела:

}

Выводится следующее:

<10> rosebud

<14,2>

<34,6>

<49,7>

<67,5>

<11,3>

<11,8>

<81,2>

<82,3>

<91,4>

<97,8>

 

 

Упражнение 20.9

Оператор ввода класса WordCount сам читает объекты класса Location. Вынесите этот код в отдельный оператор ввода класса Location.

С++ для начинающих

1054

Упражнение 20.10

Реализуйте оператор ввода для класса Date из упражнения 20.7 в разделе 20.4.

Упражнение 20.11

Реализуйте оператор ввода для класса CheckoutRecord из упражнения 20.8 в разделе

20.4.

20.6. Файловый ввод/вывод

Если программе необходимо работать с файлом, то следует включить в нее заголовочный файл fstream (который в свою очередь включает iostream):

#include <fstream>

Если файл будет использоваться только для вывода, мы определяем объект класса ofstream. Например:

ofstream outfile( "copy.out", ios::base::out );

Передаваемые конструктору аргументы задают имя открываемого файла и режим открытия. Файл типа ofstream может быть открыт либо по умолчанию в режиме вывода (ios_base::out), либо в режиме дозаписи (ios_base::app). Такое определение

// по умолчанию открывается в режиме вывода

файла outfile2 эквивалентно приведенному выше: ofstream outfile2( "copy.out" );

Если в режиме вывода открывается существующий файл, то все хранившиеся в нем данные пропадают. Если же мы хотим не заменить, а добавить данные, то следует открывать файл в режиме дозаписи: тогда новые данные помещаются в конец. Если указанный файл не существует, то он создается в любом режиме.

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

if ( ! outfile ) { // открыть файл не удалось

cerr << "не могу открыть "copy.out" для записи\n"; exit( -1 );

файл был успешно открыт:

}

Класс ofstream является производным от ostream. Все определенные в ostream

char ch = ' ';

outFile.put( '1' ).put( ')' ).put( ch );

операции применимы и к ofstream. Например, инструкции outFile << "1 + 1 = " << (1 + 1) << endl;

С++ для начинающих

1055

выводят в файл outFile последовательность символов:

1) 1 + 1 = 2

Следующая программа читает из стандартного ввода символы и копирует их в

#include <fstream>

int main()

{

// открыть файл copy.out для вывода ofstream outFile( "copy.out" );

if ( ! outFile ) {

cerr << "Не могу открыть 'copy.out' для вывода\n"; return -1;

}

char ch;

while ( cin.get( ch ) ) outFile.put( ch );

стандартный вывод:

}

К объекту класса ofstream можно применять и определенные пользователем экземпляры оператора вывода. Данная программа вызывает оператор вывода класса WordCount из

#include <fstream> #include "WordCount.h"

int main()

{

//открыть файл word.out для вывода ofstream oFile( "word.out" );

//здесь проверка успешности открытия ...

//создать и вручную заполнить объект WordCount WordCount artist( "Renoir" );

artist.found( 7, 12 ); artist.found( 34, 18 );

//вызывается оператор <<(ostream&, const WordCount&); oFile << artist;

предыдущего раздела:

}

Чтобы открыть файл только для чтения, применяется объект класса ifstream, производного от istream. Следующая программа читает указанный пользователем файл и копирует его содержимое на стандартный вывод:

С++ для начинающих

1056

#include <fstream> #include <string>

int main()

{

cout << "filename: "; string file_name;

cin >> file_name;

// открыть файл для ввода

ifstream inFile( file_name.c_str() );

if ( !inFile ) {

cerr << "не могу открыть входной файл: "

<< file_name << " -- аварийный останов!\n"; return -1;

}

char ch;

while ( inFile.get( ch )) cout.put( ch );

}

Программа, показанная ниже, читает наш текстовый файл alice_emma, фильтрует его с помощью функции filter_string() (см. раздел 20.2.1, где приведены текст этой функции и содержимое файла), сортирует строки, удаляет дубликаты и записывает результат на стандартный вывод:

С++ для начинающих

1057

 

 

#include <fstream>

 

 

 

 

 

 

#include <iterator>

 

 

 

#include <vector>

 

 

 

#include <algorithm>

 

 

 

template <class InputIterator>

 

 

 

void filter_string( InputIterator first, InputIterator last,

 

 

 

string filt_elems = string("\",?."))

 

 

 

{

 

 

 

for ( ; first != last; first++ )

 

 

 

{

 

 

 

string::size_type pos = 0;

 

 

 

while (( pos = (*first).find_first_of( filt_elems, pos ))

 

 

 

!= string::npos )

 

 

 

(*first).erase( pos, 1 );

 

 

 

}

 

 

 

}

 

 

 

int main()

 

 

 

{

 

 

 

ifstream infile( "alice_emma" );

 

 

 

istream_iterator<string> ifile( infile );

 

 

 

istream_iterator<string> eos;

 

 

 

vector< string > text;

 

 

 

copy( ifile, eos, inserter( text, text.begin() ));

 

 

 

string filt_elems( "\",.?;:" );

 

 

 

filter_string( text.begin(), text.end(), filt_elems );

 

 

 

vector<string>::iterator iter;

 

 

 

sort( text.begin(), text.end() );

 

 

 

iter = unique( text.begin(), text.end() );

 

 

 

text.erase( iter, text.end() );

 

 

 

ofstream outfile( "alice_emma_sort" );

 

 

 

iter = text.begin();

 

 

 

for ( int line_cnt = 1; iter != text.end();

 

 

 

++iter, ++line_cnt )

 

 

 

{

 

 

 

outfile << *iter << " ";

 

 

 

if ( ! ( line_cnt % 8 ))

 

 

 

outfile << '\n';

 

 

 

}

 

 

 

outfile << endl;

 

 

 

}

 

 

 

 

 

 

 

После компиляции и запуска программа выводит следующее:

 

A Alice Daddy Emma Her I Shyly a

alive almost asks at beautiful bird blows but creature fiery flight flowing hair has he her him in is it like long looks magical

mean more no red same says she shush such tell tells the there through time to untamed wanting when wind

С++ для начинающих

1058

Объекты классов ofstream и ifstream разрешено определять и без указания имени файла. Позже к этому объекту можно присоединить файл с помощью функции-члена

ifstream curFile; // ...

curFile.open( filename.c_str() );

if ( ! curFile ) // открытие успешно?

open():

// ...

#include <fstream>

const int fileCnt = 5;

string fileTabl[ fileCnt ] = {

"Melville", "Joyce", "Musil", "Proust", "Kafka"

};

int main()

{

ifstream inFile; // не связан ни с каким файлом

for ( int ix = 0; ix < fileCnt; ++ix )

{

inFile.open( fileTabl[ix].c_str() );

//... проверить успешность открытия

//... обработать файл

inFile.close();

}

Чтобы закрыть файл (отключить от программы), вызываем функцию-член close():

}

Объект класса fstream (производного от iostream) может открывать файл для ввода или вывода. В следующем примере файл word.out сначала считывается, а затем записывается с помощью объекта типа fstream. Созданный ранее в этом разделе файл word.out содержит объект WordCount:

С++ для начинающих

1059

#include <fstream> #include "WordCount.h"

int main()

{

WordCount wd; fstream file;

file.open( "word.out", ios::in ); file >> wd;

file.close();

cout << "Прочитано: " << wd << endl;

// операция ios_base::out стерла бы текущие данные file.open( "word.out", ios::app );

file << endl << wd << endl; file.close();

}

Объект класса fstream может также открывать файл одновременно для ввода и вывода. Например, приведенная инструкция открывает файл word.out для ввода и дозаписи:

fstream io( "word.out", ios_base::in|ios_base::app );

Для задания нескольких режимов используется оператор побитового ИЛИ. Объект класса fstream можно позиционировать с помощью функций-членов seekg() или seekp(). Здесь буква g обозначает позиционирование для чтения (getting) символов (используется с объектом класса ofstream), а p для записи (putting) символов (используется с объектом класса ifstream). Эти функции делают текущим тот байт в файле, который

//установить абсолютное смещение в файле seekg( pos_type current_position )

//смещение от текущей позиции в том или ином направлении

имеет указанное абсолютное или относительное смещение. У них есть два варианта: seekg( off_type offset_position, ios_base::seekdir dir );

В первом варианте текущая позиция устанавливается в некоторое абсолютное значение, заданное аргументом current_position, причем значение 0 соответствует началу файла. Например, если файл содержит такую последовательность символов:

abc def ghi jkl

то вызов

io.seekg( 6 );

позиционирует io на шестой символ, т.е. на f. Второй вариант устанавливает указатель рабочей позиции файла на заданное расстояние от текущей, от начала файла или от его конца в зависимости от аргумента dir, который может принимать следующие значения:

С++ для начинающих

1060

ios_base::beg от начала файла;

ios_base::cur от текущей позиции;

ios_base::end от конца файла.

for ( int i = 0; i < recordCnt; ++i )

В следующем примере каждый вызов seekg() позиционирует файл на i-ую запись: readFile.ssekg( i * sizeof(Record), ios_base::beg );

С помощью первого аргумента можно задавать отрицательное значение. Переместимся на 10 байтов назад от текущей позиции:

readFile.seekg( -10, ios_base::cur );

Текущая позиция чтения в файле типа fstream возвращается любой из двух функций- членов tellg() или tellp(). Здесь 'p' означает запись (putting) и используется с

// сохранить текущую позицию ios_base::pos_type mark = writeFile.tellp();

// ...

if ( cancelEntry )

// вернуться к сохраненной позиции

объектом ofstream, а 'g' говорит о чтении (getting) и обслуживает объект ifstream: writeFile.seekp( mark );

Если необходимо сместиться вперед от текущей позиции на одну запись типа Record, то

// эквивалентные вызовы seekg

readFile.seekg( readFile.tellg() + sizeof(Record) );

// данный вызов считается более эффективным

можно воспользоваться любой из данных инструкций: readFile.seekg( sizeof(Record), ios_base::cur );

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

abcd efg hi j

Программа должна создать файл, модифицированный следующим образом:

С++ для начинающих

1061

abcd efg hi j

5 9 12 14 24

#include <iostream> #include <fstream>

main() {

// открыть файл для ввода и дозаписи

fstream inOut( "copy.out", ios_base::in|ios_base::app ); int cnt = 0; // счетчик байтов

char ch;

while ( inOut.get( ch ))

{

cout.put( ch ); // скопировать на терминал

++cnt;

if ( ch == '\n' ) { inOut << cnt ;

inOut.put( ' ' ); // пробел

}

}

// вывести окончательное значение счетчика байтов inOut << cnt << endl;

cout << "[ " << cnt << " ]" << endl; return 0;

Так выглядит первая попытка реализации:

}

inOut это объект класса fstream, связанный с файлом copy.out, открытым для ввода и дозаписи. Если файл открыт в режиме дозаписи, то все новые данные записываются в конец.

При чтении любого (включая пробельные) символа, кроме конца файла, мы увеличиваем переменную 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.

то получим такой результат: