Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
c++book1.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
2 Мб
Скачать
☆

6.3 Определяемое Пользователем Преобразование Типа

Приведенная во введении реализация комплексных чисел слишком ограничена, чтобы она могла устроить кого-либо, поэ­тому ее нужно расширить. Это будет в основном повторением описанных выше методов. Например:

class complex (*

double re, im;

public:

complex(double r, double i) (* re=r; im=i; *)

friend complex operator+(complex, complex);

friend complex operator+(complex, double);

friend complex operator+(double, complex);

friend complex operator-(complex, complex);

friend complex operator-(complex, double);

friend complex operator-(double, complex);

complex operator-() // унарный -

friend complex operator*(complex, complex);

friend complex operator*(complex, double);

friend complex operator*(double, complex);

// ...

*);

Теперь, имея описание complex, мы можем написать:

void f()

(*

complex a(1,1), b(2,2), c(3,3), d(4,4), e(5,5);

a = -b-c;

b = c*2.0*c;

c = (d+e)*a;

*)

Но писать функцию для каждого сочетания complex и double, как это делалось выше для operator+(), невыносимо нудно. Кроме того, близкие к реальности средства комплексной арифметики должны предоставлять по меньшей мере дюжину таких функций. Посмотрите, например, на тип complex, описанный в <complex.h>.

6.3.1 Конструкторы

Альтернативу использованию нескольких функций (перегру­женных) составляет описание конструктора, который по заданно­му double создает complex. Например:

class complex (*

// ...

complex(double r) (* re=r; im=0; *)

*);

Конструктор, требующий только один параметр, необяза­тельно вызывать явно:

complex z1 = complex(23);

complex z2 = 23;

И z1, и z2 будут инициализированы вызовом complex(23).

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

class complex (*

double re, im;

public:

complex(double r, double i = 0) (* re=r; im=i; *)

friend complex operator+(complex, complex);

friend complex operator*(complex, complex);

*);

и действия, в которые будут входить переменные complex и целые константы, стали бы допустимы. Целая константа будет интерпретироваться как complex с нулевой мнимой частью. Нап­ример, a=b*2 означает:

a=operator*( b, complex( double(2), double(0) ) )

Определенное пользователем преобразование типа применя­ется неявно только тогда, когда оно является единственным.

Объект, сконструированный с помощью явного или неявного вызова конструктора, является автоматическим и будет уничто­жен при первой возможности, обычно сразу же после оператора, в котором он был создан.

6.3.2 Операции Преобразования

Использование конструктора для задания преобразования типа является удобным, но имеет следствия, которые могут ока­заться нежелательными:

[1] Не может быть неявного преобразования из определен-

ного пользователем типа в основной тип (поскольку основ­ные типы не являются классами)

[2] Невозможно задать преобразование из нового типа в

старый, не изменяя описание старого

[3] Невозможно иметь конструктор с одним параметром, не

имея при этом преобразования.

Последнее не является серьезной проблемой, а с первыми двумя можно справиться, определив для исходного типа операцию преобразования. Функция член X::operator T(), где T - имя ти­па, определяет преобразование из X в T. Например, можно опре­делить тип tiny (крошечный), который может иметь значение только в диапазоне 0...63, но все равно может свободно соче­таться в целыми в арифметических операциях:

class tiny (*

char v;

int assign(int i)

(*return v=(i&~63) ? (error("ошибка диапазона"),0):i;*) public:

tiny(int i) (* assign(i); *)

tiny(tiny& i) (* v = t.v; *)

int operator=(tiny& i) (* return v = t.v; *)

int operator=(int i) (* return assign(i); *)

operator int() (* return v; *)

*)

Диапазон значения проверяется всегда, когда tiny инициа­лизируется int, и всегда, когда ему присваивается int. Одно tiny может присваиваться другому без проверки диапазона. Что­бы разрешить выполнять над переменными tiny обычные целые операции, определяется tiny::operator int(), неявное преобра­зование из tiny в int. Всегда, когда в том месте, где требу­ется int, появляется tiny, используется соответствующее ему int. Например:

void main()

(*

tiny c1 = 2;

tiny c2 = 62;

tiny c3 = c2 - c1; // c3 = 60

tiny c4 = c3; // нет проверки диапазона (необязательна)

int i = c1 + c2; // i = 64

c1 = c2 + 2 * c1; // ошибка диапазона: c1 = 0 (а не 66)

c2 = c1 -i; // ошибка диапазона: c2 = 0

c3 = c2; // нет проверки диапазона (необязательна)

*)

Тип вектор из tiny может оказаться более полезным, пос­кольку он экономит пространство. Чтобы сделать этот тип более удобным в обращении, можно использовать операцию индексирова­ния.

Другое применение определяемых операций преобразования - это типы, которые предоставляют нестандартные представления чисел (арифметика по основанию 100, арифметика, арифметика с фиксированной точкой, двоично-десятичное представление и т.п.). При этом обычно переопределяются такие операции, как + и *.

Функции преобразования оказываются особенно полезными для работы со структурами данных, когда чтение (реализованное посредством операции преобразования) тривиально, в то время как присваивание и инициализация заметно более сложны.

Типы istream и ostream опираются на функцию преобразова­ния, чтобы сделать возможными такие операторы, как

while (cin>>x) cout<<x;

Действие ввода cin>>x выше возвращает istream&. Это зна­чение неявно преобразуется к значению, которое указывает сос­тояние cin, а уже это значение может проверяться оператором while (см. #8.4.2). Однако определять преобразование из оного типа в другой так, что при этом теряется информация, обычно не стоит.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]