Сделай Сам Свою Работу на 5

Наилучшая из устоявших функций





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

· преобразование аргумента типа производного класса в параметр типа любого из его базовых;

· преобразование указателя на тип производного класса в указатель на тип любого из его базовых;

· инициализация ссылки на тип базового класса с помощью l-значения типа производного.

Они не являются пользовательскими, так как не зависят от конвертеров и конструкторов, имеющихся в классе:

extern void release( const ZooAnimal& ); Panda yinYang;   // стандартное преобразование: Panda -> ZooAnimal

release( yinYang );

Поскольку аргумент yinYang типа Panda инициализирует ссылку на тип базового класса, то преобразование имеет ранг стандартного.

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



class Panda : public Bear, public Endangered { // наследует ZooAnimal::operator const char *() };   Panda yinYang;   extern void release( const ZooAnimal& ); extern void release( const char * );   // стандартное преобразование: Panda -> ZooAnimal // выбирается: release( const ZooAnimal& )

release( yinYang );

Как release(const char*), так и release(ZooAnimal&) являются устоявшими функциями: первая потому, что инициализация параметра-ссылки значением аргумента – стандартное преобразование, а вторая потому, что аргумент можно привести к типу const char* с помощью конвертера ZooAnimal::operator const char*(), который представляет собой пользовательское преобразование. Так как стандартное преобразование лучше пользовательского, то в качестве наилучшей из устоявших выбирается функция release(const ZooAnimal&).

При ранжировании различных стандартных преобразований из производного класса в базовые лучшим считается приведение к тому базовому классу, который ближе к производному. Так, показанный ниже вызов не будет неоднозначным, хотя в обоих случаях требуется стандартное преобразование. Приведение к базовому классу Bear лучше, чем к ZooAnimal, поскольку Bear ближе к классу Panda. Поэтому лучшей из устоявших будет функция release(const Bear&):



extern void release( const ZooAnimal& ); extern void release( const Bear& );   // правильно: release( const Bear& )

release( yinYang );

Аналогичное правило применимо и к указателям. При ранжировании стандартных преобразований из указателя на тип производного класса в указатели на типы различных базовых лучшим считается то, для которого базовый класс наименее удален от производного. Это правило распространяется и на тип void*.

Стандартное преобразование в указатель на тип любого базового класса всегда лучше, чем преобразование в void*. Например, если дана пара перегруженных функций:

void receive( void* );

void receive( ZooAnimal* );

то наилучшей из устоявших для вызова с аргументом типа Panda* будет receive(ZooAnimal*).

В случае множественного наследования два стандартных преобразования из типа производного класса в разные типы базовых могут иметь одинаковый ранг, если оба базовых класса равноудалены от производного. Например, Panda наследует классам Bear и Endangered. Поскольку они равноудалены от производного Panda, то преобразования объекта Panda в любой из этих классов одинаково хороши. Но тогда единственной наилучшей из устоявших функции для следующего вызова не существует, и он считается ошибочным:

extern void mumble( const Bear& ); extern void mumble( const Endangered& );   /* ошибка: неоднозначный вызов: * может быть выбрана любая из двух функций * void mumble( const Bear& ); * void mumble( const Endangered& ); */

mumble( yinYang );

Для разрешения неоднозначности программист может применить явное приведение типа:

mumble( static_cast< Bear >( yinYang ) ); // правильно



Инициализация объекта производного класса или ссылки на него объектом типа базового, а также преобразование указателя на тип базового класса в указатель на тип производного никогда не выполняются компилятором неявно. (Однако их можно выполнить с помощью явного применения dynamic_cast, как мы видели в разделе 19.1.) Для данного вызова не существует наилучшей из устоявших функции, так как нет неявного преобразования аргумента типа ZooAnimal в тип производного класса:

extern void release( const Bear& ); extern void release( const Panda& );   ZooAnimal za;   // ошибка: нет соответствия

release( za );

В следующем примере наилучшей из устоявших будет release(const char*). Это может показаться удивительным, так как к аргументу применена последовательность пользовательских преобразований, в которой участвует конвертер const char*(). Но поскольку неявного приведения от типа базового класса к типу производного не существует, то release(const Bear&) не является устоявшей функцией, так что остается только release(const char*):

Class ZooAnimal { public: // преобразование: ZooAnimal ==> const char* operator const char*();   // ... };   extern void release( const char* ); extern void release( const Bear& );   ZooAnimal za;   // za ==> const char* // правильно: release( const char* )

release( za );Ошибка! Закладка не определена.Ошибка! Закладка не определена.Ошибка! Закладка не определена.

Упражнение 19.9

Дана такая иерархия классов:

class Base1 { public: ostream& print(); void debug(); void writeOn(); void log( string ); void reset( void *); // ... };   class Base2 { public: void debug(); void readOn(); void log( double ); // ... };   class MI : public Base1, public Base2 { public: ostream& print(); using Base1::reset; void reset( char * ); using Base2::log; using Base2::log; // ...

};

Какие функции входят в множество кандидатов для каждого из следующих вызовов:

MI *pi = new MI; (a) pi->print(); (c) pi->readOn(); (e) pi->log( num );

(b) pi->debug(); (d) pi->reset(0); (f) pi->writeOn();

Упражнение 19.10

Дана такая иерархия классов:

class Base { public: operator int(); operator const char *(); // ... }; class Derived : public Base { public: operator double(); // ...

};

Удастся ли выбрать наилучшую из устоявших функций для каждого из следующих вызовов? Назовите кандидаты, устоявшие функции и преобразования типов аргументов для каждой из них, наилучшую из устоявших (если она есть):

(a) void operate( double ); void operate( string ); void operate( const Base & );   Derived *pd = new Derived;

operate( *pd );

(b) void calc( int ); void calc( double ); void calc( const Derived & );   Base *pb = new Derived;

operate( *pb );

Библиотека iostream

Частью стандартной библиотеки C++ является библиотека iostream – объектно-ориентированная иерархия классов, где используется и множественное, и виртуальное наследование. В ней реализована поддержка для файлового ввода/вывода данных встроенных типов. Кроме того, разработчики классов могут расширять эту библиотеку для чтения и записи новых типов данных.

Для использования библиотеки iostream в программе необходимо включить заголовочный файл

#include <iostream>

Операции ввода/вывода выполняются с помощью классов istream (потоковый ввод) и ostream (потоковый вывод). Третий класс, iostream, является производным от них и поддерживает двунаправленный ввод/вывод. Для удобства в библиотеке определены три стандартных объекта-потока:

· cin – объект класса istream, соответствующий стандартному вводу. В общем случае он позволяет читать данные с терминала пользователя;

· cout – объект класса ostream, соответствующий стандартному выводу. В общем случае он позволяет выводить данные на терминал пользователя;

· cerr – объект класса ostream, соответствующий стандартному выводу для ошибок. В этот поток мы направляем сообщения об ошибках программы.

Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод – с помощью оператора сдвига вправо (>>):

#include <iostream> #include <string>   int main() { string in_string;   // вывести литерал на терминал пользователя cout << "Введите свое имя, пожалуйста: ";   // прочитать ответ пользователя в in_string cin >> in_string;   if ( in_string.empty() ) // вывести сообщение об ошибке на терминал пользователя cerr << "ошибка: введенная строка пуста!\n"; else cout << "Привет, " << in_string << "!\n";

}

Назначение операторов легче запомнить, если считать, что каждый “указывает” в сторону перемещения данных. Например,

>> x

перемещает данные в x, а

<< x

перемещает данные из x. (В разделе 20.1 мы покажем, как библиотека iostream поддерживает ввод данных, а в разделе 20.5 – как расширить ее для ввода данных новых типов. Аналогично раздел 20.2 посвящен поддержке вывода, а раздел 20.4 – расширению для вывода данных определенных пользователем типов.)

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

· ifstream, производный от istream, связывает ввод программы с файлом;

· ofstream, производный от ostream, связывает вывод программы с файлом;

· fstream, производный от iostream, связывает как ввод, так и вывод программы с файлом.

Чтобы использовать часть библиотеки iostream, связанную с файловым вводом/выводом, необходимо включить в программу заголовочный файл

#include <fstream>

(Файл fstream уже включает iostream, так что включать оба файла необязательно.) Файловый ввод/вывод поддерживается теми же операторами:

#include <fstream> #include <string> #include <vector> #include <algorithm> int main() { string ifile;   cout << "Введите имя файла для сортировки: "; cin >> ifile;   // сконструировать объект класса ifstream для ввода из файла ifstream infile( ifile.c_str() );   if ( ! infile ) { cerr << "ошибка: не могу открыть входной файл: " << ifile << endl; return -1; }   string ofile = ifile + ".sort";   // сконструировать объект класса ofstream для вывода в файл ofstream outfile( ofile.c_str() ); if ( ! outfile) { cerr << "ошибка: не могу открыть выходной файл: " << ofile << endl; return -2; }   string buffer; vector< string, allocator > text;   int cnt = 1; while ( infile >> buffer ) { text.push_back( buffer ); cout << buffer << (cnt++ % 8 ? " " : "\n" ); }   sort( text.begin(), text.end() );   // выводим отсортированное множество слов в файл vector< string >::iterator iter = text.begin(); for ( cnt = 1; iter != text.end(); ++iter, ++cnt ) outfile << *iter << (cnt % 8 ? " " : "\n" );   return 0;

}

