La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Structures de données IFT-10541 Abder Alikacem Introduction aux classes Département d’informatique et de génie logiciel Édition Septembre 2009.

Présentations similaires


Présentation au sujet: "Structures de données IFT-10541 Abder Alikacem Introduction aux classes Département d’informatique et de génie logiciel Édition Septembre 2009."— Transcription de la présentation:

1

2 Structures de données IFT-10541 Abder Alikacem Introduction aux classes Département d’informatique et de génie logiciel Édition Septembre 2009

3 Plan Classes et objets Constructeur et destructeur Méthode et classe friend Méthodes statiques Surcharge des opérateurs Le pointeur this Constructeur par copie et opérateur d’affectation

4 Les classes Définition d’un nouveau type au sens C++ : ensemble de méthodes (fonctionnalités) auxquelles sont ajoutés des membres (données). Les classes sont utilisées afin de fournir aux programmeurs les outils Nécessaires pour concevoir des nouveaux types qui soient aussi faciles d'utilisation que les types de base. Les classes sont comme les structures du langage C auxquelles on a ajouter des fonctionnalités supplémentaires. En particulier: 1.Tout comme les structures, les classes peuvent contenir des définitions de variables. 2. Les classe peuvent en plus contenir des définitions de fonctions. 3. Par défaut les membres (variables ou fonctions) d'une classe sont privés et inaccessibles au reste du programme. Pour rendre un membre accessible celui-ci doit explicitement être déclaré public.

5 Les classes Une classe est une implémentation d’un type. Une variable de ce type (une instance de la classe) est appelée un objet. Les classes sont des structures d'objets, c'est-à-dire la déclaration de l'ensemble des entités qui composeront des objets. Une classe peut être considérée comme un moule à partir duquel on peut créer des objets. Un objet est donc "issu" d'une classe. C'est une instanciation d'une classe, c'est la raison pour laquelle on pourra parler indifféremment d'objet ou d'instance.

6 Les classes Une classe est composée de deux parties: - Les attributs (parfois appelés données membres): il s'agit des données représentant l'état de l'objet - Les méthodes (parfois appelées fonctions membres): il s'agit des opérations applicables aux objets Si on définit la classe voiture, les objets Toyota_Civic, Mustang2003 seront des instanciations de cette classe. Il pourra éventuellement exister plusieurs objets Toyota_Civic, différenciés par leur numéro de série. Deux instanciations de classes pourront même avoir tous leurs attributs égaux sans pour autant être le même objet.

7 Les classes Public, private, protected Par défaut, dans une classe, seules les méthodes de cette classe peuvent accéder aux autres méthodes et aux membres. On dit que ceux-ci sont en accès private. Pour être utilisable, une classe doit donc rendre publiques certaines méthodes. La spécification de public : (resp. private :) à l’intérieur de la classe change l’accès par défaut pour les membres et fonctions situés après cette déclaration. Plusieurs public : (resp. private :) peuvent apparaître dans une classe. Troisième accès : protected, l’accès est autorisé aussi aux classes dérivées (nous verrons cela plus tard), mais pas pour les utilisations externes.

