Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 73
Текст из файла (страница 73)
д. В качестве иллюстрации давайте перепишем пример из 2 5.5, в котором в небольшой программе, подсчитывающей количество вхождений слов в файл, использовался ассоциативньпл массив. Тогда мы применили функцию. Здесь мы определим тип ассоциативного массива: с1аее Аззо с ( зггис1 Рп(г ( зсгту пате; с(оиЫе оа(, Рагг (зггГпу пса ', с(ггиЫе оГ 0) г пате (пг, оа1 (о) ( ) рибйс с(аез Уеаг( гп1 у; риЬйс ехрйсг1 1еаг (гпГГ) . у (1) ( ) орегп1ог Гпс () сопз1( ге1игп у; ) ), с(а ее Ра 1е ( риЫ!с.
Ра1е (гп1с(, мопГь т, Уеаг у), // ), оесГогкРагг» оес; Аззос (сопзГАззосЦ Лезосй орега1ог=',сопз1 АззосВ), // ггакрлггггмгй нтоблг // ггредопгвратит» копирование //закрытын, нтобы // ггредогпврсг гпггтл котгрова юге 335 11.9. Вызов функции Аззос () ( ) сопз1 с(вийей орегасог(] (сопз1з1пссуй)> с(оиЫей орегп1оп[) (зггслЯ; иоаург!п1 аИ() сопз1, Аввос (ассоциативный массив) хранит вектор Расгв (пар).
Реализация использует тот же самый тривиальный н неэффективный метод поиска, что и в ч) 5.5: О поиск з, возврат ее значения, если найдена; в противнол> случае создание О нового обивка>и Расг л возврат значею>я ао умолчали>о (нулевого > с(оиЫей Алсос: орегп1ог[) (зггсляй з) ~ог (иес1ог<расг> Иега1огр = иес Ьеи1л (); р >= иес.епд (); ++р) сг" (з==р — ' ппте) ге1игп р — иа1, вес ризЬ Ьасй (Рпсг(з,д)); с>>> начальное значение 0 гегигпиес Ьасй () иа1, О возврат последнего зле>сента (э 16ЗЦЗ) Рак как представленне Аввос скрыто, требуется некоторьш способ его вывода на печать: ио(с(Аззос рплг аИ () сопзс ( )ог (иес1ог<расг> солз1 Иегп1огр = иес Ьеа(п () р! = вес елд (); ч+р) сои1 «р — >пате « '.
«р->иа1« ">л', ) И, наконец, мы можем написать тривиальнук> главнусс> программу: >>>> подсчеп> количества вхождении каждого слова во вкодносс по>паке сп1 тасп (') з1ппя ЬиЯ Авзос иес; юЬИе >с1п» Ьи)) иес[Ьи)) +ч; иесрппс аИ(); Дальнейшее развитие идеи ассоциативного массива описывается в З 17.4.1. Функцыя орега1огП () должна быть членом. 11.9. Вызов функции Вызов функции, то есть обозначение вьсраженне(гтсгок выражений), можно интерпретировать как бинарную операп>по, где вьсралсе>сие — левый операнд, а список выражений — правып. Оператор вызова () может быть перегружен точно так же, как и другие операторы.
Аргун>инты пз списка оператора орега1ог() () вычисляются и проверяются в соответствии с обычными правилами передачи аргулсентов. Пересрузка вызова функции скорее все> о полезна в основном для определения типов с одной единственной операцией илп типов с одной главной операцией. Оператор вызова также известен как прикладной оператор (арр)юайоп орегатог).
На>>более очевидным и, вероятно, наиболее важным использованием оператора () является предоставление синтаксиса стандартного вызова функций для объектов, ко- Глава 11. Перегрузка операторов 336 !ог еас6,'па Ьеу!и (), аа.епл((), пеуаге); //люл~енять знак у всекзлел~ениюв вектора !ог енсу (П Ьеут (), Пепе((), педа1е), //пол~внять зчак у ввел элементов списка Зта функция меняет знак у всех элементов вектора и списка. Что, если бы мы захотели прибавить сотр1ех (2, 3) ко всем элементам'? Зто можно легко сделать следующим образом: иоИ агЫ22 (сотр!ехй с) с»=- сотр1ех (2, в); ) оо?ау (оес1ог<сотр1ех>й аа, Пв!'сотр!ех»й П) Хог еас6 (аа Ьеу1п (), аа епс1 (), псЫ23) Уог еас6 (ПЬеу?п (), Пепе?(), а<Ы23); ) Каким образом мы написали бы функцию для регулярно~о добавления произвольного комплексного числа? Нам требуется нечто, чему мы можем передать произвольное значение и что затем может использовать это значение каждый раз, когда вызывается, Зтого не происходит естественным образом при использовании функций.
Обычно «передача» произвольного значения заканчивается оставлением его в окружающем функцию контексте. Это приводит к излишней путанице. Однако мы можем написать класс, который ведет себя желаемым образом: сЫвв АлЫ ( сотр1ех оа1; риЫлс. , АсЫ(солпр1ехс)(иа1=с;) АсЫ Ыои61е г, доиЫе й ( оа1= сотр!ех (г, 1), ) // сохранном значение //прибавить значение // к аргументу ооы орега1ог () (сотр1ехй с) сопвг ( с += оа1, ) Объект класса Ас!П инициализируется комплексным числом, и при вызове с исполь- зованием () прибавляет это число к аргументу. Например: иоЫ 6 (вес1ог сотр!ех>й аа, Пвг<сотр1ех>й П, сотр!ех г) ( лог епс6 (ап.Ьеут (), аа.епл1 (),А~И (2, У)), /ог епсв (П Ьеуш (), П епс1 ()АсЫ(х)); торые в некотороьч смысле ведут себя как функции. Объект, который ведет себя как функция, часто называют функции-подобным обьектолл или просто объекта ~-функцией Я 18А), Объекты-функции играют важную роль, поскольку они позволяют нам писать код с использованием нетривиальных операцглй в качестве параметров.
Например, в стандартной библиотеке имеется множество алгоритмов, которые вызывают функцию для каждого элемента контейнера. Рассмотрим пример: ооЫ пеуа1е (сотр(ехй с) ( с = -с; ) ооЫХ(оес1огчсотр(ех»й аа, Пвг<сотр!ех>й 1й 337 11.10. Разыменование В примере сотр1ех (2, 3) будет прибавлено к каждому элементу вектора и а — к каждомуу элементу списка.
Обратите внимание, что АгЫ (х) создав~ объект, который затем используется повторно функциейуог еасй (). Этот объект -- не просто функция, которая вызвана один раз или даже повторно. Функция, которая вызывается повторно, — это орега1ог() () объекта АЫ (х). Изложенное выше работает, потому чтоуог еасй является шаблоном, который применяет () к третьему аргументу, це заботясь о том, чем он в действительности является: 1етр!а1е<с(авв 71ег, с(акв Ггь> Гв! Гаг еаоп (йег Ь, 11ег е, Гсгз) ( шЬ11е (Ь~.= в', Г('Ь-'- ); ге1ига Г, 11.10.
Разыменование Оператор разыменованпя — > можно опрелелить в виде унарного постфпксного опе- ратора. Например: с1авв Р!г( О... Л орега1аг — > (1, Теперь можно пользоваться объектами класса Р1г для доступа к членам класса Хсо- вершенно аналогично указателям. Например: воЫГ(Р1гр) р->и =. 7; ~( (р.орегп(ог — >()) — »п = 7 Преобразование объекта р в указатель р.орега1ог-> () не зависит от члена т, на кото- рый он указывает.
Как раз в этом смысле, орега1ог-> () является унарным постфпкс- ным оператором. Однако никакой новьпл синтаксис не вводился, поэтому все равно требуется пмя члена после ->. Например: па1г( и (Р1г р) ( Л" а1=р->; Х' а2 = р орега1ог— О гпппшкпшескпк ошибка 0 правильно Перегрузка -> в основном полезна для создания куь>ных указателей>, то есть объектов, которые веду> себя как указатели и, кроме того, выполняют некоторые действия, когда через ннх осуществляется доступ к объекту. Например, можно определить Г! а первый взгляд может показаться, что такая техника рассчитана па посвященных, но на самом деле опа проста, эффективна и исключительно полезна (см.
3 3 85, 3 18 4), Другим популярным способом использования орега1ог() () является применеш>е его в качестве оператора подстроки и оператора индексации в многомерных массивах Я 22А.5). Функция орега1ог () () должна быть членом. Глава 11. Перегрузка операторов 338 с1аяя Ряс р1г( сапя1 с1таг" Ыеп101ег, )сес" 1п саге аййгезз, риЬИс Вес р1«(салзс с1~аг'р) т саге аййгеяя (О), Ыеп1Яег(р) () -)сес р1г()( тй1е 1а й1зЬ(т саге аййгеяз, 1йеп1фег), ) )сес' арега1аг — > (); ), зчес'Вес р!г арега1аг — > () 11 (т саге аййгеяя == О) т саге пййгезз = геий /гат йсяд (1йеп1111ег).
ге1игп 1л саге аййгезз; ясес р1г можно попо.льзовю ь следуюпсим осбразом: я1«исс Нес ( //)сес, на которыйукаяыапе(пиес р1г ягг1пд пате; /ч иаЫ ирйа1е (сопя1 сдаг* я) ( )сес р1гр (я), //позвчить Гсес р1г для я // излечить я (при неодяад~ьпосчпи //считая его гначаза с диска) р- лате ='Разсае"; Естественно, реальный )сес р1г бы.л бы сделан шаблоном, чтобы тип )сес был параметром. Кроьче того, реальная программа содержала бы в себе код обработки ошибок и использовала лченее наивный способ взаимодействия с диском.
Для обыкновенных указателей, использование оператора -> является синонимом некоторой комбинации унарных операторов * и []. Если имеется У, для которого ->, * и Ц имеют значение по умолчанию, и У" вызывает р, то // истина // истина //истина р->т == (*р) т (*р) т == р[0] т р->т == р[0] т Как обычно, никаких гарантий не существует для операторов, определяемых пользо- вателем.
Гели требуется подобная эквивалентность, ее можно добиться: с1аяя Р1г 1а Т( У'р, риЬЕсс. У* арегп1ог — > () ( ге1игл р, ) класс )сес р1г для доступа к обьектам класса )сес, хранящимся на диске. Конструктор )1ес р1«получает в качестве аргумента пмя, которое используется для нахождения объекта на диске, )сес р1гсорега1о1-> () извлекает объект в память при доступе через его Лес р1г, а деструктор )сес р1г записывает изменения объекта на диск: 339 11.11. Инкремент и декремент г8, арегагаг* )) ( гесигп *р, ) Ус' арегасаг)) ~тС 1) ) геГвгп рЯ, ) ), Если вы пишете несколько таких операторов, разумно обеспечить эквивалентность, точно также, как желательно убедиться, что ++х и х«=Т имеют тот же эффект, что и х=х«1 для простой переменной х некоторого кяасса, для которого реализованы операторы ++, +=, = и +. Перегрузка -> имеет значение для целого круга интересных задач, так что это не просто занятная безделушка.
При п«на здесь в том, что косвенньяи доступ является ключевой концепцией, а -> предоставляет ясный, прямой н эффективный способ его реализации в программе. Важными примерами являются итераторы (глава 19). С другой стороны. можно рассматривать оператор -> в качестве способа реализации в С+-~- ограниченной, но полеаной формы делегирования Я 2 п3.6). Оператор -> должен быть функцией-членолк Типом возвращаемого результата должен быть либо указатель, либо обьект класса, к которому вы можете применять ->. При объявлении для класса-шаблона орега1ог-> () часто не используется, поэтому имеет смысл отложить проверку ограничений на возвращаемый тип до реального использования.
11.11. Инкремент и декремент Раз уж изобретены «умные указатели», часто возникает желание реализовать и операторы инкремента ++ и декремента —, аналогичные по смыслу тем, что используются со встроенными типамп. Это особенно очевидно и необходимо там, где целью является замещение обыкновенного типа указателя типом «умного указателя» с той жс семантикой, по с добавлением проверок во время выполнения. Например, рассмотрим традиционную программу с возможными проблемами: /г' традиционное ииюльзавание иаЫ Ц )Та) Т и'Ь2001, Т'р = Ди)0), р 'р = а, ,ГГ проблема: р вне допустимых пределов /~ и эта ив перехва пил ва ется -Н-р, 'р=а, !управильно Мы можем заменить указатель р на объект класса Р)г То Т, к которому можно применять операцию разыменования только в том случае, если он действительно указывает на объект. Мы также хотели бы, чтобы к р можно было применять операции пнкремента и декремента только в том случае, если он указывает на элемент массива, и в результате этих операций он по прежнему будет указывать на некий элемент массива.