Автор работы: Пользователь скрыл имя, 07 Декабря 2013 в 07:54, реферат
В информатике, полиморфизм — свойство языка программирования, позволяющее единообразно обрабатывать данные разных типов. Существует несколько принципиально различных видов полиморфизма, два из которых были описаны Кристофером Стрэчи в 1967 г.
Если функция описывает разные реализации (возможно, с различным поведением) для ограниченного набора явно заданных типов и их комбинаций, это называется ситуативным полиморфизмом (ad hoc polimorphism). Ситуативный полиморфизм поддерживается во многих языках посредством перегрузки функций и методов.
Динамический полиморфизм
В информатике, полиморфизм — свойство языка программирования, позволяющее единообразно обрабатывать данные разных типов. Существует несколько принципиально различных видов полиморфизма, два из которых были описаны Кристофером Стрэчи в 1967 г.
Если функция описывает разные реализации (возможно, с различным поведением) для ограниченного набора явно заданных типов и их комбинаций, это называется ситуативным полиморфизмом (ad hoc polimorphism). Ситуативный полиморфизм поддерживается во многих языках посредством перегрузки функций и методов.
Если же код написан отвлеченно от
конкретного типа данных и потому может
свободно использоваться с любыми новыми
типами, имеет место параметрический полиморфизм. Джон
С.Рейнольдс, и независимо от него Жан-Ив
Жирар формально описали эту нотацию как
развитие лямбда-исчисления (названную
Полиморфизм является фундаментальным свойством системы типов. Обычно различают статически полиморфную семантику системы типов (потомки ML), неполиморфную семантику системы типов (потомки Алгола), и динамическую типизацию (в частности, утиную). Параметрический полиморфизм и динамическая типизация намного существеннее, чем ситуативный полиморфизм, повышают коэффициент повторного использования кода, поскольку определенная единственный раз функция реализует без дублирования заданное поведение для бесконечного множества вновь определяемых типов, удовлетворяющих требуемым в функции условиям.
Некоторые языки совмещают различные формы полиморфизма, порой сложным образом, что формирует самобытную идеологию в них и влияет на применяемые методологии декомпозиции задач. Например, в Smalltalk любой класс способен принять сообщения любого типа, и либо обработать его самостоятельно (в том числе посредством интроспекции), либо ретранслировать другому классу — таким образом, несмотря на широкое использование перегрузки функций, формально любая операция является неограниченно полиморфной и может применяться к данным любого типа.
В объектно-ориентированном
программировании полиморфизм подтипов (или полиморфизм
включения) представляет собой концепцию
в теории типов, предполагающую использование
единого имени (идентификатора) при обращении
к объектам нескольких разных классов,
при условии, что все они являются подклассами одного
общего надкласса (суперкласса)
Виды полиморфизма[править | пр
Параметрический полиморфизм[править | править исходный текст]
См. также: en:Parametric polymorphism
Параметрический полиморфизм позволяет определять функцию или тип данных обобщённо, чтобы значения могли обрабатываться идентично вне зависимости от их типа. Параметрический полиморфизм делает язык более выразительным, сохраняя полную статическуютипобезопасность.
Параметрический полиморфизм повсеместно используется в функциональном программировании, где он обычно обозначается просто как «полиморфизм». Следующий пример демонстрирует параметризованный тип «список» и две опредёленные на нём параметрически полиморфные функции:
data List a = Nil | Cons a (List a)
length :: List a -> Integer
length Nil = 0
length (Cons x xs) = 1 + length xs
map :: (a -> b) -> List a -> List b
map f Nil = Nil
map f (Cons x xs) = Cons (f x) (map f xs)
Параметрический полиморфизм
связывается с подтипизацией по
Концепция параметрического полиморфизма применима как к данным, так и к функциям. Функция, принимающая на входе или порождающая значения разных типов, называется полиморфной функцией. Тип данных, используемый как обобщённый (например, список элементов произвольного типа), называется полиморфным типом данных.
Параметрический полиморфизм
также доступен в некоторых императивных (в
частности, объектно-
struct segment { int start; int end; };
int seg_cmpr( struct segment *a, struct segment *b )
{ return abs( a->end - a->start ) - abs( b->end - b->start ); }
int str_cmpr( char **a, char **b )
{ return strcmp( *a, *b ); }
struct segment segs[] = { {2,5}, {4,3}, {9,3}, {6,8} };
char* strs[] = { "three", "one", "two", "five", "four" };
main()
{
qsort( strs, sizeof(strs)/sizeof(char*), sizeof(char*),
(int (*)(void*,void*))str_cmpr );
qsort( segs, sizeof(segs)/sizeof(struct segment), sizeof(struct segment),
(int (*)(void*,void*))seg_cmpr );
...
}
class List<T> {
class Node<T> {
T elem;
Node<T> next;
}
Node<T> head;
int length() { ... }
}
List map( Func<A,B> f, List<A> xs ) {
...
}
В первом случае одна функция обрабатывает массивы элементов любого типа, для которого определена функция сравнения. Во втором случае определение функции на уровне исходного кода по-прежнему единственно, но в результате компиляции порождается такое количество перегруженных мономорфных функций, с каким количеством типов данных функция вызывается в программе (параметрический полиморфизм на уровне исходного кода транслируется в ad hoc полиморфизм на уровне целевой платформы).
Параметрически полиморфная функция использует аргументы на основе поведения, а не значения, апеллируя лишь к необходимым ей свойствам аргументов, что делает её применимой в любом контексте, где тип объекта удовлетворяет заданным требованиям поведения. Таким образом, реализуется концепция параметричности.
Ситуативный (ad hoc) полиморфизм
Кристофер Стрэчи избрал термин «ситуативный полиморфизм» для описания полиморфных функций, вызываемых для аргументов разных типов, но реализующих различное поведение в зависимости от типа аргумента (называемых также перегруженными функциями иперегруженными операторами). Термин «ad hoc» (лат. спонтанный, сделанный под текущую задачу) в этом смысле не несёт уничижительного подтекста — он означает лишь, что этот вид полиморфизма не является фундаментальным свойством системы типов языка. В следующем примере функции Add выглядят как реализующие один и тот же функционал над разными типами, но компилятор определяет их как две совершенно разные функции.
program Adhoc;
function Add( x, y : Integer ) : Integer;
begin
Add := x + y
end;
function Add( s, t : String ) : String;
begin
Add := Concat( s, t )
end;
begin
Writeln(Add(1, 2));
Writeln(Add('Hello, ', 'World!'));
end.
В динамически типизируемых языках ситуация может быть более сложной, так как выбор требуемой функции для вызова может быть осуществлён только во время исполнения программы.
Ситуативный полиморфизм
иногда используется совместно с
параметрическим. В языке Haskell стремление
предоставить одновременно полиморфизм
и перегрузку привело к необходимости
введения принципиально нового понятия — классов
типов (которые, вопреки распространённому
заблуждению, не являются подмножеством объектно-
Полиморфизм подтипов
Некоторые языки представляют идею подтипизации для ограничения спектра типов, применимых в определённом частном случае параметрического полиморфизма. В этих языках полиморфизм подтипов (обычно называемый также динамическим полиморфизмом) позволяет функции, определённой на типе T, также корректно исполняться для аргументов, принадлежащих типу S, являющемуся подтипом T (в соответствии с принципом подстановки Барбары Лисков). Такое отношение типов обычно записывается как S <: T. При этом тип T называетсянадтипом (или супертипом) для S, что обозначается как T :> S.
Например, если имеются типы Number, Rational
Объектно-ориентированные языки программирования реализуют полиморфизм подтипов посредством наследования, то есть определения подклассов. В большинстве реализаций каждый класс содержит т. н. виртуальную таблицу — таблицу функций, реализующих полиморфную часть интерфейса класса — и каждый объект (экземпляр класса) содержит указатель на виртуальную таблицу своего класса, по согласованию с которой производится вызов полиморфного метода. Такой механизм используется в случаях:
позднего связывания, где виртуальные функции не связаны до момента вызова;
одиночной диспетчеризации (то есть одноаргументного полиморфизма), где виртуальные функции связываются посредством простого просмотра виртуальной таблицы по первому аргументу (данному экземпляру класса), так что динамические типы остальных аргументов не учитываются.
То же применимо и к
большинству остальных
В следующем примере коты и псы являются подтипами животных. Процедура определена для животных, но также будет работать корректно, получив на входе один из подтипов:
abstract class Animal {
abstract String talk();
}
class Cat extends Animal {
String talk() { return "Meow!"; }
}
class Dog extends Animal {
String talk() { return "Woof!"; }
}
public class MyClass {
public static void write(Animal a) {
System.out.println(a.talk());
}
public static void main(String args[]) {
write(new Cat());
write(new Dog());
}
}
Виртуальный деструктор в C++
В языке программирования C++ деструктор полиморфного базового класса должен объявляться виртуальным. Только так обеспечивается корректное разрушение объекта производного класса через указатель на соответствующий базовый класс.
Рассмотрим следующий пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <iostream>
using namespace std;
// Вспомогательный класс class Object { public: Object() { cout << "Object::ctor()" << endl; } ~Object() { cout << "Object::dtor()" << endl; } };
// Базовый класс class Base { public: Base() { cout << "Base::ctor()" << endl; } virtual ~Base() { cout << "Base::dtor()" << endl; } virtual void print() = 0; };
// Производный класс class Derived: public Base { public: Derived() { cout << "Derived::ctor()" << endl; } ~Derived() { cout << "Derived::dtor()" << endl; } void print() {} Object obj; };
int main () { Base * p = new Derived; delete p; return 0; } |
В функции main указателю на базовый класс присваивается адрес динамически создаваемого объекта производного класса Derived. Затем через этот указатель объект разрушается. При этом наличие виртуального деструктора базового класса обеспечивает вызовы деструкторов всех классов в ожидаемом порядке, а именно, в порядке, обратном вызовам конструкторов соответствующих классов.
Вывод программы с использованием виртуального деструктора в базовом классе будет следующим:
1 2 3 4 5 6 |
Base::ctor() Object::ctor() Derived::ctor() Derived::dtor() Object::dtor() Base::dtor() |
Уничтожение объекта производного класса через указатель на базовый класс с невиртуальным деструктором дает неопределенный результат. На практике это выражается в том, что будет разрушена только часть объекта, соответствующая базовому классу. Если в коде выше убрать ключевое слово virtual перед деструктором базового класса, то вывод программы будет уже иным. Обратите внимание, что член данных obj класса Derived также не разрушается.
1 2 3 4 |
Base::ctor() Object::ctor() Derived::ctor() Base::dtor()
Чисто виртуальные функции и абстрактные типы |
Когда же следует объявлять деструктор виртуальным? Cуществует правило - если базовый класс предназначен для полиморфного использования, то его деструктор должен объявляться виртуальным. Для реализации механизма виртуальных функций каждый объект класса хранит указатель на таблицу виртуальных функций vptr, что увеличивает его общий размер. Обычно, при объявлении виртуального деструктора такой класс уже имеет виртуальные функции, и увеличения размера соответствующего объекта не происходит.
Если же базовый класс не предназначен для полиморфного использования (не содержит виртуальных функций), то его деструктор не должен объявляться виртуальным.
Когда виртуальная функция не переопределена в производном классе, то при вызове ее в объекте производного класса вызывается версия из базового класса. Однако во многих случаях невозможно ввести содержательное определение виртуальной функции в базовом классе. Например, при объявлении базового класса figure в предыдущем примере определение функции show_area() не несет никакого содержания. Она не вычисляет и не выводит на экран площадь объекта какого- либо типа. Имеется два способа действий в подобной ситуации. Первый, подобно предыдущему примеру, заключается в выводе какого-нибудь предупреждающего сообщения. Хотя такой подход полезен в некоторых ситуациях, он не является универсальным. Могут быть такие виртуальные функции, которые обязательно должны быть определены в производных классах, без чего эти производные классы не будут иметь никакого значения. Например, класс triangle бесполезен, если не определена функция show_агеа(). В таких случаях необходим метод, гарантирующий, что производные классы действительно определят все необходимые функции. Язык С++ предлагает в качестве решения этой проблемы чисто виртуальные функции.