Une explication détaillée de l'affectation des classes dérivées aux classes de base basées sur C/C++

Table des matières

Attribuer un objet de classe dérivé à un objet de classe de base

Attribuer un pointeur de classe dérivé au pointeur de classe de base

Attribuer une référence de classe dérivée à une référence de classe de base

Bibliographie recommandée pour ce numéro

 


La conversion de type de données se produit souvent en C/C++. Par exemple, lors de l'affectation de données de type int à une variable de type flottant, le compilateur convertira d'abord les données de type int en un type flottant avant d'affecter une valeur ; à l'inverse, les données de type flottant Il peut également être affecté à une variable de type int après conversion de type.

La prémisse de la conversion de type de données est que le compilateur sait comment échanger les données. Par exemple:

int a = 10.9;
printf("%d\n", a);

 Le résultat de sortie est 10 et le compilateur supprimera directement la partie décimale (pas d'arrondi). Un autre exemple:

float b = 10;
printf("%f\n", b);

La sortie est 10.000000 et le compilateur ajoute automatiquement la partie décimale.

Une classe est en fait un type de données, et une conversion de type de données peut également se produire, mais cette conversion n'a de sens qu'entre la classe de base et la classe dérivée, et seule la classe dérivée peut être affectée à la classe de base, y compris l'affectation de la classe dérivée objets à l'objet de classe de base, affectez le pointeur de classe dérivé au pointeur de classe de base, affectez la référence de classe dérivée à la référence de classe de base, cela s'appelle Upcasting en C++. De même, l'affectation de la classe de base à la classe dérivée est appelée downcasting (Downcasting).

L'upcasting est très sûr et peut être effectué automatiquement par le compilateur ; le downcasting est risqué et nécessite une intervention manuelle du programmeur. Cette section ne présente que la transformation vers le haut, et la transformation vers le bas sera introduite dans les chapitres suivants.

Upcasting et downcasting sont des concepts courants dans la programmation orientée objet, et ils existent également dans des langages de programmation tels que Java et C#.

 

Attribuer un objet de classe dérivé à un objet de classe de base

L'exemple suivant montre comment affecter un objet de classe dérivée à un objet de classe de base :

#include <iostream>
using namespace std;

//基类
class A{
public:
    A(int a);
public:
    void display();
public:
    int m_a;
};
A::A(int a): m_a(a){ }
void A::display(){
    cout<<"Class A: m_a="<<m_a<<endl;
}

//派生类
class B: public A{
public:
    B(int a, int b);
public:
    void display();
public:
    int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
    cout<<"Class B: m_a="<<m_a<<", m_b="<<m_b<<endl;
}


int main(){
    A a(10);
    B b(66, 99);
    //赋值前
    a.display();
    b.display();
    cout<<"--------------"<<endl;
    //赋值后
    a = b;
    a.display();
    b.display();

    return 0;
}

Résultat courant :
Classe A : m_a=10
Classe B : m_a=66, m_b=99
----------------------------
Classe A : m_a=66
Classe B : m_a=66, m_b=99

Dans cet exemple, A est la classe de base, B est la classe dérivée, et a et b sont leurs objets respectivement. Puisque la classe dérivée B contient des membres hérités de la classe de base A, Par conséquent, l'objet de classe dérivé b peut être affecté à l'objet de classe de base a. Il peut également être trouvé à travers les résultats en cours d'exécution que la valeur de la variable membre contenue dans a a changé après l'affectation.

L'essence de l'affectation est d'écrire les données existantes dans la mémoire allouée. La mémoire de l'objet ne contient que des variables membres, donc l'affectation entre les objets est l'affectation de variables membres, et il n'y a pas de problème d'affectation dans les fonctions membres. Les résultats de l'exécution prouvent également fortement ce point.Bien qu'il existe a=b;un tel processus d'affectation, a.display() appelle toujours la fonction display() de la classe A. En d'autres termes, les affectations entre objets n'affectent pas les fonctions membres, ni le pointeur this.

Lors de l'affectation d'un objet de classe dérivée à un objet de classe de base, les membres nouvellement ajoutés de la classe dérivée seront ignorés, c'est-à-dire "overkill", comme illustré dans la figure suivante :

 On peut constater que même si l'objet de la classe dérivée est affecté à l'objet de la classe de base, l'objet de la classe de base ne contiendra pas les membres de la classe dérivée, il est donc toujours différent d'accéder aux membres de la classe dérivée via la classe de base objet. Pour l'exemple ci-dessus, a.m_a est vrai, mais a.m_b est faux car a ne contient pas le membre m_b.

Cette relation de conversion est irréversible et seuls les objets de la classe dérivée peuvent être utilisés pour attribuer des valeurs aux objets de la classe de base, mais les objets de la classe de base ne peuvent pas être utilisés pour attribuer des valeurs aux objets de la classe dérivée. La raison est simple, la classe de base ne contient pas les variables membres de la classe dérivée, et ne peut pas affecter de valeurs aux variables membres de la classe dérivée. De même, les affectations ne peuvent pas être faites entre des objets de différentes classes dérivées de la même classe de base.

Pour comprendre ce problème, nous devons commencer par l'essence de l'affectation. L'affectation consiste en fait à remplir la mémoire de données. Lorsqu'il y a beaucoup de données, il est facile de les gérer et de les supprimer ; dans cet exemple, lorsque b est affecté à a (exécution de l'instruction), le membre m_b est a=b;redondant et sera directement rejeté, donc il ne se produira pas d'erreur d'affectation. Mais lorsqu'il y a moins de données, le problème est très difficile, le compilateur ne sait pas comment remplir la mémoire restante ; s'il y a b= a;une telle instruction dans cet exemple, le compilateur ne sait pas comment affecter une valeur à la variable m_b, donc une erreur se produit.

Attribuer un pointeur de classe dérivé au pointeur de classe de base

Outre l'affectation d'objets de classe dérivée à des objets de classe de base (affectation entre variables d'objet), vous pouvez également affecter des pointeurs de classe dérivée à des pointeurs de classe de base (affectation entre des pointeurs d'objet). Examinons d'abord un exemple d'héritage multiple. La relation d'héritage est la suivante :

 

#include <iostream>
using namespace std;

//基类A
class A{
public:
    A(int a);
public:
    void display();
protected:
    int m_a;
};
A::A(int a): m_a(a){ }
void A::display(){
    cout<<"Class A: m_a="<<m_a<<endl;
}

//中间派生类B
class B: public A{
public:
    B(int a, int b);
public:
    void display();
protected:
    int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
    cout<<"Class B: m_a="<<m_a<<", m_b="<<m_b<<endl;
}

//基类C
class C{
public:
    C(int c);
public:
    void display();
protected:
    int m_c;
};
C::C(int c): m_c(c){ }
void C::display(){
    cout<<"Class C: m_c="<<m_c<<endl;
}

//最终派生类D
class D: public B, public C{
public:
    D(int a, int b, int c, int d);
public:
    void display();
private:
    int m_d;
};
D::D(int a, int b, int c, int d): B(a, b), C(c), m_d(d){ }
void D::display(){
    cout<<"Class D: m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}


int main(){
    A *pa = new A(1);
    B *pb = new B(2, 20);
    C *pc = new C(3);
    D *pd = new D(4, 40, 400, 4000);

    pa = pd;
    pa -> display();

    pb = pd;
    pb -> display();

    pc = pd;
    pc -> display();

    cout<<"-----------------------"<<endl;
    cout<<"pa="<<pa<<endl;
    cout<<"pb="<<pb<<endl;
    cout<<"pc="<<pc<<endl;
    cout<<"pd="<<pd<<endl;

    return 0;
}

Résultats cumulés :
Classe A : m_a=4
Classe B : m_a=4, m_b=40
Classe C : m_c=400
-----------------------
pa= 0x9b17f8
pb=0x9b17f8
pc=0x9b1800
pd=0x9b17f8

Dans cet exemple, plusieurs pointeurs d'objet sont définis et une tentative est effectuée pour affecter des pointeurs de classe dérivés à des pointeurs de classe de base. Différente de l'affectation entre variables d'objet, l'affectation entre pointeurs d'objets ne copie pas les membres de l'objet, ni ne modifie les données de l'objet lui-même, mais change seulement le pointage du pointeur.

1) Accéder aux membres de la classe dérivée via le pointeur de classe de base

Veuillez d'abord prêter attention à la ligne 68 du code. Nous attribuons le pointeur de classe dérivée pd au pointeur de classe de base pa. D'après les résultats en cours d'exécution, nous pouvons voir que bien que les variables membres de la classe dérivée soient utilisées lors de l'appel de display() fonction, la fonction display() elle-même est la classe de base. C'est-à-dire que lors de l'affectation d'un pointeur de classe dérivée à un pointeur de classe de base, seules les variables membres de la classe dérivée peuvent être utilisées via le pointeur de classe de base, mais les fonctions membres de la classe dérivée ne peuvent pas être utilisées. indéfinissable. Pourquoi ? Les lignes 71 et 74 sont similaires.

pa était à l'origine un pointeur de la classe de base A, mais maintenant il pointe vers un objet de la classe dérivée D, ce qui change le pointeur implicite this, et pointe également vers un objet de la classe D, donc l'objet de la classe D est finalement utilisé à l'intérieur display () Variables membres, je crois que ce n'est pas difficile à comprendre.

Bien que le compilateur accède à la variable membre via le pointage du pointeur, il n'accède pas à la fonction membre via le pointage du pointeur : le compilateur accède à la fonction membre via le type du pointeur. Pour pa, son type est A, quel que soit l'objet vers lequel il pointe, la fonction membre de la classe A est utilisée

En un mot : le compilateur accède aux variables membres via des pointeurs et utilise les données de l'objet vers lequel pointe le pointeur ; le compilateur accède aux fonctions membres via le type du pointeur et utilise la fonction à laquelle appartient le pointeur.

2) Valeurs incohérentes après affectation

Dans cet exemple, nous affectons le pointeur pd de la classe dérivée finale aux pointeurs de classe de base pa, pb et pc respectivement. Il va de soi que leurs valeurs doivent être égales et pointer vers le même bloc de mémoire, mais le les résultats courants réfutent fortement cette inférence. Seules les valeurs des trois pointeurs pa, pb et pd sont égales, et la valeur de pc est supérieure à toutes. Autrement dit, pc = pd;après l'exécution de l'instruction, les valeurs de pc et pd ne sont pas égales.

 

Attribuer une référence de classe dérivée à une référence de classe de base

Les références sont essentiellement réalisées à travers des pointeurs. Puisque le pointeur de la classe de base peut pointer vers l'objet de la classe dérivée, nous avons des raisons d'en déduire que : la référence de la classe de base peut aussi pointer vers l'objet de la classe dérivée, et ses performances et son pointeur sont similaires.

Modifiez le code à l'intérieur de la fonction main() dans l'exemple ci-dessus pour utiliser des références au lieu de pointeurs :

int main(){
    D d(4, 40, 400, 4000);
   
    A &ra = d;
    B &rb = d;
    C &rc = d;
   
    ra.display();
    rb.display();
    rc.display();

    return 0;
}

 Résultats en cours :
Classe A : m_a=4
Classe B : m_a=4, m_b=40
Classe C : m_c=400

ra, rb, rc sont des références de la classe de base, elles font toutes référence à l'objet de classe dérivé d et appellent display ( ), il ressort des résultats de l'exécution que bien que la variable membre de l'objet de classe dérivée soit utilisée, la fonction membre de la classe dérivée n'est pas utilisée, ce qui est identique à la performance du pointeur.

La raison pour laquelle les performances des références et des pointeurs sont si similaires est qu'il n'y a pas de différence essentielle entre les références et les pointeurs, et les références sont simplement une encapsulation de pointeurs

Enfin, il convient de noter qu'après la transformation vers le haut, les objets, les pointeurs et les références de la classe de base ne peuvent accéder qu'aux membres hérités de la classe de base (y compris les variables membres et les fonctions membres), et ne peuvent pas accéder aux membres nouvellement ajoutés de la classe dérivée.

Bibliographie recommandée pour ce numéro

 règles de don de livres

Jusqu'au 9 juin à 18h

Le tirage aura lieu le 9 juin à 19h

Favoris + like + commentaire (La vie est courte, refusez de vous introvertir !) Chaque personne peut poster jusqu'à 5 commentaires

Le programme sélectionne au hasard 2 partenaires chanceux + le plus grand nombre de commentaires et aime donner un livre physique !

 

 

 

 

        Action on Code : Apprendre la programmation Python avec Zero Basics (ChatGPT Edition) Présente les bases du langage de programmation Python du moins profond au plus profond, et est un didacticiel d'introduction pour les apprenants en programmation basée sur zéro. Le livre comporte 17 chapitres, dont les chapitres 1 à 9 sont les bases, présentant les bases du langage Python, y compris l'installation de l'environnement, les variables d'entrée/sortie, les types de données courants, les opérations mathématiques et logiques, le jugement conditionnel et les instructions de boucle, les types de données composés. , Fonctions, modules et opérations sur les fichiers ; les chapitres 10 à 13 sont des chapitres avancés, qui présentent les connaissances approfondies liées à la programmation Python, y compris le traitement des données de table Excel, la correspondance d'informations à l'aide d'expressions régulières, la conception de programmation orientée objet, le multi-threading et le multi-threading. -threading. Le processus ; Les chapitres 14 à 16 sont de véritables articles de combat, qui présentent 3 projets de combat réels, à savoir, l'utilisation de requêtes pour développer des robots d'exploration Web, l'utilisation de tkinter pour développer des calculatrices GUI et l'utilisation de pygame pour développer des jeux de guerre d'avions ; Le chapitre 17 est ChatGPT, qui présente principalement comment les débutants peuvent utiliser l'outil d'IA le plus populaire ChatGPT pour apprendre la programmation Python. Le contenu de ce livre est systématique et complet, avec des cas riches et des explications faciles à comprendre. Il convient non seulement aux débutants avec des bases nulles en Python, mais convient également comme manuel pour les majors concernées dans les collèges professionnels secondaires et supérieurs. En une phrase, je recommande le tutoriel classique d'introduction pour Xiaobai pour apprendre la programmation Python, combiné avec l'application ChatGPT, afin que vous puissiez l'apprendre, l'utiliser et le faire ! Brève introduction de l'auteur Yuan Xin (Crossin) est titulaire d'un baccalauréat en génie logiciel de l'Université de Nanjing et d'une maîtrise en informatique de l'Université Jiaotong de Shanghai. Il a de l'expérience dans la finance Internet, la réalité virtuelle/réalité augmentée, les jeux et d'autres industries. Il a été invité en tant que conférencier à la Python China Developers Conference et expert en Tencent Cloud Classroom Review. En 2013, il fonde le self-média d'apprentissage de la programmation "Crossin's Programming Classroom", avec plus de 300 000 lecteurs sur toutes les plateformes. Jia Wei, ingénieur senior, a effectué des recherches approfondies sur divers langages de développement, en se concentrant sur le développement de l'intelligence artificielle Python, l'analyse de données et l'apprentissage automatique, et possède une riche expérience dans l'éducation et la formation. Lien d'achat Jingdong : https://item.jd.com/13951968.html

Je suppose que tu aimes

Origine blog.csdn.net/m0_64122244/article/details/131073838
conseillé
Classement