Перегрузка операторов

Автор работы: Пользователь скрыл имя, 22 Декабря 2012 в 03:04, реферат

Краткое описание

Объектно-ориентированный подход к проектированию программных систем является элементом так называемых наукоемких технологий проектирования программ. Использование этого подхода дает возможность на порядок по сравнению с обычным директивным программированием сократить трудоемкость отладки программ и внесения изменений в программу при ее последующем развитии. Платой за это является наукоемкость проектирования, т.е. уделение весьма большой части времени на детальную проработку предметной области программы, составление структуры данных и их взаимосвязи, а также проектирование программной архитектуры.

Содержание

Введение………………………………………………………………………2
Механизм перегрузки………………………………………………………...3
Варианты и проблемы………………………………………………………..4
Перегрузка и полиморфные переменные…………………………………...8
Функциональная форма операторов…………………………………….....10
Перегрузка унарных операторов…………………………………………...17
Бинарные операторы. …………………………………………………….....20
Возвращаемые значения. …………………………………………………...21
Особые операторы…………………………………………………………...23
Рекомендации к форме определения операторов………………………….25
Заключение…………………………………………………………………...26
Литература……………………………………………………………………27

Прикрепленные файлы: 1 файл

Курсовик чистовой вариант.docx

— 52.33 Кб (Скачать документ)

Содержание.

  1. Введение………………………………………………………………………2
  2. Механизм перегрузки………………………………………………………...3
  3. Варианты и проблемы………………………………………………………..4
  4. Перегрузка и полиморфные переменные…………………………………...8
  5. Функциональная форма операторов…………………………………….....10
  6. Перегрузка унарных операторов…………………………………………...17
  7. Бинарные операторы. …………………………………………………….....20
  8. Возвращаемые значения. …………………………………………………...21
  9. Особые операторы…………………………………………………………...23
  10. Рекомендации к форме определения операторов………………………….25
  11. Заключение…………………………………………………………………...26
  12. Литература……………………………………………………………………27

 

 

 

 

 

 

 

 

 

 

 

 

1.Введение

       Объектно-ориентированный подход к проектированию программных систем является элементом так называемых наукоемких технологий проектирования программ. Использование этого подхода дает возможность на порядок по сравнению с обычным директивным программированием сократить трудоемкость отладки программ и внесения изменений в программу при ее последующем развитии. Платой за это является наукоемкость проектирования, т.е. уделение весьма большой части времени на детальную проработку предметной области программы, составление структуры данных и их взаимосвязи, а также проектирование программной архитектуры.

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

 

2.Механизм перегрузки

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

Чтобы разрешить существование  нескольких одноимённых операций, достаточно ввести в язык правило, согласно которому операция (процедура, функция или  оператор) опознаются компилятором не только по имени (обозначению), но и  по типам их параметров. Таким образом, abs(i), где i объявлено как целое, и abs(x), где x объявлено как вещественное — это две разные операции. Принципиально в обеспечении именно такой трактовки нет никаких сложностей.

Чтобы дать возможность определять и переопределять операции, необходимо ввести в язык соответствующие синтаксические конструкции. Вариантов их может  быть достаточно много, но по сути они  ничем друг от друга не отличаются, достаточно помнить, что запись вида «<операнд1> <знакОперации> <операнд2>» принципиально аналогична вызову функции «<знакОперации>(<операнд1>,<операнд2>)». Достаточно разрешить программисту описывать поведение операторов в виде функций — и проблема описания решена.

 

 

 

 

3.Варианты и проблемы.

       Перегрузка процедур и функций на уровне общей идеи, как правило, не представляет сложности ни в реализации, ни в понимании. Однако даже в ней имеются некоторые «подводные камни», которые необходимо учитывать. Разрешение перегрузки операций создаёт гораздо больше проблем как для реализатора языка, так и для работающего на этом языке программиста.

Проблема идентификации.

Первый вопрос, с которым  сталкивается разработчик транслятора  языка, разрешающего перегрузку процедур и функций: каким образом из числа  одноимённых процедур выбрать ту, которая должна быть применена в  данном конкретном случае? Всё хорошо, если существует вариант процедуры, типы формальных параметров которого в точности совпадают с типами параметров фактических, применённых  в данном вызове. Однако практически  во всех языках в употреблении типов  существует некоторая степень свободы, предполагающая, что компилятор в  определённых ситуациях автоматически  выполняет безопасные преобразования типов. Например, в арифметических операциях  над вещественным и целым аргументами  целый обычно приводится к вещественному  типу автоматически, и результат  получается вещественным. Предположим, что существует два варианта функции  add:

int   add(int a1, int a2);

float add(float a1, float a2);

Каким образом компилятор должен обработать выражение y = add(x, i), где x имеет тип float, а i — тип int? Очевидно, что точного совпадения нет. Имеется два варианта: либо y=add_int((int)x,i), либо как y=add_flt(x, (float)i) (здесь именами add_int и add_flt обозначены соответственно, первый и второй варианты функции).

