Dernière mise à jour :2008-07-24

informatique

Résumé des notions de classes, héritage et dérivation

Le langage c++ comprend un certain nombre de types prédéfinis comme par exemple le type entier qui a pour but d'habriter des valeurs numériques entières. En utilisant certains de ces types, on peut fabriquer un programme simple. Cependant, penser à faire la création d'une application doté d'une interface graphique simplement en utilisant de tels types primaires se révèlerait une tâche plutôt ardu. Le langage c++ offre donc la possibilité aux programmeurs de créer leur propre types.

Faire la création d'un type implique la définition de la classe qui aura pour fontion de recevoir celui-ci. On dit d'une classe que c'est un ensemble de types ou propriétés différent(e)s et de fonctions ou méthodes. Par exemple, on pourrait définir une classe chien qui disposerait des propriétés pattes, gueule, oreilles etc... et qui aurait pour fonction aboyer, manger, avancer etc.

Ce qui utilise la classe est nommé le client. Celui-ci peut profiter des propriétés et méthodes de celle-ci sans avoir à ce préocuper de connaitre sa forme et son fonctionnement interne. Par exemple, si on vous demande: "Que faites vous pour voir?" vous répondrez bien sûr que vous utilisez vos yeux. Mais si on vous demande de quoi ceux-ci sont-il composé, vous répondrez probablement que vous n'en avez aucune idée.

Déclaration d'une classe:

À la base, une déclaration de classe se fait par l'utilisation du mot clé 'class' suivit du nom de la classe. Celui-ci est ensuite suivit de deux accolades contenants les déclarations des variable membres et des différentes fonctions. Après l'accolade fermante vient un point-virgule ';'. Voici un exemple de la syntaxe à utiliser:

class nom_de_la_classe
{
   short int variable_membre_1;
   double variable_membre_2;
   void fonction1();
};

Définition d'un objet

Un objet est une instance de la classe à laquelle il est relié. La définition d'un objet est aussi simple que la définition d'une variable d'un type quelconque. On écrit d'abord le nom de la classe à laquelle appartient l'objet qui doit être créé et ensuite on indique le nom de l'objet. La syntaxe est la suivante:

nom_de_la_classe nom_de_l_objet;

Considérons l'exemple suivant qui sera relié à la suite des informations de ce texte:

Listing 1.0 Exemple de classe simple
1. class Chien()
2. {
3. public:
4.  Chien();
5.  ~Chien();
6. 
7.  unsigned int m_age;
8.  unsigned int m_taille;
9.  unsigned int m_poids;
10. 
11.  void aboyer();
12.  }

Différence entre les mots clefs 'public' et 'private'

Il faut préciser que par défaut, tous les membres (fonctions et variables) sont privé. Donc, si on reprennait l'exemple plus haut en enlevant le mot 'public', tous les membres de la classe seraient interprétés comme privé. Si ensuite vous écrivez le code suivant:

Chien monChien;
Chien.m_age = 4;

Si vous compilez ce code sans avoir noté le mot 'public' dans la classe, le compilateur génèrera une erreur parce qu'il est impossible d'affecter une valeur à la propriété m_age de l'objet Chien car cette variable ne peut être modifié que par la classe elle-même. Par contre, en ajoutant le mot 'public', toute affectation devient possible bien sûr tant quel reste dans le domaine du type de la variable.

Par convention, il est préférable que les variables membres d'une classe demeure privées. Pour y permettre l'accès, on fait la création de méthode publique qui ce charge de la modification. Celà permet d'effectuer une validation des affectations. Par exemple, la variable m_age est publique, un client pourrait tenter d'y affecter une variable de type caractère par exemple qui n'est pas du tout du type de la variable. Ceci provoquerait une erreur. Par contre, si la variable est rangée dans un bloque privé, on peut déclarer une fonction public destiner à vérifier la valeur qui tente d'être affecté. Si la valeur est acceptable, la fontion change l'ancienne valeur de la variable membre par la valeur passée à la fonction. Par contre, si la valeur est innacceptable, la fonction n'affectera pas la variable par la nouvelle valeur. L'exemple suivant montre un exemple de ce type de validation.

