НА ГЛАВНУЮ
Меню сайта
Категория
Ghost++ [1]
С++ [55]
Развлечение
ON - LINE
Опрос
У кого cпособность Invissible круче ?
Всего ответов: 358
Оbserver Ward

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Друзья сайта
Заведи себе Бота
Hаша кнопка
Для обмена банерами , наша кнопка для размещения у вас на сайте

Клансайт USSR


Главная » Статьи » Программирование » С++

20. Библиотека iostream (2)
20.3. Дополнительные операторы ввода/вывода

Иногда необходимо прочитать из входного потока последовательность не интерпретируемых байтов, а типов данных, таких, как char, int, string и т.д. Функция-член get() класса istream читает по одному байту, а функция getline() читает строку, завершающуюся либо символом перехода на новую строку, либо каким-то иным символом, определяемым пользователем. У функции-члена get() есть три формы:

1. get(char& ch) читает из входного потока один символ (в том числе и пустой) и помещает его в ch. Она возвращает объект iostream, для которого была вызвана. Например, следующая программа собирает статистику о входном потоке, а затем копирует входной поток в выходной:

#include <iostream>

int main()
{
   char ch;
   int  tab_cnt = 0, nl_cnt = 0, space_cnt = 0,
        period_cnt = 0, comma_cnt = 0;
   while ( cin.get(ch)) {
           switch( ch ) {
              case ' ':  space_cnt++;  break;
              case '\t': tab_cnt++;    break;
              case '\n': nl_cnt++;     break;
              case '.':  period_cnt++; break;
              case ',':  comma_cnt++;  break;
        }
        cout.put(ch);
   }

   cout << "\nнаша статистика:\n\t"
        << "пробелов: "    <<  space_cnt  <<  '\t'
        <<  "символов новой строки: " <<  nl_cnt    << '\t'
        <<  "табуляций: "      << tab_cnt    <<  "\n\t"
        <<  "точек: "   <<  period_cnt << '\t'
        <<  "запятых: "    <<  comma_cnt << endl;
}

Функция-член put() класса ostream дает альтернативный метод вывода символа в выходной поток: put() принимает аргумент типа char и возвращает объект класса ostream, для которого была вызвана.

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

Alice Emma has long flowing red hair. Her Daddy says
when the wind blows through her hair, it looks almost alive,
like a fiery bird in flight. A beautiful fiery bird, he tells her,
magical but untamed. "Daddy, shush, there is no such creature,"
she tells him, at the same time wanting him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

наша статистика:
       пробелов: 59      символов новой строки: 6     табуляций: 0
       точек: 4          запятых: 12

2. вторая форма get() также читает из входного потока по одному символу, но возвращает не поток istream, а значение прочитанного символа. Тип возвращаемого значения равен int, а не char, поскольку необходимо возвращать еще и признак конца файла, который обычно равен -1, чтобы отличаться от кодов реальных символов. Для проверки на конец файла мы сравниваем полученное значение с константой EOF, определенной в заголовочном файле iostream. Переменная, в которой сохраняется значение, возвращенное get(), должна быть объявлена как int, чтобы в ней можно было представить не только код любого символа, но и EOF:

#include <iostream>

int main()
{
    int ch;

    // альтернатива:
     // while ( ch = cin.get() && ch != EOF )
    while (( ch = cin.get()) != EOF )
              cout.put( ch );

    return 0;
}

При использовании любой из этих форм get() для чтения данной последовательности нужно семь итераций:

a b c
d