Вот пример сеанса работы с этой программой. Нас просят ввести файл для сортировки. Мы набираем alice_emma (набранные на клавиатуре символы напечатаны полужирным шрифтом). Затем программа направляет на стандартный вывод все, что прочитала из файла:

 

Введите имя файла для сортировки: alice_emma

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

 

Далее программа выводит в файл outfile отсортированную последовательность строк. Конечно, на порядок слов влияют знаки препинания; в следующем разделе мы это исправим:

 

"Daddy, "I A Alice Daddy Daddy, Emma Her

Shyly, a alive, almost asks, at beautiful bird

bird, blows but creature," fiery fiery flight. flowing

hair, hair. has he her her her, him

him, in is is it like long looks

magical mean, more. no red same says she

she shush, such tell tells tells the the

there there?" through time to untamed. wanting when

wind

 

(В разделе 20.6 мы познакомимся с файловым вводом/выводом более подробно.)

Библиотека iostream поддерживает также ввод/вывод в область памяти, при этом поток связывается со строкой в памяти программы. С помощью потоковых операторов ввода/вывода мы можем записывать данные в эту строку и читать их оттуда. Объект для строкового ввода/вывода определяется как экземпляр одного из следующих классов:

· istringstream, производный от istream, читает из строки;

· ostringstream, производный от ostream, пишет в строку;