8 Les classes Classe ou struct Il existe une autre déclaration de classe : struct (accès public par défaut pour tous les membres et méthodes). class (accès privé par défaut pour tous les membres et méthodes). class Point { // Deux membres, les coordonnées // du point, inaccessibles de l’extérieur int x, y; // Interface utilisateur, accessible de l’extérieur public: // Méthodes d’accès (en lecture) aux membres int abscisse() { return x; } int ordonnee() { return y; } // Méthodes de modification des membres void affect_x(int x) { x = x; } void affect_y(int y) { y = y; } }; struct Point { // les coordonnées du point // sont accessibles de l’extérieur int x, y; private: // Ce qui suit, jusqu’au prochain // "public:" n’est pas accessible... };

9 Les classes Nous allons illustrer ce concept à l'aide de l'exemple des tableaux. Lorsque on passe un tableau en paramètre à une fonction, celle-ci connaît l'adresse du début du tableau mais pas l'adresse de la fin. C'est pourquoi il est presque toujours nécessaire de fournir aussi la taille du tableau. Par exemple: void copier(int *A, int n, int *B, int m) { int min = (n<m)?n:m; for (int i=0; i<min; i++) B[i]=A[i]; } int echanger(int *A, int n, int *B, int m) { if (n!=m) return 0; int* tmp=new int[n]; copier(A, n, tmp, n); copier(B, m, A, n); copier(tmp, n, B, m); return 1; }

10 Les classes On doit donc fournir quatre paramètres alors qu'on ne veut réellement Passer que deux objets. Ce problème peut être résolu en définissant notre propre objet tableau qui inclue aussi sa taille: la classe Tableau. class Tableau{ private:int nbelements; int *T; public:Tableau(int); ~Tableau(); int longueur() const; int element(int) const; void modifier (int, int); };

11 Les classes La déclaration de la classe commence par le mot clef class et est encadrée par une paire d'accolades. L'accolade finale est suivie d'un point virgule. Les membres déclarés après le mot clef public forment l'interface de la classe. Ceux qui suivent le mot private sont invisibles de l'utilisateur ; L'ordre de déclaration des méthodes et des attributs est laissé au libre arbitre du programmeur. La déclaration des attributs est semblable à la déclaration d'une variable alors que celle d'une méthode ressemble à s'y méprendre au prototype d'une fonction ! Néanmoins, il ne faut pas oublier que chaque méthode possède un argument caché : l'objet sur lequel elle est invoquée.

12 Les fonctions membres (méthodes) En général, la déclaration d'une classe contient simplement les prototypes des fonctions membres de la classe. Exemple class Tableau{ private://définition des attributs privés public:Tableau(int); ~Tableau(); int longueur() const; int element(int) const; void modifier (int, int); };

13 Les fonctions membres (méthodes) Les fonctions membres peuvent être définies dans la classe même, dans un module séparé ou plus loin dans le code source. Dans ces cas, il suffit de préciser la classe pour laquelle la méthode déclarée doit être implémentée. Syntaxe de la définition hors de la classe d'une méthode : type Classe::nom_méthode( paramètres_formels ) { // corps de la fonction } Si le code n’est pas trop long, les méthodes définies à l’intérieur de la classe (méthodes inline) peuvent avoir leur corps recopié à chaque appel pour accélérer l’exécution. Ce n’est pas le cas de celle définies à l’extérieur.

14 Les classes Lors de l'implémentation des méthodes, il est donc nécessaire de préfixer le nom de la méthode implémentée du nom de la classe suivi de '::'. Par exemple, l'implémentation de la méthode element() de la classe Tableau se fait en spécifiant: int Tableau::element(int i) const Le constructeur est une méthode particulière qui porte le même nom que la classe et dont le but est d'initialiser les attributs lors de la création d'un objet. Les méthodes element et longueur() sont déclarées constantes à l'aide du mot clef const. Cela signifie que leur code n'affecte en aucune manière la valeur des attributs de l'objet cible.

15 Méthodes constantes Pour permettre au compilateur de mieux optimiser le code, le mot-clé const peut être ajouté à une méthode pour indiquer que celle-ci ne modifiera pas les membres de la classe. Celui-ci ndique qu'une méthode n'est pas intrusive (pas de modif. de *this). class Point { int x, y; public: // Interface utilisateur int abscisse() const { return x; } void affect_x(int x) { x = x; } }; class Forme { … double get_x () const; }; void foo(const Forme& f) { f.get_x(); // OK } Exemple 1 Exemple 2

16 Les classes Tableau::Tableau(int n) { T=new int[n]; nbelements=n; } Tableau::~Tableau() { delete[] T; } int Tableau::longueur() const { return nbelements; } int Tableau::element(int i) const { return T[i]; } void Tableau::modifier(int i, int a) { T[i]=a; } Implémentation des fonctions membres (méthodes)

17 Les classes On peut utiliser la classe Tableau de la façon suivante: void copier(Tableau& A, Tableau& B) { int min=(A.longueur()<B.longueur()) ? A.longueur():B.longueur(); for (int i=0; i<min; i++) B.modifier(i,A.element(i)); } int echanger(Tableau &A, Tableau &B) { if (A.longueur() != B.longueur()) return 0;; Tableau tmp(A.longueur()); copier(A, tmp); copier(B, A); copier(tmp, B); return 1; }

18 Les classes Dans la fonction echanger, la variable tmp est créée en appelant le constructeur avec le paramètre A.longueur(). Cela nous assure que tmp est un tableau de même taille que A. Seuls les membres dont la déclaration apparaît après le mot clef public peuvent être utilisés ailleurs dans le programme. Les variables nbelements et T ne sont accessibles que par les fonctions membres (privées ou publiques) de la classe Tableau. Par conséquent, le code suivant est illégale: Tableau t(10); // tableau de 10 éléments int n=t.nbelements; // instruction illégale

19 Instanciation d’une classe D’une manière générale, comme pour struct, le nom de la classe représente un nouveau type de donnée. On peut donc définir des variables, de ce nouveau type; (créer des objets ou des instances) : Tableau t1; // une instance simple (statique) Tableau *t2; // un pointeur (non initialisé) t2 = new Tableau(10); // création (dynamique) d'une instance

20 Les objets Ce sont des instances d’une classe, et de la même manière qu'une classe, un objet est caractérisé par: Ses attributs: Il s'agit des données caractérisant l'objet. Ce sont des variables stockant des informations d'état de l'objet Ses Méthodes (appelées parfois fonctions membres): Les méthodes d'un objet caractérisent son comportement, c'est-à-dire l'ensemble des actions (appelées opérations) que l'objet est à même de réaliser. Ces opérations permettent de faire réagir l'objet aux sollicitations extérieures (ou d'agir sur les autres objets). De plus, les opérations sont étroitement liées aux attributs, car leurs actions peuvent dépendre des valeurs des attributs, ou bien les modifier De plus, un objet a aussi une Identitée.

21 Utilisation d’un objet Après avoir créé une instance, un objet de façon statique ou dynamique, on peut accéder aux attributs et méthodes de la classe. Cet accès se fait comme pour les structures à l'aide de l'opérateur. ou ->. cout << t1.longeur() << endl; cout element(0) << endl;

22 Constructeur On observe que la fonction membre Tableau(int) ne retourne aucune valeur et qu'elle possède le même nom que la classe dont elle est membre. Une telle fonction est appelée constructeur, il est utilisés à la déclaration de l’objet pour le construire (affecter les membres, allouer la mémoire, etc.) à partir de différents paramètres. Les constructeur sont utilisés afin d'initialiser les objets lors de leur création. Si aucun constructeur n'est présent dans la définition d'une classe alors un constructeur par défaut est utilisé. Celui-ci ne fait qu'allouer l'espace nécessaire pour représenter les instances de la classe concernée. class Point { int x, y; public: Point() : x(0), y(0) { } Point(int x, int y) : x(x), y(y) { } Point(const Point& P) : x(P.x), y(P.y) { } int abscisse() { return x; } int ordonnee() ; }; Point X; // appel du constructeur vide Point M(3,4); // appel du constructeur sur les entiers Point P(M); // appel du constructeur de recopie physique Exemple

23 Destructeur La fonction membre ~Tableau() est appelée destructeur, elle est appelé automatiquement quand l’objet ne sera plus utilisé (fermeture d’accolade }). Au retour de la fonction echanger, la variable locale tmp sera éliminée par le destructeur et l'espace qu'elle utilise sera libéré. Si aucun destructeur n‘était défini alors le destructeur par défaut serait utilisé et seuls tmp.T et tmp.nbelements seraient éliminés. Le tableau créé par le constructeur serait conservé mais inaccessible jusqu‘à la fin de l'exécution du programme, ce qui résulterait en une perte d'espace inacceptable. class Tableau { int * d; public: Tableau(int s = 10) { d = new int[s]; } ~Tableau() { delete [] d; } }; { Tableau t; // ici de taille 10 par défaut... } // Appel de tous les destructeurs des objets déclarés dans ce groupe Exemple

24 Constructeur et destructeur  Les données membres d'une classe doivent être initialisées par une méthode d'initialisation.  De même, après avoir fini d'utiliser un objet, il est bon de prévoir une méthode permettant de détruire l'objet (en libérant par exemple la mémoire).  Le constructeur est une méthode qui porte le même nom que la classe. C’est dans le constructeur que les attributs d’un objet sont initialisées lors de sa création.  Un constructeur ne peut pas spécifier de valeur ou type de retour.  Le constructeur d’une classe est éxécuté automatiquement à chaque fois qu’une instance de la classe est crée. Que se passe t ’il si l’instance est passée par valeur comme paramètre à une fonction?  Une classe peut avoir ou non un constructeur par défaut. Il s’agit d’un constructeur sans aucun paramètre.

25 Constructeur et destructeur  Si aucun constructeur n’a été défini dans la classe, C++ en crée un automatiquement. C’est un constructeur par défaut, et il ne fait rien. Une classe a donc toujours un constructeur, mais pas forcément un constructeur par défaut ; en effet, si on ne définit que des constructeurs qui prennent des paramètres, C++ ne fournit pas le constructeur par défaut automatiquement.  De la même façon que pour les constructeurs, le destructeur est une fonction membre spécifique de la classe qui est appelée implicitement à la destruction de l'objet. Ce destructeur est une fonction qui porte comme nom, le nom de la classe précédé du caractère ~(tilda), qui ne retourne pas de valeur (pas même un void) et qui n'accepte aucun paramètre (le destructeur ne peut donc pas être surchargé).

26 Constructeur  Le constructeur est donc une fonction membre spécifique de la classe qui est appelée implicitement à l'instanciation de l'objet. class Cercle { private: int x, y; int rayon; public : Cercle(int, int=0, int=0); // constructeur }; Cercle::Cercle(int r, int cx, int cy) { rayon = r; x = cx; y = cy; } Cercle ballon(20,10,10);  Lorsqu’un constructeur par défaut n’existe pas, on ne peut déclarer une instance de classe sans préciser de paramètres. Par exemple, pour l’exemple suivant, class Autre { private: double d; public: Autre(double dd) { d = dd; } } l’appel: Autre a; est illégal.

27 Constructeur  Si on veut créer un tableau d’instances de la classe sans donner de valeurs initiales, il faut définir un constructeur par défaut. Il est alors appelé pour tous les éléments du tableau : Avion A[10]; //10 appels du constructeur par défaut de Avion  Important: bien que les constructeurs aussi peuvent avoir des arguments par défaut comme toute autre fonction: class Autre { private: double d; public: Autre(double dd = 0){ d = dd;} }; Autre a; // ok Autre tab[3]; //NON, car pas de constructeur par défaut

28 Constructeur  Un constructeur ne peut pas être appelé autrement que lors d’une initialisation. Cependant, il peut l’être de différentes façons. Par exemple, s’il existe un constructeur qui n’admet qu’un seul paramètre, ou plusieurs mais tel que tous les arguments sauf le premier ont une valeur par défaut, on peut l’appeler en écrivant le signe égal suivi du paramètre: Autre au = 1.2; // appel de Autre::Autre(1.2) Cette écriture est équivalente à la forme classique :Autre au(1.2);  En outre, il est possible d’initialiser des tableaux de cette façon : Autre atab[4] = { 1.2, 2, 0.7, -9.9 };  Par contre, dans ce cas, il faut absolument préciser toutes les valeurs initiales parce que il n'y a pas de constructeur par défaut dans notre implémentation de la classe Autre.

29 Constructeur  Il est parfaitement possible de préciser une valeur par défaut à un argument de type classe, pourvu qu’on utilise un constructeur : void f(exemple ex = exemple() ); void f2(exemple ex = exemple(1, 1) ); void g(Autre au = 0);  Dans le dernier cas, on a encore utilisé le changement de type automatique.

30 Destructeur  Le destructeur est donc une méthode particulière qui porte le même nom que la classe précédé du symbol ‘~’. Le destructeur est la dernière méthode appelée par une instance lorque le bloc de code dans laquelle celle ci évoluait a terminé son exécution.  Le destructeur est aussi appelé lors d ’un appel d ’opérateur delete sur l ’instance.  C’est dans le destructeur que la mémoire qui avait été allouée à la construction et pendant la vie de l ’instance est rendue au système. Que se passe t ’il si deux blocs mémoires sont partagé par deux instances de la même classe?

31 Destructeur  Un destructeur n’a aucun résultat, comme les constructeurs, et n’admet aucun argument ; de ce fait, il ne peut y avoir qu’un destructeur par classe.  D’une façon générale, le destructeur doit tout « remettre en ordre dans ce que l’instance de classe peut avoir modifié. Outre la libération de la mémoire prise, il peut aussi avoir à fermer des fichiers ouverts, à détruire des éléments provisoires, etc.  Le destructeur standard (fournit par défaut) ne fait rien.

32 new et delete avec constructeurs et destructeurs  Un appel à l ’opérateur new (création d ’une instance de manière dynamique) provoquera un appel de constructeur. Autre* C = new Autre(...); class show{ private: int a; public: show(int); int get_a(){return a;} }; show::show(int i){ a = i; cout<< "Appel du constructeur: show(int i)"; } int foo(show a){ cout<<"Dans foo"<<endl; return a.get_a(); } int main(){foo(1);return 1;}//foo(show(1));serait également légale  Dans ces cas les constructeurs adéquats sont appelés à l’entrée de la fonction (et les destructeurs à la sortie).

33  L’opérateur new réserve donc la place mémoire nécessaire à l’objet dans le heap ; il appelle aussi un constructeur. Inversement, delete appelle d’abord le destructeur, puis libère la place mémoire.  Comme une classe peut avoir plusieurs constructeurs, on peut préciser quel constructeur est appelé au moment de l’appel de new. Il suffit pour cela d’écrire la liste des arguments derrière le nom de classe qui suit new. exemple *pe1 = new exemple(1, 2); // appel du constructeur 2 exemple *pe2 = new exemple; // appel du constructeur 1 classexmpl *c2 = new classexmpl(*c1); // constructeur de copie new et delete avec constructeurs et destructeurs

34  Lorsqu’aucun paramètre n’est précisé le constructeur par défaut est appelé ; s’il n’existe pas, une erreur de compilation se produit.  Il est possible de créer un tableau avec new, mais dans ce cas c’est le constructeur par défaut qui est obligatoirement appelé ; il n’y a pas de moyen d’en préciser un autre (contrairement aux tableaux statiques qui peuvent être initialisés par un constructeur à argument unique).  Pour ce qui est de l’instruction delete, il n’y a pas le choix : chaque classe ayant un seul destructeur (possiblement implicite), c’est celui- là qui est appelé avant de supprimer la place mémoire. new et delete avec constructeurs et destructeurs

35  Problème particulier aux tableaux: exemple *pex = new exemple[10];... delete pex; // incorrect  Le compilateur, qui n’a aucun moyen de connaître la taille du tableau pointé par pex, n’appellera le destructeur que pour le premier élément, ce qui peut poser problème. Pour lui demander de tout détruire, il faut préciser explicitement avec delete, le nombre d’éléments à supprimer : exemple *pex = new exemple[10];... delete[10] pex;  Il faut se rappeler de ceci dans la déclaration du destructeur. new et delete avec constructeurs et destructeurs

36 Ce que l’on pourrait prendre pour une méthode déclarée friend est en fait une fonction classique : ce n’est absolument pas une méthode de la classe! Le mot clef friend permet simplement d’indiquer à la classe que cette fonction pourra accéder à toutes les données privées (membres ou méthodes). L’ambiguïté vient du fait que cette indication passe pour une déclaration. class Point { int x, y; public: friend void affiche(const Point& M) { cout << M.x <<   <<M.y; } void affiche_interne() {cout << x <<   << y; } };... Point M(3,4); affiche( M ) ; // affiche est une fonction globale classique M.affiche_interne(); // affiche_interne est une méthode Méthode friend

37 De même, une classe peut être indiquée friend à l’intérieur d’une autre classe. Là encore cela ressemble à une déclaration précédée du mot-clé friend. Ainsi les méthodes de cette classe (implémentées ultérieurement) pourront manipuler les données privées de la classe dont elle est «amie». Classe friend

38 Fonctions membres statiques Le mot-clé static permet de définir des membres ou des méthodes «globales». Un membre static est identique pour toutes les instances de la classe. Toute modification par un objet est reconnue par tous les autres objets. Une méthode est normalement appelée au travers d’un objet. Une méthode static est appelée par la classe (pour manipuler les membres static). class Repere { static Point origine; public: Repere() {}; static void changement_origine(const Point& P) { origine = P; } Point _origine() { return origine; } }; Point Repere:: origine(0,0); int main() { Repere R1, R2; Point M(3,4); Repere::changement_origine( M ); cout << "Abscisse de l’origine" <<(R2._origine()).abscisse() << endl; return 0; }

39 Opérateurs Un opérateur est une fonction ou une méthode avec un appel particulier. class Complexe { float re, im; public: // une méthode peut être un opérateur... Complexe& operator+= (Complexe x) { re += x.re; im += x.im; return *this; } }; //... tout comme une fonction classique Complexe operator+ (Complexe x, Complexe y) { Complexe r = x; return r += y; } void f(Complexe x, Complexe y, Complexe z) { Complexe r1 = x + y + z; // r1 = operator+(x, operator+(y,z) ) Complexe r2 = x; r2 += y; // r2.operator+=( y ) r2 += z; // r2.operator+=( z ) }

40 Surcharge des opérateurs  Il est plus intuitif et plus clair d'additionner par exemple deux matrices en surchargeant l'opérateur d'addition et en écrivant : result = m0 + m1; que d'écrire : matrice_add(result, m0, m1);  La plupart des opérateurs sont surchargeables. Il faut veiller à respecter l'esprit de l'opérateur.  Lorsque l'on surcharge un opérateur, il n'est pas possible de :  changer sa priorité  changer son associativité  changer sa pluralité (unaire, binaire, ternaire)  créer de nouveaux opérateurs

41 Surcharge des opérateurs  L'opérateur = est le seul à être prédéfini pour toutes les classes: a=b; Par défaut, il affecte aux variables de champs de l'objet a les valeurs des variables de b. Il est important de le surcharger dans certains cas.  Il est possible de surcharger tous les opérateurs, sauf:. (sélection de membre) :: (résolutionde nom).* ?:  Les opérateurs suivant peuvent tous être redéfinis: () [] -> + - ++ -- ! ~ * & new new[] delete * / % + - > >= == != & ^ || && | = += -= *= =,

42 Surcharge des opérateurs La syntaxe est : operator ( ) {...} Exemple 1. Opérateur () L’opérateur parenthèses permet de donner un type à une fonction (de définir une fonction de classe). Ainsi les fonctions peuvent être manipulées plus facilement (comme des objets). class Incrementeur { int inc; public: Incrementeur(int i) : inc(i) {} int operator() (int i) { return i+inc; } }; int main() { Incrementeur plus_un(1), plus_deux(2); // Appel du constructeur int a = plus_deux(3); // a <-- 5, par l’appel de la méthode () }

43 Surcharge des opérateurs class point { private : float abscisse, ordonnee; public : point(); point(float,float); void afficher (); point operator + (point); float operator * (point); }; point point::operator +(point p){ point resultat; resultat.abscisse = abscisse + p.abscisse; resultat.ordonnee = ordonnee + p.ordonnee; return resultat; } float point::operator *(point p){ return (abscisse * p.abscisse + ordonnee * p.ordonnee); } Exemple 2. Opérateurs + et *

44 Surcharge des opérateurs p1 + p2; est alors équivalent à: p1.operator +(p2); C'est la méthode de l'instance de gauche qui est appelée. Donc, si on définit: point point::operator +(int x){ point resultat; resultat.abscisse = abscisse + x; resultat.ordonnee = ordonnee; return resultat; } L'appel suivant est légal: p1 + 3; Mais celui ci ne l'est pas: 3 + p1;

45 Surcharge des opérateurs  Quand l'opérateur + (par exemple) est appelé, le compilateur génère un appel à la fonction operator+. Ainsi, l'instruction a = b + c; Est équivalente aux instructions : a = operator+(b, c); // fonction globale a = b.operator+(c); // fonction membre

46 Surcharge des opérateurs operator++ et operator−− sont ambigus : s’agit-il de la définition de l’opérateur suffixé ou préfixé ? Un paramètre artificiel int dans la définition permet de lever cette ambiguïté. Exemple 3. Opérateurs ++ et -- class Pointeur { int * p; public: Pointeur& operator++ (); // préfixe Pointeur operator++ (int); // suffixe Pointeur& operator-- (); // préfixe Pointeur operator-- (int); // suffixe int operator*() { return *p; } // déréférencement };

47 Surcharge des opérateurs L'utilité des classes est de pouvoir définir des objets qui soient aussi facile à utiliser que les types de base, ce n'est pas le cas du type Tableau que nous avons vu précédemment. Par exemple, si T est une variable de type Tableau, on aimerait pouvoir Écrire T[i]=T[i]+1 plutôt que T.modifier(i,T.element(i)+1). Cela est possible en remplaçant les fonctions membres element et modifier par une modification(surcharge) de l'opérateur []. class Tableau{ private: int nbelements; int *T; public:Tableau(int); ~Tableau(); int longueur() const; int& operator[](int); }; Exemple 4. Opérateur []

48 Surcharge des opérateurs Tableau::Tableau(int n) { T=new int[n]; nbelements=n; } Tableau::~Tableau() { delete[] T; } int Tableau::longueur() const { return nbelements; } int& Tableau::operator[](int i) { return T[i]; } La valeur retournée par la fonction operator[] est de type int& plutôt que int. Cela est essentiel si on veut pouvoir écrire: Tableau tab(20); tab[0]=666; Pour affecter la valeur 666 à l'objet tab.T[0] il est nécessaire que l'appel de la fonction tab[0] retourne l'objet lui-même plutôt qu'une simple copie.

49  Ces opérateurs permettant d'écrire ou de lire toutes sortes de données, types de base et tous les types désirés.  Ils offrent en effet l'avantage de pouvoir les utiliser avec les opérateurs surchargés >> et << respectivement.  Ainsi, pour les nouvelles classes, on peut surcharger les fonctions friend : std::ostream& operator<<(std::ostream&, const Type& obj); std::istream& operator>>(std::istream&, Type& obj); Surcharge des opérateurs Exemple 5. Opérateurs > Il est possible de définir les opérateurs > pour n’importe quel type et ainsi chaque objet peut être facilement manipulé par les flots de sorties (de type std::ostream) std::cout et std::cerr pour le standard et l’erreur respectivement et le flot d’entrée (de type std::istream) std::cin (Comme l’indique le préfixe std::, tous ces objets sont dans le namespace std).

50 Surcharge des opérateurs Exemple 5… suite class Complexe { float re, im;... friend std::ostream& operator<< (std::ostream& o, const Complexe& c); friend std::istream& operator>> (std::istream& i, Complexe& c) ; }; std::ostream& operator<< (std::ostream& o, const Complexe& c) { return o << c.re << ‘‘+’’ << c.im << ‘‘i’’; } std::istream& operator>> (std::istream& i, Complexe& c) { return i >> c.re >> c.im; }

51 Surcharge des opérateurs Exemple 5… suite class Complexe { float re, im;... friend std::ostream& operator<< (std::ostream& o, const Complexe& c); friend std::istream& operator>> (std::istream& i, Complexe& c) ; }; std::ostream& operator<< (std::ostream& o, const Complexe& c) { return o << c.re << ‘‘+’’ << c.im << ‘‘i’’; } std::istream& operator>> (std::istream& i, Complexe& c) { return i >> c.re >> c.im; }

52 Surcharge des opérateurs Exemple 6. Conversions Dans une classe, par constructeur dans un sens, par opérateur de type (transtypage) dans l’autre, le constructeur étant prioritaire. En C++ deux syntaxes sont préférées :  float re = float(a) est un appel au constructeur de float, de paramètre un entier.  float re = static_cast (a) est un transtypage plus contraint, donc plus précis. En effet, en C++, il existe trois autres transtypages possibles :  dynamic_cast qui permet de réaliser des conversions valides, vérifiées à l’exécution, mais que le compilateur n’arrive pas à faire (car il ne connaît pas encore le véritable type – un template, par exemple –).  reinterpret_cast est utilement atroce (permet par exemple de transformer un int en une adresse de pointeur).  const_cast permet d’ajouter ou de retirer un qualificatif const (le comportement ensuite peut-être non spécifié).

53 Le constructeur par copie est une méthode implicite dans toute classe. Constructeur de copie Cette méthode est appelée automatiquement dans les opérations suivantes :  C Création et initialisation d ’une nouvelle instance X I2=I1; X I2(I1);  passage d ’un argument par valeur  r retour d ’une fonction return (I); // une copie de I est retournée Utilisation

54 Constructeur de copie et opérateur d’affectation Notre version de la classe Tableau ne se comporte pas encore tout à fait Comme un type de base. Par exemple, considérez le fragment de code suivant: int a; int b=a; a=b; Dans cet exemple, deux variables sont créées en faisant appel à deux constructeurs différents. Dans le premier cas, le constructeur est de la forme int::int() alors que dans le second cas il est de la forme int::int(const int&). On aurait d'ailleurs pu remplacer la seconde ligne par int b(a); Ainsi le symbole '=' n'a pas la même signification dans la seconde et dans la troisième ligne où il désigne l'opérateur d'affectation.

55 Constructeur de copie et opérateur d’affectation On aimerait pouvoir écrire: Tableau a(100) ; Tableau b=a; a=b; Pour ce faire nous allons faire deux modifications. Nous allons surcharger l'opérateur = et nous allons définir un constructeur de copie, c'est-à-dire un constructeur de la forme:Tableau::Tableau(const Tableau&). class Tableau{ private: int nbelements; int *T; public: Tableau(int); Tableau(const Tableau&); ~Tableau() {delete[] T;}; int longueur() const {return nbelements;}; int& operator[](int) const; Tableau& operator=(const Tableau&); };

56 Constructeur de copie et opérateur d’affectation Tableau::Tableau(int n) { T=new int[n]; nbelements=n; } Tableau::Tableau(const Tableau &tab) { T=new int[tab.nbelements]; (*this)=tab; } int& Tableau::operator[](int i) const { return T[i]; }

57 Constructeur de copie et opérateur d’affectation Tableau& Tableau::operator=(const Tableau &tab) { if (nbelements<tab.nbelements) { delete[] T; T=new int[tab.nbelements]; } nbelements=tab.nbelements; for (int i=0; i<nbelements; i++) T[i]=tab.T[i]; return *this; }

58 Constructeur de copie et opérateur d’affectation Les fonctions membres peuvent être définies en même temps que leur déclaration comme c'est le cas du destructeur et de la fonction longueur(). On retrouve le mot clef const immédiatement après la liste des paramètres de longueur et de operator[]. Il indique donc que ces fonctions ne peuvent Pas modifier l'objet pour lequel ces fonctions sont appelées. Ainsi dans l'exemple: Tableau A(20); int n=A[0]; le constructeur par copie et l'opérateur [] sont appelés pour le Tableau A qui ne doit pas être modifié par ces fonctions. Finalement, l'expression this est un mot clef du C++ désignant le pointeur de l'objet pour lequel une fonction membre est appelée.

59 Le pointeur this Dans une méthode, ou un constructeur, le mot-clé this est un pointeur sur l’objet considéré. Ainsi, lorsque l'on appelle une méthode d'une classe, celle-ci reçoit en plus de ses paramètres, un paramètre caché : le pointeur this. Ce pointeur (constant) permet à la méthode d'accéder à l'objet qui l'a appelé. Exemple Point& Point::copie(const Point& P) { if (this == &P) return *this; // ce n’est pas la peine de se copier soit même. // Attention, ce test vérifie l’égalité en adresse mémoire // et non l’égalité mathématique (celle des membres). x = P.x; this->y = P.y; }

60 Constructeur de copie et opérateur d’affectation Avec cette nouvelle version de la classe Tableau, il est maintenant beaucoup plus simple d‘écrire une fonction pour échanger le contenu de deux variables. void echanger(Tableau &A, Tableau &B) { Tableau tmp=A; A=B; B=tmp; } Le problème de l'affectation d'un grand tableau dans un petit est résolue par la surcharge que nous avons fait de l'opérateur =: un tableau trop petit est simplement remplacé par un autre de la dimension appropriée.

61 Constructeur de copie Il y a donc un type spécial de constructeur, le constructeur de copie:  Si un constructeur de copie est spécifié, il sera appelé à chaque fois qu’un objet est rendu par une fonction, et à chaque fois qu’un objet est passé par valeur à une fonction.  C’est grâce au constructeur de copie que le programmeur évite un certain nombre de problèmes d’allocation de mémoire lorsque celle ci est référée par des variables membres de l’objet.

62 Constructeur de copie  Toute classe a nécessairement un constructeur de copie. Lorsqu’aucun n’est défini explicitement, le compilateur en crée un automatiquement, qui se contente de recopier champ par champ l’argument dans this.  Le constructeur de copie n’est appelé (comme tout constructeur) que lors d’une initialisation. Donc, si on écrit : c2 = c1;  Ce n’est pas le constructeur qui est appelé, mais l’opérateur d’affectation =, qui par défaut recopie les champs un à un ; il faut donc également le redéfinir.  Cette remarque met en relief un fait essentiel qui est que lors des deux écritures : Exemple c2 = c1; // appel du constructeur de copie c2 = c1 // appel de l'opérateur d'affectation;  L’opérateur d’affectation n’est appelé qu’une fois (la seconde), tandis que c’est le constructeur de copie qui est appelé la première fois. Par défaut les deux appels provoquent le même effet ; Ce qui n'est pas le cas dans des classes définies par un programmeur.

63 Constructeur de copie student& student::operator=(const student& s) { if (&s == this) return *this; delete [] name; name = new char[strlen(s.name) + 1]; strcpy(name,s.name); return *this; } Exemple 1

64 Constructeur de copie class classexmpl { public : classexmpl(); // constructeur par défaut classexmpl(int i); // un autre constructeur classexmpl(classexmpl& c); // constructeur de copie // autres méthodes... }; classexmpl c1; // constructeur par défaut classexmpl c2 = c1; // appel du constructeur de copie // équivaut à classexmpl c2(c1); Exemple 2

65 Constructeur de copie class Obj{ private: int* a; public: Obj(); ~Obj(); }; Obj::Obj(){ cout In Obj::Obj() " << endl; a = new int[10]; for(int i=0; i<10;i++){a[i]=i;} } Obj::~Obj(){ cout In Obj::~Obj() " << endl; delete[] a; a = (int*)0; } void foo(Obj Inst){ cout In foo(Obj) " << endl; } Exemple 3

66 Constructeur de copie int main(){ Obj test; foo(test); } a test Dans foo(obj) a Copy de test Allocation mémoire du tableau

67 Constructeur de copie int main(){ Obj test; foo(test); } a test Dans foo(obj) a Copy de test Allocation mémoire du tableau de l'objet d'origine Allocation mémoire de la copie de l'objet d'origine Où les deux tableaux sont égales, valeurs par valeurs.

68 Constructeur de copie class Obj{ private: int* a; public: Obj(); Obj(const Obj&); ~Obj(); }; Obj::Obj(const Obj& cpy){ a = new int[10]; for(int i=0; i<10;i++) { a[i]=cpy[i]; } La déclaration ressemble à :


Télécharger ppt "Structures de données IFT-10541 Abder Alikacem Introduction aux classes Département d’informatique et de génie logiciel Édition Septembre 2009."

Présentations similaires


Annonces Google