Читаются следующие символы: ('a', пробел, 'b', пробел, 'c', символ новой строки, 'd'). На восьмой итерации читается EOF. Оператор ввода (>>) по умолчанию пропускает пустые символы, поэтому на ту же последовательность потребуется четыре итерации, на которых возвращаются символы: 'a', 'b', 'c', 'd'. А вот следующая форма get() может прочесть всю последовательность всего за две итерации;

    * сигнатура третьей формы get() такова:

      get(char *sink, streamsize size, char delimiter='\n')

      sink – это массив, в который помещаются символы. size – это максимальное число символов, читаемых из потока istream. delimiter – это символ-ограничитель, при обнаружении которого чтение прекращается. Сам ограничитель не читается, а оставляется в потоке и будет прочитан следующим. Программисты часто забывают удалить его из потока перед вторым обращением к get(). Чтобы избежать этой ошибки, в показанной ниже программе мы воспользовались функцией-членом ignore() класса istream. По умолчанию ограничителем является символ новой строки.

      Символы читаются из потока, пока одно из следующих условий не окажется истинным. Как только это случится, в очередную позицию массива помещается двоичный нуль.
    * прочитано size-1 символов;
    * встретился конец файла;
    * встретился символ-ограничитель (еще раз напомним, что он остается в потоке и будет считан следующим).

Эта форма get() возвращает объект istream, для которого была вызвана (функция-член gcount() позволяет узнать количество прочитанных символов). Вот простой пример ее применения:

#include <iostream>
int main()
{
    const int max_line = 1024;
    char line[ max_line ];

    while ( cin.get( line, max_line ))
    {
        // читается не больше max_line - 1 символов,
        // чтобы оставить место для нуля
        int get_count = cin.gcount();
        cout << "фактически прочитано символов: "
             << get_count << endl;

        // что-то сделать со строкой

        // если встретился символ новой строки,
        // удалить его, прежде чем приступать к чтению следующей
        if ( get_count < max_line-1 )
              cin.ignore();
    }
}

Если на вход этой программы подать текст о юной Алисе Эмме, то результат будет выглядеть так:

фактически прочитано символов: 52

фактически прочитано символов: 60

фактически прочитано символов: 66

фактически прочитано символов: 63

фактически прочитано символов: 61

фактически прочитано символов: 43

Чтобы еще раз протестировать поведение программы, мы создали строку, содержащую больше max_line символов, и поместили ее в начало текста. Получили:

фактически прочитано символов: 1023

фактически прочитано символов: 528

фактически прочитано символов: 52

фактически прочитано символов: 60

фактически прочитано символов: 66

фактически прочитано символов: 63

фактически прочитано символов: 61

фактически прочитано символов: 43

По умолчанию ignore() читает и удаляет один символ из потока, для которого вызвана, но можно и явно задать ограничитель и количество пропускаемых символов. В общем виде ее сигнатура такова:

ignore( streamsize length = 1, int delim = traits::eof )

ignore() читает и отбрасывает length символов из потока или все символы до ограничителя включительно или до конца файла и возвращает объект istream, для которого вызвана.

Мы рекомендуем пользоваться функцией getline(), а не get(), поскольку она автоматически удаляет ограничитель из потока. Сигнатура getline() такая же, как у get() с тремя аргументами (и возвращает она тоже объект istream, для которого вызвана):

getline(char *sink, streamsize size, char delimiter='\n')

Поскольку и getline(), и get() с тремя аргументами могут читать size символов или меньше, то часто нужно "спросить” у объекта istream, сколько символов было фактически прочитано. Это позволяет сделать функция-член gcount(): она возвращает число символов, прочитанных при последнем обращении к get() или getline().

Функция-член write() класса ostream дает альтернативный метод вывода массива символов. Вместо того чтобы выводить символы до завершающего нуля, она выводит указанное число символов, включая и внутренние нули, если таковые имеются. Вот ее сигнатура:

write( const char *sink, streamsize length )

Здесь length определяет, сколько символов выводить. write() возвращает объект класса ostream, для которого она вызвана.

Парной для функции write() из класса ostream является функция read() из класса istream с такой сигнатурой:

read( char* addr, streamsize size )