Listing 1.0 Exemple de classe simple
1. #include <iostream.h>
2.
3. class Chien
4. {
5. public:
6.   Chien();
7.   Chien(unsigned int intAge);
8.   ~Chien();
9. 
10.   unsigned int m_age_Get() const;
11.   void m_age_Set(unsigned int intAge);
12. 
13. private:
14.   unsigned int m_age;
15. };
16. 
17. Chien::Chien()
18. {
19. }
20. 
21. Chien::Chien(unsigned int intAge)
22. {
23.   m_age = intAge;
24. }
25. 
26. Chien::~Chien()
27. {
28. }
29. 
30. unsigned int Chien::m_age_Get() const
31. {
32.   return m_age;
33. }
34. 
35. void Chien::m_age_Set(unsigned int intAge)
36. {
37.   if (intAge < 20)
38.    m_age = intAge;
39. }
40. 
41. void main()
42. {
43.   Chien monChien(4);
44.   cout << "L'age de mon chien est: \n" ;
45.   cout << monChien.m_age_Get() ;
46.   cout << "\n";
47.   monChien.m_age_Set(30);
48.   cout << "L'age de mon chien est: \n";
49.   cout << monChien.m_age_Get();
50.   cout << "\n";
51.   monChien.m_age_Set(10);
52.   cout << "L'age de mon chien est: \n";
53.   cout << monChien.m_age_Get();
54.   cout << "\n";
55. }

Résultat de l'exécution de ce petit programme

L'age de mon chien est:
4
L'age de mon chien est:
4
L'age de mon chien est:
10

La fontion m_age_Get permet d'obtenir la valeur de la variable privé m_age. La fonction m_age_Set permet de définir la valeur de la variable m_age de façon conditionnel c'est à dire que si un client tente de définir l'age d'un objet chien plus élevé que 20 an, la veleur de la variable m_age n'est pas modifiée. Pour cette raison, l'instruction de la ligne 49 affiche 4 et non 30.

Accès aux membres publiques d'une classe

L'accès aux membres d'une classe se fait par l'opérateur point (.). Par exemple, dans le listing ci-haut, l'objet monChien accède aux méthodes de la classe Chien à cinq reprise soit aux lignes 45, 47, 49, 51, 53.

Constructeurs et destructeurs

Un constructeur a pour fonction d'initialiser l'instance d'une classe c'est à dire un objet. Toute classe possède un constructeur. Celui-ci peut contenir des paramètres d'entré mais ne retourne pas de valeur.

Tout constructeur porte le même nom que la classe elle-même. Dans le listing ci-dessus, on peut voir un exemple de constructeur de la classe Chien en ligne 6.

En programmation, tout ce qui est construit est voué tôt ou tard, à être détruit, de là la nécessité d'ajouter un destructeur à chaque classe. Le destructeur poursuit le but inverse du constructeur. Il sert à libérer l'espace mémoire alloué à l'objet. La syntaxe de déclaration d'un destructeur est la même que celle d'un constructeur sauf que la déclaration est précédée du caractère '~'. Dans le listing ci-dessus, un destructeur est déclaré à la ligne 8.

Un constructeur étant une fonction, on peut lui faire exécuter une série d'instruction. Dans l'exemple ci-haut, l'un des constructeurs de la classe Chien accepte un argument en entré. En ligne 23 l'unique instruction de la fonction initialise la variable m_age à l'aide de la valeur passée en paramètre au constructeur.

Il est à noter que le compilateur offre toujours un constructeur ainsi qu'un destructeur par défaut si aucun de ceux-ci n'est déclaré dans la classe.

Application du mot const aux fonctions membre

