из примера 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+= объявлен вот так.
void operator+=(double other) { // Не делайте так
val += other;
}
Затем кто-то захочет использовать этот оператор для экземпляра класса и передать результат в другую функцию.
Balance moneyMarket(1000.00);
// ...
updateGeneralLeager(moneyMarket += deposit); // He скомпилируется
Это приведет к проблеме, так как
Balance::operator+=
возвращает
void
, а функция типа
updateGeneralLedger
ожидает получить объект типа Balance. Если из арифметических операторов и оператора присвоения возвращать текущий объект, то этой проблемы не возникнет. Однако это верно не для всех операторов. Другие операторы, типа оператора элемента массива
[]
или оператора отношения возвращают объекты, отличные от
*this
, так что это правило верно только для арифметических операторов и операторов присвоения.
Здесь обеспечивается работа операторов присвоения, выполняющих какие-то вычисления, но как насчет вычислений без присвоения? Еще один способ использовать арифметические операторы выглядит так.
int i = 0, j = 2;
i = j + 5;
В этом случае к значению
j
прибавляется 5, а затем результат присваивается
i
(при этом, если бы
i
был объектом класса, а не встроенного типа, использовался бы оператор присвоения этого класса), а значение
j
остается без изменения. Если требуется, чтобы класс вел себя точно так же, то перегрузите оператор сложения как самостоятельную функцию. Например, имеется возможность сделать так, чтобы можно было записать следующее.
, складывает их частные члены, создает временный объект и возвращает его. Обратите внимание, что в отличие от оператора присвоения здесь возвращается объект, а не ссылка на него. Это сделано потому, что возвращаемый объект является временным, и возврат ссылки на него будет означать, что вызывающий код получит ссылку на удаленную из памяти переменную. Однако само по себе это работать не будет, так как здесь требуется доступ к закрытым (частным) членам аргументов оператора (если, конечно, вы не сделали данные класса открытыми). Чтобы обеспечить такой доступ, класс