На этом шаге мы рассмотрим несколько особенностей, присущих перегрузке стандартных операций.
Механизм классов дает возможность программисту определять новые типы данных, отображающие
понятия решаемой задачи. Перегрузка стандартных операций языка C++ позволяет сделать
операции над объектами новых классов удобными и общепонятными. Но возникают два вопроса.
Можно ли вводить собственные обозначения для операций, не совпадающие со стандартными операциями
языка C++?
И все ли операции языка C++ могут быть перегружены?
К сожалению, вводить операции с совершенно новыми обозначениями язык C++ не позволяет.
Ответ на второй вопрос также отрицателен - существует несколько операций, не допускающих
перегрузки. Вот их список:
Таблица 1. Операции, которые не перегружаются
Операция | Назначение |
---|---|
.(точка) | Прямой выбор компонента структурированного объекта. |
.* | Обращение к компоненту через указатель на него. |
?: | Условная операция. |
:: | Операция указания области видимости. |
sizeof | Операция вычисления размера в байтах. |
# | Препроцессорная операция. |
## | Препроцессорная операция. |
Рассмотрим еще несколько важных особенностей механизма перегрузки (расширения действия) стандартных операций языка
C++.
нет возможности изменять их приоритеты.
невозможно ввести унарную операцию "=" или бинарную операцию "++".
из допустимых символов. Например, возведение в степень "**" из языка
Fortran нельзя ввести в языке C++.
существенно разными способами:
- либо как компонентная функция с одним параметром,
- либо как глобальная (возможно дружественная) функция с двумя параметрами.
В первом случае X @ Y означает вызов X.operator @ (Y), во втором случае
X @ Y означает вызов operator @ (X,Y).
В соответствии с семантикой бинарных операций "=", "[ ]", "->" операции-функции
с названиями operator =, operator [ ], operator -> не могут быть глобальными функциями,
а должны быть нестатическими компонентными функциями.
двумя способами:
- либо как компонентная функция без параметров,
- либо как глобальная (возможно дружественная) функция с одним параметром.
Для префиксной операции "$" выражение $z означает вызов компонентной функции
z.operator $ () или вызов глобальной функции operator $ (z).
Для постфиксной операции выражение z$ означает либо вызов компонентной функции
z.operator $ (), либо вызов глобальной функции operator $ (z).
как комбинации других встроенных операций над теми же операндами. Например, для переменной
long m = 0; выражение ++m означает m += 1, что в свою очередь означает
выполнение выражения m = m + 1. Такие автоматические замены выражений не
реализуются и не справедливы для перегруженных операций. Например, в общем
случае определение operator *=() нельзя вывести из определений operator * () и
operator = ().
введенного пользователем. Невозможно для операнда m типа int
изменить смысл выражения 2 + m и т.п.
тип, не может быть компонентной функцией. Для объяснения этого ограничения
предположим, что аа - объект некоторого класса и для него расширено действие операции
"+".
При разборе выражения аа + 2 компилятором выполняется вызов операции-функции
аа.operator + (2) или operator + (aa,2).
При разборе 2 + аа допустим вызов operator + (2,аа), но ошибочен 2.operator + (аа).
Таким образом, расширение действия операции "+" на выражение
стандартный_тип + объект_класса
допустимо только с помощью глобальных операций-функций.
типов операндов. Например, определяя операцию сложения "+" для комплексных чисел,
приходится учитывать сложение комплексного числа с вещественным и вещественного с комплексным,
комплексного с целым и целого с комплексным и т.д. Если учесть, что вещественные
числа представлены несколькими типами (float, double, long double) и целые числа
имеют разные типы (int, long, unsigned, char), то оказывается необходимым ввести
большое количество операций-функций. К счастью, при вызове операций-функций действуют все
соглашения о преобразованиях стандартных типов параметров, и нет необходимости учитывать
сочетания всех типов. В ряде случаев для бинарной операции достаточно определить только
три варианта:
- стандартный тип, класс;
- класс, стандартный_тип;
- класс, класс.
Например, для рассмотренного класса complex можно
ввести как дружественные такие операции-функции:
complex operator + (complex x, complex у) { return complex(x.real + у.real, x.imag + y.imag); } complex operator + (double x, complex y) { return complex(x + y.real, y.imag); } complex operator + (complex x, double y) { return complex(x.real + y, x.imag); }
После этого станут допустимыми выражения в следующих операторах:
complex CC(1.0,2.0); complex ЕЕ; ЕЕ = 4.0 + CC; ЕЕ = ЕЕ + 2.0; ЕЕ = CC + ЕЕ; ЕЕ = CC + 20; // По умолчанию приведение int к double. CC = ЕЕ + 'е'; // По умолчанию приведение char к double.
Вместо использования нескольких (в нашем примере вместо трех) очень схожих операций-функций
можно задачу преобразования стандартного типа в объект класса поручить конструктору.
Для этого требуется только одно - необходим конструктор, формирующий объект класса по
значению стандартного типа. Например, добавление в класс complex такого конструктора:
complex(double x) { real = x; imag = 0.0; }
позволяет удалить все дополнительные операции-функции, оставив только одну с прототипом:
friend complex operator + (complex, complex);
В этом случае целый операнд выражения 6 + ЕЕ автоматически преобразуется к типу
double, а затем конструктор формирует комплексное число с нулевой мнимой частью.
Далее выполняется операция-функция:
operator + (complex(double(6),double(0)), ЕЕ)
Вместо включения в класс дополнительного конструктора с одним аргументом можно в заголовке
единственного конструктора ввести умалчиваемое значение второго параметра:
complex(double re, double im = 0.0) { real = re; imag = im; }
Теперь каждое выражение с операцией "+", в которое входит, кроме объекта класса
complex, операнд одного из стандартных типов, будет обрабатываться совершенно
верно. Однако такое умалчивание является частным решением и не для всех классов пригодно.
Можно было бы в качестве умалчиваемого значения мнимой части взять и число, отличное от нуля,
но поведение объектов класса complex при сложении с данными
стандартных типов оказалось бы неверным.
На следующем шаге мы продолжим подводить итоги перегрузки операций.