· stringstream, производный от iostream, выполняет как чтение, так и запись.

Для использования любого из этих классов в программу нужно включить заголовочный файл

#include <sstream>

(Файл sstream уже включает iostream, так что включать оба файла необязательно.) В следующем фрагменте объект класса ostringstream используется для форматирования сообщения об ошибке, которое возвращается вызывающей программе.

#include <sstream>   string program_name( "our_program" ); string version( 0.01 );   // ...   string mumble( int *array, int size ) { if ( ! array ) { ostringstream out_message;   out_message << "ошибка: " << program_name << "--" << version << ": " << __FILE__ << ": " << __LINE__ << " -- указатель равен 0; " << " а должен адресовать массив.\n";   // возвращаем строку, в которой находится сообщение return out_message.str(); } // ...

}

(В разделе 20.8 мы познакомимся со строковым вводом/выводом более подробно.)

Потоки ввода/вывода поддерживают два предопределенных типа: char и wchar_t. В этой главе мы расскажем только о чтении и записи в потоки данных типа char. Помимо них, в библиотеке iostream имеется набор классов и объектов для работы с типом wchar_t. Они отличаются от соответствующих классов, использующих тип char, наличием префикса ‘w’. Так, объект стандартного ввода называется wcin, стандартного вывода – wcout, стандартного вывода для ошибок – wcerr. Но набор заголовочных файлов для char и wchar_t один и тот же.

Классы для ввода/вывода данных типа wchar_t называются wostream, wistream, wiostream, для файлового ввода/вывода – wofstream, wifstream, wfstream, а для строкового – wostringstream, wistringstream, wstringstream.

20.1. Оператор вывода <<

Оператор вывода обычно применяется для записи на стандартный вывод cout. Например, программа

#include <iostream>   int main() { cout << "сплетница Анна Ливия\n";

}

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

 

сплетница Анна Ливия

 

Имеются операторы, принимающие аргументы любого встроенного типа данных, включая const char*, а также типов string и complex из стандартной библиотеки. Любое выражение, включая вызов функции, может быть аргументом оператора вывода при условии, что результатом его вычисления будет тип, принимаемый каким-либо вариантом этого оператора. Например, программа

#include <iostream> #include <string.h>   int main() { cout << "Длина 'Улисс' равна:\t"; cout << strlen( "Улисс" ); cout << '\n'; cout << "Размер 'Улисс' равен:\t"; cout << sizeof( "Улисс" ); cout << endl;

}

выводит на терминал следующее:

 

