Polymorphisme C++ | principes du polymorphisme | fonctions virtuelles

Annuaire d'articles

Table des matières

1. Le concept de polymorphisme

1. Concept

2. Définition et mise en œuvre du polymorphisme

1.Conditions du polymorphisme

2. Fonction virtuelle

3. Réécriture de fonctions virtuelles

Deux exceptions au remplacement de fonctions virtuelles :

4.override et final en c++11

5. Comparaison de la surcharge, de l'écrasement (réécriture), du masquage (redéfinition)

3. Classe abstraite

1. Concept

2. Héritage d'interface et héritage d'implémentation

4. Le principe du polymorphisme

1. Tableau des fonctions virtuelles

2. Le principe du polymorphisme

3. Liaison dynamique et liaison statique

5. Table de fonctions virtuelles en héritage simple et en héritage multiple

1. Table de fonctions virtuelles en héritage unique

2. Table de fonctions virtuelles en héritage multiple

6. Questions d'entretien courantes sur l'héritage et le polymorphisme



1. Le concept de polymorphisme

1. Concept

L'achèvement d'un certain comportement produira différents états lorsque différents objets le termineront.

Par exemple : lorsque les gens ordinaires achètent des billets , ils les achètent au prix fort ; lorsque les étudiants achètent des billets, ils les achètent à moitié prix ; les militaires les achètent à moitié prix.

Lors de l'achat de billets, achetez d'abord les billets

2. Définition et mise en œuvre du polymorphisme

1.Conditions du polymorphisme

Le polymorphisme se produit lorsque des objets de classe avec des relations d'héritage différentes appellent la même fonction, ce qui entraîne des comportements différents. Par exemple, Student hérite de Person. L'objet Person achète des billets à plein tarif et l'objet Student achète des billets à moitié prix.

Deux conditions qui constituent le polymorphisme en héritage :

1. Les fonctions virtuelles doivent être appelées via des pointeurs ou des références de la classe de base

2. La fonction appelée doit être une fonction virtuelle et la classe dérivée doit remplacer la fonction virtuelle de la classe de base.

Réécriture : le nom de la fonction est le même, les types de paramètres sont les mêmes et la valeur de retour est la même

2. Fonction virtuelle

Fonction virtuelle : une fonction membre de classe modifiée par virtual est appelée une fonction virtuelle

class Person
{
    public:
        virtual void BuyTicker()
        {
        }
};

3. Réécriture de fonctions virtuelles