Le mot const ajouté à une fonction membre permet de sécuriser les valeurs des variables membres en interdisant toute modifications à celles-ci par la dite fonction. Dans notre exemple ci-haut, m_age_Get() est déclaré en utilisant le mot clé const car elle ne modifit par la variable m_age mais en retourne la valeur. Par contre, la fonction m_age_Set() ne peut être déclaré avec const puisque celle-ci change la valeur de la variable m_age. Si cette fonction aurait été déclarée avec le mot const, le compilateur aurait retourné une erreur à la compilation.

Fichiers de déclarations de classes et leurs méthodes

En général, les classes sont définie dans des fichiers d'entêtes (header file). L'extention de ces fichiers est notté généralement en utilisant le nom de la classe suivit de l'extention '.h' ou encore '.hpp'. Les méthodes de la classe pour leur par sont généralement définies dans un second fichier. Le nom du fichier portant lui aussi le nom de la classe suivit par contre d'une extention différente qui peut être '.c' dans le cas d'un fichier écrit en langage c ou '.cpp' dans le cas d'un fichier en c++. Donc, en reprenant le code de l'exemple plus haut, les lignes 3 à 15 pourraient être enregistrées dans un fichier nommé 'Chien.h' et les lignes 17 à 55 dans un autres nommé 'Chien.cpp'. Il faut noter cepandant, qu'en utilisant cette dernière façon de procéder, il serait important d'ajouter une ligne supplémentaire dans le second fichier indiquant le nom du fichier d'entête associé. Cette instruction pourrait ressemblé à ceci: #include "Chien.h";

Héritage et dérivation

Pour clarifier les deux points ci-dessus, reprenons l'exemple de notre Chien. À la base, qu'est-ce qu'un chien? C'est bien sûr un être vivant tout comme vous et moi, un chat ou encore une tortue. Par contre, comparativement à vous et moi, le chien et le chat, la tortue n'est pas un mammifère mais un reptile. En fesant ces deux énoncés, une relation hiérarchique vient d'être créé qui pourrait prendre la forme illustrée ci-dessous:


Figure 1.0 Exemple d'hiérarchie

Le principe de l'héritage est de permettre à une classe d'hériter des fonctionnalités d'une autre. Avant d'avoir fait la création de notre classe Chien, il aurait donc été possible de faire la création de deux autres classes. La première nommée 'EtreVivant' comprendrait toute les caractéristiques générales à tout être vivant et la seconde 'Mammifere' comprendrait pour sa part les caractéristiques de tout mammifères. La classe Mammifere hériterait des fonctionnalités de la classe EtreVivant et finalement, la classe Chien hériterait de la classe Mammifere et du même coup de la classe EtreVivant. On dirait alors que la classe Chien dérive de la classe Mammifere. La dérivation est un concept d'appartenance. Lorsqu'une classe hérite d'une autre, on dit qu'elle dérive de la classe parente donc en langage humain, qu'elle 'est une ou un ...'.

La syntaxe utilisée pour faire en sorte qu'une classe hérite d'une autre et donc qu'elle dérive d'une autre est la suivante:

class nom_de_la_classe : public nom_de_la_classe_parente

Plus haut dans ce texte, il était question de membres privé et publique. Tel que mentionné, les membres privés ne pouvaient être vu par le client contrairement au membres publique. Losqu'on parle d'héritage et de dérivation, il peut s'avérer utile qu'une classe dérivée puisse lire les valeurs de certains membres de la classe parente sans toutefois que celle-ci puisse modifier ces variables. En utilisant le mot public, la classe dérivée pourrait procédée à une modification ce qui n'est pas souhaité tandis qu'en utilisant le mot private, la classe dérivée ne pourrait même pas accéder à la valeur. Pour contrer ce problème, il existe le mot 'protected' qui permet de protéger les données en écriture mais sans toutefois restreindre leur accès en lecture.

Le listing suivant montre un exemple d'héritage et donc de dérivation ainsi que de l'utilisation du mot clef 'protected'.

