Язык Программирования C++. Вводный курс [rus]
.pdfС++ для начинающих |
302 |
Например, библиотека iostream не соответствовала текущему стандарту. Шаблоны не поддерживали значения аргументов по умолчанию. Возможно, вам придется изменить кое-что в этой программе, чтобы она компилировалась в вашей системе.
#include <algorithm>
#include <string>
С++ для начинающих 303
#include <vector> #include <utility> #include <map> #include <set>
//заголовочный файл iostream, не отвечающий стандарту
#include <fstream.h>
//заголовочные файлы С
#include <stddef.h> #include <ctype.h>
// typedef для удобства чтения |
location; |
typedef pair<short,short> |
|
typedef vector<location,allocator> |
loc; |
typedef vector<string,allocator> |
text; |
typedef pair<text*,loc*> |
text_loc; |
class TextQuery { public:
TextQuery() { memset( this, 0, sizeof( TextQuery )); }
static void
filter_elements( string felems ) { filt_elems = felems; }
void query_text();
void display_map_text();
void display_text_locations(); void doit() {
retrieve_text(); separate_words(); filter_text(); suffix_text(); strip_caps(); build_word_map();
}
private:
void |
retrieve_text(); |
|
void |
separate_words(): |
|
void |
filter_text(); |
|
void |
strip_caps(); |
|
void |
suffix_textQ; |
|
void |
suffix_s( string& ); |
|
void build_word_map(); |
|
|
private: |
|
*lines_of_text; |
vector<string,allocator> |
||
text_loc |
*text_locations; |
|
map< string,loc*, |
*word_map; |
|
less<string>,allocator> |
||
static string |
filt_elems; |
|
}; |
|
|
string TextQuery::filt_elems( "\", •;: !?)(\V" );
int main()
{
TextQuery tq; tq.doit(); tq.query_text(); tq.display_map_text();
}
void TextQuery:: retrieve_text()
{
string file_name;
cout << "please enter file name: "; cin >> file_name;
ifstream infile( file_name.c_str(), ios::in );
С++ для начинающих |
304 |
|||
|
|
} |
|
|
|
|
|
||
|
|
|
|
|
Упражнение 6.25 |
|
|||
Объясните, почему нам потребовался специальный класс inserter |
для заполнения |
|||
|
|
set<string> exclusion_set; |
|
|
|
|
ifstream |
infile( "exclusion_set" ); |
|
copy( default_excluded_words, default_excluded_words+25,
набора стоп-слов (это упоминается в разделе 6.13.1, а детально рассматривается в 12.4.1). inserter(exclusion_set, exclusion_set.begin() ));
Упражнение 6.26
Первоначальная реализация поисковой системы отражает процедурный подход: набор глобальных функций оперирует набором независимых структур данных. Окончательный вариант представляет собой альтернативный подход, когда мы инкапсулируем функции и данные в класс TextQuery. Сравните оба способа. Каковы недостатки и преимущества каждого?
Упражнение 6.27
В данной версии программы имя файла с текстом вводится по запросу. Более удобно было бы задавать его как параметр командной строки; в главе 7 мы покажем, как это делается. Какие еще параметры командной строки желательно реализовать?
6.15. Контейнеры multimap и multiset
Контейнеры map и set не допускают повторяющихся значений ключей, а multimap (мультиотображение) и multiset (мультимножество) позволяют сохранять ключи с дублирующимися значениями. Например, в телефонном справочнике может понадобиться отдельный список номеров для каждого абонента. В перечне книг одного автора может быть несколько названий, а в нашей программе с одним словом текста сопоставляется несколько позиций. Для использования multimap и multiset нужно
#include <map>
multimap< key_type, value_type > multimapName;
// ключ - string, значение - list< string > multimap< string, list< string > > synonyms;
#include <set>
включить соответствующий заголовочный файл – map или set: multiset< type > multisetName;
Для прохода по мультиотображению или мультимножеству можно воспользоваться комбинацией итератора, который возвращает find() (он указывает на первый найденный элемент), и значения, которое возвращает count(). (Это работает, поскольку
в данных контейнерах элементы с одинаковыми ключами обязательно являются соседними). Например:
С++ для начинающих |
305 |
#include <map> #include <string>
void code_fragment()
{
multimap< string, string > authors; string search_item( "Alain de Botton" ); // ...
int number = authors.count( search_item ); mu1timap< string,string >::iterator iter;
iter = authors.find( search_item );
for ( int cnt = 0; cnt < number; ++cnt, ++-iter ) do_something( *iter );
// ...
}
Более элегантный способ перебрать все значения с одинаковыми ключами использует специальную функцию-член equal_range(), которая возвращает пару итераторов. Один из них указывает на первое найденное значение, а второй – на следующее за последним найденным. Если последний из найденных элементов является последним в контейнере, второй итератор содержит величину, равную end():
С++ для начинающих |
306 |
#include <map> #include <string> #include <utility>
void code_fragment()
{
multimap< string, string > authors; // ...
string search_item( "Haruki Murakami" );
while ( cin && cin >> search_item ) switch ( authors.count( search_item ))
{
// не найдено case 0:
break;
// найден 1, обычный find() case 1: {
multimap< string, string >: iterator iter; iter = authors.find( search_item );
// обработка элемента ...
break;
}
// найдено несколько ...
default:
{
typedef multimap<string,string>::iterator iterator; pair< iterator, iterator > pos;
//pos.first - адрес 1-го найденного
//pos.second - адрес 1-го отличного
//от найденного
pos = authors.equa1_range( search_item ); for (; pos.first != pos.second; pos.first++ )
// обработка элемента ...
}
}
}
Вставка и удаление элементов в multimap и multiset ничем не отличаются от аналогичных операций с контейнерами map и set. Функция equal_range() доставляет
#include <multimap> #include <string>
typedef multimap< string, string >::iterator iterator; pair< iterator, iterator > pos;
string search_item( "Kazuo Ishiguro" );
//authors - multimap<string, string>
//эквивалентно
//authors.erase( search_item );
pos = authors.equa1_range( search_item );
итераторную пару, задающую диапазон удаляемых элементов: authors.erase( pos.first, pos.second );
С++ для начинающих |
307 |
При каждом вызове функции-члена insert() добавляется новый элемент, даже если в
typedef multimap<string,string>::value_type valType; multimap<string,string> authors;
// первый элемент с ключом Barth authors.insert( valType (
string( "Barth, John" ), string( "Sot-Weed Factor" )));
// второй элемент с ключом Barth authors.insert( va1Type(
string( "Barth, John" ),
контейнере уже был элемент с таким же ключом. Например: string( "Lost in the Funhouse" )));
Контейнер multimap не поддерживает операцию взятия индекса. Поэтому следующее выражение ошибочно:
authors[ "Barth, John" ]; // ошибка: multimap
Упражнение 6.28
Перепишите программу текстового поиска из раздела 6.14 с использованием multimap для хранения позиций слов. Каковы производительность и дизайн в обоих случаях? Какое решение вам больше нравится? Почему?
6.16. Стек
В разделе 4.5 операции инкремента и декремента были проиллюстрированы на примере реализации абстракции стека. В общем случае стек является очень полезным механизмом для сохранения текущего состояния, если в разные моменты выполнения программы одновременно существует несколько состояний, вложенных друг в друга. Поскольку стек – это важная абстракция данных, в стандартной библиотеке С++ предусмотрен класс stack, для использования которого нужно включить заголовочный файл:
#include <stack>
В стандартной библиотеке стек реализован несколько иначе, чем у нас. Разница состоит в том, что доступ к элементу с вершины стека и удаление его осуществляются двумя функциями – top() и pop(). Полный набор операций со стеком приведен в таблице 6.5.
Таблица 6.5. Операции со стеком
Операция |
Действие |
|
|
|
|
|
|
|
|
empty() |
Возвращает |
, если стек пуст, и |
false |
в |
|
|
true |
|
|
|
противном случае |
|
|
|
size() |
Возвращает количество элементов в стеке |
|
||
pop() |
Удаляет элемент с вершины стека, но не |
|||
|
возвращает его значения |
|
|
|
top() |
Возвращает значение элемента с вершины |
С++ для начинающих |
308 |
||
|
|
|
|
|
|
стека, но не удаляет его |
|
|
|
|
|
|
push(item) |
Помещает новый элемент в стек |
|
#include <stack> #include <iostream> int main()
{
const int ia_size = 10;
int ia[ia_size ]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // заполним стек
int ix = 0;
stack< int > intStack;
for ( ; ix < ia_size; ++ix ) intStack.push( ia[ ix ] );
int error_cnt = 0;
if ( intStack.size() != ia_size ) {
cerr << "Ошибка! неверный размер IntStack: "
<<intStack.size()
<<"\t ожидается: " << ia_size << endl, ++error_cnt;
}
int value;
while ( intStack.empty() == false )
{
// считаем элемент с вершины value = intStack.top();
if ( value != --ix ) {
cerr << "Ошибка! ожидается " << ix
<< " получено " << value << endl; ++error_cnt;
}
// удалим элемент intStack.pop();
}
cout << "В результате запуска программы получено "
<< error_cnt << " ошибок" << endl;
Внашей программе приводятся примеры использования этих операций:
}
Объявление
stack< int > intStack;
определяет intStack как пустой стек, предназначенный для хранения элементов типа int. Стек является надстройкой над некоторым контейнерным типом, поскольку реализуется с помощью того или иного контейнера. По умолчанию это deque, поскольку именно эта структура обеспечивает эффективную вставку и удаление первого элемента, а vector эти операции не поддерживает. Однако мы можем явно указать другой тип контейнера, задав его как второй параметр:
С++ для начинающих |
309 |
stack< int, list<int> > intStack;
Элементы, добавляемые в стек, копируются в реализующий его контейнер. Это может приводить к потере эффективности для больших или сложных объектов, особенно если мы только читаем элементы. В таком случае удобнее определить стек указателей на
#include <stack>
class NurbSurface { /* mumble */ };
объекты. Например:
stack< NurbSurface* > surf_Stack;
К двум стекам одного типа можно применять операции сравнения: равенство, неравенство, меньше, больше, меньше или равно, больше или равно, если они определены над элементами стека. Элементы сопоставляются попарно. Первая пара несовпадающих элементов определяет результат операции сравнения в целом.
Стек будет использован в нашей программе текстового поиска в разделе 17.7 для
поддержки сложных запросов типа
Civil && ( War || Rights )
6.17. Очередь и очередь с приоритетами
Абстракция очереди реализует метод доступа FIFO (first in, first out – “первым вошел, первым вышел”): объекты добавляются в конец очереди, а извлекаются из начала. Стандартная библиотека предоставляет две разновидности этого метода: очередь FIFO, или простая очередь, и очередь с приоритетами, которая позволяет сопоставлять элементы с их приоритетами. Текущий элемент помещается не в конец такой очереди, а перед элементами с более низким приоритетом. Программист, определяющий такую структуру, задает способ вычисления приоритетов. В реальной жизни подобное можно увидеть, скажем, при регистрации багажа в аэропорту. Как правило, пассажиры, чей рейс через 15 минут, передвигаются в начало очереди, чтобы не опоздать на самолет. Примером из практики программирования служит планировщик операционной системы, определяющий последовательность выполнения процессов.
Для использования queue и priority_queue необходимо включить заголовочный файл:
#include <queue>
Полный набор операций с контейнерами queue и priority_queue приведен в таблице
6.6.
Таблица 6.6. Операции с queue и priority_queue
Операция |
Действие |
|
|
|
|
empty() |
Возвращает |
, если очередь пуста, и |
|
|
true |
|
false в противном случае |
С++ для начинающих |
|
|
|
|
310 |
||
|
|
|
|
||||
|
size() |
Возвращает количество элементов в очереди |
|
||||
|
pop() |
Удаляет первый элемент очереди, но не |
|
||||
|
|
возвращает его значения. Для очереди с |
|
||||
|
|
приоритетом первым является элемент с |
|
||||
|
|
наивысшим приоритетом |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
front() |
Возвращает |
значение |
первого |
элемента |
|
|
|
|
очереди, но не удаляет его. Применимо |
|
||||
|
|
только к простой очереди |
|
|
|
|
|
|
|
|
|
|
|
||
|
back() |
Возвращает |
значение последнего |
элемента |
|
||
|
|
очереди, но не удаляет его. Применимо |
|
||||
|
|
только к простой очереди |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
top() |
Возвращает |
значение |
элемента |
с |
|
|
|
|
наивысшим приоритетом, но не удаляет его. |
|
||||
|
|
Применимо только к очереди с приоритетом |
|
||||
|
|
|
|
||||
|
push(item) |
Помещает новый элемент в конец очереди. |
|
||||
|
|
Для очереди с приоритетом позиция |
|
||||
|
|
элемента определяется его приоритетом. |
|
|
|||
|
|
|
|
|
|
|
|
Элементы priority_queue отсортированы в порядке убывания приоритетов. По умолчанию упорядочение основывается на операции “меньше”, определенной над парами элементов. Конечно, можно явно задать указатель на функцию или объект-функцию, которая будет использоваться для сортировки. (В разделе 12.3 можно найти более подробное объяснение и иллюстрации использования такой очереди.)
6.18. Вернемся в классу iStack
Укласса iStack, разработанного нами в разделе 4.15, два недостатка:
∙он поддерживает только тип int. Мы хотим обеспечить поддержку любых типов. Это можно сделать, преобразовав наш класс в шаблон класса Stack;
∙он имеет фиксированную длину. Это неудобно в двух отношениях: заполненный стек становится бесполезным, а в попытке избежать этого мы окажемся перед необходимостью отвести ему изначально слишком много памяти. Разумным выходом будет разрешить динамический рост стека. Это можно сделать, пользуясь тем, что лежащий в основе стека вектор способен динамически расти.
Напомним определение нашего класса iStack:
С++ для начинающих |
311 |
#include <vector>
class iStack { public:
iStack( int capacity )
: _stack( capacity ), _top( 0 ) {};
bool pop( int &value ); bool push( int value );
bool full(); bool empty(); void display();
int size();
private:
int _top;
vector< int > _stack;
};
Сначала реализуем динамическое выделение памяти. Тогда вместо использования
индекса при вставке и удалении элемента нам нужно будет применять соответствующие функции-члены. Член _top больше не нужен: функции push_back() и pop_back() автоматически работают в конце массива. Вот модифицированный текст функций pop()
bool iStack::pop( int &top_value )
{
if ( empty() ) return false;
top_value = _stack.back(); _stack.pop_back(); return true;
}
bool iStack::push( int value )
{
if ( full() ) return false;
_stack.push_back( value ); return true;
иpush():
}
Функции-члены empty(), size() и full() также нуждаются в изменении: в этой версии
inline bool iStack::empty(){ return _stack.empty(); } inline bool iStack::size() { return _stack.size(); } inline bool iStack::full() {
они теснее связаны с лежащим в основе стека вектором. return _stack.max_size() == _stack.size(); }
Надо немного изменить функцию-член display(), чтобы _top больше не фигурировал в качестве граничного условия цикла.