Maison > Article > développement back-end > Il faut apprendre ! Conditions de base pour implémenter un mécanisme polymorphe en C++
Comment implémenter le mécanisme polymorphe du C++. Le mécanisme pour implémenter le polymorphisme en cours consiste à ajouter le mot-clé virtuel avant la fonction de la classe de base, à réécrire la fonction dans la classe dérivée, et elle sera appelée en fonction du type réel. de l'objet au moment de l'exécution de la fonction correspondante.
L'implémentation et le principe du polymorphisme C++
Le polymorphisme du C++ peut se résumer en une phrase : ajouter le mot-clé virtual avant la fonction de la classe de base, et ajouter le mot-clé virtual avant la fonction dérivée. Si cette fonction est remplacée dans la classe, la fonction correspondante sera appelée en fonction du type réel de l'objet au moment de l'exécution. Si le type d'objet est une classe dérivée, appelez la fonction de la classe dérivée ; si le type d'objet est une classe de base, appelez la fonction de la classe de base
1 : La fonction déclarée avec le virtuel Le mot-clé est appelé une fonction virtuelle, virtual La fonction doit être une fonction membre de la classe.
2 : Les classes avec des fonctions virtuelles ont une table de fonctions virtuelles unidimensionnelle appelée table virtuelle. L'objet de la classe a un pointeur virtuel pointant vers le début de la table virtuelle. La table virtuelle correspond à la classe et le pointeur de table virtuelle correspond à l'objet.
3 : Le polymorphisme est les multiples implémentations d'une interface et est le cœur de l'orientation objet. Il est divisé en polymorphisme de classe et polymorphisme de fonction.
4 : Le polymorphisme est implémenté avec des fonctions virtuelles, combinées à une liaison dynamique
5 : Les fonctions virtuelles pures sont des fonctions virtuelles plus = 0 ;
6 : Résumé Une classe est. une classe qui contient au moins une fonction virtuelle pure.
Fonction virtuelle pure : virtual void fun()=0; c'est-à-dire classe abstraite ! Cette fonction doit être implémentée dans la sous-classe, c'est-à-dire qu'il y a d'abord un nom, pas de contenu et que le contenu est implémenté dans la classe dérivée.
Regardons d'abord un exemple
#include <iostream> #include <stdlib.h>using namespace std; class Father {public: void Face() { cout << "Father's face" << endl; } void Say() { cout << "Father say hello" << endl; } };class Son:public Father {public: void Say() { cout << "Son say hello" << endl; } };int main() { Son son; Father *pFather=&son; // 隐式类型转换 pFather->Say(); return 0; }
Le résultat de sortie est :
Father say hello
Nous définissons d'abord un objet fils de la classe Son dans le main() function , puis définissez une variable pointeur pFather pointant vers la classe Father, puis utilisez cette variable pour appeler pFather->Say() On estime que beaucoup de gens ont tendance à confondre cette situation avec le polymorphisme du C++, pensant que fils est en fait. la classe Son L'objet devrait appeler la classe Say of the Son et afficher "Son say hello", mais le résultat ne l'est pas
D'un point de vue compilation :
Lorsque le compilateur c++ compile. , Pour déterminer l'adresse de la fonction (fonction non virtuelle) appelée par chaque objet, c'est ce qu'on appelle la liaison anticipée. Lorsque l'on attribue l'adresse de l'objet fils de la classe Son à pFather, le compilateur c++ effectue une conversion de type At. cette fois, le compilateur c++ Le processeur pense que la variable pFather enregistre l'adresse de l'objet Father. Lorsque pFather->Say() est exécuté dans la fonction principale, bien sûr la fonction Say de l'objet Father est appelée
.Du point de vue de la mémoire
Le modèle de mémoire de l'objet de classe Son est comme indiqué ci-dessus
Lorsque nous construisons un objet de classe Son, nous devons appelez d'abord le constructeur de la classe Père pour construire un objet de la classe Père, puis appelez Le constructeur de la classe Fils termine la construction de sa propre partie, créant ainsi un objet complet de la classe Fils. Lorsque nous convertissons l'objet de classe Son en type Père, l'objet est considéré comme la moitié supérieure de l'ensemble du modèle de mémoire de l'objet d'origine, qui est la "mémoire occupée par l'objet Père" dans la figure ci-dessus, puis lorsque nous utilisez la conversion de type Lorsque le pointeur d'objet appelle sa méthode, il appelle bien sûr la méthode dans la mémoire où il se trouve. Il est donc logique de sortir "Père, dis bonjour".
Comme beaucoup de gens le pensent, dans le code ci-dessus, nous savons que pFather pointe en fait vers un objet de la classe Son. Nous espérons que le résultat de sortie est la méthode Say de la classe son. pour atteindre ce résultat, vous devez utiliser des fonctions virtuelles.
Le résultat de sortie précédent est dû au fait que le compilateur a déjà déterminé l'adresse de la fonction appelée par l'objet lors de la compilation. Pour résoudre ce problème, une liaison tardive doit être utilisée. Lorsque le compilateur utilise une liaison tardive, il la déterminera. le type de l'objet et la fonction appelante correcte au moment de l'exécution. Pour que le compilateur adopte une liaison tardive, il est nécessaire d'utiliser le mot-clé virtual lors de la déclaration de la fonction dans la classe de base. Nous appelons une telle fonction virtual Function, une fois qu'une fonction est. déclarée comme virtuelle dans la classe de base, alors la fonction est virtuelle dans toutes les classes dérivées, sans qu'il soit nécessaire de la déclarer explicitement comme virtuelle.
Modifiez légèrement le code et jetez un œil aux résultats en cours
#include <iostream> #include <stdlib.h>using namespace std; class Father {public: void Face() { cout << "Father's face" << endl; } virtual void Say() { cout << "Father say hello" << endl; } };class Son:public Father {public: void Say() { cout << "Son say hello" << endl; } };int main() { Son son; Father *pFather=&son; // 隐式类型转换 pFather->Say(); return 0; }
Les résultats en cours :
Son say hello
Nous avons constaté que le résultat est "Fils, dis bonjour ", qui est basé sur l'objet. Le type appelle la fonction correcte, alors que se passe-t-il dans les coulisses lorsque nous déclarons Say() comme virtuel.
Lorsque le compilateur compile, il constate qu'il y a des fonctions virtuelles dans la classe Father. À ce moment, le compilateur créera une table virtuelle (c'est-à-dire vtable) pour chaque classe qui contient des fonctions virtuelles. un tableau unidimensionnel, stocke l'adresse de chaque fonction virtuelle dans ce tableau,
那么如何定位虚表呢?编译器另外还为每个对象提供了一个虚表指针(即vptr),这个指针指向了对象所属类的虚表,在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数,对于第二段代码程序,由于pFather实际指向的对象类型是Son,因此vptr指向的Son类的vtable,当调用pFather->Son()时,根据虚表中的函数地址找到的就是Son类的Say()函数.
正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针是在什么时候,或者什么地方初始化呢?
答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。
总结(基类有虚函数的):
1:每一个类都有虚表
2:虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现,如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现,如果派生类有自己的虚函数,那么虚表中就会添加该项。
3:派生类的虚表中虚地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。
这就是c++中的多态性,当c++编译器在编译的时候,发现Father类的Say()函数是虚函数,这个时候c++就会采用晚绑定技术,也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型来确认调用的是哪一个函数,这种能力就叫做c++的多态性,我们没有在Say()函数前加virtual关键字时,c++编译器就确定了哪个函数被调用,这叫做早期绑定。
c++的多态性就是通过晚绑定技术来实现的。
c++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。
虚函数是在基类中定义的,目的是不确定它的派生类的具体行为,例如:
定义一个基类:class Animal //动物,它的函数为breathe()
再定义一个类class Fish //鱼。它的函数也为breathe()
再定义一个类class Sheep //羊,它的函数也为breathe()
将Fish,Sheep定义成Animal的派生类,然而Fish与Sheep的breathe不一样,一个是在水中通过水来呼吸,一个是直接呼吸,所以基类不能确定该如何定义breathe,所以在基类中只定义了一个virtual breathe,它是一个空的虚函数,具体的函数在子类中分别定义,程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。
这里的多态性是指类的多态性。
函数的多态性是指一个函数被定义成多个不同参数的函数。当你调用这个函数时,就会调用不同的同名函数。
一般情况下(不涉及虚函数),当我们用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。
当设计到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数
现在我们看一个体现c++多态性的例子,看看输出结果:
#include <iostream> #include <stdlib.h>using namespace std; class CA { public: void f() { cout << "CA f()" << endl; } virtual void ff() { cout << "CA ff()" << endl; f(); } }; class CB : public CA { public : virtual void f() { cout << "CB f()" << endl; } void ff() { cout << "CB ff()" << endl; f(); CA::ff(); } }; class CC : public CB { public: virtual void f() { cout << "C f()" << endl; } }; int main() { CB b; CA *ap = &b; CC c; CB &br = c; CB *bp = &c; ap->f(); cout << endl; b.f(); cout << endl; br.f(); cout << endl; bp->f(); cout << endl; ap->ff(); cout << endl; bp->ff(); cout << endl; return 0; }
输出结果:
CA f()CB f()C f()C f()CB ff()CB f()CA ff()CA f()CB ff()C f()CA ff()CA f()
相关推荐:
Tutoriel vidéo C++_free Tutoriel C++ apprentissage en ligne
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!