Длина 'Улисс' равна:7

Размер 'Улисс' равен:8

 

endl – это манипулятор вывода, который вставляет в выходной поток символ перехода на новую строку, а затем сбрасывает буфер объекта ostream. (С буферизацией мы познакомимся в разделе 20.9.)

Операторы вывода, как правило, удобнее сцеплять в одну инструкцию. Например, предыдущую программу можно записать таким образом:

#include <iostream> #include <string.h>   int main() { // операторы вывода можно сцеплять   cout << "Длина 'Улисс' равна:\t"; << strlen( "Улисс" ) << '\n';   cout << "Размер 'Улисс' равен:\t" << sizeof( "Улисс" ) << endl;

}

Сцепление операторов вывода (и ввода тоже) возможно потому, что результатом выражения

cout << "некоторая строка";

служит левый операнд оператора вывода, т.е. сам объект cout. Затем этот же объект передается следующему оператору и далее по цепочке (мы говорим, что оператор << левоассоциативен).

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

#include <iostream>   int main() { int i = 1024; int *pi = &i;   cout << "i: " << i << "\t&i:\t" << &i << '\n';   cout << "*pi: " << *pi << "\tpi:\t" << pi << endl << "\t\t&pi:\t" << &pi << endl;

}

выводит на терминал следующее:

 

i: 1024 &i: 0x7fff0b4

*pi: 1024 pi: 0x7fff0b4

&pi: 0x7fff0b0

 

Позже мы покажем, как напечатать адреса в десятичном виде.

Следующая программа ведет себя странно. Мы хотим напечатать адрес, хранящийся в переменной pstr:

#include <iostream>   const char *str = "vermeer"; int main() { const char *pstr = str; cout << "Адрес pstr равен: " << pstr << endl;

}

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

 

Адрес pstr равен: vermeer

 

Проблема в том, что тип const char* интерпретируется как C-строка. Чтобы все же напечатать адрес, хранящийся в pstr, необходимо подавить обработку типа const char* по умолчанию. Для этого мы сначала убираем спецификатор const, а затем приводим pstr к типу void*:

<< static_cast<void*>(const_cast<char*>(pstr))

Теперь программа выводит ожидаемый результат:

 

Адрес pstr равен: 0x116e8

 

А вот еще одна загадка. Нужно напечатать большее из двух чисел:

#include <iostream>   inline void max_out( int val1, int val2 ) { cout << ( val1 > val2 ) ? val1 : val2; }   int main() { int ix = 10, jx = 20;   cout << "Большее из " << ix << ", " << jx << " равно ";   max_out( ix, jx );   cout << endl;

}

Однако программа выдает неправильный результат:

Большее из 10, 20 равно 0

 

Проблема в том, что оператор вывода имеет более высокий приоритет, чем оператор условного выражения, поэтому печатается результат сравнения val1 и val2. Иными словами, выражение

cout << ( val1 > val2 ) ? val1 : val2;

вычисляется как

(cout << ( val1 > val2 )) ? val1 : val2;

Поскольку val1 не больше val2, то результатом сравнения будет false, обозначаемый нулем. Чтобы изменить приоритет операций, весь оператор условного выражения следует заключить в скобки:

cout << ( val1 > val2 ? val1 : val2 );

Теперь результат получается правильный:

 

Большее из 10, 20 равно 20

 

Такого рода ошибку было бы проще найти, если бы значения литералов true и false типа bool печатались как строки, а не как 1 и 0. Тогда мы увидели бы строку:

 

Большее из 10, 20 равно false

 

и все стало бы ясно. По умолчанию литерал false печатается как 0, а true – как 1. Это можно изменить, воспользовавшись манипулятором boolalpha(), что и сделано в следующей программе:

int main() { cout << "печать значений типа bool по умолчанию: " << true << " " << false << "\nи в виде строк: " << boolalpha() << true << " " << false << endl;

}

Вот результат:

 

печать значений типа bool по умолчанию: 1 0

и в виде строк: true false

 

Для вывода массива, а также вектора или отображения, необходимо обойти все элементы и напечатать каждый из них:

#include <iostream> #include <vector> #include <string>   string pooh_pals[] = { "Тигра", "Пятачок", "Иа-Иа", "Кролик" };   int main() { vector<string> ppals( pooh_pals, pooh_pals+4 );   vector<string>::iterator iter = ppals.begin(); vector<string>::iterator iter_end = ppals.end();   cout << "Это друзья Пуха: "; for ( ; iter != iter_end; iter++ ) cout << *iter << " ";   cout << endl;

}

