Б. Страуструп - Язык программирования С++ (1119446), страница 62
Текст из файла (страница 62)
Если такое значение не найдено, возвращается новый элемент со стандартным значением. Этопозволяет использовать операцию индексации в левой части присваивания. Стандартные значения дляключей и значений устанавливаются конструкторами Map. В операции индексации определяетсязначение current, используемое итераторами.Реализация остальных функций-членов оставлена в качестве упражнения:template<class K, class V>void Map<K,V>::remove(const K& k){// см. упражнение 2 из $$8.10}template<class K, class V>Map<K,V>::Map(const Map<K,V>& m){// копирование таблицы Map и всех ее элементов}template<class K, class V>Map& Map<K,V>::operator=(const Map<K,V>& m){// копирование таблицы Map и всех ее элементов}Теперь нам осталось только определить итерацию.
В классе Map есть функции-члены first(), last() иelement(const K&), которые возвращают итератор, установленный соответственно на первый, последнийили задаваемый ключом-параметром элемент. Сделать это можно, поскольку элементы хранятся вупорядоченном по ключам виде. Итератор Mapiter для Map определяется так:template<class K, class V> class Mapiter {friend class Map<K,V>;Map<K,V>* m;Link<K,V>* p;Mapiter(Map<K,V>* mm, Link<K,V>* pp){ m = mm; p = pp; }public:Mapiter() { m = 0; p = 0; }Mapiter(Map<K,V>& mm);228Бьерн Страуструп.operator void*() { return p; }const K& key();V& value();Mapiter& operator--();void operator--(int);Mapiter& operator++();void operator++(int);Язык программирования С++////////префикснаяпостфикснаяпрефикснаяпостфиксная};После позиционирования итератора функции key() и value() из Mapiter выдают ключ и значение тогоэлемента, на который установлен итератор.template<class K, class V> const K& Mapiter<K,V>::key(){if (p) return p->key; else return m->def_key;}template<class K, class V> V& Mapiter<K,V>::value(){if (p) return p->value; else return m->def_val;}По аналогии с указателями определены операции ++ и -- для продвижения по элементам Map вперед иназад:Mapiter<K,V>& Mapiter<K,V>::operator--() //префиксный декремент{if (p) p = p->pre;return *this;}void Mapiter<K,V>::operator--(int){if (p) p = p->pre;}// постфиксный декрементMapiter<K,V>& Mapiter<K,V>::operator++() // префиксный инкремент{if (p) p = p->suc;return *this;}void Mapiter<K,V>::operator++(int){if (p) p = p->suc;}// постфиксный инкрементПостфиксные операции определены так, что они не возвращают никакого значения.
Дело в том, чтозатраты на создание и передачу нового объекта Mapiter на каждом шаге итерации значительны, апольза от него будет не велика.Объект Mapiter можно инициализировать так, чтобы он был установлен на начало Map:template<class K, class V> Mapiter<K,V>::Mapiter(Map<K,V>& mm){m == &mm; p = m->head;}Операция преобразования operator void*() возвращает нуль, если итератор не установлен на элементMap, и ненулевое значение иначе. Значит можно проверять итератор iter, например, так:void f(Mapiter<const char*, Shape*>& iter){229Бьерн Страуструп.Язык программирования С++// ...if (iter) {// установлен на элемент таблицы}else {// не установлен на элемент таблицы}// ...}Аналогичный прием используется для контроля потоковых операций ввода-вывода в $$10.3.2.Если итератор не установлен на элемент таблицы, его функции key() и value() возвращают ссылки настандартные объекты.Если после всех этих определений вы забыли их назначение, можно привести еще одну небольшуюпрограмму, использующую таблицу Map.
Пусть входной поток является списком пар значенийследующего вида:hammernailsawsawhammernailnail21003471000250Нужно отсортировать список так, чтобы значения, соответствующие одному предмету, складывались, инапечатать получившийся список вместе с итоговым значением:hammer9nail1350saw7------------------total1366Вначале напишем функцию, которая читает входные строки и заносит предметы с их количеством втаблицу. Ключом в этой таблице является первое слово строки:template<class K, class V>void readlines(Map<K,V>&key){K word;while (cin >> word) {V val = 0;if (cin >> val)key[word] +=val;elsereturn;}}Теперь можно написать простую программу, вызывающую функцию readlines() и печатающуюполучившуюся таблицу:main(){Map<String,int> tbl("nil",0);readlines(tbl);int total = 0;for (Mapiter<String,int> p(tbl); p; ++p) {int val = p.value();total +=val;230Бьерн Страуструп.Язык программирования С++cout << p.key() << '\t' << val << '\n';}cout << "--------------------\n";cout << "total\t" << total << '\n';}8.9 Упражнения1.(*2) Определите семейство списков с двойной связью, которые будут двойниками списков с однойсвязью, определенных в $$8.3.2.(*3) Определите шаблон типа String, параметром которого является тип символа.
Покажите как егоможно использовать не только для обычных символов, но и для гипотетического класса lchar,который представляет символы не из английского алфавита или расширенный набор символов.Нужно постараться так определить String, чтобы пользователь не заметил ухудшения характеристикпрограммы по памяти и времени или в удобстве по сравнению с обычным строковым классом.3.(*1.5) Определите класс Record (запись) с двумя членами-данными: count (количество) и price(цена).
Упорядочите вектор из таких записей по каждому из членов. При этом нельзя изменятьфункцию сортировки и шаблон Vector.4.(*2) Завершите определения шаблонного класса Map, написав недостающие функции-члены.5.(*2) Задайте другую реализацию Map из $$8.8, используя списочный класс с двойной связью.6.(*2.5) Задайте другую реализацию Map из $$8.8, используя сбалансированное дерево. Такиедеревья описаны в $$6.2.3 книги Д. Кнут "Искусство программирования для ЭВМ" т.1, "Мир", 1978[K].7.(*2) Сравните качество двух реализаций Map. В первой используется класс Link со своейсобственной функцией размещения, а во второй - без нее.8.(*3) Сравните производительность программы подсчета слов из $$8.8 и такой же программы, неиспользующей класса Map.
Операции ввода-вывода должны одинаково использоваться в обеихпрограммах. Сравните несколько таких программ, использующих разные варианты класса Map, втом числе и класс из вашей библиотеки, если он там есть.9.(*2.5) С помощью класса Map реализуйте топологическую сортировку. Она описана в [K] т.1, стр.323-332. (см. упражнение 6).10. (*2) Модифицируйте программу из $$8.8 так, чтобы она работала правильно для длинных имен идля имен, содержащих пробелы (например, "thumb back").11. (*2) Определите шаблон типа для чтения различных видов строк, например, таких (предмет,количество, цена).12.
(*2) Определите класс Sort из $$8.4.5, использующий сортировку по методу Шелла. Покажите какможно задать метод сортировки с помощью параметра шаблона. Алгоритм сортировки описан в [K]т.3, $$5.2.1 (см. упражнение 6).13. (*1) Измените определения Map и Mapiter так, чтобы постфиксные операции ++ и -- возвращалиобъект Mapiter.14. (*1.5) Используйте шаблоны типа в стиле модульного программирования, как это было показано в$$8.4.5 и напишите функцию сортировки, рассчитанную сразу на Vector<T> и T[].231Бьерн Страуструп.Язык программирования С++ГЛАВА 9.Я прервал вас, поэтому не прерывайте меня.- Уинстон ЧерчиллВ этой главе описан механизм обработки особых ситуаций и некоторые, основывающиеся на нем,способы обработки ошибок.
Механизм состоит в запуске особой ситуации, которую должен перехватитьспециальный обработчик. Описываются правила перехвата особых ситуаций и правила реакции нанеперехваченные и неожиданные особые ситуации. Целые группы особых ситуаций можно определитькак производные классы. Описывается способ, использующий деструкторы и обработку особыхситуаций, который обеспечивает надежное и скрытое от пользователя управление ресурсами.9.1 Обработка ошибокСоздатель библиотеки способен обнаружить динамические ошибки, но не представляет какой в общемслучае должна быть реакция на них.
Пользователь библиотеки способен написать реакцию на такиеошибки, но не в силах их обнаружить. Если бы он мог, то сам разобрался бы с ошибками в своейпрограмме, и их не пришлось бы выявлять в библиотечных функциях. Для решения этой проблемы вязык введено понятие особой ситуации.Только недавно комитетом по стандартизации С++ особые ситуации были включены в стандарт языка,но на время написания этой книги они еще не вошли в большинство реализаций.Суть этого понятия в том, что функция, которая обнаружила ошибку и не может справиться с нею,запускает особую ситуацию, рассчитывая, что устранить проблему можно в той функции, которая прямоили опосредованно вызывала первую. Если функция рассчитана на обработку ошибок некоторого вида,она может указать это явно, как готовность перехватить данную особую ситуацию.Рассмотрим в качестве примера как для класса Vector можно представлять и обрабатывать особыеситуации, вызванные выходом за границу массива:class Vector {int* p;int sz;public:class Range { };int& operator[](int i);// ...};// класс для особой ситуацииПредполагается, что объекты класса Range будут использоваться как особые ситуации, и запускать ихможно так:int& Vector::operator[](int i){if (0<=i && i<sz) return p[i];throw Range();}Если в функции предусмотрена реакция на ошибку недопустимого значения индекса, то ту частьфункции, в которой эти ошибки будут перехватываться, надо поместить в оператор try.