Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих (Стенли Липпман) 3-е хххх.pdf
Скачиваний:
86
Добавлен:
30.05.2015
Размер:
5.92 Mб
Скачать

С++ для начинающих

446

#include <string>

string format( int );

string format( unsigned int );

int main() {

// вызывается format( int )

format(a1);

format(a2);

// вызывается format( unsigned int )

return 0;

 

}

 

При первом обращении к format() фактический аргумент расширяется до типа int, так как для представления типа e1 используется char, и, следовательно, вызывается перегруженная функция format(int). При втором обращении тип фактического

аргумента e2 представлен типом unsigned int и аргумент расширяется до unsigned int, из-за чего вызывается перегруженная функция format(unsigned int). Поэтому следует помнить, что поведение двух перечислений по отношению к процессу разрешения перегрузки может быть различным и зависеть от значений элементов, определяющих, как происходит расширение типа.

9.3.3. Подробнее о стандартном преобразовании

Имеется пять видов стандартных преобразований, а именно:

1.преобразования целых типов: приведение от целого типа или перечисления к любому другому целому типу (исключая трансформации, которые выше были отнесены к категории расширения типов);

2.преобразования типов с плавающей точкой: приведение от любого типа с плавающей точкой к любому другому типу с плавающей точкой (исключая трансформации, которые выше были отнесены к категории расширения типов);

3.преобразования между целым типом и типом с плавающей точкой: приведение от любого типа с плавающей точкой к любому целому типу или наоборот;

4.преобразования указателей: приведение целого значения 0 к типу указателя или трансформация указателя любого типа в тип void*;

5.преобразования в тип bool: приведение от любого целого типа, типа с плавающей точкой, перечислимого типа или указательного типа к типу bool.

extern void print( void* ); extern void print( double );

int main() { int i;

print( i ); // соответствует print( double );

// i подвергается стандартному преобразованию из int в

double

print( &i ); // соответствует print( void* );

//&i подвергается стандартному преобразованию

//из int* в void*

return 0;

Вот несколько примеров:

С++ для начинающих

447

}

Преобразования, относящиеся к группам 1, 2 и 3, потенциально опасны, так как целевой тип может и не обеспечивать представления всех значений исходного. Например, с помощью float нельзя адекватно представить все значения типа int. Именно по этой причине трансформации, входящие в эти группы, отнесены к категории стандартных

int i;

void calc( float ); int main() {

calc( i ); // стандартное преобразование между целым типом и типом с

//плавающей точкой потенциально опасно в зависимости от

//значения i

return 0;

преобразований, а не расширений типов.

}

При вызове функции calc() применяется стандартное преобразование из целого типа int в тип с плавающей точкой float. В зависимости от значения переменной i может оказаться, что его нельзя сохранить в типе float без потери точности.

Предполагается, что все стандартные изменения требуют одного объема работы. Например, преобразование из char в unsigned char не более приоритетно, чем из char в double. Близость типов не принимается во внимание. Если две устоявших функции

требуют для установления соответствия стандартной трансформации фактического аргумента, то вызов считается неоднозначным и помечается компилятором как ошибка.

extern void manip( long );

Например, если даны две перегруженные функции: extern void manip( float );

int main() {

manip( 3.14 ); // ошибка: неоднозначность

// manip( float ) не лучше, чем manip( int )

return 0;

то следующий вызов неоднозначен:

}

Константа 3.14 имеет тип double. С помощью того или иного стандартного

преобразования соответствие может быть установлено с любой из перегруженных функций. Поскольку есть две трансформации, приводящие к цели, вызов считается неоднозначным. Ни одно преобразование не имеет преимущества над другим. Программист может разрешить неоднозначность либо путем явного приведения типа:

manip ( static_cast<long>( 3.14 ) ); // manip( long )

либо используя суффикс, обозначающий, что константа принадлежит к типу float:

С++ для начинающих

448

 

manip ( 3.14F ) );

// manip( float )

 

 

 

 

Вот еще несколько примеров неоднозначных вызовов, которые помечаются как ошибки,

extern void farith( unsigned int ); extern void farith( float );

int main() {

// каждый из последующих вызовов

неоднозначен

farith( 'a' );

// аргумент

имеет тип char

farith( 0 );

// аргумент

имеет тип int

farith( 2uL );

// аргумент

имеет тип unsigned long

farith( 3.14159 );

// аргумент

имеет тип double

farith( true );

// аргумент

имеет тип bool

поскольку соответствуют нескольким перегруженным функциям:

}

Стандартные преобразования указателей иногда противоречат интуиции. В частности, значение 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:

С++ для начинающих

449

void print( int ); void print( void * );

void set( const char * ); void set( char * );

int main ()

{

// вызывается print( int );

print( 0

);

set( 0

);

// неоднозначность

return

0;

 

}

При вызове print(int) имеет место точное соответствие, тогда как для вызова print(void*) необходимо приведение значения 0 к типу указателя. Поскольку соответствие лучше преобразования, для разрешения этого вызова выбирается функция print(int). Обращение к set() неоднозначно, так как 0 соответствует формальным

параметрам обеих перегруженных функций за счет применения стандартной трансформации. Раз обе функции одинаково хороши, фиксируется неоднозначность.

Последнее из возможных преобразований указателя позволяет привести указатель любого типа к типу void*, поскольку void* это родовой указатель на любой тип данных. Вот

#include <string>

extern void reset( void * );

void func( int *pi, string *ps ) {

// ...

// преобразование указателя: int* в void*

reset( pi );

/// ...

// преобразование указателя: string* в void*

reset( ps );

несколько примеров:

}

Только указатели на типы данных могут быть приведены к типу void* с помощью

typedef int (*PFV)();

extern PFV testCases[10]; // массив указателей на функции

extern void reset( void * );

int main() { // ...

reset( textCases[0] ); // ошибка: нет стандартного преобразования

// между int(*)() и void*

return 0;

стандартного преобразования, с указателями на функции так поступать нельзя:

}