read() читает size соседних байт из входного потока и помещает их, начиная с адреса addr. Функция gcount() возвращает число байт, прочитанных при последнем обращении к read(). В свою очередь read() возвращает объект класса istream, для которого она вызвана. Вот пример использования getline(), gcount() и write():

#include <iostream>
int main()
{
    const int lineSize = 1024;
    int lcnt = 0; // сколько строк прочитано
    int max = -1; // длина самой длинной строки

    char inBuf[ lineSize ];

    // читается до конца строки, но не более 1024 символов
    while (cin.getline( inBuf, lineSize ))
    {
        // сколько символов фактически прочитано
        int readin = cin.gcount();

        // статистика: счетчик строк, самая длинная строка
        ++lcnt;
        if ( readin >max )
              max = readin;

        cout << "Строка #" << lcnt
             <<  "\tПрочитано символов: " <<  readin <<  endl;

        cout.write( inBuf, readin).put('\n').put('\n');
    }

    cout <<  "Всего прочитано строк: " <<  lcnt<< endl;
    cout <<  "Самая длинная строка: "<<  max <<  endl;
}

Когда на вход было подано несколько фраз из романа Германа Мелвилла "Моби Дик", программа напечатала следующее:

Строка #1 Прочитано символов: 45

Call me Ishmael.  Some years ago, never mind

Строка #2 Прочитано символов: 46

how long precisely, having little or no money

Строка #3 Прочитано символов: 48

in my purse, and nothing particular to interest

Строка #4 Прочитано символов: 51

me on shore, I thought I would sail about a little

Строка #5 Прочитано символов: 47

and see the watery part of the world.  It is a

Строка #6 Прочитано символов: 43

way I have of driving off the spleen, and

Строка #7 Прочитано символов: 28

regulating the circulation.

Всего прочитано строк: 7

Самая длинная строка: 51

Функция-член getline() класса istream поддерживает только ввод в массив символов. Однако в стандартной библиотеке есть обычная функция getline(), которая помещает символы в объект класса string:

getline( istream &is, string str, char delimiter );

Эта функция читает не более str::max_size()-1 символов. Если входная последовательность длиннее, то операция завершается неудачно и объект переводится в ошибочное состояние. В противном случае ввод прекращается, когда прочитан ограничитель (он удаляется из потока, но в строку не помещается) либо достигнут конец файла.

Вот еще три необходимые нам функции-члена класса istream:

// возвращает символ в поток
putback( char class );
// устанавливает "указатель на следующий символ потока istream на один символ назад
unget();
// возвращает следующий символ (или EOF),
// но не извлекает его из потока
peek();

Следующий фрагмент иллюстрирует использование некоторых из них:

char ch, next, lookahead;
while ( cin.get( ch ))
{
   switch (ch) {
   case '/':
      // это комментарий? посмотрим с помощью peek()
      // если да, пропустить остаток строки
      next = cin.peek();
      if ( next == '/' )
         cin.ignore( lineSize, '\n' );
      break;
   case '>':
      // проверка на лексему >>=
      next = cin.peek();
      if ( next == '>' ) {
         lookahead = cin.get();
         next = cin.peek();
         if ( next != '=' )
            cin.putback( lookahead );
         }
         // ...
}

Упражнение 20.4

Прочитайте из стандартного ввода следующую последовательность символов, включая все пустые, и скопируйте каждый символ на стандартный вывод (эхо-копирование):

a  b c
d     e
f

Упражнение 20.5

Прочитайте фразу "riverrun, from bend of bay to swerve of shore" сначала как последовательность из девяти строк, а затем как одну строку.

Упражнение 20.6

С помощью функций getline() и gcount() прочитайте последовательность строк из стандартного ввода и найдите самую длинную (не забудьте, что строку, прочитанную за несколько обращений к getline(), нужно считать одной).
20.4. Перегрузка оператора вывода

Если мы хотим, чтобы наш тип класса поддерживал операции ввода/вывода, то необходимо перегрузить оба соответствующих оператора. В этом разделе мы рассмотрим, как перегружается оператор вывода. (Перегрузка оператора ввода – тема следующего раздела.) Например, для класса WordCount он выглядит так:

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

public:
    WordCount( string word, int cnt=1 );
    // ...
private:
    string word;
    int occurs;
};

