• Aucun résultat trouvé

Exemple 7-2. Opérateur de résolution de portée int valeur(void) // Fonction globale

7.4. Encapsulation des données

/*

Attention à ne pas oublier le ; à la fin de la classe dans un fichier .h ! L’erreur apparaîtrait dans tous les fichiers ayant une ligne #include "client.h" , parce que la compilation a lieu après l’appel au préprocesseur.

*/

Fichier client.cc :

/* Inclut la déclaration de la classe : */

#include "client.h"

/* Définit les méthodes de la classe : */

bool client::dans_le_rouge(void) {

return (Solde<0);

}

bool client::bon_client(void) {

return (Date_Entree<1993);

}

7.4. Encapsulation des données

Les divers champs d’une structure sont accessibles en n’importe quel endroit du programme. Une opération telle que celle-ci est donc faisable :

clientele[0].Solde = 25000;

Le solde d’un client peut donc être modifié sans passer par une méthode dont ce serait le but. Elle pourrait par exemple vérifier que l’on n’affecte pas un solde supérieur au solde maximal autorisé par le programme (la borne supérieure des valeurs des entiers signés). Par exemple, si les entiers sont codés sur 16 bits, cette borne maximum est 32767. Un programme qui ferait :

clientele[0].Solde = 32800;

obtiendrait donc un solde de -12 (valeur en nombre signé du nombre non signé 32800), alors qu’il espérerait obtenir un solde positif !

Il est possible d’empêcher l’accès des champs ou de certaines méthodes à toute fonction autre que celles de la classe. Cette opération s’appelle l’encapsulation. Pour la réaliser, il faut utiliser les mots clés suivants :

public: les accès sont libres ;

private: les accès sont autorisés dans les fonctions de la classe seulement ;

Chapitre 7. C++ : la couche objet

protected: les accès sont autorisés dans les fonctions de la classe et de ses descendantes (voir la section suivante) seulement. Le mot cléprotectedn’est utilisé que dans le cadre de l’héritage des classes. La section suivante détaillera ce point.

Pour changer les droits d’accès des champs et des méthodes d’une classe, il faut faire précéder ceux-ci du mot clé indiquant les droits d’accès suivi de deux points (’:’). Par exemple, pour protéger les données relatives au client, on changera simplement la déclaration de la classe en :

struct client {

private: // Données privées : char Nom[21], Prenom[21];

unsigned int Date_Entree;

int Solde;

// Il n’y a pas de méthode privée.

public: // Les données et les méthodes publiques : // Il n’y a pas de donnée publique.

bool dans_le_rouge(void);

bool bon_client(void) };

Outre la vérification de la validité des opérations, l’encapsulation a comme intérêt fondamental de définir une interface stable pour la classe au niveau des méthodes et données membres publiques et protégées. L’implémentation de cette interface, réalisée en privé, peut être modifiée à loisir sans pour autant perturber les utilisateurs de cette classe, tant que cette interface n’est pas elle-même modifiée.

Par défaut, les classes construites avec struct ont tous leurs membres publics. Il est possible de déclarer une classe dont tous les éléments sont par défaut privés. Pour cela, il suffit d’utiliser le mot cléclassà la place du mot cléstruct.

Exemple 7-4. Utilisation du mot clé class

class client {

// private est à présent inutile.

char Nom[21], Prenom[21];

unsigned int Date_Entree;

int Solde;

public: // Les données et les méthodes publiques.

bool dans_le_rouge(void);

bool bon_client(void);

};

Enfin, il existe un dernier type de classe, que je me contenterai de mentionner : les classesunion. Elles se déclarent comme les classes structetclass, mais avec le mot clé union. Les données sont, comme pour les unions du C, situées toutes au même emplacement, ce qui fait qu’écrire dans l’une

Chapitre 7. C++ : la couche objet

d’entre elle provoque la destruction des autres. Les unions sont très souvent utilisées en programma-tion système, lorsqu’un polymorphisme physique des données est nécessaire (c’est-à-dire lorsqu’elles doivent être interprétées de différentes façons selon le contexte).

Note :Les classes de typeunionne peuvent pas avoir de méthodes virtuelles et de membres statiques. Elles ne peuvent pas avoir de classes de base, ni servir de classe de base. Enfin, les unions ne peuvent pas contenir des références, ni des objets dont la classe a un constructeur non trivial, un constructeur de copie non trivial ou un destructeur non trivial. Pour toutes ces notions, voir la suite du chapitre.

Les classes définies au sein d’une autre classe n’ont pas de droits particuliers sur leur classe hôte. De même, la classe hôte n’a pas plus de droits spécifiques sur les membres de ses sous-classes. Notez que nombre de compilateurs ne respectent pas scrupuleusement ces règles, et donnent parfois des droits aux classes hôtes. Pour autant, le paragraphe 11.8 de la norme C++

est très clair à ce sujet. Il vous faudra donc déclarer amie la classe hôte dans les classes qui sont définies en son sein si vous voulez accéder à leurs membres librement. La manière de procéder sera décrite dans la Section 7.7.2.

7.5. Héritage