Возникает вопрос: должен ли транслятор разрешать подобное использование  перегруженных функций, а если должен, то на каком основании он будет выбирать конкретный используемый вариант? В частности, в приведённом выше примере, должен ли транслятор при выборе учитывать тип переменной y? Нужно отметить, что приведённая ситуация — простейшая, возможны гораздо более запутанные случаи, которые усугубляются тем, что не только встроенные типы могут преобразовываться по правилам языка, но и объявленные программистом классы при наличии у них родственных отношений допускают приведение один к другому. Решений у этой проблемы два:

Запретить неточную идентификацию  вообще. Требовать, чтобы для каждой конкретной пары типов существовал  в точности подходящий вариант перегруженной  процедуры или операции. Если такого варианта нет, транслятор должен выдавать ошибку. Программист в этом случае должен применить явное преобразование, чтобы привести фактические параметры  к нужному набору типов. Этот подход неудобен в языках типа C++, допускающих  достаточную свободу в обращении  с типами, поскольку он приводит к существенному различию поведения  встроенных и перегруженных операций (к обычным числам арифметические операции можно применять, не задумываясь, а к другим типам — только с  явным преобразованием) либо к появлению  огромного количества вариантов  операций.

 

Установить определённые правила выбора «ближайшего подходящего  варианта». Обычно в этом варианте компилятор выбирает те из вариантов, вызовы которых можно получить из исходного только безопасными (не приводящими к потере информации) преобразованиями типов, а если их несколько — может выбирать, исходя из того, какой вариант требует меньше таких преобразований. Если в результате остаётся несколько возможностей, компилятор выдаёт ошибку и требует явного указания варианта от программиста.

Специфические вопросы перегрузки операций.

В отличие от процедур и  функций, инфиксные операции языков программирования имеют два дополнительных свойства, существенным образом влияющих на их функциональность: приоритет  и ассоциативность, наличие которых  обусловливается возможностью «цепочной» записи операторов (как понимать a+b*c: как (a+b)*c или как a+(b*c)? Выражение a-b+c — это (a-b)+c или a-(b+c)?).

Встроенные в язык операции всегда имеют наперёд заданные традиционные приоритеты и ассоциативность. Возникает  вопрос: какие приоритеты и ассоциативность будут иметь переопределённые версии этих операций или, тем более, новые созданные программистом операции? Есть и другие тонкости, которые могут требовать уточнения. Например, в Си существуют две формы операций увеличения и уменьшения значения ++ и -- — префиксная и постфиксная, поведение которых различается. Как должны вести себя перегруженные версии таких операций?

Различные языки по-разному  решают приведённые вопросы. Так, в C++ приоритет и ассоциативность  перегруженных версий операций сохраняются  такими же, как и у определённых в языке, а описания перегрузки префиксной и постфиксной формы операторов инкремента и декремента используют различные сигнатуры:

 

Префиксная форма

Постфиксная форма

Функция

T &operator ++(T &)

T operator ++(T &, int)

Функция-член

T &T::operator ++()

T T::operator ++(int)


 

Фактически целого параметра  у операции нет — он фиктивен, и добавляется только для внесения различия в сигнатуры.

Объявление новых операций.

Ещё сложнее обстоит дело с объявлением новых операций. Включить в язык саму возможность  такого объявления несложно, но вот  реализация его сопряжена со значительными  трудностями. Объявление новой операции — это, фактически, создание нового ключевого слова языка программирования, осложнённое тем фактом, что операции в тексте, как правило, могут следовать  без разделителей с другими лексемами. При их появлении возникают дополнительные трудности в организации лексического анализатора. Например, если в языке  уже есть операции «+» и унарный  «-» (изменение знака), то выражение a+-b можно безошибочно трактовать как a + (-b), но если в программе объявляется новая операция +-, тут же возникает неоднозначность, ведь то же выражение можно уже разобрать и как a (+-) b. Разработчик и реализатор языка должен каким-то образом решать подобные проблемы. Варианты, опять-таки, могут быть различными: потребовать, чтобы все новые операции были односимвольными, постулировать, что при любых разночтениях выбирается «самый длинный» вариант операции (то есть до тех пор, пока очередной читаемый транслятором набор символов совпадает с какой-либо операцией, он продолжает считываться), пытаться обнаруживать коллизии при трансляции и выдавать ошибки в спорных случаях… Так или иначе, языки, допускающие объявление новых операций, решают эти проблемы.

Не следует забывать, что  для новых операций также стоит  вопрос определения ассоциативности  и приоритета. Здесь уже нет  готового решения в виде стандартной  языковой операции, и обычно приходится просто задать эти параметры правилами  языка. Например, сделать все новые  операции левоассоциативными и дать им один и тот же, фиксированный, приоритет, либо ввести в язык средства задания того и другого.

 

 

 

4.Перегрузка и полиморфные переменные.

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

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

Таким образом, использование  перегрузки операций в сочетании  с полиморфными переменными делает неизбежным динамическое определение  вызываемого кода..Синтаксис перегрузки операторов очень похож на определение функции с именем operator@, где @ — это идентификатор оператора (например +, -, <<, >>). Рассмотрим простейший пример:

class Integer

{

private:

int value;

public:

Integer(int i): value(i)

{}

const Integer operator+(const Integer& rv) const {

return (value + rv.value);

}

};

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

Информация о работе Перегрузка операторов