Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 43
Текст из файла (страница 43)
В этом случае аргумент можно объявить с модификатором сопя!, чтобы указать, что ссылка используется только из соображений эффективности, а це для того, чтобы функция изменила значение объекта: 189 7.2. Передача аргументов Запрещение преобразования неконстантных аргументов, передаваемых по ссылке Ц 5.5), позволяет избежать глупых ошибок, возникающих из-за создания временных переменных.
Например: ооЫ ирс1аге Яоагй !); ооЫ у у!оиЫе б,!1оа1г~ ( ирг!а1е !2.0!), !! отибкщ константгияй аргумент ирда1е ~г~, О передает ссылку на г ирг!а1е ф, О ошибки: требуется преобразование типов Если бы такие вызовы были разрешены, ирг7а1е (! спокойно изменила бы временные переменные, которые тут же были бы удалены. Скорее всего, это привело бы к неприятным сюрпризам для программиста. 7.2.1. Массивы в качестве аргументов Если в качестве аргумента функции псповьзуется массив, передается указатель ца его первьпл элемент.
Например: т1е1г!еп ~сопи! с!га~ ~; ио!г! 7'~) с!гаг оЦ = 'массив"; спг !' = вгг!еп ~о1, !и1!'= з1г!еп ~ !У!с!со!ав') То есть прн вызове функции аргумент типа Т[] будет преобразован в 7 . Из этого следует, что присвапвание элементу массива, являющегося аргументом, изменяет значение элемента самого массива, Другими словами, массивы отличаются от дру! нх типов тем, что их нельзя передать по значению.
Размер массива неизвестен в вызываемой функции. Это может составлять неудобство, но существует несколько способов решения данной проблемы. С-строки ограничены нулем, и поэтолсу их размер легко вычислить. Для друпгх массивов можно передавать второй аргумент, означающий размер массива. Например; оо!беатрисе! 1спг" оес рвот!нес е!ее~; Оодинспосоо з1гисг нес 1 !п1' р1г; сп1егее; ооЫ сотрисе2 !сопя! Уесй о~, О другои спосоо В качестве альтернативы вмссто массивов можно воспользоваться такими типами, как оес1ог Я 3.7.1, 1! 1б.З).
С многомерными массивами проблем больше !сы. 9 В.7), но часто вместо нпх можно попользовать массивы указателей; в этом случае не требуется никаких специальных трюков. Например: Глава 7. Функции 190 сбаг'дау() =( "понедельникб 'в!парник", среда", 'четверг", "пятнииа", суббота, 'воскресенье" Подчеркну еще раз, что оес1ог и подобные типы являются альтернативой встроен- ным массивам низкого уровня н указателям. 7.3. Возвращаемое значение Функция должна возвращать значение, если она не объявлена как иоЫ (при том, что функция таси (), является зюключением, см. й 3.2), И наоборот — значение не мо- жет быть возвращено из функции, если она объявлена как ооЫ.
Например: !ис/1 ()() // о!ппбка: не возвращаеп!ся значение по!с(/2 () ( ) // правильно !и!33 () ( ге!игл 1; ) // правильно по!с(14 () (ге!иги1;) //ошибка: возврищоепое значение вфункцпп ооълвленной, кпкпоЫ чп115 () ( ге!игп; ) //о!ппбка: не указано возвращпвлов значение ооЫ/б () ( !.е!игп, ) //правильно Возвращаемое значение задается инструкцией ге!игл. Например: !п! ~ас (!п! и) ( ге! игл (п>1) з п /ас (п — 1): 1, ) Функции, которые вызывают сами себя, называются рвкурсивньпии.
В функции люжет быть более одной инструкции ге1игп: !п!/ас2 (ш! п) б' (п > 1)ге!игп п'Зас2 (п — 1); ге!ига 1, с(оиб!еЯ ( //... ге!игп 1; ) // ! неявно првоорпзуется в допб!е( !) При каждом вызове функции создаются новые копии аргументов и локальных (автоматических) переменных. Память может повторно использоваться после возврата из функции, поэтому, указатель на локальную переменну.ю возвращать не следует. Значение, на которое он указывает, будет меняться непредсказуемо; тс 3р() Как и передача аргументов, семантика возврата значения из функции идентична семантике инициализации.
Инструкцию ге!игл можно рассматривать как инициализацию неименованной переменной возвращаемого типа. Осуществляется сравнение типа выраженчля в инструкции ге!ига с типом возвращаемого значения и при необходимости выполняются стандартные, либо определяемые пользователем преобразования. Например: 191 7.4. Перегруженные имена функций тггоси1=1; дге1игп с !оса!; гггг плохо ) Такая ошибка встречается реже, чем зквивалентная ошибка с использованием ссы- лок: !лязг () ( Й11оса! =-1; 0- гв1игп 1оса1, гггг плохо К счастью, компилятор может предупредить о том, что возвращается ссылка на локальную переменную. Функция иоЫ не может возвращать значение.
Однако вызов функции по!с! не лает значения, так что функция по!с! может использовать вызов функции пои как выражение в инструкции ге1игп. Например: воЫ а (гл 1* р); ооЫ)г (!л1 р) ( Р "г ге1игп д(р); ) О правильно: возвращает снлкакое знаиенггел Такая форма внструкции ге!игл важна прн написании шаблонов функций, когда тпп возвращаемого значения является параметром шаблона Ц 18.4.4.2).
7.4. Перегруженные имена функций Как правило, разным функциям дают различные имена, но когда функции выполняют концептуально аналогичные задачи д.ля объектов различных типов, лгожет оказаться удобным присвоить нм одно и то же имя. Использование одного имени для операции, выполняемой с различными типами, называется ггервгрузкои. Такая техника уже используется для базовых операций Сь+. Например, существует только одгго имя для сложения +, но его можно использовать для сложения целых, чисел с плавающей точкой п для инкремента указа~елей. Эту идею легко распростраюыь на функции, определяемые пользователем. Например: иоЫрлп1 (!лг); О пенапгь целого ьоЫр~п1(сопл!с)гаг"), (! пенагггьсггггвоггьнойсзсгггрока С точки зрения компилятора, единственное, что функции имеют общего между собой — зто имена.
Нредположгпельно, такие функции в некотором смысле похо;ьп друг на друга, но сам язык не накладывает в связи с этплг никаких дополнительных ограничений и никак не помогает программисту. Таким образом, перегруженные функции предназначены в первую очередь для удобства записи. Это имеет особое значение для распространенных имен, таких как вг)г1, рип1 и орел. Если само имя значимо с точки зрения семагп ики, преимущества перегрузки возрастают. Так происходит, например, для операторов +, * и «, конструкторов Я 11.7) и прп обобщенном прог раммпрованпн (9 272, глава 18).
Когда вызывается функцняу', компилятор долгкен определить, какую нз функций с именем !использовать. Идея состоит в том, Глава 7. Функции 192 чтобы использовать функцшо с наиболее подходящими аргументами и выдать сооб- щение об ошибке, если таковой не найдено. Например: иа!д рп п1 (до иЫе) иаЫ рпп1 (1апя); иаЫ) () ргт1 (11.); ргЫ! (1 0) ргт! (1) 11 рг!п1(!апр) у/ р па! (дои Ыв) ?(ошибка, дврсл~ысленна — рггп1(!опя(1)) О или ргт1(доиЫе(!))? ааЫргсп1 (сл1); ааЫрпп1(сопя! сдаг»); ьаи1рпп! ЫвиЫе), ьоЫрпп1 (!впе), иоЫ ргт1 (с)саг); ио!д й (с(саге, т11, враг( в,))оа1)) ( О точное саответип вне; вызывается рпп! (сваг) О точное соопгветствис; вызивае»пск рст!(т!) О интегральное «продвижение»; вызь~ваеспся рг!пг(!и!) )) «продвгсжение»1?аа! в доиЫе: вызмвавптя рпп!(даиЫв) рп'п1 (с), рст 1 (!), ргии (в); рг!'п! ()), Здесь и далее, когда речь идет о вызовах перегруженных функций, слово «разрешение» (газо)це(ап) означает яе «допущение» вли «позволение», а «анализ с цельк! выяснения, кахун> функцию выбрать».
— Прилсвч рвд. Процесс поиска подходящей функции из множества перегруженных зак'почается в нахождении наилучшего соответствия типов формальных и фактических аргументов. Вышесказанное осуп1ествляется путем проверки набора критериев в следующем порядке; [1] Точное соответствие типов; та есть полное соответствие или соответствие, достигаемое тривиальными преобразованиями типов (например, имя массива и указатель, имя функции и указатель' на функци)о, тип Т и сопя) Т).
[2] Соответствие, достигаемое «продвижением» («повышением в чине>) интегральных типов (напрнмер, Ьоо! в сл1, сйагв сл1, влог( вся(и ипвщпес) аналоги; 9 В 6.1) пЯоа1 в с(оиЫе. ]3] Соответствие, достигаемое путем стандартных преобразований (например, сл1 в с(оиЫе, с)оиЫе в т1 и с(оиЫе в 1опдс)оиЫе, указателей на производные типы в указатели на базовые (9 12.2), указателей на произвольные типы в ио!сг" (9 5.6), сп1 в илв!дпеа! гп1; 9 В.6). [4] Соответствие, достигаемое при помощи преобразований, определяемых пользователем (9 11.4). [5] Соответствие за счет многоточий (...) в объявлении функции (9 7.6). Если соответствие мажет быть получено двумя способами на одном и том же уровне критериев, вызов считается неоднозначным и отвергается. Такой подход при разрешешш перегрузки отражает правила С и С++ для встроенных числовых типов (9 В.6).
Например: 193 7.4. Перегруженные имена функций // точное соответствие; вызываепгся рппг(снаг1 // точное соогпвегпствие; вызывиется рппг(т1) // точное соответствие; вызываегпся рг(п)((пг) //точное гоответствие; вызывается ргт1(сопя( сваг') рппг('а'), рг1пг ((9), рпп( (й), рп'пг ( а'), ) Вызов рпп1(0) обращается к рпп1 ((п(), потому что 0 имеет тип сп1. Функция рпп1 ('а') вызывает рпп1 (сйаг), потому что 'а' имеет тип спас(9 4.3.1). Смысл разделения на преобразования и «продвижения» состоит в том, побы отличить безопасные «продвижения», такие как сйаг в (п1, и потенциально опасные преобразования, такие как сп1 в сйаг.
Результат разрешения перегрузки пе зависит от порядка объявления функций. Механизм перегрузки основывается на относительно сложном наборе правил и в некоторых случаях программисту будет неочевидно, какая функция вызовется. Возникает вопрос: «Зачем все это?ж Рассмотрим альтернативу перегрузке. Нам часто требуются сходные операции над объектами разных типов.
Без перегрузки мы должны определить несколько функций с различными именами: ион! рпп1 (п1(1пг). иоЫргт1 с)саг(спас) иоЫрпп1 я1г(па (сопя) сваг*), иоЫ я(1п1й с(саге, сопя1 гпа р, с(ои(г(е сб ( рпп1 ис1(1); О правильно ргы1 с)гаг (с) // привильно рпп1 я1г(пц(р~; О правильно // провально? Вызывает рпп? Ы1((п1(с) ) //правильно? Вызывает рпп1 спас(сваг(~)) //ошибка //правильно? Вызывает ргт( ии((п1(г))) рпп1 (п1 (с); рпп1 с)саг (1); ргт1 ягг(па (1); ргт1 )п1(ф ) 7.4.1.
Перегрузка и возвращаемые типы Возвращаемые типы не участвуют в разрешении перегрузки. Цель состоит в том, по- бы обеспе ппь контекстную независимость разрешения перегрузки для операторов (9 11.2.1, 9 11.2А) и вызовов функций. Рассмотрим пример: /(оа1 яс)г1 (/(оа1), с)оиЫе яс)П (йоиЫе). я!о сравнению с перегруженной рпп1 (), нам приходится помнить несколько имен и то, как их правильно использовать. Это довольно неприятно, препятствует использованию метода обобщенного программирования (9 2.7.2) и, в общем случае, стимулирует программиста обращать неоправданно большое внимание на проблемы относительно низкого уровня.