Réécriture (écrasement) des fonctions virtuelles : il existe une fonction virtuelle dans la classe dérivée qui est exactement la même que la classe de base (c'est-à-dire le type de valeur de retour, le nom de la fonction et la liste des paramètres de la fonction virtuelle de la classe dérivée et de la classe de base). les fonctions virtuelles de classe sont exactement les mêmes), appelées sous-classes. La fonction virtuelle remplace la fonction virtuelle de la classe de base.

class Person
{
    public:
         virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

class Student : public Person 
{
    public:
         virtual void BuyTicket() { cout << "买票-半价" << endl; }
};


void Func(Person& p)
{
    p.BuyTicket();
}

int main()
{
    Person p;
    Student s;
    Fun(p);
    Fun(s);
    return 0;
}
Exception : Lors du remplacement d'une fonction virtuelle de classe de base, la fonction virtuelle de la classe dérivée peut également constituer un remplacement sans ajouter le mot-clé virtual ( car après héritage, la fonction virtuelle de la classe de base est héritée et reste virtuelle dans la classe dérivée). attribut de fonction ), mais cette méthode d'écriture n'est pas très standardisée et il n'est pas recommandé de l'utiliser de cette manière.

Deux exceptions au remplacement de fonctions virtuelles :

1. La covariance (le type de valeur de retour de la fonction virtuelle de la classe de base et de la classe dérivée est différent) doit être un pointeur ou une référence de relation parent-enfant

class Person
{
    public:
        virtual Person* BuyTicket()
        {
            cout<<"all"<<endl;
            return this;
        }

};


class Student :public Person
{
    public:
            virtual Student* BuyTicket()
            {
                cout<<"half"<<endl;
                return this;
            }
};

2. Réécriture des destructeurs (les noms des destructeurs des classes de base et des classes dérivées sont différents)

Si le destructeur de la classe de base est une fonction virtuelle, tant que le destructeur de la classe dérivée est défini, que le mot-clé virtual soit ajouté ou non, il sera remplacé par le destructeur de la classe de base. Bien que les noms des destructeurs de la classe de base et la classe dérivée sont différentes, cela semble violer le principe de réécriture, mais ce n'est pas le cas. On peut comprendre que le compilateur a effectué un traitement spécial pour le nom du destructeur, et le nom du destructeur compilé est unifié en destructeur

4.override et final en c++11

1.final : Modifiez la fonction virtuelle pour indiquer que la fonction virtuelle ne peut pas être réécrite.

class Car
{
   public:
        vitual void Dirve() final
    {}
};

class Benz:public car
{
    virtual void Drive()
    {
            cout<<"benz-"<<endl;
    }
};

2.overrider : Vérifiez si la fonction virtuelle de la classe dérivée a remplacé une certaine fonction virtuelle. Sinon, une erreur sera signalée.

class Car
{
public:
 virtual void Drive(){}
};


class Benz :public Car
{
public:
 virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

5. Comparaison de la surcharge, de l'écrasement (réécriture), du masquage (redéfinition)

Surcharge : deux fonctions dans le même périmètre , avec le même nom de fonction et des paramètres différents

Réécriture (écrasement) : Les deux fonctions entrent respectivement dans la portée de la classe de base et de la classe dérivée. Les noms/paramètres/valeurs de retour des fonctions doivent être les mêmes (exceptions de covariance) et les deux fonctions doivent être des fonctions virtuelles .

Redéfinition (cachée) : Les deux fonctions sont respectivement dans la portée de la classe de base et de la classe dérivée. Les noms de fonction sont les mêmes. Les fonctions des deux classes de base et de la classe dérivée portant le même nom ne constituent pas un écrasement ou une redéfinition. .

3. Classe abstraite

1. Concept

Écrivez =0 après la fonction virtuelle, alors la fonction est une fonction virtuelle pure. La classe contenant la fonction virtuelle pure est appelée une classe abstraite (également appelée classe d'interface). Les classes abstraites ne peuvent pas instancier des objets et les classes dérivées ne peuvent pas en hériter. Pour instancier un objet, ce n'est qu'en réécrivant une fonction virtuelle pure qu'une classe dérivée peut instancier un objet. Les fonctions virtuelles pures spécifient que les classes dérivées doivent être réécrites. De plus, les fonctions virtuelles pures reflètent également l'héritage d'interface.

2. Héritage d'interface et héritage d'implémentation

L'héritage des fonctions ordinaires est une sorte d'héritage d'implémentation.La classe dérivée hérite de la fonction de classe de base et peut utiliser la fonction. Ce qui est hérité, c'est l'implémentation de la fonction.

L'héritage des fonctions virtuelles est une sorte d'héritage d'interface. La classe dérivée hérite de l'interface de la fonction virtuelle de la classe de base. Le but est de remplacer et d'obtenir le polymorphisme. Ce qui est hérité, c'est l'interface, donc si vous n'implémentez pas le polymorphisme, ne le faites pas définir la fonction comme fonction virtuelle.

4. Le principe du polymorphisme

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
 virtual void Func1()
 {
 cout << "Func1()" << endl;
 }
private:
 int _b = 1;  //4
 char _c = 0;  //1
};


sizeof(Base) //12

En plus des deux membres, il y a aussi un _vfptr supplémentaire placé devant l'objet (certaines plates-formes peuvent être placées derrière l'objet). Ce pointeur dans l'objet est appelé pointeur de table de fonctions virtuelles (v-virtual f-function) Un pointeur contenant virtual Il existe au moins un pointeur de table de fonctions virtuelles dans la classe de fonctions, car l'adresse de la fonction virtuelle est placée dans la table de fonctions virtuelles et la table de fonctions virtuelles est également appelée table virtuelle.

1. Tableau des fonctions virtuelles

class Base
{
    public:
     virtual void Func1()
     {
     cout << "Base::Func1()" << endl;
     }
     virtual void Func2()
     {
     cout << "Base::Func2()" << endl;
     }
    
    //普通函数
     void Func3()
     {
     cout << "Base::Func3()" << endl;
     }
     private:
         int _b = 1;
};

class Derive : public Base
{
public:
     //实现了虚函数的重写
     virtual void Func1()
     {
        Person::BuyTicket,传Student调用的是Student::BuyTicket
        cout << "Derive::Func1()" << endl;
     }
private:
     int _d = 2;
};



int main()
{
     Base b;
     Derive d;
     return 0;
}
  • Il existe également un pointeur de table virtuelle dans l'objet de classe dérivé d. L'objet d est composé de deux parties. Une partie est le membre hérité de la classe parent. Le pointeur de table virtuelle est le propre membre de l'autre partie de la partie existante.
  • L'objet de classe de base b et l'objet de classe dérivé d ont des tables virtuelles différentes. Fun1 termine la réécriture, donc la table virtuelle dans d stocke le Drive::Func1 réécrit, donc la réécriture de la fonction virtuelle est également appelée écrasement. La couverture fait référence à la couverture des fonctions virtuelles dans la table virtuelle. La réécriture est le nom de la syntaxe et l'écrasement est le nom de la couche distante.
  • Func2 hérite d'une fonction virtuelle, elle est donc placée dans la table virtuelle. Fun3 en hérite également, mais ce n'est pas une fonction virtuelle, elle n'est donc pas placée dans la table virtuelle.
  • La table de fonctions virtuelles est essentiellement un tableau de pointeurs qui stocke les pointeurs de fonctions virtuelles. Généralement, un nullptr est placé à la fin de ce tableau.
  • Générez une table virtuelle pour une classe dérivée : a. Copiez d'abord le contenu de la table virtuelle de la classe de base dans la table virtuelle de la classe dérivée. b. Si la classe dérivée remplace une fonction virtuelle dans la classe de base, utilisez la table virtuelle de la classe dérivée. propre à la classe La fonction virtuelle couvre la fonction virtuelle de la classe de base dans la table virtuelle c. Les fonctions virtuelles nouvellement ajoutées par la classe dérivée sont ajoutées à la fin de la table virtuelle de la classe dérivée dans l'ordre de leur déclaration dans la classe dérivée.
  • La fonction virtuelle est dans la table virtuelle, et la table virtuelle est dans l'objet (❌) devrait être : les pointeurs de fonction virtuelle sont stockés dans la table virtuelle , pas les fonctions virtuelles. La fonction virtuelle est stockée dans le segment de code et son pointeur est stocké dans la table virtuelle . Ce qui est stocké dans l'objet n'est pas une table virtuelle, mais un pointeur de table virtuelle . Les tables virtuelles sont également stockées dans des segments de code .
  • Lors du découpage, seul l'objet est copié, pas la table virtuelle.
    int main()
    {
        Student johnson;
        Func(johnson);
    
        Person mike = johnson;
    
        return 0;
    }

2. Le principe du polymorphisme

On peut voir que l'appel de fonction après avoir satisfait le polymorphisme n'est pas déterminé au moment de la compilation, mais est récupéré de l'objet après son exécution. Les appels de fonction qui ne satisfont pas au polymorphisme sont confirmés au moment de la compilation .

3. Liaison dynamique et liaison statique

  • La liaison statique est également appelée liaison précoce (liaison anticipée). Elle détermine la gravité du programme lors de la compilation du programme. Elle est également appelée polymorphisme statique, comme la surcharge de fonctions.
  • La liaison dynamique est également appelée liaison tardive. Lors de l'exécution du programme, le comportement spécifique du programme est déterminé en fonction du type obtenu et des fonctions spécifiques sont appelées. C'est également appelé polymorphisme dynamique.

5. Table de fonctions virtuelles en héritage simple et en héritage multiple

1. Table de fonctions virtuelles en héritage unique

class Base
 { 
public :
 virtual void func1() { cout<<"Base::func1" <<endl;}
 virtual void func2() {cout<<"Base::func2" <<endl;}
private :
 int a;
};


class Derive :public Base 
{ 
public :
 virtual void func1() {cout<<"Derive::func1" <<endl;}
 virtual void func3() {cout<<"Derive::func3" <<endl;}
 virtual void func4() {cout<<"Derive::func4" <<endl;}
private :
 int b;
};

//使用程序来打印虚表

typedef void(*VF_PTR)();   //定义一个函数指针vf_ptr
void printfVTable(VF_PTR table[])
{
    cout<<"虚表地址:" <<table<<endl;
    for(int i = 0; table[i] ! = nullptr ; ++i)
    {
        printf("%d -%p",i,table[i]);
        VF_PTR f = table[i];
        f();
    }

    cout<<endl;
};

    int main()
    {
        Base b; 
        Drive d;
        //思路: 取出b,d对象中的前4个字节,就是虚表的指针,这个虚函数表本质是一个存虚函数地址的指针数组,这个数组最后放了一个Nullptr
        //先取对象的地址,然后强转为int * 的指针
        //再解引用取值,就取到了b对象中的前4个字节,这个值就是虚表的指针
        //再强转为vf_ptr,因为虚表就是一个vf_ptr类型的数组
        //虚表指针传递给函数进行打印
        VF_PTR * vTable = (VF_PTR*)(*(int *)&b);
        printfVTable(vTable);
         VF_PTR * vTable = (VF_PTR*)(*(int *)&d);
         printfVTable(vTable);
    }

2. Table de fonctions virtuelles en héritage multiple

class Base1 {
public:
 virtual void func1() {cout << "Base1::func1" << endl;}
 virtual void func2() {cout << "Base1::func2" << endl;}
private:
 int b1;
};
class Base2 {
public:
 virtual void func1() {cout << "Base2::func1" << endl;}
 virtual void func2() {cout << "Base2::func2" << endl;}
private:
 int b2;
};
class Derive : public Base1, public Base2 {
public:
 virtual void func1() {cout << "Derive::func1" << endl;}
 virtual void func3() {cout << "Derive::func3" << endl;}
private:
 int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
 cout << " 虚表地址>" << vTable << endl;
 for (int i = 0; vTable[i] != nullptr; ++i)
 {
 printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
 VFPTR f = vTable[i];
 f();
 }
 cout << endl;
}
int main()
{
 Derive d;
 VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
 PrintVTable(vTableb1);
 VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));
 PrintVTable(vTableb2);
 return 0;
}

