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

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

743

istream& operator>>( istream &is, SmallInt &si ) { int ix;

is >> ix;

si = ix; // SmallInt::operator=(int) return is;

}

int SmallInt::rangeCheck( int i )

{

/* если установлен хотя бы один бит, кроме первых восьми, * то значение слишком велико; сообщить и сразу выйти */

if ( i & ~0377 ) {

cerr << "\n*** Ошибка диапазона SmallInt: " << i << " ***" << endl;

exit( -1 );

}

return i;

Ниже приведены определения функций-членов, находящиеся вне тела класса:

}

15.9.1. Конвертеры

Конвертер это особый случай функции-члена класса, реализующий определенное пользователем преобразование объекта в некоторый другой тип. Конвертер объявляется в теле класса путем указания ключевого слова operator, за которым следует целевой тип преобразования.

Имя, находящееся за ключевым словом, не обязательно должно быть именем одного из встроенных типов. В показанном ниже классе Token определено несколько конвертеров. В одном из них для задания имени типа используется typedef tName, а в другом тип

#include "SmallInt.h"

typedef char *tName; class Token { public:

Token( char *, int );

operator SmallInt() { return val; }

operator

tName()

{

return

name; }

operator

int()

{

return

val; }

// другие открытые члены private:

SmallInt val; char *name;

класса SmallInt.

};

Обратите внимание, что определения конвертеров в типы SmallInt и int одинаковы. Конвертер Token::operator int() возвращает значение члена val. Поскольку val

имеет тип SmallInt, то неявно применяется SmallInt::operator int() для преобразования val в тип int. Сам Token::operator int() неявно употребляется компилятором для преобразования объекта типа Token в значение типа int. Например,

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

744

этот конвертер используется для неявного приведения фактических аргументов t1 и t2

#include "Token.h"

void print( int i )

{

cout << "print( int ) : " << i << endl;

}

Token t1( "integer constant", 127 );

Token t2( "friend", 255 );

int main()

 

 

{

 

// t1.operator int()

print( t1 );

print( t2

);

// t2.operator int()

return 0;

 

 

типа Token к типу int формального параметра функции print():

}

print( int ) : 127

После компиляции и запуска программа выведет такие строки: print( int ) : 255

Общий вид конвертера следующий:

operator type();

где type может быть встроенным типом, типом класса или именем typedef. Конвертеры, в которых type тип массива или функции, не допускаются. Конвертер должен быть функцией-членом. В его объявлении не должны задаваться ни тип возвращаемого

operator int( SmallInt & ); // ошибка: не член

class SmallInt { public:

int operator int(); // ошибка: задан тип возвращаемого значения operator int( int = 0 ); // ошибка: задан список параметров

// ...

значения, ни список параметров:

};

Конвертер вызывается в результате явного преобразования типов. Если преобразуемое значение имеет тип класса, у которого есть конвертер, и в операции приведения указан тип этого конвертера, то он и вызывается:

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

745

#include "Token.h"

Token tok( "function", 78 );

//функциональная нотация: вызывается Token::operator SmallInt() SmallInt tokVal = SmallInt( tok );

//static_cast: вызывается Token::operator tName()

char *tokName = static_cast< char * >( tok );

У конвертера Token::operator tName() может быть нежелательный побочный эффект.

Попытка прямого обращения к закрытому члену Token::name помечается компилятором как ошибка:

char *tokName = tok.name; // ошибка: Token::name - закрытый член

Однако наш конвертер, разрешая пользователям непосредственно изменять Token::name, делает как раз то, от чего мы хотели защититься. Скорее всего, это не годится. Вот,

#include "Token.h"

Token tok( "function", 78 );

char *tokName = tok; // правильно: неявное преобразование

например, как могла бы произойти такая модификация:

*tokname = 'P'; // но теперь в члене name находится Punction!

Мы намереваемся разрешить доступ к преобразованному объекту класса Token только

typedef const char *cchar; class Token {

public:

operator cchar() { return name; } // ...

};

// ошибка: преобразование char* в const char* не допускается char *pn = tok;

для чтения. Следовательно, конвертер должен возвращать тип const char*:

 

const char *pn2 = tok;

// правильно

 

 

в определении Token тип char* на тип string из

Другое решение заменить

стандартной библиотеки C++:

 

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

746

class Token { public:

Token( string, int );

operator SmallInt() { return val; }

operator

string()

{

return

name; }

operator

int()

{

return

val; }

// другие открытые члены private:

SmallInt val; string name;

};

Семантика конвертера Token::operator string() состоит в возврате копии значения (а не указателя на значение) строки, представляющей имя лексемы. Это предотвращает случайную модификацию закрытого члена name класса Token.

Должен ли целевой тип точно соответствовать типу конвертера? Например, будет ли в

extern void calc( double ); Token tok( "constant", 44 );

//Вызывается ли оператор int()? Да

//применяется стандартное преобразование int --> double

следующем коде вызван конвертер int(), определенный в классе Token? calc( tok );

Если целевой тип (в данном случае double) не точно соответствует типу конвертера (в нашем случае int), то конвертер все равно будет вызван при условии, что существует последовательность стандартных преобразований, приводящая к целевому типу из типа конвертера. (Эти последовательности описаны в разделе 9.3.) При обращении к функции calc() вызывается Token::operator int() для преобразования tok из типа Token в

тип int. Затем для приведения результата от типа int к типу double применяется стандартное преобразование.

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

extern void calc( int ); Token tok( "pointer", 37 );

//если Token::operator int() не определен,

//то этот вызов приводит к ошибке компиляции

ошибочным: calc( tok );

Если конвертер Token::operator int() не определен, то приведение tok к типу int потребовало бы вызова двух определенных пользователем конвертеров. Сначала

фактический аргумент tok надо было бы преобразовать из типа Token в тип SmallInt с помощью конвертера