Автор работы: Пользователь скрыл имя, 22 Декабря 2012 в 03:04, реферат
Объектно-ориентированный подход к проектированию программных систем является элементом так называемых наукоемких технологий проектирования программ. Использование этого подхода дает возможность на порядок по сравнению с обычным директивным программированием сократить трудоемкость отладки программ и внесения изменений в программу при ее последующем развитии. Платой за это является наукоемкость проектирования, т.е. уделение весьма большой части времени на детальную проработку предметной области программы, составление структуры данных и их взаимосвязи, а также проектирование программной архитектуры.
Введение………………………………………………………………………2
Механизм перегрузки………………………………………………………...3
Варианты и проблемы………………………………………………………..4
Перегрузка и полиморфные переменные…………………………………...8
Функциональная форма операторов…………………………………….....10
Перегрузка унарных операторов…………………………………………...17
Бинарные операторы. …………………………………………………….....20
Возвращаемые значения. …………………………………………………...21
Особые операторы…………………………………………………………...23
Рекомендации к форме определения операторов………………………….25
Заключение…………………………………………………………………...26
Литература……………………………………………………………………27
Содержание.
1.Введение
Объектно-ориентированный подход к проектированию программных систем является элементом так называемых наукоемких технологий проектирования программ. Использование этого подхода дает возможность на порядок по сравнению с обычным директивным программированием сократить трудоемкость отладки программ и внесения изменений в программу при ее последующем развитии. Платой за это является наукоемкость проектирования, т.е. уделение весьма большой части времени на детальную проработку предметной области программы, составление структуры данных и их взаимосвязи, а также проектирование программной архитектуры.
Вместе с тем, объектно-ориентированное
программирование существенно отличается
от классических методов программирования,
в том числе структурного и
модульного программирования. При этом
коренным образом ломается понятие
об алгоритме как о
2.Механизм перегрузки
Перегрузка операций предполагает введение в язык двух взаимосвязанных особенностей: возможности объявлять в одной области видимости несколько процедур или функций с одинаковыми именами и возможности описывать собственные реализации бинарных операторов (то есть знаков операций, обычно записываемых в инфиксной нотации, между операндами). Принципиально реализация их достаточно проста:
Чтобы разрешить существование нескольких одноимённых операций, достаточно ввести в язык правило, согласно которому операция (процедура, функция или оператор) опознаются компилятором не только по имени (обозначению), но и по типам их параметров. Таким образом, abs(i), где i объявлено как целое, и abs(x), где x объявлено как вещественное — это две разные операции. Принципиально в обеспечении именно такой трактовки нет никаких сложностей.
Чтобы дать возможность определять
и переопределять операции, необходимо
ввести в язык соответствующие синтаксические
конструкции. Вариантов их может
быть достаточно много, но по сути они
ничем друг от друга не отличаются,
достаточно помнить, что запись вида
«<операнд1> <знакОперации> <операнд2>»
принципиально аналогична вызову функции
«<знакОперации>(<операнд1>,<
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? Нужно отметить, что приведённая ситуация — простейшая, возможны гораздо более запутанные случаи, которые усугубляются тем, что не только встроенные типы могут преобразовываться по правилам языка, но и объявленные программистом классы при наличии у них родственных отношений допускают приведение один к другому. Решений у этой проблемы два:
Запретить неточную идентификацию
вообще. Требовать, чтобы для каждой
конкретной пары типов существовал
в точности подходящий вариант перегруженной
процедуры или операции. Если такого
варианта нет, транслятор должен выдавать
ошибку. Программист в этом случае
должен применить явное
Установить определённые правила выбора «ближайшего подходящего варианта». Обычно в этом варианте компилятор выбирает те из вариантов, вызовы которых можно получить из исходного только безопасными (не приводящими к потере информации) преобразованиями типов, а если их несколько — может выбирать, исходя из того, какой вариант требует меньше таких преобразований. Если в результате остаётся несколько возможностей, компилятор выдаёт ошибку и требует явного указания варианта от программиста.
Специфические вопросы перегрузки операций.
В отличие от процедур и функций, инфиксные операции языков программирования имеют два дополнительных свойства, существенным образом влияющих на их функциональность: приоритет и ассоциативность, наличие которых обусловливается возможностью «цепочной» записи операторов (как понимать 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) |
Фактически целого параметра у операции нет — он фиктивен, и добавляется только для внесения различия в сигнатуры.
Объявление новых операций.
Ещё сложнее обстоит дело
с объявлением новых операций.
Включить в язык саму возможность
такого объявления несложно, но вот
реализация его сопряжена со значительными
трудностями. Объявление новой операции
— это, фактически, создание нового
ключевого слова языка
Не следует забывать, что для новых операций также стоит вопрос определения ассоциативности и приоритета. Здесь уже нет готового решения в виде стандартной языковой операции, и обычно приходится просто задать эти параметры правилами языка. Например, сделать все новые операции левоассоциативными и дать им один и тот же, фиксированный, приоритет, либо ввести в язык средства задания того и другого.
4.Перегрузка и полиморфные переменные.
Когда перегружаемые операции, функции и процедуры используются в языках со строгой типизацией, где каждая переменная имеет предварительно описанный тип, задача выбора варианта перегруженной операции, используемого в каждом конкретном случае, независимо от её сложности, решается транслятором. Это означает, что для компилируемых языков использование перегрузки операций не приводит к снижению быстродействия — в любом случае, в объектном коде программы присутствует вполне определённая операция или вызов функции. Иначе обстоит дело при возможности использования в языке полиморфных переменных, то есть переменных, которые могут в разные моменты времени содержать значения разных типов.
Поскольку тип значения,
к которому будет применяться
перегруженная операция, неизвестен
на момент трансляции кода, компилятор
лишён возможности выбрать
Таким образом, использование перегрузки операций в сочетании с полиморфными переменными делает неизбежным динамическое определение вызываемого кода..Синтаксис перегрузки операторов очень похож на определение функции с именем operator@, где @ — это идентификатор оператора (например +, -, <<, >>). Рассмотрим простейший пример:
class Integer
{
private:
int value;
public:
Integer(int i): value(i)
{}
const Integer operator+(const Integer& rv) const {
return (value + rv.value);
}
};
В данном случае, оператор оформлен
как член класса, аргумент определяет
значение, находящееся в правой части
оператора. Вообще, существует два основных
способа перегрузки операторов: глобальные
функции, дружественные для класса,
или подставляемые функции