Listing 2.0 Exemple d'héritage et dérivation
1. #include <iostream.h>
2. enum RACE{Chinemyde, Clemmyde, Emyde, Graptemyde};
3. 
4. class EtreVivant
5. {
6. public:
7.   EtreVivant();
8.   EtreVivant(int intAge);
9.   ~EtreVivant();
10. protected:
11.   unsigned int m_age;
12. };
13. 
14. class Reptile : public EtreVivant
15. {
16. public:
17.   Reptile();
18.   Reptile(int intAge, int intPoidOeuf);
19.   ~Reptile();
20. protected:
21.   unsigned int m_poidOeuf;
22. };
23. 
24. class Tortue : public Reptile
25. {
26. public:
27.   Tortue();
28.   Tortue(int intAge, int intPoidOeuf, RACE raceRace);
29.   ~Tortue();
30. protected:
31.   RACE m_race;
32. };
33. 
34. EtreVivant::EtreVivant():m_age(1)
35. {
36.   cout << "Constructeur de EtreVivant sans argument\n";
37. }
38. 
39. EtreVivant::EtreVivant(int intAge):m_age(intAge)
40. {
41.   cout << "Constructeur de EtreVivant a 1 argument\n";
42. }
43. 
44. EtreVivant::~EtreVivant()
45. {
46. }
47. 
48. Reptile::Reptile()
49. {
50.  cout << "Constructeur de Reptile sans arguments\n";
51. }
52. 
53. Reptile::Reptile(int intAge, int intPoidOeuf):EtreVivant(intAge), m_poidOeuf(intPoidOeuf)
54. {
55.   cout << "Constructeur de Reptile a 2 arguments\n";
56. }
57. 
58. Reptile::~Reptile()
59. {
60. }
61. 
62. Tortue::Tortue()
63. {
64.   cout << "Constructeur de Tortue sans argument\n";
65. }
66. 
67. Tortue::Tortue(int intAge, int intPoidOeuf, RACE raceRace):m_race(raceRace), Reptile(intAge, 68. intPoidOeuf)
69. {
70.   cout << "Constructeur de Tortue a 3 arguments\n";
71. }
72. 
73. Tortue::~Tortue()
74. {
75. }
76. 
77. void main()
78. {
79.   Tortue maTortue(1,30,Clemmyde);
80. }

Substitution de fonctions membres

Il arrive qu'une classe dérivée possède une méthode identique à celle de sa classe parente. Par identique on dit que la méthode est dotée d'une signature (nom de la fonction, liste des paramètres et si il y a lieu, mot clé const ) et d'un type de renvois pareil à celui de la méthode de la classe parente. Dans un tel cas, la fonction exécuté lors de l'appel est la fonction déclarée dans la classe dérivée. Par exemple, si dans le listing ci-dessus, la classe Reptile comprennait une méthode nommée "deplacement" qui écrirait "Le reptile se déplace." et que la classe Tortue posèderait aussi une fonction similaire mais qui écrirait "La tortue se déplace.", lorsqu'une instance de la classe tortue appellerait la fonction "deplacement", il serait écrit "La tortue se déplace" à l'écran. On dit alors qu'il y a substitution de fonction membre.

À noter qu'une méthode substituée peut tout de même être appelée en idiquant son nom complet après le nom de la classe parente. Dans l'exemple cité ci-haut, pour faire écrire "Le reptile se déplace.", il conviendrait donc d'utiliser cette syntaxe soit:

Tortue maTortue;
Tortue.Reptile::deplacement();

Ce qui fait la force du langage c++ c'est sa capacité à réutiliser facilement du code déjà écrit plutôt que d'obliger le programmeur à retaper des centaines de fois les même lignes pour rien. Par contre, ce qui en fait la difficulté c'est sans doute sa popularité et surtout, le nombre impressionnant de classe existante fournies avec certains compilateurs. Heureusement, il existe des outils destinés à en facilité l'implémentation tel Microsoft Visual c++ par exemple.

Auteur : Sylvain Bilodeau

Date de mise en ligne : 2002-09-20 01:00:00

Langage c - Les classes

Je vous remercie pour cet article très bien fait qui m'a fait comprendre ce qui restait trouble pour moi avec les autres cours sur le C++.

2008-06-27 00:00:00