Понятие полиморфизма. Классификация типов полиморфизма по Вегнеру.



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

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

Наследование без полиморфизма возможно, но не очень полезно. Это видно на примере Ada, где можно объявлять производные типы, но из-за мономорфизма языка операции жестко задаются на стадии компиляции. Каплан и Джонсон отметили, что "полиморфизм наиболее целесообразен в тех случаях, когда несколько классов имеют одинаковые протоколы". Полиморфизм позволяет обойтись без операторов выбора, поскольку объекты сами знают свой тип. При отсутствии полиморфизма код программы вынуждено содержит множество операторов выбора case или switch. Например, на языке Pascal невозможно образовать иерархию классов телеметрических данных; вместо этого придется определить одну большую запись с вариантами, включающую все разновидности данных.

Вегнер же предложил следующую классификацию полиморфизма:

Специальный полиморфизм Универсальный полиморфизм
Перегрузка (времени компиляции) Параметрический (времени компиляции)
Приведение типов (времени компиляции или времени выполнения) Полиморфизм включения (наследования или времени выполнения)

17. Специальный полиморфизм. Перегрузка функций и операторов в С++. Порядок поиска подходящей функции при перегрузке. Примеры.

Перегрузка функций Перегрузка операторов
void print(long); void print(double);  void main() {        print (1L);        print (1.0);        print (1);       //? } void operator +(X, X); void operator +(X, double); void f(X a) { X b; b+a; // ::operator+(X(1), a) a+1.0; // ::operator+(a, 1.0) }

Порядок поиска подходящей функции.

1. Точное совпадение типов.

2. Соответствие, достигаемое «продвижением» интегральных типов (например, bool à char, char à int, int àunsigned)

3. Соответствие, достигаемое путём стандартных преобразований (int àdouble, double à int, double à long double).

4. Соответствие, достигаемое при помощи преобразований, определяемых пользователем.

5. Соответствие за счёт многоточий (…).

18. Специальный полиморфизм. Приведение типов. Явные и неявные преобразования типа в С++. Примеры.

Явные:

a) static_cast<тип>(объект) – обычное статическое преобразование. Пример: double a, int b = 0; a = static_cast<double>(b);

b) dynamic_cast<тип>(объект) – контролирует динамическое приведение полиморфных типов. Позволяет также выполнять приведение типов по иерархии наследования сверху вниз, но только если приводимый объект действительно относиться к тому типу, к которому приводится. Пример: class B: A {} … A* a = new B; B* b; b = dynamic_cast<B*>(a);

c) reinterpret_cast<тип>(объект) – преобразовывает указатель одного типа в указатель совершенно другого типа. Также используется для приведения типа указатель к типу целое и наоборот. Пример: void* x; A* a; a = reinterpret_cast<A*>(x); или: double* p = &a; int i = reinterpret_cast<int>(p); std::cout << i;

d) const_cast<тип>(объект) – используется для явной подмены атрибутов const у ссылок и указателей на переменные. Пример: void myfunc(const int* p) {int* b = const_cast<int*>(p); *b = 100;}

Неявное преобразование – присваивание объекта одного типа объекту другого типа: Double a = 5.27; int b = a; // b = 5

19. Универсальный полиморфизм. Наследование. Проблемы классификации. Получение иерархии классов. Одиночное наследование в С++.

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

При одиночном наследовании наследуется только один класс. Самый общий класс в иерархии классов называется базовым. В большинстве приложений базовых классов бывает несколько, и они отражают наиболее общие абстракции предметной области. В C++, структура классов - это скорее лес из деревьев наследования, чем одна многоэтажная структура с одним корнем. Но в некоторых языках программирования определен базовый класс самого верхнего уровня. В языке Delphi эту роль играет класс TObject. В С++ при одиночном наследовании указывается спецификатор доступа: public – значит, что все открытые члены базового класса остаются открытыми в дочернем; private – значит, что все открытые члены базового класса становятся закрытыми в наследнике. В любом случае все закрытые члены базового класса остаются недоступными наследнику. Если спецификатор не указан, то используется private.

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

В большинстве языков допускается не только наследование методов суперкласса, но также добавление новых и переопределение существующих методов. В Smalltalk любой метод суперкласса можно переопределить в подклассе. В C++ степень контроля за этим несколько выше. Функция, объявленная виртуальной, может быть в подклассе переопределена, а остальные - нет.

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

Пример:class Transport {}; class Airtransport: public Transport {}; class Motortransport: public Transport{}; class Airplane: public Airtransport{};

20. Универсальный полиморфизм. Наследование. Статические и виртуальные функции в С++. Абстрактные классы в С++. Примеры.

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

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

В С++ различаются также чистые виртуальные функции, т.е. функции которые не имеют реализации в базовом классе. При этом базовый класс называется абстрактным и не может инстанцироваться.


21. Универсальный полиморфизм. Наследование. Интерфейсы. Интерфейсы в С++. Отличие наследования интерфейсов от множественного наследования. Примеры.