Derive hérite de deux classes, il y a donc deux tables virtuelles. J'ai moi-même implémenté un func3 et j'ai vu dans la mémoire que les fonctions virtuelles non réécrites de la classe dérivée à héritage multiple sont placées dans la table des fonctions virtuelles de la première partie de la classe de base héritée .

6. Questions d'entretien courantes sur l'héritage et le polymorphisme

  1. Une fonction en ligne peut-elle être une fonction virtuelle ? Réponse : Oui, mais le compilateur ignore l'attribut inline et cette fonction n'est plus en ligne car la fonction virtuelle doit être placée dans la table virtuelle.
  2.  Les membres statiques peuvent-ils être des fonctions virtuelles ? Réponse : Non, car la fonction membre statique n'a pas ce pointeur et la table de fonctions virtuelles n'est pas accessible à l'aide de la méthode d'appel de la fonction type::member, donc la fonction membre statique ne peut pas être placée dans la table de fonctions virtuelles.
  3. Un constructeur peut-il être une fonction virtuelle ? Réponse : Non, car le pointeur de la table de fonctions virtuelles dans l'objet est initialisé lors de l'étape de liste d'initialisation du constructeur.
  4.  Un destructeur peut-il être une fonction virtuelle ? Dans quels scénarios le destructeur est-il une fonction virtuelle ? Réponse : Oui, et il est préférable de définir le destructeur de la classe de base comme une fonction virtuelle. Se référer au contenu de ce didacticiel
  5. Est-il plus rapide pour les objets d’accéder aux fonctions normales ou aux fonctions virtuelles ? Réponse : Tout d’abord, s’il s’agit d’un objet normal, il est tout aussi rapide. S'il s'agit d'un objet pointeur ou d'un objet de référence, la fonction ordinaire appelée est plus rapide. Parce qu'elle constitue un polymorphisme, l'appel d'une fonction virtuelle à l'exécution nécessite une recherche dans la table des fonctions virtuelles.
  6. A quelle étape la table de fonctions virtuelles est-elle générée et où existe-t-elle ? Réponse : La table des fonctions virtuelles est générée lors de la phase de compilation, et existe généralement dans le segment de code (zone constante).
  7. Qu'est-ce qu'une classe abstraite? Quel est le rôle des classes abstraites ? Réponse : Une classe avec des fonctions virtuelles pures est appelée une classe abstraite (ajoutez =0 après la fonction virtuelle). Les classes abstraites forcent le remplacement des fonctions virtuelles et les classes abstraites reflètent les relations d'héritage d'interface.
  8. Laquelle des méthodes orientées objet suivantes peut vous rendre riche (A)
  9. A : Héritage B : Encapsulation C : Polymorphisme D : Abstraction
  10. (D) est un mécanisme dans les langages de programmation orientés objet. Ce mécanisme réalise que la définition de la méthode n'a rien à voir avec l'objet spécifique et que l'appel à la méthode peut être associé à l'objet spécifique.
  11. A : Héritage B : Modèle C : Auto-référence de l'objet D : Liaison dynamique
  12.  Concernant l’héritage et la composition dans la conception orientée objet, laquelle des affirmations suivantes est fausse ? (C)
  13. R : L'héritage nous permet de remplacer et de réécrire les détails d'implémentation de la classe parent. L'implémentation de la classe parent est visible par la classe enfant. Il s'agit d'une sorte de réutilisation statique , également appelée réutilisation de la boîte blanche .
  14. B : Les objets combinés n'ont pas besoin de se soucier de leurs détails d'implémentation respectifs. La relation entre eux est déterminée au moment de l'exécution. Il s'agit d'une sorte de réutilisation dynamique , également appelée réutilisation de la boîte noire.
  15. C : Donner la priorité à l'héritage plutôt qu'à la composition est le deuxième principe de la conception orientée objet.
  16. D : L'héritage permet aux sous-classes d'hériter automatiquement de l'interface de la classe parent, mais dans les modèles de conception, cela est considéré comme compromettant l'encapsulation
  17. Laquelle des affirmations suivantes concernant les fonctions virtuelles pures est correcte (A)
  18. A : Une classe qui déclare une fonction virtuelle pure ne peut pas instancier un objet. B : Une classe qui déclare une fonction virtuelle pure est une classe de base virtuelle.
  19. C : La sous-classe doit implémenter la fonction virtuelle pure de la classe de base D : La fonction virtuelle pure doit être une fonction vide
  20. La description correcte des fonctions virtuelles est (B)
  21. A : La fonction virtuelle de la classe dérivée et la fonction virtuelle de la classe de base ont un nombre de paramètres et de types différents. B : Les fonctions en ligne ne peuvent pas être des fonctions virtuelles.
  22. C : La classe dérivée doit redéfinir la fonction virtuelle de la classe de base D : La fonction virtuelle peut être une fonction statique
  23. 6. L'énoncé correct concernant les tables virtuelles est (B )
  24. R : Une classe ne peut avoir qu’une seule table virtuelle
  25. B : Il existe des fonctions virtuelles dans la classe de base. Si la sous-classe ne remplace pas la fonction virtuelle de la classe de base, la sous-classe et la classe de base partagent la même table virtuelle.
  26. C : La table virtuelle est générée dynamiquement pendant l'exécution
  27. D : Différents objets d'une classe partagent la table virtuelle de la classe
  28.  Supposons qu'il existe une fonction virtuelle dans la classe A, B hérite de A, B remplace la fonction virtuelle dans A et ne définit aucune fonction virtuelle, alors (D)
  29. R : Les 4 premiers octets des objets de classe A stockent l'adresse de la table virtuelle et les 4 premiers octets des objets de classe B ne sont pas l'adresse de la table virtuelle.
  30. B : Les 4 premiers octets des objets de classe A et des objets de classe B stockent l'adresse de la table de base virtuelle.
  31. C : Les adresses de table virtuelle stockées dans les 4 premiers octets des objets de classe A et des objets de classe B sont les mêmes.
  32. D : Le nombre de fonctions virtuelles dans les tables virtuelles des classes A et B est le même, mais les classes A et B n'utilisent pas la même table virtuelle.

Je suppose que tu aimes

Origine blog.csdn.net/jolly0514/article/details/132863420
conseillé
Classement