• Aucun résultat trouvé

Classes et objets

N/A
N/A
Protected

Academic year: 2022

Partager "Classes et objets"

Copied!
1
0
0

Texte intégral

(1)

Classes et objets

Structures en C++ avec fonctions membres :

En C++, nous pouvons inclure des membres fonctions à

l’intérieur d’une structure (ce qui n’est pas le cas en C).

Ces fonctions, ont le rôle de manipuler les membres données de la structure.

#include <iostream.h>

struct Point {

int x,y; // membres de données //fonctions membres

void initialise();

void afficher(char *);

double longueur();

};

// on accède aux fonctions membres par l’intermédiaire de l’opérateur // unaire de résolution de portée ::

void Point :: initialise() { x=3;

y=4;

}

void Point::afficher(char * message) { cout << message

<< " (x = " << x << " et y = " << y << ")\n\n";

}

double Point ::longueur() { return sqrt(x*x + y*y);

}

void main() {

Point P; // déclaration de la variable P du type Point

P.initialise();

P.afficher("via la fonction afficher()");

cout << "Point P, x= " << P.x << endl;

cout << "Point P, y= " << P.y << endl;

cout << "Point P, longueur= " << P.longueur() << endl;

}

(2)

/*Execution

via la fonction afficher (x=3 et y=4)

Point P, x=3 Point P, y=4

Point P, longueur=5 */

Classes :

La classe décrit le modèle structurel d'un objet :

ensemble des attributs (ou champs ou données membres) décrivant sa structure

ensemble des opérations (ou méthodes ou fonctions membres) qui lui sont applicables.

struct Point { class Point { int x,y; int x,y;

void initialise(); void initialise();

void afficher(char *); void afficher(char *);

double longueur(); double longueur();

}; };

Par défaut tous les membres d’une structure sont PUBLICS, i.e. accessibles du monde extérieur. Pour les classes, par défaut, tous les membres sont PRIVÉS.

Les mots réservés public et private délimitent les sections visibles par la classe.

class Triangle { private :

double hauteur,base; //les coordonnées du triangle: champs privés public : // fonctions membres publics

double perimetre();

double surface();

void afficher(char *);

};

Droits d'accès :

L'encapsulation consiste à masquer l'accès à certains attributs et méthodes d'une classe.

Elle est réalisée à l'aide des mots clés :

(3)

private : les membres privés ne sont accessibles que par les fonctions membres de la classe.

protected : les membres protégés sont comme les membres privés. Mais ils sont aussi accessibles par les

fonctions membres des classes dérivées (par héritage).

public : les membres publics sont accessibles par les méthodes de toutes les classes (dérivées ou non).

- Les mots réservés private , protected et public peuvent figurer plusieurs fois dans la déclaration de la classe.

Le droit d'accès ne change pas tant qu'un nouveau droit n'est pas spécifié.

- Une fonction membre (public ou private) a accès aux données private de la classe

- En général, la déclaration d'une classe contient simplement les prototypes des fonctions membres de la

classe. Les fonctions membres sont définies dans un module séparé ou plus loin dans le code source.

double Triangle :: surface() { return (hauteur* base)/2; }

- La définition de la méthode peut aussi avoir lieu à l'intérieur de la déclaration de la classe.

- Une fonction membre définie à l'extérieur de la classe peut être aussi qualifiée explicitement de fonction inline.

Instanciation d’une classe :

De façon similaire à une structure, le nom de la classe représente un nouveau type de donnée.

On peut donc définir des variables de ce nouveau type; on dit alors qu’on crée un objet ou une instance de cette classe.

L'instruction : Triangle t ;

permet de déclarer un objet t de la classe Triangle.

(4)

Pour appliquer une méthode sur un objet, on utilise : objet.méthode(arguments si nécessaire)

Exemples : t . afficher("t");

cout << "Perimetre : " <<

t.perimetre() << endl;

Constructeurs:

Un constructeur permet de construire un objet d'une classe.

