- •Предисловие
- •Введение
- •Парадигмы программирования и С++
- •Объектно-ориентированное программирование и С++
- •Инкапсуляция
- •Наследование
- •Полиморфизм
- •Структуры и объединения – абстрактные типы данных
- •Структуры
- •Объединения
- •Класс – абстрактный тип данных
- •Класс как расширение понятия структуры
- •Конструкторы, деструкторы и доступ к компонентам класса
- •Компонентные данные и компонентные функции
- •Статические компоненты класса
- •Указатели на компоненты класса
- •Определение компонентных функций
- •Указатель this
- •Друзья класса
- •Перегрузка стандартных операторов
- •Бинарные и унарные операторы
- •Смешанная арифметика
- •Вывод
- •Копирующее присваивание
- •Вызов функции
- •Индексация
- •“Умные указатели”
- •Наследование классов
- •Множественное наследование и виртуальные базовые классы
- •Виртуальные функции
- •Абстрактные классы
- •Иерархии классов и абстрактные классы
- •Применение динамического полиморфизма
- •Вложенные и локальные классы
- •БИБЛИОГРАФИЧЕСКИЙ СПИСОК
- •СОДЕРЖАНИЕ
Класс – абстрактный тип данных
Пользователь может объявить операторные функции, определяющие смысл следующих стандартных операторов:
+ |
– |
* |
/ |
% |
^ |
& |
| |
~ |
! |
= |
< |
> |
+= |
–= |
*= |
/= |
%= |
^= |
&= |
|= |
<< |
>> |
>>= |
<<= |
== |
!= |
<= |
>= |
&& |
|| |
++ |
-- |
–>* |
, |
–> |
[] |
() |
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