Le polymorphisme en c++
autant que je sache:
C++ fournit trois types différents de polymorphisme.
- fonctions virtuelles
- overloading de nom de fonction
- surcharge de L'opérateur
en plus des trois types de polymorphisme ci-dessus, il existe d'autres types de polymorphisme:
- run-time
- compilation-time
- polymorphisme ad hoc
- polymorphisme paramétrique
je sais que polymorphisme d'exécution peut être atteint par les fonctions virtuelles et polymorphisme statique peut être réalisé par les fonctions de modèle
mais pour les deux autres
- polymorphisme ad hoc
- polymorphisme paramétrique le site dit ,
polymorphisme ad hoc:
si la gamme des types réels pouvant être utilisés est limitée et que les combinaisons doivent être spécifiées individuellement avant l'utilisation, on parle de polymorphisme ad hoc.
polymorphisme paramétrique:
si tout le code est écrit sans mention de n'importe quel type spécifique et peut donc être utilisé de manière transparente avec n'importe quel nombre de nouveaux types, il est appelé polymorphisme paramétrique.
je peux à peine les comprendre: (
est-ce que quelqu'un peut expliquer les deux si possible avec un exemple? J'espère que les réponses à ces questions seront utiles pour de nombreux nouveaux élèves de leurs collèges.
7 réponses
la Compréhension de l' / exigences pour le polymorphisme
pour comprendre le polymorphisme - comme le terme est utilisé dans la science de L'informatique - il aide à partir d'un test simple pour et la définition de celui-ci. Considérons:
Type1 x;
Type2 y;
f(x);
f(y);
ici, f()
est d'effectuer une certaine opération et est donné des valeurs x
et y
comme entrées.
pour montrer un polymorphisme,
f()
doit être capable de fonctionner avec des valeurs d'au moins deux types distincts (par exempleint
etdouble
), en trouvant et en exécutant un code type distinct approprié.
C++ mécanismes pour le polymorphisme
polymorphisme spécifié par programmeur explicite
Vous pouvez écrire f()
, de sorte qu'il peut fonctionner sur plusieurs types de l'une des façons suivantes:
-
prétraitement:
#define f(X) ((X) += 2) // (note: in real code, use a longer uppercase name for a macro!)
-
Surcharge:
void f(int& x) { x += 2; } void f(double& x) { x += 2; }
- "1519500920 les Modèles":
template <typename T> void f(T& x) { x += 2; }
-
Virtuel dépêche:
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
autres mécanismes connexes
polymorphisme fourni par le compilateur pour le les types, les conversions Standard et la coulée / coercition sont discutés plus loin comme suit:
- ils sont généralement intuitivement compris de toute façon (justifiant un " oh, que "réaction),
- ils ont un impact sur le seuil, en exigeant, et la continuité dans l'utilisation des mécanismes ci-dessus, et
- l'explication est une distraction fiddly des concepts plus importants.
terminologie
nouvelle catégorisation
étant donné les mécanismes polymorphiques ci-dessus, nous pouvons les classer de diverses façons:
-
quand le code polymorphique spécifique au type est-il sélectionné?
- Run time signifie que le compilateur doit générer du code pour tous les types que le programme pourrait manipuler pendant l'exécution, et à l'exécution le code correct est sélectionné ( "1519920920 virtuelle" envoi "de 1519930920" )
- temps de compilation signifie que le choix du code spécifique au type est effectué pendant la compilation. Une conséquence de ceci: disons un programme appelé
f
ci-dessus avec des argumentsint
- selon le mécanisme polymorphique utilisé et les choix de la chaîne le compilateur pourrait éviter de générer n'importe quel code pourf(double)
, ou le code généré pourrait être jeté à un certain point dans la compilation ou de la liaison. ( tous les mécanismes ci-dessus sauf la régulation virtuelle )
-
Quels types sont pris en charge?
- Ad-hoc ce qui signifie que vous fournissez un code explicite pour soutenir chaque type (par exemple, la surcharge, la spécialisation de modèle); vous ajoutez explicitement le soutien "pour ceci "(selon ad hoc de sens), certains autres "ce", et peut-être "que" de trop ;-).
-
paramétrique ce qui signifie que vous pouvez simplement essayer d'utiliser la fonction pour différents types de paramètres sans faire spécifiquement quoi que ce soit pour permettre son soutien pour eux (par exemple gabarits, macros). Un objet avec des fonctions ou des opérateurs qui agissent comme le modèle/macro attend 1 est tout ce qui template/macro besoins pour faire son travail, le type exact n'étant pas pertinent. Les "concepts" coupés de C++11 aident à exprimer et à imposer de telles attentes - espérons qu'ils en feront une norme ultérieure.
-
parametric polymorphism provides duck typing - un concept attribué à James Whitcomb Riley qui a apparemment dit " quand je vois un oiseau qui marche comme un canard et nage comme un canard et raille comme un canard, j'appelle cet oiseau canard." .
template <typename Duck> void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); } do_ducky_stuff(Vilified_Cygnet());
-
-
sous-Type (aka inclusion) polymorphisme permet de travailler sur de nouveaux types sans mise à jour de l'algorithme/fonction, mais ils doivent être dérivés de la même classe de base (expédition virtuelle)
1 - les modèles sont extrêmement flexibles. SFINAE (voir aussi std::enable_if
) permet effectivement plusieurs ensembles d'attentes pour le polymorphisme paramétrique. Par exemple, vous pouvez encoder que lorsque le type de données que vous traitez a un membre .size()
vous utiliserez une fonction, sinon une autre fonction qui n'a pas besoin de .size()
(mais souffre probablement d'une certaine manière - par exemple en utilisant le plus lent strlen()
ou en n'imprimant pas comme utile un message dans le journal). Vous pouvez également spécifier ad-hoc comportements lorsque le modèle est instancié avec des paramètres spécifiques, soit en laissant certains paramètres paramétriques ( spécialisation partielle du modèle ) ou non ( spécialisation complète ).
"Polymorphe"
Alf Steinbach commente que dans la norme C++ polymorphic se réfère uniquement au polymorphisme en cours d'exécution en utilisant virtuel de l'expédition. General Comp. Sci. la signification est plus inclusive, selon le glossaire de Bjarne Stroustrup, créateur de C++ ( ) http://www.stroustrup.com/glossary.html ):
polymorphisme - offrant une interface unique à des entités de types différents. Les fonctions virtuelles fournissent un polymorphisme dynamique (d'exécution) grâce à une interface fournie par une classe de base. Les fonctions et les modèles surchargés offrent un polymorphisme statique (de compilation). TC++PL 12.2.6, 13.6.1, D & E 2.9.
cette réponse - comme la question - relie les fonctionnalités C++ au Comp. Sci. terminologie.
Discussion
avec la norme C++ utilisant une définition plus étroite du "polymorphisme" que le Comp. Sci. communauté, pour assurer la compréhension mutuelle pour votre audience considérer...
- utilisant une terminologie non ambiguë ("pouvons-nous faire ce code réutilisable pour d'autres types?"ou "peut-on utiliser virtual expédition?"plutôt que "peut-on rendre ce code polymorphe?"), et/ou
- définit clairement votre terminologie.
pourtant, ce qui est crucial pour être un grand programmeur C++ est comprendre ce que le polymorphisme fait vraiment pour vous...
vous permet d'écrire du code "algorithmique" une fois et de l'appliquer à de nombreux types de données
...et ensuite être très conscient de la façon dont les différents mécanismes polymorphiques correspondent à vos besoins réels.
polymorphisme de l'Exécution des costumes:
- entrée traitées par l'usine, de méthodes et de cracher comme un objet hétérogène de collecte gérés par l'intermédiaire de
Base*
s, - implémentation choisie à l'exécution basée sur les fichiers de configuration, les commutateurs de ligne de commande, les paramètres D'interface utilisateur, etc., L'implémentation de
- varie à l'exécution, comme pour un modèle de machine d'état.
Lorsqu'il n'y a pas de pilote clair pour le polymorphisme d'exécution, les options de compilation sont souvent préférables. Considérons:
- l'aspect de compilation-ce que l'on appelle des classes-modèles est préférable aux interfaces fat qui ne fonctionnent pas à l'exécution
- SFINAE
- CRTP
- optimisations (dont plusieurs avec élimination du code inlining et du code mort, déroulage de boucle, tableaux statiques à base de pile vs tas)
-
__FILE__
,__LINE__
, un littéral de chaîne de concaténation et d'autres fonctionnalités uniques de macros (qui restent mal ;-)) - templates et macros test l'usage sémantique est supporté, mais ne restreignez pas artificiellement la façon dont ce support est fourni (comme Virtual dispatch tend à par nécessitant une correspondance exacte de la fonction membre remplace)
autres mécanismes soutenant le polymorphisme
comme promis, pour être complet plusieurs sujets périphériques sont couverts:
- surcharges fournies par le compilateur
- conversions
- moulages / coercition
cette réponse se termine par une discussion sur la façon dont les éléments ci-dessus se combinent pour renforcer et simplifier le code polymorphe - en particulier le polymorphisme paramétrique (modèles et macros).
Mécanismes pour associer le type des opérations spécifiques
> surcharges implicites fournies par le compilateur
conceptuellement, le compilateur surcharges beaucoup d'opérateurs pour les types de bâtiments. Il n'est pas conceptuellement différent de la surcharge spécifiée par l'utilisateur, mais il est répertorié car il est facile négliger. Par exemple, vous pouvez ajouter à int
s et double
s en utilisant la même notation x += 2
et le compilateur produit:
- instructions CPU spécifiques au type
- résultat du même type.
la surcharge s'étend alors aux types définis par l'utilisateur:
std::string x;
int y = 0;
x += 'c';
y += 'c';
compilateur-fourni des surcharges pour les types de base est commun à haut niveau (3GL+) les langages informatiques et la discussion explicite du polymorphisme impliquent généralement quelque chose de plus. (2gls-les langages d'assemblage-exigent souvent que le programmeur utilise explicitement différents mnémoniques pour différents types.)
> conversions Standard
la quatrième section de la norme C++ décrit les conversions Standard.
le premier point résume bien (d'un vieux projet-espérons encore substantiellement correct):
-1-les conversions Standard sont des conversions implicites définies pour les types intégrés. La Clause conv énumère l'ensemble complet de ces conversions. Une séquence de conversion standard est une séquence de conversions standard dans l'ordre suivant:
-
Zéro ou une conversion de l'ensemble suivant: lvalue-à-rvalue de conversion, tableau de pointeur de la conversion, et conversion de fonction en pointeur.
-
Zéro ou une conversion de l'ensemble suivant: intégrale des promotions, à virgule flottante de la promotion, de l'intégrale des conversions, à virgule flottante conversions, flottant intégrale des conversions, pointeur de conversions, pointeur sur membre des conversions, des booléens et de conversions.
-
conversion zéro ou une qualification.
[Note: une séquence de conversion standard peut être vide, c'est-à-dire qu'elle peut consister en aucune conversion. ] Une séquence de conversion standard sera appliquée à une expression si nécessaire pour la convertir en un type de destination requis.
ces conversions permettent le code tel que:
double a(double x) { return x + 2; }
a(3.14);
a(42);
application du critère antérieur:
Pour être polymorphe, [
a()
] doit être en mesure de fonctionner avec valeurs d'au moins deux types distincts (par exempleint
etdouble
), trouver et exécuter le code approprié au type .
a()
lui-même exécute le code spécifiquement pour double
et est donc pas polymorphique.
mais, dans le second appel à a()
le compilateur sait générer le code type approprié pour un " point flottant promotion " (Standard §4) pour convertir 42
en 42.0
. Ce code supplémentaire est dans la fonction appelant . Nous allons discuter de l'importance de cette question dans la conclusion.
> coercition, moulages, constructeurs implicites
ces mécanismes permettent à des classes définies par l'utilisateur de spécifier des comportements similaires aux conversions Standard des types d'Immeubles. Regardons:
int a, b;
if (std::cin >> a >> b)
f(a, b);
Ici, l'objet std::cin
est évaluée dans un contexte booléen, avec l'aide d'un opérateur de conversion. Cela peut être regroupé conceptuellement avec des "promotions intégrales" et autres des conversions Standard dans le sujet ci-dessus.
les constructeurs implicites font effectivement la même chose, mais sont contrôlés par le cast-to-type:
f(const std::string& x);
f("hello"); // invokes `std::string::string(const char*)`
Implications de compilateur fourni des surcharges, des conversions et de la coercition
prendre en considération:
void f()
{
typedef int Amount;
Amount x = 13;
x /= 2;
std::cout << x * 1.1;
}
si nous voulons que le montant x
soit considéré comme un nombre réel au cours de la division (c.-à-d. qu'il soit de 6,5 plutôt qu'arrondi à 6), nous seulement doit être remplacé par typedef double Amount
.
c'est bien, mais il n'aurait pas été trop beaucoup de travail pour rendre le code explicitement "type correct":
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;
} }
mais, considérez que nous pouvons transformer la première version en une template
:
template <typename Amount>
void f()
{
Amount x = 13;
x /= 2;
std::cout << x * 1.1;
}
c'est en raison de ces petites" fonctionnalités de commodité "qu'il peut être si facilement instancié pour int
ou double
et travailler comme prévu. Sans ces fonctionnalités, nous aurions besoin de moulages explicites, de traits de type et / ou de classes de politique, certains messages verbeux, comme:
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;
}
Ainsi, le compilateur fourni la surcharge d'opérateur pour builtin types, Norme les conversions, le casting / la coercition / les constructeurs implicites-tous apportent un soutien subtil au polymorphisme. À partir de la définition qui se trouve au haut de la présente réponse, ils traitent de la "recherche et de l'exécution du code approprié au type" par mappage:
-
"eloigner "des types de paramètres
-
de les nombreux types de données polymorphic code algorithmique poignées
-
à code écrit pour un nombre (potentiellement moindre) de (mêmes ou d'autres) types.
-
-
" à " types paramétriques à partir de valeurs de type constant
ils font pas établir des contextes polymorphes par eux-mêmes, mais aident à renforcer/simplifier le code à l'intérieur de tels contextes.
Vous pouvez vous sentir trompé... il ne semble pas comme beaucoup. La signification est que dans les contextes polymorphiques paramétriques (c.-à-d. à l'intérieur des gabarits ou des macros), nous essayons de soutenir une large gamme arbitraire de types, mais voulons souvent exprimer des opérations sur eux en termes d'autres fonctions, littérales et opérations qui ont été conçues pour un petit ensemble de types. Cela réduit le besoin de créer des fonctions ou des données quasi identiques sur une base par type lorsque l'opération/la valeur est logiquement la même. Ces caractéristiques coopérer pour ajouter une attitude de "meilleur effort", faire ce qui est intuitivement prévu en utilisant les fonctions et les données disponibles limitées et ne s'arrêter avec une erreur quand il ya une ambiguïté réelle.
cela aide à limiter le besoin de code polymorphe supportant le code polymorphe, en traçant un filet plus serré autour de l'utilisation du polymorphisme afin que l'utilisation localisée ne force pas l'utilisation généralisée, et en rendant les avantages du polymorphisme disponibles au besoin sans imposer les coûts d'avoir à exposez l'implémentation au moment de la compilation, ayez plusieurs copies de la même fonction logique dans le code objet pour supporter les types utilisés, et en faisant l'envoi virtuel par opposition à l'affichage en ligne ou au moins aux appels résolus au moment de la compilation. Comme dans C++, le programmeur a beaucoup de liberté pour contrôler les limites à l'intérieur desquelles le polymorphisme est utilisé.
dans C++, la distinction importante est run-time vs. compile-time binding. Ad-hoc vs paramétrique n'aide pas vraiment, comme je l'expliquerai plus tard.
|----------------------+--------------|
| Form | Resolved at |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates | compile-time |
| virtual methods | run-time |
|----------------------+--------------|
Note - polymorphisme de l'exécution peut encore être résolu à la compilation, mais c'est juste de l'optimisation. Le besoin de soutenir efficacement la résolution de l'exécution du temps, et la négociation contre d'autres questions, fait partie de ce qui a conduit à des fonctions virtuelles étant ce qu'ils sont. Et c'est vraiment la clé pour toutes les formes de polymorphisme en C++, chaque provient de différents ensembles de compromis faite dans un contexte différent.
la surcharge de la fonction et la surcharge de l'opérateur sont la même chose dans tous les sens du terme. Les noms et la syntaxe pour les utiliser n'affectent pas le polymorphisme.
Les modèlesvous permettent de spécifier beaucoup de surcharges de fonctions à la fois.
il y a un autre ensemble de noms pour la même idée de temps de résolution...
|---------------+--------------|
| early binding | compile-time |
| late binding | run-time |
|---------------+--------------|
ces noms sont plus associés à OOP, il est donc un peu étrange de dire qu'un modèle ou une autre fonction non-membre utilise la liaison précoce.
pour mieux comprendre la relation entre les fonctions virtuelles et la surcharge de la fonction, il est également utile de comprendre la différence entre" expédition simple "et"expédition multiple". L'idée peut être comprise comme une progression...
- tout d'abord, il ya des fonctions monomorphiques. Le la mise en œuvre de la fonction est identifiée de façon unique par le nom de la fonction. Aucun des paramètres spéciaux.
- il n'y a donc qu'une seule expédition. L'un des paramètres est considéré comme spécial, et utilisé (avec le nom) pour identifier la mise en œuvre d'utilisation. Dans OOP, nous avons tendance à considérer ce paramètre comme "l'objet", à le lister avant le nom de la fonction, etc.
- ensuite, il y a une expédition multiple. Tous les paramètres contribuent à identifier ce qui suit: la mise en œuvre d'utilisation. Donc, une fois de plus, aucun des paramètres doit être spécial.
il y a évidemment plus à OOP qu'une excuse pour désigner un paramètre comme spécial, mais c'est une partie de celui-ci. Et pour revenir à ce que j'ai dit à propos des compromis-expédition simple est assez facile à faire efficacement (l'implémentation habituelle est appelée "tables virtuelles"). L'expédition Multiple est plus embarrassante, non seulement en termes d'efficacité, mais aussi pour séparer compilation. Si vous êtes curieux, vous pourriez chercher "le problème d'expression".
tout comme il est un peu étrange d'utiliser le terme" early binding "pour les fonctions non-membres, il est un peu étrange d'utiliser les Termes" single dispatch "et" multiple dispatch " où le polymorphisme est résolu au moment de la compilation. Généralement, C++ est considéré comme n'ayant pas d'expédition multiple, ce qui est considéré comme un type particulier de résolution de l'exécution. Toutefois, la surcharge de la fonction peut être considérée comme une expédition multiple effectuée à au moment de la compilation.
pour en revenir au polymorphisme paramétrique par opposition au polymorphisme ad-hoc, ces termes sont plus populaires dans la programmation fonctionnelle, et ils ne fonctionnent pas tout à fait en C++. De même...
polymorphisme paramétrique signifie que vous avez des types comme paramètres, et le même code exact est utilisé quel que soit le type que vous utilisez pour ces paramètres.
polymorphisme Ad-hoc est ad-hoc dans le sens où vous fournissez un code différent selon le des types particuliers.
Surcharge et les fonctions virtuelles sont deux exemples d'ad-hoc polymorphisme.
encore une fois, il y a des synonymes...
|------------+---------------|
| parametric | unconstrained |
| ad-hoc | constrained |
|------------+---------------|
sauf que ce ne sont pas tout à fait des synonymes, bien qu'ils soient généralement traités comme s'ils l'étaient, et c'est là que la confusion est susceptible d'apparaître en C++.
le raisonnement derrière le fait de traiter ces synonymes est qu'en limitant le polymorphisme à des les classes de types, il devient possible d'utiliser des opérations spécifiques à ces classes de types. Le mot "classes" ici peut être interprété dans le sens OOP, mais se réfère vraiment juste à (habituellement nommé) ensembles de types qui partagent certaines opérations.
ainsi, le polymorphisme paramétrique est généralement considéré (au moins par défaut) comme un polymorphisme non limité. Parce que le même code est utilisé indépendamment des paramètres de type, les seules opérations supportables sont celles qui fonctionnent pour tous type. En laissant l'ensemble des types sans contrainte, vous limiter sévèrement l'ensemble des opérations que vous pouvez appliquer à ces types.
dans par exemple Haskell, vous pouvez avoir...
myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y
le a
voici un type polymorphe non limité. Ça pourrait être n'importe quoi, donc on ne peut pas faire grand-chose avec ce genre de valeurs.
myfunc2 :: Num a => a -> a
myfunc2 x = x + 3
ici, a
est contraint d'être un membre de la classe Num
types ça ressemble à des chiffres. Cette contrainte vous permet de faire des choses en nombre avec ces valeurs, comme les ajouter. Même le 3
est polymorphique - type inférence se rend compte que vous voulez dire le 3
de type a
.
je pense que c'est un polymorphisme paramétrique limité. Il n'y a qu'une mise en œuvre, mais elle ne peut être appliquée que dans des cas limités. L'aspect ad hoc est le choix de +
et 3
à utiliser. Chaque "instance" de Num
a sa propre mise en œuvre distincte de ceux-ci. Donc même dans Haskell "paramétrique" et "sans contrainte" ne sont pas vraiment synonymes - ne me blâmez pas, ce n'est pas ma faute!
en C++, la surcharge et les fonctions virtuelles sont des polymorphismes ad hoc. La définition du polymorphisme ad hoc ne se soucie pas de savoir si l'implémentation est sélectionnée à l'exécution ou à la compilation.
C++ devient très proche du polymorphisme paramétrique avec des modèles si chaque le paramètre template a le type typename
. Il y a des paramètres de type, et il n'y a qu'une seule implémentation, peu importe les types utilisés. Toutefois, la règle "échec de Substitution N'est pas une erreur" signifie que des contraintes implicites découlent de l'utilisation d'opérations dans le modèle. D'autres complications comprennent la spécialisation des modèles pour la fourniture d'autres modèles - différentes mises en œuvre (ad hoc).
donc D'une certaine façon C++ a un polymorphisme paramétrique, mais il est implicitement limité et pourrait être supplanté par des alternatives ad hoc-c'est - à-dire que cette classification ne fonctionne pas vraiment pour C++.
en ce qui concerne le polymorphisme ad hoc, il s'agit de la surcharge de fonction ou de la surcharge de l'opérateur. Voir ici:
http://en.wikipedia.org/wiki/Ad-hoc_polymorphism
en ce qui concerne le polymorphisme paramétrique, les fonctions de gabarit peuvent également être comptées parce qu'elles ne prennent pas nécessairement en compte des paramètres de types fixes. Par exemple, une fonction peut trier un tableau d'entiers et elle peut aussi trier un tableau de chaînes, etc.
cela peut ne pas être d'aucune aide, mais je l'ai fait pour présenter mes amis à la programmation en donnant des fonctions définies, comme START
, et END
pour la fonction principale de sorte que ce n'était pas trop intimidant (ils ont seulement utilisé le main.cpp ). Il contient des classes et des structures polymorphiques, des gabarits, des vecteurs, des tableaux, des directives de préprocesseur, de l'amitié, des opérateurs et des pointeurs (tout ce que vous devriez probablement savoir avant de tenter le polymorphisme):
Note: Il n'est pas terminé, mais vous pouvez obtenir l'idée
principal.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
principal.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;
}
}
}
};
voici un exemple de base utilisant les classes polymorphiques
#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);
}
polymorphisme signifie plusieurs formes en tant que telles il est utilisé pour un opérateur d'agir différemment dans différentes instances. Le polymorphisme est utilisé pour mettre en œuvre l'héritage. Par exemple, nous avons défini un dessin fn () pour une forme de classe alors le dessin FN peut être mis en œuvre pour dessiner des cercles, des boîtes, des triangles et d'autres formes. (qui sont des objets de la forme de la classe)
si quelqu'un dit coupez à ces gens
The Surgeon
The Hair Stylist
The Actor
que va-t-il se passer?
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.
la représentation ci-dessus montre ce qu'est le polymorphisme (même nom, comportement différent) en OOP.
si vous allez à une entrevue et que l'intervieweur vous demande de donner/montrer un exemple en direct de polymorphisme dans la même salle où nous sommes assis, dites -
Réponse Portes / Fenêtres
Vous Vous Demandez Comment?
par la Porte / Fenêtre - une personne peut venir, l'air peut venir, la lumière peut venir, la pluie peut venir, etc.
c'est à dire Une forme de comportement différent(Polymorphisme).
pour mieux le comprendre et d'une manière simple j'ai utilisé l'exemple ci-dessus.. Si vous avez besoin de référence pour le code, suivez les réponses ci-dessus.