Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Объектно-ориентированное программирование.pdf
Скачиваний:
121
Добавлен:
28.03.2015
Размер:
1.58 Mб
Скачать

Класс – абстрактный тип данных

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

+

*

/

%

^

&

|

~

!

=

<

>

+=

–=

*=

/=

%=

^=

&=

|=

<<

>>

>>=

<<=

==

!=

<=

>=

&&

||

++

--

–>*

,

–>

[]

()

new

new[]

delete

delete[]

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

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

либо нестатическим компонентом класса;

либо функцией-другом;

либо глобальной функцией хотя бы с одним параметром типа класс (или ссылка на класс).

Бинарные и унарные операторы

Любой бинарный оператор @ определяется для объектов класса a и b:

либо как нестатическая компонентная функция с одним параметром, что для выражения a@b означает вызов:

a.operator@(b)

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

operator@(a, b)

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

Например, распространим действия трех бинарных операторов (+, - и *) на объекты класса Complex, определив operator+() как дружественную функцию класса, operator-() как нестатическую компонентную функцию, operator*() как глобальную функцию с параметрами типа класс:

//Пример 28

//C++ Абстрактный тип данных - комплексное число

#include <iostream> using namespace std; struct Complex {

//Компонентные функции - все общедоступные (public)

//Конструктор объектов класса

Complex(double r = 0.0, double i = 0.0) : re(r), im(i) {}

79

Основы объектно-ориентированного программирования в примерах на С++

//Ссылка на вещественную часть комплексного числа double& get_re()

{

return re;

}

//Ссылка на мнимую часть комплексного числа double& get_im()

{

return im;

}

//Сложение комплексных чисел

friend Complex operator+(Complex, Complex);

//Вычитание комплексных чисел

Complex operator-(Complex c)

{

Complex temporary; temporary.re = re - c.re; temporary.im = im - c.im; return temporary;

}

//Визуализация комплексного числа void print(Complex c)

{

cout << '(' << c.re << ", " << c.im << ')' << endl;

}

private:

//Компонентные данные - все собственные (private) double re;

double im;

}; // Сложение комплексных чисел

Complex operator+(Complex a, Complex b)

{

Complex temporary; temporary.re = a.re + b.re; temporary.im = a.im + b.im; return temporary;

}

// Умножение комплексных чисел

Complex operator*(Complex a, Complex b)

{

Complex temporary;

temporary.get_re() = a.get_re() * b.get_re() – a.get_im() * b.get_im();

temporary.get_im() = a.get_re() * b.get_im() + b.get_re() * a.get_im();

return temporary;

}

80

Класс – абстрактный тип данных

int main()

 

{

 

Complex x1(-1, 5);

 

Complex x2(10, 7);

// (-1, 5)

x1.print(x1);

x2.print(x2);

// (10, 7)

x1.print(x1 + x2);

// (9, 12)

x1.print(operator+(x1, x2));

// (9, 12)

x2 = x1 + x2;

// (9, 12)

x2.print(x2);

x1 = operator+(x1, x2);

// (8, 17)

x1.print(x1);

x2.print(x2 + 10);

// (19, 12)

x1.print(-10 + x1);

// (-2, 17)

x1.print(x1 - x2);

// (-1, 5)

x1.print(x1.operator-(x2));

// (-1, 5)

x1 = x1 - x2;

// (-1, 5)

x1.print(x1);

x2.print(x2 - 10);

// (-1, 12)

x2 = x2.operator-(x1);

// (10, 7)

x2.print(x2);

x1 = x1 * x2;

// (-45, 43)

x1.print(x1);

return 0;

 

}

 

Любой унарный оператор @, префиксный или постфиксный, определяется для объектов класса a:

либо как нестатическая компонентная функция без параметров, что для выражений @a и a@ означает вызов:

a.operator@()

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

operator@(a)

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

Заметим, что согласно принятому соглашению при распространении действия постфиксных операторов инкрементирования и декрементирования, которые в то же время могут быть и префиксными, операторные функции должны иметь еще один дополнительный параметр типа int, значение которого компилятор определяет равным нулю, т.е. выражение a@ означает либо вызов a.operator@(int) в случае определения нестатической компонентной функции без параметров, либо вызов operator@(a, int) в случае определения глобальной или дружественной функции с одним параметром. Если определены обе операторные функции, то по-прежнему для

81

Основы объектно-ориентированного программирования в примерах на С++

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

Например, распространим действия двух унарных операторов (префиксный ++ и постфиксный --) на объекты класса Complex, определив operator++() и operator--() как нестатические компонентные функции, полагая, что операции инкремента и декремента будут определены только для вещественной части комплексного числа:

//Пример 29

//C++ Абстрактный тип данных - комплексное число

#include <iostream> using namespace std; struct Complex {

//Компонентные функции - все общедоступные (public)

//Конструктор объектов класса

Complex(double r = 0.0, double i = 0.0):re(r), im(i) {}

//Инкремент вещественной части комплексного числа

Complex& operator++()

{

++re;

return *this;

}

//Декремент вещественной части комплексного числа

Complex operator--(int)

{

Complex temporary = *this; --re;

return temporary;

}

//Визуализация комплексного числа

void print(Complex c)

{

cout << '(' << c.re << ", " << c.im << ')' << endl;

}

private:

// Компонентные данные - все собственные (private) double re;

double im; };

int main()

{

Complex x1(-1, 5);

 

Complex x2(10, 7);

// (-1, 5)

x1.print(x1);

x2.print(x2);

// (10, 7)

x1.print(++x1);

// (0, 5)

x1.print(x1);

// (0, 5)

x2 = ++++x1;

 

82

Класс – абстрактный тип данных

x2.print(x2);

// (2, 5)

x1.print(x1);

// (2, 5)

x1.print(x1--);

// (2, 5)

x1.print(x1);

// (1, 5)

x2 = x1--;

// (1, 5)

x2.print(x2);

x1.print(x1);

// (0, 5)

return 0;

 

}

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

const Complex& operator++()

{

++re;

return *this;

}

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

будет ошибочной:

// ошибка!

x2 = ++++x1;

Отметим теперь характерные особенности механизма перегрузки стандартных операторов:

нельзя изменить приоритеты стандартных операторов;

нельзя изменить синтаксис стандартных операторов, однако им можно придать новый смысл, задав соответствующие определения;

нельзя вводить новые лексические обозначения стандартных операторов;

не реализуются неявные замены для стандартных операторов, определяемых как эквивалентные комбинации других стандартных операторов;

нельзя изменить смысл выражения, если в него не входит объект класса;

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

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

Теперь остановимся на подробном обсуждении некоторых важных для понимания деталей механизма перегрузки стандартных операторов.

Во-первых, операторные функции operator=(), operator[]() и operator–>() должны быть нестатическими компонентными функциями, так как это гарантирует, что их первый параметр будет lvalue (именующее выражение, ссылающееся на любой объект программы).

83