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

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

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

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

1042

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?"

наша статистика:

символов новой строки: 6

табуляций: 0

пробелов: 59

точек: 4

запятых: 12

 

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

#include <iostream>

int main()

{

int ch;

// альтернатива:

//while ( ch = cin.get() && ch != EOF ) while (( ch = cin.get()) != EOF )

cout.put( ch );

return 0;

чтобы в ней можно было представить не только код любого символа, но и EOF:

}

При использовании любой из этих форм 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(). Чтобы избежать этой

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

1043

ошибки, в показанной ниже программе мы воспользовались функцией-членом 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

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

1044

фактически прочитано символов: 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():

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

1045

#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:

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

1046

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

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

//возвращает символ в поток putback( char class );

//устанавливает "указатель на следующий символ потока istream на один

символ назад

unget();

//возвращает следующий символ (или EOF),

//но не извлекает его из потока

Вот еще три необходимые нам функции-члена класса istream: 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

 

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

1047

Упражнение 20.5

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

Упражнение 20.6

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

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

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

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 он выглядит так:

}

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

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

int main()

{

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

использовать вместе с любыми другими операторами:

}

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

1048

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

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:

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

1049

#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<<(). Так выглядит

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:

}

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

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

1050

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

<14,2>

<34,6>

<49,7>

<67,5>

<11,3>

<11,8>

<81,2>

<82,3>

<91,4>

<97,8>

 

 

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

Упражнение 20.7

class Date { public:

// ...

private:

int month, day, year;

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

};

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

(a)

// полное название месяца

September 8th, 1997

(b)

9 / 8 / 97

(c)Какой формат лучше? Объясните.

(d)Должен ли оператор вывода Date быть функцией-другом? Почему?

Упражнение 20.8

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

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

1051

 

 

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: