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

Язык Программирования C++. Вводный курс [rus]

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

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

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 больше не фигурировал в качестве граничного условия цикла.

Соседние файлы в предмете Программирование на C++