|
Программирование >> Включение нужных заголовков
Напомню, что функция сортировки эти строки не различает. Следует ли вставить строки в произвольном порядке, добровольно отказавшись от детерминированного порядка перебора содержимого контейнера? Недетерминированный порядок перебора уже присущ ассоциативным контейнерам multiset и multimap, поскольку Стандарт не устанавливает никаких ограничений на относительный порядок следования эквивалентных значений (multiset) или ключей (multimap). Или нам следует настоять на детерминированном порядке содержимого s и проигнорировать вторую попытку вставки (для строки persephone )? А если будет выбран этот вариант, что произойдет при выполнении следующей команды: if (s.findCpersephdne ) != s.endO)... Каким будет результат проверки? Функция find использует проверку равенства, но если проигнорировать второй вызов insert для сохранения детерминированного порядка элементов s, проверка даст отрицательный результат - хотя строка persephone была отвергнута как дубликат! Мораль: используя одну функцию сравнения и принимая решение о совпадении двух значений на основании их эквивалентности, мы избегаем многочисленных затруднений, возникающих при использовании двух функций сравнения. Поначалу такой подход выглядит несколько странно (особенно когда вы видите, что внутренняя и внешняя версии find возвращают разные результаты), но в перспективе он избавляет от всевозможных затруднений, возникающих при смешанном использовании равенства и эквивалентности в стандартных ассоциативных контейнерах. Но стоит отойти от сортированных ассоциативных контейнеров, как ситуация изменяется, и проблему равенства и эквивалентности приходится решать заново. Существуют две общепринятые реализации для нестандартных (но широко распространенных) ассоциативных контейнеров на базе хэш-таблиц. Одна реализация основана на равенстве, а другая - на эквивалентности. В совете 25 приводится дополнительная информация об этих контейнерах и тех принципах, на которых они основаны. Совет 20. Определите тип сравнения для ассоциативного контейнера, содержащего указатели Предположим, у нас имеется контейнер set, содержащий указатели string*, и мы пытаемся включить в него несколько новых элементов: set<string*> ssp: ssp = set of string ptrs ssp.i nsert(new stri ng( Anteater )): ssp.insert(new string( Wombat )): ssp.insert(new stri ng( Lemur )): ssp.insert(new string( Penguin )): Строго говоря, не все 24 перестановки равновероятны, так что вероятность 1/24 не совсем точна. Тем не менее, остается бесспорный факт: а/ществуют 24 разные перестановки, и вы можете получить любую из них. Следующий фрагмент выводит содержимое set. Предполагается, что строки будут выведены в алфавитном порядке - ведь содержимое контейнеров set автоматически сортируется! for (set<string*>: iconstjterator i = ssp.beginO: Предполагаемый i != ssp.endO; порядок вывода: ++i) Anteater . Lemur , cout *i endl: Penguin , Wombat Однако на практике ничего похожего не происходит. Вместо строк выводятся четыре шестнадцатеричных числа - значения указателей. Поскольку в контейнере set хранятся указатели, *i является не строкой, а указателем на строку. Пусть этот урок напоминает, чтобы вы следовали рекомендациям совета 43 и избегали написания собственных циклов. Использование алгоритма сору: copyCssp.beginO.ssp.endO, Скопировать строки, ostream iterator<string>(cout, \n )): содержащиеся в ssp, в cout (не компилируется!) не только делает программу более компактной, но и помогает быстрее обнаружить ошибку, поскольку вызов сору не компилируется. Итератор ostreain i terator должен знать тип выводимого объекта, поэтому когда компилятор обнаруживает расхождение между заданным в параметре шаблона типом string и типом объекта, хранящегося в ssp (string*), он вьщает ошибку. Еще один довод в пользу сильной типизации... Если заменить *i в цикле на **i, возможно, вы получите нужный результат - но скорее всего, этого не произойдет. Да, строки будут выведены, но вероятность их следования в алфавитном порядке равна всего 1 /24. Контейнер ssp хранит свои элементы в отсортированном виде, однако он содержит указатели, поэтому сортироваться будут значения указателей, а не строки. Существует 24 возможных перестановки для четырех указателей, то есть 24 разных последовательности, из которых лишь одна отсортирована в алфавитном порядке. Подходя к решению этой проблемы, нелишне вспомнить, что объявление set<string*> ssp: представляет собой сокращенную запись для объявления set<string*.1ess<string*> > ssp: Строго говоря, это сокращенная запись для объявления set<string*.less<string*>,a11ocator<string*> > ssp: но в контексте данного совета распределители памяти несущественны. Если вы хотите сохранить указатели stri ng* в контейнере set так, чтобы их порядок определялся значениями строк, стандартный функтор сравнения less<string*> вам не подойдет. Вместо этого необходимо написать собственный функтор сравнения, который получает указатели string* и упорядочивает их по содержимому строк, на которые они ссылаются. Пример: struct StringPtrLess: public binary function<const string*. Базовый класс const string*, описан в совете 40 bool> { bool operatorO (const string *psl, const string *ps2) const { return *psl<*ps2: После этого Stri ngPtrLess используется в качестве типа критерия сравнения ssp: typedef set<string*, StringPtrLess> StringPtrSet: StringPtrSet ssp; Создать множество с объектами string и порядком сортировки, определяемым критерием StringPtrLess Вставить те же четыре строки Теперь приведенный выше цикл будет работать именно так, как предполагалось (при условии, что ошибка была исправлена и вместо *i используется **i). forCStringPtrSet: :const Jterator i= ssp.beginO; i != ssp.endO; Порядок вывода: ++i) Anteater . Lemur , cout **i endl: Penguin . Wombat Если вы предпочитаете использовать алгоритм, напишите функцию, которая разыменовывает указатели string* перед выводом, а затем используйте ее в сочетании с for each: void print(const string *ps) Вывести в cout объект, { на который ссылается ps cout *ps endl: for each(ssp.begin(),ssp.endO.print); Вызвать print для каждого элемента ssp Существует более изощренное решение - обобщенный функтор разыменования, используемый с transform и ostreain i terator: Функтор получает Т* и возвращает const Т& struct Dereference! tempiate<typename Т> const Т& operatorO (const T* ptr) const return *ptr: transformCssp.beginO,ssp.endO. Преобразовать каждый ostream.iterator<string>(cout. \n ), элемент ssp посредством DereferenceO): разыменования и записать результаты в cout Впрочем, замена циклов алгоритмами будет подробно рассматриваться позднее, в совете 43. А сейчас речь идет о том, что при создании стандартного ассоциативного контейнера указателей следует помнить: содержимое контейнера будет сортиро-
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |