4ième Classe (Mercerdi, 27 octobre) CSI2572
H L'agrégation est un type de relation entre deux classes qui traduit les relations Est composé de... ou Possède … ou, dune façon plus générale, a … »; (En anglais: Has a). H Par exemple, dans un système mécanique, on pourrait considérer que la classe Voiture est composée d'une instance de la classe Moteur, quatre instances de la classe Roue et une instance de la classe Chassis. H L'instanciation passe nécessairement pas l'utilisation d'attributs qui sont eux mêmes des objets ou des pointeurs sur des objets ou même une instance d'une classe conteneur qui elle réalise effectivement l'agrégation. Aggrégation:
class Y { public: Y(); Y(int); }; class C { protected: int c; Y y; public: C(); C(int); C& operator=(const C&); ~C(); };
Y::Y() { cout << "Y::Y()" << endl; } Y::Y(int) { cout << "Y::Y(int)" << endl; } C::C(): y(2), c(5) { cout << "C::C()" << endl; } C::C(int i): y(3), c(j) { cout << "C::C(int)" << endl; } C& C::operator=(const C& v) { c = v.c; y = v.y; cout << "C::operator=" << endl; return *this; } C::~C() { cout << "C::~C()" << endl; }
H Le constructeur de la classe C commence par initialiser l'objet y en appelant son constructeur avant d'initialiser l' attribut atomique c. H Très important : s'il est possible d'affecteur leur valeur aux attributs atomiques à l'intérieur du corps du constructeur, il est obligatoire d'initialiser les attributs objets dans la liste d'initialisation du constructeur à moins que l'objet ne dispose d'un constructeur par défaut, auquel cas, celui-ci sera appelé avant l'exécution du corps du constructeur. H Si vous ne disposez pas d'un constructeur par défaut ou ne pouvez pas faire l'initialisation de l'objet dans la liste d'initialisation, car, par exemple, elle nécessite des calculs importants, alors, vous serez obligés de recourir à un pointeur comme dans l'exemple suivant : Aggrégation:
class C { protected: int c; Y* y; public: C(); C(int); C& operator=(const C&); ~C(); }; C::C(): c(5){ y = new Y(2); } C::C(int i):c(j) { y = new Y(3); }
C& C::operator=(const C& v) { c = v.c; delete y; y = new Y(*(v.y)); return *this; } C::~C() { delete y; y = NULL; }
H Il est aussi possible que l'objet agrégé ne soit pas créé par l'objet agrégateur, mais utilise plutôt un objet en provenance de l'extérieur. 2 possibilités : agréger par référence ou par pointeur. H Si vous agrégez par référence, la référence doit impérativement être initialisée dans la liste d'initialisation comme dans l'exemple suivant : Aggrégation externe:
class ObjetGraphique { public: ObjetGraphique(Point &p, int couleur, int epaisseur) : pointBase_(p) { couleur_=couleur; epaisseur_=epaisseur; } private: int epaisseur_; int couleur_; Point &pointBase_; };
class ObjetGraphique { public: ObjetGraphique( Point &p, int couleur, int epaisseur) { couleur_= couleur; epaisseur_= epaisseur; pointBase_= &p; } private: int epaisseur_; int couleur_; Point *pointBase_; }; En dehors de la liste d'initialisation, ca se fait de cette manière:
H Lorsque vous désirez renvoyer un accès en lecture à un objet agrégé, le plus simple dans ce cas sera de renvoyer une référence constante. Aggrégation accès: class ObjetGraphique{ private: Point pointBase_; … }; const Point &ObjetGraphique::pointDeBase(void) const{ return pointBase_; } class ObjetGraphique{ private: Point &pointBase_; … }; const Point &ObjetGraphique::pointDeBase(void) const{ return pointBase_; } class ObjetGraphique{ private: Point *pointBase_; … }; const Point &ObjetGraphique::pointDeBase(void) const{ return *pointBase_; }
H Commes les fonctions, les opérateurs en C++ peuvent être redéfinis (surchargés). H Nous avons déja vu la surcharge de l'opérateur d'assignation H Surcharger un opérateur consiste à en définir le comportement tel qu'il s'applique à un objet. On ne peut pas changer la pluralité des opérateurs, ni leur priorité. H Il est possible de surcharger tous les opérateurs, sauf:. ::.* ?: Surcharge d'opérateur
Les opérateurs suivant peuvent tous être redéfinis: () [] -> ! ~ * & new new[] delete * / % + - > >= == != & ^ || && | = += -= *= =, Surcharge d'opérateur
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); }
La syntaxe est : operator ( ) {...} 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 lorsque certaines variables de champs sont >. Surcharge d'opérateur
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; Surcharge d'opérateur
class chaine{ int lg; char *buf; //... public: //... char &operator[](int i); }; char &chaine::operator[](int i){ assert ( i < lg ); return(buf[i]); } assert est une macro qui est évaluée comme une instruction conditionnelle. Si expression retourne zéro, assert affiche un message d'erreur sur stderr et appelle la fonction abort pour finir le programme.
C L'héritage est le second des trois principes fondamentaux du paradigme orienté objet. Il reprend le principe naturel de Généralisation / Spécialisation. C Les systèmes réels se prêtent souvent à une classification hiérarchique des éléments qui les composent. Le principe est basé sur l'idée qu'un objet spécialisé bénéficie ou hérite des caractéristiques de l'objet le plus général auquel il rajoute ses éléments propres. C En terme de concepts objets cela se traduit de la manière suivante : = On associe une classe au concept le plus général, nous l'appellerons classe de base ou classe mère ou super - classe. = Pour chaque concept spécialisé, on dérive une classe du concept de base. La nouvelle classe est dite classe dérivée ou classe fille ou sous-classe Héritage:
Remarquez que Cercle et Ligne redéfinissent les méthodes Afficher et Effacer : en effet, un cercle ne s'affiche pas de la même manière qu'une ligne : cest le polymorphisme appliqué aux méthodes Afficher et Effacer dans le cadre des classes Ligne et Cercle. Concepts UML rapide: G En notation UML, la relation d'héritage est signalée par une flèche à l'extrémité triangulaire et dont la pointe est orientée vers la classe mère. G Les méthodes dont le nom est en italique sont abstraites. Les classes dont le nom est italique sont également abstraites (ne peuvent pas directement produire dinstances) G Les méthodes ou les attributs soulignés sont des membres de classe. L'héritage:
modélisation d'un parc de véhicules OO Design:
NomClasseDérivée : [public|private]ClasseBase1 {,[public|private] ClasseBaseI} 1..n { déclaration de classe }; Exemples : class Cercle : public ObjetGraphique class TexteGraphique : public ObjetGraphique, public Chaine class Pile : private Vecteur L'héritage (syntaxe C++):
Rappel: Un attribut protected nest pas accessible à lextérieur de la classe mais lest aux classes dérivées. Par contre, à l'inverse d'un attribut private, il respecte le principe dencapsulation tout en favorisant la transmission des attributs entre classe mère et classe fille. Le tableau suivant récapitule les accès fournis par ces trois modificateurs: Modificateur Visibilité (sous classes) Visibilité (extérieur) private Non Non protected Oui Non public Oui Oui Le rôle des modificateurs d'héritages
Le modificateur d'héritage peut être public ou private. Il conditionne la visibilité des membres de la classe mère dans la classe dérivée. Le tableau suivant indique à quel accès est associé un membre de la classe mère dans la classe fille en fonction du modificateur dhéritage : Le rôle des modificateurs d'héritages Accès dans class mère Accès dans classe fille Héritage publicHéritage privé private Non accessible protected private public private
Le constructeur dune classe dérivée appelle toujours les constructeurs de ses classes mères avant de construire ses propres attributs. De même, les destructeurs des classes mères sont automatiquement appelés par le destructeur de la classe fille. donc Vous spécifiez vous même l'appel au constructeur de la classe mère ou Vous ne le spécifiez pas, et il y a appel automatique du constructeur par défaut de la classe mère. Si celui-ci n'existe pas, le compilateur envoie un message d'erreur Construction - Destruction
H Une classe est dite abstraite si elle ne possède pas dinstance. Cest forcément le cas si elle ne fournit pas d'implémentation pour certaines de ces méthodes qui sont dites méthodes abstraites. Il appartient donc à ces classes dérivées de définir du code pour chacune des méthodes abstraites. On pourra alors parler de classes concrètes ; les classes concrètes étant les seules à même dêtre instanciées. H Les classes abstraites définissent un cadre de travail pour les classes dérivées en proposant un ensemble de méthodes que l'on retrouvera tout au long de l'arborescence. Ce mécanisme est fondamental pour la mise en place du polymorphism. En outre, si l'on considère la classe Véhicule, il est tout à fait naturel qu'elle ne puisse avoir d'instance : un véhicule ne correspond à aucun objet concret mais plutôt au concept d'un objet capable de démarrer, ralentir, accélérer ou s'arrêter, que ce soit une voiture, un camion ou un avion. Classes Abstraites