Наиболее часто используют перегрузку для арифметических операторов и операторов присвоения. Существует огромное количество различных классов, для которых имеют смысл арифметические операторы (сложение, умножение, остаток от деления, сдвиг битов вправо/влево) и операторы присвоения — вне зависимости от того, используются ли они для вычислений или для чего-то другого. Пример 8.15 показывает основные методики перегрузки этих операторов.
Давайте начнем с того, что, вероятно, является наиболее часто перегружаемым оператором, — оператора присвоения. Оператор присвоения
используется при присвоении одного объекта другому, как в следующем выражении.
Balance x(0), у(32);
x = y;
Вторая строка — это краткая запись вызова
Balance::operator=(y)
. Оператор присвоения отличается от большинства других операторов тем, что если вы не создаете собственной его версии, то компилятором создается версия по умолчанию. Версия по умолчанию просто копирует в текущий объект каждый член целевого объекта, что, конечно, не всегда приемлемо, так что его можно перегрузить и обеспечить другое поведение или перегрузить и предоставить возможность присвоения объектов типов, отличных от текущего
Для класса
Balance
из примера 8.15 оператор присвоения можно определить вот так.
Balance& operator=(const Balance& other) {
val_ = other.val_;
return(*this);
}
Первое, на что вы должны обратить внимание, если не знакомы с перегрузкой операторов, — это синтаксис
operator=
. Именно так объявляются все операторы. Все операторы можно рассматривать как функции с именами
operator[symbol]
, где
symbol
— это перегружаемый оператор. Единственным различием между операторами и обычными функциями является синтаксис их вызова. На самом деле, если вы хотите ввести побольше кода и написать отвратительно выглядящий код, то операторы можно вызывать и с помощью такого синтаксиса.
x.operator=(y); // То же самое, что и x = y, но уродливее
Работа моей реализации оператора присвоения проста. Он обновляет член
val_
текущего объекта, записывая в него значение аргумента
other
, а затем возвращает ссылку на текущий объект. Операторы присвоения возвращают текущий объект как ссылку, так что вызывающий код может использовать присвоение в выражениях:
Balance x, y, z;
// ...
x = (y = z);
Таким образом, возвращаемое из
(y = z)
значение — это измененный объект
y
, который затем передается в оператор присвоения объекта
x
. Такая запись для присвоения используется не так часто, как для арифметических операторов, но чтобы придерживаться общего соглашения, всегда следует возвращать ссылку на текущий объект (то, как это связано с арифметическими операторами, я рассказываю дальше).
Однако простое присвоение — это только начало. Скорее всего, вам потребуется использовать другие арифметические операторы, определяющие более интересное поведение. В табл. 8.1 показан перечень арифметических операторов и операторов присвоения.
Табл. 8.1. Арифметические операторы и присвоение
Оператор
Поведение
=
Присвоение (должен быть функцией-членом)
+
Сложение
– -=
Вычитание
* *=
Умножение
и разыменование
/ /=
Деление
% %=
Остаток от деления
++
Инкремент
– -
Декремент
^ ^=
Битовое исключающее ИЛИ
~
Битовое дополнение
& &=
Битовое И
| |=
Битовое ИЛИ
<< <<=
Сдвиг влево
>> >>=
Сдвиг вправо
Для большинства операторов из табл. 8.1 существует две лексемы: первая — это версия оператора, используемая обычным образом, т.е.
1+2
, а вторая версия — это версия, которая также присваивает результат операции переменной, т. е.
x+=5
. Заметьте, что операторы инкремента и декремент
++
и
– -
описываются в рецепте 8.13.
Реализация всех арифметических операторов и оператора присвоения очень похожа, за исключением оператора присвоения, который не может быть отдельной функцией (т.е. он должен быть методом).
Наиболее популярным при перегрузке является оператор сложения — благодаря тому что он может использоваться в отличных от математических контекстах, таких как объединение двух строк, так что давайте вначале рассмотрим именно его. Он складывает правый аргумент с левым и присваивает результирующее значение левому аргументу, как в выражении.
int i = 0;
i += 5;
После выполнения второй строки
int i
изменяется и содержит значение 5. Аналогично, если посмотреть на пример 8.15, следует ожидать такого же поведения от этих строк.
Balance checking(500.00), savings(23.91);
checking += 50;
Это означает, что следует ожидать, что после использования оператора
+=
значение
checking
будет увеличено на 50. Именно это происходит при использовании реализации из примера 8.15. Посмотрите на определение функции для оператора
+=
.
Balance& operator+=(double other) {
val_ += other;
return(*this);
}
Для оператора присвоения список параметров — это то, что будет указано в операторе в его правой части. В данном случае используется целое число. Тело этой функции тривиально: все, что здесь делается, — это добавление аргумента к частной переменной-члену. Когда эта работа сделана, возвращается
*this
. Возвращаемым значением из арифметических операторов и операторов присвоения должно быть
*this
, что позволяет использовать их в выражениях, когда их результаты будут входом для чего-то еще. Например, представьте, что operator+= объявлен вот так.