Операции и определяемые пользователем типы

 

Функция операция должна или быть членом, или получать в качестве параметра по меньшей мере один объект класса (функциям, которые переопределяют операции new и delete, это делать необязательно). Это правило гарантирует, что пользователь не может изменить смысл никакого выражения, не включающего в себя определенного пользователем типа. В частности, невозможно определить функцию, которая действует исключительно на указатели.

Функция операция, первым параметром которой предполагается основной тип, не может быть функцией членом. Рассмотрим, например, сложение комплексной переменной aa с целым 2: aa+2, при подходящим образом описанной функции члене, может быть проинтерпретировано как aa.operator+(2), но с 2+aa это не может быть сделано, потому что нет такого класса int, для которого можно было бы определить + так, чтобы это означало 2.operator+(aa). Даже если бы такой тип был, то для того, чтобы обработать и 2+aa и aa+2, понадобилось бы две различных функции члена. Так как компилятор не знает смысла +, определенного пользователем, то не может предполагать, что он коммутативен, и интерпретировать 2+aa как aa+2. С этим примером могут легко справиться функции друзья.

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

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

 

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

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.

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

 

Альтернативу использованию нескольких функций (перегруженных) составляет описание конструктора, который по заданному 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)))

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

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