C'est une méthode qui n'est pas de type void, ne retourne aucune valeur et porte le même nom de la classe. Ainsi, les constructeurs d'une même classe ont tous le même nom de la classe. C'est possible grâce à la surcharge des méthodes qui est permise en C++.

a) classe sans constructeur :

La classe Point0 suivante ne dispose pas du tout de constructeur : aucune méthode portant le nom

de la classe (ici Point0).

class Point0 { private :

int x, y ; public :

void afficher(char *);

};

// DÉFINITION de la fonction membre afficher void Point0::afficher(char * message) { cout << message << endl

<< " (x = " << x << " et y = " << y << ")\n\n";

}

Une déclaration du genre :

Point0 p0;

provoque l'appel d'un constructeur par défaut de C++

qui affecte des valeurs par défaut à chaque champ de l'objet (initialisation quelconque choisie par le compilateur)

Avec

Point0 p0 ;

p0.afficher("C++ construit p0 avec un constructeur par defaut");

(5)

On obtient :

C++ construit p0 avec un constructeur par defaut (x = -858993460 et y = -858993460)

Comment donner de valeurs aux champs d'un objet quand on n'a pas de constructeur ?

1. utiliser des méthodes set... (mettre telle valeur à tel champ)

class Point { private : int x, y ; public :

void setAbscisse( int abs) { x = abs ; }

void setOrdonne( int ord) { y = ord ; }

void afficher(String nom) {

cout <<("Coordonnees du point "

<< nom << ")" << endl

<< " (x = " << x << " et y = "

<< y << ")\n";

} };

Vous comprenez facilement le programme suivant :

void main () {

Point p1 ;

p1.afficher("p1 (valeurs par defaut)");

p1.setAbscisse(12);

p1.afficher("p1 (avec x vaut 12 et y par defaut)");

p1.setOrdonnee(7);

p1.afficher("p1 (x vaut 12 et y vaut 7)");

}

/* Exécution :

Coordonnees du point p1 (valeurs par defaut) x = -858993460 et y = -858993460

Coordonnees du point p1 (avec x vaut 12 et y par defaut) x = 12 et y = -858993460

Coordonnees du point p1 (x vaut 12 et y vaut 7) x = 12 et y = 7 */

(6)

2. utiliser de méthode très classique : initialiser class Point {

private : int x, y ; public :

void initialiser(int abs, int ord) {

x = abs ;

y = ord ; }

etc . . . } ;

Pour créer un point p de coordonnées 25, 52 : Point p ;

p.initialiser(25, 22);

Il y a ainsi deux actions :

- déclarer et construire avec des valeurs par défauts

- initialiser avec des valeurs voulues Peut-on simplifier avec une seule action?

- Oui! en utilisant un constructeur approprié.

b)classe avec un seul constructeur approprié : Observons la classe suivante :

class Point1 { private :

int x, y ; public :

Point1(int , int );

void afficher(char *);

};

Point1::Point1(int abs, int ord) {

cout << "Un constructeur avec deux parametres\n";

x = abs ; y = ord;

}

(7)

void Point1::afficher(char * message) { cout << message

<< " (x = " << x << " et y = " << y << ")\n\n";

}

Comment construire un point d’abscisse 12 et d’ordonné 10 ?

Point1 p1(12, 10);

p1 est un objet de la classe Point1.

C++ alloue l'espace mémoire pour mémoriser les informations de p1 (son abscisse et son ordonné).

La classe Point1 dispose donc d’un constructeur avec paramètre(s). Dans ce cas ci, l'instruction:

Point1 p1a ;

provoque une erreur à la compilation. Vous n'avez pas explicitement de constructeur sans paramètre, il

faut appeler le constructeur existant avec paramètres.

C++ ne fournit plus son constructeur par défaut (sans paramètre).

Comment régler ce problème de syntaxe?

Ajouter un constructeur sans paramètre

c) classe avec plusieurs constructeurs appropriés :