L’héritagepermet de donner à une classe toutes les caractéristiques d’une ou de plusieurs autres classes. Les classes dont elle hérite sont appeléesclasses mères,classes de baseouclasses antécé-dentes. La classe elle-même est appeléeclasse fille,classe dérivéeouclasse descendante.

Lespropriétés héritéessont les champs et les méthodes des classes de base.

Pour faire un héritage en C++, il faut faire suivre le nom de la classe fille par la liste des classes mères dans la déclaration avec les restrictions d’accès aux données, chaque élément étant séparé des autres par une virgule. La syntaxe (donnée pourclass, identique pourstruct) est la suivante :

class Classe_mere1 {

/* Contenu de la classe mère 1. */

};

[class Classe_mere2 {

/* Contenu de la classe mère 2. */

};]

[...]

class Classe_fille : public|protected|private Classe_mere1 [, public|protected|private Classe_mere2 [...]]

{

/* Définition de la classe fille. */

};

Dans cette syntaxe,Classe_fillehérite de laClasse_mere1, et desClasse_mere2, etc. si elles sont présentes.

La signification des mots clésprivate,protectedetpublicdans l’héritage est récapitulée dans le tableau suivant :

Chapitre 7. C++ : la couche objet

Tableau 7-1. Droits d’accès sur les membres hérités

Accès aux données mot clé utilisé pour l’héritage

public protected private

mot clé utilisé public public protected private

pour les champs protected protected protected private

et les méthodes private interdit interdit interdit

Ainsi, les données publiques d’une classe mère deviennent soit publiques, soit protégées, soit privées selon que la classe fille hérite en public, protégé ou en privé. Les données privées de la classe mère sont toujours inaccessibles, et les données protégées deviennent soit protégées, soit privées.

Il est possible d’omettre les mots cléspublic,protectedetprivatedans la syntaxe de l’héritage.

Le compilateur utilise un type d’héritage par défaut dans ce cas. Les classes de typestructutilisent l’héritagepublicpar défaut et les classes de typeclassutilisent le mot cléprivatepar défaut.

Exemple 7-5. Héritage public, privé et protégé

class Emplacement {

protected:

int x, y; // Données ne pouvant être accédées // que par les classes filles.

public:

void Change(int, int); // Méthode toujours accessible.

};

void Emplacement::Change(int i, int j) {

x = i;

y = j;

return;

}

class Point : public Emplacement {

protected:

unsigned int couleur; // Donnée accessible // aux classes filles.

public:

void SetColor(unsigned int);

};

void Point::SetColor(unsigned int NewColor) {

couleur = NewColor; // Définit la couleur.

return;

}

Si une classe Cercle doit hériter de deux classes mères, par exemple Emplacement et Forme, sa dé-claration aura la forme suivante :

Chapitre 7. C++ : la couche objet

class Cercle : public Emplacement, public Forme {

/*

Définition de la classe Cercle. Cette classe hérite

des données publiques et protégées des classes Emplacement et Forme.

*/

};

Les membres des classes de base auxquels les classes dérivées ont accès peuvent être redéclarés avec de nouveaux droits d’accès par celles-ci. Par exemple, une donnée membre publique héritée publiquement peut être protégée unitairement. Inversement, une donnée membre protégée peut être rendue publique dans la classe dérivée. Cela se fait via une simple redéclaration à l’aide du mot clé using, avec des droits d’accès différents. Ce mot clé s’emploie comme suit :

using Base::membre;

oùmembreest le nom du membre de la classe de base que l’on veut redéclarer. Nous verrons plus en détail les diverses utilisation de ce mot clé dans la Section 10.2.

Il est possible de redéfinir les fonctions et les données des classes de base dans une classe dérivée.

Par exemple, si une classe B dérive de la classe A, et que toutes deux contiennent une donnéed, les instances de la classe B utiliseront la donnéedde la classe B et les instances de la classe A utiliseront la donnéedde la classe A. Cependant, les objets de classe B contiendront également un sous-objet, lui-même instance de la classe de base A. Par conséquent, ils contiendront la donnéedde la classe A, mais cette dernière sera cachée par la donnéedde la classe la plus dérivée, à savoir la classe B.

Ce mécanisme est général : quand une classe dérivée redéfinit un membre d’une classe de base, ce membre est caché et on ne peut plus accéder directement qu’au membre redéfini (celui de la classe dérivée). Cependant, il est possible d’accéder aux données cachées si l’on connaît leur classe, pour cela, il faut nommer le membre complètement à l’aide de l’opérateur de résolution de portée (::).

Le nom complet d’un membre est constitué du nom de sa classe suivi de l’opérateur de résolution de portée, suivis du nom du membre :

classe::membre

Exemple 7-6. Opérateur de résolution de portée et membre de classes de base

#include <stdlib.h>

struct Base {

int i;

};

struct Derivee : public Base {

int i;

int LitBase(void);

};

int Derivee::LitBase(void) {

Chapitre 7. C++ : la couche objet return Base::i; // Renvoie la valeur i de la classe de base.

}

int main(void) {

Derivee D;

D.i=1; // Accède à l’entier i de la classe Derivee.

D.Base::i=2; // Accède à l’entier i de la classe Base.

return EXIT_SUCCESS;

}