Programmation C · 11 min read · Oct 10, 2025
Apprendre C/C++ Étape par Étape - Page 16
16. Étape par Étape C/C++ — Programmation C++ - Polymorphisme
Polymorphisme
| | 1. Surcharge de Fonction
- Polymorphisme
- Types de polymorphisme
- Fonctions membres normales accessibles avec des pointeurs
- Fonction Virtuelle
- Fonction Pure
- Affectation et Initialisation par Copie
- Le Constructeur de COPIE
- Pointeur ‘this’ |
1. Surcharge de Fonction
Si une fonction avec son nom diffère par le comportement des arguments, elle est appelée polymorphisme de fonctions ou surcharge de fonction.
| | // Un programme exemple pour démontrer l’utilisation de la surcharge de fonction
#include
using namespace std;
void printline()
{
for(int i=0;i<=80; i++) cout << “-“;
}
void printline(int n)
{
for(int i =0 ;i<=n;i++) cout << “-“;
}
void printline(int n,char ch)
{
for(int i=0;i<=n; i++) cout << ch;
}
int main()
{
printline();
printline(5);
printline(10, ‘*’);
return 0;
}
|
Polymorphisme
Le polymorphisme est l’une des caractéristiques cruciales de la POO. Cela signifie simplement un nom, plusieurs formes. Nous avons déjà vu comment le concept de polymorphisme est mis en œuvre en utilisant des fonctions et des opérateurs surchargés. Les fonctions membres surchargées sont sélectionnées pour être invoquées en faisant correspondre les arguments, tant en type qu’en nombre. Le compilateur connaît cette information au moment de la compilation et, par conséquent, il est capable de sélectionner la fonction appropriée pour un appel particulier au moment de la compilation elle-même. Cela s’appelle liaison précoce ou liaison statique ou liaison statique. Également connu sous le nom de polymorphisme à temps de compilation, liaison précoce signifie simplement qu’un objet est lié à son appel de fonction au moment de la compilation.
Maintenant, considérons une situation où le nom de la fonction et le prototype sont les mêmes dans les classes de base et dérivées. Par exemple, considérons les définitions de classe suivantes.
| | #include
using namespace std;
class A
{
int x;
public : void show();
};
class B : public A
{
int y;
public : void show();
};
int main()
{
B b;
b.show();
return 0;
} |
Comment utilisons-nous la fonction membre show( ) pour imprimer les valeurs des objets des classes A et B ? Puisque le prototype de show( ) est le même aux deux endroits, la fonction n’est pas surchargée et donc la liaison statique ne s’applique pas. En fait, le compilateur ne sait pas quoi faire et différera la décision.
Il serait bien si la fonction membre appropriée pouvait être sélectionnée pendant que le programme s’exécute. Cela est connu sous le nom de polymorphisme à l’exécution. Comment cela pourrait-il se produire ? C++ prend en charge un mécanisme connu sous le nom de fonction virtuelle pour atteindre le polymorphisme à l’exécution. À l’exécution, lorsqu’il est connu quels objets de classe sont en considération, la version appropriée de la fonction est appelée.
Puisque la fonction est liée à une classe particulière beaucoup plus tard après la compilation, ce processus est appelé liaison tardive. Il est également connu sous le nom de liaison dynamique ou liaison dynamique car la sélection de la fonction appropriée est effectuée dynamiquement à l’exécution.
3. Types de Polymorphisme
Le polymorphisme est de deux types, à savoir.
| 1 | Polymorphisme à temps de compilation
Ou Liaison précoce
Ou Liaison statique
Ou Polymorphisme de liaison statique. Un objet est lié à son appel de fonction au moment de la compilation. |
| 2 | Polymorphisme à l’exécution
Ou liaison tardive
Ou liaison dynamique
Ou Polymorphisme de liaison dynamique. La sélection et la fonction appropriée sont effectuées dynamiquement à l’exécution. |
| |
|
La liaison dynamique est l’une des caractéristiques puissantes de C++. Cela nécessite l’utilisation de pointeurs vers des objets. Nous discuterons en détail de la façon dont les pointeurs d’objet et les fonctions virtuelles sont utilisés pour mettre en œuvre la liaison dynamique.
4. Fonctions Membres Normales Accédées avec des Pointeurs
Le programme ci-dessous consiste en une classe de base
| | / Fonctions normales accessibles depuis un pointeur /
/ Polymorphisme avec des classes (sans utiliser le polymorphisme VIRTUEL) /
#include
using namespace std;
class BASE
{
public :
void disp() { cout << “\nVous êtes dans la classe BASE “; }
};
class DERIVED1 : public BASE
{
public :
void disp() { cout << “\nVous êtes dans la classe DERIVED1”; }
};
class DERIVED2 : public BASE
{
public :
void disp() { cout << “\nVous êtes dans la classe DERIVED2”; }
};
int main()
{
DERIVED1 d1; // Objet de la classe dérivée 1
DERIVED2 d2; // Objet de la classe dérivée 2
BASE *b; // pointeur vers la classe de base
b=&d1; // Assigner l’adresse de d1 dans le pointeur b
b->disp(); // appel à disp()
b=&d2; // Assigner l’adresse de d2 dans le pointeur b
b->disp(); // appel à disp()
return 0;
} |
Le programme ci-dessus démontre :
| | • Une classe BASE
• Classes DERIVED1, DERIVED2 dérivées de BASE
• Objets de classes dérivées (d1,d2)
• Pointeur de classe BASE b | | | *Sortie Vous êtes dans la classe BASE
Vous êtes dans la classe BASE |
5. Fonction Virtuelle
Virtuel signifie existant en effet mais pas en réalité.
Une fonction membre peut être déclarée comme fonction virtuelle en précédant la fonction membre par le mot-clé virtuel.
| | / Polymorphisme avec des Classes (Polymorphisme Virtuel) /
#include
using namespace std;
class B
{
public :
void show(){ cout << “\nméthode Show() de la classe B “; }
virtual void disp() { cout << “\nméthode disp() de la classe B”; }
};
class D : public B
{
public :
void show(){cout << “\nméthode Show() de la classe D “; }
void disp(){ cout << “\nméthode disp() de la classe D”; }
};
int main()
{
D d1;
d1.show();
d1.disp(); // Membre de la classe de base
B b;
D d;
B *Bptr;
Bptr = &b;
Bptr->show();
Bptr->disp(); // Membre de la classe de base
Bptr=&d;
Bptr->show(); // membres de la classe dérivée
Bptr->disp(); // Membre de la classe de base
return 0;
} | | | Sortie méthode Show() de la classe D
méthode disp() de la classe D |
6. Fonction Pure
Une fonction définie dans une classe de base et n’ayant pas de définition relative à la classe dérivée est appelée fonction pure. En d’autres termes, une fonction pure est une fonction virtuelle sans corps.
| | #include
using namespace std;
class B
{
public :
void show(){ cout << “\nméthode Show() de la classe B “; }
virtual void disp() = 0; // fonction virtuelle pure
};
class D : public B
{
public :
void show(){cout << “\nméthode Show() de la classe D “; }
void disp(){ cout << “\nméthode disp() de la classe D”; }
};
int main()
{
D d1;
d1.show(); // O/P : méthode Show() de la classe D
d1.disp(); // O/P : méthode disp() de la classe D
D d;
B *Bptr;
Bptr=&d;
Bptr->show(); // O/P : méthode Show() de la classe B
Bptr->disp(); // O/P : méthode disp() de la classe D
return 0;
} |
Bptr -> show() * est la fonction exécutable par défaut de la classe de base Bptr -> disp() est la fonction exécutable par défaut de la classe de base mais elle est déclarée comme une fonction virtuelle pure donc à l’exécution, la fonction disp()* de la classe dérivée sera appelée.
| | / Programme pour démontrer l’avantage des fonctions virtuelles pures /
#include
using namespace std;
enum boolean { false, true };
class NAME
{
protected : char name[20];
public :
void getname()
{ cout << “Entrez le nom :”; cin >> name; }
void showname()
{ cout << “\nLe nom est “<< name; }
boolean virtual isGradeA() = 0; // fonction virtuelle pure
};
class student : public NAME
{
private : float avg;
public :
void getavg()
{
cout << “\nEntrez la moyenne de l’étudiant :”;
cin >> avg;
}
boolean isGradeA()
{ return (avg>=80) ? true : false ; }
};
class employee : public NAME
{
private : int sal;
public :
void getsal()
{ cout << “\nEntrez le salaire “; cin >> sal; }
boolean isGradeA()
{ return (sal>=10000) ? true : false ; }
};
int main()
{
NAME names[20]; // nombre de pointeurs vers le nom
student s; // pointeur vers l’étudiant
employee *e; // pointeur vers l’employé
int n = 0; // nombre de noms sur la liste
char choice;
do{
cout << “Entrez Étudiant ou Employé (s/e) “;
cin >> choice;
if(choice==’s’)
{
s = new student; // créer un nouvel étudiant
s->getname();
s->getavg();
names[n++]=s;
}
else
{
e = new employee; // créer un nouvel employé
e->getname();
e->getsal();
names[n++]=e;
}
cout << “Entrez un autre (y/n) ?”; // faire un autre
cin >> choice;
} while(choice==’y’);
for(int j=0; j
names[j]->showname( );
if(names[j]->isGradeA( )==true)
cout << “Il est une personne de Grade 1”;
}
return 0;
} |
7. Affectation et Initialisation par Copie
Le compilateur C++ est toujours occupé en votre nom, faisant des choses que vous ne pouvez pas être dérangé de faire. Si vous prenez les commandes, il se conformera à votre jugement ; sinon, il fera les choses à sa manière. Deux exemples importants de ce processus sont l’opérateur d’affectation et le constructeur de copie.
Vous avez utilisé l’opérateur d’affectation de nombreuses fois, probablement sans trop y penser. Supposons que a1 et a2 soient des objets. À moins que vous ne disiez au compilateur autrement, l’instruction.
a2 = a1; // définir a2 à la valeur de a1
fera que le compilateur copiera les données de a1, membre par membre, dans a2. C’est l’action par défaut de l’opérateur d’affectation, =.
Vous êtes également familier avec l’initialisation des variables, l’initialisation d’un objet avec un autre objet, comme dans
alpha a2(a1); // initialiser a2 à la valeur de a1
Cela provoque une action similaire. Le compilateur crée un nouvel objet, a2, et copie les données de a1, membre par membre, dans a2. C’est l’action par défaut du constructeur de copie.
Ces deux activités par défaut sont fournies, gratuitement, par le compilateur. Si la copie membre par membre est ce que vous voulez, vous n’avez besoin de prendre aucune autre mesure. Cependant, si vous souhaitez que l’affectation ou l’initialisation fasse quelque chose de plus complexe, alors vous pouvez remplacer les fonctions par défaut. Nous discuterons des techniques pour surcharger l’opérateur d’affectation et le constructeur de copie séparément.
Surcharge de l’Opérateur d’Affectation
| | // Surcharge de l’Opérateur d’Affectation ( = )
#include
using namespace std;
class alpha
{
private:
int data;
public:
alpha() { } // constructeur sans argument
alpha( int d )
{ data = d; } // constructeur à un argument
void display()
{ cout << data; } // Afficher les données
alpha operator =(alpha & a) // opérateur = surchargé
{
data = a.data; // pas fait automatiquement
cout << “\n Opérateur d’affectation invoqué “;
return alpha(data);
}
};
int main()
{
alpha a1(37);
alpha a2;
a2 = a1; // Invoquer = surchargé
cout << “\n a2 = “; a2.display(); // afficher a2
alpha a3 = a2; // ne PAS invoquer =
cout << “\n a3 = “; a3.display(); // afficher a3
return 0;
} | | | Sortie: a2 = 37
a3 = 37 |
8. Le CONSTRUCTEUR DE COPIE
Comme nous l’avons discuté, nous pouvons définir et en même temps initialiser un objet à la valeur d’un autre objet avec deux types d’instructions :
alpha a3(a2); // Initialisation par copie
alpha a3 = a2; // initialisation par copie, syntaxe alternative
Les deux styles de définition invoquent un constructeur de copie : c’est-à-dire un constructeur qui copie son argument dans un nouvel objet. Le constructeur de copie par défaut, qui est fourni automatiquement par le compilateur pour chaque objet, effectue une copie membre par membre. Cela est similaire à ce que fait l’opérateur d’affectation ; la différence est que le constructeur de copie crée également un nouvel objet.
L’exemple suivant démontre le constructeur de copie.
| | #include
using namespace std;
class alpha
{
private :
int data;
public:
alpha( ) { } // constructeur sans arguments
alpha(int d) { data = d; } // constructeur à un argument
alpha(alpha& a) // constructeur de copie
{
data = a.data;
cout << “\nConstructeur de copie invoqué”;
}
void display( )
{ cout << data; }
void operator = (alpha& a) // opérateur = surchargé
{
data = a.data;
cout << “\nOpérateur d’affectation invoqué”;
}
};
int main()
{
alpha a1( 37 );
alpha a2;
a2 = a1; // invoquer = surchargé
cout << “ a2 = “; a2.display(); // afficher a2
alpha a3( a1 ); // invoquer le constructeur de copie
// alpha a3 = a1; // définition équivalente de a3
cout << “ a3 = “; a3.display(); // afficher a3
return 0;
} |
Le programme ci-dessus surcharge à la fois l’opérateur d’affectation et le constructeur de copie.
L’opérateur d’affectation surchargé est similaire à celui de l’exemple précédent.
9. Pointeur ‘this’
C++ utilise un mot-clé unique appelé this pour représenter un objet qui invoque une fonction membre. This est un pointeur qui pointe vers l’objet pour lequel la fonction this a été appelée.
Ce pointeur this effectue simplement la tâche de retourner l’objet lui-même.
Le programme suivant définit les objets i, j et i est assigné avec la valeur de 5 et l’objet entier de i est assigné par sa fonction membre à j
| | #include
using namespace std;
class A
{
int a;
public:
A() { }
A(int x)
{ a = x; }
void display()
{
cout << a;
}
A get()
{
return *this; // Retourner lui-même
}
};
int main()
{
A i(5);
A j;
j = i.get();
j.display();
return 0;
} |
Réf : Programmation orientée objet dans Turbo C++ : Robert Lafore
Recevez de nouveaux articles dans votre boîte de réception.
Aucun spam. Désabonnez-vous à tout moment.