20.2.1. Строковый ввод
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; }
Работать с типом string значительно проще:
#include iostream
#include string.h
string str;
while ( cin str )
// ... сделать что-то со строкой
Рассмотрим операторы ввода в 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-строку и находит самое длинное слово:
#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
Если в объявлении массива buf размер явно не указан:
char buf[] = "Нереалистичный пример";
то программист может применить оператор sizeof, но при условии, что идентификатор является именем массива и находится в области видимости выражения:
while ( cin setw(sizeof( buf )) buf )
Применение оператора sizeof в следующем примере дает неожиданный результат:
#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;
}
Программа печатает:
$ 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, помещается только нулевой символ. До чтения из стандартного ввода дело так и не доходит, программа зацикливается.
При использовании класса string все проблемы управления памятью исчезают, об этом заботится сам 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;
}
Однако запятая и кавычка по-прежнему считаются частью слова. Напишем функцию для удаления этих символов из слова:
#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 );
}
Эта функция работает правильно, но множество символов, которые мы собираемся отбрасывать, "зашито" в код. Лучше дать пользователю возможность самому передать строку, содержащую такие символы. Если он согласен на множество по умолчанию, то может передать пустую строку.
#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 );
}
}
С использованием этой функции программа будет выглядеть так:
#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
Прочитайте из стандартного ввода последовательность данных таких типов: string, double, string, int, string. Каждый раз проверяйте, не было ли ошибки чтения.
Упражнение 20.3
Прочитайте из стандартного ввода заранее неизвестное число строк. Поместите их в список. Найдите самую длинную и самую короткую строку.