C++. Бархатный путь. Часть 2 - Представление операций для классов. Операторные функции

ОГЛАВЛЕНИЕ

 

Представление операций для классов. Операторные функции

Классы вводят в программу производные типы. Такие типы могут входить в списки параметров функций и определять тип возвращаемого значения. В вызовах функций при передаче параметров и возвращении значений данные производных типов используются в программе наравне с данными базовых типов. В условиях фактического равноправия производных и основных типов данных должна существовать возможность сохранения привычной структуры выражений при работе с данными производных типов.

Это означает, что выражение для вычисления суммы двух слагаемых уже известного нам типа ComplexType по своей структуре не должно отличаться от соответствующих выражений для слагаемых типа int или float. Но большинство операций языка C++ определены лишь для основных типов данных. Использование в качестве операндов операций выражений производных типов вызывает ошибки трансляции. Поэтому в классе ComplexType и были определены специальные функции-члены, реализующие арифметические операции над множеством комплексных чисел.

И всё же возможность сохранения привычной структуры выражений для производных типов в C++ существует.

Вернёмся к известному классу ComplexType. Мы определим два объекта класса ComplexType, после чего воспользуемся операцией присвоения.

ComplexType ctVal1(3.14, 0.712); ComplexType ctVal2, ctVal3; /* Комплексные числа со случайными значениями данных-членов.*/ ::::: ctVal2 = ctVal1; ctVal3 = ctVal2 = ctVal1; /* Операция присвоения коммутативна.*/

Если теперь вывести значения данных-членов объектов ctVal2 и ctVal3, то окажется, что они полностью совпадают со значениями данных-членов объекта ctVal1. Операция присваивания изначально определена для объектов класса ComplexType. Её можно рассматривать как предопределённую операцию, которая обеспечивает фактически побитовое копирование объекта, стоящего справа от символа = в объект, расположенный слева от этого знака.

Подобно любому другому выражению, выражение присваивания имеет собственное значение. Это значение равно выражению, стоящему справа от операции =. В ходе выполнения программы изменяется значение l-выражения в выражении присваивания, после чего значение этого выражения оказывается равным изменённому значению этого самого l-выражения.

Строго говоря, операцию присваивания для объектов производных типов нельзя называть операцией. Как и ранее рассмотренные нами "операции" приведения, она является операторной функцией. Это значит, что при её объявлении используется специальное имя, состоящее из ключевого слова operator с символом операции, а для её вызова можно использовать полную и сокращённую форму.

Мы приступаем к очередной модификации объявления класса ComplexType с целью переопределения новой операторной функции, реализующей то, что можно называть "операцией присваивания".

Работу по объявлению этой функции мы начнём с того, что попытаемся представить её общий вид:

  • функция объявляется как нестатический член класса и вызывается для объекта, которому надо присвоить соответствующее значение;
  • её имя состоит из ключевого слова operator, за которым, очевидно, следует символ = ;
  • основное назначение функции состоит в присваивании значения (множества значений) одного объекта другому. Следовательно, операторная функция operator=() должна иметь, по крайней мере, один параметр, который должен представлять присваиваемое значение;
  • и последнее, очень важное обстоятельство. Операторная функция operator=() должна возвращать новое значение объекта и по форме вызова должна создавать видимость коммутативности, поскольку этим свойством обладает операция присвоения.

class ComplexType { ::::: ComplexType operator = (const ComplexType &); /* Ссылка на константу при объявлении параметра не является обязательным условием для объявления операторной функции. Но это гарантия того, что присваиваемое значение не будет изменено в результате обращения к данным-членам. Операторная функция operator=() возвращает значение (именно значение!) объект класса ComplexType. Это не самый оптимальный способ обеспечения коммутативности операторной функции. Но при этом обеспечивается подобие операторной функции операции присваивания. */ ::::: } ::::: ComplexType ComplexType::operator = (const ComplexType& ctKey) { cout << "This is operator = (ComplexType& ctKey)..." << endl; /* Подтверждение о том, что выполняется именно эта функция. */ this->real = ctKey.real; this->imag = ctKey.imag; this->CTcharVal = ctKey.CTcharVal; this->x = ctKey.x; /* Теперь вся ответственность за корректность процесса копирования целиком и полностью возлагается на программиста. */ return *this; /* Мы возвращаем значение объекта, представленного this указателем. */ } ::::: /* Будем считать, что объекты ctVal1 и ctVal2 уже определены. Осталось рассмотреть варианты вызовов этой функции. */ ctVal2.operator = (ctVal1); /* Вариант полной формы вызова функции.*/ ctVal2 = ctVal1; /* Вариант сокращённой формы вызова функции. Операция обращения, ключевое слово operator в составном имени операторной функции и скобки, заключающие выражение, представляющее значение параметра опускаются. Создаётся иллюзия использования обычной операции присваивания. */ /* Демонстрация коммутативности операторной функции присваивания. */ ctVal3.operator = (ctVal2.operator = (ctVal1)); /* Операторная функция operator=() вызывается непосредственно из объекта ctVal3 со значением атрибута (ссылкой на объект), который сам в свою очередь является результатом применения операторной функции operator=() к объекту ctVal2 с параметром-ссылкой на объект ctVal1. Всё очень просто и красиво! */ ctVal3 = ctVal2 = ctVal1; /* Сокращённая форма коммутативного вызова операторной функции присваивания. */