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

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

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

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

1032

cin >> item_number; if ( ! cin )

cerr << "ошибка: введено некорректное значение item_number!\n";

Хотя сцепление операторов ввода поддерживается, проверить корректность каждой отдельной операции нельзя, поэтому пользоваться таким приемом следует лишь тогда,

#include <iostream> #include <string>

int main()

{

int item_number; string item_name; double item_price;

cout << "Пожалуйста, введите item_number, item_name и price: "

<<endl;

//хорошо, но легче допустить ошибку

cin >> item_number >> item_name >> item_price;

cout << "Введены значения: item# "

<<item_number << " "

<<item_name << " @$"

<<item_price << endl;

когда ошибка невозможна. Наша программа теперь выглядит так:

}

Последовательность

ab c

d e

составлена из девяти символов: 'a', 'b', ' ' (пробел), 'c', '\n' (переход на новую строку), 'd', '\t' (табуляция), 'e' и '\n'. Однако приведенная программа читает лишь

#include <iostream>

int main()

{

char ch;

// прочитать и вывести каждый символ while ( cin >> ch )

cout << ch; cout << endl;

// ...

пять букв:

}

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

1033

И печатает следующее:

abcde

По умолчанию все пустые символы отбрасываются. Если нам нужны и они, например для сохранения формата входного текста или обработки пустых символов (скажем, для подсчета количества символов перехода на новую строку), то можно воспользоваться функцией-членом get() класса istream (обычно в паре с ней употребляется функция-

#include <iostream>

int main()

{

char ch;

//читать все символы, в том числе пробельные while ( cin.get( ch ))

cout.put( ch );

//...

член put() класса ostream; они будут рассмотрены ниже). Например:

}

Другая возможность сделать это использовать манипулятор noskipws.

Каждая из двух данных последовательностей считается составленной из пяти строк, разделенных пробелами, если для чтения используются операторы ввода с типами const char* или string:

A fine and private place "A fine and private place"

Наличие кавычек не делает пробелы внутри закавыченной строки ее частью. Просто открывающая кавычка становится начальным символом первого слова, а закрывающая конечным символом последнего.

Вместо того чтобы читать из стандартного ввода по одному символу, можно воспользоваться потоковым итератором istream_iterator:

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

1034

#include <algorithm> #include <string> #include <vector> #include <iostream>

int main()

{

istream_iterator< string > in( cin ), eos ; vector< string > text ;

//копировать прочитанные из стандартного ввода значения

//в вектор text

copy( in , eos , back_inserter( text ) ) ;

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

// удалить дубликаты

vector< string >::iterator it;

it = unique( text.begin() , text.end() ) ; text.erase( it , text.end() ) ;

// вывести получившийся вектор int line_cnt = 1 ;

for ( vector< string >::iterator iter = text.begin() ; iter != text.end() ; ++iter , ++line_cnt )

cout << *iter

<< ( line_cnt % 9 ? " " : "\n" ) ; cout << endl;

}

Пусть входом для этой программы будет файл istream_iter.C с исходным текстом. В системе UNIX мы можем перенаправить стандартный ввод на файл следующим образом (istream_iter имя исполняемого файла программы):

istream_iter < istream_iter.C

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

!= " " "\n" #include % ( ) *iter ++iter ++line_cnt , 1 9 : ; << <algorithm> <iostream.h>