ostream&
operator <<( ostream& os, const WordCount& wd )
{    // формат: <счетчик> слово
     os << "< " << " >  " >  "
        << wd.word;
     return os;
}

Проектировщик должен решить, следует ли выводить завершающий символ новой строки. Лучше этого не делать: поскольку операторы вывода для встроенных типов такой символ не печатают, пользователь ожидает аналогичного поведения и от операторов в других классах. Определенный нами в классе WordCount оператор вывода можно использовать вместе с любыми другими операторами:

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

int main()
{
   WordCount wd( " sadness" , 12 );
   cout << " wd:\n"  << wd << endl;
   return 0;
}

Программа печатает на терминале строки:

wd:
<12> sadness

Оператор вывода – это бинарный оператор, который возвращает ссылку на объект класса ostream. В общем случае структура определения перегруженного оператора вывода выглядит так:

// структура перегруженного оператора вывода
ostream&
operator <<( ostream& os, const ClassType &object )
{
   // произвольный код для подготовки объекта

   // фактическое число членов
   os << // ...

   // возвращается объект ostream
   return os;
}

Первый его аргумент – это ссылка на объект ostream, а второй – ссылка (обычно константная) на объект некоторого класса. Возвращается ссылка на ostream. Значением всегда является объект ostream, для которого оператор вызывался.

Поскольку первым аргументом является ссылка, оператор вывода должен быть определен как обычная функция, а не член класса. (Объяснение см. в разделе 15.1.) Если оператору необходим доступ к неоткрытым членам, то следует объявить его другом класса. (О друзьях говорилось в разделе 15.2.)

Пусть Location – это класс, в котором хранятся номера строки и колонки вхождения слова. Вот его определение:

#include <iostream>

class Location {
    friend ostream& operator<<( ostream&, const Location& );
private:
    short _line;
    short _col;
};
ostream& operator <<( ostream& os, const Location& lc )
{
    // объект Loc выводится в виде:  < 10,37 >
    os << " <"  <<lc._line
       << " ,"  << lc._col << " >  " ;

    return os;
}

Изменим определение класса WordCount, включив в него вектор occurList объектов Location и объект word класса string:

#include <vector>
#include <string>
#include <iostream>
#include "Location.h"

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

public:
        WordCount() {}
        WordCount( const string &word ) : _word( word ) {}
        WordCount( const string &word, int ln, int col )
                : _word( word ){ insert_location( ln, col ); }

        string word()   const { return _word; }
        int    occurs() const { return _occurList.size(); }
        void   found( int ln, int col )
                    { insert_location( ln, col ); }

private:
        void insert_location( int ln, int col )
                { _occurList.push_back( Location( ln, col )); }

        string             _word;
        vector< Location > _occurList;
};

В классах string и Location определен оператор вывода operator<<(). Так выглядит измененное определение оператора вывода в WordCount:

ostream&
operator <<( ostream& os, const WordCount& wd )
{
    os << "<" << wd._occurList.size() << "> "
       << wd._word << endl;

    int cnt = 0, onLine = 6;
    vector< Location >::const_iterator first =
            wd._occurList.begin();
    vector<  Location >::const_iterator last  =
            wd._occurList.end();

    for ( ; first != last; ++first )
    {
        // os < <  Location
        os < <   *first < <  " ";

       // форматирование: по 6 в строке
       if ( ++cnt >= onLine )
          { os < <   "\n"; cnt = 0;  }

    }
    return os;
}

А вот небольшая программа для тестирования нового определения класса WordCount; позиции вхождений для простоты "зашиты” в код:

