Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 43
Текст из файла (страница 43)
И наоборот — функция не может возвращать значение, если она объявлена с ключевым словом го!д. Например: Возвращаемое значение задается в операторе гетги. Например: !п!/ас(тг п) (ге(игп (п>1)? и+уас(и — 1):1; ) Функцию, которая вызывает саму себя, называют рекурсивной (гесигя!яе). Функция может содержать более одного оператора гешгп: Как и семантика передачи аргументов, семантика возврата значения из функции идентична семантике иниииализаиии.
Оператор гешгп инициализирует неименованную переменную, имеющую тип возврата функции. Тип выражения, который содер- !п! 17 ( ) ( ) гоЫ !3 ( ) ( ) !п(13 () (гвгиги 1; гоЫ/<г() (ге!игл 1; ) !и! Гу() (ге!иго; ) гоЫ/б() (ге!игл; ) !игр г(!и!и) ( (!'(и>1) ге!игп и уасг (п-1) ге!игл 1; ) // еггог: не возвращается значение р о!с // о)г //еггог: возврат в гоЫ фунячии // еггог: отсутствует возвращаемое значение // о)г 7.4. Перегрузка имен функций 201 жится в операторе гешгп, сопоставляется с объявленным типом возврата функции, и при необходимости выполняются стандартные преобразования типов, или преоб- разования, определенные пользователем. Например: аоиЫе Г() ( ге!иги 1; ) //! неявно преобразуется в с(оиб!еЯ Каждый раз при вызове функции выделяется память под ее аргументы и локальные (автоматические) переменные.
После возврата из функции эта память считается свободной и может использоваться повторно. Поэтому указатель на локальную переменную возврашать не следует: значение,на которое он указывает, может измениться неожиданно: /и!*1р() ( ии !оса!=1; /" ... */ ге!игп Иоса!; ) //плохо Эта ошибка встречается реже эквивалентной ошибки с возвратом ссылок: 1п!6 Гг() (1п! !оса!=1; /* ...
*/ ге!игп !оса!> ) //плохо гоЫЛ(!и!* р); кои й (т!* р) ( /* ... '/ ге!игл Л(р) ! ) //орс возвращает "никакое зночгние" Подобного вида операторы ге(цгп широко применяются при написании шаблонных функций, в которых тип возвращаемого значения является параметром шаблона ($18.4.4.2). 7.4. Перегрузка имен функций Чаще всего, разным функциям дают разные имена. Однако когда концептуально одинаковые функции выполняют одинаковую работу над объектами разных типов, то удобно дать им одинаковые имена. Это называют перегрузкой имен функций (очег(оай!иб). Такая техника всегда применялась для встроенных операций, например, одно и то же имя (обозначение) операции сложения, +, используется для сложения целых чисел, чисел с плавающей запятой и указательных типов.
Эта идея легко распространяется на функции, определяемые пользователем. Например: // печать целого / яечать С-строки чоЫ рг!и! (1пи; го!г! ргт! (сопл! спас*) С точки зрения компилятора у этих функций есть только одна общая черта— имя. Конечно, предполагается, что такие функции должны быть похожи по смыслу, но сам язык не накладывает здесь никаких ограничений, так что в этом вопросе он программисту и не помогает, и не мешает.
Таким образом, перегрузка имен функций является просто удобным и наглядным способом именования, особенно для К счастью, компилятор обычно предупреждает, что осушествляется возврат ссылки на локальную переменную. Функция, объявленная с ключевым словом го!а, не может возвращать значений. Поэтому в теле го!а-функций можно использовать операторы гегиги с выражениями вызова других го!а-функций. Например: 2О2 Глава 7 Функции функций с расхозкиии именами, такими как з<!г<, ргт< или орел.
Когда выбор имени важен с точки зрения семантики, возможность перегрузки имен функций становится ценной вдвойне. Это имеет место, например, для операций +, *, «и т.д., для конструкторов (81!.7) и в обобщенном программировании (82.7.2, глава 18). Когда вызывается функция Т(), компилятор должен решить, какая именно из функций с этим именем должна быть выбрана. Для выбора компилятор сравнивает типы фактических аргументов вызова с типом формальных параметров всех функций с именем ( Идея состоит в том, чтобы вызвать ту функцию, чьи аргументы подходят больше всего, или выдать ошибку компиляции в случае невозможности такого выбора.
Например; чоЫ рпт ( «оиЫе ) < чоЫ ргт< (1опд) < чай< (() ( рг<п< (1А) < ?? рт!и<(!Опя) рпп< (1. 0); ?? рты<(доиЫе) рпп< (1); У егтог: неоднозначность: рг<пфопд(!)) изи рпп<(доиЫе(!))? ) Поиск наиболее подходящей для вызова функции из имеющегося набора перегруженных функций выполняется выбором единственного наилучшего соответствия между типами выражений для аргументов вызова и типами формальных параметров этих функций. Чтобы формализовать понятие наилучшего соответствия, следующие критерии применяются в указанном порядке; 1. Точное совпадение типов, при котором или вообще не нужно выполнять преобразований типов, или только самые тривиальные (имя массива к указателю на первый элемент, имя функции к указателю на функцию, тип Тк типу сопз< Т).
2. Совпадение после «продвижения типов вверх»: для интегральных типов это продвижения Ьоо! в 1п<, айаг в т<, зйог< в йз< и их ипз<йпе<1 аналоги 6С.6.1), и продвижение 11оа< в <1оиЫе. 3. Совпадение после стандартных преобразований типов: например, <и< в <1оиЬ- !е, <1оиЫе в т«, (оиЫе в 1опи <1оиЫо, указатели на производные типы в указатели на базовые типы (812.2), Т" в гоЫ* Я5.6), т< в ииз(иле<1 т< (5С.6). 4. Совпадение после преобразований типов, определяемых пользователем (81! .4).
5. Совпадение типов из-за много<почий (... — е!!!рзй) в обьявлении функции (57.6). Если выявляются два совпадения одного и того же наивысшего уровня, то вызов функции считается неоднозначным (а<пЬ<8иоиз) и отвергается компилятором. Сформулированные кригперии разрешения перегрузки (ге<о!и<юп и<!ез) основаны на соответствующих правилах языков С и С+-ь для встроенных числовых типов данных ЯС.6). Например: чоЫ рпп< (о«); чоЫ рпп< (сопз< слог' ); чоЫ рпп< (доиЫе); чоЫртпи(!опи) < чо1«рт<п<(сйаг) < 7.4. Перегрузка имен функций 203 ис с, зйогс з, )!оаг)) Для вызова рг!пг (О) выбирается вариант рг!пг(спг), поскольку О имеет тип иг.
А для вызова рлпг('а') выбирается рппг(сйаг), ибо 'а' имеет тип айаг 54 3.1). Причина раздельного рассмотрения преобразований и продвижений заключается в предпочтении безопасных продвижений, таких как сйат в Ыг, небезопасным преобразованиям вроде !пг в айаг. Результат разрешения перегрузки функций не зависит от порядка объявления функций в программе. Разрешение перегрузки функций базируется на довольно-таки сложном наборе правил, так что иногда программисту выбор компилятора покажется неочевидным. Здесь уместен вопрос, ради чего все зто делается? Рассмотрим альтернативу перегрузки функций.
Часто приходится выполнять похожие действия над объектами разных типов. Без перегрузки мы в таких случаях вынуждены определять несколько функций с разными именами: чоЫрппс сп1(!пс) г го!с! рг1п1 сйаг (сйаг) ! гоЫ рпум згт!па (сонм сйаг*) с, сопя! сйаг* р, с!оиЫе с!) // ой // о!с // о!с .
до!с? вызывается рп«1 !пс(ис(с)) // о!с? вызывается рг!пг сйат(сйаг(!)) // еп ог /У о!с? вызывается рппг !пг(!п1(с!)) В итоге нужно помнить все функции и не ошибаться в выборе правильного варианта. Это утомительно, препятствует обобщенному программированию (52.7.2) и фокусирует внимание на относительно низкоуровневых аспектах типов данных.
В отсутствие перегрузки к аргументам вызова функций применяются все стандартные преобразования типов. Это может приводить к ошибкам. Так, в последнем примере из четырех ошибочных вызовов компилятором обнаруживается лишь один. Перегрузка же функций повышает шансы компилятора на то, что неправильные аргументы вызова будут им обнаружены и отвергнуты.
юИ й (сйаг с, ( рт!пс (с) 1 рпп1(!) 1 рлпг (з) 1 ргйм (!) ! рг!пг( 'а' ) 1 рппг (49); рлпг (()) 1 рлпс("а") 1 ) // точное соответствие! У точное соответствие: // интегральное продвижение: // продвижение от)) оас «йоиЫес р точное соответствие: // точное соответствие: У точное соответствие: У точное соответствие.' ио!И е(йм 1', айаг ( рппг ис(с) 1 рпи сйаг(с) 1 рппг згт!пК (р) 1 рт(пг спг(с) ! рпиг сйат (1); рппг згт!пд (1) 1 рлпг спг(с() ! ) рппг(сйат) рппс(па) ргис(!пг) рг!п1(с!оиЫе) рлпс(сйаг) рт(пг(!пг) ртиг(ис) отсп1(соп51 сйат ) 2О4 Глава 7.
Функции 7.4.1. Перегрузка и возвращаемые типы Возвращаемые типы не участвуют в разрешении перегрузки. Это сделано для обеспечения независимости выбора перегруженных операций 611.2.1, 811.2.4) и вызова функций от окружающего контекста. Рассмотрим пример: 7)оаз здге ())оаз) з доиЫе здгз (йоиЫе); гоЫ 7(йоийе Иа, 71 вазу)а ) )гаазу) = здгз(в(а) ) л' вызывается здг(ЫоиЫе) иоиЫе а = здг( (да); У вызывается здг((доиЫе) )з = здгз ())а); гУ вызывается здг(1))оаг) д = здгз ()1а) ) // вызывается здг(Г))оа() ) Если возвращаемые типы принимать во внимание, то уже нельзя по одному лишь виду вызова здгг() решить, о каком варианте функции идет речь.
7.4.2. Перегрузка и области видимости Перегрузка имен функций имеет место только в одной и той же облаопи видимости. Например; го1~Ц'(1нз) гоЫи() ( во1а )'(авиа)е); Х(1); ) гу вызывается )ЫоиЫе) 7.4.3. Явное разрешение неоднозначностей Объявление слишком малого (нли слишком большого) количества перегруженных вариантов функции может приводить к неоднозначностям. Например: го1И)7 (сваг); гоЫ)7 (!опе); гоЫ /2 (сйаг*) гоЫ /2 (1нз* ); Очевидно, что /'(1иг) была бы идеальным соответствием вызовут(1), но в текущей области видимости объявлен лишь вариант 7(аоиЫе).