<string> <vector> = > >::difference_type >::iterator ? allocator back_inserter(

cin copy( cout diff_type eos for in in( int

istream_iterator< it iter line_cnt main() sort( string test test.begin() test.end() test.erase( typedef unique( vector< { }

(Потоковые итераторы ввода/вывода iostream рассматривались в разделе 12.4.)

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

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

1035

20.2.1. Строковый ввод

Считывание можно производить как в C-строки, так и в объекты класса string. Мы рекомендуем пользоваться последними. Их главное преимущество автоматическое управление памятью для хранения символов. Чтобы прочитать данные в C-строку, т.е. массив символов, необходимо сначала задать его размер, достаточный для хранения строки. Обычно мы читаем символы в буфер, затем выделяем из хипа ровно столько памяти, сколько нужно для хранения прочитанной строки, и копируем данные из буфера

#include <iostream> #include <string.h>

char inBuf[ 1024 ]; try

{

while ( cin >> inBuf ) {

char *str = new char[ strlen( inBuf ) + 1 ]; strcpy( str, inBuf );

// ... сделать что-то с массивом символов str delete [] str;

}

}

в эту память:

catch( ... ) { delete [] str; throw; }

#include <iostream> #include <string.h>

string str;

while ( cin >> str )

Работать с типом string значительно проще:

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

Рассмотрим операторы ввода в C-строки и в объекты класса string. В качестве входного текста по-прежнему будет использоваться рассказ об Алисе Эмме:

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

Поместим этот текст в файл alice_emma, а затем перенаправим на него стандартный вход программы. Позже, когда мы познакомимся с файловым вводом, мы откроем и прочтем этот файл непосредственно. Следующая программа помещает прочитанные со стандартного ввода слова в C-строку и находит самое длинное слово:

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

1036

#include <iostream.h> #include <string.h>

int main()

{

const int bufSize = 24;

char buf[ bufSize ], largest[ bufSize ];

// для хранения статистики int curLen, max = -1, cnt = 0; while ( cin >> buf )

{

curLen = strlen( buf ); ++cnt;

// новое самое длинное слово? сохраним его if ( curLen > max ) {

max = curLen;

strcpy( largest, buf );

}

}

cout << "Число прочитанных слов " << cnt << endl;

cout << "Длина самого длинного слова " << max << endl;

cout << "Самое длинное слово " << largest << endl;

}

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

Число прочитанных слов 65 Длина самого длинного слова 10 Самое длинное слово creature,"

На самом деле этот результат неправилен: самое длинное слово beautiful, в нем девять букв. Однако выбрано creature, потому что программа сочла его частью запятую и кавычку. Следовательно, необходимо отфильтровать небуквенные символы.

Но прежде чем заняться этим, рассмотрим программу внимательнее. В ней каждое слово помещается в массив buf, длина которого равна 24. Если бы в тексте попалось слово длиной 24 символа (или более), то буфер переполнился бы и программа, вероятно, закончилась бы крахом. Чтобы предотвратить переполнение входного массива, можно воспользоваться манипулятором setw(). Модифицируем предыдущую программу:

while ( cin >> setw( bufSize ) >> buf )

Здесь bufSize размер массива символов buf. setw() разбивает строку длиной bufSize или больше на несколько строк, каждая из которых не длиннее, чем bufSize - 1.

Завершается такая частичная строка двоичным нулем. Для использования setw() в программу необходимо включить заголовочный файл iomanip:

#include <iomanip>

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

1037

Если в объявлении массива buf размер явно не указан:

char buf[] = "Нереалистичный пример";

то программист может применить оператор sizeof, но при условии, что идентификатор является именем массива и находится в области видимости выражения:

while ( cin >> setw(sizeof( buf )) >> buf )

#include <iostream> #include <iomanip>

int main()

{

const int bufSize = 24; char buf[ bufSize ]; char *pbuf = buf;

//если строка длиннее, чем sizeof(char*),

//она разбивается на несколько строк

while ( cin >> setw( sizeof( pbuf )) >> pbuf ) cout << pbuf << endl;

Применение оператора sizeof в следующем примере дает неожиданный результат:

}

Программа печатает:

$ a.out

The winter of our discontent

The win ter of our dis con ten t

Функции setw() вместо размера массива передается размер указателя, длина которого на нашей машине равна четырем байтам, поэтому вывод разбит на строки по три символа.

Попытка исправить ошибку приводит к еще более серьезной проблеме:

while ( cin >> setw(sizeof( *pbuf )) >> pbuf )

Мы хотели передать setw() размер массива, адресуемого pbuf. Но выражение

*pbuf

дает только один символ, т.е. объект типа char. Поэтому setw() передается значение 1. На каждой итерации цикла while в массив, на который указывает pbuf, помещается

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

1038

только нулевой символ. До чтения из стандартного ввода дело так и не доходит, программа зацикливается.

При использовании класса string все проблемы управления памятью исчезают, об этом

#include <iostream.h> #include <string>

int main()

{

string buf, largest;

// для хранения статистики

int curLen, // длина текущего слова max = -1, // максимальная длина слова cnt = 0; // счетчик прочитанных слов

while ( cin >> buf )

{

curLen = buf.size(); ++cnt;

// новое самое длинное слово? сохраним его if ( curLen > max )

{

max = curLen; largest = buf;

}

}

cout << "Число прочитанных слов " << cnt << endl; cout << "Длина самого длинного слова " << max << endl; cout << "Самое длинное слово " << largest << endl;

заботится сам string. Вот как выглядит наша программа в данном случае:

}

Однако запятая и кавычка по-прежнему считаются частью слова. Напишем функцию для

#include <string>

void filter_string( string &str )

{

// элементы, подлежащие фильтрации string filt_elems( "\",?." ); string::size_type pos = 0;

while (( pos = str.find_first_of( filt_elems, pos )) != string::npos )

str.erase( pos, 1 );

удаления этих символов из слова:

}

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

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

1039

 

 

#include <string>

 

 

 

 

 

 

void filter_string( string &str,

 

 

 

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

 

 

 

{

 

 

 

string::size_type pos = 0;

 

 

 

while (( pos = str.find_first_of( filt_elems, pos ))

 

 

 

!= string::npos )

 

 

 

str.erase( pos, 1 );

 

 

 

}

 

 

 

 

 

 

 

Более общая версия filter_string() принимает пару итераторов, обозначающих

 

 

 

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

 

 

 

}

 

диапазон, где производится фильтрация:

 

 

 

}

 

 

 

 

 

 

 

С использованием этой функции программа будет выглядеть так:

 

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

1040

 

 

#include <string>

 

 

 

 

 

 

#include <algorithm>

 

 

 

#include <iterator>

 

 

 

#include <vector>

 

 

 

#include <iostream>

 

 

 

bool length_less( string s1, string s2 )

 

 

 

{ return s1.size() < s2.size(); }

 

 

 

int main()

 

 

 

{

 

 

 

istream_iterator< string > input( cin ), eos;

 

 

 

vector< string > text;

 

 

 

// copy - это обобщенный алгоритм

 

 

 

copy( input, eos, back_inserter( text ));

 

 

 

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

 

 

 

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

 

 

 

int cnt = text.size();

 

 

 

// max_element - это обобщенный алгоритм

 

 

 

string *max = max_element( text.begin(), text.end(),

 

 

 

length_less );

 

 

 

int len = max->size();

 

 

 

cout << "Число прочитанных слов "

 

 

 

<< cnt << endl;

 

 

 

cout << "Длина самого длинного слова "

 

 

 

<< len << endl;

 

 

 

cout << "Самое длинное слово "

 

 

 

<< *max << endl;

 

 

 

}

 

 

 

 

 

 

 

Когда мы применили в алгоритме max_element() стандартный оператор меньше”,

 

определенный в классе string, то были удивлены полученным результатом:

 

Число прочитанных слов 65 Длина самого длинного слова 4 Самое длинное слово wind

Очевидно, что wind это не самое длинное слово. Оказывается, оператор меньшев классе string сравнивает строки не по длине, а в лексикографическом порядке. И в этом смысле wind действительно максимальный элемент. Для того чтобы найти слово максимальной длины, мы должны заменить оператор меньшепредикатом length_less(). Тогда результат будет таким:

Число прочитанных слов 65 Длина самого длинного слова 9

Самое длинное слово beautiful

Упражнение 20.2

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

1041

Прочитайте из стандартного ввода последовательность данных таких типов: string, double, string, int, string. Каждый раз проверяйте, не было ли ошибки чтения.

Упражнение 20.3

Прочитайте из стандартного ввода заранее неизвестное число строк. Поместите их в список. Найдите самую длинную и самую короткую строку.

20.3. Дополнительные операторы ввода/вывода

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

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 ) {

 

break;

 

case ' ': space_cnt++;

 

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'

<< '\t'

<< "символов новой строки: " << nl_cnt

<< "табуляций: "

<< 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,