ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 13.12.2020
Просмотров: 4303
Скачиваний: 28
Г Л А В А
16
Перегрузка операторов
16.1. Общие правила
В языке программирования C++
операторы
рассматриваются как
функции, которые имеют следующий прототип:
тип operator имя_оператора(список_параметров);
Здесь
operator
— ключевое слово. Поэтому операторы, так же
как и функции, можно перегружать. Перегружаются почти все
операторы, исключая нижеприведенные случаи:
нельзя перегружать следующие операторы:
.
— точка, оператор доступа к члену класса;
.*
— оператор доступа к члену класса через указатель;
::
— оператор разрешения области видимости;
:?
— условный оператор;
нельзя перегружать препроцессорные операторы:
#
— преобразование в строку;
##
— сцепление строк;
нельзя перегружать операторы
sizeof
и
typeof
;
нельзя перегружать операторы преобразования типов данных:
static_cast
,
const_cast
,
reinterpret_cast
,
dynamic_cast
.
Глава 16. Перегрузка операторов
207
Перегрузка операторов должна удовлетворять следующим требо-
ваниям:
нельзя вводить новых операторов;
не допускается перегрузка операторов со встроенными типами
данных в качестве параметров, тип, по крайней мере, одного
параметра перегруженного оператора должен быть классом;
нельзя изменять количество параметров оператора;
ассоциативность перегруженных операторов не изменяется;
приоритеты перегруженных операторов не изменяются;
оператор не может иметь аргументов по умолчанию, за ис-
ключением оператора вызова функции
()
;
оператор не может иметь неопределенного количества пара-
метров, за исключением оператора вызова функции
()
.
Перегруженные операторы разрешается определять как члены
класса и как функции не члены класса. Один оператор может
быть перегружен несколько раз. Если оператор перегружен как
член класса, то он не должен иметь спецификатор
static
и его
первым операндом по умолчанию является объект класса, для
которого вызывается этот оператор. В данном случае преобразо-
вание типа первого аргумента такого оператора к типу первого
параметра не производится в силу невозможности его эффектив-
ной реализации. При выборе способа перегрузки оператора нуж-
но учитывать его семантику, т. е. смысл. Хорошо бы при пере-
грузке операторов принимать во внимание то, что они в основном
выполняют алгебраические операции над объектами и поэтому
сохранять алгебраическую природу этих операторов.
Перегрузку унарных и бинарных операторов будем рассматри-
вать на примере класса
Bool
, определение которого приведено в
листинге 16.1.
Листинг 16.1. Определение класса четных чисел
#include <iostream>
#if !defined BOOL
#define BOOL
Часть II. Язык программирования С++
208
class Bool
{
int n;
public:
// конструкторы
Bool() { n ? n = 1 : n = 0; }
Bool(int _n) : n(_n ? 1 : 0) {}
// унарные операторы логического сложения и умножения
Bool& operator +=(const Bool& b);
Bool& operator *=(const Bool& b);
// оператор преобразования типа
operator bool(void) const;
// унарный логический оператор отрицания
friend Bool operator !(const Bool& b);
// бинарные операторы логического сложения и умножения
friend Bool operator +(const Bool& b1, const Bool& b2);
friend Bool operator *(const Bool& b1, const Bool& b2);
// бинарные операторы сравнения
friend Bool operator ==(const Bool& b1, const Bool& b2);
friend Bool operator !=(const Bool& b1, const Bool& b2);
// операторы ввода/вывода
friend std::istream& operator >>(std::istream& in, Bool& b);
friend std::ostream& operator <<(std::ostream& out,
const Bool& b);
};
#endif
Реализация перегруженных операторов будет приведена в сле-
дующих разделах. При этом заметим, что большинство из рас-
сматриваемых операторов имеют простую реализацию. Поэтому
их можно определять прямо в теле класса как встроенные функ-
ции.
Отметим, что перегруженные операторы могут вызываться двумя
способами: явно, как функции, или неявно, используя оператор-
ную запись. Например, над объектами типа
Bool
можно прово-
дить следующие операции:
Bool a(0), b(1), c(0);
c.operator +=(a); // явный вызов оператора +=
Глава 16. Перегрузка операторов
209
c += b; // неявный вызов оператора +=
c = operator +(a, b); // явный вызов оператора +
c = a + b; // неявный вызов оператора +
16.2. Унарные операторы
Префиксные унарные операторы могут быть перегружены как
нестатические члены класса без параметров:
тип operator @();
или как операторы (не члены класса) с одним параметром:
тип operator @(параметр);
Здесь
@
обозначает один из следующих унарных операторов:
& * + - ~ !
При перегрузке выбирают тот вариант, который соответствует
смыслу оператора.
Например, возможны следующие два варианта перегрузки унар-
ного оператора отрицания как члена класса
Bool
:
Bool& operator !() { n ? n = 0 : n = 1; return *this; }
Bool operator !() const { return Bool(n ? 0 : 1); }
Выбираем второй вариант, так как он не изменяет логическое
значение объекта, к которому применяется оператор
!
. Это согла-
суется с семантикой этого оператора при работе с логическими
значениями.
Унарный оператор отрицания можно также перегрузить как дру-
жественную функцию класса:
Bool operator !(const Bool& b) { return Bool(b.n ? 0 : 1); }
Такая реализация более предпочтительна, чем реализация унар-
ного отрицания как члена класса, так как она позволяет преобра-
зование типа параметра
b
. Эту реализацию и оставляем для клас-
са
Bool
. Такой же подход используем и для остальных префикс-
ных унарных операторов.
После перегрузки унарного отрицания над логическими значе-
ниями можно выполнять следующие операции:
Часть II. Язык программирования С++
210
Bool a(0), b(0);
a = !b; // a = 1
Можно оставить два определения префиксного унарного опера-
тора: как члена и как друга класса. В этом случае при вызове
унарного оператора компилятор выбирает тот вариант, для кото-
рого преобразования типа считается наилучшим.
16.3. Оператор присваивания
Оператор присваивания может быть перегружен только как не-
статический член класса. Перегруженный оператор присваивания
должен иметь следующий прототип:
имя_класса& operator =(const имя_класса&);
При реализации оператора присваивания должна проверяться
возможность присваивания объекта самому себе. Например, пе-
регрузим оператор присваивания для класса
Bool
:
Bool& operator =(const Bool& b)
{
if (&b != this)
n = b.n;
return *this;
}
Теперь можно выполнять присваивание логических значений,
например:
Bool a(0), b(1);
a = b; // a = 1
Если оператор присваивания не определен в классе, то компиля-
тор генерирует оператор присваивания по умолчанию, который
выполняет по членное копирование атрибутов класса. Если при
присваивании объектов класса дополнительно к копированию
атрибутов требуется выполнить еще какие-то действия, то опера-
тор присваивания нужно обязательно перегрузить. Так как для
класса
Bool
оператор присваивания выполняет только почленное
копирование атрибутов, то его определение можно опустить.