#include <iostream.h>

// avoir un constructeur avec parametre(s) // avoir aussi un constructeur sans parametre class Point2 {

private :

int x, y ; public :

Point2() {}

Point2(int , int = 1234 );

void afficher(char *);

};

Point2::Point2(int abs, int ord) {

cout << "Un constructeur avec deux parametres dont le dernier par defaut\n";

x = abs;

y = ord;

}

(8)

void Point2::afficher(char * message) { cout << message

<< " (x = " << x << " et y = " << y << ")\n\n";

}

void main() { Point2 p2a ;

p2a.afficher("Coordonnees de p2a ");

p2a = Point2(20);

p2a.afficher("Coordonnees de p2a avec p2a = Point2(20); ");

Point2 p2b(50, 45);

P2b.afficher("Coordonnees de p2b avec Point2 p2b(50, 45) ; ");

Point2 p2c = Point2(555, 444);

P2c.afficher("Coordonnees de p2c avec p2c = Point2(555, 444); ");

}

/* Exécution :

Coordonnees de p2a (x = -858993460 et y = -858993460)

Un constructeur avec deux parametres dont le dernier par defaut Coordonnees de p2a avec p2a = Point2(20); (x = 20 et y = 1234) Un constructeur avec deux parametres dont le dernier par defaut Coordonnees de p2b avec Point2 p2b(50, 45) ; (x = 50 et y = 45) Un constructeur avec deux parametres dont le dernier par defaut Coordonnees de p2c avec p2c = Point2(555, 444); (x = 555 et y = 444)

*/

Supposons maintenant que l'en-tête du constructeur avec paramètres de la classe Point2 est le suivant:

Point2(int x , int y) {

Dans un tel cas, dans le corps du constructeur, il est logique d'écrire :

x = x;

y = y ;

où à gauche, on espère qu'ils sont des champs de l'objet et à droite des affectations, les paramètres du constructeur.

Ça passe bien à la compilation. Malheureusement, C++ n'est

pas capable de distinguer ces identificateurs.

(9)

Il faut alors utiliser l'auto référence this qui représente l'objet courant :

Point2 :: Point2(int x , int y) {

this->x = x ; this->y = y ;

}

On interprète : this->x comme l’abscisse du point courant.

Voir l’exemple this.cpp disponible sur la page web

Destructeur:

Définition : méthode spécifique de la classe appelée implicitement à la destruction de l’objet.

Caractéristiques :

 porte le même nom que la classe précédé de ~ (tilde)

 ne retourne pas de valeur, même pas void

 n’accepte aucun paramètre, donc ne peut pas être surdéfinie.

Exemple :

#include <iostream.h>

class Point { private :

int x, y ; public :

Point() {}

Point(int , int = 1234 );

// destructeur ~Point() {

cout << "On detruit le point de coordonnes "

<< x << " et " << y << endl;

}

void afficher(char *);

};

Point::Point(int abs, int ord) {

cout << "Un constructeur avec deux parametres dont le dernier par defaut\n";

(10)

x = abs;

y = ord;

}

void Point::afficher(char * nom) {

cout << "Coordonnes du point " << nom << " : "

<< " (x = " << x << " et y = " << y << ")\n\n";

}

Point glob(99, 66);

void test1Destructeur() { Point a(20,15), b(75);

cout << "On est dans la fonction test1Destructeur() :\n";

a.afficher("a");

b.afficher("b");

glob.afficher("glob");

cout << "On est a la fin de la fonction test1Destructeur()\n\n";

} void main() {

test1Destructeur();

Point z(100, 200);

z.afficher("z");

{ // bloc1 :

Point c (15, 50);

cout << "\nOn est dans le bloc 1 :\n";

c.afficher("c");

{ // bloc 2

Point d (567, 98);

cout << "\nOn est dans le bloc 2 :\n";

d.afficher("d");

glob.afficher("glob dans bloc 2");

cout << "On quitte le bloc 2 :\n";

}

cout << "\nOn quitte le bloc 1 :\n";

}

cout << "On est en dehors du bloc 1 :\n";

cout << "\nOn quitte main\n";

}

/* Exécution :

Un constructeur avec deux parametres dont le dernier par defaut Un constructeur avec deux parametres dont le dernier par defaut Un constructeur avec deux parametres dont le dernier par defaut On est dans la fonction test1Destructeur() :

Coordonnes du point a : (x = 20 et y = 15) Coordonnes du point b : (x = 75 et y = 1234)

(11)

Coordonnes du point glob : (x = 99 et y = 66) On est a la fin de la fonction test1Destructeur() On detruit le point de coordonnes 75 et 1234 On detruit le point de coordonnes 20 et 15

Un constructeur avec deux parametres dont le dernier par defaut Coordonnes du point z : (x = 100 et y = 200)

Un constructeur avec deux parametres dont le dernier par defaut On est dans le bloc 1 :

Coordonnes du point c : (x = 15 et y = 50)

Un constructeur avec deux parametres dont le dernier par defaut On est dans le bloc 2 :

Coordonnes du point d : (x = 567 et y = 98)

Coordonnes du point glob dans bloc 2 : (x = 99 et y = 66) On quitte le bloc 2 :

On detruit le point de coordonnes 567 et 98 On quitte le bloc 1 :

On detruit le point de coordonnes 15 et 50 On est en dehors du bloc 1 :

On quitte main

On detruit le point de coordonnes 100 et 200 On detruit le point de coordonnes 99 et 66

*/

Membres statiques :

Une classe est une moule à objets ; chaque objet a sa propre copie de champs.

Un champ statique est un champ de la classe et non des objets. Ce champ est partagé par tous les objets de la classe ; il existe un seul exemplaire pour toute la classe.

#include < iostream.h>

class Point { private : int x, y ; public :

static int nbPoints;

Point();

~Point();

};

(12)

int Point:: nbPoints =0; //initialisation obligatoire sous cette forme

Point :: Point() { nbPoints++;

}

Point :: ~Point() { nbPoints--;

}