int main()
{
    WordCount search( "rosebud" );

    // для простоты явно введем 8 вхождений
    search.found(11,3);  search.found(11,8);
    search.found(14,2);  search.found(34,6);
    search.found(49,7);  search.found(67,5);
    search.found(81,2);  search.found(82,3);
    search.found(91,4);  search.found(97,8);

    cout <<  "Вхождения: " << "\n"
         <<search << endl;

    return 0;
}

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

Вхождения:

<10> rosebud
<11,3>   <11,8>   <14,2>   <34,6>   <49,7>   <67,5>
<81,2>   <82,3>   <91,4>   <97,8>

Полученный результат сохранен в файле output. Далее мы определим оператор ввода, с помощью которого прочитаем данные из этого файла.

Упражнение 20.7

Дано определение класса Date:

class Date {
public:
   // ...
private:
   int month, day, year;
};

Напишите перегруженный оператор вывода даты в формате:

(a)
// полное название месяца
September 8th, 1997
(b)
9 / 8 / 97
(c) Какой формат лучше? Объясните.
(d) Должен ли оператор вывода Date быть функцией-другом? Почему?

Упражнение 20.8

Определите оператор вывода для следующего класса CheckoutRecord:

class CheckoutRecord {       // запись о выдаче
public:
   // ...
private:
   double book_id;          // идентификатор книги
   string title;            // название
   Date date_borrowed;      // дата выдачи
   Date date_due;           // дата возврата
   pair<string,string> borrower;  // кому выдана
   vector pair<string,string> wait_list;   // очередь на книгу
};

20.5. Перегрузка оператора ввода

Перегрузка оператора ввода (>>) похожа на перегрузку оператора вывода, но, к сожалению, возможностей для ошибок гораздо больше. Вот, например, его реализация для класса WordCount:

#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;
}

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

    * поток, чтение из которого невозможно из-за неправильного формата, переводится в состояние 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
<11,3>   <11,8>   <14,2>   <34,6>   <49,7>   <67,5>
<81,2>   <82,3>   <91,4>   <97,8>

Упражнение 20.9

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

Упражнение 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 операции применимы и к ofstream. Например, инструкции

char ch = ' ';
outFile.put( '1' ).put( ')' ).put( ch );
outFile <<"1 + 1 = " << (1 + 1) << endl;

выводят в файл 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. Следующая программа читает указанный пользователем файл и копирует его содержимое на стандартный вывод:

#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, где приведены текст этой функции и содержимое файла), сортирует строки, удаляет дубликаты и записывает результат на стандартный вывод:

#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

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

ifstream curFile;
// ...
curFile.open( filename.c_str() );
if ( ! curFile )  // открытие успешно?
   // ...
Чтобы закрыть файл (отключить от программы), вызываем функцию-член close():
#include

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();
    }
}

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

#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, который может принимать следующие значения:

    * ios_base::beg – от начала файла;
    * ios_base::cur – от текущей позиции;
    * ios_base::end – от конца файла.

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

for ( int i = 0; i < recordCnt; ++i )
      readFile.ssekg( i * sizeof(Record), ios_base::beg );

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

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

Текущая позиция чтения в файле типа fstream возвращается любой из двух функций-членов tellg() или tellp(). Здесь 'p' означает запись (putting) и используется с объектом ofstream, а 'g' говорит о чтении (getting) и обслуживает объект ifstream:

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

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

// эквивалентные вызовы seekg
readFile.seekg( readFile.tellg() + sizeof(Record) );

// данный вызов считается более эффективным
readFile.seekg( sizeof(Record), ios_base::cur );

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

abcd
efg
hi
j

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

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.

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

 [ 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) в конец файла
Категория: С++ | Добавил: r2d2 (29.09.2011)
Просмотров: 908 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Born in Ussr
Залогиниться
Турниры

/j clan ussr /j clan cccp