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