void main() {

cout << Point ::nbPoints << endl; // 0 Point p1;

cout << Point ::nbPoints << endl; // 1 Point p2;

// 3 manières d’y accéder au champ nbPoints cout << p1.nbPoints << endl; // 2

cout << p2.nbPoints << endl; // 2 cout << Point ::nbPoints << endl; // 2 }

Méthodes statiques :

- Une méthode statique est une méthode associée à la classe, plutôt qu’à un objet en particulier.

- Une méthode statique a accès seulement aux membres statiques.

- Une méthode statique peut être appelée même si aucun objet n’a été créé.

#include < iostream.h>

class Point {

int x, y ;

static int nbPoints;

public :

Point(int abs,int ord) { x=abs;

y=ord;

nbPoints++;

}

void afficher() {

cout << "nbPoints : " << nbPoints << " "

<< "x : " << x << " " << "y : " << y << endl;

}

(13)

static void afficher_static () {

cout << "nbPoints : " << nbPoints << endl;

// cout << "x : " << x << endl; <- erreur car x n’est pas static };

int Point:: nbPoints =0;

void main() {

Point :: afficher_static(); // correcte

// Point :: afficher(); -> erreur, aucun objet n’a été créé

// cout << Point :: nbPoints << endl; -> erreur, nbPoints private Point p(5,6);

Point :: afficher_static(); // correcte p.afficher_static(); // correcte

p.afficher(); // correcte }

/*Exécution nbPoints : 0 nbPoints : 1 nbPoints : 1

nbPoints : 1 x : 5 y : 6 /*

Objet constant :

L’instruction : const Point point_const(3,4) ;

déclare un objet constant de la classe Point. Il est

impossible de modifier le contenu de l’objet point_const.

Seules les méthodes constantes peuvent manipuler des objets constantes.

#include < iostream.h>

class Point {

int x, y ;

public

:

Point(int abs,int ord) { x=abs;

y=ord;

}

void afficher() {

cout << "x : " << x << " " << "y : " << y << endl;

(14)

} };

void main() { int a=10 ;

const int b=20 ; a+=10 ; // correcte

// b+=20 ; // incorrecte b est une constante Point p(2,5) ;

p.afficher() ; // correcte const Point point_const(3,4) ;

// point_const.afficher() ; // erreur point_const n’est accessible // que par une méthode constante

}

Méthodes constantes :

Les méthodes constantes sont utilisées pour manipuler des objets constants. Elles indiquent au compilateur que l’objet receveur ne sera pas modifié.

#include < iostream.h>

class Point {

int x, y ;

public

:

Point(int abs,int ord) { x=abs;

y=ord;

}

void afficher() const {

cout << "x : " << x << " " << "y : " << y << endl;

} };

void main() {

Point p(2,5) ;

p.afficher() ;// correcte même si afficher est const alors // que p ne l’est pas

(15)

const Point point_const(3,4) ;

point_const.afficher() ; // correcte }

Constructeur de recopie :

Pour chaque classe quelconque, le C++ fournit par défaut un constructeur de recopie. Ce constructeur effectue la copie membre à membre de la source vers la destination.

Appelé dans trois situations : a) initialisation d’un objet

création d’un nouveau objet initialisé comme étant une copie d’un objet existant :

Point p1(5,8) ;

Point p2(p1) ; // constructeur de recopie : création de l’objet p2

// et son initialisation avec les données de p1.

ou bien

Point p2=p1 ; // constructeur de recopie : création de l’objet p2 // et son initialisation avec les données de p1.

Attention : il faut différencier entre recopie et affectation Point p2 ; // création de l’objet

p2=p1 ; //pas de recopie, mais l’affectation, car objet déjà créé

b) paramètre passé par valeur

transmission d’une valeur à une fonction

Point p(4,6) ;

int fonction (Point) ;

int z=fonction(p) ; // passage par valeur de p

c) retour de fonction par valeur

Point fonction () { Point x ;

return x ; // retour par valeur de x }

(16)

Utilité d’un constructeur de recopie :

#include <iostream.h>

class test {

int * ptr; // pointeur sur un seul élément void alloc_test() {

if (ptr==NULL) {

cerr << "allocation de la mémoire a échoué\n";

exit(1);

}

public :

test(int d=1000) {

ptr= new int(d) // allocation et initialization alloc_test ();

}

~test() {

delete ptr; //liberation }

void afficher() {

cout << "valeur: " << *ptr << endl;

} };

void main() {

test x; // appel du constructeur, déclaration permise car par // défaut d=1000

test y=x; // appel du constructeur de recopie, dans ce cas par // par défaut fourni par C++ : création de l’objet y // puis recopie membre à membre de x vers y.

y.afficher();

}

Le constructeur de recopie par défaut va effectuer une copie membre à membre. Copier membre à membre un pointeur signifie que ptr de l’objet y pointe au même endroit que ptr de l’objet x.

À la fin du programme, le destructeur va être appelé pour détruire les objets x et y.

Détruire x revient à libérer la mémoire allouée par le pointeur ptr de x.

Détruire y revient à libérer la mémoire allouée par le pointeur ptr de y.

Or comme les deux pointeurs pointent vers le même endroit, le même endroit va être détruit deux fois, ce qui provoque une erreur

d’exécution car il y aura violation de la mémoire : tentative d’accéder à quelque chose qui n’existe pas.

(17)

Pour corriger cette erreur, il faut définir le constructeur de recopie, afin de masquer le constructeur de recopie par défaut fourni par C++.

Prototype du constructeur de recopie : nom_classe (const nom_classe &) ;

#include <iostream.h>

class test {

int * ptr; // pointeur sur un seul élément void alloc_test() {

if (ptr==NULL) {

cerr << "allocation de la mémoire a échoué\n";

exit(1);

}

public :

test(int d=1000) {

ptr= new int(d) // allocation et initialization alloc_test ();

}

test (const test & T) { ptr =new int(T.ptr);

alloc_test();

}

~test() {

delete ptr; //liberation }

void afficher() {

cout << "valeur: " << *ptr << endl;

} };

void main() {

test x; // appel du constructeur, déclaration permise car par // défaut d=1000

test y=x; // appel du constructeur de recopie.

y.afficher();

}

/*Exécution valeur : 1000

*/

(18)

Objet dynamique :

La déclaration :

Point * p = new Point(12,10);

demande au compilateur d’allouer un espace mémoire

dynamique pour mémoriser *p qui est un objet de la classe Point.

Tableau d’objets :

Les éléments d’un tableau peuvent être des objets (tableau classique ou tableau dynamique d’objets).

Exemple :

Nous désirons créer un tableau dynamique contenant des objets Vecteur.

La définition de la classe Vecteur étant la suivante :

class Vecteur { private:

int x, y, z ; // aucune importante si int ou float ou double pour l'exemple

public :

// constructeur avec paramètres par défaut Vecteur(int=0, int = 0, int=0);

p p1

pp

12 10

(19)

// constructeur de recopie Vecteur(const Vecteur & autre);

// calcul de la longueur double longueur() {

return sqrt(x*x + y*y + z*z);

}

// afficher (fonction membre) void afficher(char * nom) {

cout << "Vecteur " << nom << " : "

<< "<" << x << ", " << y << ", " << z << ">"

<< " (longueur : " << longueur() << ")\n\n";

} };

Vecteur::Vecteur(int abs, int ord, int haut) { x = abs ;

y = ord;

z = haut;

}

Vecteur::Vecteur(const Vecteur & autre) { x = autre.x ;

y = autre.y ; z = autre.z ; }

Construisons maintenant un tableau dynamique de Vecteur

Vecteur * z = new Vecteur[15];

for(int i = 0 ; i < 10 ; i++)

*(z+i) = Vecteur(5*(i+1), 4 * (i+2) , 3 * (i+3));

Afficher le contenu du tableau z for (i = 0 ; i < 10 ; i++)

(z+i)->afficher(" z + i ");

/*Exécution

Vecteur z + i : <5 , 8 , 9 >

Vecteur z + i : <10 , 12 , 12 >

(20)

Vecteur z + i : <15 , 16 , 15 >

Vecteur z + i : <20 , 20 , 18 >

Vecteur z + i : <25 , 24 , 21 >

Vecteur z + i : <30 , 28 , 24 >

Vecteur z + i : <35 , 32 , 27 >

Vecteur z + i : <40 , 36 , 30 >

Vecteur z + i : <45 , 40 , 33 >

Vecteur z + i : <50 , 44 , 36 >

*/

Références

Documents relatifs

Waar ben je geboren ? Dat weet ik niet !.. r) Mademoiselle, je voudrais un petit pain au jambon parce que j'ai faim. Juffrouw, ik zou graag een broodje met ham hebben omdat ik

[r]

(Indication : on construira un premier point I de cette droite puis un deuxième point J)... Exercice

[r]

sont premiers entre eux on peut déterminer k par le théorème des restes Chinois ,en déduire t,u,w et finalement A*(m+B*t),B*(n+A*t), .... les nom- bres

Pour cela, on travaille avec l’hypoth` ese que les donn´ ees sont issues d’un mod` ele de m´ elange de distributions de Poisson2. Ecrire le mod` ele de m´ elange

Nous comptons sur vous pour nous retrouver tout au long des festivités de cette année au cours de laquelle nous célébrerons la quarantième St Jean à Baho.. Festivités 2022

La nature d’une isom´ etrie fait r´ ef´ erence ` a la classification des isom´ etries de l’espace donn´ ee en cours.. Soient deux points A et B de C, distincts et non