|
Reprenons la classe Point :
Imaginer que souhaitions savoir combien de points ont été crées. Nous allons prendre un 3ème attribut de Point, Compteur. Pour que cet attribut soit commun à tous les objets Point, il faut le déclarer en static :
Reprenons encore la classe Point :
Nous avons abordé la surdéfinition des fonctions (ou méthodes de classes) dans La surdéfinition des fonctions. Nous avons vu la surdéfinition des opérateurs dans le paragraphe sur la surdéfinition et dans le chapitre sur l'amitié. Voyons encore quelques exemples qui sont souvent utiles en les illustrant par la classe Vecteur dont voici la définition :
Vecteur::Vecteur()
{
Size=0;
X=0;
}
Vecteur::Vecteur(int n, double *v)
{
Size=n;
X=new double[Size];
for(int i=0 ; i<Size ; i++) X[i]=v[i];
}
Lors de la définition de la classe Vecteur pour un vecteur de taille constante nous avons écrit quelque chose du type Vecteur V=U où U est un Vecteur. La classe Vecteur que nous avions écrite ne faisait pas d'allocation dynamique et chaque attribut du vecteur U était automatiquement recopié dans les attributs de V. Si nous faisions la même chose sur notre nouvelle classe Vecteur (qui possède un pointeur ``dynamique'') voilà ce qui se passerait pour l'attribut X
en supposant que U soit un vecteur de R3
Pour ce faire, nous devons écrire explicitement
le constructeur de recopie. Il faut donc rajouter au prototype de
la classe :
Pour les mêmes raisons, une instruction du type V=U qui ne
pose pas de problème dans le cas où tous les attributs ne sont pas
des pointeurs dynamiques, doit faire l'objet d'une redéfinition
de l'opérateur = pour ne pas conduire aux mêmes problèmes que précédemment.
Le prototype de l'opérateur = sera donc (dans la classe Vecteur)
Il peut être pratique d'accéder à une composante d'un Vecteur
V en la syntaxe suivante : V[i]. Pour cela il suffit
de surdéfinir l'opérateur [] en ajoutant le prototype
suivant
Il peut être pratique de pouvoir utiliser les méthodes cout
et cin pour afficher ou entrer un Vecteur. Ces méthodes
appartiennent à aux classes ostream et istream.
Comme le premier argument de << ou >>
sera un flot (cout ou cin), les opérateurs <<
et >> doivent être surdéfinis avec un
lien d'amitié. Voici le code à rajouter au prototype
de la classe.
L'héritage est un outils très puissant pour la réalisation et l'utilisation
des classes. Nous présenterons ici que les notions de bases sur l'héritage.
Considérons un objet de la classe A ayant un certain nombre
d'attributs et de méthodes ; maintenant, supposons que nous ayons
un objet semblable à la classe A mais avec de petites différences
(des particularités ou des compléments). On construira
alors la classe B comme la classe
dérivée de la classe A (A sera la classe
de base). En faisant ceci, tous les attributs et les méthodes de
A seront attributs et méthodes de B.
Reprenons l'exemple de la classe Point déjà utilisé :
Reprenons l'exemple précédant en changeant le main()
de la sorte
Quelle est la raison de ce changement ? Sans entrer dans les
détails, dans le cas général, c'est le compilateur qui décide d'appeler
telle méthode de telle classe. Par contre, lorsque que l'on met virtual ,
ce choix n'est pas fait par le compilateur, mais à l'exécution (on
parle de typage dynamique ).
cette partie est en cours d'élaboration....patience
Imaginons que nous voulions écrire une fonction Min qui renvoie
le minimum de 2 entiers ; nous écrirons,
Cette notion de patron peut s'étendre aux classes. Reprenons la classe
Point (encore!!!)
et la définition de ce constructeur de recopie est
{
Size=U.Size;
X= new double[Size];
for(int i=0; i<Size;i++) X[i]=U.X[i];
}
Opérateur =
et le code de cette méthode sera
{
if(this!=&U)
{
delete X;
Size=U.Size;
X=new double[Size];
for(int i=0; i<Size;i++) X[i]=U.X[i];
}
return *this;
}
Remarques :
Opérateur []
au prototype de la classe et en écrivant cette méthode ainsi
{
if(i<Size) return X[i];
else
{
cout<<"Index>Size"<<endl;
}
return 0;
}
Remarque :
Opérateur « ou »
Et voici le code à rajouter après les méthodes de la classe (rappelons
que comme il s'agit d'amitié, ces fonctions ne sont pas des méthodes
de la classe).
friend istream & operator>>(istream & entree, Vecteur &U);
{
sortie<<"(";
for(int i=0 ; i< U.Size-1 ; i++ ) sortie<<U.X[i]<<",";
sortie<<U.X[U.Size-1]<<")";
return sortie;
}
istream & operator>>(istream & entree, Vecteur &U)
{
if(!U.Size)
{
cout<<"taille du vecteur : "<<flush ;
entree>>U.Size;
U.X = new double[U.Size];
}
cout<<"Entrer les "<<U.Size<<" composantes :"<<endl
for(int i=0 ; i< U.Size ; i++ ) entree>>U.X[i];
return entree;
}
Remarques :
L'héritage
Principe
Supposons à présent que nous voulions construire également un point
coloré : c'est un Point avec un attribut donnant sa couleur
(représentée par un entier) ; nous allons écrire la classe PointCol
de cette façon :
{
private:
double X;
double Y;
public:
Point(double m, double n);
void Print();
};
Point::Point(double m, double n)
{
X=m;
Y=n;
cout<<"Constructeur de Point"<<endl;
}
void Point::Print()
{
cout<<"Point::Print : ("<<X<<","<<Y<<")"<<endl;
}
et voici le programme principale
{
int Color;
public:
PointCol(double x, double y,int c);
void Print();
};
PointCol::PointCol(double x, double y,int c) :Point(x,y)
{
Color=c;
cout<<"Constructeur de PointCol"<<endl;
}
void PointCol::Print()
{
cout<<"PointCol::Print :"<<endl;
Point::Print();
cout<<"la couleur est "<<Color<<endl;
}
{
cout<<"----- Point P1 ------"<<endl;
Point P1(1.5,2.);
P1.Print();
cout<<"----- PointCol P2 ----"<<endl;
PointCol P2(2.1,5.3,5);
P2.Print();
return 0;
}
Remarques :
Virtuel, vous avez dit Virtuel
{
cout<<"--- Point P1 ---"<<endl;
Point P1(1.5,2.);
P1.Print();
cout<<"--- PointCol P2 --"<<endl;
PointCol P2(2.1,5.3,5);
P2.Print();
cout<<"--- Pointeur P3 --"<<endl;
Point *P3;
cout<<"--- P3=P1 --"<<endl;
P3=&P1;
P3->Print();
P3=&P2;
P3->Print();
return 0;
}
Remarque
Faîtes-le et regardez le changement.
Héritage multiple
Les patrons de fonctions et de classes
Patron de fonctions
A présent nous aimerions avoir la même fonction Min pour des
float, des double, .... Nous avons vu qu'il était
possible, grâce au mécanisme de la surdéfinition de fonctions (ou
méthodes), de définir une fonction ayant le même nom mais un type
et des arguments différents. Nous pourrions alors écrire la même fonction
Min pour des float, .... Cependant cela serait un
peu fastidieux. Le C++ offre une possibilité très intéressante pour
rendre ce travail plus simple : il s'agit des patrons
de fonctions (``template'' en anglais).
Notre exemple précédent devient simplement
{
if (a<b) return a;
else return b;
}
Pour utiliser cette fonction, on fera simplement
{
if (a<b) return a;
else return b;
}
{
int a=3,b=5;
float x=4.7, y=2.1;
double u=2.5,v=3.1;
cout<<"le min de ("<<a<<","<<b<<") est "<<Min(a,b)<<endl;
cout<<"le min de ("<<x<<","<<y<<") est "<<Min(x,y)<<endl;
cout<<"le min de ("<<u<<","<<v<<") est "<<Min(u,v)<<endl;
}
Remarques
{
U z;
T *y=new T[10];
...
return 0;
}
Dans cet exemple MyFunction est de
type int , elle admet 3 arguments (un de
type T , un de type U
et un pointeur sur un type T ).
Patron de classes
Si à présent nous voulons créer des Points d'entiers ou de
float, nous allons utiliser les patrons comme suit :
{
private:
double X;
double Y;
public:
Point(double m, double n);
void Print();
};
Point::Point(double m, double n)
{
X=m;
Y=n;
}
void Point::Print()
{
cout<<"Point::Print : ("<<X<<","<<Y<<")"<<endl;
}
{
private:
T X;
T Y;
public:
Point(T m, T n);
void Print();
};
template <class T> Point<T>::Point(T m, T n)
{
X=m;
Y=n;
}
template <class T> void Point<T>::Print()
{
cout<<"Point::Print : ("<<X<<","<<Y<<")"<<endl;
}
int main()
{
Point<int> P(3,5);
Point<double> P1(3.1,5.1);
Point<float> P2(4.9,8.1);
P.Print();
P1.Print();
P2.Print();
}
Remarques