Exercices en langage C++
Exercice 142
Énoncé
Réaliser une classe nommée set_int permettant de manipuler des ensembles de nombres entiers. Le nombre maximal d’entiers que pourra contenir l’ensemble sera précisé au constructeur qui allouera dynamiquement l’espace nécessaire. On prévoira les opérateurs suivants(e désigne un élément de type set_int et n un entier :
- <<, tel que e<<n ajoute l’élément n à l’ensemble eþ;
- %, tel que n%e vale 1 si n appartient à e et 0 sinonþ;
- <<, tel que flot << e envoie le contenu de l’ensemble e sur le flot indiqué, sous la forme :
[entier1, entier2, … entiern] La fonction membre cardinal fournira le nombre d’éléments de l’ensemble. Enfin, on s’arrangera pour que l’affectation ou la transmission par valeur d’objets de type set_int ne pose aucun problème (on acceptera la duplication complète d’objets).
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide des composants standard introduits par la norme, qu’il ne faut pas chercher à utiliser ici. exos_c++.book Page 289 Jeudi, 5. juillet 2007 11:10 11
Naturellement, notre classe comportera, en membres donnée, le nombre maximal (nmax) d’éléments de l’ensemble, le nombre courant d’éléments (nelem) et un pointeur sur l’emplacement contenant les valeurs de l’ensemble. Comme notre classe comporte une partie dynamique, il est nécessaire, pour que l’affectation
et la transmission par valeur se déroulent convenablement, de surdéfinir l’opérateur d’affectation et de munir notre classe d’un constructeur par recopie. Les deux fonctions membre (operator = et set_int ) devront prévoir une « copie profonde » des objets. Nous utiliserons pour cela une méthode que nous avons déjà rencontrée et qui consiste à considérer que deux objets différents disposent systématiquement de deux parties dynamiques différentes, même si elles possèdent le même contenu. L’opérateur % doit être surdéfini obligatoirement sous la forme d’une fonction amie, puisque son premier opérande n’est pas de type classe. L’opérateur de sortie dans un flot doit, lui aussi, être surdéfini sous la forme d’une fonction amie, mais pour une raison différente : son premier argument est de type ostream. Voici la déclaration de notre classe set_int :
/* fichier SETINT.H : déclaration de la classe set_int */
#include <iostream>
using namespace std ;
class set_int
{ int * adval ; // adresse du tableau des valeurs
int nmax ; // nombre maxi d’éléments
int nelem ; // nombre courant d’éléments
public :
set_int (int = 20) ; // constructeur
set_int (set_int &) ; // constructeur par recopie
// voir remarque 1 ci-après
set_int & operator = (set_int &) ; // opérateur d’affectation
// voir remarque 2 ci-après
~set_int () ; // destructeur
int cardinal () ; // cardinal de l’ensemble
set_int & operator << (int) ; // ajout d’un élément
friend int operator % (int, set_int &) ; // appartenance d’un élément
// voir remarque 3 ci-après
// envoi ensemble dans un flot, voir remarque 4
friend ostream & operator << (ostream &, set_int &) ;
} ;
Voici ce que pourrait être la définition de notre classe (les points délicats sont commentés au
sein même des instructions) :
#include « setint.h »
#include <iostream>
using namespace std ;
exos_c++.book Page 290 Jeudi, 5. juillet 2007 11:10 11
© Éditions Eyrolles 291
chapitre n° 20 Exercices de synthèse
/*************** constructeur ********************/
set_int::set_int (int dim)
{ adval = new int [nmax = dim] ; // allocation tableau de valeurs
nelem = 0 ;
}
/****************** destructeur ******************/
set_int::~set_int ()
{ delete adval ; // libération tableau de valeurs
}
/********** constructeur par recopie *************/
set_int::set_int (set_int & e)
{ adval = new int [nmax = e.nmax] ; // allocation nouveau tableau
nelem = e.nelem ;
int i ;
for (i=0 ; i<nelem ; i++) // copie ancien tableau dans nouveau
adval[i] = e.adval[i] ;
}
/************ opérateur d’affectation ************/
set_int & set_int::operator = (set_int & e)//commentaires fait pour b = a
{ if (this != &e) // on ne fait rien pour a = a
{ delete adval ; // libération partie dynamique de b
adval = new int [nmax = e.nmax] ; // allocation nouvel ensemble pour a
nelem = e.nelem ; // dans lequel on recopie
int i ; // entièrement l’ensemble b
for (i=0 ; i<nelem ; i++) // avec sa partie dynamique
adval[i] = e.adval[i] ;
}
return * this ;
}
/************ fonction membre cardinal ***********/
int set_int::cardinal ()
{ return nelem ;
}
/************ opérateur d’ajout << ***************/
set_int & set_int::operator << (int nb)
{ // on examine si nb appartient déjà à l’ensemble
// en utilisant l’opérateur %
// s’il n’y appartient pas, et s’il y a encore de la place
// on l’ajoute à l’ensemble
if ( ! (nb % *this) && nelem < nmax ) adval [nelem++] = nb ;
return (*this) ;
}
exos_c++.book Page 291 Jeudi, 5. juillet 2007 11:10 11
/*********** opérateur d’appartenance % **********/
int operator % (int nb, set_int & e)
{ int i=0 ;
// on examine si nb appartient déjà à l’ensemble
// (dans ce cas i vaudra nele en fin de boucle)
while ( (i<e.nelem) && (e.adval[i] != nb) ) i++ ;
return (i<e.nelem) ;
}
/****** opérateur << pour sortie sur un flot *****/
ostream & operator << (ostream & sortie, set_int & e)
{ sortie << « [ » ;
int i ;
for (i=0 ; i<e.nelem ; i++)
sortie << e.adval[i] << » » ;
sortie << « ] » ;
return sortie
Exercice 143
donnez 10 entiers 3 5 3 1 8 5 1 7 7 3 il y a : 5 entiers différents qui forment l’ensemble : [ 3 5 1 8 7 ] ensemble reçu par fct : [ 3 5 1 8 7 ] au retour de fct, il y en a 5 qui forment l’ensemble : [ 3 5 1 8 7 ] ensemble reçu par fctref : [ 3 5 1 8 7 ] au retour de fctref, il y en a 8 qui forment l’ensemble : [ 3 5 1 8 7 -1 -2 -3 ] appartenance de -1 : 1 appartenance de 500 : 0 ensemble a : [ 3 5 1 8 7 -1 -2 -3 ] ensemble b : [ 3 5 1 8 7 -1 -2 -3 ] Énoncé Créer une classe vect permettant de manipuler des « vecteurs dynamiques » d’entiers, c’est-à-dire des tableaux d’entiers dont la dimension peut être définie au moment de leur création (une telle classe a déjà été partiellement réalisée dans l’exercice 39). Cette classe devra disposer des opérateurs suivants : • [] pour l’accès à une des composantes du vecteur, et cela aussi bien au sein d’une expression qu’à gauche d’une affectation (mais cette dernière situation ne devra pas être autorisée sur des « vecteurs constants ») ; • ==, tel que si v1 et v2 sont deux objets de type vect, v1==v2 prenne la valeur 1 si v1 et v2 sont de même dimension et ont les mêmes composantes et la valeur 0 dans le cas contraire ; • <<, tel que flot< De plus, on s’arrangera pour que l’affectation et la transmission par valeur d’objets de type vect ne pose aucun problème ; pour ce faire, on acceptera de dupliquer complètement les objets concernés. N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide des composants standard introduits par la norme, qu’il ne faut pas chercher à utiliser ici. exos_c++.book Page 294 Jeudi, 5. juillet 2007 11:10 11 © Éditions Eyrolles 295 chapitre n° 20 Exercices de synthèse Rappelons que lorsqu’on définit des objets constants, il n’est pas possible de leur appliquer une fonction membre publique, sauf si cette dernière a été déclarée avec le qualificatif const (auquel cas une telle fonction peut indifféremment être utilisée avec des objets constants ou non constants). Pour obtenir l’effet demandé de l’opérateur [], lorsqu’il est appliqué à un vecteur constant, il est nécessaire d’en prévoir deux définitions dont l’une s’applique aux vecteurs constants ; pour éviter qu’on ne puisse, dans ce cas, l’utiliser à gauche d’une affectation, il est nécessaire qu’elle renvoie son résultat par valeur (et non par adresse comme le fera la fonction applicable aux vecteurs non constants). Voici la déclaration de notre classe : #include using namespace std ; class vect { int nelem ; // nombre de composantes du vecteur int * adr ; // pointeur sur partie dynamique public : vect (int n=1) ; // constructeur « usuel » vect (vect & v) ; // constructeur par recopie, // voir remarque 1 ci-après ~vect () ; // destructeur friend ostream & operator << (ostream &, vect &) ; // sortie sur un flot vect & operator = (vect & v) ; // surdéfinition opérateur affectation // voir remarque 2 ci-après int & operator [] (int i) ; // surdef [] pour vect non constants int operator [] (int i) const ; // surdef [] pour vect constants } ; 1. On pourrait ajouter le qualificatif const au constructeur par recopie, ce qui autoriserait l’initialisation d’un vecteur par un vecteur constant. Mais compte tenu des possibilités de l’autre constructeur dans des conversions implicites, on autoriserait du même coup l’initialisation par un entier ou un flottant, ce qui n’est guère satisfaisant ; on pourrait cependant interdire de telles possibilités en utilisant le mot-clé explicit dans la déclaration du constructeur. 2.La transmission de la valeur de retour de l’opérateur d’affectation n’est utile que si l’on souhaite permettre les affectations multiples. Il n’est pas indispensable de transmettre l’argument et la valeur de retour par référence, mais cela évite les recopies. On pourrait ici déclarer constant l’unique argument, ce qui autoriserait l’utilisation d’un objet constant en second opérande de l’affectation (moyennant une recopie). Mais, du même coup, compte tenu des possibilités de conversions implicites, on autoriserait également l’utilisation d’un entier ou d’un flottant, ce qui n’est pas nécessairement souhaité ; là encore, ces possibilités pourraient être interdites, moyennant l’utilisation appropriée du mot-clé explicit. exos_c++.book Page 295 Jeudi, 5. juillet 2007 11:10 11 296 © Éditions Eyrolles Voici la définition des différentes fonctions : #include « vect.h » #include using namespace std ; vect::vect (int n) // constructeur « usuel » { adr = new int [nelem = n] ; } vect::vect (vect & v) // constructeur par recopie { adr = new int [nelem = v.nelem] ; int i ; for (i=0 ; i » ; return sortie ; }