С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 76
Текст из файла (страница 76)
В частности,значение 0 приводится к указателю на любой тип; полученный таким образом указательназывается нулевым. Значение 0 может быть представлено как константное выражениеvoid set(int*);int main() {// преобразование указателя из 0 в int* применяется к аргументам// в обоих вызовахset( 0L );set( 0x00 );return 0;целого типа:}Константное выражение 0L (значение 0 типа long int) и константное выражение 0x00(шестнадцатеричное целое значение 0) имеют целый тип и потому могут бытьпреобразованы в нулевой указатель типа int*.Но поскольку перечисления не относятся к целым типам, элемент, равный 0, не приводимenum EN { zr = 0 };к типу указателя:set( zr );// ошибка: zr нельзя преобразовать в тип int*Вызов функции set() является ошибкой, так как не существует преобразования междузначением zr элемента перечисления и формальным параметром типа int*, хотя zrравно 0.Следует отметить, что константное выражение 0 имеет тип int.
Для его приведения ктипу указателя требуется стандартное преобразование. Если в множестве перегруженныхфункций есть функция с формальным параметром типа int, то именно в ее пользу будетразрешена перегрузка в случае, когда фактический аргумент равен 0:С++ для начинающих449void print( int );void print( void * );void set( const char * );void set( char * );int main () {print( 0 );set( 0 );return 0;// вызывается print( int );// неоднозначность}При вызове print(int) имеет место точное соответствие, тогда как для вызоваprint(void*) необходимо приведение значения 0 к типу указателя. Посколькусоответствие лучше преобразования, для разрешения этого вызова выбирается функцияprint(int).
Обращение к set() неоднозначно, так как 0 соответствует формальнымпараметрам обеих перегруженных функций за счет применения стандартнойтрансформации. Раз обе функции одинаково хороши, фиксируется неоднозначность.Последнее из возможных преобразований указателя позволяет привести указатель любоготипа к типу void*, поскольку void* – это родовой указатель на любой тип данных. Вот#include <string>extern void reset( void * );void func( int *pi, string *ps ) {// ...reset( pi );// преобразование указателя: int* в void*/// ...reset( ps );// преобразование указателя: string* в void*несколько примеров:}Только указатели на типы данных могут быть приведены к типу void* с помощьюtypedef int (*PFV)();extern PFV testCases[10];// массив указателей на функцииextern void reset( void * );int main() {// ...reset( textCases[0] );// ошибка: нет стандартного преобразования// между int(*)() и void*return 0;стандартного преобразования, с указателями на функции так поступать нельзя:}С++ для начинающих4509.3.4.
СсылкиФактический аргумент или формальный параметр функции могут быть ссылками. Какэто влияет на правила преобразования типов?Рассмотрим, что происходит, когда ссылкой является фактический аргумент. Его типникогда не бывает ссылочным. Аргумент-ссылка трактуется как l-значение, тип которогоint i;int& ri = i;void print( int );int main() {print( i );print( ri );return 0;// аргумент - это lvalue типа int// то же самоесовпадает с типом соответствующего объекта:}Фактический аргумент в обоих вызовах имеет тип int.
Использование ссылки для егопередачи во втором вызове не влияет на сам тип аргумента.Стандартные преобразования и расширения типов, рассматриваемые компилятором,одинаковы для случаев, когда фактический аргумент является ссылкой на тип T и когдаint i;int& ri = i;void calc( double );int main() {calc( i );calc( ri );return 0;// стандартное преобразование между целым типом// и типом с плавающей точкой// то же самоеон сам имеет такой тип. Например:}А как влияет на преобразования, применяемые к фактическому аргументу, формальныйпараметр-ссылка? Сопоставление дает следующие результаты:•фактический аргумент подходит в качестве инициализатора параметра-ссылки.void swap( int &, int & );void manip( int i1, int i2 ) {// ...swap( i1, i2 );// правильно: вызывается swap( int &, int & )// ...return 0;В таком случае мы говорим, что между ними есть точное соответствие:}С++ для начинающих•451фактический аргумент не может инициализировать параметр-ссылку.
В такойситуации точного соответствия нет, и аргумент нельзя использовать для вызоваint obj;void frd( double & );int main() {frd( obj );// ошибка: параметр должен иметь иметь тип const double &return 0;функции. Например:}Вызов функции frd() является ошибкой. Фактический аргумент имеет тип int идолжен быть преобразован в тип double, чтобы соответствовать формальномупараметру-ссылке. Результатом такой трансформации является временнаяпеременная. Поскольку ссылка не имеет спецификатора const, то для ееинициализации такие переменные использовать нельзя.Вот еще один пример, в котором между формальным параметром-ссылкой иclass B;void takeB( B& );B giveB();int main() {takeB( giveB() );return 0;// ошибка: параметр должен быть типа const B &фактическим аргументом нет соответствия:}Вызов функции takeB() – ошибка.
Фактический аргумент – это возвращаемоезначение, т.е. временная переменная, которая не может быть использована дляинициализации ссылки без спецификатора const.В обоих случаях мы видим, что если формальный параметр-ссылка имеетспецификатор const, то между ним и фактическим аргументом может бытьустановлено точное соответствие.Следует отметить, что и преобразование l-значения в r-значение, и инициализацияссылки считаются точными соответствиями.
В данном примере первый вызов функцииvoid print( int );void print( int& );int iobj;int &ri = iobj;int main() {print( iobj );print( ri );print( 86 );return 0;приводит к ошибке:// ошибка: неоднозначность// ошибка: неоднозначность// правильно: вызывается print( int )С++ для начинающих}Объект iobj – это аргумент, для которого может быть установлено соответствие с обеимифункциями print(), то есть вызов неоднозначен. То же относится и к следующей строке,где ссылка ri обозначает объект, соответствующий обеим функциям print().
С третьимвызовом, однако, все в порядке. Для него print(int&) не является устоявшей. Целаяконстанта – это r-значение, так что она не может инициализировать параметр-ссылку.Единственной устоявшей функцией для вызова print(86) является print(int), поэтомуона и выбирается при разрешении перегрузки.Короче говоря, если формальный параметр представляет собой ссылку, то дляфактического аргумента точное соответствие устанавливается, если он можетинициализировать ссылку, и не устанавливается в противном случае.Упражнение 9.6Назовите два тривиальных преобразования, допустимых при установлении точногосоответствия.Упражнение 9.7(a) void print( int *, int );int arr[6];print( arr, 6 );// вызов функции(b) void manip( int, int );manip( 'a', 'z' ); // вызов функции(c) int calc( int, int );double dobj;double = calc( 55.4, dobj ) // вызов функции(d) void set( const int * );int *pi;Каков ранг каждого из преобразований аргументов в следующих вызовах функций:set( pi ); // вызов функцииУпражнение 9.8Какие из данных вызовов ошибочны из-за того, что не существует преобразования междутипом фактического аргумента и формального параметра:452С++ для начинающих(a) enum Stat { Fail, Pass };void test( Stat );text( 0 ); // вызов функции(b) void reset( void *);reset( 0 ); // вызов функции(c) void set( void * );int *pi;set( pi ); // вызов функции(d) #include <list>list<int> oper();void print( oper() ); // вызов функции(e) void print( const int );int iobj;print( iobj ); // вызов функции9.4.
Детали разрешения перегрузки функцийВ разделе 9.2 мы уже упоминали, что процесс разрешения перегрузки функций состоит изтрех шагов:1. Установить множество функций-кандидатов для разрешения данного вызова, а такжесвойства списка фактических аргументов.2. Отобрать из множества кандидатов устоявшие функции – те, которые могут бытьвызваны с данным списком фактических аргументов при учете их числа и типов.3. Выбрать функцию, лучше всего соответствующую вызову, подвергнув ранжированиюпреобразования, которые необходимо применить к фактическим аргументам, чтобыпривести их в соответствие с формальными параметрами устоявшей функции.Теперь мы готовы к тому, чтобы изучить эти шаги более детально.9.4.1.
Функции-кандидатыФункцией-кандидатом называется функция, имеющая то же имя, что и вызванная.Кандидаты отыскиваются двумя способами:voidvoidvoidvoidf();f( int );f( double, double = 3.4 );f( char*, char* );int main() {f( 5.6 );return 0;•}// для разрешения этого вызова есть четыре кандидатаобъявление функции видимо в точке вызова. В следующем примере453С++ для начинающих454все четыре функции f() удовлетворяют этому условию. Поэтому множествокандидатов содержит четыре элемента;•если тип фактического аргумента объявлен внутри некоторого пространстваимен, то функции-члены этого пространства, имеющие то же имя, что и вызваннаяnamespace NS {class C { /* ...
*/ };void takeC( C& );}// тип cobj - это класс C, объявленный в пространстве имен NSNS::C obj;int main() {//takeC( cobj); ////////return 0;в точке вызова не видна ни одна из функций takeC()правильно: вызывается NS::takeC( C& ),потому что аргумент имеет тип NS::C, следовательно,принимается во внимание функция takeC(),объявленная в пространстве имен NSфункция, добавляются в множество кандидатов:}Таким образом, совокупность кандидатов является объединением множества функций,видимых в точке вызова, и множества функций, объявленных в том же пространствеимен, к которому принадлежат типы фактических аргументов.При идентификации множества перегруженных функций, видимых в точке вызова,применимы уже рассмотренные ранее правила.Функция, объявленная во вложенной области видимости, скрывает, а не перегружаетодноименную функцию во внешней области. В такой ситуации кандидатами будут толькофункции из во вложенной области, т.е.