AFAIK:
C ++ udostępnia trzy różne typy polimorfizmu.
- Funkcje wirtualne
- Przeciążanie nazw funkcji
- Przeciążanie operatorów
Oprócz powyższych trzech typów polimorfizmu istnieją inne rodzaje polimorfizmu:
- w czasie wykonywania
- czas kompilacji
- polimorfizm ad hoc
- parametryczny polimorfizm
Wiem, że polimorfizm w czasie wykonywania można osiągnąć za pomocą funkcji wirtualnych, a polimorfizm statyczny można osiągnąć za pomocą funkcji szablonowych
Ale dla pozostałych dwóch
- polimorfizm ad hoc
- parametryczne Polimorfizm strona mówi ,
polimorfizm ad hoc:
Jeśli zakres rzeczywistych typów, które mogą być użyte, jest ograniczony, a kombinacje muszą być indywidualnie określone przed użyciem, nazywa się to polimorfizmem ad hoc.
polimorfizm parametryczny:
Jeśli cały kod jest napisany bez wzmianki o jakimkolwiek konkretnym typie, a zatem może być używany w sposób przezroczysty z dowolną liczbą nowych typów, nazywa się to polimorfizmem parametrycznym.
Ledwo ich rozumiem :(
Czy ktoś może wyjaśnić je obie, jeśli to możliwe, na przykładzie? Mam nadzieję, że odpowiedzi na te pytania byłyby pomocne dla wielu nowych pasjonatów z ich uczelni.
źródło
Odpowiedzi:
Zrozumienie / wymagania dotyczące polimorfizmu
Aby zrozumieć polimorfizm - jak termin jest używany w informatyce - warto zacząć od prostego testu i zdefiniowania go. Rozważać:
Tutaj
f()
ma wykonać jakąś operację i otrzymuje wartościx
iy
jako dane wejściowe.Mechanizmy C ++ dla polimorfizmu
Jawny polimorfizm określony przez programistę
Możesz napisać w
f()
taki sposób, aby działał na wielu typach w dowolny z następujących sposobów:Przetwarzanie wstępne:
#define f(X) ((X) += 2) // (note: in real code, use a longer uppercase name for a macro!)
Przeciążenie:
void f(int& x) { x += 2; } void f(double& x) { x += 2; }
Szablony:
template <typename T> void f(T& x) { x += 2; }
Wirtualna wysyłka:
struct Base { virtual Base& operator+=(int) = 0; }; struct X : Base { X(int n) : n_(n) { } X& operator+=(int n) { n_ += n; return *this; } int n_; }; struct Y : Base { Y(double n) : n_(n) { } Y& operator+=(int n) { n_ += n; return *this; } double n_; }; void f(Base& x) { x += 2; } // run-time polymorphic dispatch
Inne powiązane mechanizmy
Polimorfizm dostarczony przez kompilator dla typów wbudowanych, standardowych konwersji i rzutowania / koercji są omówione później dla kompletności jako:
Terminologia
Dalsza kategoryzacja
Biorąc pod uwagę powyższe mechanizmy polimorficzne, możemy je kategoryzować na różne sposoby:
Kiedy wybierany jest kod specyficzny dla typu polimorficznego?
f
powyżej tylko zint
argumentami - w zależności od zastosowanego mechanizmu polimorficznego i opcji wbudowanych kompilator może uniknąć generowania jakiegokolwiek koduf(double)
lub wygenerowany kod może zostać wyrzucony w pewnym momencie kompilacji lub linkowania. ( wszystkie powyższe mechanizmy z wyjątkiem wirtualnej wysyłki )Jakie typy są obsługiwane?
Co oznacza parametryczne , możesz po prostu spróbować użyć funkcji dla różnych typów parametrów, nie robiąc nic, aby umożliwić jej obsługę (np. Szablony, makra). Obiekt z funkcjami / operatorami, które działają tak, jak szablon / makro, którego oczekuje od 1, to wszystko, czego szablon / makro potrzebuje do wykonania swojej pracy, przy czym dokładny typ nie ma znaczenia. „Koncepcje” wprowadzone przez C ++ 20 wyrażają i wymuszają takie oczekiwania - zobacz stronę cppreference tutaj .
Polimorfizm parametryczny zapewnia kaczkę na klawiaturze - koncepcję przypisywaną Jamesowi Whitcombowi Rileyowi, który najwyraźniej powiedział: „Kiedy widzę ptaka, który chodzi jak kaczka i pływa jak kaczka i kwacze jak kaczka, nazywam tego ptaka kaczką”. .
template <typename Duck> void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); } do_ducky_stuff(Vilified_Cygnet());
Polimorfizm podtypów (inaczej włączający) umożliwia pracę nad nowymi typami bez aktualizacji algorytmu / funkcji, ale muszą one pochodzić z tej samej klasy bazowej (wirtualna wysyłka)
1 - Szablony są niezwykle elastyczne. SFINAE (zobacz także
std::enable_if
) skutecznie dopuszcza kilka zestawów oczekiwań dla polimorfizmu parametrycznego. Na przykład możesz zakodować, że gdy typ przetwarzanych danych ma element.size()
członkowski, użyjesz jednej funkcji, w przeciwnym razie inna funkcja, która nie potrzebuje.size()
(ale prawdopodobnie w jakiś sposób cierpi - np. Używając wolniejszegostrlen()
lub nie drukującego jako przydatna wiadomość w dzienniku). Możesz również określić zachowania ad hoc, gdy szablon jest tworzony z określonymi parametrami, pozostawiając niektóre parametry parametryczne ( częściowa specjalizacja szablonu ) lub nie ( pełna specjalizacja )."Polimorficzny"
Alf Steinbach komentuje, że w C ++ Standard polimorfizm odnosi się tylko do polimorfizmu w czasie wykonywania przy użyciu wirtualnej wysyłki. Ogólne Comp. Sci. znaczenie jest bardziej inkluzywne, zgodnie z glosariuszem twórcy C ++ Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):
Ta odpowiedź - podobnie jak pytanie - wiąże funkcje C ++ z plikiem Comp. Sci. terminologia.
Dyskusja
Ze standardem C ++ używającym węższej definicji „polimorfizmu” niż w przypadku Comp. Sci. społeczność, w celu zapewnienia wzajemnego zrozumienia dla Twojego rozważyć publiczność ...
Jednak kluczem do bycia świetnym programistą C ++ jest zrozumienie, co naprawdę robi dla Ciebie polimorfizm ...
pozwalając na jednorazowe napisanie „algorytmicznego” kodu, a następnie zastosowanie go do wielu typów danych
... a następnie bądź bardzo świadomy tego, jak różne mechanizmy polimorficzne odpowiadają Twoim rzeczywistym potrzebom.
Polimorfizm w czasie wykonywania:
Base*
s,Kiedy nie ma jasnego sterownika dla polimorfizmu w czasie wykonywania, często preferowane są opcje kompilacji. Rozważać:
__FILE__
,__LINE__
, String literal konkatenacji i inne unikalne możliwości makr (które pozostają złe ;-))Inne mechanizmy wspierające polimorfizm
Zgodnie z obietnicą, w celu zapewnienia kompletności omówiono kilka dodatkowych tematów:
Ta odpowiedź kończy się dyskusją na temat tego, jak powyższe łączą się, aby wzmocnić i uprościć kod polimorficzny - zwłaszcza polimorfizm parametryczny (szablony i makra).
Mechanizmy mapowania do operacji specyficznych dla typu
> Niejawne przeciążenia dostarczone przez kompilator
Koncepcyjnie kompilator przeciąża wiele operatorów dla typów wbudowanych. Koncepcyjnie nie różni się od przeciążenia określonego przez użytkownika, ale jest wymieniony, ponieważ łatwo go przeoczyć. Na przykład możesz dodać do
int
s idouble
s używając tej samej notacji,x += 2
a kompilator wygeneruje:Przeciążanie, a następnie płynnie obejmuje typy zdefiniowane przez użytkownika:
std::string x; int y = 0; x += 'c'; y += 'c';
Dostarczane przez kompilator przeciążenia dla typów podstawowych są powszechne w językach komputerowych wysokiego poziomu (3GL +), a jawna dyskusja na temat polimorfizmu generalnie oznacza coś więcej. (2GL - języki asemblera - często wymagają od programisty jawnego użycia różnych mnemoników dla różnych typów).
> Konwersje standardowe
Czwarta sekcja standardu C ++ opisuje konwersje standardowe.
Pierwszy punkt podsumowuje ładnie (ze starego projektu - miejmy nadzieję, że nadal merytorycznie poprawny):
Zero lub jedna konwersja z następującego zestawu: konwersja l-wartości do r-wartości, konwersja tablicy do wskaźnika i konwersja funkcji do wskaźnika.
Zero lub jedna konwersja z następującego zestawu: promocje integralne, promocja zmiennoprzecinkowa, konwersje integralne, konwersje zmiennoprzecinkowe, konwersje zmiennoprzecinkowe, konwersje wskaźnika, konwersje wskaźnika na składowe i konwersje logiczne.
Zero lub jedna konwersja kwalifikacji.
Te konwersje zezwalają na kod taki jak:
double a(double x) { return x + 2; } a(3.14); a(42);
Zastosowanie wcześniejszego testu:
a()
sam uruchamia kod specjalnie dladouble
i dlatego nie jest polimorficzny.Ale w drugim wywołaniu
a()
kompilatora wie wygenerować kod typu, właściwe dla „zmiennoprzecinkowej promocji” (standard §4) do przekształcania42
się42.0
. Ten dodatkowy kod znajduje się w funkcji wywołującej . W podsumowaniu omówimy znaczenie tego.> Wymuszanie, rzutowanie, niejawne konstruktory
Te mechanizmy umożliwiają klasom zdefiniowanym przez użytkownika określanie zachowań podobnych do standardowych konwersji typów wbudowanych. Spójrzmy:
int a, b; if (std::cin >> a >> b) f(a, b);
Tutaj obiekt
std::cin
jest oceniany w kontekście logicznym, przy pomocy operatora konwersji. Można to koncepcyjnie zgrupować z „integralnymi promocjami” i innymi ze Standardowych konwersji w powyższym temacie.Niejawne konstruktory skutecznie robią to samo, ale są kontrolowane przez typ rzutowania:
f(const std::string& x); f("hello"); // invokes `std::string::string(const char*)`
Implikacje przeciążeń, konwersji i wymuszania dostarczonych przez kompilator
Rozważać:
void f() { typedef int Amount; Amount x = 13; x /= 2; std::cout << x * 1.1; }
Jeśli chcemy ilość
x
należy traktować jako liczbę rzeczywistą podczas podziału (tj wynosić 6,5 zamiast zaokrąglona w dół do 6), my tylko potrzebujemy zmianytypedef double Amount
.To fajne, ale nie byłoby zbyt wiele pracy, aby kod wyraźnie „poprawiał typ”:
void f() void f() { { typedef int Amount; typedef double Amount; Amount x = 13; Amount x = 13.0; x /= 2; x /= 2.0; std::cout << double(x) * 1.1; std::cout << x * 1.1; } }
Ale weźmy pod uwagę, że możemy przekształcić pierwszą wersję w
template
:template <typename Amount> void f() { Amount x = 13; x /= 2; std::cout << x * 1.1; }
To dzięki tym małym „wygodnym funkcjom” można tak łatwo utworzyć instancję dla jednej
int
lub drugiejdouble
i działać zgodnie z przeznaczeniem. Bez tych funkcji potrzebowalibyśmy jawnych rzutowań, cech typu i / lub klas zasad, a także jakiegoś rozwlekłego, podatnego na błędy bałaganu, takiego jak:template <typename Amount, typename Policy> void f() { Amount x = Policy::thirteen; x /= static_cast<Amount>(2); std::cout << traits<Amount>::to_double(x) * 1.1; }
Tak więc przeciążanie operatorów dostarczone przez kompilator dla typów wbudowanych, konwersje standardowe, rzutowanie / wymuszanie / niejawne konstruktory - wszystkie one zapewniają subtelną obsługę polimorfizmu. Z definicji u góry tej odpowiedzi odnoszą się do „znajdowania i wykonywania kodu odpowiedniego dla typu” poprzez mapowanie:
„z dala” od typów parametrów
z wielu typów danych, które obsługuje polimorficzny kod algorytmiczny
do kodu napisanego dla (potencjalnie mniejszej) liczby (takich samych lub innych) typów.
„na” typy parametryczne z wartości typu stałego
Oni nie ustanawiają konteksty polimorficznych przez siebie, ale nie pomagają Empower / kod uprościć wewnątrz takich kontekstach.
Możesz czuć się oszukany ... to nie wydaje się dużo. Istotne jest to, że w parametrycznych kontekstach polimorficznych (tj. Wewnątrz szablonów lub makr) staramy się obsługiwać dowolnie duży zakres typów, ale często chcemy wyrazić na nich operacje w kategoriach innych funkcji, literałów i operacji, które zostały zaprojektowane dla mały zestaw typów. Zmniejsza potrzebę tworzenia prawie identycznych funkcji lub danych na podstawie typu, gdy operacja / wartość jest logicznie taka sama. Funkcje te współpracują ze sobą, aby dodać podejście „najlepszego wysiłku”, robiąc to, czego intuicyjnie oczekiwano, wykorzystując ograniczone dostępne funkcje i dane i zatrzymując się z błędem tylko wtedy, gdy pojawia się prawdziwa niejasność.
Pomaga to ograniczyć potrzebę stosowania kodu polimorficznego obsługującego kod polimorficzny, tworząc ściślejszą sieć wokół stosowania polimorfizmu, aby lokalne użycie nie wymuszało powszechnego stosowania, a także udostępnianie korzyści z polimorfizmu w razie potrzeby bez nakładania kosztów związanych z koniecznością ujawniania implementacji czasu kompilacji, mają wiele kopii tej samej funkcji logicznej w kodzie obiektowym do obsługi używanych typów i wykonywania wirtualnego wysyłania w przeciwieństwie do wywołań wewnętrznych lub przynajmniej rozstrzyganych w czasie kompilacji. Jak to jest typowe w C ++, programista ma dużą swobodę kontrolowania granic, w ramach których używany jest polimorfizm.
źródło
dynamic_cast
wymaga „wskaźnika do lub lwartości typu polimorficznego”. Pozdrawiam i hth.,W C ++ ważną różnicą jest powiązanie w czasie wykonywania i w czasie kompilacji. Ad-hoc vs. parametrric tak naprawdę nie pomaga, co wyjaśnię później.
|----------------------+--------------| | Form | Resolved at | |----------------------+--------------| | function overloading | compile-time | | operator overloading | compile-time | | templates | compile-time | | virtual methods | run-time | |----------------------+--------------|
Uwaga - polimorfizm w czasie wykonywania może nadal zostać rozwiązany w czasie kompilacji, ale to tylko optymalizacja. Potrzeba wydajnego wspierania rozwiązywania problemów w czasie wykonywania i wymiany z innymi problemami jest częścią tego, co doprowadziło do tego, że funkcje wirtualne są tym, czym są. I to jest naprawdę kluczowe dla wszystkich form polimorfizmu w C ++ - każda wynika z innego zestawu kompromisów dokonanych w innym kontekście.
Przeciążanie funkcji i operatora to to samo pod każdym względem. Nazwy i składnia ich używania nie wpływają na polimorfizm.
Szablony umożliwiają jednoczesne określenie wielu przeciążeń funkcji.
Jest inny zestaw nazw dla tego samego pomysłu na czas rozwiązania ...
Nazwy te są bardziej powiązane z OOP, więc trochę dziwne jest stwierdzenie, że szablon lub inna funkcja niebędąca składnikiem używa wczesnego wiązania.
Aby lepiej zrozumieć związek między funkcjami wirtualnymi a przeciążeniem funkcji, warto również zrozumieć różnicę między „wysyłką pojedynczą” a „wysyłką wielokrotną”. Pomysł można rozumieć jako postęp ...
Oczywiście OOP jest czymś więcej niż wymówką, by wyznaczyć jeden parametr jako specjalny, ale to tylko jedna z części. Wracając do tego, co powiedziałem o kompromisach - pojedyncza wysyłka jest dość łatwa do wykonania i wydajnie (zwykle implementacja nazywa się „wirtualnymi tabelami”). Wysyłanie wielokrotne jest bardziej niewygodne, nie tylko pod względem wydajności, ale także w przypadku osobnej kompilacji. Jeśli jesteś ciekawy, możesz sprawdzić „problem z wyrażeniem”.
Tak jak trochę dziwne jest używanie terminu „wczesne wiązanie” dla funkcji niebędących składnikami, trochę dziwne jest używanie terminów „pojedyncze wysłanie” i „wielokrotne wysłanie”, gdzie polimorfizm jest rozwiązywany w czasie kompilacji. Zwykle uważa się, że C ++ nie ma wielu wysyłek, co jest uważane za szczególny rodzaj rozwiązania w czasie wykonywania. Jednak przeciążenie funkcji może być postrzegane jako wielokrotne wysyłanie wykonywane w czasie kompilacji.
Wracając do polimorfizmu parametrycznego i ad-hoc, terminy te są bardziej popularne w programowaniu funkcjonalnym i nie do końca działają w C ++. Nawet jeśli...
Polimorfizm parametryczny oznacza, że masz typy jako parametry i używany jest dokładnie ten sam kod, niezależnie od typu użytego dla tych parametrów.
Polimorfizm ad-hoc jest ad-hoc w tym sensie, że podajesz inny kod w zależności od poszczególnych typów.
Funkcje przeciążeniowe i wirtualne są przykładami polimorfizmu ad-hoc.
Znowu jest kilka synonimów ...
Z wyjątkiem tego, że nie są to całkiem synonimy, chociaż są powszechnie traktowane tak, jakby były, i właśnie tam prawdopodobnie pojawi się zamieszanie w C ++.
Powodem traktowania ich jako synonimów jest to, że ograniczając polimorfizm do określonych klas typów, staje się możliwe użycie operacji specyficznych dla tych klas typów. Słowo „klasy” może być tutaj interpretowane w sensie OOP, ale tak naprawdę odnosi się tylko do (zwykle nazwanych) zestawów typów, które współużytkują pewne operacje.
Tak więc polimorfizm parametryczny jest zwykle przyjmowany (przynajmniej domyślnie), aby implikować polimorfizm nieograniczony. Ponieważ ten sam kod jest używany niezależnie od parametrów typu, jedynymi obsługiwanymi operacjami są te, które działają dla wszystkich typów. Pozostawiając zestaw typów bez ograniczeń, poważnie ograniczasz zestaw operacji, które możesz zastosować do tych typów.
Na przykład w Haskell możesz mieć ...
myfunc1 :: Bool -> a -> a -> a myfunc1 c x y = if c then x else y
a
Tutaj jest niewymuszony polimorficzny typu. Może to być wszystko, więc niewiele możemy zrobić z wartościami tego typu.myfunc2 :: Num a => a -> a myfunc2 x = x + 3
Tutaj
a
jest ograniczony do bycia członkiemNum
klasy - typy, które zachowują się jak liczby. To ograniczenie pozwala ci robić rzeczy numeryczne z tymi wartościami, na przykład je dodawać. Nawet z3
wnioskowania o typie polimorficznym wynika, że masz na myśli3
typa
.Myślę o tym jako o ograniczonym polimorfizmie parametrycznym. Jest tylko jedna implementacja, ale można ją zastosować tylko w ograniczonych przypadkach. Aspekt doraźny to wybór
+
i3
wykorzystanie. Każda „instancja”Num
ma swoją własną odrębną implementację. Więc nawet w Haskell „parametryczne” i „nieograniczone” nie są tak naprawdę synonimami - nie wiń mnie, to nie moja wina!W C ++ zarówno przeciążanie, jak i funkcje wirtualne są polimorfizmem ad-hoc. Definicja polimorfizmu ad-hoc nie ma znaczenia, czy implementacja jest wybierana w czasie wykonywania, czy w czasie kompilacji.
C ++ zbliża się do polimorfizmu parametrycznego z szablonami, jeśli każdy parametr szablonu ma typ
typename
. Istnieją parametry typu i istnieje jedna implementacja bez względu na używane typy. Jednak reguła „Niepowodzenie podstawienia nie jest błędem” oznacza, że niejawne ograniczenia powstają w wyniku użycia operacji w szablonie. Dodatkowe komplikacje obejmują specjalizację szablonów w zakresie udostępniania szablonów alternatywnych - różne (ad-hoc) implementacje.Więc w pewnym sensie C ++ ma polimorfizm parametryczny, ale jest on niejawnie ograniczony i może być zastąpiony przez alternatywy ad-hoc - tj. Ta klasyfikacja tak naprawdę nie działa dla C ++.
źródło
a
oto nieskrępowany typ polimorficzny [...] więc niewiele możemy zrobić z wartościami tego typu”. był interesujący - w C ++ sans Concepts nie jesteś ograniczony tylko do próby wykonania określonego zestawu operacji na argumencie typu określonego jako parametr szablonu ... biblioteki takie jak koncepcje boost działają w drugą stronę - upewniając się, że typ obsługuje operacje określasz, zamiast chronić się przed przypadkowym użyciem dodatkowych operacji.Jeśli chodzi o polimorfizm ad-hoc, oznacza to przeciążanie funkcji lub przeciążanie operatorów. Sprawdź tutaj:
http://en.wikipedia.org/wiki/Ad-hoc_polymorphism
Jeśli chodzi o polimorfizm parametryczny, funkcje szablonowe również mogą być uwzględnione, ponieważ niekoniecznie przyjmują parametry typów FIXED. Na przykład jedna funkcja może sortować tablicę liczb całkowitych, a także może sortować tablicę ciągów itd.
http://en.wikipedia.org/wiki/Parametric_polymorphism
źródło
<
operatory i podobne). W Haskell wyraziłbyś to wymaganie jawnie za pomocą klasyOrd
. Fakt, że otrzymujesz różne w<
zależności od konkretnego typu (dostarczonego przez instancjęOrd
), byłby uważany za polimorfizm ad hoc.To może nie być pomocne, ale zrobiłem to, aby wprowadzić moich przyjaciół w programowanie, przekazując określone funkcje, takie jak
START
, iEND
dla funkcji głównej, więc nie było to zbyt trudne (używali tylko pliku main.cpp ). Zawiera klasy i struktury polimorficzne, szablony, wektory, tablice, dyrektywy preprocesora, przyjaźń, operatory i wskaźniki (z których wszystkie prawdopodobnie powinieneś wiedzieć przed przystąpieniem do polimorfizmu):Uwaga: to jeszcze nie koniec, ale możesz mieć pomysł
main.cpp
#include "main.h" #define ON_ERROR_CLEAR_SCREEN false START Library MyLibrary; Book MyBook("My Book", "Me"); MyBook.Summarize(); MyBook += "Hello World"; MyBook += "HI"; MyBook.EditAuthor("Joe"); MyBook.EditName("Hello Book"); MyBook.Summarize(); FixedBookCollection<FairyTale> FBooks("Fairytale Books"); FairyTale MyTale("Tale", "Joe"); FBooks += MyTale; BookCollection E("E"); MyLibrary += E; MyLibrary += FBooks; MyLibrary.Summarize(); MyLibrary -= FBooks; MyLibrary.Summarize(); FixedSizeBookCollection<5> Collection("My Fixed Size Collection"); /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook); /* Extension Work */ Duplicate->Summarize(); END
main.h
#include <iostream> #include <sstream> #include <vector> #include <string> #include <type_traits> #include <array> #ifndef __cplusplus #error Not C++ #endif #define START int main(void)try{ #define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);} #define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar(); class Book; class Library; typedef std::vector<const Book*> Books; bool sContains(const std::string s, const char c){ return (s.find(c) != std::string::npos); } bool approve(std::string s){ return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~')); } template <class C> bool isBook(){ return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>()); } template<class ClassToDuplicate> class DuplicatableClass{ public: ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){ return new ClassToDuplicate(ToDuplicate); } }; class Book : private DuplicatableClass<Book>{ friend class Library; friend struct BookCollection; public: Book(const char* Name, const char* Author) : name_(Name), author_(Author){} void operator+=(const char* Page){ pages_.push_back(Page); } void EditAuthor(const char* AuthorName){ if(approve(AuthorName)){ author_ = AuthorName; } else{ std::ostringstream errorMessage; errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved"; throw std::exception(errorMessage.str().c_str()); } } void EditName(const char* Name){ if(approve(Name)){ name_ = Name; } else{ std::ostringstream errorMessage; errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved"; throw std::exception(errorMessage.str().c_str()); } } virtual void Summarize(){ std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains " << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl; if(pages_.size() > 0){ ListPages(std::cout); } } private: std::vector<const char*> pages_; const char* name_; const char* author_; void ListPages(std::ostream& output){ for(int i = 0; i < pages_.size(); ++i){ output << pages_[i] << std::endl; } } }; class FairyTale : public Book{ public: FairyTale(const char* Name, const char* Author) : Book(Name, Author){} }; struct BookCollection{ friend class Library; BookCollection(const char* Name) : name_(Name){} virtual void operator+=(const Book& Book)try{ Collection.push_back(&Book); }catch(const std::exception& e){ std::ostringstream errorMessage; errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3); throw std::exception(errorMessage.str().c_str()); } virtual void operator-=(const Book& Book){ for(int i = 0; i < Collection.size(); ++i){ if(Collection[i] == &Book){ Collection.erase(Collection.begin() + i); return; } } std::ostringstream errorMessage; errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased"; throw std::exception(errorMessage.str().c_str()); } private: const char* name_; Books Collection; }; template<class FixedType> struct FixedBookCollection : public BookCollection{ FixedBookCollection(const char* Name) : BookCollection(Name){ if(!isBook<FixedType>()){ std::ostringstream errorMessage; errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection"; throw std::exception(errorMessage.str().c_str()); delete this; } } void operator+=(const FixedType& Book)try{ Collection.push_back(&Book); }catch(const std::exception& e){ std::ostringstream errorMessage; errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3); throw std::exception(errorMessage.str().c_str()); } void operator-=(const FixedType& Book){ for(int i = 0; i < Collection.size(); ++i){ if(Collection[i] == &Book){ Collection.erase(Collection.begin() + i); return; } } std::ostringstream errorMessage; errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased"; throw std::exception(errorMessage.str().c_str()); } private: std::vector<const FixedType*> Collection; }; template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{ FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } } void operator+=(const Book& Book)try{ if(currentPos + 1 > Size){ std::ostringstream errorMessage; errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled"; throw std::exception(errorMessage.str().c_str()); } this->at(currentPos++) = &Book; }catch(const std::exception& e){ std::ostringstream errorMessage; errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3); throw std::exception(errorMessage.str().c_str()); } private: const char* name_; int currentPos; }; class Library : private std::vector<const BookCollection*>{ public: void operator+=(const BookCollection& Collection){ for(int i = 0; i < size(); ++i){ if((*this)[i] == &Collection){ std::ostringstream errorMessage; errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added"; throw std::exception(errorMessage.str().c_str()); } } push_back(&Collection); } void operator-=(const BookCollection& Collection){ for(int i = 0; i < size(); ++i){ if((*this)[i] == &Collection){ erase(begin() + i); return; } } std::ostringstream errorMessage; errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased"; throw std::exception(errorMessage.str().c_str()); } Book* DuplicateBook(Book* Book)const{ return (Book->Duplicate(*Book)); } void Summarize(){ std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl; if(size() > 0){ for(int i = 0; i < size(); ++i){ std::cout << (*this)[i]->name_ << std::endl; } } } };
źródło
Oto podstawowy przykład użycia klas polimorficznych
#include <iostream> class Animal{ public: Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/ virtual void Speak(){ std::cout << "I am an animal called " << name_ << std::endl; } const char* name_; }; class Dog : public Animal{ public: Dog(const char* Name) : Animal(Name) {/*...*/} void Speak(){ std::cout << "I am a dog called " << name_ << std::endl; } }; int main(void){ Animal Bob("Bob"); Dog Steve("Steve"); Bob.Speak(); Steve.Speak(); //return (0); }
źródło
Polimorfizm oznacza wiele form jako takich, jest używany dla operatora, aby działał inaczej w różnych przypadkach. Do implementacji dziedziczenia używany jest polimorfizm. Na przykład zdefiniowaliśmy funkcję fn draw () dla kształtu klasy, a następnie rysowanie fn można zaimplementować do rysowania okręgu, ramki, trójkąta i innych kształtów. (które są obiektami o kształcie klasy)
źródło
Jeśli ktoś powie CUT tym ludziom
Co się stanie?
The Surgeon would begin to make an incision. The Hair Stylist would begin to cut someone's hair. The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.
Tak więc powyższa reprezentacja pokazuje, czym jest polimorfizm (ta sama nazwa, inne zachowanie) w OOP.
Jeśli idziesz na rozmowę kwalifikacyjną, a prowadzący rozmowę poprosi Cię o podanie / pokazanie na żywo przykładu polimorfizmu w tym samym pokoju, w którym siedzimy, powiedz:
Odpowiedź - drzwi / okna
Zastanawiasz się jak?
Przez drzwi / okno - może przyjść człowiek, powietrze, światło, deszcz itp.
tj. Jedna forma innego zachowania (polimorfizm).
Aby lepiej to zrozumieć iw prosty sposób posłużyłem się powyższym przykładem. Jeśli potrzebujesz odniesienia do kodu, postępuj zgodnie z powyższymi odpowiedziami.
źródło