Расширение действия (перегрузка) стандартных операций


Одной из привлекательных особенностей языка Си++ является возможность распространения действия стандартных операций на операнды, для которых эти операции первоначально в языке не предполагались. Например, определив новый тип комплексное число нам наверняка захочется иметь возможность записать сумму двух объектов класса комплексное число (comp) в виде s+g. Для того чтобы это стало возможным программист должен определить специальную функцию, называемую "операция-функция" (operator function). Формат определения операции-функции:

тип_функции operator# (список формальных параметров) {операторы_тела_операции-функции}

При необходимости может добавляться и прототип операции-функции с таким форматом:

тип_функции operator#(список формальных параметров);

где # - знак операции (например, =, +, -, и т.д.); operator# - имя функции. Как мы видим определение и описание операции-функции практически ни чем не отличается от обычной функции, но отличия все-таки имеются, и состоят они в наличии двух абсолютно эквивалентных способах вызова оператора-функции. Так для операции сложения двух комплексных чисел после определения операции-функции

comp operator+( comp s, comp g){

comp d;

d.Re = s.Re+g.Re;

d.Im= s.Im+g.Im;

return d;

}

два способа её вызова выглядя следующим образом:

comp k(7,2), e(2,5);

operator+(k, e); //первый способ вызова

k+e+k; //второй способ вызова

 

comp comp::operator+( comp g){

comp d;

d.Re = Re+g.Re;

d.Im= Im+g.Im;

return d;

}

 

как видно из примера первый способ очень похож на обычный вызов обычной глобальной функции имя_функции(список фактических параметров), а второй способ более краткий позволяет добиться от типов данных, определённых программистом, такой же функциональности как и у фундаментальных типов данных, т.е. перегрузить для новых типов данных необходимый им набор, определённых в языке Си++ операций.

В отличии от обычных функций при реализации операция-функция надо учитывать несколько ограничений, так количество параметров у операции-функции зависит от арности операции и от способа определения функции. Операция-функция определяет алгоритм выполнения перегруженной операции, когда эта операция применяется к объектам класса, для которого операция-функция введена. Чтобы явная связь с классом была обеспечена, операция функция должна быть либо компонентом класса, либо она должна быть определена в классе как дружественная, либо у нее должен быть хотя бы один параметр типа класс (или ссылка на класс). Начнем с последнего варианта.

Если для класса Т введена операция-функция с заголовком Т operator *(T х, T у) и определены два объекта А, В класса Т, то выражение А*В интерпретируется как вызов функции operator * (А,B).

Вторую возможность перегрузки бинарной операции представляют компонентные функции классов. Любая стандартная бинарная операция @ может быть перегружена с помощью нестатической операции-функции, входящей в число компонентов класса. В этом случае у нее должен быть только один параметр и заголовок может выглядеть так:

Т operator @ (Т х)

(здесь Т - определенный пользователем тип, т.е. класс). В этом случае выражение A@B c объектами А, В класса T в качестве операндов интерпретируется как вызов функции А.operator@(В), причем в теле операции-функции выполняется обработка компонентов объекта-параметра В и того объекта А, для которого осуществлен вызов. При необходимости принадлежность компонентов объекту А в теле операции-функции можно сделать явным с помощью указателя this.

Итак, механизм классов дает возможность программисту определять новые типы данных, отображающие понятия решаемой задачи. Перегрузка стандартных операций языка Си++ позволяет сделать операции над объектами новых классов удобными и общепонятными. Но возникают два вопроса. Можно ли вводить собственные обозначения для операций, не совпадающие со стандартными операциями языка Си++? И все ли операции языка Си++ могут быть перегружены? К сожалению (или как констатация факта), вводить операции с совершенно новыми обозначениями язык Си++ не позволяет. Ответ на второй вопрос также отрицателен - существует несколько операций, не допускающих перегрузки. Вот их список:

прямой выбор компонента структурированного объекта;

.* обращение к компоненту через указатель на него;

?: условная операция;

:: операция указания области видимости;

sizeof() операция вычисления размера в байтах;

# препроцессорная операция;

## препроцессорная операция.

Рассмотрим еще несколько важных особенностей механизма перегрузки (расширения действия) стандартных операций языка Си++.

При расширении действия (при перегрузке) стандартных операций нельзя и нет возможности изменять их приоритеты.

Нельзя изменить для перегруженных операций синтаксис выражений, т.е. невозможно ввести унарную операцию = или бинарную операцию ++.