Полностью абстрактный класс (т.е. содержащий только чистые виртуальные функции) называется интерфейсом.

В С++ понятие интерфейса отсутствовало и поэтому для решения проблемы множественного наследования: class A{ }; class B: public A {}; class C: public A {}; class D: public A, B {}; применялось понятие виртуального базового класса: class A { }; class B: public virtual A {}; class C: public virtual A {}; class D: public A, B {}; В таких случаях при двойном наследовании одного и тоже базового класса, компилятор соответственным образом обработает такую ситуацию. В остальном такая обработка ничем не отличается от обычного наследования. В Visual С++ для определения интерфейсов появилось ключевое слово interface.

Отличие наследования интерфейсов от множественного наследования (наследование реализации):

a) Наследование реализации означает, что класс происходит от базового класса и получает все его поля-члены и функции. Наследник полностью реализует базовый класс, если только явно не указано, что используются виртуальные функции, которые могут быть переопределены наследником. Такое наследование полезно когда надо добавить функциональность существующему классу или же когда несколько классов имеют частично схожую функциональность. Но такой тип наследования вызывает описанные выше ошибки.

b) Наследование интерфейсов означает, что класс наследует только сигнатуру функций, но не наследует реализации. Такой тип полезен, когда надо специфицировать несколько классов одним интерфейсом. При нем не возникает описанных ошибок. Поэтому многие языки (C#, Object Pascal) поддерживают только его.

22. Универсальный полиморфизм. Наследование. Проблема «среза». Примеры на С++.

Проблема среза – вызов методов определенных только в классе наследнике через указатель на базовый класс. Решение – прямое приведение типа для указателя.

class Transport

{ public:

virtual double Speed()const =0;

};

class Airtransport: public Transport {};

{ public:

virtual double Speed()const {}

   double Altitude()const{}

};

main()

Transport * t = new Airtransport;

cout << t->Speed();

cout << t->Altitude();    //проблема «среза», решение: ((Airtransport*)t)-> Altitude();

delete t;

}

23. Универсальный полиморфизм. Множественное наследование в С++. Проблемы множественного наследования. Варианты решений проблем множественного наследования.

В ряде случаев полезно реализовать наследование от нескольких суперклассов. Предположим, что нужно определить класс, представляющий разновидности растений.

Множественное наследование - вещь нехитрая, но оно осложняет реализацию языков программирования. Есть две проблемы - конфликты имен между различными суперклассами и повторное наследование. Первый случай, это когда в двух или большем числе суперклассов определено поле или операция с одинаковым именем. В C++ этот вид конфликта должен быть явно разрешен вручную, а в Smalltalk берется то, которое встречается первым. Повторное наследование, это когда класс наследует двум классам, а они порознь наследуют одному и тому же четвертому. Получается ромбическая структура наследования и надо решить, должен ли самый нижний класс получить одну или две отдельные копии самого верхнего класса? В некоторых языках повторное наследование запрещено, в других конфликт решается "волевым порядком", а в C++ это оставляется на усмотрение программиста использованием виртуальных базовых классов: class A { }; class B: public virtual A {}; class C: public virtual A {}; class D: public A, B {};. Виртуальные базовые классы используются для запрещения дублирования повторяющихся структур, в противном случае в подклассе появятся копии полей и функций и потребуется явное указание происхождения каждой из копий.

24. Параметрический полиморфизм. Параметризованные классы в С++. Примеры.

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

Общая форма декларации шаблона класса:

template <список аргументов шаблона> class Имя класса {// Тело класса};

Пример:

template <class T, int n> class Vector

{

       T m_t[n];

       int m_size;

public:

       Vector();

       {

                   m_size=n;

       }

       T& operator [] (int Index)

       {

                   assert(Index>=0 && Index < m_size);

              return m_t[Index];

       }

};

int main()

{

       Vector<int, 10> intVector; // массив целых чисел

       Vector<Point, 5> pointVector; // массив объектов класса                                

       return 0;

}


25. Параметрический полиморфизм. Полная специализация шаблонов в С++. Примеры.

Определяет частные случаи применения шаблонна на основе явных аргументов.

template <class T> class Vector{};

template <> class Vector<int> {реализация специфичная для типа int};

template <> class Vector<void*> { реализация специфичная для типа void*};

int main()

{

Vector<char> c;

Vector<int> I;

Vector<void*> v;

}

26. Параметрический полиморфизм. Частичная специализация шаблонов в С++. Примеры.

template <class T, class Alg> class Vector{};

template <> class Vector<int, int> {}; // Полная специализация

template <class T> class Vector<T, int> {}; // Частичная специализация

int main()

{

Vector<char, bool> c;

Vector<int, int> I;

Vector<void*, int> v;

}


Дата добавления: 2018-04-15; просмотров: 1486; Мы поможем в написании вашей работы!

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






Мы поможем в написании ваших работ!