Вместо того чтобы явно обходить все элементы контейнера, выводя каждый по очереди, можно воспользоваться потоковым итератором ostream_iterator. Так выглядит эквивалентная программа, где используется эта техника (подробное обсуждение итератора ostream_iterator см. в разделе 12.4):

#include <iostream> #include <algorithm> #include <vector> #include <string>   string pooh_pals[] = { "Тигра", "Пятачок", "Иа-Иа", "Кролик" };   int main() { vector<string> ppals( pooh_pals, pooh_pals+4 );   vector<string>::iterator iter = ppals.begin(); vector<string>::iterator iter_end = ppals.end();   cout << "Это друзья Пуха: ";   // копируем каждый элемент в cout ... ostream_iterator< string > output( cout, " " ); copy( iter, iter_end, output );   cout << endl;

}

Программа печатает такую строку:

 

Ýòî äðóçüÿ Ïóõà: Òèãðà Ïÿòà÷îê Èà-Èà Êðîëèê

 

Упражнение 20.1

Даны следующие определения объектов:

string sa[4] = { "пух", "тигра", "пятачок", "иа-иа" }; vector< string > svec( sa, sa+4 ); string robin( "кристофер робин" ); const char *pc = robin.c_str(); int ival = 1024; char blank = ' '; double dval = 3.14159;

complex purei( 0, 7 );

(a) Направьте значение каждого объекта в стандартный вывод.

(b) Напечатайте значение адреса pc.

(c) Напечатайте наименьшее из двух значений ival и dval, пользуясь оператором условного выражения:

ival < dval ? ival : dval

Ввод

Основное средство реализации ввода – это оператор сдвига вправо (>>). Например, в следующей программе из стандартного ввода читается последовательность значений типа int и помещается в вектор:

#include <iostream> #include <vector>   int main() { vector<int> ivec; int ival;   while ( cin >> ival ) ivec.push_back( ival ); // ...

}

Подвыражение

cin >> ival;

читает целое число из стандартного ввода и копирует его в переменную ival. Результатом является левый операнд – объект класса istream, в данном случае cin. (Как мы увидим, это позволяет сцеплять операторы ввода.)

Выражение

while ( cin >> ival )

читает последовательность значений, пока cin не станет равно false. Значение istream может быть равно false в двух случаях: достигнут конец файла (т.е. все значения из файла прочитаны успешно) или встретилось неверное значение, скажем 3.14159 (десятичная точка недопустима в целом числе), 1e-1 (буква e недопустима) или любой строковый литерал. Если вводится неверное значение, объект istream переводится в состояние ошибки и чтение прекращается. (В разделе 20.7 мы подробнее расскажем о таких состояниях.)

Есть набор предопределенных операторов ввода, принимающих аргументы любого встроенного типа, включая C-строки, а также стандартных библиотечных типов string и complex:

#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; cin >> item_name; cin >> item_price;   cout << "Введены значения: item# " << item_number << " " << item_name << " @$" << item_price << endl;  

}

Вот пример выполнения этой программы:

 

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

10247 widget 19.99

Введены значения: item# 10247 widget @$19.99

 

Можно ввести каждый элемент на отдельной строке. По умолчанию оператор ввода отбрасывает все разделяющие пустые символы: пробел, символ табуляции, символ перехода на новую строку, символ перевода страницы и символ возврата каретки. (О том, как отменить это поведение, см. в разделе 20.9.)

 

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

widget

19.99

Введены значения: item# 10247 widget @$19.99

 

При чтении ошибка iostream более вероятна, чем при записи. Если мы вводим такую последовательность:

 

// ошибка: item_name должно быть вторым

BuzzLightyear 10009 8.99

 

то инструкция

cin >> item_number;

закончится ошибкой ввода, поскольку BuzzLightyear не принадлежит типу int. При проверке объекта istream будет возвращено false, поскольку возникло состояние ошибки. Более устойчивая к ошибкам реализация выглядит так:

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;   // ...

}

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

 

abcde

 

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

#include <iostream>   int main() { char ch;   // читать все символы, в том числе пробельные while ( cin.get( ch )) cout.put( ch ); // ...

}

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

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

 

A fine and private place

"A fine and private place"

 

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

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

#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.)

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

Считывание можно производить как в 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

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

 








Не нашли, что искали? Воспользуйтесь поиском по сайту:



©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.