Нельзя вводить новые лексические обозначения операций, даже формируя их из допустимых символов. Например, возведение в степень ** из языка Фортран нельзя ввести в языке Си++.

Любая бинарная операция @ определяется для объектов некоторого класса двумя существенно разными способами: либо как компонентная функция с одним параметром, либо как глобальная (возможно дружественная) функция с двумя параметрами. В первом случае запись х@у означает вызов х.operator@(у), во втором случае х@у означает вызов operator@(x,y).

В соответствии с семантикой бинарных операций ' = ', ' [ ] ' , ' ->' операции-функции с названиями operator=, operator[], operator-> не могут быть глобальными функциями, а должны быть нестатическими компонентными функциями. "Это гарантирует, что первыми операндами будут lvalue".

Любая унарная операция '$' определяется для объектов некоторого класса также двумя способами: либо как компонентная функция без параметров, либо как глобальная (возможно дружественная) функция с одним параметром.

Для префиксной операции ' $' выражение $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), то оказывается необходимым ввести большое количество операций-функций. К счастью, при вызове операций-функций действуют все соглашения о преобразованиях стандартных типов параметров, и нет необходимости учитывать сочетания всех типов. В ряде случаев для бинарной операции достаточно определить только три варианта:

• стандартный_тип, класс

• класс, стандартный_тип

• класс, класс.

Например, для рассмотренного класса comp можно ввести как дружественные такие операции-функции:

comp operator+(comp x, comp у)

{return(comp(x.real + y.real, x.imag + y.imag)); }

comp operator + (double x, comp y)

{return(comp(x + y.real, y.imag)); }

comp operator + (comp x, double y)

{ return(comp(x.real + y, x.imag));}

После этого станут допустимыми выражения в следующих операторах:

comp СС(1.0,2.0);

comp ЕЕ;

ЕЕ = 4.0 + СС;

ЕЕ = CC + 2.0;

ЕЕ = СС + ЕЕ;

ЕЕ = СС + 20; // По умолчанию приведение int к double

СС = ЕЕ + 'е' ; //По умолчанию приведение char к double

Вместо использования нескольких (в нашем примере вместо трех) очень схожих операций-функций можно задачу преобразования стандартного типа в объект класса поручить конструктору. Для этого требуется только одно - необходим конструктор, формирующий объект класса по значению стандартного типа. Например, добавление в класс comp такого конструктора

comp (double x)

{ real = x; imag =0.0; }

позволяет удалить все дополнительные операции-функции, оставив только одну с прототипом:

friend comp operator +(comp, comp);

В этом случае целый операнд выражения 6+ЕЕ автоматически преобразуется к типу double, а затем конструктор формирует комплексное число с нулевой мнимой частью. Далее выполняется операция-функция

operator +(comp(double(6),double(0)), ЕЕ)

Вместо включения в класс дополнительного конструктора с одним аргументом можно в заголовке единственного конструктора ввести умалчиваемое значение второго параметра:

compl(double r, double i = 0.0) { Re=r; Im = i; }

Теперь каждое выражение с операцией ' + ', в которое входит, кроме объекта класса comp, операнд одного из стандартных типов, будет обрабатываться совершенно верно. Однако такое умалчивание является частным решением и не для всех классов пригодно.

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

В современной версии языка Си++ принято соглашение, что перегрузка префиксных операций ++ и -- ничем не отличается от перегрузки других унарных операций, т.е. глобальные и, возможно, дружественные функции operator ++() и operator - -() с одним параметром некоторого класса определяют префиксные операции ++ и --. Компонентные операции-функции без параметров определяют те же префиксные операции. При расширении действия постфиксных операций ++ и -- операции-функции должны иметь еще один дополнительный параметр типа int. Если для перегрузки используется компонентная операция-функция, то она должна иметь один параметр типа int. Если операция-функция определена как глобальная (не компонентная), то ее первый параметр должен иметь тип класса, а второй - тип int.

Когда в программе используется соответствующее постфиксное выражение, то операция-функция вызывается с нулевым целым параметром.



Дата добавления: 2020-12-11; просмотров: 390;


Поиск по сайту:

Воспользовавшись поиском можно найти нужную информацию на сайте.

Поделитесь с друзьями:

Считаете данную информацию полезной, тогда расскажите друзьям в соц. сетях.
Poznayka.org - Познайка.Орг - 2016-2024 год. Материал предоставляется для ознакомительных и учебных целей.
Генерация страницы за: 0.015 сек.