View
86
Download
17
Category
Preview:
Citation preview
Résumé
150exercicescorrigéspourmaîtriserlalangageC++ComplémentidéaldeProgrammerenlangageC++,dumêmeauteur,cetouvragevouspropose150exercicescorrigésetcommentéspourmieuxassimilerlasyntaxedebaseduC++(typesetopérateurs,instructionsdecontrôle,fonctions,tableaux,pointeurs…)etlesconceptsobjetdulangage.
LesexercicesproposésvouspermettrontdevousforgerunevéritableméthodologiedeconceptiondevospropresclassesC++.Voussaureznotammentdéciderdubien-fondéde la surdéfinition de l’opérateur d’affectation ou du constructeur par recopie, tirerparti de l’héritage (simple oumultiple), créer vos propres bibliothèques de classes,exploiterlespossibilitésoffertesparlespatronsdefonctionsetdeclasses,etc.
Chaque chapitre débute par un rappel de cours suivi de plusieurs exercices dedifficulté croissante. Les corrigés sont tous présentés suivant le même canevas :analyse détaillée du problème, solution sous forme de programme avec exemple derésultat d’exécution, justification des choix opérés – car il n’y a jamais de solutionuniqueàunproblèmedonné!–et, sibesoin,commentairessur lespointsdélicatsetsuggestionssurlesextensionspossiblesduprogramme.
Lecodesourcedescorrigésestfournisurlesitewww.editions-eyrolles.com.
Àquis’adressecelivre?•Auxétudiantsdescursusuniversitaires (DUT, licence,master), ainsiqu’auxélèvesdesécolesd’ingénieur.
•Àtoutprogrammeurayantdéjàuneexpériencedelaprogrammation(C,Python,Java,PHP…)etsouhaitants’initieraulangageC++.
Ausommaire
Généralités sur le C++, types de base, opérateurs et expressions (7 exercices) •Instructionsdecontrôle(16exercices)•Fonctions(10exercices)•Tableaux,pointeurset chaînes de type C (13 exercices) • Structures (6 exercices) • Du C au C++ (9exercices) • Classes, constructeurs et destructeurs (7exercices) • Propriétés desfonctionsmembres(5exercices)Construction,destructionetinitialisationdesobjets(7exercices)•Fonctionsamies(3exercices)•Surdéfinitiond’opérateurs(11exercices)•Conversionsdetypedéfiniesparl’utilisateur(7exercices)•Techniquedel’héritage(7exercices)•Héritagemultiple(6exercices)•Fonctionsvirtuellesetpolymorphisme(3
1
exercices) • Flots d’entrée et de sortie (5 exercices) • Patrons de fonctions (4exercices)•Patronsdeclasses(8exercices)•Gestiondesexceptions(7exercices)•Exercicesdesynthèse(6exercices)•Composantsstandard(11exercices).
BiographieauteurIngénieurinformaticienauCNRS,ClaudeDelannoypossèdeunegrandepratiquedelaformation continue et de l’enseignement supérieur. Réputés pour la qualité de leurdémarche pédagogique, ses ouvrages sur les langages et la programmation totalisentplusde500000exemplairesvendus.
www.editions-eyrolles.com
2
AUXÉDITIONSEYROLLES
Dumêmeauteur
C.DELANNOY.–ProgrammerenlangageC++.N°14008,8eédition,2011,820pages.C.DELANNOY.–ProgrammerenJava.Java5à8.N°11889,9eédition,2014,948pages(rééditionavecnouvelleprésentation,2016).
C.DELANNOY.–ExercicesenJava.N°67385,4eédition,2014,360pages(rééditionavecnouvelleprésentation,2016).C.DELANNOY.–S’initieràlaprogrammationetàl’orientéobjet.AvecdesexemplesenC,C++,C#,Python,JavaetPHP.N°11826,2eédition,2014,360pages.
C.DELANNOY.–LeguidecompletdulangageC.N°14012,2014,844pages.
AutresouvragesH.BERSINI,I.WELLESZ.–Laprogrammationorientéeobjet.CoursetexercicesenUML2avecPython,PHP,Java,C#,C++.N°67399,7eédition,2017,672pages.
P.ROQUES.–UML2parlapratiqueN°12565,7eédition,2009,396pages.J.ENGELS.–PHP7.Coursetexercices.N°67360,2017,608pages.
P.MARTIN,J.PAULI,C.PIERREdeGEYERetE.DASPET.–PHP7avancé.N°14357,2016,728pages.G.SWINNEN.–ApprendreàprogrammeravecPython3.N°13434,3eédition,2012,435pages.
E.BIERNAT,M.LUTZ.–Datascience:fondamentauxetétudesdecas.N°14243,2015,312pages.
3
ClaudeDelannoy
ExercicesenlangageC++
3eédition
Troisièmetirage2017,avecnouvelleprésentation
4
ÉDITIONSEYROLLES61,bdSaint-Germain75240ParisCedex05
www.editions-eyrolles.com
Attention:pourlirelesexemplesdelignesdecode,réduisezlapolicedevotresupportaumaximum.
Enapplicationdelaloidu11mars1957,ilestinterditdereproduireintégralementoupartiellementleprésentouvrage,surquelquesupportquecesoit,sansl’autorisationdel’ÉditeurouduCentrefrançaisd’exploitationdudroitdecopie,20,ruedesGrands-Augustins,75006Paris.
La troisième édition du présent ouvrage est parue en 2007 sous l’ISBN 978-2-212-12201-5. À l’occasion de cetroisièmetirage,ellebénéficied’unenouvellecouverture.Letexteresteinchangé.
©GroupeEyrolles,1997-2007,pourletextedelaprésenteédition.©GroupeEyrolles,2017,pourlanouvelleprésentation.ISBN:978-2-212-67387-6.
5
Avant-propos
La maîtrise d’un langage de programmation passe obligatoirement par la pratique,c’est-à-dire la recherche personnelle d’une solution à un problème donné, et cetteaffirmationrestevraiepourleprogrammeurchevronnéquiétudieunnouveaulangage.C’estdanscettesituationquesetrouvegénéralementunepersonnequiabordeleC++:
soitelleconnaîtdéjàun langageprocéduralclassiqueautreque leC(Java,VisualBasic,Pascal,...),
soitelleconnaîtdéjàlalangageCsurlequels’appuieeffectivementC++;toutefois,cedernierlangageintroduitsuffisammentdepossibilitéssupplémentairesetsurtoutde nouveaux concepts (en particulier ceux de la Programmation Orientée Objet)pourquesonapprentissages’apparenteàceluid’unnouveaulangage.
Cet ouvrage vous propose d’accompagner votre étude du C++ et de la prolonger àl’aide d’exercices appropriés, variés et de difficulté croissante, et ceci quelles quesoientvosconnaissancespréalables.Ilcomporte:
4chapitresdestinésàceuxd’entrevousquineconnaissentpasleC:typesdebase,opérateursetexpressions;instructionsdecontrôle;fonctions;tableaux,pointeursetchaînesdestyleC;
unchapitredestinéàassurerlatransitiondeCàC++destinésàceuxquiconnaissentdéjàlelangageC;
seizechapitresdestinésàtous:lesnotionsdeclasse,constructeuretdestructeur;lespropriétés des fonctionsmembre ; la construction, la destruction et l’initialisationdesobjets;lesfonctionsamies;lasurdéfinitiond’opérateurs;lesconversionsdetypedéfiniesparl’utilisateur;latechniquedel’héritage;lesfonctionsvirtuelles;
6
lesflotsd’entréeetdesortie,lespatronsdefonctionsetlespatronsdeclasses;lagestiondesexceptions.Lechapitre20proposedesexercicesdesynthèse.
Chaque chapitre débute par un rappel détaillé des connaissances nécessaires pouraborder lesexercicescorrespondants (naturellement,unexerciced’unchapitredonnépeutfaireintervenirdespointsrésumésdansleschapitresprécédents).
Lecourscompletcorrespondantàcesrésuméssetrouvedansl’ouvrageApprendre leC++,dumêmeauteur.
Auseindechaquechapitre, lesexercicesproposésvontd’uneapplicationimmédiatedu cours à des réalisations de classes relativement complètes. Au fil de votreprogression dans l’ouvrage, vous réaliserez des classes de plus en plus réalistes etopérationnelles,etayantunintérêtgénéral;citons,parexemple:
lesensembles;
lesvecteursdynamiques;
lestableauxdynamiquesàplusieursdimensions;
leslisteschaînées;
lestableauxdebits;
les(vraies)chaînesdecaractères;
lespiles;
lescomplexes.
Naturellement,touslesexercicessontcorrigés.Pourlaplupart,lasolutionproposéenese limite pas à une simple liste d’un programme (laquelle ne représente finalementqu’une rédactionpossibleparmid’autres).Vousy trouverezune analysedétailléeduproblèmeet,sibesoin,lesjustificationsdecertainschoix.Descommentairesviennent,lecaséchéant,éclairerlespartiesquelquepeudélicates.Fréquemment,voustrouverezdessuggestionsdeprolongementoudegénéralisationduproblèmeabordé.
Outre la maîtrise du langage C++ proprement dit, les exercices proposés vouspermettront de vous forger uneméthodologie de conception de vos propres classes.Notamment,voussaurez:
décider du bien-fondé de la surdéfinition de l’opérateur d’affectation ou duconstructeurparrecopie;
exploiter, lorsque vous jugerez que cela est opportun, les possibilités de«conversionsimplicites»quelecompilateurpeutmettreenplace;
7
tirerpartidel’héritage(simpleoumultiple)etdéterminerquelsavantagesprésentela création d’une bibliothèque de classes, notamment par le biais du typagedynamiquedesobjetsquidécouledel’emploidesfonctionsvirtuelles;
mettreenœuvrelespossibilitésdefonctionsgénériques(patronsdefonctions)etdeclassesgénériques(patronsdeclasses).
Quelques exercices proposés dans les précédentes éditions de l’ouvrage trouventmaintenant une solution évidente en faisant appel aux composants standard introduitspar la norme. Nous les avons cependant conservés, dans lamesure où la recherched’une solution ne faisant pas appel aux composants standard conserve un intérêtdidactiquemanifeste.De surcroît,nousavons introduitunnouveauchapitre (21),quimontrecommentrésoudrelesexerciceslorsqu’onaccepte,cettefois,derecouriràcescomposantsstandard.
8
Tabledesmatières
1Généralités,typesdebase,opérateursetexpressionsExercice1Exercice2Exercice3Exercice4Exercice5Exercice6Exercice7
2LesinstructionsdecontrôleExercice8Exercice9Exercice10Exercice11Exercice12Exercice13Exercice14Exercice15Exercice16Exercice17Exercice18Exercice19Exercice20Exercice21Exercice22Exercice23
3LesfonctionsExercice24Exercice25Exercice26Exercice27Exercice28
9
Exercice29Exercice30Exercice31Exercice32Exercice33
4Lestableaux,lespointeursetleschaînesdestyleCExercice34Exercice35Exercice36Exercice37Exercice38Exercice39Exercice40Exercice41Exercice42Exercice43Exercice44Exercice45Exercice46
5LesstructuresExercice47Exercice48Exercice49Exercice50Exercice51Exercice52
6DeCàC++Exercice53Exercice54Exercice55Exercice56Exercice57Exercice58Exercice59Exercice60Exercice61
7Notionsdeclasse,constructeuretdestructeur
10
Exercice62Exercice63Exercice64Exercice65Exercice66Exercice67Exercice68
8PropriétésdesfonctionsmembreExercice69Exercice70Exercice71Exercice72Exercice73
9Construction,destructionetinitialisationdesobjetsExercice74Exercice75Exercice76Exercice77Exercice78Exercice79Exercice80
10LesfonctionsamiesExercice81Exercice82Exercice83
11Surdéfinitiond’opérateursExercice84Exercice85Exercice86Exercice87Exercice88Exercice89Exercice90Exercice91Exercice92Exercice93Exercice94
11
12Lesconversionsdetypedéfiniesparl’utilisateurExercice95Exercice96Exercice97Exercice98Exercice99Exercice100Exercice101
13Latechniquedel’héritageExercice102Exercice103Exercice104Exercice105Exercice106Exercice107Exercice108
14L’héritagemultipleExercice109Exercice110Exercice111Exercice112Exercice113Exercice114
15LesfonctionsvirtuellesExercice115Exercice116Exercice117
16Lesflotsd’entréeetdesortieExercice118Exercice119Exercice120Exercice121Exercice122
17LespatronsdefonctionsExercice123Exercice124
12
Exercice125Exercice126
18LespatronsdeclassesExercice127Exercice128Exercice129Exercice130Exercice131Exercice132Exercice133Exercice134
19GestiondesexceptionsExercice135Exercice136Exercice137Exercice138Exercice139Exercice140Exercice141
20ExercicesdesynthèseExercice142Exercice143Exercice144Exercice145Exercice146Exercice147
21LescomposantsstandardExercice148(67revisité)Exercice149(68revisité)Exercice150(77revisité)Exercice151(78revisité)Exercice152(79revisité)Exercice153(90revisité)Exercice154(91revisité)Exercice155(93revisité)Exercice156(142revisité)Exercice157(143revisité)
13
Exercice158(94revisité)
14
Chapitre1Généralités,typesdebase,opérateurset
expressions
15
Rappels
GénéralitésLecanevasminimalàutiliserpourréaliserunprogrammeC++seprésenteainsi:
#include<iostream>
usingnamespacestd;
main()//en-tête
{.....//corpsduprogramme
}
Toutevariableutiliséedansunprogrammedoitavoirfaitl’objetd’unedéclarationenprécisant le type et, éventuellement, la valeur initiale. Voici des exemples dedéclarations:
inti;//iestunevariabledetypeintnomméei
floatx=5.25;//xestunevariabledetypefloatnomméex
//initialiséeaveclavaleur5.25
constintNFOIS=5;//NFOISestunevariabledetypeintdontla
//valeur,fixéeà5,nepeutplusêtremodifiée
L’affichaged’informations à l’écran est réalisé en envoyant des valeurs sur le « flotcout»,commedans:
cout<<n<<2*p;//affichelesvaleursdenetde2*psurl’écran
La lecture d’informations au clavier est réalisée en extrayant des valeurs du « flotcin»,commedans:
cin>>x>>y;//litdeuxvaleursauclavieretlesaffecteàxetày
TypesdebaseLes types de base sont ceux à partir desquels seront construits tous les autres, ditsdérivés(ils’agiradestypesstructuréscommelestableaux,lesstructures,lesunionsetlesclasses,oud’autrestypessimplescommelespointeursoulesénumérations).
Ilexistetroistypesentiers:shortint(oushort),intetlongint(oulong).Leslimitationscorrespondantes dépendent de l’implémentation.On peut également définir des typesentiersnonsignés:unsignedshortint(ouunsignedshort),unsignedintetunsignedlongint(ouunsigned long). Ces derniers sont essentiellement destinés à lamanipulation demotifsbinaires.
Lesconstantesentièrespeuventêtreécritesennotationhexadécimale(comme0xF54B)ouoctale(comme014).Onpeutajouterle«suffixe»upourunentiernonsignéetle
16
suffixelpourunentierdetypelong.
Il existe trois types flottants : float,doubleetlongdouble.Laprécisionet le«domainereprésentable»dépendentdel’implémentation.
Letype«caractère»permetdemanipulerdescaractèrescodéssurunoctet.Lecodeutilisédépenddel’implémentation.Ilexistetroistypescaractère:signedchar,unsignedcharetchar(lanormeneprécisepass’ilcorrespondàsignedcharouunsignedchar).
Les constantes de type caractère, lorsqu’elles correspondent à des « caractèresimprimables»,senotentenplaçantlecaractèrecorrespondantentreapostrophes.
Certains caractères disposent d’une représentation conventionnelle utilisant lecaractère«\»notamment’\n’quidésigneunsautdeligne.Demême,’\’’représentelecaractère ’ et ’\"’ désigne le caractère ". On peut également utiliser la notationhexadécimale(commedans’\x41’)ouoctale(commedans’\07’).
Letypeboolpermetdemanipulerdes«booléens».Ildisposededeuxconstantesnotéestrueetfalse.
LesopérateursdeC++Voici un tableau présentant l’ensemble des opérateurs de C++ (certains ne serontexploitésquedansdeschapitresultérieurs):
Catégorie Opérateurs Associativité
Résolutiondeportée ::(portéeglobale-unaire)::(portéedeclasse-binaire)
<---->
Référence ()[]->. -->
Unaire
+-++--!~*&sizeofcastdynamic_caststatic_castreinterpret_castconst_castnewnew[]deletedelete[]
<---
Sélection ->*.* <--Arithmétique */% --->Arithmétique +- --->Décalage <<>> --->Relationnels <<=>>= --->Relationnels ==!= --->Manipulationdebits & --->
17
Manipulationdebits ^ --->Manipulationdebits | --->Logique && --->Logique || --->Conditionnel(ternaire) ?: --->
Affectation =+=-=*=/=%=&=^=|=<<=>>= <---
Séquentiel , --->
LesopérateursarithmétiquesetlesopérateursrelationnelsLesopérateursarithmétiquesbinaires(+,-,*et/)etlesopérateursrelationnelsnesontdéfinisquepourdesopérandesd’unmêmetypeparmi:int,longint(etleursvariantesnonsignées),float,doubleetlongdouble.Maisonpeutconstituerdesexpressionsmixtes(opérandesdetypesdifférents)oucontenantdesopérandesd’autrestypes(bool,charetshort),grâceàl’existencededeuxsortesdeconversionsimplicites:
lesconversionsd’ajustementdetype,selonl’unedeshiérarchies:int->long->float->double->longdouble
unsingedint->unsignedlong->float->double->longdouble
lespromotionsnumériques,àsavoirdesconversionssystématiquesdechar(avecousansattributdesigne),booletshortenint.
LesopérateurslogiquesLes opérateurs logiques && (et), || (ou) et (non) acceptent n’importe quel opérandenumérique(entierouflottant)oupointeur,enconsidérantquetoutopérandedevaleurnonnullecorrespondà«faux»:
18
Lesdeuxopérateurs&&et||sont«àcourt-circuit»:lesecondopéranden’estévaluéquesilaconnaissancedesavaleurestindispensable.
Opérateursd’affectationL’opérandedegauched’unopérateurd’affectationdoitêtreunelvalue,c’est-à-direlaréférenceàquelquechosedemodifiable.
Lesopérateursd’affectation(=,-=,+=...),appliquésàdesvaleursdetypenumérique,provoquentlaconversiondeleuropérandededroitedansletypedeleuropérandedegauche.Cetteconversion«forcée»peutêtre«dégradante».
Opérateursd’incrémentationetdedécrémentationLesopérateursunairesd’incrémentation (++) etdedécrémentation (--) agissent sur lavaleurdeleuruniqueopérande(quidoitêtreunelvalue)etfournissentlavaleuraprèsmodification lorsqu’ils sontplacésàgauche (commedans ++n) ou avantmodificationlorsqu’ilssontplacésàdroite(commedansn--).
OpérateurdecastIlestpossibledeforcerlaconversiond’uneexpressionquelconquedansuntypedesonchoix, grâce à l’opérateur dit de « cast ». Par exemple, si n et p sont des variablesentières,l’expression:
19
(double)n/p//ou:static_cast<double>(n/p)
auracommevaleurcelledel’expressionentièren/pconvertieendouble.
OpérateurconditionnelCetopérateurternairefournitcommerésultatlavaleurdesondeuxièmeopérandesilaconditionmentionnée en premier opérande est non nulle (vraie pour une expressionbooléenne),etlavaleurdesontroisièmeopérandedanslecascontraire.Parexemple,aveccetteaffectation:
max=a>b?a:b;
onobtiendradanslavariablemaxlavaleurdeasilaconditiona>bestvraie,lavaleurdebdanslecascontraire.Avec:
valeur=2*n-1?a:b;
onobtiendradanslavariablevaleurlavaleurdeasil’expression2*n-1estnonnulle,lavaleurdebdanslecascontraire.
20
Exercice1
ÉnoncéÉliminerlesparenthèsessuperfluesdanslesexpressionssuivantes:
a=(x+5)/*expression1*/
a=(x=y)+2/*expression2*/
a=(x==y)/*expression3*/
(a<b)&&(c<d)/*expression4*/
(i++)*(n+p)/*expression5*/
a=x+5/*expression1*/
L’opérateur+estprioritairesurl’opérateurd’affectation=.a=(x=y)+2/*expression2*/
Ici,l’opérateur+étantprioritairesur=,lesparenthèsessontindispensables.a=x==y/*expression3*/
L’opérateur==estprioritairesur=.a<b&&c<d/*expression4*/
L’opérateur&&estprioritairesurl’opérateur<.i++*(n+p)/*expression5*/
L’opérateur++estprioritairesur*;enrevanche,*estprioritairesur+,desortequ’onnepeutéliminerlesdernièresparenthèses.
21
Exercice2
ÉnoncéSoientlesdéclarations:
charc='\x01';
shortintp=10;
Quelssontletypeetlavaleurdechacunedesexpressionssuivantes:p+3/*1*/
c+1/*2*/
p+c/*3*/
3*p+5*c/*4*/
1.pestd’abordsoumisàlaconversion«systématique»short->int,avantd’êtreajoutéàlavaleur3(int).Lerésultat13estdetypeint.
2.cestd’abordsoumisàlaconversion«systématique»char->int(cequiaboutitàlavaleur1),avantd’êtreajoutéàlavaleur1(int).Lerésultat2estdetypeint.
3.pestd’abordsoumisàlaconversionsystématiqueshort->int,tandisquecestsoumisà laconversion systématique char->int ; les résultats sont alors additionnés pouraboutiràlavaleur11detypeint.
4.petcsontd’abordsoumisauxmêmesconversionssystématiquesqueci-dessus;lerésultat35estdetypeint.
22
Exercice3
ÉnoncéSoientlesdéclarations:
charc='\x05';
intn=5;
longp=1000;
floatx=1.25;
doublez=5.5;
Quelssontletypeetlavaleurdechacunedesexpressionssuivantes:n+c+p/*1*/
2*x+c/*2*/
(char)n+c/*3*/
(float)z+n/2/*4*/
1.cesttoutd’abordconvertienint,avantd’êtreajoutéàn.Lerésultat(10),detypeint,estalorsconvertienlong,avantd’êtreajoutéàp.Onobtientfinalementlavaleur1010,detypelong.
2.Onévalued’abordlavaleurde2*x,enconvertissant2(int)enfloat,cequifournitlavaleur 2.5 (de type float). Par ailleurs, c est converti en int (conversionsystématique).Onévalueensuitelavaleurde2*x,enconvertissant2(int)enfloat,cequifournitlavaleur2.5(detypefloat).Poureffectuerl’addition,onconvertitalorslavaleurentière5 (c)enfloat, avantde l’ajouterau résultatprécédent.Onobtientfinalementlavaleur7.75,detypefloat.
3.nesttoutd’abordconvertienchar(àcausedel’opérateurde«cast»),tandisquecestconverti(conversionsystématique)enint.Puis,pourprocéderàl’addition,ilestnécessaire de reconvertir la valeur de (char) n en int. Finalement, on obtient lavaleur10,detypeint.
4.z estd’abordconverti en float, cequi fournit lavaleur 5.5 (approximative, car, enfait, on obtient une valeur un peu moins précise que ne le serait 5.5 exprimé endouble). Par ailleurs, on procède à la division entière de n par 2, ce qui fournit lavaleurentière2.Cettedernièreestensuiteconvertieenfloat,avantd’êtreajoutéeà5.5,cequifournitlerésultat7.5,detypefloat.
23
Exercice4
ÉnoncéSoientlesdéclarationssuivantes:
intn=5,p=9;
intq;
floatx;
Quelle est lavaleur affectée auxdifférentesvariables concernéespar chacunedesinstructionssuivantes?
q=n<p;/*1*/
q=n==p;/*2*/
q=p%n+p>n;/*3*/
x=p/n;/*4*/
x=(float)p/n;/*5*/
x=(p+0.5)/n;/*6*/
x=(int)(p+0.5)/n;/*7*/
q=n*(p>n?n:p);/*8*/
q=n*(p<n?n:p);/*9*/
1.1
2.0
3.5(p%nvaut4,tandisquep>nvaut1).
4.1(p/nestd’abordévaluéenint,cequifournit1;puislerésultatestconvertienfloat,avantd’êtreaffectéàx).
5.1.8(pestconvertienfloat,avantd’êtrediviséparlerésultatdelaconversiondenenfloat).
6.1.9 (p est converti en float, avant d’être ajouté à 0.5 ; le résultat est divisé par lerésultatdelaconversiondenenfloat).
7.1(pestconvertienfloat,avantd’êtreajoutéà0.5;lerésultat(5.5)estalorsconvertienintavantd’êtrediviséparn).
8.25
9.45
24
Exercice5
ÉnoncéQuelsrésultatsfournitleprogrammesuivant:
#include<iostream>
usingnamespacestd;
main()
{
inti,j,n;
i=0;n=i++;
cout<<"A:i="<<i<<"n="<<n<<"\n";
i=10;n=++i;
cout<<"B:i="<<i<<"n="<<n<<"\n";
i=20;j=5;n=i++*++j;
cout<<"C:i="<<i<<"j="<<j<<"n="<<n<<"\n";
i=15;n=i+=3;
cout<<"D:i="<<i<<"n="<<n<<"\n";
i=3;j=5;n=i*=--j;
cout<<"E:i="<<i<<"j="<<j<<"n="<<n<<"\n";
}
A:i=1n=0
B:i=11n=11
C:i=21j=6n=120
D:i=18n=18
E:i=12j=4n=12
25
Exercice6
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
main()
{
intn=10,p=5,q=10,r;
r=n==(p=q);
cout<<"A:n="<<n<<"p="<<p<<"q="<<q
<<"r="<<r<<"\n";
n=p=q=5;
n+=p+=q;
cout<<"B:n="<<n<<"p="<<p<<"q="<<q<<"\n";
q=n<p?n++:p++;
cout<<"C:n="<<n<<"p="<<p<<"q="<<q<<"\n";
q=n>p?n++:p++;
cout<<"D:n="<<n<<"p="<<p<<"q="<<q<<"\n";
}
A:n=10p=10q=10r=1
B:n=15p=10q=5
C:n=15p=11q=10
D:n=16p=11q=15
26
Exercice7
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
main()
{intn,p,q;
n=5;p=2;/*cas1*/
q=n++>p||p++!=3;
cout<<"A:n="<<n<<"p="<<p<<"q="<<q<<"\n";
n=5;p=2;/*cas2*/
q=n++<p||p++!=3;
cout<<"B:n="<<n<<"p="<<p<<"q="<<q<<"\n";
n=5;p=2;/*cas3*/
q=++n==3&&++p==3;
cout<<"C:n="<<n<<"p="<<p<<"q="<<q<<"\n";
n=5;p=2;/*cas4*/
q=++n==6&&++p==3;
cout<<"D:n="<<n<<"p="<<p<<"q="<<q<<"\n";
}
Ilnefautpasoublierquelesopérateurs&&et||n’évaluent leursecondopérandequelorsquecelaestnécessaire.Ainsi,ici,iln’estpasévaluédanslescas1et3.Voicilesrésultatsfournisparceprogramme:
A:n=6p=2q=1
B:n=6p=3q=1
C:n=6p=2q=0
D:n=6p=3q=1
27
Chapitre2Lesinstructionsdecontrôle
28
Rappels
Letermeinstructiondésigneraindifféremment:uneinstructionsimple(terminéeparunpoint-virgule),uneinstructionstructurée(choix,boucle)ouunbloc(instructionsentre{et}).
InstructionifEllepossèdedeuxformes:
if(expression)instruction_1
elseinstruction_2
if(expression)instruction_1
Lorsquedesinstructionsifsontimbriquées,unelseserapporte toujoursaudernierifauquelunelsen’apasencoreétéattribué.
Instructionswitchswitch(expression){bloc_d_instructions]
Cette instruction évalue la valeur de l’expression entièrementionnée, puis recherchedansleblocquisuits’ilexisteuneétiquettedelaformecasex(xétantuneexpressionconstante,c’est-à-direcalculablepar lecompilateur)correspondantàcettevaleur.Sic’estlecas,ilyabranchementàl’instructionfigurantàlasuitedecetteétiquette.Danslecascontraire,onpasseàl’instructionsuivantlebloc.L’expressionpeutêtrede typechar,auquelcaselleseraconvertieenentier.
Uneinstructionswitchpeutconteniruneouplusieursinstructionsbreakquiprovoquentlasortie du bloc. Il est possible d’utiliser lemot default comme étiquette à laquelle leprogramme se branche lorsque aucune valeur satisfaisante n’a été rencontréeauparavant.
Instructionsdo...whileetwhiledoinstructionwhile(expression);
while(expression)instruction
L’expressiongouvernantlabouclepeutêtred’untypequelconque;elleseraconvertieenboolselonlarègle:nonnuldevientvrai,nuldevientfaux.
29
Instructionforfor([expression_déclaration_1];[expression_2];[expression_3])
instruction
Lescrochetssignifientqueleurcontenuestfacultatif.
expression_déclaration_1estsoituneexpression,soitunedéclarationd’uneouplusieursvariablesd’unmêmetype,initialiséesounon;
expression_2 est une expression quelconque (qui sera éventuellement convertie enbool);
expression_3estuneexpressionquelconque.
Cetteinstructionestéquivalenteà:{expression_déclaration_1;
while(expression_2){instruction;
expression_3;
}
}
Instructionsbreak,continueetgotoUneboucle (do...while,while ou for) peut contenir une ou plusieurs instructions breakdont le rôleestd’interrompre ledéroulementde laboucle, enpassantà l’instructionquisuitcetteboucle.Encasdebouclesimbriquées,breakfaitsortirdelabouclelaplusinterne.Sibreakapparaîtdansunswitchimbriquédansuneboucle,ellenefaitsortirqueduswitch.L’instruction continue s’emploie uniquement dans une boucle. Elle permet de passerprématurémentautourdebouclesuivant.L’instructiongotopermetlebranchementenunemplacementquelconqueduprogramme,repéréparuneétiquette,commedanscetexempleoù,lorsqu’unecertaineconditionestvraie,onsebrancheàl’étiquetteerreur:
for(...){.....
if(...)gotoerreur;
.....
}
erreur:.....
30
Exercice8
ÉnoncéQuelleserreursontétécommisesdanschacundesgroupesd’instructionssuivants:
1.if(a<b)cout<<"ascendant"
elsecout<<"nonascendant";
2.intn;
...
switch(2*n+1)
{case1:cout<<"petit";
casen:cout<<"moyen";
}
3.constintLIMITE=100
intn;
...
switch(n)
{caseLIMITE-1:cout<<"unpeumoins";
caseLIMITE:cout<<"juste";
caseLIMITE+1:cout<<"unpeuplus";
}
1.Ilmanqueunpoint-virguleàlafindelapremièreligne:if(a<b)cout<<"ascendant";
elsecout<<"nonascendant";
2. Les valeurs suivant le mot case doivent obligatoirement être des « expressionsconstantes»,c’est-à-diredesexpressionscalculablesparlecompilateurlui-même.Cen’estpaslecasden.
3. Aucune erreur, les expressions telles que LIMITE-1 étant bien des expressionsconstantes(cequin’étaitpaslecasenlangageC).
31
Exercice9
ÉnoncéSoitleprogrammesuivant:
#include<iostream>
main()
{intn;
cin>>n;
switch(n)
{case0:cout<<"Nul\n";
case1:
case2:cout<<"Petit\n";
break;
case3:
case4:
case5:cout<<"Moyen\n";
default:cout<<"Grand\n";
}
}
Quelsrésultatsaffiche-t-illorsqu’onluifournitendonnée:a.0b.1c.4d.10e.-5
a.Nul
Petit
b.Petit
c.Moyen
Grand
d.Grand
e.Grand
32
Exercice10
ÉnoncéQuelleserreursontétécommisesdanschacunedesinstructionssuivantes:
a.docin>>cwhile(c!='\n');
b.dowhile(cin>>c,c!='\n');
c.do{}while(1);
a.Ilmanqueunpoint-virgule:docin>>c;while(c!='\n');
b. Il manque une instruction (éventuellement « vide ») après lemot do. On pourraitécrire,parexemple:do{}while((cin>>c,c!='\n');
ou:do;while(cin>>c,c!='\n');
c.Iln’yaurapasd’erreurdecompilation(lavaleurentière1estconvertieenbooléen,cequifournitlavaleurvrai);toutefois,ils’agitd’une«boucleinfinie».
33
Exercice11
ÉnoncéÉcrirepluslisiblement:
do{}while(cout<<"donnezunnombre>0",cin>>n,n<=0);
Plusieurs possibilités existent, puisqu’il « suffit » de reporter, dans le corps de laboucle,desinstructionsfigurant«artificiellement»sousformed’expressionsdanslaconditiondepoursuite:
do
cout<<donnezunnombre>0";
while(cin>>n,n<=0);
ou,mieux:do
{cout<<"donnezunnombre>0";
cin>>n;
}
while(n<=0);
34
Exercice12
ÉnoncéSoitlepetitprogrammesuivant:
#include<iostream>
usingnamespacestd;
main()
{inti,n,som;
som=0;
for(i=0;i<4;i++)
{cout<<"donnezunentier";
cin>>n;
som+=n;
}
cout<<"Somme:"<<som;
}
Écrireunprogrammeréalisantexactementlamêmechose,enemployant,àlaplacedel’instructionfor:a.uneinstructionwhile,b.uneinstructiondo...while.
a.#include<iostream>
usingnamespacestd;
main()
{inti,n,som;
som=0;
i=0;/*nepasoubliercette"initialisation"*/
while(i<4)
{cout<<"donnezunentier";
cin>>n;
som+=n;
i++;/*nicette"incrémentation"*/
}
cout<<"Somme:"<<som;
}
b.#include<iostream>
usingnamespacestd;
main()
{inti,n,som;
som=0;
i=0;/*nepasoubliercette"initialisation"*/
do
{cout<<"donnezunentier";
cin>>n;
som+=n;
i++;/*nicette"incrémentation"*/
}
35
while(i<4);/*attention,ici,toujours<4*/
cout<<"Somme:"<<som;
}
36
Exercice13
ÉnoncéQuelsrésultatsfournitleprogrammesuivant:
#include<iostream>
usingnamespacestd;
main()
{intn=0;
do
{if(n%2==0){cout<<n<<"estpair\n";
n+=3;
continue;
}
if(n%3==0){cout<<n<<"estmultiplede3\n";
n+=5;
}
if(n%5==0){cout<<n<<"estmultiplede5\n";
break;
}
n+=1;
}
while(1);
}
0estpair
3estmultiplede3
9estmultiplede3
15estmultiplede3
20estmultiplede5
37
Exercice14
ÉnoncéQuelsrésultatsfournitleprogrammesuivant:
#include<iostream>
usingnamespacestd;
main()
{intn,p;
n=0;
while(n<=5)n++;
cout<<"A:n="<<n<<"\n";
n=p=0;
while(n<=8)n+=p++;
cout<<"B:n="<<n<<"\n";
n=p=0;
while(n<=8)n+=++p;
cout<<"C:n="<<n<<"\n";
n=p=0;
while(p<=5)n+=p++;
cout<<"D:n="<<n<<"\n";
n=p=0;
while(p<=5)n+=++p;
cout<<"E:n="<<n<<"\n";
}
A:n=6
B:n=10
C:n=10
D:n=15
E:n=21
38
Exercice15
ÉnoncéQuelsrésultatsfournitleprogrammesuivant:
#include<iostream>
usingnamespacestd;
main()
{intn,p;
n=p=0;
while(n<5)n+=2;p++;
cout<<"A:n="<<n<<"p="<<p<<"\n";
n=p=0;
while(n<5){n+=2;p++;}
cout<<"B:n="<<n<<"p="<<p<<"\n";
}
A:n=6,p=1
B:n=6,p=3
39
Exercice16
ÉnoncéQuelsrésultatsfournitleprogrammesuivant:
#include<iostream>
usingnamespacestd;
main()
{inti,n;
for(i=0,n=0;i<5;i++)n++;
cout<<"A:i="<<i<<"n="<<n<<"\n";
for(i=0,n=0;i<5;i++,n++){}
cout<<"B:i="<<i<<"n="<<n<<"\n";
for(i=0,n=50;n>10;i++,n-=i){}
cout<<"C:i="<<i<<"n="<<n<<"\n";
for(i=0,n=0;i<3;i++,n+=i,
cout<<"D:i="<<i<<"n="<<n<<"\n");
cout<<"E:i="<<i<<"n="<<n<<"\n";
}
A:i=5,n=5
B:i=5,n=5
C:i=9,n=5
D:i=1,n=1
D:i=2,n=3
D:i=3,n=6
E:i=3,n=6
40
Exercice17
ÉnoncéÉcrireunprogrammequicalculelesracinescarréesdenombresfournisendonnée.Ils’arrêteralorsqu’onluifourniralavaleur0.Ilrefuseralesvaleursnégatives.Sonexécutionseprésenteraainsi:
donnezunnombrepositif:2
saracinecarréeest:1.414214e+00
donnezunnombrepositif:-1
svppositif
donnezunnombrepositif:5
saracinecarréeest:2.236068e+00
donnezunnombrepositif:0
Rappelonsque la fonctionsqrt fournit la racinecarrée(double)de lavaleur (double)qu’onluidonneenargument.
Ilexistebeaucoupderédactionspossibles;envoici3:
1.#include<iostream>
#include<cmath>//pourladéclarationdesqrt
usingnamespacestd;
main()
{doublex;
do
{cout<<"donnezunnombrepositif:";
cin>>x;
if(x<0)cout<<"svppositif\n";
if(x<=0)continue;
cout<<"saracinecarréeest:"<<sqrt(x)<<"\n";
}
while(x);
}
2.#include<iostream>
#include<cmath>
usingnamespacestd;
main()
{doublex;
do
{cout<<"donnezunnombrepositif:";
cin>>x;
if(x<0){cout<<"svppositif\n";
continue;
}
41
if(x>0)cout<<"saracinecarréeest:"<<sqrt(x)<<"\n";
}
while(x);
}
3.#include<iostream>
#include<cmath>
usingnamespacestd;
main()
{doublex;
do
{cout<<"donnezunnombrepositif:";
cin>>x;
if(x<0){cout<<"svppositif\n";
continue;
}
if(x>0)cout<<"saracinecarréeest:"<<sqrt(x)<<"\n";
if(x==0)break;
}
while(1);
}
42
Exercice18
ÉnoncéCalculerlasommedesnpremierstermesdela«sérieharmonique»,c’est-à-direlasomme:
1+1/2+1/3+1/4+.....+1/n
Lavaleurdenseralueendonnée.
#include<iostream>
usingnamespacestd;
main()
{
intnt;/*nombredetermesdelasérieharmonique*/
floatsom;/*pourlasommedelasérie*/
inti;
do
{cout<<"combiendetermes:";
cin>>nt;
}
while(nt<1);
for(i=1,som=0;i<=nt;i++)som+=(float)1/i;
cout<<"Sommedes"<<nt<<"premierstermes="<<som;
}
1.Rappelonsquedans:som+=(float)1/i
l’expressiondedroiteestévaluéeenconvertissantd’abord1etienfloat.Ilfautéviterd’écrire:som+=1/i
auquel cas, les valeurs de 1/i seraient toujours nulles (sauf pour i=1) puisquel’opérateur/,lorsqu’ilportesurdesentiers,correspondàladivisionentière.Demême,enécrivant:som+=(float)(1/i)
le résultatne seraitpasplus satisfaisantpuisque la conversionen flottantn’auraitlieuqu’aprèsladivision(enentier).Enrevanche,onpourraitécrire:som+=1.0/i;
2. Si l’on cherchait à exécuter ce programme pour des valeurs élevées de n (enprévoyantalorsunevariabledetypefloatoudouble),onconstateraitquelavaleurdela somme semble « converger » vers une limite (bien qu’en théorie la série
43
harmonique«diverge»).Celaprovienttoutsimplementdeceque,dèsquelavaleurde 1/i est « petite » devant som, le résultat de l’addition de 1/i et de som estexactementsom.Onpourrait toutefoisaméliorer le résultateneffectuant lasomme«àl’envers»(eneffet,danscecas,lerapportentrelavaleuràajouteretlasommecouranteseraitplusfaiblequeprécédemment).
44
Exercice19
ÉnoncéAfficherun triangle isocèle forméd’étoiles.Lahauteurdu triangle (c’est-à-dire lenombre de lignes) sera fourni en donnée, comme dans l’exemple ci-dessous. Ons’arrangera pour que la dernière ligne du triangle s’affiche sur le bord gauche del’écran.
combiendelignes?10
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
#include<iostream>
usingnamespacestd;
main()
{constcharcar='*';/*caractèrederemplissage*/
intnlignes;/*nombretotaldelignes*/
intnl;/*compteurdeligne*/
intnesp;/*nombred'espacesprécédantuneétoile*/
intj;
cout<<"combiendelignes?";
cin>>nlignes;
for(nl=0;nl<nlignes;nl++)
{nesp=nlignes-nl-1;
for(j=0;j<nesp;j++)cout<<'';
for(j=0;j<2*nl+1;j++)cout<<car;
cout<<'\n';
}
}
45
Exercice20
ÉnoncéAffichertouteslesmanièrespossiblesd’obteniruneuroavecdespiècesde2cents,5centset10cents.Direcombiendepossibilitésontétéainsitrouvées.Lesrésultatsserontaffichéscommesuit:
1euro=50X2c
1euro=45X2c2X5c
1euro=40X2c4X5c
1euro=35X2c6X5c
1euro=30X2c8X5c
1euro=25X2c10X5c
1euro=20X2c12X5c
1euro=15X2c14X5c
1euro=10X2c16X5c
1euro=5X2c18X5c
1euro=20X5c
1euro=45X2c1X10c
1euro=40X2c2X5c1X10c
1euro=35X2c4X5c1X10c
1euro=10X2c2X5c7X10c
1euro=5X2c4X5c7X10c
1euro=6X5c7X10c
1euro=10X2c8X10c
1euro=5X2c2X5c8X10c
1euro=4X5c8X10c
1euro=5X2c9X10c
1euro=2X5c9X10c
1euro=10X10c
Entout,ilya66façonsdefaire1euro
Rappelonsquel’insertiondansleflotcoutd’uneexpressiondelaformesetw(n,oùnestuneexpressionentière,demandederéaliserl’affichagesuivant(etuniquementcedernier) sur n caractères au minimum. L’emploi de setw nécessite l’inclusion dufichieriomanip.
#include<iostream>
#include<iomanip>//poursetw
usingnamespacestd;
main()
{
intnbf;//compteurdunombredefaçonsdefaire1euro
intn10;//nombredepiècesde10centimes
intn5;//nombredepiècesde5centimes
intn2;//nombredepiècesde2centimes
nbf=0;
for(n10=0;n10<=10;n10++)
for(n5=0;n5<=20;n5++)
for(n2=0;n2<=50;n2++)
if(2*n2+5*n5+10*n10==100)
46
{nbf++;
cout<<"1euro=";
if(n2)cout<<setw(2)<<n2<<"X2c";
if(n5)cout<<setw(2)<<n5<<"X5c";
if(n10)cout<<setw(2)<<n10<<"X10c";
cout<<"\n";
}
cout<<"\nEntout,ilya"<<nbf<<"façonsdefaire1euro\n";
}
47
Exercice21
ÉnoncéÉcrireunprogrammequidéterminelanièmevaleurun(nétantfourniendonnée)dela«suitedeFibonacci»définiecommesuit:
u1=1
u2=1
un=un-1+un-2pourn>2
#include<iostream>
usingnamespacestd;
main()
{
intu1,u2,u3;/*pour"parcourir"lasuite*/
intn;/*rangdutermedemandé*/
inti;/*compteur*/
do
{cout<<"rangdutermedemandé(aumoins3)?";
cin>>n;
}
while(n<3);
u2=u1=1;/*lesdeuxpremierstermes*/
i=2;
while(i++<=n)/*attention,l'algorithmenefonctionne*/
{u3=u1+u2;/*quepourn>2*/
u1=u2;
u2=u3;
}
//autreformulationpossible:
//for(i=3;i<=n;i++,u1=u2,u2=u3)u3=u1+u2;
cout<<"Valeurdutermederang"<<n<<":"<<u3;
}
Notezque, commeà l’accoutuméeenC++,beaucoupde formulations sontpossibles.Nousenavonsd’ailleursplacéunesecondeencommentairedenotreprogramme.
48
Exercice22
ÉnoncéÉcrire un programme qui trouve la plus grande et la plus petite valeur d’unesuccessiondenotes(nombresentiersentre0et20)fourniesendonnées,ainsiquelenombredefoisoùcemaximumetceminimumontétéattribués.Onsupposeraquelesnotes,ennombrenonconnuàl’avance,serontterminéesparunevaleurnégative.Ons’astreindraànepasutiliserde«tableau».L’exécutionduprogrammepourraseprésenterainsi:
donnezunenote(-1pourfinir):12
donnezunenote(-1pourfinir):8
donnezunenote(-1pourfinir):13
donnezunenote(-1pourfinir):7
donnezunenote(-1pourfinir):11
donnezunenote(-1pourfinir):12
donnezunenote(-1pourfinir):7
donnezunenote(-1pourfinir):9
donnezunenote(-1pourfinir):-1
notemaximale:13attribuée1fois
noteminimale:7attribuée2fois
#include<iostream>
usingnamespacestd;
main()
{intnote;//note"courante"
intmax;//notemaxi
intmin;//notemini
intnmax;//nombredefoisoùlanotemaxiaététrouvée
intnmin;//nombredefoisoùlanoteminiaététrouvée
max=-1;//initialisationmax(possiblecartoutesnotes>=0
min=21;//initialisationmin(possiblecartoutesnotes<21
while(cout<<"donnezunenote(-1pourfinir):",
cin>>note,note>=0)
{if(note==max)nmax++;
if(note>max){max=note;
nmax=1;
}
if(note==min)nmin++;
if(note<min){min=note;
nmin=1;
}
}
if(max>=0)
{cout<<"\nnotemaximale:"<<max<<"attribuée"
<<nmax<<"fois\n";
cout<<"noteminimale:"<<min<<"attribuée"
<<nmin<<"fois\n";
}
elsecout<<"vousn'avezfourniaucunenote";
49
}
50
Exercice23
ÉnoncéÉcrireunprogrammequiaffichela«tabledemultiplication»desnombresde1à10,souslaformesuivante:
I12345678910
-----------------------------------------------
1I12345678910
2I2468101214161820
3I36912151821242730
4I481216202428323640
5I5101520253035404550
6I6121824303642485460
7I7142128354249566370
8I8162432404856647280
9I9182736455463728190
10I102030405060708090100
Rappelonsquel’insertiondansleflotcoutd’uneexpressiondelaformesetw(n),oùnestuneexpressionentière,demandederéaliser l’affichagesuivantsurncaractèresauminimum.L’emploidesetwnécessitel’inclusiondufichieriomanip.
#include<iostream>
#include<iomanip>//poursetw
usingnamespacestd;
main()
{constintNMAX=10;//nombredevaleurs
inti,j;
/*affichageligneen-tête*/
cout<<"I";
for(j=1;j<=NMAX;j++)cout<<setw(4)<<j;
cout<<"\n";
printf("-------");
for(j=1;j<=NMAX;j++)cout<<"----";
cout<<"\n";
/*affichagedesdifférenteslignes*/
for(i=1;i<=NMAX;i++)
{cout<<setw(4)<<i<<"I";
for(j=1;j<=NMAX;j++)
cout<<setw(4)<<i*j;
cout<<"\n";
}
}
51
Chapitre3Lesfonctions
52
Rappels
GénéralitésUne fonction est unblocd’instructions éventuellementparamétrépar unouplusieursarguments et pouvant fournir un résultat nommé souvent « valeur de retour ». Ondistingue ladéfinitiond’unefonctiondesonutilisation,cettedernièrenécessitantunedéclaration.
Ladéfinitiond’unefonctionseprésentecommedanscetexemple:floatfexple(floatx,intb,intc)//en-têtedelafonction
{//corpsdelafonction
}
L’en-têtepréciselenomdelafonction(fexple)ainsiqueletypeetlenom(muet)desesdifférentsarguments(x,betc).Lecorpsestunblocd’instructionsquidéfinitlerôledelafonction.
Auseind’uneautrefonction(ycomprismain),onutilisecettefonctiondecettefaçon:floatfepxle(float,int,int);//déclarationdefexple("prototype")
.....
fexple(z,n,p);//appelfexpleaveclesargumentseffectifsz,netp
Ladéclaration d’une fonction peut être omise lorsqu’elle est connue du compilateur,c’est-à-direquesadéfinitionadéjàétérencontréedanslemêmefichiersource.
ModedetransmissiondesargumentsPardéfaut,lesargumentssonttransmisparvaleur.Danscecas,lesargumentseffectifspeuventseprésentersouslaformed’uneexpressionquelconque.
Enfaisantsuivredusymbole& letyped’unargumentdansl’en-têted’unefonction(etdans sadéclaration),on réaliseune transmissionpar référence.Cela signifieque leséventuelles modifications effectuées au sein de la fonction porteront sur l’argumenteffectifdel’appeletnonplussurunecopie.Onnoteraqu’alorsl’argumenteffectifdoitobligatoirement être une lvalue du même type que l’argument muet correspondant.Toutefois,si l’argumentmuetest,desurcroît,déclaréavecl’attributconst, la fonctionreçoit quandmêmeune copie de l’argument effectif correspondant, lequel peut alorsêtreuneconstanteouuneexpressiond’untypesusceptibled’êtreconvertidansletypeattendu.
Cespossibilitésdetransmissionparréférences’appliquentégalementàunevaleurde
53
retour(danscecas,lanotiondeconstancen’aplusdesignification).
La notion de référence est théoriquement indépendante de celle de transmissiond’argument;enpratique,elleestrarementutiliséeendehorsdececontexte.
L’instructionreturnL’instruction return sert à la fois à fournir une valeur de retour et à mettre fin àl’exécutiondelafonction.Ellepeutmentionneruneexpression;ellepeutapparaîtreàplusieurs reprises dans une même fonction ; si aucune instruction return n’estmentionnée,leretourestmisenplaceàlafindelafonction.
Lorsqu’unefonctionnefournitaucunrésultat,sonen-têteetsadéclarationcomportentlemotvoidàlaplacedutypedelavaleurderetour,commedans:
voidfSansValRetour(...)
Lorsqu’unefonctionnereçoitaucunargument,l’en-têteetladéclarationcomportentunelistevide,commedans:
intfSansArguments()
LesargumentspardéfautDans la déclaration d’une fonction, il est possible de prévoir pour un ou plusieursarguments(obligatoirementlesderniersdelaliste)desvaleurspardéfaut;ellessontindiquéesparlesigne=,àlasuitedutypedel’argument,commedanscetexemple:
floatfct(char,int=10,float=0.0);
Cesvaleurspardéfautserontalorsutiliséeslorsqu’onappelleraladitefonctionavecunnombre d’arguments inférieur à celui prévu. Par exemple, avec la précédentedéclaration,l’appelfct('a')seraéquivalentàfct('a',10,0.0) ;demême,l’appelfct('x',12)seraéquivalentàfct('x',12,0.0).Enrevanche,l’appelfct()seraillégal.
ConversiondesargumentsLorsqu’unargumentesttransmisparvaleur,ilestéventuellementconvertidansletypementionnédansladéclaration(laconversionpeutêtredégradante).Cespossibilitésdeconversiondisparaissentencasdetransmissionparréférence:l’argumenteffectifdoitalorsêtreune lvalue du typeprévu ; cttedernièrenepeutposséder l’attribut const si
54
celui-ci n’est pas prévu dans l’argument muet ; en revanche, si l’agument muetmentionne l’attributconst, l’argumenteffectifpourraêtrenonseulementuneconstante,maiségalementuneexpressiond’untypequelconquedontlavaleurseraalorsconvertiedansunevariabletemporairedontl’adresseserafournieàlafonction.
VariablesglobalesetlocalesUnevariabledéclaréeendehorsdetoutefonction(ycomprisdumain)estditeglobale.Laportéed’unevariableglobaleestlimitéeàlapartieduprogrammesourcesuivantsadéclaration.Lesvariableglobalesontuneclassed’allocationstatique,cequisignifiequeleursemplacementsenmémoirerestentfixespendantl’exécutionduprogramme.
Unevariabledéclaréedansunefonctionestditelocale.Laportéed’unevariablelocaleest limitée à la fonctiondans laquelle elle est définie.Lesvariables locales ont uneclassed’allocationautomatique,cequisignifiequeleursemplacementssontallouésàl’entréedanslafonction,etlibérésàlasortie.Ilestpossiblededéclarerdesvariableslocalesàunbloc.
On peut demander qu’une variable locale soit de classe d’allocation statique, en ladéclarantàl’aidedumot-cléstatic,commedans:
staticinti;
Les variables de classe statique sont, par défaut, initialisées à zéro. On peut lesinitialiser explicitement à l’aide d’expressions constantes d’un type compatible paraffectation avec celui de la variable. Celles de classe automatique ne sont pasinitialisées par défaut. Elles doivent être initialisées à l’aide d’une expressionquelconque,d’untypecompatibleparaffectationavecceluidelavariable.
SurdéfinitiondefonctionsEn C++, il est possible, au sein d’un même programme, que plusieurs fonctionspossèdent lemêmenom.Danscecas, lorsque lecompilateurrencontre l’appeld’unetellefonction,ileffectuelechoixdela«bonne»fonctionentenantcomptedelanaturedesargumentseffectifs.
D’unemanière générale, si les règles utilisées par le compilateur pour sa recherchesontassezintuitives,leurénoncéprécisestassezcomplexe,etnousnelerappelleronspasici.Ontrouveradenombreuxexemplesdesurdéfinitionetunrécapitulatifcompletdes règles dans nos ouvrages consacrés au C++ (publiés également aux éditionsEyrolles).Signalonssimplementque larecherched’unefonctionsurdéfiniepeutfaireintervenir toutes les conversions usuelles (promotions numériques et conversions
55
standards,cesdernièrespouvantêtredégradantes),ainsiquelesconversionsdéfiniespar l’utilisateur en cas d’argument de type classe, à condition qu’aucune ambiguïtén’apparaisse.
LesfonctionsenligneUne fonction en ligne (on dit aussi « développée ») est une fonction dont lesinstructionssontincorporéesparlecompilateur(danslemoduleobjetcorrespondant)àchaqueappel.Celaévitelapertedetempsnécessaireàunappelusuel(changementdecontexte, copie des valeurs des arguments sur la « pile »...) ; en revanche, lesinstructionsenquestionsontgénéréesplusieursfois.
Une fonctionen ligneestnécessairementdéfinieenmême tempsqu’elleestdéclarée(elle ne peut plus être compilée séparément) et son en-tête est précédée dumot-cléinline,commedans:
inlinefct(...){.....}
56
Exercice24
ÉnoncéQuelle modification faut-il apporter au programme suivant pour qu’il deviennecorrect:
#include<iostream>
usingnamespacestd;
main()
{intn,p=5;
n=fct(p);
cout<<"p="<<p<<"n="<<n;
}
intfct(intr)
{return2*r;
}
La fonction fct est utilisée dans la fonction main, sans être encore « connue » ducompilateur,cequiprovoqueuneerreurdecompilation.Pouryrémédier,ondisposededeuxpossibilités:
déclarerlafonctionavantsonutilisationdansmain,depréférenceaudébut:#include<iostream>
usingnamespacestd;
main()
{intfct(int);//déclarationdefct;onpourraitécrireintfct
(intx)
intn,p=5;
n=fct(p);
cout<<"p="<<p<<"n="<<n;
}
intfct(intr)
{return2*r;
}
placerladéfinitiondelafonctionavantcelledelafonctionmain:#include<iostream>
usingnamespacestd;
intfct(intr)
{return2*r;
}
main()
{intn,p=5;
n=fct(p);
cout<<"p="<<p<<"n="<<n;
}
Ilestconseilléd’utiliserlapremièredémarchequialeméritedepermettredemodifierlesemplacementsdesdéfinitionsdefonctionsdansunfichiersourceou,même,de le
57
scinderenplusieursparties(compilationséparée).
58
Exercice25
ÉnoncéÉcrire:
•unefonction,nomméef1,secontentantd’afficher«bonjour»(elleneposséderaaucunargument,nivaleurderetour);
• une fonction, nommée f2, qui affiche « bonjour » un nombre de fois égal à lavaleurreçueenargument(int)etquinerenvoieaucunevaleur;
• une fonction, nommée f3, qui fait la même chose que f2, mais qui, de plus,renvoielavaleur(int)0.
Écrire un petit programme appelant successivement chacune de ces 3 fonctions,après les avoir convenablement déclarées (on ne fera aucune hypothèse sur lesemplacementsrelatifsdesdifférentesfonctionscomposantlefichiersource).
L’énoncéneprécisant rien, nousutiliseronsune transmissiond’argumentsparvaleur.Comme on n’impose pas d’ordre aux définitions des différentes fonctions dans lefichiersource,ondéclarerasystématiquementtouteslesfonctionsutilisées.
#include<iostream>
usingnamespacestd;
voidf1(void)
{cout<<"bonjour\n";
}
voidf2(intn)
{inti;
for(i=0;i<n;i++)
cout<<"bonjour\n";
}
intf3(intn)
{inti;
for(i=0;i<n;i++)
cout<<"bonjour\n";
return0;
}
main()
{voidf1(void);
voidf2(int);
intf3(int);
f1();
f2(3);
f3(3);
}
59
Exercice26
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
intn=10,q=2;
main()
{
intfct(int);
voidf(void);
intn=0,p=5;
n=fct(p);
cout<<"A:dansmain,n="<<n<<"p="<<p
<<"q="<<q<<"\n";
f();
}
intfct(intp)
{
intq;
q=2*p+n;
cout<<"B:dansfct,n="<<n<<"p="<<p
<<"q="<<q<<"\n";
returnq;
}
voidf(void)
{
intp=q*n;
cout<<"C:dansf,n="<<n<<"p="<<p
<<"q="<<q<<"\n";
}
B:dansfct,n=10,p=5,q=20
A:dansmain,n=20,p=5,q=2
C:dansf,n=10,p=20,q=2
60
Exercice27
ÉnoncéÉcrireunefonctionquireçoitenarguments2nombresflottantsetuncaractère,etquifournit un résultat correspondant à l’une des 4 opérations appliquées à ses deuxpremiersarguments,enfonctiondelavaleurdudernier,àsavoir:additionpourlecaractère+, soustractionpour-,multiplicationpour * etdivisionpour / (tout autrecaractèrequel’undes4citésserainterprétécommeuneaddition).Onnetiendrapascomptedesrisquesdedivisionparzéro.
Écrire un petit programme (main) utilisant cette fonction pour effectuer les 4opérationssurles2nombresfournisendonnée.
#include<iostream>
usingnamespacestd;
floatoper(floatv1,floatv2,charop)
{floatres;
switch(op)
{case'+':res=v1+v2;
break;
case'-':res=v1-v2;
break;
case'*':res=v1*v2;
break;
case'/':res=v1/v2;
break;
default:res=v1+v2;
}
returnres;
}
main()
{floatoper(float,float,char);//déclarationdeoper
floatx,y;
cout<<"donnezdeuxnombresréels:";
cin>>x>>y;
cout<<"leursommeest:"<<oper(x,y,'+')<<"\n";
cout<<"leurdifférenceest:"<<oper(x,y,'-')<<"\n";
cout<<"leurproduitest:"<<oper(x,y,'*')<<"\n";
cout<<"leurquotientest:"<<oper(x,y,'/')<<"\n";
}
61
Exercice28
ÉnoncéTransformer le programme (fonction + main) écrit dans l’exercice précédent demanièrequelafonctionnedisposeplusquede2arguments,lecaractèreindiquantlanature de l’opération à effectuer étant précisé, cette fois, à l’aide d’une variableglobale.
#include<iostream>
usingnamespacestd;
charop;//variableglobalepourlanaturedel'opération
//attention:doitêtredéclaréeavantd'êtreutilisée
floatoper(floatv1,floatv2)
{floatres;
switch(op)
{case'+':res=v1+v2;
break;
case'-':res=v1-v2;
break;
case'*':res=v1*v2;
break;
case'/':res=v1/v2;
break;
default:res=v1+v2;
}
returnres;
}
main()
{floatoper(float,float);/*prototypedeoper*/
floatx,y;
cout<<"donnezdeuxnombresréels:";
cin>>x>>y;
op='+';
cout<<"leursommeest:"<<oper(x,y)<<"\n";
op='-';
cout<<"leurdifférenceest:"<<oper(x,y)<<"\n";
op='*';
cout<<"leurproduitest:"<<oper(x,y)<<"\n";
op='/';
cout<<"leurquotientest:"<<oper(x,y)<<"\n";
}
Ils’agissait icid’unexerciced’«école»destinéàforcerl’utilisationd’unevariableglobale.Dans lapratique, on évitera autantquepossible cegenredeprogrammation
62
quifavorisetroplesrisquesd’«effetsdebord».
63
Exercice29
ÉnoncéÉcrireunefonction,sansargumentnivaleurderetour,quisecontented’afficher,àchaqueappel,lenombretotaldefoisoùelleaétéappeléesouslaforme:appelnuméro3
La meilleure solution consiste à prévoir, au sein de la fonction en question, unevariabledeclassestatique.Elleserainitialiséeuneseulefoisàzéro(ouàtouteautrevaleur éventuellement explicitée) au début de l’exécution du programme. Ici, nousavons,deplus,prévuunpetitprogrammed’essai.
#include<iostream>
usingnamespacestd;
voidfcompte(void)
{
staticinti;//ilestinutile,maispasinterdit,d'écrirei=0
i++;
cout<<"appelnuméro"<<i<<"\n";
}
/*petitprogrammed'essaidefcompte*/
main()
{voidfcompte(void);
inti;
for(i=0;i<3;i++)fcompte();
}
Là encore, la démarche consistant à utiliser comme compteur d’appels une variableglobale(quidevraitalorsêtreconnueduprogrammeutilisateur)estàproscrire.
64
Exercice30
ÉnoncéÉcrire2fonctionsàunargumententieretunevaleurderetourentièrepermettantdeprécisersil’argumentreçuestmultiplede2(pourlapremièrefonction)oumultiplede3(pourlasecondefonction).
Utilisercesdeuxfonctionsdansunpetitprogrammequi litunnombreentieretquiprécises’ilestpair,multiplede3et/oumultiplede6,commedanscetexemple(ilyadeuxexécutions):
donnezunentier:9
ilestmultiplede3
---------------
donnezunentier:12
ilestpair
ilestmultiplede3
ilestdivisiblepar6
#include<iostream>
usingnamespacestd;
intmul2(intn)
{if(n%2)return0;
elsereturn1;
}
intmul3(intn)
{if(n%3)return0;
elsereturn1;
}
main()
{intmul2(int);
intmul3(int);
intn;
cout<<"donnezunentier:";
cin>>n;
if(mul2(n))cout<<"ilestpair\n";
if(mul3(n))cout<<"ilestmultiplede3\n";
if(mul2(n)&&mul3(n))cout<<"ilestdivisiblepar6\n";
}
65
Exercice31
ÉnoncéÉcrire une fonction permettant d’ajouter une valeur fournie en argument à unevariablefournieégalementenargument.Parexemple,l’appel(netpétantentiers):
ajouter(2*p+1,n);
ajouteralavaleurdel’expression2*p+1àlavariablen.Écrireunpetitprogrammedetestdelafonction.
Étantdonnéque la fonctiondoit être enmesuredemodifier lavaleurde son secondargument,ilestnécessairequecederniersoittransmisparréférence.
#include<iostream>
usingnamespacestd;
voidajoute(intexp,int&var)
{var+=exp;
return;
}
main()
{voidajoute(int,int&);
intn=12;
intp=3;
cout<<"Avant,n="<<n<<"\n";
ajoute(2*p+1,n);
cout<<"Après,n="<<n<<"\n";
}
66
Exercice32
ÉnoncéSoientlesdéclarationssuivantes:
intfct(int);//fonctionI
intfct(float);//fonctionII
voidfct(int,float);//fonctionIII
voidfct(float,int);//fonctionIV
intn,p;
floatx,y;
charc;
doublez;
Les appels suivants sont-ils corrects et, si oui, quelles seront les fonctionseffectivementappeléesetlesconversionséventuellementmisesenplace?
a.fct(n);
b.fct(x);
c.fct(n,x);
d.fct(x,n);
e.fct(c);
f.fct(n,p);
g.fct(n,c);
h.fct(n,z);
i.fct(z,z);
Lescasa,b,cetdneposentaucunproblème.IlyarespectivementappeldesfonctionsI,II,IIIetIV,sansqu’aucuneconversiond’argumentnesoitnécessaire.
e.AppeldelafonctionI,aprèsconversiondelavaleurdecenint.
f.Appelincorrect,comptetenudesonambiguïté;deuxpossibilitésexistenteneffet:conservern,convertirpenfloatetappelerlafonctionIIIou,aucontraire,convertirnenfloat,conserverpetappelerlafonctionIV.
g.AppeldelafonctionIII,aprèsconversiondecenfloat.
67
h.AppeldelafonctionIII,aprèsconversion(dégradante)dezenfloat.
i.Appelincorrect,comptetenudesonambiguïté;deuxpossibilitésexistenteneffet:convertirlepremierargumentenfloatet lesecondenintetappeler lafonctionIIIou,aucontraire,convertirlepremierargumentenintetlesecondenfloatetappelerlafonctionIV.
68
Exercice33
Énoncéa.Transformerleprogrammesuivantpourquelafonctionfctdevienneunefonctionenligne.
#include<iostream>
usingnamespacestd;
main()
{intfct(char,int);//déclaration(prototype)defct
intn=150,p;
charc='s';
p=fct(c,n);
cout<<"fct(\'"<<c<<"\',"<<n<<")vaut:"<<p;
}
intfct(charc,intn)//définitiondefct
{intres;
if(c=='a')res=n+c;
elseif(c=='s')res=n-c;
elseres=n*c;
returnres;
}
b.Commentfaudrait-ilprocédersil’onsouhaitaitquelafonctionfct soitcompiléeséparément?
a. Nous devons donc d’abord déclarer (et définir en même temps) la fonction fctcommeunefonctionenligne.Leprogrammemains’écritdelamêmemanière,sicen’est que la déclaration de fct n’y est plus nécessaire puisqu’elle apparaîtauparavant (il reste permis de la déclarer, à condition de ne pas utiliser lequalificatifinline).#include<iostream>
usingnamespacestd;
inlineintfct(charc,intn)
{intres;
if(c=='a')res=n+c;
elseif(c=='s')res=n-c;
elseres=n*c;
returnres;
}
main()
{intn=150,p;
charc='s';
p=fct(c,n);
cout<<"fct(\'"<<c<<"\',"<<n<<")vaut:"<<p;
}
b.Ils’agitenfaitd’unequestionpiège.Eneffet,lafonctionfctétantenligne,ellenepeutplusêtrecompiléeséparément.Ilestcependantpossibledelaconserverdans
69
un fichier d’extension h et d’incorporer simplement ce fichier par #include pourcompilerlemain.Cettedémarcheserencontrerad’ailleursfréquemmentdanslecasdeclassescomportantdesfonctionsenligne.Alors,dansunfichierd’extensionh,ontrouvera la déclaration de la classe en question, à l’intérieur de laquelleapparaîtrontles«déclarations-définitions»desfonctionsenligne.
70
Chapitre4Lestableaux,lespointeursetleschaînes
destyleC
71
Rappels
TableauàunindiceUn tableau à un indice est un ensemble d’éléments de même type désignés par unidentificateur unique. Chaque élément est repéré par un indice précisant sa positiondansl’ensemble(lepremierélémentestrepéréparl’indice0).
L’instructionsuivante:floatt[10];
réservel’emplacementpouruntableaude10élémentsdetypefloat,nommét.
Un élément de tableau est une lvalue.Un indice peut prendre la forme de n’importequelleexpressionarithmétiqued’untypeentierquelconque.Lenombred’élémentsd’untableau(dimension)doitêtreindiquésousformed’uneexpressionconstante.
TableauxàplusieursindicesLadéclaration:
intt[5][3];
réserve un tableau de 15 (5 × 3) éléments.Un élément quelconque de ce tableau setrouvealorsrepérépardeuxindicescommedanscesnotations:
t[3][2]t[i][j]
D’unemanièregénérale,onpeutdéfinirdestableauxcomportantunnombrequelconqued’indices.
Lesélémentsd’untableausontrangésenmémoireselonl’ordreobtenuenfaisantvarierledernierindiceenpremier.
InitialisationdestableauxLes tableaux de classe statique sont, par défaut, initialisés à zéro. Les tableaux declasseautomatiquenesontpasinitialiséspardéfaut.
On peut initialiser (partiellement ou totalement) un tableau lors de sa déclaration,commedanscesexemples:
72
intt1[5]={10,20,5,0,3};
//placelesvaleurs10,20,5,0et3danslescinqélémentsdet1
intt2[5]={10,20};
//placelesvaleurs10et20danslesdeuxpremiersélémentsdet2
Lesdeuxdéclarationssuivantessontéquivalentes:inttab[3][4]={{1,2,3,4},
{5,6,7,8},
{9,10,11,12}}
inttab[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
Lesinitialiseursfournispourl’initialisationdesélémentsd’untableaudoiventêtredesexpressions constantes pour les tableaux de classe statique, et des expressionsquelconques (d’un type compatible par affectation) pour les tableaux de classeautomatique.
Lespointeurs,lesopérateurs&et*Unevariablepointeurestdestinéeàmanipulerdesadressesd’informationsd’un typedonné.Onladéclarecommedanscesexemples:
int*adi;//adicontiendradesadressesd’entiersdetypeint
float*adf;//adfcontiendradesadressesdeflottantsdetypefloat
L’opérateur&permetd’obtenirl’adressed’unevariable:intn;
.....
adi=&n;//adicontientl’adresseden
L’opérateur* permet d’obtenir l’information d’adresse donnée.Ainsi *adi désigne lalvalued’adresseadi,c’est-à-direicin:
intp=*adi;//placedansplavaleurden
*adi=40;//placelavaleur40àl’adressecontenuedansadi,
//doncicidansn
Ilexisteuntype«pointeurgénérique»,c’est-à-direpourlequelonneprécisepaslanaturedesinformationspointées,àsavoirletypevoid*.
OpérationssurlespointeursOnpeutincrémenteroudécrémenterdespointeursd’unevaleurdonnée.Parexemple:
adi++;//modifieadidemanièrequ’ellepointe
//surl’entiersuivantn
adi-=10;//modifieadidemanièrequ’ellepointe
//10entiersavant
L’unitéd’incrémentationoudedécrémentationdespointeursgénériquesestl’octet.
Onpeutcomparerousoustrairedespointeursdemêmetype(pointantsurdeséléments
73
demêmetype).
AffectationsdepointeursetconversionsIln’existeaucuneconversionimplicited’untypepointeurenunautre,àl’exceptiondelaconversionenpointeurgénérique.
Iln’existeaucuneconversionimplicited’unentierenunpointeur,à l’exceptiondelavaleur0quiestalorsconvertieenunpointeurne«pointantsurrien».
TableauxetpointeursUnnomdetableauestuneconstantepointeur.Avec:
intt[10];
t+1estéquivalentà&t[1];
t+iestéquivalentà&t[i];
ti]estéquivalentà*(t+i).
Avec:intt[3][4];
testéquivalentà&t[0][0]ouàt[0];
t+2estéquivalentà&t[2][0]ouàt[2]
Lorsqu’unnomdetableauestutiliséenargumenteffectif,c’est(lavaleurde)l’adressequiesttransmiseàlafonction.Lanotiondetransmissionparréférencen’apasdesensdans ce cas. Dans la déclaration d’une fonction disposant d’un argument de typetableau, on peut utiliser indifféremment le formalisme « tableau » (avec ou sans ladimensioneffectivedutableau)ouleformalismepointeur;cestroisdéclarationssontéquivalentes:
voidfct(intt[10])
voidfct(intt[])
voidfct(int*t)
GestiondynamiquedelamémoireSitypereprésenteladescriptiond’untypeabsolumentquelconqueetsinreprésenteuneexpressiond’untypeentier(généralementlongouunsignedlong),l’expression:
newtype[n]
74
allouel’emplacementnécessairepournélémentsdutypeindiquéetfournitenrésultatunpointeur(detypetype*)surlepremierélément.Encasd’échecd’allocation,ilyadéclenchement d’une exception bad_alloc (les exceptions font l’objet du chapitre 19).L’indication n est facultative : avec new type, on obtient un emplacement pour unélémentdutypeindiqué,commesil’onavaitécritnewtype[1].
L’expression:deleteadresse//ilexisteuneautresyntaxepourles
//tableauxd’objets-voirchap.9
libèreunemplacementpréalablementallouéparnewà l’adresse indiquée. Iln’estpasnécessairederépéterlenombred’éléments,dumoinslorsqu’ilnes’agitpasd’objets,mêmesicelui-ciestdifférentde1.Lecasdestableauxd’objetsestexaminéauchapitre9.
PointeurssurdesfonctionsUn pointeur sur une fonction est défini par le type des arguments et de la valeur deretourdelafonction,commedans:
int(*adf)(double,int);//adfestunpointeursurunefonction
//recevantundoubleetunint
//etrenvoyantunint
Unnomdefonction,employéseul,esttraduitparlecompilateurenl’adressedecettefonction.
ChaînesdestyleCUne chaîne de caractère de style C est une suite d’octets (représentant chacun uncaractère),terminéeparunoctetdecodenul.Leschaînesconstantessontreprésentéesseloncetteconvention.Leslecturesopéréessurleflotcin,ainsiquelesécrituressurleflotcout,utilisentcetteconventionlorsqu’ellesportentsurdeslvaluedetypetableaudecaractèresoupointeursurdescaractères(typechar*).
Un tableau de caractères peut être initialisé par une chaîne constante. Ces deuxinstructionssontéquivalentes:
charch[20]="bonjour";
charch[20]={’b’,’o’,’n’,’j’,’o’,’u’,’r’,’\0’);
ArgumentsdelalignedecommandeLors du lancement d’un programme, la plupart des environnements permettent de lui
75
fournirdes«paramètres».Ceux-cisontalorssimplementtransmisàlafonctionmain,sousformedechaînedestyleC,commedesargumentseffectifsd’appeld’unefonction.Ilssont,enoutre,précédésdunombretotald’arguments(int)etdunomduprogramme(chaîne de styleC). Pour pouvoir les exploiter, il est alors nécessaire d’utiliser unedéclarationdemaintelleque:
main(intnbarg,char*argv[])
76
Exercice34
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<stdio.h>
#include<iostream>
usingnamespacestd;
main()
{
intt[3];
inti,j;
int*adt;
for(i=0,j=0;i<3;i++)t[i]=j+++i;/*1*/
for(i=0;i<3;i++)cout<<t[i]<<"";/*2*/
cout<<"\n";
for(i=0;i<3;i++)cout<<*(t+i)<<"";/*3*/
printf("\n");
for(adt=t;adt<t+3;adt++)cout<<*adt<<"";/*4*/
cout<<"\n";
for(adt=t+2;adt>=t;adt--)cout<<*adt<<"";/*5*/
cout<<"\n";
}
/*1*/remplitletableauaveclesvaleurs0(0+0),2(1+1)et4(2+2);onobtiendraitplussimplementlemêmerésultatavecl’expression2*i.
/*2*/afficheclassiquementlesvaleursdutableaut,dansl’ordrenaturel.
/*3*/ fait lamêmechose, enutilisant le formalismepointeur au lieudu formalismetableau.Ainsi,*(t+i)estparfaitementéquivalentàt[i].
/*4*/faitlamêmechose,enutilisantlalvalueadt(àlaquelleonaaffectéinitialementl’adressetdutableau)etenl’incrémentantpourparcourirlesdifférentesadressesdes4élémentsdutableau.
/*5*/affichelesvaleursdet,àl’envers,enutilisantlemêmeformalismepointeurquedans4.Onauraitpuécrire,defaçonéquivalente:
for(i=2;i>=0;i--)cout<<t[i]<<"";
Voicilesrésultatsfournisparceprogramme:024
77
024
024
420
78
Exercice35
ÉnoncéÉcrire,dedeuxfaçonsdifférentes,unprogrammequilit10nombresentiersdansuntableauavantd’enrechercherleplusgrandetlepluspetit:a.enutilisantuniquementle«formalismetableau»;b.enutilisantle«formalismepointeur»,àchaquefoisquecelaestpossible.
a.La programmation est, ici, classique.Nous avons simplement défini une constanteNVAL destinée à contenir le nombre de valeurs du tableau. Notez bien que ladéclarationintt[NVAL]estacceptéepuisqueNVALestune«expressionconstante».#include<iostream>
usingnamespacestd;
main()
{constintNVAL=10;/*nombredevaleursdutableau*/
inti,min,max;
intt[NVAL];
cout<<"donnez"<<NVAL<<"valeurs\n";
for(i=0;i<NVAL;i++)cin>>t[i];
max=min=t[0];
for(i=1;i<NVAL;i++)
{if(t[i]>max)max=t[i];/*oumax=t[i]>max?t[i]:max*/
if(t[i]<min)min=t[i];/*oumin=t[i]<min?t[i]:min*/
}
cout<<"valeurmax:"<<max<<"\n";
cout<<"valeurmin:"<<min<<"\n";
}
b.Onpeutremplacersystématiquementt[i]par*(t+i).Voicifinalementleprogrammeobtenu:#include<iostream>
usingnamespacestd;
main()
{constintNVAL=10;/*nombredevaleursdutableau*/
inti,min,max;
intt[NVAL];
cout<<"donnez"<<NVAL<<"valeurs\n";
for(i=0;i<NVAL;i++)cin>>*(t+i);
max=min=*t;
for(i=1;i<NVAL;i++)
{if(*(t+i)>max)max=*(t+i);
if(*(t+i)<min)min=*(t+i);
79
}
cout<<"valeurmax:"<<max<<"\n";
cout<<"valeurmin:"<<min<<"\n";
}
80
Exercice36
ÉnoncéSoientdeuxtableauxt1ett2déclarésainsi:
floatt1[10],t2[10];
Écrirelesinstructionspermettantderecopier,danst1, touslesélémentspositifsdet2,encomplétantéventuellementt1pardeszéros.Ici,onnechercherapasàfournirunprogrammecompletetonutiliserasystématiquementleformalismetableau.
Onpeutcommencerparremplirt1dezéros,avantd’yrecopierlesélémentspositifsdet2:
inti,j;
for(i=0;i<10;i++)t1[i]=0;
/*isertàpointerdanst1etjdanst2*/
for(i=0,j=0;j<10;j++)
if(t2[j]>0)t1[i++]=t2[j];
Maisonpeutrecopierd’aborddanst1lesélémentspositifsdet2,avantdecompléteréventuellement par des zéros. Cette seconde formulation, moins simple que laprécédente,serévéleraittoutefoisplusefficacesurdegrandstableaux:
inti,j;
for(i=0,j=0;j<10;j++)
if(t2[j]>0)t1[i++]=t2[j];
for(j=i;j<10;j++)t1[j]=0;
81
Exercice37
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
main()
{intt[4]={10,20,30,40};
int*ad[4];
inti;
for(i=0;i<4;i++)ad[i]=t+i;/*1*/
for(i=0;i<4;i++)cout<<*ad[i]<<"";/*2*/
cout<<"\n";
cout<<*(ad[1]+1)<<""<<*ad[1]+1<<"\n";/*3*/
}
Letableauadestuntableaude4éléments;chacundecesélémentsestunpointeursurunint.L’instruction/*1*/ remplit le tableauad avec lesadressesdes4élémentsdutableaut.L’instruction/*2*/affichefinalementles4élémentsdutableaut;eneffet,*ad[i]représentelavaleursituéeàl’adressead[i]./*2*/estéquivalenteicià:
for(i=0;i<4;i++)cout<<t[i]<<"";
Enfin,dansl’instruction/*3*/,*(ad[1]+1)représentelavaleursituéeàl’entiersuivantceluid’adressead[1];ils’agitdoncdet[2].Enrevanche,*ad[1]+1représentelavaleursituéeàl’adressead[1]augmentéede1,autrementditt[1]+1.
Voici,endéfinitive,lesrésultatsfournisparceprogramme:10203040
3021
82
Exercice38
ÉnoncéSoitletableautdéclaréainsi:
floatt[3][4];
Écrireles(seules)instructionspermettantdecalculer,dansunevariablenomméesom,lasommedesélémentsdet:
a.enutilisantle«formalismeusueldestableauxàdeuxindices»;b.enutilisantle«formalismepointeur».
a.Lapremièresolutionneposeaucunproblèmeparticulier:inti,j;
som=0;
for(i=0;i<3;i++)
for(j=0;j<4;j++)
som+=t[i][j];
b.Leformalismepointeuresticimoinsfacileàappliquerquedanslecasdestableauxàunindice.Eneffet,avec,parexemple,floatt[4],testdetypeint*etilcorrespondà un pointeur sur le premier élément du tableau. Il suffit donc d’incrémenterconvenablementtpourparcourirtouslesélémentsdutableau.
En revanche, avec notre tableau float t [3] [4], t est du type pointeur sur destableaux de 4 flottants (type : * float[4]). La notation *(t+i) est généralementinutilisablesouscetteformepuisque,d’unepart,ellecorrespondàdesvaleursdetableaux de 4 flottants et que, d’autre part, l’incrément i porte, non plus sur desflottants,maissurdesblocsde4flottants;parexemple,t+2représentel’adresseduhuitièmeflottant,comptéàpartirdeceluid’adresset.
Unesolutionconsisteà«convertir»lavaleurdetenunpointeurdetypefloat*.Onpourraitsecontenterdeprocéderainsi:float*adt;
.....
adt=t;
Eneffet,danscecas,l’affectationentraîneuneconversionforcéedetenfloat*,cequinechangepasl’adressecorrespondante(seulelanaturedupointeurachangé).
83
Celan’estvraiqueparcequel’onpassedepointeurssurdesgroupesd’élémentsàunpointeursurceséléments.Autrementdit,aucunecontrainted’alignementnerisquedenuireici.Iln’eniraitpasdemême,parexemple,pourdesconversionsdechar*enint*.
Généralement,onygagneraenlisibilitéenexplicitantlaconversionmiseenœuvreà l’aide de l’opérateur de cast. Notez que, par ailleurs, cela peut éviter certainsmessagesd’avertissement(warnings)delapartducompilateur.
Voicifinalementcequepourraientêtrelesinstructionsdemandées:inti;
int*adt;
som=0;
adt=(float*)t;
for(i=0;i<12;i++)
som+=*(adt+i);
84
Exercice39
ÉnoncéÉcrire une fonction qui fournit en valeur de retour la somme des éléments d’untableaudeflottantstransmis,ainsiquesadimension,enargument.
Écrireunpetitprogrammed’essai.
En C++, par défaut, les arguments sont transmis par valeur.Mais, dans le cas d’untableau, cettevaleur, de typepointeur, n’est riend’autreque sonadresse.Quant à latransmission par référence, elle n’a pas de signification dans ce cas. Nous n’avonsdoncaucunchoixencequiconcernelemodedetransmissiondenotretableau.
En ce qui concerne le nombre d’éléments (de type int), nous le transmettronsclassiquementparvaleur.L’en-têtedenotrefonctionpourraseprésentersousl’unedesformessuivantes:
floatsomme(floatt[],intn)
floatsomme(float*t,intn)
floatsomme(floatt[5],intn)//déconseillécarlaissecroirequet
//estdedimensionfixe5
En effet, la dimension réelle de t n’a aucune incidence sur les instructions de lafonctionelle-même(ellen’intervientpasdans lecalculde l’adressed’unélémentdutableau1etellenesertpasà«allouer»unemplacementpuisqueletableauenquestionauraétéallouédanslafonctionappelantsomme).
Voicicequepourraitêtrelafonctiondemandée:floatsomme(floatt[],intn)//onpeutécriresomme(float*t,...
//ouencoresomme(floatt[4],...
//maispassomme(floatt[n],...
{inti;
floats=0;
for(i=0;i<n;i++)
s+=t[i];//onpourraitécrires+=*(t+i);
returns;
}
Pour ce qui est du programme d’utilisation de la fonction somme, on peut, là encore,écrirele«prototype»sousdifférentesformes:
floatsomme(float[],int);
floatsomme(float*,int);
floatsomme(float[5],int);//déconseillécarlaissecroirequet
//estdedimensionfixe5
85
Voiciunexempled’untelprogramme:#include<iostream>
usingnamespacestd;
main()
{
floatsomme(float*,int);
floatt[4]={3,2.5,5.1,3.5};
cout<<"sommedet:"<<somme(t,4);
}
86
Exercice40
ÉnoncéÉcrireunefonctionquinerenvoieaucunevaleuretquidéterminelavaleurmaximaleet lavaleurminimaled’un tableaud’entiers(àun indice)de taillequelconque.Onprévoira4 arguments : le tableau, sadimension, lemaximumet leminimum.Pourchacun d’entre eux, on choisira le mode de transmission le plus approprié (parvaleurouparréférence).Danslecasoùlatransmissionparréférenceestnécessaire,proposer deux solutions : l’une utilisant effectivement cette notion de référence,l’autrela«simulant»àl’aidedepointeurs.
Écrireunpetitprogrammed’essai.
En C++, par défaut, les arguments sont transmis par valeur.Mais, dans le cas d’untableau,cettevaleur,detypepointeur,n’estriend’autrequel’adressedutableau.Quantàlatransmissionparréférence,ellen’apasdesignificationdanscecas.Nousn’avonsdoncaucunchoixconcernantlemodedetransmissiondenotretableau.
En ce qui concerne le nombre d’éléments du tableau, on peut indifféremment entransmettre l’adresse (sous forme d’un pointeur de type int *), ou la valeur ; ici, laseconde solution est la plus appropriée, puisque la fonction n’a pas besoin d’enmodifierlavaleur.
En revanche, en cequi concerne lemaximumet leminimum, ils nepeuventpas êtretransmisparvaleur,puisqu’ilsdoiventprécisémentêtredéterminéspar lafonction.Ilfautdoncobligatoirementprévoirdepasser:
soitdesréférences.L’en-têtedenotrefonctionseprésenteraainsi:voidmaxmin(intt[],intn,int&admax,int&admin)
soitdespointeurssurdesfloat.L’en-têtedenotrefonctionseprésenteraainsi:voidmaxmin(intt[],intn,int*admax,int*admin)
L’algorithme de recherche demaximumet deminimumpeut être calqué sur celui del’exercice39,enremplaçantmaxpar*admaxetminpar*admin.Voicicequepourraitêtrenotrefonction:
avectransmissionparréférence:
87
voidmaxmin(intt[],intn,int&admax,int&admin)
{inti;
admax=t[1];
admin=t[1];
for(i=1;i<n;i++)
{if(t[i]>admax)admax=t[i];
if(t[i]<admin)admin=t[i];
}
}
avectransmissionparpointeursvoidmaxmin(intt[],intn,int*admax,int*admin)
{inti;
*admax=t[1];
*admin=t[1];
for(i=1;i<n;i++)
{if(t[i]>*admax)*admax=t[i];
if(t[i]<*admin)*admin=t[i];
}
}
Ici,sil’onsouhaiteéviterles«indirections»quiapparaissentsystématiquementdansles instructions de comparaison, on peut travailler temporairement sur des variableslocalesà la fonction(nommées icimaxetmin).Celanousconduitàune fonctionde laformesuivante:
voidmaxmin(intt[],intn,int*admax,int*admin)
{inti,max,min;
max=t[1];
min=t[1];
for(i=1;i<n;i++)
{if(t[i]>max)max=t[i];
if(t[i]<min)min=t[i];
}
*admax=max;
*admin=min;
}
Voiciunpetitexempledeprogrammed’utilisationdelapremièrefonction:#include<iostream>
usingnamespacestd;
main()
{voidmaxmin(int[],int,int&,int&);
intt[8]={2,5,7,2,9,3,9,4};
intmax,min;
maxmin(t,8,max,min);
cout<<"valeurmaxi:"<<max<<"\n";
cout<<"valeurmini:"<<min<<"\n";
}
Etvoicilemêmeexempleutilisantlasecondefonction:#include<iostream>
usingnamespacestd;
main()
{voidmaxmin(int[],int,int*,int*);
intt[8]={2,5,7,2,9,3,9,4};
intmax,min;
maxmin(t,8,&max,&min);
cout<<"valeurmaxi:"<<max<<"\n";
cout<<"valeurmini:"<<min<<"\n";
88
}
89
Exercice41
ÉnoncéÉcrire une fonction qui fournit en retour la somme des valeurs d’un tableau deflottantsàdeuxindicesdontlesdimensionssontfourniesenargument.
Paranalogieaveccequenousavionsfaitdansl’exercice39,nouspourrionssongeràdéclarerletableauconcernédansl’en-têtedelafonctionsouslaformet[][].Maiscelan’estpluspossiblecar,cettefois,pourdéterminerl’adressed’unélémentt[i][j]d’unteltableau,lecompilateurdoitenconnaîtreladeuxièmedimension.
Unesolutionconsisteàconsidérerqu’onreçoitunpointeur(detypefloat*)surledébutdutableauetd’enparcourirtousleséléments(aunombreden*psinetpdésignentlesdimensionsdutableau)commesil’onavaitaffaireàuntableauàunedimension.
Celanousconduitàcettefonction:floatsomme(float*adt,intn,intp)
{inti;
floats;
for(i=0;i<n*p;i++)s+=adt[i];/*ous+=*(adt+i)*/
returns;
}
Pour utiliser une telle fonction, la seule difficulté consiste à lui transmettreeffectivementl’adressededébutdutableau,souslaformed’unpointeurdetypeint*.Or,avec,parexemplet[3][4],t, s’il correspondbienà labonneadresse,estdu type« pointeur sur des tableaux de 4 flottants ». A priori, toutefois, compte tenu de laprésenceduprototype,laconversionvoulueseramiseenœuvreautomatiquementparlecompilateur.Toutefois,commenousl’avonsdéjàditdansl’exercice38,ongagneraenlisibilité(etenéventuelsmessagesd’avertissement!)enfaisantappelàl’opérateurde«cast».
Voicifinalementunexempled’untelprogrammed’utilisationdenotrefonction:#include<iostream>
usingnamespacestd;
main()
{floatsomme(float*,int,int);
floatt[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
cout<<"somme:"<<somme((float*)t,3,4)<<"\n";
}
90
Exercice42
ÉnoncéÉcrire un programme allouant dynamiquement un emplacement pour un tableaud’entiers,dontlatailleestfournieendonnée.Utilisercetableaupouryplacerdesnombresentiersluségalementendonnée.Créerensuitedynamiquementunnouveautableau destiné à recevoir les carrés des nombres contenus dans le premier.Supprimerlepremiertableau,afficherlesvaleursdusecondetsupprimerletout.Onnechercherapasàtraiterunéventuelproblèmedemanquedemémoire.
Ilnousfaututiliserdeuxvariables (adt1etadt2)de typepointeur surdesentierspourconserver les adresses des emplacements alloués pour chacun des deux tableauxd’entiers.
#include<iostream>
usingnamespacestd;
main()
{intnval;//nombredevaleurs
int*adt1,*adt2;//attention,pasint*adt1,adt2
do{cout<<"combiendevaleurs:";
cin>>nval;
}
while(nval<=0);//onrefuselesvaleursnégatives
/*allocationpremiertableau,lecturevaleurs*/
adt1=newint[nval];
cout<<"donnez"<<nval<<"valeurs\n";
for(inti=0;i<nval;i++)cin>>adt1[i];//oucin*(adt1+i)
/*allocationsecondtableau,calculdescarrés*/
adt2=newint[nval];
for(inti=0;i<nval;i++)adt2[i]=adt1[i]*adt1[i];
/*suppressionpremiertableau,affichagevaleurs*/
deleteadt1;
cout<<"voicileurscarrés:\n";
for(inti=0;i<nval;i++)cout<<adt2[i]<<"";
/*suppressiondusecondtableau*/
deleteadt2;
}
Notez que, dans l’appel de l’opérateur delete, il n’est pas nécessaire de préciser lenombred’élémentsdutableaud’entiersàlibérer.Iln’eniraplusdemême,lorsquel’onauraaffaireàdestableauxd’objets.
Voiciunexempled’exécutiondeceprogramme:combiendevaleurs:0
combiendevaleurs:-2
combiendevaleurs:8
donnez8valeurs
91
125973086-2
voicileurscarrés:
142581499064
92
Exercice43
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
main()
{
char*ad1;
ad1="bonjour";
cout<<ad1<<"\n";
ad1="monsieur";
cout<<ad1;
}
L’instructionad1="bonjour"placedanslavariablead1 l’adressedelachaîneconstante"bonjour". L’instruction cout << ad1 se contente d’afficher la valeur de la chaîne dontl’adresse figure dans ad1, c’est-à-dire en l’occurrence "bonjour". De manièrecomparable, l’instruction ad1 = "monsieur" place l’adresse de la chaîne constante"monsieur" dans ad1 ; l’instruction cout << ad1 affiche la valeur de la chaîne ayantmaintenantl’adressecontenuedansad1,c’est-à-dire"monsieur".
Finalement,ceprogrammeaffichetoutsimplement:bonjour
monsieur
Onauraitobtenuplussimplementlemêmerésultatenécrivant:cout<<"bonjour\nmonsieur";
93
Exercice44
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
main()
{
char*adr="bonjour";/*1*/
inti;
for(i=0;i<3;i++)cout<<adr[i];/*2*/
cout<<"\n";
i=0;
while(adr[i])cout<<adr[i++];/*3*/
}
Ladéclaration/*1*/placedanslavariableadrl’adressedelachaîneconstantebonjour.L’instruction /* 2 */ affiche les caractères adr[0], adr[1] et adr[2], c’est-à-dire les 3premierscaractèresdecettechaîne.L’instruction/*3*/ affiche tous lescaractèresàpartirdeceluid’adresseadr,tantquel’onapasaffaireàuncaractèrenul;commenotrechaîne "bonjour" est précisément terminée par un tel caractère nul, cette instructionaffichefinalement,unparun,touslescaractèresde"bonjour".
Endéfinitive,leprogrammefournitsimplementlesrésultatssuivants:bon
bonjour
94
Exercice45
ÉnoncéÉcrireleprogrammeprécédent(exercice44),sansutiliserle«formalismetableau»(ilexisteplusieurssolutions).
Voicideuxsolutionspossibles:
a.Onpeutremplacersystématiquementlanotationadr[i]par*(adr+i),cequiconduitàceprogramme:#include<iostream>
usingnamespacestd;
main()
{
char*adr="bonjour";
inti;
for(i=0;i<3;i++)cout<<*(adr+i);
cout<<"\n";
i=0;
while(adr[i])cout<<*(adr+i++);
}
b.Onpeutégalementparcourirnotrechaîne,nonplusàl’aided’un«indice»i,maisenincrémentantunpointeurdetypechar*:ilpourraits’agirtoutsimplementdeadr,maisgénéralementonpréféreranepasdétruirecetteinformationetenemployerunecopie:#include<iostream>
usingnamespacestd;
main()
{
char*adr="bonjour";
char*adb;
for(adb=adr;adb<adr+3;adb++)cout<<*adb;
cout<<"\n";
adb=adr;
while(*adb)cout<<*(adb++);
}
Notez bien que si nous incrémentions directement adr dans la première instructiond’affichage, nous ne disposerions plus de la « bonne adresse » pour la seconceinstructiond’affichage.
95
Exercice46
ÉnoncéÉcrire un programme qui demande à l’utilisateur de lui fournir un nombre entierentre1et7etquiaffichelenomdujourdelasemaineayantlenuméroindiqué(lundipour1,mardipour2,...dimanchepour7).
Une démarche consiste à créer un « tableau de 7 pointeurs sur des chaînes »,correspondant chacune aunomd’un jour de la semaine.Commeces chaînes sont iciconstantes, il estpossibledecréerun tel tableauparunedéclarationcomportantuneinitialisationdelaforme:
char*jour[7]={"lundi","mardi",...
N’oubliezpasalorsquejour[0]contiendral’adressedelapremièrechaîne,c’est-à-direl’adressedelachaîneconstante"lundi";jour[1]contiendral’adressede"mardi"...
Pourafficherlavaleurdelachaînederangi,ilsuffitderemarquerquesonadresseestsimplementjour[i-1].
D’oùleprogrammedemandé:#include<iostream>
usingnamespacestd;
main()
{
char*jour[7]={"lundi","mardi","mercredi","jeudi",
"vendredi","samedi","dimanche"
};
inti;
do
{cout<<"donnezunnombreentierentre1et7:";
cin>>i;
}
while(i<=0||i>7);
cout<<"lejournuméro"<<i<<"delasemaineest"<<jour[i-1];
}
1.Iln’eniraitpasdemêmepourdestableauxàplusieursindices.
96
Chapitre5Lesstructures
97
Rappels
Déclarationd’untypestructureetdesvariablesdecetypeC++permetdedéclareruntypestructure,decettemanière:
structenreg{intnumero;
intqte;
floatprix;
};
Cette déclaration définit un type (modèle) de structure mais ne réserve pas devariablescorrespondantàcettestructure.Cetypes’appelleicienregetilpréciselenometletypedechacundeschampsconstituantlastructure(numero,qteetprix).
Une foisun tel typede structuredéfini,nouspouvonsdéclarerdesvariablesdu typecorrespondant.Parexemple,l’instruction:
enregart1,art2;
réserve deux emplacements nommés art1 et art2, de type enreg, destinés à contenirchacundeuxentiersetunflottant.
Les champs d’une structure peuvent être de n’importe quel type de base, d’un typetableau ou structure. De même, les éléments d’un tableau peuvent être d’un typestructure.
Utilisationd’une(variabledetype)structureUnchampd’unestructurepeutêtremanipulécommen’importequellevariabledutypecorrespondant. On désigne un champ donné en faisant suivre le nom de la variablestructuredel’opérateur«point»(.),suividunomdechamp,commedanscesexemplesutilisantlesdéclarationsprécédentes:
art1.numero//champnumerodelastructureart1
art2.prix//champprixdelastructureart2
Contrairement au tableau, une variable de type structure peut être affectée à unevariabledemêmetype(mêmenomdemodèle).
InitialisationdesstructuresLes structures de classe automatique ne sont pas initialisées par défaut. Celles declasse statique voient leurs champs initialisés à « zéro » (entier nul, flottant nul,
98
caractèredecodenul,pointeurnul).Entouterigueur,cetterègles’appliqueauxchampsquisontdesscalairesoudestableauxdescalaires.Sicertainschampssonteux-mêmesdesstructures,larègles’appliqueraàchacundeleurschamps,etainsidesuite.
Àl’instard’untableau,unestructurepeutêtreinitialiséelorsdesadéclaration,commedanscetteinstructionquiutiliseletypeenregdéfiniprécédemment:
enregart1={100,285,200};
Onpeutomettrecertainesvaleurs.
99
Exercice47
ÉnoncéSoitlemodèle(type)destructuresuivant:
structpoint
{charc;
intx,y;
};
Écrireunefonctionquireçoitenargumentunestructuredetypepointetquienaffichelecontenusouslaforme:
pointBdecoordonnées1012
a.entransmettantenargumentlavaleurdelastructureconcernée,
b.entransmettantenargumentl’adressedelastructureconcernée,
c.entransmettantlastructureconcernéeparréférence.
Danslestroiscas,onécriraunpetitprogrammed’essaidelafonctionainsiréalisée.
a.Voicilafonctiondemandée:voidaffiche(pointp)
{cout<<"point"<<p.c<<"decoordonnées"
<<p.x<<""<<p.y<<"\n";
}
Notezquesacompilationnécessiteobligatoirementladéclarationdutypepoint.
Voiciunpetitprogrammecompletquiaffectelesvaleurs'A',10et12auxdifférentschamps d’une structure nommée s, avant d’en afficher les valeurs à l’aide de lafonctionprécédente:#include<iostream>
usingnamespacestd;
structpoint
{charc;
intx,y;
};
voidaffiche(pointp)
{cout<<"point"<<p.c<<"decoordonnées"
<<p.x<<""<<p.y<<"\n";
}
main()
{voidaffiche(point);//déclarationdeaffiche
points;
s.c='A';
s.x=10;
s.y=12;
100
affiche(s);
}
b.Voiciladeuxièmefonctiondemandée,accompagnéedesonprogrammedetest:#include<iostream>
usingnamespacestd;
structpoint
{charc;
intx,y;
};
voidaffiche(point*adp)
{cout<<"point"<<adp->c<<"decoordonnées"
<<adp->x<<""<<adp->y;
}
main()
{voidaffiche(point*);
points;
s.c='A';
s.x=10;
s.y=12;
affiche(&s);
}
Notezquel’ondoit,cettefois,faireappelàl’opérateur->,àlaplacedel’opérateur«point»(.),puisque l’on travailleavecunpointeursurunestructure,etnonplusaveclavaleurdelastructureelle-même.Toutefoisl’usagede->n’estpastotalementindispensable,danslamesureoù,parexemple,adp->xestéquivalentà(*adp).x.
c.Voicilatroisièmefonctiondemandée,accompagnéedesonprogrammedetest:#include<iostream>
usingnamespacestd;
structpoint
{charc;
intx,y;
};
voidaffiche(point&p)
{cout<<"point"<<p.c<<"decoordonnées"
<<p.x<<""<<p.y;
}
main()
{voidaffiche(point&);
points;
s.c='A';
s.x=10;
s.y=12;
affiche(s);
}
Aulieud’affecterdesvaleursauxchampsc,xetydenotrestructures (dans les troisprogrammes d’essai), nous pourrions (ici) utiliser les possibilités d’initialisationoffertesparlelangageC,enécrivant:
points={'A',10,12};
101
Exercice48
ÉnoncéSoitletypestructureenregdéfiniainsi:
constintNMOIS=12;
structenreg
{intstock;
floatprix;
intventes[NMOIS]
}
Écrireunefonctionnomméerazqui«metàzéro» leschampsstocketventesd’unestructuredecetype,transmiseenargument.Lafonctionnecomporterapasdevaleurderetour.
Écrireunpetitprogrammed’essaiquiaffectetoutd’aborddesvaleursauxdifférentschampsd’unetellestructure,avantdeleurappliquerlafonctionraz.Onafficheralesvaleurs de la structure, avant et après appel (on pourra s’aider d’une fonctiond’affichage).
Ici,pourquelafonctionpuissemodifierlavaleurd’unestructurereçueenargument,ilest nécessaire qu’elle en reçoive, soit la référence, soit l’adresse.Voici un exemplecompletutilisantlapremièrepossibilité:
#include<iostream>
usingnamespacestd;
constintNMOIS=12;
structenreg
{intstock;
floatprix;
intventes[NMOIS];
};
voidraz(enreg&s)
{s.stock=0;
for(inti=0;i<NMOIS;i++)
s.ventes[i]=0;
return;
}
voidaffiche(enregs)//transmissionparvaleurici
{cout<<"stock:"<<s.stock<<"\n";
cout<<"prix:"<<s.prix<<"\n";
cout<<"ventes:";
for(inti=0;i<NMOIS;i++)cout<<s.ventes[i]<<"";
cout<<"\n";
}
main()
{voidraz(enreg&);
102
enrege={12,5.25,{12,23,4,8,4,9,5,2,7,2,8,7}};
cout<<"contenuavantraz:\n";
affiche(e);
raz(e);
cout<<"contenuapresraz:\n";
affiche(e);
}
Voiciunexempled’exécutiondeceprogramme:contenuavantraz:
stock:12
prix:5.25
ventes:12234849527287
contenuapresraz:
stock:0
prix:5.25
ventes:000000000000
À titre indicatif, voici ce que serait notre fonction raz, avec une transmission parpointeur:
voidraz(enreg*ads)
{ads->stock=0;
for(inti=0;i<NMOIS;i++)
ads->ventes[i]=0;
return;
}
Danslafonctionmain,sadéclarationetsonappeldeviendraient:voidraz(enreg*);
raz(&e)
103
Exercice49
ÉnoncéSoitletypestructuresuivant,représentantunpointd’unplan:
structpoint{charc;//nomattribuéaupoint
intx,y;//sescoordonnées
}
Écrireunefonctionquireçoitenargumentl’adressed’unestructuredutypepointetquirenvoieenrésultatunestructuredemêmetypecorrespondantàunpointdemêmenometdecoordonnéesopposées.
Écrireunpetitprogrammed’essai.
Voicicequepourraitêtrenotrefonction(nousavonsreproduitladéclarationdepoint,laquelle pourrait éventuellement figurer dans un fichier en-tête séparé qu’onincorporeraitparunedirective#include):
#include<iostream>
usingnamespacestd;
structpoint
{charc;
intx,y;
};
pointsym(point*adp)
{pointres;
res.c=adp->c;
res.x=-adp->x;
res.y=-adp->y;
returnres;
}
Notez la « dissymétrie » d’instructions telles que res.c = adp->c ; on y fait appel àl’opérateur«.»àgaucheetàl’opérateur«->»àdroite(onpourraitcependantécrireres.c=(*adp).c).
Voici un exemple d’essai de notre fonction (ici, nous avons utilisé les possibilitésd’initialisationd’unestructurepourdonnerdesvaleursàp1):
main()
{pointsym(point*);
pointp1={'P',5,8};
pointp2;
p2=sym(&p1);
cout<<p1.c<<""<<p1.x<<""<<p1.y<<"\n";
cout<<p2.c<<""<<p2.x<<""<<p2.y<<"\n";
}
104
Sil’énoncénel’avaitpasimposé,nousaurionsputransmettreparréférencel’argumentde la fonctionsym.Nousaurionspuégalementutiliserune transmissionparvaleur,cequin’auraitguèreétépénalisantpouruneinformationdesipetitetaille.Enthéorie,cechoixdumodedetransmission(valeur,référenceouadresse)existeégalementpourlavaleur de retour. Toutefois, cette dernière est généralement (comme c’est le cas ici)créée dans une variable locale à la fonction. Dans ces conditions, en transmettrel’adresseoularéférencereviendraitàrenvoyerl’adressedequelquechosedestinéàdisparaître.En toute rigueur,onpourrait renvoyer l’adressed’unemplacementallouédynamiquement, mais encore faudrait-il définir clairement à qui incomberait laresponsabilitédesasuppressionultérieure.
105
Exercice50
ÉnoncéSoitlastructuresuivante,représentantunpointd’unplan:
structpoint
{charc;//nomdupoint
intx,y;//coordonnées
};
1.Écrireladéclarationd’untableau(nommécourbe)deNPpoints (NP supposédéfiniparuneconstante).
2. Écrire une fonction (nommée affiche) qui affiche les valeurs des différents«points»dutableaucourbe,transmisenargument,souslaforme:pointDdecoordonnées102
3.Écrireunprogrammequi:–litendonnéesdesvaleurspourletableaucourbe;–faitappelàlafonctionprécédentepourlesafficher.
1.Ilsuffitdedéclareruntableaudestructures:structpointcourbe[NP];
2.Commecourbeestuntableau,onnepeutqu’entransmettrel’adresseenargumentdeaffiche.Ilestpréférabledeprévoirégalementenargumentlenombredepoints.Voicicequepourraitêtrenotrefonction:voidaffiche(pointcourbe[],intnp)
/*courbe:adressedelapremièrestructuredutableau*/
/*(onpourraitécrirepoint*courbe)*/
/*np:nombredepointsdelacourbe*/
{inti;
for(i=0;i<np;i++)
cout<<"point"<<courbe[i].c<<"decoordonnées"
<<courbe[i].x<<""<<courbe[i].x<<"\n";
}
Commepour n’importe quel tableau à une dimension transmis en argument, il estpossibledenepasenmentionner ladimensiondans l’en-têtede la fonction.Bienentendu, comme, en fait, l’identificateur courbe n’est qu’unpointeur de type point *(pointeur sur la première structure du tableau), nous aurions pu également écrirepoint*courbe.
Notez que, comme à l’accoutumée, le « formalisme tableau » et le « formalismepointeur»peuventêtreindifféremmentutilisés(voirecombinés).Parexemple,notrefonctionauraitpuégalements’écrire:
106
voidaffiche(point*courbe,intnp)
{point*adp;
inti;
for(i=0,adp=courbe;i<np;i++,adp++)
cout<<"point"<<(courbe+i)->c<<"decoordonnées"
<<(courbe+i)->x<<(courbe+i)->y);
}
3.Voicicequepourraitdonnerleprogrammedemandé:#include<iostream>
usingnamespacestd;
constintNP=4;//nombredepointsd’unecourbe
structpoint
{charc;
intx,y;
};
voidaffiche(pointcourbe[],intnp)
{
inti;
for(i=0;i<np;i++)
cout<<"point"<<courbe[i].c<<"decoordonnées"
<<courbe[i].x<<""<<courbe[i].x<<"\n";
}
main()
{pointcourbe[NP];
inti;
voidaffiche(point[],int);
/*lecturedesdifférentspointsdelacourbe*/
for(i=0;i<NP;i++)
{cout<<"nom(1caractère)etcoordonnéespoint"<<i+1<<"\n";
cin>>courbe[i].c>>courbe[i].x>>courbe[i].y;
}
affiche(courbe,NP);
}
107
Exercice51
ÉnoncéÉcrire le programme de la question 3 de l’exercice précédent, sans utiliser destructures.Onprévoiratoujoursunefonctionpourlirelesinformationsrelativesàunpoint.
Ici,ilnousfautobligatoirementprévoir3tableauxdifférentsdemêmetaille:unpourlesnomsdepoints,unpourleursabscissesetunpourleursordonnées.Leprogrammene présente pas de difficultés particulières (son principal intérêt est de pouvoir êtrecomparéauprécédent!).
#include<iostream>
usingnamespacestd;
constintNP=4;//nombredepointsd'unecourbe
main()
{charc[NP];//nomsdesdifférentspoints
intx[NP];//abscissesdesdifférentspoints
inty[NP];//ordonnéesdesdifférentspoints
inti;
voidaffiche(char[],int[],int[],int);
/*lecturedesdifférentspointsdelacourbe*/
for(i=0;i<NP;i++)
{cout<<"nom(1caractère)etcoordonnéespoint"
<<i+1<<":\n";
cin>>c[i]>>x[i]>>y[i];
}
affiche(c,x,y,NP);
}
voidaffiche(charc[],intx[],inty[],intnp)
{for(inti=0;i<np;i++)
cout<<"point"<<c[i]<<"decoordonnées"
<<x[i]<<""<<y[i]<<"\n";
}
108
Exercice52
ÉnoncéSoientlesdeuxmodèlesdestructuredateetpersonnedéclarésainsi:
contintLG_NOM=30;
structdate
{intjour;
intmois;
intannee;
};
structpersonne
{charnom[LG_NOM+1];//chaînedecaractères(destyleC)
//représentantlenom
structdatedate_embauche;
structdatedate_poste;
};
Écrire une fonction qui reçoit en argument une structure de type personne et qui enremplitlesdifférentschampsavecundialogueseprésentantsousl’unedes2formessuivantes:
nom:DUPONT
dateembauche(jjmmaa):16175
dateposte=dateembauche?(O/N):O
nom:DUPONT
dateembauche(jjmmaa):10381
dateposte=dateembauche?(O/N):N
dateposte(jjmmaa):23891
Notre fonction doitmodifier le contenu d’une structure de type personne ; il est doncnécessaire qu’elle en reçoive la référence ou l’adresse en argument. Ici, l’énoncén’imposantriendeparticulier,nouschoisironsunetransmissionparréférence.Voicicequepourraitêtrelafonctiondemandée:
voidremplit(personne&p)
{charrep;//pourlireuneréponsedetypeO/N
cout<<"nom:";
cin>>p.nom;//attention,pasdecontrôledelongueur
cout<<"dateembauche(jjmmaa):";
cin>>p.date_embauche.jour
>>p.date_embauche.mois
>>p.date_embauche.annee;
cout<<"dateposte=dateembauche?(O/N):";
cin>>rep;
if(rep=='O')p.date_poste=p.date_embauche;
else{cout<<"dateposte(jjmmaa):";
cin>>p.date_poste.jour
>>p.date_poste.mois
109
>>p.date_poste.annee;
}
}
Voici, à titre indicatif, un petit programme d’essai de notre fonction (sa compilationnécessitelesdéclarationsdesstructuresdateetpersonne):
main()
{voidremplit(personne&);//déclarationremplit
personnebloc;
remplit(bloc);
cout<<"nom:"<<bloc.nom<<"\ndateembauche:"
<<bloc.date_embauche.jour<<""
<<bloc.date_embauche.mois<<""
<<bloc.date_embauche.annee<<"\n"
<<"dateposte:"
<<bloc.date_poste.jour<<""
<<bloc.date_poste.mois<<""
<<bloc.date_poste.annee;
}
110
Chapitre6DeCàC++
N.B.Cechapitreconstitue le«pointd’entrée»de l’ouvragepour lesprogrammeursabordantl’étudedeC++,enayantdéjàuneconnaissancedulangageC.Iln’apasàêtreprisencompteparlesautresprogrammeursquipourronttoutsimplementl’ignorer.
111
Rappels
C++estpresqueunsur-ensembleduC,telqu’ilestdéfiniparlanormeANSI.Seulesquelques incompatibilités existent ; nous rappelons ici les principales. Par ailleurs,C++dispose,parrapportauCANSI,d’uncertainnombredespécificitésquinesontpas véritablement axées sur la programmation orientée objet. Elles sont égalementexaminéesici.
DéclarationsdefonctionsEnC++, toute fonctionutiliséedansun fichier sourcedoit obligatoirement avoir faitl’objet:
soitd’unedéclarationsousformed’unprototype (ilpréciseàlafois lenomdelafonction, le type de ses arguments éventuels et le type de sa valeur de retour),commedanscetexemple:floatfexp(int,double,char*);
soitd’unedéfinitionpréalableauseindumêmefichiersource(cederniercasétantd’ailleurspeuconseillé,danslamesureoùdesproblèmesrisquentd’apparaîtredèslorsqu’onsépareladitefonctiondufichiersourceenquestion).
EnC,unefonctionpouvaitnepasêtredéclarée(auquelcasonconsidérait,pardéfaut,quesavaleurderetourétaitdetypeint),ouencoredéclaréepartiellement(sansfournirletypedesesarguments),commedans:
floatfexp();
FonctionssansargumentsEnC++,unefonctionsansargumentsedéfinit(auniveaudel’en-tête)etsedéclare(auniveauduprototype)enfournissantune«listed’argumentsvide»commedans:
floatfct();
EnC,onpouvait indifféremmentutilisercettenotationoufaireappelaumot-clévoid,commedans:
floatfct(void)
Fonctionssansvaleurderetour
112
EnC++,unefonctionsansvaleurderetoursedéfinit(en-tête)etsedéclare(prototype)obligatoirementàl’aidedumot-clévoid,commedans:
voidfct(int,double);
EnC,l’emploidumot-clévoidétait,danscecas,facultatif.
LequalificatifconstEnC++,unsymboleglobaldéclaréaveclequalificatifconst:
a une portée limitée au fichier source concerné, tandis qu’en C il pouvaitéventuellement être utilisé dans un autre fichier source (en utilisant le mot-cléextern);
peut être utilisé dans une expression constante (calculable au moment de lacompilation), alors qu’il ne pouvait pas l’être en C ; ce dernier point permetnotamment d’utiliser de tels symboles pour définir la taille d’un tableau (enC, ilfallaitobligatoirementavoir recoursàunedéfinitiondesymbolespar ladirective#define).
Letypevoid*EnC++,unpointeurdetypevoid*nepeutpasêtreconverti implicitement lorsd’uneaffectationenunpointeurd’unautretype;lachoseétaitpermiseenC.Bienentendu,enC++,ilrestepossibledefaireappelàl’opérateurdecast.
Nouvellespossibilitésd’entrées-sortiesC++ dispose de nouvelles facilités d’entrées-sorties. Bien qu’elles soient fortementliées à des aspects P.O.O. (surdéfinition d’opérateur en particulier), elles sontparfaitementutilisablesendehorsdececontexte.C’esttoutparticulièrementlecasdespossibilités d’entrées-sorties conversationnelles (clavier, écran) qui remplacentavantageusementlesfonctionsprintfetscanf.Ainsi:
cout<<expression1<<expression2<<.........<<expressionn
affichesurleflotcout (connectépardéfautàlasortiestandardstdout) lesvaleursdesdifférentes expressions indiquées, selonuneprésentationadaptée à leur type (on saitdistinguerlesattributsdesigneetonpeutafficherdesvaleursdepointeurs).Demême:
cin>>lvalue1>>lvalue2>>.........>>lvaluen
113
litsurleflotcin(connectépardéfautàl’entréestandardstdin)desinformationsdel’undes typeschar,short,int,long,float,doubleouchar* (on sait distinguer les attributs designe ; en revanche, lespointeursne sontpasadmis).Lesconventionsd’analysedescaractèreslussontcomparablesàcellesdescanf,aveccetteprincipaledifférencequela lectured’uncaractèrecommencepar sauter lesespacesblancs (espace, tabulationhorizontale,tabulationverticale,findeligne,changementdepage).
NouvelleformedecommentairesLesdeuxcaractères//permettentd’introduiredescommentairesdefindeligne: toutce qui suit ces caractères, jusqu’à la fin de la ligne, est considéré comme uncommentaire.
EmplacementlibredesdéclarationsEnC++,iln’estplusnécessairederegrouperlesdéclarationsendébutdefonctionouen début de bloc. Il devient ainsi possible d’employer des expressions dans desinitialisations,commedanscetexemple:
intn;
.....
n=...;
.....
intq=2*n-1;
.....
Cespossibilitéss’appliquentégalementauxinstructionsstructuréesfor,switch,whileetdo...while,commedanscetexemple:
for(inti=0;...;...)//laportéedeiestlimitéeaublocquisuit
{.....
}
LatransmissionparréférenceEn faisant suivre du symbole & le type d’un argument dans l’en-tête (et dans leprototype)d’unefonction,onréaliseunetransmissionparréférence.Celasignifiequeleséventuellesmodificationseffectuéesauseindelafonctionporterontsurl’argumenteffectifdel’appeletnonplussurunecopie.Onnoteraqu’alorsl’argumenteffectifdoitobligatoirement être une lvalue du même type que l’argument muet correspondant.Toutefois,si l’argumentmuetest,desurcroît,déclaréavecl’attributconst, la fonctionreçoit quandmêmeune copie de l’argument effectif correspondant, lequel peut alorsêtreuneconstanteouuneexpressiond’untypesusceptibled’êtreconvertidansletype
114
attendu.
Cespossibilitésdetransmissionparréférences’appliquentégalementàunevaleurderetour(danscecas,lanotiondeconstancen’aplusdesignification).
La notion de référence est théoriquement indépendante de celle de transmissiond’argument;enpratique,elleestrarementutiliséeendehorsdececontexte.
LesargumentspardéfautDans ladéclarationd’une fonction (prototype), il estpossibledeprévoirpourunouplusieursarguments(obligatoirement lesderniersde la liste)desvaleurspardéfaut ;elles sont indiquéespar le signe =, à la suite du type de l’argument commedans cetexemple:
floatfct(char,int=10,float=0.0);
Cesvaleurspardéfautserontalorsutiliséeslorsqu’onappelleraladitefonctionavecunnombre d’arguments inférieur à celui prévu. Par exemple, avec la précédentedéclaration,l’appelfct('a')seraéquivalentàfct('a',10,0.0) ;demême,l’appelfct('x',12)seraéquivalentàfct('x',12,0.0).Enrevanche,l’appelfct()seraillégal.
SurdéfinitiondefonctionsEn C++, il est possible, au sein d’un même programme, que plusieurs fonctionspossèdent lemêmenom.Danscecas, lorsque lecompilateurrencontre l’appeld’unetellefonction,ileffectuelechoixdela«bonne»fonctionentenantcomptedelanaturedes arguments effectifs. D’une manière générale, si les règles utilisées par lecompilateur pour sa recherche sont assez intuitives, leur énoncé précis est assezcomplexeetnousnelerappelleronspasici(onletrouvera,parexemple,dansl’annexede nos différents ouvrages consacrés à C++ et publiés également aux ÉditionsEyrolles.) Signalons simplement que ces règles peuvent faire intervenir toutes lesconversionsusuelles (promotionsnumériqueset conversions standards, cesdernièrespouvant être dégradantes), ainsi que les conversions définies par l’utilisateur en casd’argumentdetypeclasse,àconditionqu’aucuneambiguïtén’apparaisse.
Gestiondynamiquedelamémoire
115
EnC++,lesfonctionsmalloc,calloc...etfree sont remplacéesavantageusementpar lesopérateursnewetdelete.
Sitypereprésenteladescriptiond’untypeabsolumentquelconqueetsinreprésenteuneexpressiond’untypeentier(généralementlongouunsignedlong),l’expression:
newtype[n]
allouel’emplacementnécessairepournélémentsdutypeindiquéetfournitenrésultatunpointeur(detypetype*)surlepremierélément.Encasd’échecd’allocation,ilyadéclenchement d’une exception bad_alloc (les exceptions font l’objet du chapitre 19).L’indication n est facultative : avec new type, on obtient un emplacement pour unélémentdutypeindiqué,commesil’onavaitécritnewtype[1].
L’expression:deleteadresse//ilexisteuneautresyntaxepourlestableauxd’objets
//voirchap.9
libèreunemplacementpréalablementallouéparnewà l’adresse indiquée. Iln’estpasnécessairederépéterlenombred’éléments,dumoinslorsqu’ilnes’agitpasd’objets,mêmesicelui-ciestdifférentde1.Lecasdestableauxd’objetsestexaminéauchapitre9.
LesfonctionsenligneUne fonction en ligne (on dit aussi « développée ») est une fonction dont lesinstructionssontincorporéesparlecompilateur(danslemoduleobjetcorrespondant)àchaqueappel.Celaévitelapertedetempsnécessaireàunappelusuel(changementdecontexte, copie des valeurs des arguments sur la « pile »...) ; en revanche, lesinstructionsenquestionsontgénéréesplusieursfois.
Les fonctions « en ligne» offrent lemême intérêt que lesmacros, sans présenter derisques d’effets de bord. Une fonction en ligne est nécessairement définie en mêmetempsqu’elleestdéclarée(ellenepeutplusêtrecompiléeséparément)etsonen-têteestprécédédumot-cléinline,commedans:
inlinefct(...){.....}
116
Exercice53
ÉnoncéQuelleserreursserontdétectéesparuncompilateurC++danscefichiersourcequiestacceptéparuncompilateurC?
main()
{
inta=10,b=20,c;
c=g(a,b);
printf("valeurdeg(%d,%d)=%d",a,b,c);
}
g(intx,inty)
{
return(x*x+2*x*y+y*y);
}
1.Lafonctiongdoitobligatoirementfairel’objetd’unedéclaration(souslaformed’unprototype)dans la fonctionmain.Parexemple,onpourrait introduire (n’importeoùavantl’appeldeg):intg(int,int);
ouencore:intg(intx,inty);
Rappelonsque,danscederniercas,lesnomsxetysontfictifs:ilsn’ontaucunrôledanslasuiteetilsn’interfèrentnullementavecd’autresvariablesdemêmenomquipourraientêtredéclaréesdanslamêmefonction(icimain).
2.Lafonctionprintfdoit,elleaussi,comme toutes les fonctionsC++(lecompilateurn’étant pas enmesurededistinguer les fonctionsde la bibliothèquedes fonctionsdéfinies par l’utilisateur), faire l’objet d’un prototype.Naturellement, il n’est pasnécessairedel’écrireexplicitement; ilestobtenuparincorporationdufichieren-têtecorrespondant:#include<cstdio>
Lessymbolesdéfinisdanscefichierappartiennentàl’espacedenomsstd,desortequ’ilfautégalementprévoiruneinstruction:usingnamespacestd;
NotezquecertainscompilateursCrefusentl’absencedeprototypepourunefonctiondelabibliothèquestandardtellequeprintf(maislanormeANSIn’imposaitrienà
117
cesujet!).
118
Exercice54
ÉnoncéÉcrirecorrectementenCceprogrammequiestcorrectenC++:
#include<cstdio>
usingnamespacestd;
constintnb=10;
constintexclus=5;
main()
{
intvaleurs[nb];
inti,nbval=0;
printf("donnez%dvaleurs:\n",nb);
for(i=0;i<nb;i++)scanf("%d",&valeurs[i]);
for(i=0;i<nb;i++)
switch(valeurs[i])
{caseexclus-1:
caseexclus:
caseexclus+1:nbval++;
}
printf("%dvaleurssontinterdites",nbval);
}
EnC,lessymbolesnbetexclusnesontpasutilisablesdansdesexpressionsconstantes.Il fautdonc lesdéfinirsoitcommedesvariables,soità l’aided’unedirective#definecommesuit:
#include<stdio.h>
#defineNB10
#defineEXCLUS5
main()
{
intvaleurs[NB];
inti;
intnbval=0;
printf("donnez%dvaleurs:\n",NB);
for(i=0;i<NB;i++)scanf("%d",&valeurs[i]);
for(i=0;i<NB;i++)
switch(valeurs[i])
{caseEXCLUS-1:
caseEXCLUS:
caseEXCLUS+1:nbval++;
}
printf("%dvaleurssontinterdites",nbval);
}
119
Exercice55
ÉnoncéModifierleprogrammeCsuivant,defaçonqu’ilsoitcorrectenC++etqu’ilnefasseappelqu’auxnouvellespossibilitésd’entrées-sortiesdeC++,c’est-à-direqu’ilévitelesappelsàprintfetscanf:
#include<stdio.h>
main()
{
intn;floatx;
printf("donnezunentieretunflottant\n");
scanf("%d%e",&n,&x);
printf("leproduitde%dpar%e\n'est:%e",n,x,n*x);
}
#include<iostream>
usingnamespacestd;
main()
{
intn;floatx;
cout<<"donnezunentieretunflottant\n";
cin>>n>>x;
cout<<"leproduitde"<<n<<"par"<<x<<"\n'est:"<<n*x;
}
120
Exercice56
ÉnoncéÉcrire une fonction permettant d’échanger les contenus de 2 variables de type intfourniesenargument:
a. en transmettant l’adresse des variables concernées (seuleméthode utilisable enC);
b.enutilisantlatransmissionparréférence.
Danslesdeuxcas,onécriraunpetitprogrammed’essai(main)delafonction.
a.Aveclatransmissiondesadressesdesvariables#include<iostream>
usingnamespacestd;
main()
{voidechange(int*,int*);//prototypedelafonctionechange
intn=15,p=23;
cout<<"avant:"<<n<<""<<p<<"\n";
echange(&n,&p);
cout<<"après:"<<n<<""<<p<<"\n";
}
voidechange(int*a,int*b)
{intc;//pourlapermutation
c=*a;
*a=*b;
*b=c;
}
b.Avecunetransmissionparréférence#include<iostream>
usingnamespacestd;
main()
{voidechange(int&,int&);//prototypedelafonctionechange
intn=15,p=23;
cout<<"avant:"<<n<<""<<p<<"\n";
echange(n,p);//attentionnetnon&n,petnon&p
cout<<"après:"<<n<<""<<p<<"\n";
}
voidechange(int&a,int&b)
{intc;//pourlapermutation
c=a;
a=b;
b=c;
}
121
Exercice57
ÉnoncéSoitlemodèledestructuresuivant:
structessai
{intn;
floatx;
};
Écrire une fonctionnommée raz permettant de remettre à zéro les 2 champs d’unestructuredecetypetransmiseenargument:
a.paradresse;
b.parréférence.
Danslesdeuxcas,onécriraunpetitprogrammed’essaidelafonction;ilafficheralesvaleursd’unestructuredecetype,aprèsappeldeladitefonction.
a.Avecunetransmissiond’adresse#include<iostream>
usingnamespacestd;
structessai
{intn;
floatx;
};
voidraz(structessai*ads)
{ads->n=0;//ouencore(*ads).n=0;
ads->x=0.0;//ouencore(*ads).x=0.0;
}
main()
{structessais;
raz(&s);
cout<<"valeursaprèsraz:"<<s.n<<""<<s.x;
}
b.Avecunetransmissionparréférence#include<iostream>
usingnamespacestd;
structessai
{intn;
floatx;
};
voidraz(structessai&s)
{s.n=0;
s.x=0.0;
}
122
main()
{structessais;
raz(s);//notezbiensetnon&s!
cout<<"valeursaprèsraz:"<<s.n<<""<<s.x;
}
123
Exercice58
ÉnoncéSoientlesdéclarations(C++)suivantes:
intfct(int);//fonctionI
intfct(float);//fonctionII
voidfct(int,float);//fonctionIII
voidfct(float,int);//fonctionIV
intn,p;
floatx,y;
charc;
doublez;
Les appels suivants sont-ils corrects et, si oui, quelles seront les fonctionseffectivementappeléesetlesconversionséventuellementmisesenplace?
a.fct(n);
b.fct(x);
c.fct(n,x);
d.fct(x,n);
e.fct(c);
f.fct(n,p);
g.fct(n,c);
h.fct(n,z);
i.fct(z,z);
Lescasa,b,cetdneposentaucunproblème.IlyarespectivementappeldesfonctionsI,II,IIIetIV,sansqu’aucuneconversiond’argumentnesoitnécessaire.
e.AppeldelafonctionI,aprèsconversiondelavaleurdecenint.f.Appelincorrect,comptetenudesonambiguïté;deuxpossibilitésexistenteneffet:
conservern,convertirpenfloatetappelerlafonctionIIIou,aucontraire,convertirnenfloat,conserverpetappelerlafonctionIV.
g.AppeldelafonctionIII,aprèsconversiondecenfloat.h.AppeldelafonctionIII,aprèsconversion(dégradante)dezenfloat.
124
i.Appelincorrect,comptetenudesonambiguïté;deuxpossibilitésexistenteneffet:convertirlepremierargumentenfloatet lesecondenintetappeler lafonctionIIIou,aucontraire,convertirlepremierargumentenintetlesecondenfloatetappelerlafonctionIV.Notezque,danslesdeuxcas,ils’agitdeconversionsdégradantes.
125
Exercice59
ÉnoncéÉcrireplussimplementenC++lesinstructionssuivantes,enutilisantlesopérateursnewetdelete:
int*adi;
double*add;
.....
adi=malloc(sizeof(int));
add=malloc(sizeof(double)*100);
int*adi;
double*add;
.....
adi=newint;
add=newdouble[100];
On peut éventuellement tenir compte des possibilités de déclarations dynamiquesoffertesparC++(c’est-à-direquel’onpeutintroduireunedéclarationàn’importequelemplacementd’unprogramme),etécrire,parexemple:
int*adi=newint;
double*add=newdouble[100];
126
Exercice60
ÉnoncéÉcrire plus simplement en C++, en utilisant les spécificités de ce langage, lesinstructionsCsuivantes:
double*adtab;
intnval;
.....
printf("combiendevaleurs?");
scanf("%d",&nval);
adtab=malloc(sizeof(double)*nval);
double*adtab;
intnval;
.....
cout<<"combiendevaleurs?";
cin>>nval;
adtab=newdouble[nval];
127
Exercice61
Énoncé
a.Transformerleprogrammesuivantpourquelafonctionfctdevienneunefonctionenligne.#include<iostream>
usingnamespacestd;
main()
{
intfct(char,int);//déclaration(prototype)defct
intn=150,p;
charc='s';
p=fct(c,n);
cout<<"fct(\'"<<c<<"\',"<<n<<")vaut:"<<p;
}
intfct(charc,intn)//définitiondefct
{
intres;
if(c=='a')res=n+c;
elseif(c=='s')res=n-c;
elseres=n*c;
returnres;
}
b.Commentfaudrait-ilprocédersil’onsouhaitaitquelafonctionfct soitcompiléeséparément?
a. Nous devons donc d’abord déclarer (et définir en même temps) la fonction fctcommeunefonctionenligne.Leprogrammemains’écritdelamêmemanière,sicen’est que la déclaration de fct n’y est plus nécessaire puisqu’elle apparaîtauparavant.#include<iostream>
usingnamespacestd;
inlineintfct(charc,intn)
{intres;
if(c=='a')res=n+c;
elseif(c=='s')res=n-c;
elseres=n*c;
returnres;
}
main()
{intn=150,p;
charc='s';
p=fct(c,n);
cout<<"fct(\'"<<c<<"\',"<<n<<")vaut:"<<p;
}
b.Ils’agitenfaitd’unequestionpiège.Eneffet,lafonctionfctétantenligne,ellene
128
peutplusêtrecompiléeséparément.Ilestcependantpossibledelaconserverdansun fichier d’extension h et d’incorporer simplement ce fichier par #include pourcompilerlemain.Cettedémarcheserencontrerad’ailleursfréquemmentdanslecasdeclassescomportantdesfonctionsenligne.Alors,dansunfichierd’extensionh,ontrouvera la déclaration de la classe en question, à l’intérieur de laquelleapparaîtrontles«déclarations-définitions»desfonctionsenligne.
129
Chapitre7Notionsdeclasse,constructeuret
destructeur
130
Rappels
LespossibilitésdeProgrammationOrientéeObjetdeC++reposentsurleconceptdeclasse.Uneclasseestlagénéralisationdelanotiondetypedéfiniparl’utilisateur,danslequelsetrouventassociéesàlafoisdesdonnées(onparlede«membresdonnée»)etdesfonctions(onparlede«fonctionsmembre»oudeméthodes).EnP.O.O.pure,lesdonnéessont«encapsulées»,cequisignifiequeleuraccèsnepeutsefairequeparlebiais des méthodes. C++ vous autorise à n’encapsuler qu’une partie seulement desdonnéesd’uneclasse.
En toute rigueur,C++vouspermetégalementd’associerdes fonctionsmembreàdesstructuresouàdesunions.Danscecas,toutefois,aucuneencapsulationn’estpossible(cequirevientàdirequetouslesmembressont«publics»).
Déclarationetdéfinitiond’uneclasseLadéclaration d’une classe précise quels sont lesmembres (données ou fonctions)publics(c’est-à-direaccessiblesàl’utilisateurdelaclasse)etquelssontlesmembresprivés (inaccessibles à l’utilisateur de la classe).On utilise pour cela lesmots-cléspublic et private, comme dans cet exemple dans lequel la classe point comporte deuxmembresdonnéeprivésxetyettroisfonctionsmembrespubliquesinitialise,deplaceetaffiche:
/*------------Déclarationdelaclassepoint-------------*/
classpoint
{/*déclarationdesmembresprivés*/
private:
intx;
inty;
/*déclarationdesmembrespublics*/
public:
voidinitialise(int,int);
voiddeplace(int,int);
voidaffiche();
};
Ladéfinitiond’uneclasseconsisteàfournirlesdéfinitionsdesfonctionsmembre.Onindiquealorslenomdelaclassecorrespondante,àl’aidedel’opérateurderésolutionde portée (::). Au sein de la définition même, les membres (privés ou publics —données ou fonctions) sont directement accessibles sans qu’il soit nécessaire depréciserlenomdelaclasse.Voici,parexemple,cequepourraitêtreladéfinitiondela
131
fonctioninitialisedelaclasseprécédente:voidpoint::initialise(intabs,intord)
{
x=abs;y=ord;
}
Ici,xetyreprésententimplicitementlesmembresxetyd’unobjetdelaclassepoint.
1. Les mots-clés public et private peuvent apparaître à plusieurs reprises dans ladéclarationd’unemêmeclasse.Unedéclarationtellequelasuivanteesttoutàfaitenvisageable:classX
{private:
...
public:
...
private:
...
public:
...
};
2.Commeonleverradanslechapitreconsacréàl’héritage,ilexisteuntroisièmemot-clé,protected,quin’intervientqu’encasd’existencedeclassesdérivées.
Utilisationd’uneclasseOndéclareun«objet»d’untypeclassedonnéenfaisantprécédersonnomdeceluidelaclasse,commedansl’instructionsuivantequidéclaredeuxobjetsaetbdetypepoint:
pointa,b;
Onpeutaccéderàn’importequelmembrepublic(donnéeoufonction)d’uneclasseenutilisantl’opérateur".".Parexemple:
a.initialise(5,2);
appellelafonctionmembreinitialisedelaclasseàlaquelleappartientl’objeta,c’est-à-dire,ici,laclassepoint.
AffectationentreobjetsC++autorise l’affectationd’unobjetd’un typedonnéàunautreobjetdemême type.Dans ce cas, il y a (tout naturellement) recopie des valeurs des champs de données(qu’ils soient publics ou privés). Toutefois, si, parmi ces champs, se trouvent des
132
pointeurs,lesemplacementspointésneserontpassoumisàcetterecopie.Siunteleffetestnécessaire(etilleserasouvent!),ilnepourraêtreobtenuqu’en«surdéfinissant»l’opérateur d’affectation pour la classe concernée (voyez le chapitre consacré à lasurdéfinitiond’opérateurs).
ConstructeuretdestructeurUnefonctionmembreportant lemêmenomquesaclassesenommeunconstructeur.Dès qu’une classe comporte un constructeur (aumoins un), il n’est plus possible dedéclarerunobjetdu typecorrespondant, sans fournirdesvaleurspour les argumentsrequis par ce constructeur (sauf si ce dernier ne possède aucun argument). Leconstructeurestappeléaprèsl’allocationdel’espacemémoiredestinéàl’objet.
Pardéfinition,unconstructeurnerenvoiepasdevaleur(aucuneindicationdetype,pasmêmevoid,nedoitfigurerdevantsadéclarationousadéfinition).
Unefonctionmembreportantlemêmenomquesaclasse,précédédusymboletilde(~),se nomme undestructeur. Le destructeur est appelé avant la libération de l’espacemémoire associé à l’objet. Par définition, un destructeur ne peut pas comporterd’arguments et il ne renvoie pas de valeur (aucune indication de type ne doit êtreprévue).
MembresdonnéestatiquesUnmembredonnéedéclaréavec l’attribut static estpartagépar tous lesobjetsde lamêmeclasse. Il existemême lorsque aucunobjet de cette classe n’a été déclaré.Unmembre donnée statique doit être initialisé explicitement, à l’extérieur de la classe(mêmes’ilestprivé),enutilisantl’opérateurderésolutiondeportée(::)pourspécifiersaclasse.Engénéral,soninitialisationsefaitdansladéfinitiondelaclasse.
Exploitationd’uneclasseEnpratique,àl’utilisateurd’uneclasse(onditsouventle«client»),onfournira:
un fichier en-tête contenant la déclaration de la classe : l’utilisateur l’employantdevral’incluredanstoutprogrammefaisantappelàlaclasseenquestion;
unmoduleobjetrésultantdelacompilationdufichiersourcecontenantladéfinitiondelaclasse,c’est-à-direladéfinitiondesesfonctionsmembre.
133
Exercice62
ÉnoncéRéaliseruneclassepointpermettantdemanipulerunpointd’unplan.Onprévoira:
•unconstructeurrecevantenargumentslescoordonnées(float)d’unpoint;
• une fonction membre deplace effectuant une translation définie par ses deuxarguments(float);
• une fonction membre affiche se contentant d’afficher les coordonnéescartésiennesdupoint.
Lescoordonnéesdupointserontdesmembresdonnéeprivés.
Onécriraséparément:
•unfichiersourceconstituantladéclarationdelaclasse;
•unfichiersourcecorrespondantàsadéfinition.
Écrire,parailleurs,unpetitprogrammed’essai(main)déclarantunpoint,l’affichant,ledéplaçantetl’affichantànouveau.
a.Fichiersource(nommépoint.h)contenantladéclarationdelaclasse/*fichierPOINT1.H*/
/*déclarationdelaclassepoint*/
classpoint
{
floatx,y;//coordonnées(cartésiennes)dupoint
public:
point(float,float);//constructeur
voiddeplace(float,float);//déplacement
voidaffiche();//affichage
};
b.Fichiersourcecontenantladéfinitiondelaclasse/*définitiondelaclassepoint*/
#include"point1.h"
#include<iostream>
usingnamespacestd;
point::point(floatabs,floatord)
{x=abs;y=ord;
}
voidpoint::deplace(floatdx,floatdy)
{x=x+dx;y=+dy;
134
}
voidpoint::affiche()
{cout<<"Mescoordonnéescartésiennessont"<<x<<""<<y<<"\n";
}
Notezque,pourcompilercefichiersource,ilestnécessaired’inclurelefichiersource,nomméicipoint1.h,contenantladéclarationdelaclassepoint.
c.Exempled'utilisationdelaclasse#include<iostream>
usingnamespacestd;
#include"point1.h"
main()
{
pointp(1.25,2.5);//constructiond'unpointdecoordonnées1.252.5
p.affiche();//affichagedecepoint
p.deplace(2.1,3.4);//déplacementdecepoint
p.affiche();//nouvelaffichage
}
Bienentendu,pourpouvoirexécuterceprogramme,ilseranécessaired’introduire,lorsde l’édition de liens, le module objet résultant de la compilation du fichier sourcecontenantladéfinitiondelaclassepoint.
Notezque,généralement,lefichierpoint1.hcontiendradesdirectivesconditionnellesdecompilation, afin d’éviter les risques d’inclusion multiple. Par exemple, on pourraprocéderainsi:
#ifndefPOINT1_H
#definePOINT1_H
.....
déclarationdelaclasse.....
.....
#endif
135
Exercice63
ÉnoncéRéaliser une classe point, analogue à la précédente, mais ne comportant pas defonction affiche. Pour respecter le principe d’encapsulation des données, prévoirdeuxfonctionsmembrepubliques(nomméesabscisseetordonnee)fournissantenretourl’abscisse et l’ordonnéed’unpoint.Adapter le petit programmed’essai précédentpourqu’ilfonctionneaveccettenouvelleclasse.
Il suffit d’introduire deux nouvelles fonctions membre abscisse et ordonnee et desupprimerlafonctionaffiche.Lanouvelledéclarationdelaclasseestalors:
/*fichierPOINT2.H*/
/*déclarationdelaclassepoint*/
classpoint
{
floatx,y;//coordonnées(cartésiennes)dupoint
public:
point(float,float);//constructeur
voiddeplace(float,float);//déplacement
floatabscisse();//abscissedupoint
floatordonnee();//ordonnéedupoint
};
Voicisanouvelledéfinition:/*définitiondelaclassepoint*/
#include"point2.h"
#include<iostream>
usingnamespacestd;
point::point(floatabs,floatord)
{x=abs;y=ord;
}
voidpoint::deplace(floatdx,floatdy)
{x=x+dx;y=+dy;
}
floatpoint::abscisse()
{returnx;
}
floatpoint::ordonnee()
{returny;
}
Etlenouveauprogrammed’essai:/*exempled'utilisationdelaclassepoint*/
#include"point2.h"
#include<iostream>
usingnamespacestd;
main()
136
{
pointp(1.25,2.5);//construction
//affichage
cout<<"Coordonnéescartésiennes:"<<p.abscisse()<<""
<<p.ordonnee()<<"\n";
p.deplace(2.1,3.4);//déplacement
//affichage
cout<<"Coordonnéescartésiennes:"<<p.abscisse()<<""
<<p.ordonnee()<<"\n";
}
DiscussionCetexemplemontrequ’ilesttoujourspossiblederespecterleprinciped’encapsulationen introduisant ce que l’on nomme des « fonctions d’accès ». Il s’agit de fonctionsmembre destinées à accéder (aussi bien en consultation — comme ici — qu’enmodification) auxmembres privés. L’intérêt de leur emploi (par rapport à un accèsdirectauxdonnéesqu’il faudraitalors rendrepubliques) résidedans lasouplessedemodificationde l’implémentationde laclassequiendécoule.Ainsi, ici, il est toutàfait possible de modifier la manière dont un point est représenté (par exemple, enutilisantsescoordonnéespolairesplutôtquesescoordonnéescartésiennes),sansquel’utilisateurdelaclassen’aitàsesoucierdecetaspect.C’estd’ailleurscequevousmontreral’exercice65ci-après.
137
Exercice64
ÉnoncéAjouteràlaclasseprécédente(comportantunconstructeurettroisfonctionsmembredeplace,abscisseetordonnee)denouvellesfonctionsmembre:
•homothetiequieffectueunehomothétiedontlerapportestfournienargumentþ;
•rotationquieffectueunerotationdontl’angleestfournienargumentþ;
•rhoetthetaquifournissentenretourlescoordonnéespolairesdupoint.
Ladéclarationdelanouvelleclassepointdécouledirectementdel’énoncé:/*fichierPOINT3.H*/
/*déclarationdelaclassepoint*/
classpoint
{floatx,y;//coordonnées(cartésiennes)dupoint
public:
point(float,float);//constructeur
voiddeplace(float,float);//déplacement
voidhomothetie(float);//homothétie
voidrotation(float);//rotation
floatabscisse();//abscissedupoint
floatordonnee();//ordonnéedupoint
floatrho();//rayonvecteur
floattheta();//angle
};
Sa définition mérite quelques remarques. En effet, si homothetie ne présente aucunedifficulté, la fonction membre rotation nécessite quant à elle une transformationintermédiaire des coordonnées cartésiennes du point en coordonnées polaires. Demême,lafonctionmembrerhodoitcalculerlerayonvecteurd’unpointdontonconnaîtlescoordonnéescartésiennestandisquelafonctionmembrethetadoitcalculerl’angled’unpointdontonconnaîtlescoordonnéescartésiennes.
Le calcul de rayon vecteur étant simple, nous l’avons laissé figurer dans les deuxfonctionsconcernées(rotationetrho).Enrevanche,lecalculd’angleaétéréaliséparceque nous nommons une « fonction de service », c’est-à-dire une fonction qui n’ad’intérêt que dans la définition de la classe elle-même. Ici, il s’agit d’une fonctionindépendantemais,bienentendu,onpeutprévoirdesfonctionsdeservicesousformedefonctionsmembre(ellesserontalorsgénéralementprivées).
Voicifinalementladéfinitiondenotreclassepoint:
138
/****************déclarationsdeservice*****************/
#include"point3.h"
#include<cmath>//poursqrtetatan
#include<iostream>
usingnamespacestd;
constfloatpi=3.141592653;//valeurdepi
floatangle(float,float);//fonctiondeservice(nonmembre)
/***************définitiondesfonctionsmembre**********/
point::point(floatabs,floatord)
{x=abs;y=ord;
}
voidpoint::deplace(floatdx,floatdy)
{x+=dx;y+=dy;
}
voidpoint::homothetie(floathm)
{x*=hm;y*=hm;
}
voidpoint::rotation(floatth)
{floatr=sqrt(x*x+y*y);//passageen
floatt=angle(x,y);//coordonnéespolaires
t+=th;//rotationth
x=r*cos(t);//retouren
y=r*sin(t);//coordonnéescartésiennes
}
floatpoint::abscisse()
{returnx;
}
floatpoint::ordonnee()
{returny;
}
floatpoint::rho()
{returnsqrt(x*x+y*y);
}
floatpoint::theta()
{returnangle(x,y);
}
/**********définitiondesfonctionsdeservice***********/
/*fonctiondecalculdel'anglecorrespondantauxcoordonnées*/
/*cartésiennesfourniesenargument*/
/*Onchoisitunedéterminationentre-piet+pi(0six=0)*/
floatangle(floatx,floaty)
{floata=x?atan(y/x):0;
if(y<0)if(x>=0)returna+pi;
elsereturna-pi;
returna;
}
139
Exercice65
ÉnoncéModifier la classe point précédente, de manière que les données (privées) soientmaintenant les coordonnées polaires d’un point, et non plus ses coordonnéescartésiennes.Onéviterademodifier la déclarationdesmembrespublics, de sortequel’interfacedelaclasse(cequiestvisiblepourl’utilisateur)nechangepas.
Ladéclarationdelanouvelleclassedécouledirectementdel’énoncé:/*fichierPOINT4.H:déclarationdelaclassepoint*/
classpoint
{floatr,t;//coordonnées(polaires)dupoint
public:
point(float,float);//constructeur
voiddeplace(float,float);//déplacement
voidhomothetie(float);//homothétie
voidrotation(float);//rotation
floatabscisse();//abscissedupoint
floatordonnee();//ordonnéedupoint
floatrho();//rayonvecteur
floattheta();//angle
};
Encequiconcernesadéfinition,ilestmaintenantnécessairederemarquerque:
leconstructeurreçoittoujoursenargumentlescoordonnéescartésiennesd’unpoint;ildoitdoncopérerlestransformationsappropriées;
la fonction deplace reçoit un déplacement exprimé en coordonnées cartésiennes ; ilfaut donc tout d’abord déterminer les coordonnées cartésiennes du point aprèsdéplacement,avantderepasserencoordonnéespolaires.
Enrevanche,lesfonctionshomothetieetrotations’exprimenttrèssimplement.
Voiciladéfinitiondenotrenouvelleclasse(nousavonsfaitappelàlamêmefonctiondeserviceanglequedansl’exerciceprécédent):
#include"point4.h"
#include<cmath>//pourcos,sin,sqrtetatan
#include<iostream>
usingnamespacestd;
constintpi=3.141592635;//valeurdepi
/**********définitiondesfonctionsdeservice***********/
/*fonctiondecalculdel'anglecorrespondantauxcoordonnées*/
/*cartésiennesfourniesenargument*/
/*Onchoisitunedéterminationentre-piet+pi(0six=0)*/
140
floatangle(floatx,floaty)
{floata=x?atan(y/x):0;
if(y<0)if(x>=0)returna+pi;
elsereturna-pi;
returna;
}
/**********définitiondesfonctionsmembre*****************/
point::point(floatabs,floatord)
{r=sqrt(abs*abs+ord*ord);
t=atan(ord/abs);
}
voidpoint::deplace(floatdx,floatdy)
{floatx=r*cos(t)+dx;//nouvelleabscisse
floaty=r*sin(t)+dy;//nouvelleordonnée
r=sqrt(x*x+y*y);
t=angle(x,y);
}
voidpoint::homothetie(floathm)
{r*=hm;
}
voidpoint::rotation(floatth)
{t+=th;
}
floatpoint::abscisse()
{returnr*cos(t);
}
floatpoint::ordonnee()
{returnr*sin(t);
}
floatpoint::rho()
{returnr;
}
floatpoint::theta()
{returnt;
}
141
Exercice66
ÉnoncéSoitlaclassepointcrééedansl’exercice62,dontladéclarationétaitlasuivante:
classpoint
{floatx,y;
public:
point(float,float);
voiddeplace(float,float);
voidaffiche();
}
Adapter cette classe,demanièreque la fonctionmembre affiche fournisse, enplusdescoordonnéesdupoint,lenombred’objetsdetypepoint.
Il faut doncdéfinir un compteurdunombred’objets existant àunmomentdonné.Cecompteur doit être incrémenté à chaque création d’un nouvel objet, donc par leconstructeurpoint.Demême, il doit êtredécrémenté à chaquedestructiond’unobjet,donc par le destructeur de la classe point ; il faudra donc ajouter ici une fonctionmembrenommée~point.
Quantaucompteurproprementdit,nouspourrionscertesenfaireunevariableglobale,définieparexempleenmêmetempsque laclasse ;cettedémarcheprésente toutefoisdesrisquesd’effetsdebord(modificationaccidentelledelavaleurdecettevariable,depuis n’importe quel programme utilisateur). Il est plus judicieux d’en faire unmembreprivéstatique.
Voicilanouvelledéclarationdenotreclassepoint:/*fichierPOINT5.H*/
/*déclarationdelaclassepoint*/
classpoint
{
staticnb_pts;//compteurdunombred'objetscréés
floatx,y;//coordonnées(cartésiennes)dupoint
public:
point(float,float);//constructeur
~point();//destructeur
voiddeplace(float,float);//déplacement
voidaffiche();//affichage
};
Etvoicisanouvelledéfinition(notezl’initialisationdumembrestatique):#include"point5.h"
#include<iostream>
142
usingnamespacestd;
intpoint::nb_pts=0;//initialisationobligatoirestatiquenb_pts
point::point(floatabs,floatord)//constructeur
x=abs;y=ord;
nb_pts++;//actualisationnbpoints
}
point::~point()//destructeur
{nb_pts--;//actualisationnbpoints
}
voidpoint::deplace(floatdx,floatdy)
{x=x+dx;y=+dy;
}
voidpoint::affiche()
{cout<<"Jesuisunpointparmi"<<nb_pts
<<"decoordonnées"<<x<<""<<y<<"\n";
}
Ilpourraitêtrejudicieuxdemunirnotreclassepointd’unefonctionmembrefournissantlenombred’objetsde typepointexistantàunmomentdonné.C’estcequenousvousproposeronsdansunexerciceduprochainchapitre.
143
Exercice67
ÉnoncéRéaliser une classe nommée set_char permettant de manipuler des ensembles decaractères.Ondevrapouvoirréalisersuruntelensemblelesopérationsclassiquessuivantes : lui ajouter un nouvel élément, connaître son « cardinal » (nombred’éléments),savoirsiuncaractèredonnéluiappartient.
Ici,onn’effectueraaucuneallocationdynamiqued’emplacementsmémoire.Ilfaudradoncprévoir,enmembredonnée,untableaudetaillefixe.
Écrire,enoutre,unprogramme(main)utilisant laclasse set_char pourdéterminer lenombredecaractèresdifférentscontenusdansunmotluendonnée.
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,etqu’ilnefautpaschercheràutiliserici.
Compte tenu des contraintes imposées par l’énoncé (pas de gestion dynamique), unesolutionconsiste àprévoirun tableaudans lequelunélémentde rang i précise si lecaractèredecodeiappartientounonàl’ensemble.Notezqu’ilestnécessairequeisoitpositifounul;ontravailleradonctoujourssurdescaractèresnonsignés.Latailledutableaudoitêtreégaleaunombredecaractèresqu’ilestpossibledereprésenterdansuneimplémentationdonnée(généralement256).
Lerestedeladéclarationdelaclassedécouledel’énoncé./*fichierSETCHAR1.H*/
/*déclarationdelaclasseset_char*/
#defineN_CAR_MAX256//onpourraitutiliserUCHAR_MAXdéfini
//dans<limits.h>
classset_char
{
unsignedcharens[N_CAR_MAX];
//tableaudesindicateurs(présent/absent)
//pourchacundescaractèrespossibles
public:
set_char();//constructeur
voidajoute(unsignedchar);//ajoutd'unélément
intappartient(unsignedchar);//appartenanced'unélément
intcardinal();//cardinaldel'ensemble
};
144
Ladéfinitiondelaclasseendécouleasseznaturellement:/*définitiondelaclasseset_char*/
#include"setchar1.h"
set_char::set_char()
{inti;
for(i=0;i<N_CAR_MAX;i++)ens[i]=0;
}
voidset_char::ajoute(unsignedcharc)
{ens[c]=1;
}
intset_char::appartient(unsignedcharc)
{returnens[c];
}
intset_char::cardinal()
{inti,n;
for(i=0,n=0;i<N_CAR_MAX;i++)if(ens[i])n++;
returnn;
}
Ilenvademêmepourleprogrammed’utilisation:/*utilisationdelaclasseset_char*/
#include<cstring>
#include"setchar1.h"
#include<iostream>
usingnamespacestd;
main()
{set_charens;
charmot[81];
cout<<"donnezunmot";
cin>>mot;
inti;
for(i=0;i<strlen(mot);i++)ens.ajoute(mot[i]);
cout<<"ilcontient"<<ens.cardinal()<<"caractèresdifférents";
if(ens.appartient('e'))cout<<"lecaractèreeestprésent\n";
elsecout<<"lecaractèreen'estpasprésent\n";
}
Sil’onavaitdéclarédetypecharlesargumentsdeajouteetappartient,onauraitalorspuaboutirsoitautypeunsignedchar,soitautypesignedchar, selon l’environnementutilisé.Dans le dernier cas, on aurait couru le risque de transmettre à l’une des fonctionsmembrecitéesunevaleurnégative,etpartantd’accéderàl’extérieurdutableauens.
DiscussionLetableauens[N_CHAR_MAX]occupeunoctetparcaractère;chacundecesoctetsneprendquel’unedesvaleurs0ou1;onpourraitéconomiserdel’espacemémoireenprévoyantseulement 1 bit par caractère. Les fonctions membre y perdraient toutefois ensimplicité,ainsiqu’envitesse.
145
Bien entendu, beaucoup d’autres implémentations sont possibles ; c’est ainsi, parexemple,que l’onpourrait fournir auconstructeurunnombremaximald’éléments, etallouerdynamiquementl’emplacementmémoirecorrespondant;toutefois,làencore,onperdrait lebénéficede lacorrespondance immédiateentreuncaractèreet lapositionde son indicateur. Notez toutefois que ce sera la seule possibilité réaliste lorsqu’ils’agiradereprésenterdesensemblesdanslesquelslenombremaximald’élémentsseratrèsgrand.
146
Exercice68
ÉnoncéModifierlaclasseset_charprécédente,demanièreàdisposerdecequel’onnommeun«itérateur»sur lesdifférentsélémentsdel’ensemble.Ils’agitd’unmécanismepermettant d’accéder séquentiellement aux différents éléments. On prévoira troisnouvellesfonctionsmembre:init,quiinitialiseleprocessusd’exploration;prochain,qui fournit lavaleurde l’élément suivant lorsqu’il existe et existe, quiprécise s’ilexisteencoreunélémentnonexploré.
Oncompléteraalorsleprogrammed’utilisationprécédent,demanièrequ’ilaffichelesdifférentscaractèrescontenusdanslemotfourniendonnée.
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,etqu’ilnefautpaschercheràutiliserici.
Comptetenudel’implémentationdenotreclasse,lagestiondumécanismed’itérationnécessite l’emploi d’un pointeur (que nous nommerons courant) sur un élément dutableauens.Nousconviendronsquecourantdésignelepremierélémentdeensnonencoretraitédans l’itération,c’est-à-direnonencorerenvoyépar la fonctionmembresuivant(nousaurionspuadopterlaconventioncontraire,àsavoirquecourantdésigneledernierélémenttraité).
Enoutre, pour faciliter la reconnaissancede la finde l’itération, nousutiliseronsunmembre donnée supplémentaire (fin) valant 0 dans les cas usuels, et 1 lorsqu’aucunélémentneseradisponible(poursuivant).
Lerôledelafonctioninitseradoncdefairepointercourantsurlapremièrevaleurnonnulledeenss’ilenexisteune;danslecascontraire,finseraplacéà1.
La fonction suivant fournira en retour l’élémentpointépar courant lorsqu’il existe (finnonnul)ou lavaleur0dans lecascontraire (il s’agit làd’uneconventiondestinéeàprotéger l’utilisateur ayant appelé cette fonction, alors qu’aucun élément n’était plusdisponible).Danslepremiercas,suivantrechercheraleprochainélémentdel’ensemble(enmodifiantlavaleurdefinlorsqu’untelélémentn’existepas).Notezbienqu’icilafonctionsuivantdoitrenvoyernonpasleprochainélément,maisl’élémentcourant.
147
Enfin, la fonction existe se contentera de renvoyer la valeur de fin puisque cettedernièreindiquel’existenceoul’inexistenced’unélémentcourant.
Voicilanouvelledéfinitiondelaclasseset_char:/*fichierSETCHAR2.H*/
/*déclarationdelaclasseset_char*/
#defineN_CAR_MAX256//onpourraitutiliserUCHAR_MAXdéfini
//dans<climits>
classset_char
{
unsignedcharens[N_CAR_MAX];
//tableaudesindicateurs(présent/absent)
//pourchacundescaractèrespossibles
intcourant;//positioncourantedansletableauens
intfin;//indiquesifinatteinte
public:
set_char();//constructeur
voidajoute(unsignedchar);//ajoutd'unélément
intappartient(unsignedchar);//appartenanced'unélément
intcardinal();//cardinaldel'ensemble
voidinit();//initialisationitération
unsignedcharsuivant();//caractèresuivant
intexiste();
};
Voiciladéfinitiondestroisnouvellesfonctionsmembreinit,suivantetexiste:voidset_char::init()
{courant=0;fin=0;
while((++courant<N_CAR_MAX)&&(!ens[courant]));
//silafindeensestatteinte,courantvautN_CAR_MAX
if(courant>=N_CAR_MAX)fin=1;
}
unsignedcharset_char::suivant()
{if(fin)return0;//aucasoùonseraitdéjàenfindeens
unsignedcharc=courant;//conservationducaractèrecourant
//etrecherchedusuivants'ilexiste
while((++courant<N_CAR_MAX)&&(!ens[courant]));
//silafindeensestatteinte,courantvautN_CAR_MAX
if(courant>=N_CAR_MAX)fin=1;//s'iln'yaplusdecaractère
returnc;
}
intset_char::existe()
{return(!fin);
}
Voicienfinl’adaptationduprogrammed’utilisation:#include<cstring>
#include"setchar2.h"
#include<iostream>
usingnamespacestd;
main()
{set_charens;
charmot[81];
cout<<"donnezunmot";
cin>>mot;
inti;
for(i=0;i<strlen(mot);i++)ens.ajoute(mot[i]);
cout<<"ilcontient"<<ens.cardinal()<<"caractèresdifférents"
<<"quisont:\n";
148
ens.init();//inititérationsurlescaractèresdel'ensemble
while(ens.existe())
cout<<ens.suivant();
}
149
Chapitre8Propriétésdesfonctionsmembre
150
Rappels
SurdéfinitiondesfonctionsmembreetargumentspardéfautIl s’agit simplement de la généralisation aux fonctionsmembre des possibilités déjàoffertesparC++pourlesfonctions«ordinaires».
FonctionsmembreenligneIls’agitégalementdelagénéralisationauxfonctionsmembred’unepossibilitéoffertepour les fonctions ordinaires, avec une petite nuance concernant samise enœuvre ;pourrendre«enligne»unefonctionmembre,onpeut:
soit fournirdirectement ladéfinitiondelafonctiondans ladéclarationmêmedelaclasse ; dans ce cas, le qualificatif inline n’a pas à être utilisé, comme dans cetexemple:classtruc
{...
intfctenlig(int,float)
{définitiondefctenlig}
.....
};
soit procéder comme pour une fonction ordinaire, en fournissant une définition endehors de la déclaration de la classe ; dans ce cas, le qualificatif inline doitapparaître,àlafoisdevantladéclarationetdevantl’en-tête.
Casdesobjetstransmisenargumentsd’unefonctionmembreUnefonctionmembrereçoitimplicitementl’adressedel’objetl’ayantappelé.Mais,enoutre, il est toujours possible de lui transmettre explicitement un argument (ouplusieurs)dutypedesaclasse,oumêmedutyped’uneautreclasse.Danslepremiercas,lafonctionmembreauraaccèsauxmembresprivésdel’argumentenquestion(car,enC++, l’unitéd’encapsulationest laclasseelle-mêmeetnon l’objet).En revanche,dans le second cas, la fonction membre n’aura accès qu’aux membres publics de
151
l’argument.
Un tel argument peut être transmis classiquement par valeur, par adresse ou parréférence.Avec la transmission par valeur, il y a recopie des valeurs desmembresdonnéedansunemplacementlocalàlafonctionappelée.Desproblèmespeuventsurgirdès lors que l’objet transmis en argument contient des pointeurs sur des partiesdynamiques. Ils seront réglés par l’emploi d’un « constructeur par recopie » (voirchapitresuivant).
Bienquecesoitd’unusagepluslimité,unefonctionordinairepeutégalementrecevoirun argument de type classe. Bien entendu, elle n’aura alors accès qu’aux membrespublics de cet argument. (Àmoins d’avoir été déclarée fonction amie, comme on leverradanslechapitrecorrespondant.)
CasdesfonctionsmembrefournissantunobjetenretourUnefonctionmembrepeutfournircommevaleurderetourunobjetdutypedesaclasseou d’un autre type classe (dans ce dernier cas, elle n’accédera bien sûr qu’auxmembrespublicsdel’objetenquestion).Latransmissionpeut,làencore,sefaireparvaleur,paradresseouparréférence.
Latransmissionparvaleurimpliqueunerecopiequiposedonclesmêmesproblèmesqueceuxévoqués ci-dessuspour lesobjets comportant despointeurs surdespartiesdynamiques.Quantaux transmissionsparadresseoupar référence,ellesdoiventêtreutilisées avec beaucoup de précautions, dans lamesure où, dans ce cas, on renvoie(généralement)l’adressed’unobjetallouéautomatiquement,c’est-à-diredontladuréedeviecoïncideaveccelledelafonction.
Autoréférence:lemot-cléthisAu sein d’une fonctionmembre, this représente un pointeur sur l’objet ayant appeléladitefonctionmembre.
FonctionsmembrestatiquesLorsqu’une fonctionmembre a une action indépendante d’un quelconque objet de saclasse, onpeut ladéclarer avec l’attribut static.Dans ce cas, une telle fonctionpeut
152
êtreappelée,sansmentionnerd’objetparticulier,enpréfixantsimplementsonnomdunomdelaclasseconcernée,suividel’opérateurderésolutiondeportée(::).
FonctionsmembreconstantesOn peut déclarer des objets constants (à l’aide du qualificatif const). Dans ce cas,seuleslesfonctionsmembredéclarées(etdéfinies)aveccemêmequalificatif(exemplededéclaration:voidaffiche()const)peuventrecevoir(implicitementouexplicitement)enargumentunobjetconstant.
153
Exercice69
ÉnoncéOnsouhaiteréaliseruneclassevecteur3dpermettantdemanipulerdesvecteursàtroiscomposantes.Onprévoitquesadéclarationseprésenteainsi:
classvecteur3d
{floatx,y,z;//pourles3composantes(cartésiennes)
.....
};
Onsouhaitepouvoirdéclarerunvecteur,soitenfournissantexplicitementses troiscomposantes, soit en en fournissant aucune, auquel cas le vecteur créé posséderatroiscomposantesnulles.Écrireleoulesconstructeurscorrespondants:
a.enutilisantdesfonctionsmembresurdéfinies;
b.enutilisantuneseulefonctionmembre;
c.enutilisantuneseulefonctionenligne.
Il s’agit de simples applications des possibilités de surdéfinition, d’arguments pardéfautetd’écritureenlignedesfonctionsmembres.
a./*déclarationdelaclassevecteur3d*/
classvecteur3d
{floatx,y,z;
public:
vecteur3d();//constructeursansarguments
vecteur3d(float,float,float);//constructeur3composantes
.....
};
/*définitiondesconstructeursdelaclassevecteur3d*/
vecteur3d::vecteur3d()
{x=0;y=0;z=0;
}
vecteur3d::vecteur3d(floatc1,floatc2,floatc3)
{x=c1;y=c2;z=c3;
}
b./*déclarationdelaclassevecteur3d*/
classvecteur3d
{floatx,y,z;
public:
154
vecteur3d(float=0.0,float=0.0,float=0.0);//constructeur(unique)
.....
};
/*définitionduconstructeurdelaclassevecteur3d*/
vecteur3d::vecteur3d(floatc1,floatc2,floatc3)
{x=c1;y=c2;z=c3;
}
On notera toutefois qu’avec ce constructeur il est possible de déclarer un point enfournissant non seulement zéro ou trois composantes,mais éventuellement seulementuneoudeux.Cettesolutionn’estdoncpasrigoureusementéquivalenteàlaprécédente.
c./*déclarationdelaclassevecteur3d*/
classvecteur3d
{floatx,y,z;
public:
//constructeurunique"enligne"
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
.....
};
Ici,iln’yaplusaucunedéfinitiondeconstructeur,puisquecedernierestenligne.
155
Exercice70
ÉnoncéSoituneclassevecteur3ddéfiniecommesuit:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
.....
};
Introduire une fonction membre nommée coincide permettant de savoir si deuxvecteursontlesmêmescomposantes:
a.enutilisantunetransmissionparvaleur;
b.enutilisantunetransmissionparadresse;
c.enutilisantunetransmissionparréférence.
Si v1 et v2 désignent 2 vecteurs de type vecteur3d, comment s’écrit le test decoïncidencedeces2vecteurs,danschacundes3casconsidérés?
Lafonctioncoincideestmembredelaclassevecteur3d;ellerecevradoncimplicitementl’adresseduvecteurl’ayantappelé.Elleneposséderadoncqu’unseulargument, lui-mêmedetypevecteur3d.Noussupposeronsqu’ellefournitunevaleurderetourdetypeint(1pourlacoïncidence,0danslecascontraire).
a.Ladéclarationdecoincidepourraseprésenterainsi:intcoincide(vecteur3d);
Voicicequepourraitêtresadéfinition:intvecteur3d::coincide(vecteur3dv)
{if((v.x==x)&&(v.y==y)&&(v.z==z))return1;
elsereturn0;
}
b.Ladéclarationdecoincidedevient:intcoincide(vecteur3d*);
Etsanouvelledéfinitionpourraitêtre:
156
intvecteur3d::coincide(vecteur3d*adv)
{if((adv->x==x)&&(adv->y==y)&&(adv->z==z)
return1;
elsereturn0;
}
Enutilisantthis, ladéfinitionde coincide pourrait fairemoins de distinction entre sesdeuxarguments(l’unimplicite,l’autreexplicite):
intvecteur3d::coincide(vecteur3d*adv)
{if((adv->x==this->x)&&(adv->y==this->y)&&(adv->z==this->z))
return1;
elsereturn0;
}
c.Ladéclarationdecoincidedevient:intcoincide(vecteur3d&);
Etsanouvelledéfinitionest:intvecteur3d::coincide(vecteur3d&v)
{if((v.x==x)&&(v.y==y)&&(v.z==z))return1;
elsereturn0;
}
Notezquelecorpsdelafonctionestrestélemêmequ’ena.
Voici les trois appels de coincide correspondant respectivement aux trois définitionsprécédentes:
a.v1.coincide(v2);ouv2.coincide(v1);
b.v1.coincide(&v2)ouv2.coincide(&v1);
c.v1.coincide(v2)ouv2.coincide(v1);
DiscussionLa surdéfinition d’opérateur offrira une mise en œuvre plus agréable de ce test decoïncidencededeuxvecteurs.C’estainsiqu’ilserapossibledesurdéfinirl’opérateurde comparaison == (pour la classe vecteur3d) et, partant, d’exprimer ce test sous lasimpleformev1==v2.
157
Exercice71
ÉnoncéSoituneclassevecteur3ddéfiniecommesuit:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
.....
};
Introduire, dans cette classe, une fonction membre nommée normax permettantd’obtenir,parmideuxvecteurs,celuiquialaplusgrandenorme.Onprévoiratroissituations:
a.lerésultatestrenvoyéparvaleurþ;
b. le résultat est renvoyé par référence, l’argument (explicite) étant égalementtransmisparréférenceþ;
c.lerésultatestrenvoyéparadresse,l’argument(explicite)étantégalementtransmisparadresse.
a. La seule difficulté réside dans lamanière de renvoyer la valeur de l’objet ayantappeléunefonctionmembre,àsavoir*this.Voiciladéfinitiondelafonctionnormax(ladéclarationendécouleimmédiatement):vecteur3dvecteur3d::normax(vecteur3dv)
{floatnorm1=x*x+y*y+z*z;
floatnorm2=v.x*v.x+v.y*v.y+v.z*v.z;
if(norm1>norm2)return*this;
elsereturnv;
}
Voiciunexempled’utilisation(onsupposequev1,v2etwsontdetypevecteur3d):w=v1.normax(v2);/*onobtientdanswceluidesdeuxvecteursv1etv2*/
/*ayantlaplusgrandenorme*/
Notez bien que l’affectation ne pose aucun problème ici, puisque notre classe necomporteaucunpointeursurdespartiesdynamiques.
b.Aucun nouveau problème ne se pose. Il suffit demodifier ainsi l’en-tête de notre
158
fonction,sansenmodifierlecorps:vecteur3d&vecteur3d::normax(vecteur3d&v)
Lafonctionnormaxs’utilisecommeprécédemment.
c.Ilfaut,cettefois,adapterenconséquencel’en-têteetlecorpsdelafonction:vecteur3d*vecteur3d::normax(vecteur3d*adv)
{
floatnorm1=x*x+y*y+z*z;
floatnorm2=adv->x*adv->x+adv->y*adv->y+adv->z*adv->z;
if(norm1>norm2)returnthis;
elsereturnadv;
}
Ici,l’utilisationdelafonctionnécessitequelquesprécautions.Envoiciunexemple(v1,v2etwsonttoujoursdetypevecteur3d):
w=*(v1.normax(&v2));
DiscussionEncequiconcernelatransmissiondel’uniqueargumentexplicitedenormax,ilfautnoterqu’il est impossible de la prévoir par valeur, dès lors que normax doit restituer sonrésultat par adresse ou par référence.En effet, dans ce cas, on obtiendrait en retourl’adresseou la référenced’unvecteurallouéautomatiquementauseinde la fonction.Notez qu’un tel problème ne se pose pas pour l’argument implicite (this), car ilcorrespondtoujoursàl’adressed’unvecteur(transmisautomatiquementparréférence),etnonàunevaleur.
Par ailleurs, on ne perdra pas de vue qu’il est rare qu’une fonction puisse renvoyerl’adresse ou la référence d’un objet. Ici, la chose n’est possible que parce que cerésultatn’apasétécréédynamiquementdanslafonction.
159
Exercice72
ÉnoncéRéaliseruneclassevecteur3dpermettantdemanipulerdesvecteursà3composantes(detypefloat).Onyprévoira:
•unconstructeur,avecdesvaleurspardéfaut(0),
•unefonctiond’affichagedes3composantesduvecteur,souslaforme:<composante1,composante2,composante3>
•unefonctionpermettantd’obtenirlasommede2vecteurs;
•unefonctionpermettantd’obtenirleproduitscalairede2vecteurs.
On choisira les modes de transmission les mieux appropriés. On écrira un petitprogrammeutilisantlaclasseainsiréalisée.
La fonctionmembre calculant la sommede deux vecteurs (nous la nommerons somme)reçoit implicitement (par référence) un argument de type vecteur3d. Elle comporteradoncunseulargument,luiaussidetypevecteur3d.Onpeut,apriori,letransmettreparadresse,parvaleurouparréférence.Enfait,latransmissionparadresse,enC++,n’aplus guère de raison d’être, dans lamesure où la transmission par référence fait lamêmechose,moyennantuneécritureplusagréable.
Lechoixdoitdonc se faireentre transmissionparvaleuroupar référence.Lorsqu’ils’agitdetransmettreunobjet(comportantplusieursmembresdonnée),latransmissionpar référence estplus efficace (en tempsd’exécution).Quiplus est, la fonction sommereçoitdéjàimplicitementunvecteurparréférence,desortequ’iln’yaaucuneraisondeluitransmettredifféremmentlesecondvecteur.
Lemêmeraisonnements’appliqueàlafonctiondecalculduproduitscalaire(quenousnommonsprodscal).
Encequiconcernelavaleurderetourdesomme,égalementdetypevecteur3d,iln’estenrevanchepaspossibledelatransmettreparréférence.Eneffet,ce«résultat»(detypevecteur3d) sera créé au sein de la fonction elle-même, ce qui signifie que l’objetcorrespondant sera de classe automatique, donc détruit à la fin de l’exécution de lafonction.Ilfautdoncabsolumententransmettrelavaleur.
160
Voici ce que pourrait être la déclaration de notre classe vecteur3d (ici, seul leconstructeuraétéprévuenligne):
/*déclarationdelaclassevecteur3d*/
classvecteur3d
{
floatx,y,z;
public:
vecteur3d(floatc1=0,floatc2=0,floatc3=0)//constructeur
{x=c1;y=c2;z=c3;
}
vecteur3dsomme(vecteur3d&);//somme(résultatparvaleur)
floatprodscal(vecteur3d&);//produitscalaire
voidaffiche();//affichagecomposantes
};
Voicisadéfinition:/*définitiondelaclassevect3d*/
#include<iostream>
usingnamespacestd;
vecteur3dvecteur3d::somme(vecteur3d&v)
{vecteur3dres;
res.x=x+v.x;
res.y=y+v.y;
res.z=z+v.z;
returnres;
}
floatvecteur3d::prodscal(vecteur3d&v)
{return(v.x*x+v.y*y+v.z*z);
}
voidvecteur3d::affiche()
{cout<<"<"<<x<<","<<y<<","<<z<<">";
}
Voici un petit programme d’essai de la classe vecteur3d, accompagné des résultatsproduitsparsonexécution:
/*programmed'essaidelaclassevecteur3d*/
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
main()
{vecteur3dv1(1,2,3),v2(3,0,2),w;
cout<<"v1=";v1.affiche();cout<<"\n";
cout<<"v2=";v2.affiche();cout<<"\n";
cout<<"w=";w.affiche();cout<<"\n";
w=v1.somme(v2);
cout<<"w=";w.affiche();cout<<"\n";
cout<<"V1.V2="<<v1.prodscal(v2)<<"\n";
}
v1=<1,2,3>
v2=<3,0,2>
w=<0,0,0>
w=<4,2,5>
V1.V2=9
161
Exercice73
ÉnoncéCommentpourrait-onadapter laclassepointcrééedansl’exercice66,pourqu’elledisposed’unefonctionmembrenombre fournissant lenombredepointsexistantàuninstantdonné?
Onpourraitcertes introduireunefonctionmembreclassique.Toutefois,cettesolutionprésenterait l’inconvénient d’obliger l’utilisateur à appliquer une telle fonction à unobjetdetypepoint.Quepenseralorsd’unappeltelquep.compte()(pétantunpoint)pourconnaîtrelenombredepoints?Quiplusest,commentfaireappelàcomptes’iln’existeaucunpoint?
La solution la plus agréable consiste à faire de compte une fonction statique. On ladéclareradoncainsi:
staticintcompte();
Voicisadéfinition:intpoint::compte()
{returnnb_pts;
}
Voiciunexempled’appeldecompteauseind’unprogrammedanslequellaclassepointaétédéclarée:
cout<<"ilya"<<point::compte()<<"points\n";
162
Chapitre9Construction,destructionetinitialisation
desobjets
163
Rappels
AppelsduconstructeuretdudestructeurDans tous les cas (objets statiques, automatiques ou dynamiques), s’il y a appel duconstructeur, celui-ci a lieu après l’allocation de l’emplacementmémoire destiné àl’objet.Demême,s’ilexisteundestructeur,cedernierestappeléavantlalibérationdel’espacemémoireassociéàl’objet.
LesobjetsautomatiquesetstatiquesLesobjetsautomatiquessontcréésparunedéclarationsoitdansunefonction,soitauseind’unbloc.Ilssontcréésaumomentdel’exécutiondeladéclaration(laquelle,enC++,peutapparaîtren’importeoùdansunprogramme).Ilssontdétruitslorsqu’onsortdelafonctionoudubloc.
Lesobjetsstatiquessontcréésparunedéclarationsituéeendehorsdetoutefonctionouparunedéclarationprécédéedumot-cléstatic(dansunefonctionoudansunbloc).Ils sont créés avant l’entrée dans la fonction main et détruits après la fin de sonexécution.
LesobjetstemporairesL’appel explicite, au sein d’une expression, du constructeur d’un objet provoque lacréation d’un objet temporaire (on n’a pas accès à son adresse) qui pourra êtreautomatiquementdétruit dèsqu’il ne seraplusutile.Par exemple, si une classe pointpossèdeleconstructeurpoint(float,float)etsiaestdetypepoint,nouspouvonsécrire:
a=point(1.5,2.25);
Ne confondez pas une telle affectation avec une initialisation d’un objet lors de sadéclaration.
Cetteinstructionprovoquelacréationd’unobjettemporairedetypepoint(avecappelduconstructeurconcerné),suiviedel’affectationdecetobjetàa.
164
LesobjetsdynamiquesIlssontcréésparl’opérateurnew,auquelondoitfournir,lecaséchéant,lesvaleursdesargumentsdestinésàunconstructeur,commedanscetexemple(quisupposequ’ilexisteuneclassepointpossédantleconstructeurpoint(float,float)):
point*adp;
.....
adp=newpoint(2.5,5.32);//créationd'unobjetdetypepoint,par
//appeld'unconstructeuràdeuxarguments
Commepourlesvariablesordinaires,onpeutpréciserunnombred’objets,mais,desrestrictions apparaissent alors, qui portent sur le constructeur qu’il est possibled’appeler(voyezci-aprèslarubrique«tableauxd’objets»).
L’accès aux membres d’un objet dynamique est réalisé comme pour les variablesordinaires. Par exemple, si point possède une méthode nommée affiche, on pourral’appelerpar(*adp).affiche()ouencoreparadp->affiche().
Lesobjetsdynamiquesn’ontpasdeduréedeviedéfinieapriori.Ilssontdétruitsàlademande en utilisant l’opérateur delete comme dans : delete adr. (Dans le cas d’untableaud’objets,lasyntaxeseradifférente,voirunpeuplusloin.)
Constructiond’objetscontenantdesobjetsmembreUneclassepeutposséderunmembredonnéequiestlui-mêmedetypeclasse.Envoiciunexemple.Sinousavonsdéfini:
classpoint
{floatx,y;
public:
point(float,float);
.....
};
nouspouvonsdéfiniruneclassepointcol,dontunmembreestdetypepoint:classpointcol
{pointp;
intcouleur;
public:
pointcol(float,float,int);
.....
};
Danscecas,lorsdelacréationd’unobjetdetypepointcol,ilyauratoutd’abordappeld’unconstructeurdepointcol,puisappeld’unconstructeurdepoint;cedernierrecevra
165
lesargumentsqu’onauramentionnésdansl’en-têtedeladéfinitionduconstructeurdepointcol.Parexemple,avec:
pointcol::pointcol(floatabs,floatord,intcoul):p(abs,ord)
{...
}
onprécisequeleconstructeurdumembreprecevraenargumentlesvaleursabsetord.
Si l’en-tête de pointcol nementionnait rien concernant p, il faudrait alors que le typepointpossèdeunconstructeursansargumentpourquecelasoitcorrect.
Initialisationd’objetsEnC++,onparled’initialisationd’unobjetdansdessituationstellesque:
pointa=5;//ildoitexisterunconstructeuràunargumentdetypeentier
pointb=a;//ildoitexisterunconstructeuràunargumentdetypepoint
Ledeuxièmecas correspondà l’initialisationd’unobjet à l’aided’unautreobjet demêmetype,qu’onnommeparfois«initialisationparrecopie».L’opérationestréaliséeparappeldecequel’onnommeun«constructeurparrecopie».
Mais il existe d’autres situations, plus courantes, qui mettent en œuvre un telmécanisme,àsavoir:
latransmissiond’unobjetparvaleurenargumentd’appeld’unefonctionþ;
latransmissiond’unobjetparvaleurenvaleurderetourd’unefonction.
Dans toutes ces situations d’initialisation par recopie, le constructeur par recopieemployéest:
soitunconstructeurdelaformetype(type&)outype(consttype&)s’ilenexisteun;cedernierdoit alorsprendre encharge la recopiede tous lesmembresde l’objet, ycomprisceuxquisontdesobjets;ilpeutcependants’appuyersurlespossibilitésdetransmissiond’informationentreconstructeurs,présentéeauparavant;
La transmission par référence est obligatoire ici. D’autre part, le fait d’utiliserl’attribut const permet d’appliquer le constructeur à un objet constant ou à uneexpression ; rappelons que, dans ce cas, il y création d'un objet temporaire dont ontransmetlaréférenceauconstructeur.
soit, dans le cas contraire, ce que l’on nomme un « constructeur de recopie par
166
défaut»,quirecopielesdifférentsmembresdel’objet.Sicertainsdecesmembressont eux-mêmes des objets, la recopie sera réalisée par appel de son propreconstructeur par recopie (qui pourra être soit un constructeur par défaut, soit unconstructeurdéfinidanslaclassecorrespondante).
Dans les très anciennes versions (antérieures à 2.0), la recopie se faisait de façonglobale;autrementdit,la«valeur»d’unobjetétaitreportée(bitparbit)dansunautre,sans tenir compte de sa structure. Les choses étaient alors relativement peusatisfaisantes...
Lestableauxd’objetsSi point est un type objet possédant un constructeur sans argument (ou, situationgénéralementdéconseillée,sansconstructeur),ladéclaration:
pointcourbe[20];
crée un tableau courbe de 20 objets de type point en appelant, le cas échéant, leconstructeurpourchacund’entreeux.Noteztoutefoisquecourben’estpaslui-mêmeunobjet.
Demême:point*adcourbe=newpoint[20];
allouel’emplacementmémoirenécessaireàvingtobjets(consécutifs)detypepoint,enappelant,lecaséchéant,leconstructeurpourchacund’entreeux,puisplacel’adressedupremierdansadcourbe.
La destruction d’un tableau d’objets se fait en utilisant une syntaxe particulière del’opérateurnew.Parexemple,pourdétruireletableauprécédent,onécrira:
delete[]adcourbe;
En théorie, onpeut compléter ladéclarationd’un tableaud’objetsparun initialiseurcontenantune listedevaleurs (ellespeuventéventuellementêtrede typesdifférents).Chaquevaleurestalorstransmiseàunconstructeurapproprié.Cesvaleursdoiventêtreconstantespour les tableauxdeclassestatique ; ilpeuts’agird’expressionspour lestableauxdeclasseautomatique.Onpeutnepaspréciserdevaleurspour lesderniersélémentsdutableau.
167
Onnoteraqu’untelinitialiseurnepeutpasêtreutiliséavecdestableauxdynamiques(ilenvademêmepourlestableauxordinaires!).
168
Exercice74
ÉnoncéCommentconcevoirletypeclassechosedefaçonquecepetitprogramme:
main()
{chosex;
cout<<"bonjour\n";
}
fournisselesrésultatssuivants:créationobjetdetypechose
bonjour
destructionobjetdetypechose
Quefourniraalorsl’exécutiondeceprogramme(utilisantlemêmetypechose):main()
{chose*adc=newchose
}
Ilsuffitdeprévoir,dansleconstructeurdechose,l’instruction:cout<<"créationobjetdetypechose\n";
et,dansledestructeur,l’instruction:cout<<"destructionobjetdetypechose\n";
Danscecas,ledeuxièmeprogrammefournirasimplementàl’exécution(puisqu’ilcréeunobjetdetypechosesansjamaisledétruire):
créationobjetdetypechose
169
Exercice75
ÉnoncéQuels seront les résultats fournis par l’exécution du programme suivant (ici, ladéclaration de la classe demo, sa définition et le programme d’utilisation ont étéregroupésenunseulfichier):
#include<iostream>
usingnamespacestd;
classdemo
{intx,y;
public:
demo(intabs=1,intord=0)//constructeurI(0,1ou2arguments)
{x=abs;y=ord;
cout<<"constructeurI:"<<x<<""<<y<<"\n";
}
demo(demo&);//constructeurII(parrecopie)
~demo();//destructeur
};
demo::demo(demo&d)//oudemo::demo(constdemo&d)
{cout<<"constructeurII(recopie):"<<d.x<<""<<d.y<<"\n";
x=d.x;y=d.y;
}
demo::~demo()
{cout<<"destruction:"<<x<<""<<y<<"\n";
}
main()
{
voidfct(demo,demo*);//protofonctionindépendantefct
cout<<"débutmain\n";
demoa;
demob=2;
democ=a;
demo*adr=newdemo(3,3);
fct(a,adr);
demod=demo(4,4);
c=demo(5,5);
cout<<"finmain\n";
}
voidfct(demod,demo*add)
{cout<<"entréefct\n";
deleteadd;
cout<<"sortiefct\n";
}
Voicilesrésultatsquefournitleprogramme(ici,nousavonsutilisélecompilateurC++BuilderX;cepointn’ayantd’importancequepourlesobjetstemporaires,danslecasdes implémentations qui ne respecteraient pas la norme quant à l’instant de leurdestruction).
170
La plupart des lignes sont assorties d’explications complémentaires présentéesconventionnellement sous forme de commentaires et qui précisent les instructionsconcernées.
débutmain
constructeurI:10/*demoa;*/
constructeurI:20/*demob=2;*/
constructeurII(recopie):10/*democ=a;*/
constructeurI:33/*newdemo(3,3)*/
constructeurII(recopie):10/*recopiedelavaleurdeadansfct(a,...)*/
/*cequicréeunobjettemporaire*/
entréefct
destruction:33/*deleteadd;(dansfct)*/
sortiefct
destruction:10/*destructionobjettemporairecréépour*/
/*l'appeldefct*/
constructeurI:44/*demod=demo(4,4)*/
constructeurI:55/*c=demo(5,5)(contructionobjettemporaire)*/
destruction:55/*destructionobjettemporaireprécédent*/
finmain
destruction:44/*destructiond*/
destruction:55/*destructionc*/
destruction:20/*destructionb*/
destruction:10/*destructiona*/
Notezbienquel’affectationc=demo(5,5)entraînelacréationd’unobjettemporaireparappelduconstructeurdedemo (arguments5et5) ; cetobjet est ensuite affecté à a. Onconstated’ailleursquecetobjetesteffectivementdétruitaussitôtaprès.Maisilexistecertaines implémentationsquine respectentpas lanormeetoùcelapeut seproduireplustard.
Parailleurs,l’appeldefctaentraînélaconstructiond’unobjettemporaire,parappeldu constructeur par recopie. Cet objet est ici libéré dès la sortie de la fonction. Làencore,danscertainesimplémentations,celapeutseproduireplustard.
171
Exercice76
ÉnoncéCréeruneclassepointnecontenantqu’unconstructeursansarguments,undestructeuretunmembredonnéeprivéreprésentantunnumérodepoint(lepremiercrééporteralenuméro1,lesuivantlenuméro2...).Leconstructeurafficheralenumérodupointcrééetledestructeurafficheralenumérodupointdétruit.Écrireunpetitprogrammed’utilisationcréantdynamiquementuntableaude4pointsetledétruisant.
Pourpouvoirnuméroternospoints,ilnousfautpouvoircompterlenombredefoisoùleconstructeuraétéappelé,cequinouspermettrabiend’attribuerunnumérodifférentàchaquepoint.Pourcefaire,nousdéfinissons,auseindelaclassepoint,unmembredonnéestatiquenb_points.Ici,ilseraincrémentéparleconstructeurmaisledestructeurn’aurapasd’actionsurlui.Commetoutmembrestatique,nb_pointsdevraêtreinitialisé.
Voici la déclaration (définition) de notre classe, accompagnée du programmed’utilisationdemandé:
#include<iostream>
usingnamespacestd;
classpoint
{intnum;
staticintnb_points;
public:
point()
{num=++nb_points;
cout<<"créationpointnuméro:"<<num<<"\n";
}
~point()
{cout<<"Destructionpointnuméro:"<<num<<"\n";
}
};
intpoint::nb_points=0;//initialisationobligatoire
main()
{point*adcourb=newpoint[4];
delete[]adcourb;
}
Àtitreindicatif,voicilesrésultatsfournisparceprogramme:créationpointnuméro:1
créationpointnuméro:2
créationpointnuméro:3
créationpointnuméro:4
Destructionpointnuméro:4
Destructionpointnuméro:3
Destructionpointnuméro:2
Destructionpointnuméro:1
172
Exercice77
Énoncé1. Réaliser une classe nommée set_int permettant de manipuler des ensembles de
nombres entiers. On devra pouvoir réaliser sur un tel ensemble les opérationsclassiques suivantes : lui ajouter un nouvel élément, connaître son cardinal(nombred’éléments),savoirsiunentierdonnéluiappartient.
Ici, on conservera les différents éléments de l’ensemble dans un tableau allouédynamiquement par le constructeur.Un argument (auquel on pourra prévoir unevaleurpardéfaut)luipréciseralenombremaximald’élémentsdel’ensemble.
2.Écrire,enoutre,unprogramme(main)utilisantlaclasseset_intpourdéterminerlenombred’entiersdifférentscontenusdans20entierslusendonnées.
3. Que faudrait-il faire pour qu’un objet du type set_int puisse être transmis parvaleur, soit comme argument d’appel, soit comme valeur de retour d’unefonction?
N.B. Le chapitre 17 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.
1.Ladéclarationdelaclassedécouledel’énoncé:/*fichierSETINT1.H*/
/*déclarationdelaclasseset_int*/
classset_int
{
int*adval;//adressedutableaudesvaleurs
intnmax;//nombremaxid'éléments
intnelem;//nombrecourantd'éléments
public:
set_int(int=20);//constructeur
~set_int();//destructeur
voidajoute(int);//ajoutd'unélément
intappartient(int);//appartenanced'unélément
intcardinal();//cardinaldel'ensemble
};
Lemembredonnéeadvalestdestinéàpointersurletableaud’entiersquiseraallouéparle constructeur. Lemembre nmax représentera la taille de ce tableau, tandis que nelemfournira lenombreeffectifd’entiersstockésdanscetableau.Cesentiersseront,cettefois, rangésdans l’ordreoù ils seront fournis à ajoute, et nonplus à un emplacement
173
prédéterminé, comme nous l’avions fait pour les caractères (dans les exercices duchapitreprécédent).
Commelacréationd’unobjetentraîneiciuneallocationdynamiqued’unemplacementmémoire, il est raisonnable de prévoir la libération de cet emplacement lors de ladestructiondel’objet;cetteopérationdoitdoncêtrepriseenchargeparledestructeur,d’oùlaprésencedecettefonctionmembre.
Voiciladéfinitiondenotreclasse:#include"setint1.h"
set_int::set_int(intdim)
{adval=newint[nmax=dim];//allocationtableaudevaleurs
nelem=0;
}
set_int::~set_int()
{deleteadval;//libérationtableaudevaleurs
}
voidset_int::ajoute(intnb)
{//onexaminesinbappartientdéjààl'ensemble
//enutilisantlafonctionmembreappartient
//s'iln'yappartientpasetsil'ensemblen'estpasplein
//onl'ajoute
if(!appartient(nb)&&(nelem<nmax))adval[nelem++]=nb;
}
intset_int::appartient(intnb)
{inti=0;
//onexaminesinbappartientdéjààl'ensemble
//(sicen'estpaslecas,ivaudraneleenfindeboucle)
while((i<nelem)&&(adval[i]!=nb))i++;
return(i<nelem);
}
intset_int::cardinal()
{returnnelem;
}
Notez que, dans la fonction membre ajoute, nous avons utilisé la fonction membreappartient pour vérifier que le nombre à ajouter ne figurait pas déjà dans notreensemble.
Parailleurs,l’énoncéneprévoitrienpourlecasoùl’onchercheàajouterunélémentàunensembledéjà«plein»; ici,nousnoussommescontentédenerienfairedanscecas. Dans la pratique, il faudrait soit prévoir un moyen pour que l’utilisateur soitprévenudecettesituation,soit,mieux,prévoirautomatiquementl’agrandissementdelazonedynamiqueassociéeàl’ensemble.
2.Voicileprogrammed’utilisationdemandé:#include"setint1.h"
#include<iostream>
usingnamespacestd;
main()
{set_intens;
cout<<"donnez20entiers\n";
inti,n;
for(i=0;i<20;i++)
{cin>>n;
ens.ajoute(n);
174
}
cout<<"ilya:"<<ens.cardinal()<<"entiersdifférents\n";
}
Àtitreindicatif,voiciunexempled’exécution:donnez20entiers
02528518207255450045
ilya:7entiersdifférents
3. Telle qu’est actuellement prévue notre classe set_int, si un objet de ce type esttransmisparvaleur, soit enargumentd’appel, soit en retourd’une fonction, ilyaappelduconstructeurde recopiepardéfaut.Orcedernier secontented’effectuerunecopiedesmembresdonnéedel’objetconcerné,cequisignifiequ’onseretrouveenprésencededeuxobjetscontenantdeuxpointeursdifférentssurunmêmetableaud’entiers.Unproblèmevadoncseposer,dèslorsquel’objetcopiéseradétruit(cequipeutseproduiredèslasortiedelafonction);eneffet,danscecas,letableaudynamique d’entiers sera détruit, alors même que l’objet d’origine continuera à«pointer»dessus.
Indépendamment de cela, d’autres problèmes similaires pourraient se poser si lafonctionétaitamenéeàmodifierlecontenudel’ensemble;eneffet,onmodifieraitalorsletableaud’entiersoriginal,choseàlaquelleonnes’attendpasdanslecasdelatransmissionparvaleur.
Pourréglercesproblèmes,ilestnécessairedemunirnotreclassed’unconstructeurpar recopie approprié, c’est-à-dire tenant compte de la « partie dynamique » del’objet(onparleparfoisde«copieprofonde»).Pourcefaire,onalloueunsecondemplacement pour un tableau d’entiers, dans lequel on recopie les valeurs dupremierensemble.Naturellement,ilnefautpasoublierdeprocéderégalementàlarecopiedesmembresdonnée,puisquecelle-cin’estplusassuréeparleconstructeurde recopie par défaut (lequel n’est plus appelé, dès lors qu’un constructeur parrecopieaétédéfini).
Nousajouteronsdoncdansladéclarationdenotreclasse:set_int(set_int&);//constructeurparrecopie
Et,danssadéfinition:set_int::set_int(set_int&e)//ouset_int::set_int(constset_int&e)
{
adval=newint[nmax=e.nmax];//allocationnouveautableau
nelem=e.nelem;
inti;
for(i=0;i<nelem;i++)//copieancientableaudansnouveau
adval[i]=e.adval[i];
}
Discussion
175
La surdéfinition du constructeur par recopie est quasiment indispensable pour touteclasse comportant un pointeur sur une partie dynamique. En l’état actuel de C++, iln’estpaspossibled’interdirelatransmissionparvaleurd’unobjetquin’enposséderaitpas ! Et il ne semble pas raisonnable de livrer à un « client » un tel objet, en luidemandant de ne jamais le transmettre par valeur ! Cela signifie que la plupart desclasses«intéressantes»devrontdéfiniruntelconstructeurparrecopie.
Nous verrons que lesmêmes réflexions s’appliqueront à l’affectation entre objets etqu’elles conduiront à la conclusion que la plupart des classes intéressantes devrontredéfinirl’opérateurd’affectation.
176
Exercice78
ÉnoncéModifier l’implémentation de la classe précédente (avec son constructeur parrecopie)defaçonquel’ensembled’entierssoitmaintenantreprésentéparune listechaînée (chaqueentierest rangédansunestructurecomportantunchampdestinéàcontenir un nombre et un champ destiné à contenir un pointeur sur la structuresuivante).L’interfacedelaclasse(lapartiepubliquedesadéclaration)devraresterinchangée, ce qui signifie qu’un client de la classe continuera à l’employer de lamêmefaçon.
Comme nous le suggère l’énoncé, nous allons donc définir une structure que nousnommeronsélément:
structnoeud
{intvaleur;//valeurd'unélémentdel'ensemble
noeud*suivant;//pointeursurlenœudsuivantdelaliste
};
Notrestructurenoeudpeutêtredéfinie indifféremmentdans ladéclarationde laclasseset_intouendehors.
Encequiconcernelesmembresdonnéeprivésdelaclasse,nousneconserveronsquenelem qui, bien que non indispensable, nous évitera de parcourir toute la liste pourdéterminerlecardinaldenotreensemble.
Enrevanche,nousyintroduironsunpointeurnommédebut,destinéàcontenirl’adressedupremierélémentdelaliste,s’ilexiste(audépart,ilserainitialiséàNULL).
Encequiconcerneleconstructeurdeset_int,nousluiconserveronssonargument(detypeint),bienqu’iciiln’aitplusaucunintérêt,etceladanslebutdenepasmodifierl’interfacedenotreclasse(commeledemandaitl’énoncé).
Voicidonclanouvelledéclarationdenotreclasse:/*fichierSETINT3.H*/
/*déclarationdelaclasseset_int*/
structnoeud
{
intvaleur;//valeurd'unélémentdel'ensemble
noeud*suivant;//pointeursurlenœudsuivantdelaliste
};
classset_int
177
{
noeud*debut;//pointeursurledébutdelaliste
intnelem;//nombrecourantd'éléments
public:
set_int(int=20);//constructeur(argumentinutileici)
set_int(set_int&);//constructeurparrecopie
~set_int();//destructeur
voidajoute(int);//ajoutd'unélément
intappartient(int);//appartenanced'unélément
intcardinal();//cardinaldel'ensemble
};
Ladéfinitiondunouveauconstructeurneprésentepasdedifficulté.Lafonctionmembreajoute réalise une classique insertion d’un noeud en début de liste et incrémente lenombre d’éléments de l’ensemble. La fonction appartient effectue une exploration deliste tant qu’elle n’a pas trouvé la valeur concernée ou atteint la fin de la liste. Enrevanche,lenouveauconstructeurparrecopiedoitrecopierlalistechaînée.Ilréaliseàlafoisuneexplorationdelalisted’origineetuneinsertiondanslalistecopiée.Quantaudestructeur, ildoitmaintenant libérersystématiquement tous lesemplacementsdesdifférentsnœudscrééspourlaliste.
Voicilanouvelledéfinitiondenotreclasse:#include<stdlib.h>//pourNULL
#include"setint3.h"
set_int::set_int(intdim)//dimestconservépourlacompatibilité
//avecl'ancienneclasse
{debut=NULL;
nelem=0;
}
set_int::set_int(set_int&e)//ou:set_int::set_int(constset_int&e)
{nelem=e.nelem;
//créationd'unenouvellelisteidentiqueàl'ancienne
noeud*adsource=e.debut;
noeud*adbut;
debut=NULL;
while(adsource)
{adbut=newnoeud;//créationnouveaunœud
adbut->valeur=adsource->valeur;//copievaleur
adbut->suivant=debut;//insertionnouveaunœud
debut=adbut;//dansnouvelleliste
adsource=adsource->suivant;//nœudsuivantancienneliste
}
}
set_int::~set_int()
{noeud*adn;
noeud*courant=debut;
while(courant)
{adn=courant;//libérationdetous
courant=courant->suivant;//lesnœuds
deleteadn;//delaliste
}
}
voidset_int::ajoute(intnb)
{if(!appartient(nb))//sinbn'appartientpasàlaliste
{noeud*adn=newnoeud;//onl'ajouteendébutdeliste
adn->valeur=nb;
adn->suivant=debut;
debut=adn;
178
nelem++;
}
}
intset_int::appartient(intnb)
{noeud*courant=debut;
//attentionàl'ordredesdeuxconditions
while(courant&&(courant->valeur!=nb))courant=courant->suivant;
return(courant!=NULL);
}
intset_int::cardinal()
{returnnelem;
}
Notez que le programme d’utilisation proposé dans l’exercice 26 reste valable ici,puisquenousn’avonsprécisémentpasmodifiél’interfacedenotreclasse.
Par ailleurs, le problème évoqué à propos de l’ajout d’un élément à un ensemble«plein»neseposeplusici,comptetenudelanouvelleimplémentationdenotreclasse(hormisunéventuelmanquedemémoire).
179
Exercice79
ÉnoncéModifierlaclasseset_intprécédente(implémentéesouslaformed’unelistechaînée,avec ou sans son constructeur par recopie) pour qu’elle dispose de ce que l’onnommeun« itérateur» sur lesdifférents élémentsde l’ensemble.Rappelonsqu’ils’agitd’unmécanismepermettantd’accéderséquentiellementauxdifférentsélémentsdel’ensemble.Onprévoiratroisnouvellesfonctionsmembre:init,pourinitialiserleprocessusd’itérationþ;prochain,pour fournir l’élémentsuivant lorsqu’ilexisteetexiste,pourtesters’ilexisteencoreunélémentnonexploré.
On complétera alors le programme d’utilisation précédent (en fait, celui del’exercice 26), de manière qu’il affiche les différents entiers contenus dans lesvaleursfourniesendonnée.
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.D’autre part, cet exercice sera plus profitable s’il est traité après l’exercice duchapitre3quiproposaitl’introductiond’untelitérateurdansuneclassereprésentantdes ensembles de caractères (mais dont l’implémentation était différente del’actuelleclasse).
Ici, la gestion du mécanisme d’itération nécessite l’emploi d’un pointeur (que nousnommeronscourant) surunnœuddenotre liste.Nousconviendronsqu’ilpointesur lepremier élément non encore traité dans l’itération, c’est-à-dire dont la valeurcorrespondanten’apasencoreétérenvoyéeparlafonctionprochain. Iln’estpasutile,ici,deprévoirunmembredonnéepourindiquersilafindelisteaétéatteinte;eneffet,aveclaconventionadoptée,ilnoussuffitdetesterlavaleurdecourant(quiseraégaleàNULL,lorsquel’onseraenfindeliste).
Lerôledelafonctioninitselimiteàl’initialisationdecourantàlavaleurdupointeursurledébutdelaliste(debut).
La fonction suivant fournira en retour la valeur entière associée au nœud pointé parcourantlorsqu’ilexiste(courantdifférentdeNULL)oulavaleur0danslecascontraire(ils’agit, làencore,d’uneconventiondestinéeàprotéger l’utilisateurayantappelécettefonction alors que la fin de liste était déjà atteinte et, donc, qu’aucun élément de
180
l’ensemblen’étaitdisponible).Deplus,danslepremiercas(usuel),lafonctionsuivantactualiseralavaleurdecourant,demanièrequ’ilpointesurlenœudsuivant.
Enfin,lafonctionexisteexaminerasimplementlavaleurdedebutpoursavoirs’ilexisteencoreunélémentàtraiter.
Voiciladéclarationcomplètedenotrenouvelleclasse:/*fichierSETINT4.H*/
/*déclarationdelaclasseset_int*/
structnoeud
{intvaleur;//valeurd'unélémentdel'ensemble
noeud*suivant;//pointeursurlenœudsuivantdelaliste
};
classset_int
{noeud*debut;//pointeursurledébutdelaliste
intnelem;//nombrecourantd'éléments
noeud*courant;//pointeursurnœudcourant
public:
set_int(int=20);//constructeur
set_int(set_int&);//constructeurparrecopie
~set_int();//destructeur
voidajoute(int);//ajoutd'unélément
intappartient(int);//appartenanced'unélément
intcardinal();//cardinaldel'ensemble
voidinit();//initialisationitération
intprochain();//entiersuivant
intexiste();//testfinliste
};
Voiciladéfinitiondestroisnouvellesfonctionsmembreinit,suivantetexiste:voidset_int::init()
{courant=debut;
}
intset_int::prochain()
{if(courant)
{intval=courant->valeur;
courant=courant->suivant;
returnval;
}
elsereturn0;//parconvention
}
intset_int::existe()
{return(courant!=NULL);
}
Voicilenouveauprogrammed’utilisationdemandé:/*utilisationdelaclasseset_int*/
#include"setint1.h"
#include<iostream>
usingnamespacestd;
main()
{set_intens;
cout<<"donnez20entiers\n";
inti,n;
for(i=0;i<20;i++)
{cin>>n;
ens.ajoute(n);
}
cout<<"ilya:"<<ens.cardinal()<<"entiersdifférents\n";
cout<<"Cesont:\n";
181
ens.init();
while(ens.existe())
cout<<ens.prochain()<<"";
}
Àtitreindicatif,voiciunexempled’exécution:donnez20entiers
02154120214512021452
ilya:5entiersdifférents
Cesont:
45120
182
Exercice80
ÉnoncéQuelsserontlesrésultatsfournisparl’exécutiondeceprogramme:
#include<iostream>
usingnamespacestd;
classpoint
{intx,y;
public:
point()
{x=0;y=0;
cout<<"**constructeur0argument\n";
}
point(intabs)
{x=abs;y=0;
cout<<"**constructeur1argument\n";
}
point(intabs,intord)
{x=abs;y=ord;
cout<<"**constructeur2arguments\n";
}
point(point&p)
{x=p.x;y=p.y;
cout<<"**constructeurparrecopie\n";
}
voidaffiche()
{cout<<"pointdecoordonnees:"<<x<<""<<y<<"\n";
}
};
main()
{pointa(10,20);
pointb(30,40);
pointcourbe[6]={4,a,0,b};
for(inti=0;i<6;i++)courbe[i].affiche();
}
**constructeur2arguments//construction(classique)dea
**constructeur2arguments//construction(classique)deb
**constructeur1argument//constructioncourbe[0]
**constructeurparrecopie//constructioncourbe[1]
**constructeur1argument//constructioncourbe[2]
**constructeurparrecopie//constructioncourbe[3]
**constructeur0argument//constructioncourbe[4]
**constructeur0argument//constructioncourbe[5]
pointdecoordonnees:40
pointdecoordonnees:1020
pointdecoordonnees:00
pointdecoordonnees:3040
pointdecoordonnees:00
pointdecoordonnees:00
Aprèslaconstructionclassiquedespointsaetb,ilyacréationd’untableaude6objetsdetypepoint.Lesquatrepremierspointssontconstruitspar:
183
appeld’unconstructeuràunargumentdetypeint(valeur4)pourlepremierpointþ;
appeld’unconstructeuràunargumentdetypepoint(valeura)pourledeuxièmeþ;
appeld’unconstructeuràunargumentdetypeint(valeur0)pourletroisièmeþ;
appeld’unconstructeuràunargumentdetypepoint(valeurb)pourlequatrième.
Les deux derniers points du tableau ne disposent pas d’initialiseur ; ils sont doncconstruitsparappeld’unconstructeursansargument.
184
Chapitre10Lesfonctionsamies
185
Rappels
EnC++, l’unité de protection est la classe, et non pas l’objet. Cela signifie qu’unefonctionmembre d’une classe peut accéder à tous lesmembres privés de n’importequel objet de sa classe. En revanche, ces membres privés restent inaccessibles àn’importe quelle fonctionmembre d’une autre classe ou à n’importe quelle fonctionindépendante.
Lanotiondefonctionamie,ouplusexactementde«déclarationd’amitié»,permetdedéclarerdansuneclasselesfonctionsquel’onautoriseàaccéderàsesmembresprivés(donnéesoufonctions).Ilexisteplusieurssituationsd’amitié.
Fonctionindépendante,amied’uneclasseAclassA
{
......
friend---fct(-----);
.....
}
LafonctionfctayantleprototypespécifiéestautoriséeàaccéderauxmembresprivésdelaclasseA.
Fonctionmembred’uneclasseB,amied’uneautreclasseA
classA
{
.....
friend---B:fct(-----);
.....
};
La fonction fct, membre de la classe B, ayant le prototype spécifié, est autorisée àaccéderauxmembresprivésdelaclasseA.
Pourqu’ilpuissecompilerconvenablementladéclarationdeA,doncenparticulier ladéclarationd’amitié relativeàfct, lecompilateurdevraconnaître ladéclarationdeB(maispasnécessairementsadéfinition).
Généralement,lafonctionmembrefctposséderaunargumentouunevaleurderetourdetypeA(cequijustifierasadéclarationd’amitié).Pourcompilersadéclaration(auseindeladéclarationdeA),ilsuffiraaucompilateurdesavoirqueAestuneclasse;sisa
186
déclarationn’estpasconnueàceniveau,onpourrasecontenterde:classA;
En revanche, pour compiler la définition de fct, le compilateur devra posséder lescaractéristiquesdeA,doncdisposerdesadéclaration.
Touteslesfonctionsd’uneclasseBsontamiesd’uneautreclasseADans ce cas, plutôt que d’utiliser autant de déclarations d’amitié que de fonctionsmembre, on utilise (dans la déclaration de la classe A) la déclaration (globale)suivante:
friendclassB;
PourcompilerladéclarationdeA,onpréciserasimplementqueBestuneclassepar:classB;
QuantàladéclarationdelaclasseB,ellenécessiteragénéralement(dèsqu’unedesesfonctions membre possédera un argument ou une valeur de retour de type A) ladéclarationdelaclasseA.
187
Exercice81
ÉnoncéSoitlaclassepointsuivante:
classpoint
{intx,y;
public:
point(intabs=0,intord=0)
{x=abs;y=ord;
}
};
Écrire une fonction indépendante affiche, amie de la classe point, permettantd’afficher les coordonnées d’un point. On fournira séparément un fichier sourcecontenantlanouvelledéclaration(définition)depointetunfichiersourcecontenantladéfinitiondelafonctionaffiche.Écrireunpetitprogramme(main)quicréeunpointde classe automatique et un point de classe dynamique et qui en affiche lescoordonnées.
Nous devons donc réaliser une fonction indépendante, nommée affiche, amie de laclassepoint. Une telle fonction, contrairement à une fonctionmembre, ne reçoit plusd’argument implicite ; affiche devra donc recevoir un argument de type point. Sonprototypeseradoncdelaforme:
voidaffiche(point);
sil’onsouhaitetransmettreunpointparvaleur,oudelaforme:voidaffiche(point&);
si l’onsouhaite transmettreunpointpar référence. Ici,nouschoisironscettedernièrepossibilitéet,commeaffiche n’a aucune raisondemodifier lesvaleursdu point reçu,nousleprotégeronsàl’aideduqualificatifconst:
voidaffiche(constpoint&);
Lequalificatifconstpermetd’appliquerlafonctionafficheàunobjetconstant.Maisellepourraégalementêtreappliquéeàuneexpressiondetypepoint,voireàuneexpressiond’untypesusceptibled’êtreconvertiimplicitementenunpoint(voirlechapitrerelatif
188
aux conversions définies par l’utilisateur). Ce dernier aspect ne constitue plusnécessairementunavantage!
Manifestement,affiche devrapouvoir accéderauxmembresprivés xety de la classepoint.Ilfautdoncprévoirunedéclarationd’amitiéauseindecetteclasse,dontvoicilanouvelledéclaration:
/*fichierPOINT1.H*/
/*déclarationdelaclassepoint*/
classpoint
{intx,y;
public:
friendvoidaffiche(constpoint&);//constnonobligatoire
point(intabs=0,intord=0)
{x=abs;y=ord;
}
};
Pourécrireaffiche,ilnoussuffitd’accéderauxmembres(privés)xetydesonargumentde type point. Si ce dernier se nomme p, les membres en question se notent(classiquement)p.xetp.y.Voiciladéfinitiondeaffiche:
#include"point1.h"//nécessairepourcompileraffiche
#include<iostream>
usingnamespacestd;
voidaffiche(constpoint&p)
{cout<<"Coordonnées:"<<p.x<<""<<p.y<<"\n";
}
Notezbienquelacompilationdeaffichenécessiteladéclarationdelaclassepoint,etpasseulementunedéclarationtellequeclasspoint,carlecompilateurdoitconnaîtrelescaractéristiquesdelaclassepoint(notamment,ici,lalocalisationdesmembresxety).
Voicilepetitprogrammed’essaidemandé:#include"point1.h"
main()
{pointa(1,5);
affiche(a);
point*adp;
adp=newpoint(2,12);
affiche(*adp);//attention*adpetnonadp
}
Notez que nous n’avons pas eu à fournir le prototype de la fonction indépendanteaffiche, car il figure dans la déclaration de la classe point. Le faire constitueraitd’ailleursuneerreur.
189
Exercice82
ÉnoncéSoitlaclassevecteur3ddéfiniedansl’exercice70par:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;}
.....
};
Écrireunefonctionindépendantecoincide,amiede laclassevecteur3d,permettantdesavoir si deux vecteurs ont lesmêmes composantes (cette fonction remplacera lafonctionmembrecoincidequ’ondemandaitd’écriredansl’exercice70).
Siv1etv2 désignent deuxvecteurs de type vecteur3d, comment s’écritmaintenant letestdecoïncidencedecesdeuxvecteurs?
La fonction coincide va donc disposer de deux arguments de type vecteur3d. Si l’onprévoit de les transmettre par référence, en interdisant leur éventuelle modificationdanslafonction,leprototypedecoincidesera:
intcoincide(constvecteur3d&,constvecteur3d&);
Là encore, le qualificatif const a un double rôle : il permet d’appliquer la fonctioncoincide à des objets constants ou à des expressions de type point.Mais il permettraégalementdel’appliqueràtoutevaleursusceptibled’êtreconvertiedansletypepoint(voirlechapitrerelatifauxconversionsdéfiniesparl’utilisateur).
Voicilanouvelledéclarationdenotreclasse:/*fichiervect3D.H*/
/*déclarationdelaclassevecteur3d*/
classvecteur3d
{floatx,y,z;
public:
friendintcoincide(constvecteur3d&,constvecteur3d&);
vecteur3d(floatc1=0,floatc2=0,floatc3=0)
{x=c1;y=c2;z=c3;
}
190
};
Voiciladéfinitiondelafonctioncoincide:#include"vect3d.h"//nécessairepourcompilercoincide
intcoincide(constvecteur3d&v1,constvecteur3d&v2)
{if((v1.x==v2.x)&&(v1.y==v2.y)&&(v1.z==v2.z))
return1;
elsereturn0;
}
Letestdecoïncidencededeuxvecteurss’écritmaintenant:coincide(v1,v2)
Onnoteraque,tantdansladéfinitiondecoincidequedanscetest,onvoitsemanifesterlasymétrieduproblème,cequin’étaitpaslecaslorsquenousavionsfaitdecoincideunefonctionmembredelaclassevecteur3d.
191
Exercice83
ÉnoncéCréerdeuxclasses(dontlesmembresdonnéesontprivés):
•l’une,nomméevect,permettantdereprésenterdesvecteursà3composantesdetype double ; elle comportera un constructeur et une fonction membred’affichage;
• l’autre, nommée matrice, permettant de représenter des matrices carrées dedimension3x3;ellecomporteraunconstructeuravecunargument(adressed’un tableau de 3 x 3 valeurs) qui initialisera la matrice avec les valeurscorrespondantes.
Réaliser une fonction indépendante prod permettant de fournir le vecteurcorrespondantauproduitd’unematriceparunvecteur.Écrireunpetitprogrammedetest.Onfourniraséparémentlesdeuxdéclarationsdechacunedesclasses,leursdeuxdéfinitions,ladéfinitiondeprodetleprogrammedetest.
Ici,pournousfaciliterl’écriture,nousreprésenteronslescomposantesd’unvecteurparuntableauàtroisélémentsetlesvaleursd’unematriceparuntableauà2indices.Lafonctiondecalculduproduitd’unvecteurparunematricedoitobligatoirementpouvoiraccéder aux données des deux classes, ce qui signifie qu’elle devra être déclarée«amie»danschacunedecesdeuxclasses.
En ce qui concerne ses deux arguments (de type vect et mat), nous avons choisi latransmission la plus efficace, c’est-à-dire la transmission par référence. Quant aurésultat (de type vect), il doit obligatoirement être renvoyé par valeur (nous enreparleronsdansladéfinitiondeprod).
Voiciladéclarationdelaclassevect:/*fichiervect1.h*/
classmatrice;//pourpouvoircompilerladéclarationdevect
classvect
{
doublev[3];//vecteurà3composantes
public:
vect(doublev1=0,doublev2=0,doublev3=0)//constructeur
{v[0]=v1;v[1]=v2;v[2]=v3;}
friendvectprod(constmatrice&,constvect&);//fonctionamie
//indépendante
192
voidaffiche();
};
etladéclarationdelaclassemat:/*fichiermat1.h*/
classvect;//pourpouvoircompilerladéclarationdematrice
classmatrice
{
doublemat[3][3];//matrice3X3
public:
matrice();//constructeuravecinitialisationàzéro
matrice(doublet[3][3]);//constructeuràpartird'untableau
//3x3
friendvectprod(constmatrice&,constvect&);//fonctionamie
//indépendante
};
Nous avons déclaré constants les arguments de la fonction vect, ce qui nous protèged'uneéventuellefautedeprogrammationdanslesinstructionsdecettefonction.Danscecas, la fonction pourra être appelée avec en arguments effectifs non seulement desobjetsconstants,maisaussidesexpressionsd'untypesusceptibled'êtreconvertidansletypevoulu(commeonleverradanslechapitrerelatifauxconversionsdéfiniesparl'utilisateur).
Ladéfinitiondesfonctionsmembre(enfaitaffiche)delaclassevectneprésentepasdedifficultés:
#include"vect1.h"
#include<iostream>
usingnamespacestd;
voidvect::affiche()
{inti;
for(i=0;i<3;i++)cout<<v[i]<<"";
cout<<"\n";
}
Ilenvademêmepourlesfonctionsmembre(enfaitleconstructeur)delaclassemat:#include"mat1.h"
#include<iostream>
usingnamespacestd;
matrice::matrice(doublet[3][3])
{
inti;intj;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
mat[i][j]=t[i][j];
}
Ladéfinitionprodnécessitelesfichierscontenantlesdéclarationsdevectetdemat:#include"vect1.h"
#include"mat1.h"
vectprod(constmatrice&m,constvect&x)
193
{
inti,j;
doublesom;
vectres;//pourlerésultatduproduit
for(i=0;i<3;i++)
{for(j=0,som=0;j<3;j++)
som+=m.mat[i][j]*x.v[j];
res.v[i]=som;
}
returnres;
}
Notezquecette fonctioncréeunobjetautomatiqueresdeclassevect. Ilnepeutdoncêtrerenvoyéqueparvaleur.Dans lecascontraire, lafonctionappelanterécupéreraitl’adressed’unemplacementlibéréaumomentdelasortiedelafonction.
Voici,enfin,unexempledeprogrammedetest,accompagnédesonrésultat:#include"vect1.h"
#include"mat1.h"
main()
{vectw(1,2,3);
vectres;
doubletb[3][3]={1,2,3,4,5,6,7,8,9};
matricea=tb;
res=prod(a,w);
res.affiche();
}
143250
194
Chapitre11Surdéfinitiond’opérateurs
195
Rappels
C++vouspermetdesurdéfinirlesopérateursexistants,c’est-à-diredeleurdonnerunenouvellesignificationlorsqu’ilsportent(enpartieouentotalité)surdesobjetsdetypeclasse.
Lemécanismedelasurdéfinitiond’opérateursPoursurdéfinirunopérateurexistantop,ondéfinitunefonctionnomméeoperatorop (onpeutplacerunouplusieursespacesentre lemotoperator et l’opérateur,maiscen’estpasuneobligation):
soit sous forme d’une fonction indépendante (généralement amie d’une ou deplusieursclasses);
soitsousformed’unefonctionmembred’uneclasse.
Danslepremiercas,siopestunopérateurbinaire,lanotationaopbestéquivalenteà:operatorop(a,b)
Danslesecondcas,lamêmenotationestéquivalenteà:a.operatorop(b)
Lespossibilitésetleslimitesdelasurdéfinitiond'opérateursOn doit se limiter aux opérateurs existants, en conservant leur « pluralité » (unaire,binaire). Les opérateurs ainsi surdéfinis gardent leur priorité et leur associativitéhabituelle(voirtableaurécapitulatif,unpeuplusloin).
Unopérateursurdéfinidoit toujoursposséderunopérandede typeclasse(onnepeutdoncpasmodifierlessignificationsdesopérateursusuels).Ildoitdoncs’agir:
soitd’unefonctionmembre,auquelcaselledisposeobligatoirementd’unargumentimplicitedutypedesaclasse(this);
soitd’unefonctionindépendante(ouplutôtamie)possédantaumoinsunargumentdetypeclasse.
Il ne faut pas faire d’hypothèse sur la signification a priori d’un opérateur ; par
196
exemple,lasignificationde+=pouruneclassenepeutenaucuncasêtredéduitedelasignificaitonde+etde=pourcettemêmeclasse.
CasparticuliersLes opérateurs [], (), ->, new et delete doivent obligatoirement être définis commefonctionsmembre.
Lesopérateurs=(affectation)et&(pointeursur)possèdentunesignificationprédéfiniepour les objets de n’importe quel type classe.Cela ne les empêchenullement d’êtresurdéfinis.Encequiconcernel’opérateurd’affectation,onpeutchoisirdetransmettresonuniqueargumentparvaleurouparréférence.Danslederniercas,onneperdrapasdevuequeleseulmoyend’autoriserl’affectationd’uneexpressionconsisteàdéclarercetargumentconstant.
La surdéfinition de new, pour un type classe donné, se fait par une fonction deprototype:
void*new(size_t)
Ellereçoit,enuniqueargument,latailledel’objetàallouer(cetargumentseragénéréautomatiquement par le compilateur, lors d’un appel de new), et elle doit fournir enretourl’adressedel’objetalloué.
Lasurdéfinitiondedelete,pouruntypedonné,sefaitparunefonctiondeprototype:voiddelete(type*)
Ellereçoit,enuniqueargument,l’adressedel’objetàlibérer.
Tableaurécapitulatif
LesopérateurssurdéfinissablesenC++(classésparprioritédécroissante)
Pluralité Opérateurs AssociativitéBinaire ()
(3)[]
(3)->
(3) ->
Unaire+-++
(5)--
(5)!~*&
(1)new
(4)(6)
new[](4)(6)
delete(4)(6)
delete[](4)(6)
(cast)
<-
Binaire */% ->
Binaire +- ->
197
Binaire <<>> ->
Binaire <<=>>= ->
Binaire ==!= ->
Binaire & ->
Binaire ^ ->
Binaire || ->
Binaire && ->
Binaire | ->
Binaire =(1)(3)
+=-=*=/=%=&=^=|=<<=
>>=<-
Binaire , ->
(1)S’iln’estpassurdéfini,ilpossèdeunesignificationpardéfaut.
(3)Doitêtredéfinicommefonctionmembre.
(4)Soitàun«niveauglobal»(fonctionindépendante),soitpouruneclasse(fonctionmembre).
(5)Lorsqu’ilssontdéfinisdefaçonunaire,cesopérateurscorrespondentàlanotation«pré»;maisilenexisteunedéfinitionbinaire(avecdeuxièmeopérandefictifdetypeint)quicorrespondàlanotation«post».
(6)Ondistinguebiennewdenew[]etdeletededelete[]
Mêmelorsqu’onasurdéfinilesopérateursnewetdeletepouruneclasse,ilrestepossibledefaireappelauxopérateursnewetdeleteusuels,enutilisantl’opérateurderésolutiondeportée(::).
198
Exercice84
ÉnoncéSoituneclassevecteur3ddéfiniecommesuit:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
};
Définirlesopérateurs==et!=demanièrequ’ilspermettentdetesterlacoïncidenceoulanon-coïncidencededeuxpoints:
a.enutilisantdesfonctionsmembreþ;
b.enutilisantdesfonctionsamies.
a.Avecdesfonctionsmembre
Ilsuffitdoncdeprévoir,danslaclassevecteur3d,deuxfonctionsmembredenomoperator== et operator !=, recevant un argument de type vecteur3d correspondant au secondargumentdesopérateurs(lepremieropérandeétantfourniparl’argumentimplicitethisdesfonctionsmembre).Voiciladéclarationcomplètedenotreclasse,accompagnéedesdéfinitionsdesopérateurs:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
intoperator==(vecteur3d);
intoperator!=(vecteur3d);
};
intvecteur3d::operator==(vecteur3dv)
{if((v.x==x)&&(v.y==y)&&(v.z==z))return1;
elsereturn0;
}
intvecteur3d::operator!=(vecteur3dv)
{return!((*this)==v);
}
Notez que, dans la définition de !=, nous nous sommes servi de l’opérateur ==. Enpratique,onserasouventamenéàréécrireentièrementladéfinitiond’untelopérateur,
199
pour de simples raisons d’efficacité (d’ailleurs, pour lesmêmes raisons, on placera«enligne»lesfonctionsoperator==etoperator!=).
b.Avecdesfonctionsamies
Ilfautdoncprévoirdedéclarercommeamies,danslaclassevecteur3d,deuxfonctions(operator==etoperator!=) recevantdeuxargumentsde typevecteur3dcorrespondantauxdeux opérandes des opérateurs Voici la nouvelle déclaration de notre classe,accompagnéedesdéfinitionsdesopérateurs:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
friendintoperator==(vecteur3d,vecteur3d);
friendintoperator!=(vecteur3d,vecteur3d);
};
intoperator==(vecteur3dv,vecteur3dw)
{if((v.x==w.x)&&(v.y==w.y)&&(v.z==w.z))return1;
elsereturn0;
}
intoperator!=(vecteur3dv,vecteur3dw)
{return!(v==w);
}
Voici, à titre indicatif, un exempledeprogramme, accompagnédu résultat fourni parsonexécution,utilisantn’importe laquelledesdeuxclassesvecteur3d quenousvenonsdedéfinir:
#include"vecteur3d.h"
#include<iostream>
usingnamespacestd;
main()
{vecteur3dv1(3,4,5),v2(4,5,6),v3(3,4,5);
cout<<"v1==v2:"<<(v1==v2)<<"v1!=v2:"<<(v1!=v2)<<"\n";
cout<<"v1==v3:"<<(v1==v3)<<"v1!=v3:"<<(v1!=v3)<<"\n";
}
v1==v2:0v1!=v2:1
v1==v3:1v1!=v3:0
200
Exercice85
ÉnoncéSoitlaclassevecteur3dainsidéfinie:
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
};
Définir l’opérateur binaire + pour qu’il fournisse la somme de deux vecteurs, etl’opérateur binaire * pour qu’il fournisse le produit scalaire dedeuxvecteurs.Onchoisiraicidesfonctionsamies.
Ilsuffitdecréerdeuxfonctionsamiesnomméesoperator+etoperator*.Ellesrecevrontdeux arguments de type vecteur3d ; la première fournira en retour un objet de typevecteur3d,lasecondefourniraenretourunfloat.
classvecteur3d
{floatx,y,z;
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{x=c1;y=c2;z=c3;
}
friendvecteur3doperator+(vecteur3d,vecteur3d);
friendfloatoperator*(vecteur3d,vecteur3d);
};
vecteur3doperator+(vecteur3dv,vecteur3dw)
{vecteur3dres;
res.x=v.x+w.x;
res.y=v.y+w.y;
returnres;
}
floatoperator*(vecteur3dv,vecteur3dw)
{return(v.x*w.x+v.y*w.y+v.z*w.z);
}
Il est possible de transmettre par référence les arguments des deux fonctions amiesconcernées.
Souvent,danscecas,onutiliseralequalificatifconstpuisquelafonctionestsupposéenepasmodifierlesvaleursdesesarguments.Parexemple:
201
vecteur3doperator+(constvecteur3d&v,constvecteur3d&w)
Danscecas,lafonctionpeutcertesêtreappeléeavecdesargumentseffectifsconstants.Mais il ne faudra pas perdre de vue qu'elle peut aussi être appelée avec argumentsfournissousformed'expressionsdetypevecteur3d,voireavecdesargumentsd'untypeautrequevecteur3d,pourpeuqu'ilexisteuneconversion impliciteappropriée (voir lechapitrerelatifauxconversionsdéfiniesparl'utilisateur).
Enrevanche,iln’estpaspossiblededemanderàoperator+detransmettresonrésultatparréférence,puisquecedernierestcréédanslafonctionmême,sousformed’unobjetde classe automatique. En toute rigueur, operator * pourrait transmettre son résultat(float)parréférence,maiscelan’aguèred’intérêtenpratique.
202
Exercice86
ÉnoncéSoitlaclassevecteur3dainsidéfinie:
classvecteur3d
{
floatv[3];
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{//àcompléter
}
};
Compléter la définitiondu constructeur (en ligne), puis définir l’opérateur [] pourqu’il permette d’accéder à l’unedes trois composantes d’unvecteur, et cela aussibienauseind’uneexpression(...=v1[i])qu’àgauched’unopérateurd’affectation(v1[i] = ...) ; de plus, on cherchera à se protéger contre d’éventuels risques dedébordementd’indice.
Ladéfinitionduconstructeurneposeaucunproblème.Encequiconcernel’opérateur[], C++ ne permet de le surdéfinir que sous la forme d’une fonctionmembre (cetteexceptionestjustifiéeparlefaitquel’objetconcernénedoitpasrisquerd’êtresoumisàuneconversion, lorsqu’ilapparaîtàgauched’uneaffectation).Elleposséderadoncunseulargumentdetypeintetellerenverraunrésultatdetypevecteur3d.
Pour que notre opérateur puisse être utilisé à gauche d’une affectation, il fautabsolumentquelerésultatsoitrenvoyéparréférence.Pournousprotégerd’unéventueldébordementd’indice,nous avons simplementprévuque toute tentatived’accès àunélémentendehorsdeslimitesconduiraitàaccéderaupremierélément.
Voici la déclaration de notre classe, accompagnée de la définition de la fonctionoperator[]:
classvecteur3d
{
floatv[3];
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{v[0]=c1;v[1]=c2;v[2]=c3;
}
float&operator[](int);
};
float&vecteur3d::operator[](inti)
{if((i<0)||(i>2))i=0;//pouréviterun"débordement"
203
returnv[i];
}
À titre indicatif, voici un petit exemple de programme faisant appel à notre classevecteur3d,accompagnédurésultatdesonexécution:
#include"vecteur3d.h"
#include<iostream>
usingnamespacestd;
main()
{
vecteur3dv1(3,4,5);
inti;
cout<<"v1=";
for(i=0;i<3;i++)cout<<v1[i]<<"";
for(i=0;i<3;i++)v1[i]=i;
cout<<"\nv1=";
for(i=0;i<3;i++)cout<<v1[i]<<"";
}
v1=345
v1=012
204
Exercice87
ÉnoncéL’exercice 77 vous avait proposé de créer une classe set_int permettant dereprésenterdesensemblesdenombresentiers:
classset_int
{
int*adval;//adressedutableaudesvaleurs
intnmax;//nombremaxid'éléments
intnelem;//nombrecourantd'éléments
public:
set_int(int=20);//constructeur
~set_int();//destructeur
......//autresfonctionsmembre
};
Son implémentation prévoyait de placer les différents éléments dans un tableauallouédynamiquement;aussil’affectationentreobjetsdetypeset_intposait-elledesproblèmes,puisqu’elleaboutissaitàdesobjetsdifférentscomportantdespointeurssurunmêmeemplacementdynamique.
Modifier la classe set_int pour qu’elle ne présente plus de telles lacunes. Onprévoiraquetoutobjetdetypeset_intcomportesonpropreemplacementdynamique,comme on l’avait fait pour permettre la transmission par valeur. De plus, ons’arrangerapourquel’affectationmultiplesoitutilisable.
Noussommesenprésenced’unproblèmevoisindeceluiposéparleconstructeurparrecopie.Nousl’avionsrésoluenprévoyantcequel’onappelleune«copieprofonde»del’objetconcerné(c’est-à-direunecopienonseulementdel’objetlui-même,maisdetoutes ses parties dynamiques). Quelques différences supplémentaires surgissentnéanmoins.Eneffet,ici:
onpeutsetrouverenprésenced’uneaffectationd’unobjetàlui-même;
avant affectation, il existe deuxobjets « complets » (avec leur partie dynamique),alorsquedans lecasduconstructeurparrecopie, iln’existaitqu’unseulobjet, lesecondétantàcréer.
Voicicommenttraiterl’affectationb=a,danslecasoùbestdifférentdea:
libérationdel’emplacementpointéparb;
205
créationdynamiqued’unnouvelemplacementdanslequelonrecopielesvaleursdel’emplacementdésignépara;
miseenplacedesvaleursdesmembresdonnéedeb.
Siaetbdésignent lemêmeobjet (ons’enassureradans l’opérateurd’affectation,encomparantlesadressesdesobjetsconcernés),onéviterad’appliquercemécanismequiconduirait à un emplacement dynamique pointé par « personne », et qui, donc, nepourraitjamaisêtrelibéré.Enfait,onsecontenterade...nerienfaire!
Par ailleurs, pour que l’affectation multiple (telle que c = b = a) fonctionnecorrectement, ilestnécessairequenotreopérateur renvoieunevaleurde retour (elleseradetypeset_int)représentantlavaleurdesonpremieropérande(aprèsaffectation,c’est-à-direlavaleurdebaprèsb=a).
Voicicequepourraitêtreladéfinitiondenotrefonctionoperator=(encequiconcerneladéclarationdelaclasseset_int,ilsuffiraitd’yajouterset_int&operator=(set_int&):
set_int&set_int::operator=(set_int&e)//ou:constset_int&e
//surdéfinitiondel'affectation-lescommentairescorrespondentàb=a
{
if(this!=&e)//onnefaitrienpoura=a
{deleteadval;//libérationpartiedynamiquedeb
adval=newint[nmax=e.nmax];//allocationnouvelensemble
//poura
nelem=e.nelem;//danslequelonrecopie
inti;//entièrementl'ensembleb
for(i=0;i<nelem;i++)//avecsapartiedynamique
adval[i]=e.adval[i];
}
return*this;
}
1.Onassocieraobligatoirementconstà latransmissionparréférence,si l’onsouhaitepouvoir appliquer l’affectation à une expression de type set_int. Cela n’est pasnécessairementjustifiéici.
2.Une telle surdéfinition d’un opérateur d’affectation pour une classe possédant desparties dynamiques ne sera valable que si elle est associée à une surdéfinitioncomparableduconstructeurparrecopie(pourlaclasseset_int,celleproposéedanslasolutiondel’exercice26convientparfaitement).
3.Apriori,seulelavaleurdupremieropérandeavraimentbesoind’êtretransmiseparréférence (pour que = puisse le modifier !) ; cette condition est obligatoirementremplie puisque notre opérateur doit être surdéfini comme fonction membre.Toutefois,enpratique,onutiliseraégalementlatransmissionparréférence,àlafoispourlesecondopérandeetpourlavaleurderetour,defaçonàêtreplusefficace(en
206
temps).Notezd’ailleursquesi l’opérateur= renvoyaitsonrésultatparvaleur, ilyauraitalorsappelduconstructeurderecopie(laremarqueprécédentes’appliqueraitalorsàunesimpleaffectation).
207
Exercice88
ÉnoncéConsidérerànouveaulaclasseset_intcrééedansl’exercice77(etsurlaquelleestégalementfondél’exerciceprécédent):
classset_int
{int*adval;//adressedutableaudesvaleurs
intnmax;//nombremaxid'éléments
intnelem;//nombrecourantd'éléments
public:
set_int(int=20);//constructeur
~set_int();//destructeur
......
};
Adaptercetteclasse,demanièreque:
•l’onpuisseajouterunélémentàunensembledetypeset_intpar(edésignantunobjetdetypeset_intetnunentier):e<n;
•e[n]prennelavaleur1sinappartientàeetlavaleur0danslecascontraire.Ons’arrangera pour qu’une instruction de la forme e[i] = ... soit rejetée à lacompilation.
Il nous faut donc surdéfinir l’opérateur binaire <, de façon qu’il reçoive commeopérandesunobjetdetypeset_intetunentier.Bienquel’énoncéneprévoierien,ilestprobable que l’on souhaitera pouvoir écrire des choses telles que (e étant de typeset_int,netpdesentiers):
e<n<p;
Cela signifie donc que notre opérateur devra fournir comme valeur de retourl’ensembleconcerné,aprèsqu’onluiauraajoutél’élémentvoulu.
Nouspouvons ici utiliser indifféremmentune fonctionmembreouune fonction amie.Nouschoisironslapremièrepossibilité.Parailleurs,noustransmettronslesopérandesetlavaleurderetourparréférence(c’estpossibleicicarl’objetcorrespondantn’estpas créé au sein de l’opérateur même, c’est-à-dire qu’il n’est pas de classeautomatique);ainsi,notreopérateurfonctionneramêmesileconstructeurparrecopien’a pas été surdéfini (en pratique toutefois, il faudra le faire dès qu’on souhaiterapouvoirtransmettrelavaleurd’unobjetdetypeset_intenargument).
208
En ce qui concerne l’opérateur [], il doit être surdéfini comme fonction membre,comme l’impose leC++, et cela bien qu’ici une affectation telle e[i]= soit interdite(alorsque c’est précisémentpour l’autoriserqueC++obliged’en faireune fonctionmembre!).Pourinterdiredetellesaffectations,ladémarchelaplussimpleconsisteàfaire en sorte que le résultat fourni par l’opérateur ne soit pas une lvalue, en latransmettantparvaleur.
Voici ce que pourraient être la définition et la déclaration de notre nouvelle classemuniedecesdeuxopérateurs(notezquenousutilisons[]pourdéfinir<):
classset_int
{int*adval;//adressedutableaudesvaleurs
intnmax;//nombremaxid'éléments
intnelem;//nombrecourantd'éléments
public:
set_int(int=20);//constructeur
~set_int();//destructeur
set_int&operator<(int);
intoperator[](int);//attentionrésultatparvaleur
};
set_int::set_int(intdim)
{adval=newint[nmax=dim];
nelem=0;
}
set_int::~set_int()
{deleteadval;
}
/*surdéfinitionde<*/
set_int&set_int::operator<(intnb)
{//onexaminesinbappartientdéjààl'ensemble
//enutilisantl'opérateur[]
if(!(*this)[nb]&&(nelem<nmax))adval[nelem++]=nb;
return(*this);
}
/*surdéfinitionde[]*/
intset_int::operator[](intnb)//attentionrésultatparvaleur
{inti=0;
//onexaminesinbappartientdéjààl'ensemble
//(danscecasivaudraneleenfindeboucle)
while((i<nelem)&&(adval[i]!=nb))i++;
return(i<nelem);
}
À titre indicatif, voici un exemple d’utilisation accompagné du résultat de sonexécution:
#include"set_int.h"
#include<iostream>
usingnamespacestd;
main()
{set_intens(10);
ens<25<2<25<3;
cout<<(ens[25])<<""<<(ens[5])<<"\n";
}
10
209
Exercice89
ÉnoncéSoituneclassevecteur3ddéfiniecommesuit:
classvecteur3d
{floatv[3];
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{v[0]=c1;v[1]=c2;v[2]=c3;
}
//àcompléter
};
Définirl’opérateur[]demanièreque:
•ilpermetted’accéder«normalement»àunélémentd’unobjetnonconstantdetypevecteur3d,etcelaaussibiendansuneexpressionqu’enopérandedegauched’uneaffectation;
•ilnepermettequelaconsultation(etnonlamodification)d’unobjetconstantdetypevecteur3d (autrementdit, siv estun telobjet,une instructionde la formev[i]=...devraêtrerejetéeàlacompilation).
Rappelonsquelorsquel’ondéfinitdesobjetsconstants(qualificatifconst),iln’estpaspossiblede leurappliquerunefonctionmembrepublique,saufsicettedernièreaétédéclarée avec le qualificatif const (auquel cas, elle peut indifféremment être utiliséeavecdesobjetsconstantsounonconstants).Ici,nousdevonsdoncdéfinirunefonctionmembreconstantedenomoperator[].
Par ailleurs, pour qu’une affectation de la forme v[i] = ... soit interdite, il estnécessaire que notre opérateur renvoie son résultat par valeur (et non par adressecommeonagénéralementl’habitudedelefaire).
Dans ces conditions, on voit qu’il est nécessaire de prévoir deux fonctionsmembredifférentes,pourtraiterchacundesdeuxcas:objetconstantouobjetnonconstant.Lechoix de la « bonne fonction » sera assuré par le compilateur, selon la présence oul’absencedel’attributconstpourl’objetconcerné.
Voici la définition complète de notre classe, accompagnée de la définition des deuxfonctionsoperator[]:
classvecteur3d
210
{floatv[3];
public:
vecteur3d(floatc1=0.0,floatc2=0.0,floatc3=0.0)
{v[0]=c1;v[1]=c2;v[2]=c3;
}
floatoperator[](int)const;//[]pourunvecteurconstant
float&operator[](int);//[]pourunvecteurnonconstant
};
/********operator[]pourunobjetnonconstant*******/
float&vecteur3d::operator[](inti)
{if((i<0)||(i>2))i=0;//pouréviterundébordement
returnv[i];
}
/**********operator[]pourunobjetconstant*********/
floatvecteur3d::operator[](inti)const
{if((i<0)||(i>2))i=0;//pouréviterundébordement
returnv[i];
}
À titre indicatif, voici un petit programme utilisant la classe vecteur3d ainsi définie,accompagnédurésultatproduitparsonexécution:
#include"vecteur3d.h"
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
main()
{inti;
vecteur3dv1(3,4,5);
constvecteur3dv2(1,2,3);
cout<<"V1:";
for(i=0;i<3;i++)cout<<v1[i]<<"";
cout<<"\nV2:";
for(i=0;i<3;i++)cout<<v2[i]<<"";
for(i=0;i<3;i++)v1[i]=i;
cout<<"\nV1:";
for(i=0;i<3;i++)cout<<v1[i]<<"";
//v2[1]=3;estbienrejetéàlacompilation
}
V1:345
V2:123
V1:012
211
Exercice90
ÉnoncéDéfinir une classe vect permettant de représenter des « vecteurs dynamiquesd’entiers»,c’est-à-diredontlenombred’élémentspeutnepasêtreconnulorsdelacompilation. Plus précisément, on prévoira de déclarer de tels vecteurs par uneinstructiondelaforme:
vectt(exp);
danslaquelleexpdésigneuneexpressionquelconque(detypeentier).
Ondéfinira,defaçonappropriée,l’opérateur[]demanièrequ’ilpermetted’accéderà des éléments d’un objet d’un type vect comme on le ferait avec un tableauclassique.
Onnechercherapasàrésoudrelesproblèmesposéséventuellementparl’affectationoulatransmissionparvaleurd’objetsdetypevect.Enrevanche,ons’arrangerapourqu’aucunrisquede«débordement»d’indicen’existe.
NB. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.Il montrera également comment se protéger des débordements d’indice par unetechniquedegestiond’exceptions.
Lesélémentsd’unobjetde type vect doivent obligatoirement être rangés enmémoiredynamique.L’emplacementcorrespondantseradoncallouépar leconstructeurquienrecevra la taille en argument. Le destructeur devra donc, naturellement, libérer cetemplacement. En ce qui concerne l’accès à un élément, il se fera en surdéfinissantl’opérateur [], comme nous l’avons déjà fait au cours des précédents exercices ;rappelonsqu’ilfaudraobligatoirementlefairesousformed’unefonctionmembre.
Pour nous protéger d’un éventuel débordement d’indice, nous ferons en sorte qu’unetentative d’accès à un élément situé en dehors du vecteur conduise à accéder àl’élémentderang0.
Voicicequepourraientêtreladéclarationetladéfinitiondenotreclasse:/**********déclarationdelaclassevect********/
classvect
{intnelem;//nombred'éléments
212
int*adr;//adressezonedynamiquecontenantleséléments
public:
vect(int);//constructeur
~vect();//destructeur
int&operator[](int);//accèsàunélémentparson"indice"
};
/***********définitiondelaclassevect********/
vect::vect(intn)
{adr=newint[nelem=n];
inti;
for(i=0;i<nelem;i++)adr[i]=0;
}
vect::~vect()
{deleteadr;
}
int&vect::operator[](inti)
{if((i<0)||(i>=nelem))i=0;//protection
returnadr[i];
}
Voiciunpetit exempledeprogrammed’utilisationd’une telleclasse,accompagnédurésultatproduitparsonexécution:
#include"vect.h"
#include<iostream>
usingnamespacestd;
main()
{vectv(6);
inti;
for(i=0;i<6;i++)v[i]=i;
for(i=0;i<6;i++)cout<<v[i]<<"";
}
012345
213
Exercice91
ÉnoncéEns’inspirantdel’exerciceprécédent,onsouhaitecréeruneclasseint2dpermettantde représenterdes tableauxdynamiquesd’entiers àdeux indices, c’est-à-diredontlesdimensionspeuventnepasêtreconnueslorsdelacompilation.Plusprécisément,onprévoiradedéclarerdetelstableauxparunedéclarationdelaforme:
int2dt(exp1,exp2);
danslaquelleexp1etexp2désignentuneexpressionquelconque(detypeentier).
On surdéfinira l’opérateur (), demanière qu’il permette d’accéder à des élémentsd’unobjetd’untypeint2dcommeonleferaitavecuntableauclassique.
Làencore,onnechercherapasàrésoudrelesproblèmesposéséventuellementparl’affectation ou la transmission par valeur d’objets de type int2d. En revanche, ons’arrangerapourqu’iln’existeaucunrisquededébordementd’indice.
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.Ilmontreraégalementcommentseprotégercontrelesdébordementsd’indiceparunetechniquedegestiond’exceptions.
Comme dans l’exercice précédent, les éléments d’un objet de type int2d doiventobligatoirementêtrerangésenmémoiredynamique.L’emplacementcorrespondantseradoncallouéparleconstructeurquiendéduiralatailledesdeuxdimensionsreçuesenargumentsLedestructeurlibéreracetemplacement.
Nous devons décider de la manière dont seront rangés les différents éléments enmémoire,àsavoir«parligne»ou«parcolonne»(entouterigueur,cetteterminologiefait référence à la façon dont on a l’habitude de visualiser des tableaux à deuxdimensions, ceque l’onnommeune ligne correspondant en fait àdes éléments ayantmêmevaleurdupremierindice).Nouschoisironsicilapremièresolution(c’estcelleutiliséeparCouC++pourlestableauxàdeuxdimensions).Ainsi,unélémentrepérépar lesvaleursietj des2 indices sera situé à l’adresse (adv désignant l’adresse del’emplacementdynamiqueallouéautableau):
adv+i*ncol+j
214
En ce qui concerne l’accès à un élément, il se fera en surdéfinissant l’opérateur (),d’unemanièrecomparableàcequenousavionsfaitpour[] ; làencore,C++imposeque()soitdéfinicommefonctionmembre.
Pour nous protéger d’un éventuel débordement d’indice, nous ferons en sorte qu’unevaleur incorrecte d’un indice conduise à « faire comme si » on lui avait attribué lavaleur0.
Voicicequepourraientêtreladéclarationetladéfinitiondenotreclasse:/********déclarationdelaclasseint2d********/
classint2d
{intnlig;//nombrede"lignes"
intncol;//nombrede"colonnes"
int*adv;//adresseemplacementdynamiquecontenantlesvaleurs
public:
int2d(intnl,intnc);//constructeur
~int2d();//destructeur
int&operator()(int,int);//accèsàunélément,parses2"indices"
};
/***********définitionduconstructeur**********/
int2d::int2d(intnl,intnc)
{nlig=nl;
ncol=nc;
adv=newint[nl*nc];
inti;
for(i=0;i<nl*nc;i++)adv[i]=0;//miseàzéro
}
/***********définitiondudestructeur***********/
int2d::~int2d()
{deleteadv;
}
/**********définitiondel'opérateur()*********/
int&int2d::operator()(inti,intj)
{if((i<0)||(i>=nlig))i=0;//protectionssurpremierindice
if((j<0)||(j>=ncol))j=0;//protectionssursecondindice
return*(adv+i*ncol+j);
}
Voiciunpetitexempled’utilisationdenotreclasseint2d,accompagnédurésultatfourniparsonexécution:
#include"int2d.h"
#include<iostream>
usingnamespacestd;
main()
{int2dt1(4,3);
inti,j;
for(i=0;i<4;i++)
for(j=0;j<3;j++)
t1(i,j)=i+j;
for(i=0;i<4;i++)
{for(j=0;j<3;j++)
cout<<t1(i,j)<<"";
cout<<"\n";
}
}
215
012
123
234
345
1.Dans lapratique,onseraamenéàdéfinirdeuxopérateurs()commenous l’avionsfait dans l’exercice précédent pour l’opérateur [] : l’un pour des objets nonconstants (celui défini ici), l’autre pour des objets constants (il sera prévu demanièreànepaspouvoirmodifierl’objetsurlequelilporte).
2.Généralement,par soucid’efficacité, lesopérateurs telsque() serontdéfinis«enligne».
3.Onauraitpusongeràemployerl’opérateur[]dontl’utilisations’avèreplusnaturelleque celle de l’opérateur (). Cependant, [] ne peut être surdéfini que sous formebinaire.Iln’estdoncpaspossibledel’employersouslaformet[i,j]pouraccéderàunélémentd’un tableaudynamique.Onpourraitalorspenserà l’utilisersous laformehabituellet[i][j].Or,cetteécrituredoitêtreinterprétéecomme(t[i])[j]),cequisignifiequ’onn’yappliquepluslesecondopérateuràunobjetdetypeint2d.
Comme, de surcroît, [] doit être défini comme fonction membre, l’écriture enquestiondemanderaitdedéfinir[]pouruneclasseint2d,ens’arrangeantpourqu’ilfournisseun résultat («vecteur ligne») lui-mêmede typeclasse.Danscederniercas,ilfaudraitégalementsurdéfinirl’opérateur[]pourqu’ilfournisseunrésultatdetype int. Pour obtenir le résultat souhaité, il serait alors nécessaire de définird’autresclassesqueint2d.
216
Exercice92
ÉnoncéCréeruneclassenommée histo permettantdemanipulerdes«histogrammes».Onrappellequel’onobtientunhistogrammeàpartird’unensembledevaleursx(i),endéfinissant n tranches (intervalles) contiguës (souvent de même amplitude) et encomptabilisantlenombredevaleursx(i)appartenantàchacunedecestranches.
Onprévoira:
• un constructeur de la forme histo (float min, float max, int ninter), dont lesargumentsprécisentlesbornes(minetmax)desvaleursàprendreencompteetlenombredetranches(ninter)supposéesdemêmeamplitude;
•unopérateur<<définitelqueh<<xajoutelavaleurxàl’histogrammeh,c’est-à-dire qu’elle incrémente de 1 le compteur relatif à la tranche à laquelleappartient x. Les valeurs sortant des limites (min - max) ne seront pascomptabilisées;
•unopérateur[]définitelqueh[i]représentelenombredevaleursrépertoriéesdanslatranchederangi(lapremièretrancheportantlenuméro1;unnuméroincorrect de tranche conduira à considérer celle de rang 1). On s'arrangerapourqu'uneinstructiondelaformeh[i]=...soitrejetéeencompilation.
On ne cherchera pas ici à régler les problèmes posés par l’affectation ou latransmissionparvaleurd’objetsdutypehisto.
Nousn’avonspasbesoindeconserverlesdifférentesvaleursx(i),maisseulementlescompteurs relatifs à chaque tranche. En revanche, il faut prévoir d’allouerdynamiquement (dans le constructeur) l’emplacement nécessaire à ces compteurs,puisquelenombredetranchesn’estpasconnulorsdelacompilationdelaclassehisto.Il faudra naturellement prévoir de libérer cet emplacement dans le destructeur de laclasse. Lesmembres donnée de histo seront donc les valeurs extrêmes (minimale etmaximale), le nombre de tranches et un pointeur sur l’emplacement contenant lescompteurs.
L’opérateur<< peut être surdéfini, soit comme fonctionmembre, soit comme fonctionamie ; nous choisirons ici la première solution. En revanche, l’opérateur [] doit
217
absolument être surdéfini comme fonction membre. Pour qu’il ne soit pas possibled’écrire des affectations de la forme h[i] = ..., on fera en sorte que cet opérateurfournissesonrésultatparvaleur.
Voicicequepourraitêtreladéclarationdenotreclassehisto:/***fichierhisto.h:déclarationdelaclassehisto***/
classhisto
{floatmin;//borneinférieure
floatmax;//bornesupérieure
intnint;//nombredetranchesutiles
int*adc;//pointeursurlescompteursassociésàchaqueintervalle
//adc[i-1]=compteurvaleursdelatranchederangi
floatecart;//largd'unetranche(pouréviterunrecalculsystématique)
public:
histo(float=0.0,float=1.0,int=10);//constructeur
~histo();//destructeur
histo&operator<<(float);//ajouteunevaleur
intoperator[](int);//nombredevaleursdanschaquetranche
};
Notezquenousavonsprévudesvaleurspardéfautpourlesargumentsduconstructeur(celles-cin’étaientpasimposéesparl’énoncé).
En ce qui concerne la définition des différents membres, il faut noter qu’il estindispensable qu’une telle classe soit protégée contre toute utilisation incorrecte.Certes, cela passe par un contrôle de la valeur du numéro de tranche fourni àl’opérateur [], ou par le refus de prendre en compte une valeur hors limites (quifourniraitunnumérodetrancheconduisantàunemplacementsituéendehorsdeceluiallouéparleconstructeur).
Mais nous devons de surcroît nous assurer que les valeurs des arguments fournis auconstructeur ne risquent pas de mettre en cause le fonctionnement ultérieur desdifférentes fonctionsmembre.Enparticulier, il est bondevérifier que le nombredetranchesn’estpasnégatif(notamment,unevaleurnulleconduiraitdans<<àunedivisionpar zéro) et que les valeurs du minimum et du maximum sont convenablementordonnées et différentes l’unede l’autre (dans cedernier cas, onaboutirait encore àunedivisionparzéro).Ici,nousavonsprévuderéglerceproblèmeenattribuantlecaséchéantdesvaleurspardéfautarbitraires(max=min+1,nint=1).
Voicicequepourraitêtreladéfinitiondesdifférentesfonctionsmembredenotreclassehisto:
/**************définitiondelaclassehisto**********/
#include"histo.h"
#include<iostream>
usingnamespacestd;
/*********************constructeur********************/
histo::histo(floatmini,floatmaxi,intninter)
{//protectioncontreargumentserronés
if(maxi<mini)
{floattemp=maxi;maxi=mini;mini=temp;}
if(maxi==mini)maxi=mini+1.0;//arbitraire
218
if(ninter<1)nint=1;
min=mini;max=maxi;nint=ninter;
adc=newint[nint];//allocemplacementscompteurs
inti;
for(i=0;i<=nint-1;i++)adc[i]=0;//etr.a.z.
ecart=(max-min)/nint;//largeurd'unetranche
}
/*********************destructeur*********************/
histo::~histo()
{deleteadc;
}
/*********************opérateur<<********************/
histo&histo::operator<<(floatv)
{intnt=(v-min)/ecart;
//onnecomptabilisequelesvaleurs"convenables"
if((nt>=0)&&(nt<=nint-1))adc[nt]++;
return(*this);
}
/*********************opérateur[]********************/
inthisto::operator[](intn)//résultatparvaleurici
{if((n<1)||(n>nint))n=1;//protection"débordement"
returnadc[n-1];
}
Voici, à titre indicatif, unpetit programmed’essaide la classe histo, accompagnédurésultatfourniparsonexécution:
#include"histo.h"
#include<iostream>
usingnamespacestd;
main()
{constintnint=4;
inti;
histoh(0.,5.,nint);//4tranchesentre0et5
h<<1.5<<2.4<<3.8<<3.0<<2.0<<3.5<<2.8<<4.6;
h<<12.0<<-3.5;
for(i=0;i<10;i++)h<<i/2.0;
cout<<"valeursdestranches\n";
for(i=1;i<=nint;i++)
cout<<"numéro"<<i<<":"<<h[i]<<"\n";
//uneaffectationtellequeh[2]=...seraitrejetéeencompilation
}
valeursdestranches
numéro1:3
numéro2:5
numéro3:6
numéro4:4
219
Exercice93
ÉnoncéRéaliser une classe nommée stack_int permettant de gérer une pile d’entiers. Cesderniers seront conservés dans un emplacement alloué dynamiquement ; sadimensionseradéterminéeparl’argumentfourniàsonconstructeur(onluiprévoiraunevaleurpardéfautde20).Cetteclassedevracomporter lesopérateurssuivants(noussupposonsquepestunobjetdetypestack_intetnunentier):
•<<,telquep<<najoutel'entiernàlapilep(silapileestpleine,riennesepasse);
•>>,telquep>>nplacedansnlavaleurduhautdelapilep,enlasupprimantdelapile(silapileestvide,lavaleurdenneserapasmodifiée);
•++,telquep++vale1silapilepestpleineet0danslecascontraire;
•--,telquep--vale1silapilepestvideet0danslecascontraire.
On prévoira que les opérateurs << et >> pourront être utilisés sous les formessuivantes(n1,n2etn3étantdesentiers):
p<<n1<<n2<<n3;p>>n1>>n2<<n3;
Onferaensortequ’ilsoitpossibledetransmettreunepileparvaleur.Enrevanche,l’affectation entre piles ne sera pas permise, et on s’arrangera pour que cettesituationaboutisseàunarrêtdel’exécution.
N.B.Lechapitre17vousmontreracomment résoudre leprésentexerciceà l’aidedescomposantsstandardintroduitsparlanorme,qu'ilnefautpaschercheràutiliserici.
La classe stack_int contiendra comme membres donnée : la taille de l’emplacementréservépourlapile(nmax),lenombred’élémentsplacésàunmomentdonnésurlapile(nelem) et un pointeur sur l’emplacement qui sera alloué par le constructeur pour yranger les éléments de la pile (adv).Notez qu’il n’est pas nécessaire de prévoir unedonnéesupplémentairepourunéventuel«pointeur»depile,danslamesureoùc’estlenombred’élémentsnelemquijoueicicerôle.
Lesopérateursrequispeuventindifféremmentêtredéfiniscommefonctionsmembreoucomme fonctions amies. Nous choisirons ici la première solution. Pour que les
220
opérateurs<<et>>puissentêtreutilisésàplusieursreprisesdansunemêmeexpression,ilestnécessaireque,parexemple:
p<<n1<<n2<<n3;
soitéquivalentà:(((p<<n1)<<n2)<<n3);
Pour ce faire, lesopérateurs << et >> doivent fournir comme résultat la pile reçue enpremieropérande, aprèsqu’ellea subi l’opérationvoulue (empilageoudépilage). Ilestpréférablequecerésultatsoittransmisparréférence(onéviteralapertedetempsdueauconstructeurparrecopie).
En ce qui concerne la transmission par valeur d’un objet de type stack_int, nous nepouvonspasnouscontenterduconstructeurparrecopiepardéfautpuisquecedernierne recopierait pas la partie dynamique de l’objet, ce qui poserait les problèmeshabituels. Nous devons donc surdéfinir le constructeur par recopie de façon qu’ilréaliseune«copieprofonde»del’objet.
Pour satisfaire à la contrainte imposée par l’énoncé sur l’affectation, nous allonssurdéfinir l’opérateurd’affectation.Commecedernierdoit secontenterd’afficherunmessaged’erreuretd’interromprel’exécution,iln’estpasnécessairequ’ilrenvoieunequelconquevaleur(d’oùladéclarationvoid).
Voicicequepourraitêtreladéclarationdenotreclasse:#include<stdlib.h>
#include<iostream>
usingnamespacestd;
classstack_int
{intnmax;//nombremaximaldelavaleursdelapile
intnelem;//nombrecourantdevaleursdelapile
int*adv;//pointeursurlesvaleurs
public:
stack_int(int=20);//constructeur
~stack_int();//destructeur
stack_int(stack_int&);//constructeurparrecopie
//voirremarque1ci-après
voidoperator=(stack_int&);//affectation–voirremarque2
stack_int&operator<<(int);//opérateurd'empilage
stack_int&operator>>(int&);//opérateurdedépilage
//(attentionint&)
intoperator++();//opérateurdetestpilepleine
intoperator--();//opérateurdetestpilevide
};
1. Il est nécessaire que l’argument de stack_int soit transmis par référence. On peututiliserconst ;danscecas,onpourra initialiserunobjetavecunobjetconstantouune expression d’un type susceptible d’être converti implicitement dans le type
221
stack_int(cequiesticilecasdestypesentiersouflottants;voirlechapitrerelatifauxconversionsdéfiniesparl’utilisateur).
2. En revanche, il n’est pas nécessaire que l’argument de operator= soit transmis parréférence.Sil’onsouhaitaitpouvoiraffecterdesobjetsconstants,ilfaudraitajouterle qualificatif const ; dans ce cas, on pourrait alors, du même coup, affecter desexpressionsd’un type convertibledans le type stack_int, c’est-à-dire ici d’un typeentierouflottant(voirlechapitrerelatifauxconversionsdéfiniesparl’utilisateur).
3. L’opérateur >> doit absolument recevoir son deuxième opérande par référence,puisqu’ildoitpouvoirenmodifierlavaleur.
Voiciladéfinitiondesfonctionsmembredestack_int:#include"stack-int.h"
stack_int::stack_int(intn)
{nmax=n;
adv=newint[nmax];
nelem=0;
}
stack_int::~stack_int()
{deleteadv;
}
stack_int::stack_int(stack_int&p)
{nmax=p.nmax;nelem=p.nelem;
adv=newint[nmax];
inti;
for(i=0;i<nelem;i++)
adv[i]=p.adv[i];
}
voidstack_int::operator=(stack_int&p)
{cout<<"***Tentatived'affectationentrepiles-arrêtexécution***\n";
exit(1);
}
stack_int&stack_int::operator<<(intn)
{if(nelem<nmax)adv[nelem++]=n;
return(*this);
}
stack_int&stack_int::operator>>(int&n)
{if(nelem>0)n=adv[--nelem];
return(*this);
}
intstack_int::operator++()
{return(nelem==nmax);
}
intstack_int::operator--()
{return(nelem==0);
}
À titre indicatif, voici un petit programme d’utilisation de notre classe, accompagnéd’unexempled’exécution:
/************programmed'essaidestack_int*********/
#include"stack_int.h"
#include<iostream>
usingnamespacestd;
main()
{voidfct(stack_int);
222
stack_intpile(40);
cout<<"pleine:"<<pile++<<"vide:"<<pile--<<"\n";
pile<<1<<2<<3<<4;
fct(pile);
intn,p;
pile>>n>>p;//ondépile2valeurs
cout<<"hautdelapileauretourdefct:"<<n<<""<<p<<"\n";
stack_intpileb(25);
pileb=pile;//tentatived'affectation
}
voidfct(stack_intpl)
{cout<<"hautdelapilereçueparfct:";
intn,p;
pl>>n>>p;//ondépile2valeurs
cout<<n<<""<<p<<"\n";
pl<<12;//onenajouteune
}
pleine:0vide:1
hautdelapilereçueparfct:43
hautdelapileauretourdefct:43
***Tentatived'affectationentrepiles-arrêtexécution***
223
Exercice94
ÉnoncéAdapterlaclassepoint:
classpoint
{
intx,y;
//.......
public:
point(intabs=0,intord=0)//constructeur
//.....
};
defaçonqu’elledisposededeuxfonctionsmembrepermettantdeconnaître,à toutinstant, le nombre total d’objets de type point, ainsi que le nombre d’objetsdynamiques(c’est-à-direcréésparnew)decemêmetype.
Ici,iln’estpluspossibledesecontenterdecomptabiliserlesappelsauconstructeuretau destructeur. Il faut, en outre, comptabiliser les appels à new et delete. La seulepossibilité pour y parvenir consiste à surdéfinir ces deux opérateurs pour la classepoint.Lenombredepoints total et lenombredepointsdynamiques seront conservésdansdesmembresstatiques(n’existantqu’uneseulefoispourl’ensembledesobjetsdutype point). Quant aux fonctions membre fournissant les informations voulues, il estpréférable d’en faire des fonctions membre statiques (déclarées, elles aussi, avecl’attributstatic),cequipermettradelesemployerplusfacilementquesionenavaitfaitdes fonctionsmembreordinaires (puisqu’onpourra lesappelersansavoirà les faireportersurunobjetparticulier).
Voicicequepourraitêtrenotreclassepoint(déclarationetdéfinition):#include<stddef.h>//poursize_t
#include<iostream>
usingnamespacestd;
classpoint
{staticintnpt;//nombretotaldepoints
staticintnptd;//nombredepointsdynamiques
intx,y;
public:
point(intabs=0,intord=0)//constructeur
{x=abs;y=ord;
npt++;
}
~point()//destructeur
{npt--;
}
void*operatornew(size_tsz)//newsurdéfini
224
{nptd++;
return::newchar[sz];//appellenewprédéfini
}
voidoperatordelete(void*dp)
{nptd--;
::delete(dp);//appelledeleteprédéfini
}
staticintnpt_tot()
{returnnpt;
}
staticintnpt_dyn()
{returnnptd;
}
};
intpoint::npt=0;//initialisationdesmembresstatiquesdepoint
intpoint::nptd=0;
Notezque,dans lasurdéfinitiondenewetdelete,nousavons faitappelauxopérateursprédéfinis(paremploide::)pourcequiconcernelagestiondelamémoire.
Voici un exemple de programme utilisant notre classe point, accompagné du résultatfourniparsonexécution:
#include"point.h"
#include<iostream>
usingnamespacestd;
main()
{
point*ad1,*ad2;
pointa(3,5);
cout<<"A:"<<point::npt_tot()<<""<<point::npt_dyn()<<"\n";
ad1=newpoint(1,3);
pointb;
cout<<"B:"<<point::npt_tot()<<""<<point::npt_dyn()<<"\n";
ad2=newpoint(2,0);
deletead1;
cout<<"C:"<<point::npt_tot()<<""<<point::npt_dyn()<<"\n";
pointc(2);
deletead2;
cout<<"D:"<<point::npt_tot()<<""<<point::npt_dyn()<<"\n";
}
A:10
B:31
C:31
D:30
225
Chapitre12Lesconversionsdetypedéfiniespar
l’utilisateur
226
Rappels
C++vouspermetdedéfinirdesconversionsd’untypeclasseversunautretypeclasseou un type de base. On parle de conversions définies par l’utilisateur (en abrégé :C.D.U.).Cesconversionspeuventalorséventuellementêtremisesenœuvredefaçonimpliciteparlecompilateur,afindedonnerunesignificationàunappeldefonctionouà un opérateur (sans conversion, l’appel ou l’opérateur serait illégal). On retrouveainsidespossibilitéscomparablesàcellesquinoussontoffertesparleCenmatièredeconversionsimplicites.
Deuxsortesdefonctions(obligatoirementdesfonctionsmembre)permettentdedéfinirdesC.D.U.:
lesconstructeursàunargument(quelquesoitletypedecetargument)réalisentuneconversiondu typede cet argumentdans le typede sa classe ; onpeut cependantutiliser lemot-clé explicit devant la déclaration du constructeur pour en interdirel’utilisationdansuneconversionimplicite;
lesopérateursdecast;dansuneclasseA,ondéfiniraunopérateurdeconversiond’untypex(quelconque,c’est-à-direaussibienuntypedebasequ’unautretypeclasse)enintroduisantlafonctionmembredeprototype:operatorx();
Notezqueletypedelavaleurderetour(obligatoirementdéfiniparsonnom)nedoitpasfigurerdansl’en-têteouleprototyped’unetellefonction.
Lesrèglesd’utilisationdesC.D.U.rejoignentcellesconcernantlechoixd’unefonctionsurdéfinie:
LesC.D.U.nesontmisesenœuvrequesicelaestnécessaire.
Une seule C.D.U. peut intervenir dans une chaîne de conversions (d’un argumentd’unefonctionoud’unopéranded’unopérateur).
Il ne doit pas y avoir d’ambiguïté, c’est-à-dire plusieurs chaînes de conversionsconduisantaumêmetype,pourunargumentouunopérandedonné.
N. B. Aucune conversion n’est réalisable sur un argument effectif en cas detransmission par référence, sauf si l’argument muet correspondant est déclaré avecl’attributconst;danscederniercas,onretrouvelesmêmespossibilitésdeconversionqu’encasdetransmissionparvaleur.
227
Exercice95
ÉnoncéSoitlaclassepointsuivante:
classpoint
{intx,y;
public:
point(intabs=0,intord=0)
{x=abs;y=ord;
}
//.....
};
a. La munir d’un opérateur de cast permettant de convertir un point en un entier(correspondantàsonabscisse).
b.Soientalorscesdéclarations:pointp;
intn;
voidfct(int);
Quefontcesinstructions:n=p;//instruction1
fct(p);//instruction2
a. Il suffit de définir une fonction membre, de nom operator int, sans argument etrenvoyantlavaleurdel’abscissedupointl’ayantappelée.Rappelonsqueletypedela valeur de retour (queC++ déduit du nom de la fonction - ici int) ne doit pasfigurerdansl’en-têteouleprototype.Voicicequepourraientêtreladéclarationetladéfinitiondecettefonction,iciréuniesenuneseuledéclarationd’unefonctionenligne:operatorint()
{returnx;}
b. L’instruction 1 est traduite par le compilateur en une conversion de p en int (parappeldeoperatorint),suivied’uneaffectationdurésultatàn.Notezbienqu’iln’yapas d’appel d’un quelconque opérateur d’affection de la classe point, ni d’unconstructeurparrecopie(carleseulargumenttransmisàlafonctionoperatorintestl’argumentimplicitethis).
L’instruction 2 est traduite par le compilateur en une conversion de p en int (parappeldeoperatorint),suivied’unappeldelafonctionfct,àlaquelleontransmetle
228
résultat de cette conversion. Notez qu’il n’y pas, là non plus, d’appel d’unconstructeurpar recopie, ni pour fct (puisqu’elle reçoit un argument d’un type debase),nipouroperatorint(pourlamêmeraisonqueprécédemment).
229
Exercice96
ÉnoncéQuelsrésultatsfourniraleprogrammesuivant:
#include<iostream>
usingnamespacestd;
classpoint
{intx,y;
public:
point(intabs,intord)//constructeur2arguments
{x=abs;y=ord;
}
operatorint()//"cast"point-->int
{cout<<"***appelint()pourlepoint"<<x<<""<<y<<"\n";
returnx;
}
};
main()
{pointa(1,5),b(2,8);
intn1,n2,n3;
n1=a+3;//instruction1
cout<<"n1="<<n1<<"\n";
n2=a+b;//instruction2
cout<<"n2="<<n2<<"\n";
doublez1,z2;
z1=a+3;//instruction3
cout<<"z1="<<z1<<"\n";
z2=a+b;//instruction4
cout<<"z2="<<z2<<"\n";
}
Lorsque le compilateur rencontre, dans l’instruction 1, l’expression a + 3, il cherchetoutd’abords’ilexisteunopérateursurdéfinicorrespondantauxtypespointetint(danscetordre).Ici,iln’entrouvepas.Ilvaalorschercheràmettreenplacedesconversionspermettantd’aboutiràuneopérationexistante;ici,l’opérateurdecast(operatorint)luipermetdeserameneràuneadditiond’entiers(parconversiondepenint),etc’estlaseulepossibilité.Le résultat est alors,biensûr,de type int et il est affecté à n1 sansconversion.
Lemêmeraisonnements’appliqueàl’instruction2;l’évaluationdea+bsefaitalorspar conversion de a et de b en int (seule possibilité de donner une signification àl’opérateur+).Lerésultatestdetypeintetilestaffectésansconversionàn2.
Lesexpressionsfigurantàdroitedesaffectationsdesinstructions3et4sontévaluéesexactement suivant lesmêmes règles que les expressions des instructions 1 et 2.Cen’estqu’aumomentde l’affectationdurésultat (de typeint)à lavariablementionnée
230
quel’onopèreuneconversionint-->double(analogueàcellesquisontmisesenplaceenlangageC).
Àtitreindicatif,voicicequefournitprécisémentl’exécutionduprogramme:***appelint()pourlepoint15
n1=4
***appelint()pourlepoint15
***appelint()pourlepoint28
n2=3
***appelint()pourlepoint15
z1=4
***appelint()pourlepoint15
***appelint()pourlepoint28
z2=3
231
Exercice97
ÉnoncéQuelsrésultatsfourniraleprogrammesuivant:
#include<iostream>
usingnamespacestd;
classpoint
{
intx,y;
public:
point(intabs,intord)//constructeur2arguments
{x=abs;y=ord;
}
operatorint()//"cast"point-->int
{cout<<"**appelint()pourlepoint"<<x<<""<<y<<"\n";
returnx;
}
};
voidfct(doublev)
{cout<<"$$appelfctavecargument:"<<v<<"\n";
}
main()
{pointa(1,4);
intn1;
doublez1,z2;
n1=a+1.75;//instruction1
cout<<"n1="<<n1<<"\n";
z1=a+1.75;//instruction2
cout<<"z1="<<z1<<"\n";
z2=a;//instruction3
cout<<"z2="<<z2<<"\n";
fct(a);//instruction4
}
Pourévaluerl’expressiona+1.75de l’instruction1, lecompilateurmetenplaceunechaînedeconversionsdeaendouble(point-->intsuiviedeint-->double)demanièreàaboutiràl’additiondedeuxvaleursdetypedouble;lerésultat,detypedouble,estensuiteconvertipourêtreaffectéàn1 (conversionforcéepar l’affectation,commed’habitudeenC).
Notezbienqu’iln’estpasquestionpourlecompilateurdeprévoirlaconversionenintdelavaleur1.75(defaçonàserameneràl’additiondedeuxint,aprèsconversiondeaenint)carils’agitlàd’une«conversiondégradante»quin’estjamaismiseenœuvrede manière implicite dans un calcul d’expression. Il n’y a donc pas d’autre choixpossible(notezques’ilyenavaiteffectivementunautre,ilnes’agiraitpaspourautantd’une situation d’ambiguïté dans la mesure où le compilateur appliquerait alors les
232
règleshabituellesdechoixd’unefonctionsurdéfinie).
L’instruction2correspondàunraisonnementsimilaire,aveccetteseuledifférencequelerésultatdel’addition(detypedouble)peutêtreaffectéàz1sansconversion.
Enfinlesinstructions3et4entraînentuneconversiondepointendouble,parunesuitedeconversionspoint-->intetint-->double.
Voicilerésultatdel’exécutionduprogramme:**appelint()pourlepoint14
n1=2
**appelint()pourlepoint14
z1=2.75
**appelint()pourlepoint14
z2=1
**appelint()pourlepoint14
$$appelfctavecargument:1
233
Exercice98
ÉnoncéQue se passerait-il si, dans la classe point du précédent exercice, nous avionsintroduit, en plus de l’opérateur operator int, un autre opérateur de cast operator
double?
Dans ce cas, les instructions 1 et 2 auraient conduit à une situation d’ambiguïté. Eneffet, le compilateur aurait disposé de deux chaînes de conversions permettant deconvertirunpointendouble:soitpoint-->double,soitpoint-->intsuiviedeint->double.Enrevanche,lesinstructions3et4auraienttoujoursétéacceptées.
234
Exercice99
ÉnoncéConsidérerlaclassesuivante:
classcomplexe
{doublex,y;
public:
complexe(doubler=0,doublei=0);
complexe(complexe&);//oucomplexe(constcomplexe&)
};
Dansunprogrammecontenantlesdéclarations:complexez(1,3);
voidfct(complexe);
queproduirontlesinstructionssuivantes:z=3.75;//instruction1
fct(2.8);//instruction2
z=2;//instruction3
fct(4);//instruction4
L’instruction1conduitàuneconversiondelavaleurdouble3.75enuncomplexeparappeldu constructeur à un argument (compte tenu des arguments par défaut), suivie d’uneaffectationàz.
L’instruction2conduitàuneconversionde lavaleurdouble2.8enuncomplexe (commeprécédemment par appel du constructeur à un argument) ; il y aura ensuite créationd’unecopie,parappelduconstructeurparrecopiedelaclassecomplexe,copiequiseratransmiseàlafonctionfct.
Lesinstructions3et4jouentlemêmerôlequelesdeuxprécédentes,aveccetteseuledifférence qu’elles font intervenir des conversions int --> complexe obtenues par uneconversionint-->doublesuivied’uneconversiondouble-->complexe.
235
Exercice100
Énoncéa.Quelsrésultatsfourniraleprogrammesuivant:
#include<iostream>
usingnamespacestd;
classpoint
{intx,y;
public:
point(intabs=0,intord=0)//constructeur0,1ou2arguments
{x=abs;y=ord;
cout<<"$$constructionpoint:"<<x<<""<<y<<"\n";
}
friendpointoperator+(point,point);//point+point-->point
voidaffiche()
{cout<<"Coordonnées:"<<x<<""<<y<<"\n";}
};
pointoperator+(pointa,pointb)
{pointr;
r.x=a.x+b.x;r.y=a.y+b.y;
returnr;
}
main()
{pointa,b(2,4);
a=b+6;//affectation1
a.affiche();
a=6+b;//affectation2
b.affiche();
}
b.Mêmequestionensupposantquel’opérateur+aétésurdéfinicommeunefonctionmembreetnonpluscommeunefonctionamie.
c.Mêmequestionensupposantquel’opérateur+estdéfiniainsi:friendpointoperator+(point&,point&);
d.Mêmequestionensupposantquel’opérateur+estdéfiniainsi:friendpointoperator+(constpoint&,constpoint&);
a. L’évaluation de l’expression b + 6 de la première affectation se fait en utilisantl’opérateur+surdéfinipourlaclassepoint,aprèsavoirconvertil’entier6enunpoint(parappelduconstructeuràunargument).Lerésultat,detypepoint,estalorsaffectéàa.
L’instructiond’affectation2sedérouledefaçonsimilaire(conversiondel’entier6
236
enunpoint).
Endéfinitive,onobtientlesrésultatssuivants:$$constructionpoint:00
$$constructionpoint:24
$$constructionpoint:60
$$constructionpoint:00
Coordonnées:84
$$constructionpoint:60
$$constructionpoint:00
Coordonnées:24
b.Enrevanche,sil’opérateur+avaitétésurdéfiniparunefonctionmembre,lasecondeinstructiond’affectationauraitétérejetéeàlacompilation;eneffet,elleauraitétéinterprétéecomme:.operator+(b)
Onvoiticiqueseulelafonctionamiepermetdetraiterdefaçonidentiquelesdeuxopérandesd’unopérateurbinaire,notammentencequiconcernelespossibilitésdeconversionsimplicites.
c.Latransmissionparréférenceimposequel’argumenteffectifd’unefonctionsoitdumême type que l’argument muet correspondant, aucune conversion n’étant alorspossible.Lesaffectations1et2serontrejetéesencompilation.
d.Laprésencedeconstpermetaucompilateurdetransmettreàlafonction(operator+)unobjet temporaire obtenu par d’éventuelles conversions des arguments effectifs.Touteslesaffectationsredeviennentcorrectes.
237
Exercice101
ÉnoncéSoientlesdeuxclassessuivantes:
classB
{//...
public:
B();//constructeursansargument
B(int);//constructeuràunargumententier
//...
};
classA
{//...
friendoperator+(A,A);
public:
A();//constructeursansargument
A(int);//constructeuràunargumententier
A(B);//constructeuràunargumentdetypeB
//...
};
a.Dansunprogrammecontenantlesdéclarations:Aa1,a2,a3;
Bb1,b2,b3;
lesinstructionssuivantesseront-ellescorrecteset,sioui,queferont-elles?a1=b1;//instruction1
b1=a1;//instruction2
a3=a1+a2;//instruction3
a3=b1+b2;//instruction4
b3=a1+a2;//instruction5
b.Commentobtenirlemêmerésultatsansdéfinir,dansA,leconstructeurA(B)?
a.
Instruction1
Ilfautdistinguer2cas:
SiAn’apassurdéfinid’opérateurd’affectationd’unobjetdetypeBàunobjetdetypeA,il y aura conversion de b1 dans le type de A (par appel du constructeur A(B)), suivied’une affectation à a1 (en utilisant soit l’opérateur d’affectation par défaut de A, soitéventuellementceluiquiauraitpuyêtresurdéfini).
238
La formeusuelle de l’en-tête de l’opérateur A est A & operator = (const & A),mais lesréférencesnesontpasindispensables,pasplusqueconst.Demême,lavaleurderetourpeut ne pas exister dès lors qu’on ne cherche pas à pas réaliser des affectationsmultiples.
Enrevanche,siAasurdéfiniunopérateurd’affectationd’unobjetdetypeBàunobjetdetypeA,cedernierserautilisépourréaliserl’instruction1,et,danscecas,iln’yauradoncpasd’appeldeconstructeurdeA,nid’éventuelautreopérateurd’affectation.Cecomportements’expliqueparlefaitquelesC.D.U.nesontmisesenœuvrequelorsquecelaestnécessaire.
Instruction2
Làencore,ilfautdistinguerlesdeuxcasprécédents.SiBn’apassurdéfinid’opérateurd’affectationd’unobjetde type B àunobjetde type A, l’instruction2 conduira àuneerreurdecompilationpuisqu’iln’existeraaucunepossibilitédeconvertirunobjetdetypeAenunobjetdetypeB.Enrevanche,sil’opérateurd’affectationenquestionexiste,ilseratoutsimplementutilisé.
Instruction3
Elleneposeaucunproblèmeparticulier.
Instruction4
Ici,b1etb2serontconvertisdansletypeAenutilisantleconstructeurA(B)avantd’êtretransmis à l’opérateur + (de A). Le résultat, de type A, sera affecté à a3, en utilisantl’opérateurd’affectationdeA—soitceluipardéfaut,soitceluiéventuellementsurdéfini.
Instruction5
L’expression a1 + a2 ne pose pas de problème puisqu’elle est évaluée à l’aide del’opérateur + de la classe A ; elle fournit un résultat de type A. En revanche, pourl’affectationdurésultatàb3,ilfautànouveaudistinguerlesdeuxcasdéjàévoqués.SiBasurdéfiniunopérateurd’affectationd’unobjetdetypeBàunobjetdetypeA,ilserautilisé ; si un tel opérateur n’a pas été surdéfini, l’instruction sera rejetée par lecompilateur(puisqu’iln’existeraalorsaucunepossibilitédeconversiondeBenA).
b.Enintroduisant,danslaclasseB,unopérateurdecastpermettantlaconversiondeBenA,delaforme:operatorB()
239
Chapitre13Latechniquedel’héritage
240
Rappels
Le concept d’héritage constitue l’un des fondements de la Programmation OrientéeObjet.IlpermetdedéfinirunenouvelleclasseBdite«dérivée»,àpartird’uneclasseexistanteA,dite«debase»;pourcefaire,onprocèdeainsi:
classB:publicA//ou:privateAou(depuislaversion3)protectedA
{//définitiondesmembressupplémentaires(donnéesoufonctions)
//ouredéfinitiondemembresexistantsdansA(donnéesoufonctions)
};
Avec public A, on parle de « dérivation publique » ; avec private A, on parle de«dérivationprivée»;avecprotectedA,onparlede«dérivationprotégée».
Modalitésd’accèsàlaclassedebaseLes membres privés d’une classe de base ne sont jamais accessibles aux fonctionsmembredesaclassedérivée.
Outre les « statuts » public ou privé (présentés au chapitre 3), il existe un statut« protégé ». Un membre protégé se comporte comme un membre privé pour unutilisateur quelconque de la classe ou de la classe dérivée,mais commeunmembrepublicpourlaclassedérivée.
D’autrepart,ilexistetroissortesdedérivation:
publique – lesmembresde la classedebase conservent leur statut dans la classedérivée;c’estlasituationlaplususuelle;
privée – tous lesmembres de la classe de base deviennent privés dans la classedérivée;
protégée (depuis la version 3) – les membres publics de la classe de basedeviennentmembresprotégésdelaclassedérivée;lesautresmembresconserventleurstatut.
Lorsqu’unmembre (donnéeou fonction)est redéfinidansuneclassedérivée, il restetoujourspossible(soitdanslesfonctionsmembredecetteclasse,soitpourunclientdecetteclasse)d’accéderauxmembresdemêmenomdelaclassedebase;ilsuffitpourcelad’utiliserl’opérateurderésolutiondeportée(::),sousréserve,biensûr,qu’untelaccèssoitautorisé.
241
AppeldesconstructeursetdesdestructeursSoitBuneclassedérivéed’uneclassedebaseA.Naturellement,dèslorsqueBpossèdeau moins un constructeur, la création d’un objet de type B implique obligatoirementl’appel d’un constructeur de B.Mais, de plus, ce constructeur de B doit prévoir desargumentsàdestinationd’unconstructeurdeA(uneexceptionalieusoitsiAn’apasdeconstructeur, soit si A possède un constructeur sans argument). Ces arguments sontprécisésdansladéfinitionduconstructeurdeB,commedanscetexemple:
B(intx,inty,charcoul):A(x,y);
Les arguments mentionnés pour A peuvent éventuellement l’être sous formed’expressions.
CasparticulierduconstructeurparrecopieEnplusdesrèglesci-dessus,ilfautajouterquesilaclassedérivéeBnepossèdepasdeconstructeurpar recopie, ilyauraappelduconstructeurpar recopiepardéfautdeB,lequelprocéderaainsi:
appel du constructeur par recopie de A (soit celui qui y a été défini, soit leconstructeurparrecopiepardéfaut);
initialisationdesmembresdonnéedeBquinesontpashéritésdeA.
En revanche, unproblème se pose lorsque la classe dérivéedéfinit explicitement unconstructeurparrecopie.Eneffet,danscecas,ilfauttenircomptedecequel’appeldececonstructeurparrecopieentraîneral’appel:
du constructeur de la classe de basementionné dans son en-tête, comme dans cetexemple (il s’agit ici d’un constructeur par recopie de la classe de base,mais ilpourraits’agirden’importequelautreconstructeur):B(B&b):A(b);//appelduconstructeurparrecopiedeA
//auquelseratransmiselapartiedeBhéritéedeA
//(grâceauxrèglesdecompatibilitéentre
//classedérivéeetclassedebase)
d’un constructeur sans argument, si aucun constructeur de la classe de base n’estmentionné dans l’en-tête ; dans ce cas, il est nécessaire que la classe de basedisposed’untelconstructeursansargument,fautedequoionobtiendraituneerreurdecompilation.
Conséquencesdel’héritage
242
Considérons la situation suivante, dans laquelle la classe A possède une fonctionmembref (dontnousneprécisonspaslesarguments)fournissantunrésultatdetypet(quelconque:typedebaseoutypedéfiniparl’utilisateur,éventuellementtypeclasse):
classAclassB:publicA
{.....{.....
public:};
tf(...);
.....
};
Aa;//aestdutypeA
Bb;//bestdutypeB,dérivédeA
Naturellement,unappeltelquea.f(...)aunsensetilfournitunrésultatdetypet.LefaitqueBhéritepubliquementdeApermetalorsdedonnerunsensàunappeltelque:
b.f(...)
La fonction f agira sur b, comme s’il était de type A.Le résultat fourni par f seracependanttoujoursdetypet,même,notamment,lorsqueletypetestprécisémentletypeA(lerésultatdefpourratoutefoisêtresoumisàd’éventuellesconversionss’ilestaffectéàunelvalue).
Casparticulierdel’opérateurd’affectationConsidéronsuneclasseBdérivantd’uneclasseA.
SilaclassedérivéeBn’apassurdéfinil’opérateurd’affectation,l’affectationdedeuxobjetsdetypeBsedéroulemembreàmembre,enconsidérantquela«partiehéritéedeA»constitueunmembre.Ainsi, lesmembrespropresàB sont traitéspar l’affectationprévuepourleurtype(pardéfautousurdéfinie,suivantlecas).LapartiehéritéedeAesttraitéeparl’affectationprévuedanslaclasseA.
Si laclassedérivéeBasurdéfini l’opérateur=, l’affectationdedeuxobjetsde typeBferanécessairementappelàl’opérateur=définidansB.CeluideAneserapasappelé,même s’il a été surdéfini. Il faudradoncque l’opérateur=deBprenne en chargetoutcequiconcernel’affectationd’objetsdetypeB,ycomprispourcequiestdesmembreshéritésdeA(quitteàfaireappelàl’opérateurd’affectationdeA).
Compatibilitéentreobjetsd’uneclassedebaseetobjetsd’uneclassedérivéeConsidérons:
classAclassB:publicA
{.....{.....
243
};};
Aa;//aestdutypeA
Bb;//bestdutypeB,dérivédeA
A*ada;//adaestunpointeursurdesobjetsdetypeA
B*adb;//adbestunpointeursurdesobjetsdetypeB
Ilexistedeuxconversionsimplicites:
d’unobjetd’untypedérivédansunobjetd’untypedebase.Ainsil’affectationa=best légale : elle revient à convertir b dans le type A (c’est-à-dire, en fait, à neconsidérerdebquecequiestdutypeA)etàaffectercerésultatàa(avecappel,soitde l’opérateur d’affectation de A si celui-ci a été surdéfini, soit de l’opérateurd’affectationpardéfautdeA).L’affectationinverseb=aest,quantàelle,illégale;
d’un pointeur sur une classe dérivée en un pointeur sur une classe de base.Ainsil’affectationada=adbestlégale,tandisqueadb=adaestillégale(ellepeutcependantêtreforcéeparemploidel’opérateurdecast:adb=(B*)ada).
244
Exercice102
ÉnoncéOndisposed’unfichiernommépoint.hcontenantladéclarationsuivantedelaclassepoint:
classpoint
{floatx,y;
public:
voidinitialise(floatabs=0.0,floatord=0.0)
{x=abs;y=ord;
}
voidaffiche()
{cout<<"Pointdecoordonnées:"<<x<<""<<y<<"\n";
}
floatabs(){returnx;}
floatord(){returny;}
};
a. Créer une classe pointb, dérivée de point comportant simplement une nouvellefonctionmembre nommée rho, fournissant la valeur du rayon vecteur (premièrecoordonnéepolaire)d’unpoint.
b.Même question, en supposant que lesmembres x et y ont été déclarés protégés(protected)danspoint,etnonplusprivés.
c.Introduireunconstructeurdanslaclassepointb.
d.Quellessontlesfonctionsmembreutilisablespourunobjetdetypepointb?
a. Ilsuffitdeprévoir,dansladéclarationdepointb,unenouvelle fonctionmembredeprototype:floatrho();
Toutefois,commelesmembresxetysontprivés,ilsrestentprivéspourlesfonctionsmembredesaclassedérivéepointb;cequisignifiequ’auseindeladéfinitionderho,ilfautfaireappelaux«fonctionsd’accès»depointquesontabsetord.Voicicequepourraitêtrenotreclassepointb(ici,nousavonsfournirhosousformed’unefonctionenligne):#include"point.h"//pourladéclarationdelaclassepoint
#include<math.h>
classpointb:publicpoint
{public:
floatrho()
245
{returnsqrt(abs()*abs()+ord()*ord());
}
};
Notezque, tellequ’elleaétédéfinie, laclassepointn’apasdonnénaissanceàunfichierobjet(puisquetoutessesfonctionsmembresontenligne).Ilenvademêmeicipourpointb.Aussi,pourutiliserpointbauseind’unprogramme,ilsuffirad’inclurelesfichierscontenant lesdéclarationsdepointb.Naturellement,dans lapratique, ilenirararementainsi;engénéral,ondevrafournirnonseulementlesdéclarationsdela classe de base et de sa classe dérivée, mais également les fichiers objetcorrespondantàleurscompilationsrespectives.
b.Ladéfinitionprécédenterestevalablemais,néanmoins,commelesmembresxetydepoint ont été déclarés protégés, ils sont accessibles aux fonctions membre de saclasse dérivée ; aussi est-il possible, dans la définition de rho, de les utiliser«directement».Voicicequepourraitdevenirnotrefonctionrho (toujours ici«enligne»):floatrho()//ilfautquexetysoient
{returnsqrt(x*x+y*y);//déclarés"protected"danspoint
}
c. Voici ce que pourrait être un constructeur à deux arguments (avec valeurs pardéfaut):pointb(floatc1=0.0,floatc2=0.0)
{initialise(c1,c2);
}
Là encore, si les membres x et y de point ont été déclarés «þprotégésþ», il estpossibled’écrireainsinotreconstructeur:pointb(floatc1=0.0,floatc2=0.0)
{x=c1;y=c2;//ilfautquexetysoient
}//déclarés"protected"danspoint
Notezqu’iciiln’estpaspossibleauconstructeurdepointbd’appelerunquelconqueconstructeurdepointpuisquecederniertypenepossèdepasdeconstructeur.
d. Un objet de type pointb peut utiliser n’importe laquelle des fonctions membrepubliques de point, c’est-à-dire initialise, affiche, abs et ord, ainsi que n’importelaquelledesfonctionsmembrepubliquesdepointb,c’est-à-direrhoouleconstructeurpointb.Notezd’ailleursqu’icileconstructeuretinitialisefontdoubleemploi:celaprovientd’unepartdecequepointnedisposepasdevéritableconstructeur,d’autrepartdeceque pointb n’apasdéfinidemembresdonnée supplémentaires,de sortequ’il n’y a rien de plus à faire pour initialiser un objet de type pointb que pourinitialiserunobjetdetypepoint.
246
Exercice103
ÉnoncéOndisposed’unfichierpoint.hcontenantladéclarationsuivantedelaclassepoint:
#include<iostream>
usingnamespacestd;
classpoint
{floatx,y;
public:
point(floatabs=0.0,floatord=0.0)
{x=abs;y=ord;
}
voidaffiche()
{cout<<"Coordonnées:"<<x<<""<<y<<"\n";
}
voiddeplace(floatdx,floatdy)
{x=x+dx;y=y+dy;
}
};
a.Créeruneclassepointcol,dérivéedepoint,comportant:
•unmembredonnéesupplémentairecl,detypeint,contenantla«couleur»d’unpoint;
•lesfonctionsmembresuivantes:affiche(redéfinie),quiaffichelescoordonnéesetlacouleurd’un
objetdetypepointcol;
colore(intcouleur),quipermetdedéfinirlacouleurd’unobjetde
typepointcol,
unconstructeurpermettantdedéfinirlacouleuretlescoordonnées(onneledéfinirapasenligne).
b.Queferaalorsprécisémentcetteinstruction:pointcol(2.5,3.25,5);
a.Lafonctioncoloreneposeaucunproblèmeparticulierpuisqu’elleagituniquementsurunmembredonnéepropreà pointcool.Encequiconcerne affiche, il est nécessairequ’ellepuisseafficherlesvaleursdesmembresxety,héritésdepoint.Commecesmembressontprivés(etnonprotégés),iln’estpaspossiblequelanouvelleméthodeaffiche de pointcol accède à eux directement. Elle doit donc obligatoirement faireappelàlaméthodeaffichedutypepoint;ilsuffit,pourcela,d’utiliserl’opérateurde
247
résolution de portée. Enfin, le constructeur de pointcol doit retransmettre auconstructeur de point les coordonnées qu’il aura reçues par ses deux premiersarguments.
Voicicequepourraitêtrenotreclassepointcol(ici,touteslesfonctionsmembre,saufleconstructeur,sontenligne):/******fichierpointcol.h:déclarationdepointcol******/
#include"point.h"
#include<iostream>
usingnamespacestd;
classpointcol:publicpoint
{intcl;
public:
pointcol(float=0.0,float=0.0,int=0);
voidcolore(intcoul)
{cl=coul;
}
voidaffiche()//affichedoitappeleraffichede
{point::affiche();//pointpourlescoordonnées
cout<<"couleur:"<<cl;//maiselleaaccèsàlacouleur
}
};
/******définitionduconstructeurdepointcol*****/
#include"point.h"
#include"pointcol.h"
pointcol::pointcol(floatabs,floatord,intcoul):point(abs,ord)
{cl=coul;//onpourraitaussiécrirecolore(coul);
}
Notezbienquel’onpréciseleconstructeurdepointdevantêtreappeléparceluidepointcol,auniveauduconstructeurdepointcol,etnondesadéclaration.
b.Ladéclarationpointcol(2.5,3.25,5)entraîne lacréationd’unemplacementpourunobjetdetypepointcol,lequelestinitialiséparappel,successivement:
duconstructeurdepoint,quireçoitenargumentlesvaleurs2.5et3.25(commeprévudansl’en-têteduconstructeurdepointcol)þ;
duconstructeurdepointcol.
248
Exercice104
ÉnoncéOnsupposequ’ondisposede lamêmeclassepoint (etdoncdu fichier point.h) quedans l’exercice précédent. Créer une classe pointcol possédant les mêmescaractéristiques que ci-dessus, mais sans faire appel à l’héritage. Quellesdifférencesapparaîtrontentrecetteclassepointcoletcelledel’exerciceprécédent,auniveaudespossibilitésd’utilisation?
La seuledémarchepossible consiste à créer une classe pointcol dans laquelle undesmembres donnée est lui-même de type point. Sa déclaration et sa définition seprésenteraientalorsainsi:
/*****fichierpointcol.h:déclarationdepointcol*****/
#include"point.h"
#include<iostream>
usingnamespacestd;
classpointcol
{pointp;
intcl;
public:
pointcol(float=0.0,float=0.0,int=0);
voidcolore(intcoul)
{cl=coul;
}
voidaffiche()
{p.affiche();//affichedoitappeleraffiche
cout<<"couleur:"<<cl;//dupointppourles
//coordonnées
}
};
/******définitionduconstructeurdepointcol*****/
#include"point.h"
#include"pointcol.h"
pointcol::pointcol(floatabs,floatord,intcoul):p(abs,ord)
{cl=coul;
}
Apparemment, il existe une analogie étroite entre cette classe pointcol et celle del’exercice précédent. Néanmoins, l’utilisateur de cette nouvelle classe ne peut plusfairedirectementappelauxfonctionsmembrehéritéesdepoint.Ainsi,pourappliquerlaméthodedeplaceàunobjetadetypepoint,ildevraitabsolumentécrire:a.p.deplace(...);or,celan’estpasautoriséici,puisquepestunmembreprivédepointcol.
249
Exercice105
ÉnoncéSoit une classe point ainsi définie (nous ne fournissons pas la définition de sonconstructeur):
classpoint
{intx,y;
public:
point(int=0,int=0);
friendintoperator==(point,point);
};
intoperator==(pointa,pointb)
{returna.x==b.x&&a.y==b.y;
}
Soitlaclassepointcol,dérivéedepoint:classpointcol:publicpoint
{intcl;
public:
pointcol(int=0,int=0,int=0);
//éventuellesfonctionsmembre
};
a.Siaetbsontdetypepointcoletpdetypepoint,lesinstructionssuivantessont-ellescorrecteset,sioui,quefont-elles?if(a==b)...//instruction1
if(a==p)...//instruction2
if(p==a)...//instruction3
if(a==5)...//instruction4
if(5==a)...//instruction5
b.Mêmesquestions,ensupposant,cettefois,quel’opérateur+aétédéfiniauseindepoint,sousformed’unefonctionmembre.
a. Les 5 instructions proposées sont correctes. D’une manière générale, x == y estinterprétécommeoperator==(x,y).Sixetysontdetypepoint,aucunproblèmenesepose bien sûr. Si l’un des opérandes est de type pointcol (ou les deux), il seraconverti implicitementdans le type point.Si l’undesopérandes estde type int, ilsera converti implicitementdans le type point (par utilisationdu constructeur à unargumentdepoint).
Encequiconcernelasignificationdelacomparaison,onvoitqu’ellerevientàneconsidérer d’un objet de type pointcol que ses coordonnées. Pour un entier, elle
250
revientàleconsidérercommeunpointayantcetentierpourabscisseetuneordonnéenulle.
N.B.Silesargumentsdeoperator=étaienttransmisparréférence,lesdeuxdernièresaffectationsseraientrejetées,àmoinsd’avoirenplusprévul’attributconst.
b.Cettefois,x==yestinterprétécommex.operator==(y).Sixestdetypepointetyd’untype pouvant se ramener au type point (c’est-à-dire soit du type pointcol qui seraconverti implicitement en un type de base point, soit d’un type entier qui seraconverti implicitement enun type point par l’intermédiairedu constructeur), aucunproblèmenesepose(c’estlecasdelatroisièmeinstruction).
Sixestdetypepointcoletyd’untypepouvantseramenerautypepoint,onretrouvelecas précédent, dans lamesure où la fonctionmembre operator ==, héritée de point,peuttoujourss’appliqueràunobjetdetypepoint(c’estlecasdesinstructions1,2et4).
Enrevanche,sixestdetypeint,iln’estpluspossibledeluiappliquerunefonctionmembre.C’estcequisepassedansladernièreinstruction,quiseradoncrejetéeàlacompilation.
N.B. Si l’unique argument de operator= était transmis par référence, la quatrièmeaffectationseraitrejetée,àmoinsd’avoirprévuenplusl’attributconst.
251
Exercice106
ÉnoncéSoituneclassevectpermettantdemanipulerdes«vecteursdynamiques»d’entiers(c’est-à-dire dont la dimension peut être fixée aumoment de l’exécution) dont ladéclaration(fourniedanslefichiervect.h)seprésenteainsi:
classvect
{intnelem;//nombred'éléments
int*adr;//adressezonedynamiquecontenantleséléments
public:
vect(int);//constructeur(préciselatailleduvecteur)
~vect();//destructeur
int&operator[](int);//accèsàunélémentparson"indice"
};
Onsupposequeleconstructeuralloueeffectivementl’emplacementnécessairepourle nombre d’entiers reçu en argument et que l’opérateur [] peut être utiliséindifféremmentdansuneexpressionouàgauched’uneaffectation.
Créer une classe vectb, dérivée de vect, permettant de manipuler des vecteursdynamiques,danslesquelsonpeutfixerles«bornes»desindices,lesquellesserontfournies au constructeur de vectb. La classe vect apparaîtra ainsi comme un casparticulierdevectb(unobjetdetypevectétantunobjetdetypevectbdans lequel lalimiteinférieuredel’indiceest0).
Onnesepréoccuperapas,ici,desproblèmeséventuellementposésparlarecopieoul’affectationd’objetsdetypevectb.
Nousprévoirons,dansvectb,deuxmembresdonnéesupplémentaires(debutetfin)pourconserverlesbornesdel’indice(entouterigueur,onpourraitsecontenterd’unmembresupplémentairecontenantlalimiteinférieure,sachantquelavaleursupérieurepourraits’endéduireàpartirdelaconnaissancedelatailleduvecteur;toutefois,cettedernièreinformationn’étantpaspublique,nousrencontrerionsdesproblèmesd’accès!).
Manifestement,vectbnécessiteunconstructeuràdeuxargumentsentierscorrespondantauxbornesdel’indice;sonen-têtepourraitcommencerainsi:
vectb(intd,intf)
Commel’appeldececonstructeurentraîneraautomatiquementceluiduconstructeurdevect, iln’estpasquestiondefaire l’allocationdynamiquedenotrevecteurdansvectb.
252
Au contraire, nous réutilisons le travail effectué par vect, auquel nous transmettronssimplement lenombred’éléments souhaités, c’est-à-dire ici f-d+1.Voici l’en-têtecompletduconstructeurdevectb:
vectb(intd,intf):vect(f-d+1)
Latâchespécifiquedevectb se limiteraà renseigner lesvaleursdesmembresdonnéedebutetfin.
Aucun destructeur n’est nécessaire pour vectb, dans la mesure où son constructeurn’alloueaucunautreemplacementdynamiquequeceluiallouéparvect.
Encequiconcernel’opérateur[],onpeutpenserquevectb l’héritedevectetque,parconséquent, il n’est pas nécessaire de le surdéfinir. Toutefois, la notation t[i] nedésigneplusforcémentl’élémentderangid’unobjetdetypevectb.Or,manifestement,on souhaitera qu’il en aille toujours ainsi. Il faut donc redéfinir [] pour vectb, quitted’ailleursàréutiliserl’opérateurdéfinidansvect.
Voicicequepourraitêtrenotreclassevectb(ici,onnetrouvequ’unedéfinition,puisquelesdeuxfonctionsmembreontétédéfiniesenligne):
#include"vect.h"
classvectb:publicvect
{intdebut,fin;
public:
vectb(intd,intf):vect(f-d+1)
{debut=d;fin=f;
}
int&operator[](inti)
{returnvect::operator[](i-debut);
}
};
1.Silemembredonnéeadravaitétédéclaréprotégé(protected)danslaclassevect,nousaurionspuredéfinirl’opérateur[]devectb,sansfaireappelàceluidevect:int&operator[](inti)
{returnadr[i-debut];
}
2.Aucuneprotectiond’indicesn’est àprévoirdans vectb, dès lorsqu’elle adéjà étéprévuedansvect.
253
Exercice107
ÉnoncéSoit une classe int2d (telle que celle créée dans l’exercice 91) permettant demanipuler des « tableaux dynamiques » d’entiers à deux dimensions dont ladéclaration(fourniedanslefichierint2d.h)seprésenteainsi:
classint2d
{intnlig;//nombrede"lignes"
intncol;//nombrede"colonnes"
int*adv;//adresseemplacementdynamiquecontenantlesvaleurs
public:
int2d(intnl,intnc);//constructeur
~int2d();//destructeur
int&operator()(int,int);//accèsàunélément,parses2"indices"
};
Onsupposequeleconstructeuralloueeffectivementl’emplacementnécessaireetquel’opérateur[]peutêtreutiliséindifféremmentdansuneexpressionouàgauched’uneaffectation.
Créer une classe int2db, dérivée de int2d, permettant de manipuler des tableauxdynamiques,dans lesquelsonpeut fixer les«bornes» (valeurminimaleetvaleurmaximale)desdeuxindices; lesquatrevaleurscorrespondantesserontfourniesenargumentsduconstructeurdeint2db.
Onnesepréoccuperapas,ici,desproblèmeséventuellementposésparlarecopieoul’affectationd’objetsdetypeint2db.
Il suffit, en fait, de généraliser à la classe int2d le travail réalisé dans l’exerciceprécédentpourlaclassevect.Voicicequepourraitêtrenotreclasseint2db (ici,onnetrouve qu’une définition, dans la mesure où les fonctions membre de int2db ont étéfourniesenligne):
#include"int2d.h"
classint2db:publicint2d
{intligdeb,ligfin;//bornes(mini,maxi)premierindice
intcoldeb,colfin;//bornes(mini,maxi)secondindice
public://constructeur
int2db(intld,intlf,intcd,intcf):int2d(lf-ld+1,cf-cd+1)
{ligdeb=ld;ligfin=lf;
coldeb=cd;colfin=cf;
}
int&int2db::operator()(inti,intj//redéfinitiondeoperator()
{returnint2d::operator()(i-ligdeb,j-coldeb);
}
};
254
Notezque,lànonplus,aucuneprotectiond’indicesupplémentairen’estàprévoirdansint2db,dèslorsqu’elleadéjàétéprévuedansint2d.
255
Exercice108
ÉnoncéSoituneclassevectpermettantdemanipulerdes«vecteursdynamiques»d’entiers,dont la déclaration (fournie dans un fichier vect.h) se présente ainsi (notez laprésencedemembresprotégés):
classvect
{protected://enprévisiond'uneéventuelleclassedérivée
intnelem;//nombred'éléments
int*adr;//adressezonedynamiquecontenantleséléments
public:
vect(int);//constructeur
~vect();//destructeur
int&operator[](int);//accèsàunélémentparson"indice"
};
Onsupposequeleconstructeuralloueeffectivementl’emplacementnécessaireetquel’opérateur[]peutêtreutiliséindifféremmentdansuneexpressionouàgauched’uneaffectation. En revanche, comme on peut le voir, cette classe n’a pas prévu deconstructeur par recopie et elle n’a pas surdéfini l’opérateur d’affectation.L’affectation et la transmission par valeur d’objets de type vect posent donc les«problèmeshabituels».
Créeruneclassevectok,dérivéedevect,tellequel’affectationetlatransmissionparvaleur d’objets de type vectok s’y déroulent convenablement. Pour faciliterl’utilisation de cette nouvelle classe, introduire une fonction membre taille
fournissantladimensiond’unvecteur.
Écrireunpetitprogrammed’essai.
Manifestement,laclassevectokn’apasbesoindedéfinirdenouveauxmembresdonnée.Pourdéclarerdesobjetsdetypevectok,ilfaudrapouvoirenpréciserladimension,cequi signifie que vectok devra absolument disposer d’un constructeur approprié. Cedernier se contentera toutefois de retransmettre la valeur reçue en argument auconstructeurdevect ; il auradoncuncorpsvide.Notezqu’iln’estpasnécessairedeprévoirundestructeur(carceluidevectseraappeléencasdedestructiond’unobjetdetypevectoketiln’yariendeplusàfaire).
Notezquepourgérer convenablement la recopieou l’affectationd’objets, nousnouscontenterons de la méthode déjà rencontrée qui consiste à dupliquer les objets
256
concernés(enenfaisantune«copieprofonde»).
Poursatisfaireauxcontraintesde l’énoncé, ilnousfautdoncprévoirdedéfinir,dansvectok,unconstructeurparrecopie.Ilfautalorstenircomptedecequelarecopied’unobjetdetypevectok(quiferadoncappelàceconstructeur)entraîneraalorsl’appelduconstructeur de vect qui sera indiqué dans l’en-tête du constructeur par recopie devectok,dumoinssiunetelleinformationestprécisée(danslecascontraire,ilyauraitappeld’unconstructeursansargumentdevect,cequin’estpaspossibleici).Ici,nousdisposonsdoncdedeuxpossibilités:
demanderl’appelduconstructeurparrecopiedevect,cequiconduitàceten-tête:vectok::vectok(vectok&v):vect(v)
(rappelonsquel’argumentv,detypevectok,seraimplicitementconvertientypevect,pourpouvoirêtretransmisauconstructeurvect).
Cettefaçondefaireconduitàlacréationd’unobjetdetypevect,obtenuparrecopie(par défaut) de v. Il faudra alors compléter le travail en créant un nouvelemplacementdynamiquepourlevecteuretenadaptantcorrectementlavaleurdeadr.
demanderl’appelduconstructeuràunargumentdevect,cequiconduitàceten-tête:vectok::vectok(vectok&v):vect(v.nelem)
Cette fois, il y aura création d’un nouvel objet de type vect, avec son propreemplacement dynamique, dans lequel il faudra néanmoins recopier les valeurs de v.C’estcettedernièresolutionquenouschoisironsici.
Toujours pour satisfaire aux contraintes de l’énoncé, nous devons surdéfinirl’affectation dans la classe vectok. Ici, aucun choix ne se présente. Nous utiliseronsl’algorithmeprésentédansl’exercice87.
Voicicequepourraientêtreladéclarationetladéfinitiondenotreclassevectok:/****déclarationdelaclassevectok****/
#include"vect.h"
classvectok:publicvect
{//pasdenouveauxmembresdonnée
public:
vectok(intdim):vect(dim)//constructeurdevectok:secontente
{}//depasserdimauconstructeurdevect
vectok(vectok&);//constructeurparrecopiedevectok
vectok&operator=(vectok&);//surdéfinitiondel'affectationdevectok
inttaille()
{returnnelem;
}
};
/*****définitionduconstructeurparrecopiedevectok*****/
//ildoitobligatoirementprévoirdesargumentspourunconstructeur
//(quelconque)devect(icileconstructeuràunargument)
vectok::vectok(vectok&v):vect(v.nelem)//ouconstvectok&v
{inti;
for(i=0;i<nelem;i++)adr[i]=v.adr[i];
257
}
/*****définitiondel'affectationentrevectok*****/
vectok&vectok::operator=(vectok&v)//ouconstvectok&v
{if(this!=&v)
{deleteadr;
adr=newint[nelem=v.nelem];
inti;
for(i=0;i<nelem;i++)adr[i]=v.adr[i];
}
return(*this);
}
Voiciunexempledeprogrammeutilisantlaclassevectok,accompagnédurésultatdesonexécution:
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
#include"vectok.h"
main()
{voidfct(vectok);
vectokv(6);
inti;
for(i=0;i<v.taille();i++)v[i]=i;
cout<<"vecteurv:";
for(i=0;i<v.taille();i++)cout<<v[i]<<"";
cout<<"\n";
vectokw(3);
w=v;
cout<<"vecteurw:";
for(i=0;i<w.taille();i++)cout<<w[i]<<"";
cout<<"\n";
fct(v);
}
voidfct(vectokv)
{cout<<"vecteurreçuparfct:"<<"\n";
inti;
for(i=0;i<v.taille();i++)cout<<v[i]<<"";
}
vecteurv:012345
vecteurw:012345
vecteurreçuparfct:
012345
Voici,àtitreindicatif,cequeseraitladéfinitionduconstructeurparrecopiedevectok,danslecasoùl’onferaitappelauconstructeurparrecopie(pardéfaut)devect:
vectok::vectok(vectok&v):vect(v)//ouconstvectok&v
{nelem=v.nelem;
adr=newint[nelem];
inti;
for(i=0;i<nelem;i++)
adr[i]=v.adr[i];
}
Ici,ilafalluallouerunnouvelemplacementpourunvecteur,cequin'étaitpaslecas
258
lorsque l’on faisait appel au constructeur à un argument de vect (puisque ce dernierfaisaitdéjàunetelleallocation).
259
Chapitre14L’héritagemultiple
260
Rappels
Depuis la version 2.0, C++ autorise l’héritagemultiple : une classe peut hériter deplusieurs autres classes, comme dans cet exemple où la classe pointcol héritesimultanémentdesclassespointetcoul:
classpointcol:publicpoint,publiccoul//chaquedérivation,icipublique
//pourraitêtreprivéeouprotégée
{//définitiondesmembressupplémentaires(donnéesoufonctions)
//ouredéfinitiondemembresexistantsdéjàdanspointoucoul
};
Chacunedesdérivationspeutêtrepublique,privéeouprotégée.Lesmodalitésd’accèsauxmembresdechacunedesclassesdebaserestentlesmêmesquedanslecasd’unedérivation«simple».L’opérateurderésolutiondeportée(::)peutêtreutilisé:
soitlorsquel’onveutaccéderàunmembred’unedesclassesdebase,alorsqu’ilestredéfinidanslaclassedérivée,
soit lorsquedeuxclassesdebasepossèdentunmembredemêmenometqu’il fautalorspréciserceluiquinousintéresse.
AppeldesconstructeursetdesdestructeursLacréationd’unobjetentraînel’appelduconstructeurdechacunedesclassesdebase,dans l’ordre où ces constructeurs sont mentionnés dans la déclaration de la classedérivée(ici,pointpuiscoulpuisquenousavonsécritclasspointcol :publicpoint,publiccoul).Lesdestructeurssontappelésdansl’ordreinverse.
Leconstructeurdelaclassedérivéepeutmentionner,danssonen-tête,desinformationsà retransmettreàchacundesconstructeursdesclassesdebase (ceseragénéralementindispensable,saufsiuneclassedebasepossèdeunconstructeursansargumentousiellenedisposepasdutoutdeconstructeur).Envoiciunexemple:
pointcoul(.....):point(.....),coul(.....)
|||
|||
argumentsargumentsarguments
pointcoulpointcoul
ClassesvirtuellesParlebiaisdedérivationssuccessives,ilesttoutàfaitpossiblequ’uneclassehéritedeuxfoisd’unemêmeclasse.EnvoiciunexempledanslequelDhéritedeuxfoisdeA:
261
classB:publicA
{.....};
classC:publicA
{.....};
classD:publicB,publicC
{.....};
Dans ce cas, les membres donnée de la classe en question (A dans notre exemple)apparaissent deux fois dans la classe dérivée de deuxième niveau (ici D).Naturellement,ilestnécessairedefaireappelàl’opérateurderésolutiondeportée(::)pour lever l’ambiguïté. Si l’on souhaite que de tels membres n’apparaissent qu’uneseulefoisdanslaclassedérivéededeuxièmeniveau,ilfaut,danslesdéclarationsdesdérivéesdepremierniveau(iciBetC)déclareravecl’attributvirtuallaclassedontonveutéviterladuplication(iciA).
Voici comment on procéderait dans l’exemple précédent (le mot virtual peut êtreindifféremmentplacéavantouaprèslemotpublicoulemotprivate):
classB:publicvirtualA
{.....};
classC:publicvirtualA
{.....};
classD:publicB,publicC
{.....};
Lorsqu’onaainsidéclaréuneclassevirtuelle, ilestnécessaireque lesconstructeursd’éventuelles classes dérivées puissent préciser les informations à transmettre auconstructeurdecetteclassevirtuelle(danslecasusueloùl’onautoriseladuplication,ceproblèmeneseposeplus ;eneffet,chaqueconstructeur transmet les informationsauxclassesascendantesdontlesconstructeurstransmettent,àleurtour,lesinformationsaux constructeurs de chacune des occurrences de la classe en question – cesinformationspouvantéventuellementêtredifférentes).Danscecas,onleprécisedansl’en-tête du constructeur de la classe dérivée, en plus des arguments destinés auxconstructeurs des classes du niveau immédiatement supérieur, comme dans cetexemple:
D(.....):B(.....),C(.....),A(.....)
||||
||||
argumentsargumentsargumentsarguments
deDpourBpourCpourA
Deplus,danscecas, lesconstructeursdesclasses BetC (quiontdéclaréque A était«virtuelle»)n’aurontplusàspécifierd’informationspourunconstructeurdeA.
Enfin,leconstructeurd’uneclassevirtuelleesttoujoursappeléavantlesautres.
262
Exercice109
ÉnoncéQuelsserontlesrésultatsfournisparceprogramme:
#include<iostream>
usingnamespacestd;
classA
{intn;
floatx;
public:
A(intp=2)
{n=p;x=1;
cout<<"**constructionobjetA:"<<n<<""<<x<<"\n";
}
};
classB
{intn;
floaty;
public:
B(floatv=0.0)
{n=1;y=v;
cout<<"**constructionobjetB:"<<n<<""<<y<<"\n";
}
};
classC:publicB,publicA
{intn;
intp;
public:
C(intn1=1,intn2=2,intn3=3,floatv=0.0):A(n1),B(v)
{n=n3;p=n1+n2;
cout<<"**constructionobjetC:"<<n<<""<<p<<"\n";
}
};
main()
{Cc1;
Cc2(10,11,12,5.0);
}
L’objetc1estcrééparappelssuccessifsdesconstructeursdeB,puisdeA(ordreimposéparladéclarationdelaclasseC,etnonparl’en-têteduconstructeurdeC!).Lejeudelatransmissiondesargumentsetdesargumentspardéfautconduitaurésultatsuivant:
**constructionobjetB:10
**constructionobjetA:11
**constructionobjetC:33
**constructionobjetB:15
**constructionobjetA:101
**constructionobjetC:1221
263
Exercice110
ÉnoncéMême question que précédemment, en remplaçant simplement l’en-tête duconstructeurdeCpar:
C(intn1=1,intn2=2,intn3=3,floatv=0.0):B(v)
Ici,commeleconstructeurdeCn’aprévuaucunargumentpourunéventuelconstructeurdeA, il y aura appel d’un constructeur sans argument, c’est-à-dire, en fait, appel duconstructeurdeA,avectouteslesvaleursprévuespardéfaut.Voicilerésultatobtenu:
**constructionobjetB:10
**constructionobjetA:21
**constructionobjetC:33
**constructionobjetB:15
**constructionobjetA:21
**constructionobjetC:1221
264
Exercice111
EnoncéMêmequestionquedansl’exercice58,ensupposantquel’en-têteduconstructeurdeCestlasuivante:
C(intn1=1,intn2=2,intn3=3,floatv=0.0)
Cettefois,laconstructiond’unobjetdetypeCentraîneral’appeld’unconstructeursansargument,àlafoispourBetpourA.Voicilesrésultatsobtenus:
**constructionobjetB:10
**constructionobjetA:21
**constructionobjetC:33
**constructionobjetB:10
**constructionobjetA:21
**constructionobjetC:1221
265
Exercice112
ÉnoncéQuelsserontlesrésultatsfournisparceprogramme:
#include<iostream>
usingnamespacestd;
classA
{intna;
public:
A(intnn=1)
{na=nn;cout<<"$$constructionobjetA"<<na<<"\n";
}
};
classB:publicA
{floatxb;
public:
B(floatxx=0.0)
{xb=xx;cout<<"$$constructionobjetB"<<xb<<"\n";
}
};
classC:publicA
{intnc;
public:
C(intnn=2):A(2*nn+1)
{nc=nn;
cout<<"$$constructionobjetC"<<nc<<"\n";
}
};
classD:publicB,publicC
{intnd;
public:
D(intn1,intn2,floatx):C(n1),B(x)
{nd=n2;
cout<<"$$constructionobjetD"<<nd<<"\n";
}
};
main()
{Dd(10,20,5.0);
}
Laconstructiond’unobjetdetypeDentraînera l’appeldesconstructeursdeBetdeC,lesquels,avantleurexécution,appellerontchacununconstructeurdeA:danslecasdeB,ilyauraappeld’unconstructeursansargument(puisquel’en-têtedeBnementionnepas de liste d’arguments pour A) ; en revanche, dans le cas de C, il s’agira (plusclassiquement)d’unconstructeuràunargument,commementionnédansl’en-têtedeC.
Notezbienqu’ilyacréationdedeuxobjetsdetypeA.Voicilesrésultatsobtenus:$$constructionobjetA1
$$constructionobjetB5
266
$$constructionobjetA21
$$constructionobjetC10
$$constructionobjetD20
267
Exercice113
ÉnoncéTransformerleprogrammeprécédent,demanièrequ’unobjetdetypeDnecontiennequ’une seule fois les membres de A (qui se réduisent en fait à l’entier na). Ons’arrangera pour que le constructeur de A soit appelé avec la valeur 2*nn+1, danslaquellenndésignel’argumentduconstructeurdeC.
DansladéclarationdesclassesBetC,ilfautindiquerquelaclasseAest«virtuelle»,de façon qu’elle ne soit incluse qu’une fois dans d’éventuelles descendantes de cesclasses.D’autre part, le constructeur de D doit prévoir, outre les arguments pour lesconstructeursdeBetdeC,ceuxdestinésàunconstructeurdeA.
Enrésumé,ladéclarationdeAresteinchangée,celledeBesttransforméeen:classB:publicvirtualA
{//leresteestinchangé
}
CelledeCesttransforméedefaçonanalogue:classC:publicvirtualA
{//leresteestinchangé
}
Enfin,dansD,l’en-têteduconstructeurdevient:D(intn1,intn2,floatx):C(n1),B(x),A(2*n1+1)
À titre indicatif, voici les résultats que fournirait le programme précédent ainsitransformé:
$$constructionobjetA21
$$constructionobjetB5
$$constructionobjetC10
$$constructionobjetD20
268
Exercice114
ÉnoncéOnsouhaitecréeruneclasse liste permettantdemanipulerdes« listes chaînées»dans lesquelles la nature de l’information associée à chaque « nœud» de la listen’estpasconnue(parlaclasse).Unetellelistecorrespondraauschémasuivant:
Ladéclarationdelaclasselisteseprésenteraainsi:structelement//structured'unélémentdeliste
{element*suivant;//pointeursurl'élémentsuivant
void*contenu;//pointeursurunobjetquelconque
};
classliste
{element*debut;//pointeursurpremierélément
//autresmembresdonnéeséventuels
public:
liste();//constructeur
~liste();//destructeur
voidajoute(void*);//ajouteunélémentendébutde
liste
void*premier();//positionnesurpremierélément
void*prochain();//positionnesurprochainélément
intfini();
};
La fonction ajoute devra ajouter, en début de liste, un élément pointant surl’informationdont l’adresse est fournie en argument (void*). Pour « explorer » laliste,onaprévutroisfonctions:
•premier,quifourniral’adressedel’informationassociéeaupremiernœuddelalisteetqui,enmêmetemps,prépareraleprocessusdeparcoursdelaliste;
•prochain,quifourniral’adressedel’informationassociéeau«prochainnœud»;desappelssuccessifsdeprochaindevrontpermettredeparcourirlaliste(sans
269
qu’ilsoitnécessaired’appeleruneautrefonction);
•fini,quipermettradesavoirsilafindelisteestatteinteounon.
1.Compléterladéclarationprécédentedelaclasselisteetenfournirladéfinitiondemanièrequ’ellefonctionnecommedemandé.
2.Soitlaclassepointsuivante:classpoint
{intx,y;
public:
point(intabs=0,intord=0){x=abs;y=ord;}
voidaffiche(){cout<<"Coordonnées:"<<x<<""<<y<<"\n";}
};
Créeruneclasseliste_points,dérivéeàlafoisdelisteetdepoint,pourqu’ellepuissepermettre de manipuler des listes chaînées de points, c’est-à-dire des listescomparablesàcellesprésentéesci-dessus,etdanslesquellesl’informationassociéeestdetypepoint.Ondevrapouvoir,notamment:
•ajouterunpointendébutd’unetelleliste;
• disposerd’une fonctionmembre affiche affichant les informations associées àchacundespointsdelalistedepoints.
3.Écrireunpetitprogrammed’essai.
1. Manifestement, les fonctions premier et prochain nécessitent un « pointeur sur unélément courant ». Il sera membre donnée de la classe liste. Nous conviendrons(classiquement) que la fin de liste est matérialisée par un nœud comportant unpointeur « nul ».La classe liste devra disposer d’un constructeur dont le rôle selimiteraàl’initialiseràune«listevide»,cequis’obtiendrasimplementenplaçantunpointeurnulcommeadressededébutdeliste(cettefaçondeprocédersimplifiegrandement l’algorithme d’ajout d’un élément en début de liste, puisqu’elle évited’avoiràdistinguerdesautreslecasdelalistevide).
Comme un objet de type liste est amené à créer différents emplacementsdynamiques,ilestnécessairedeprévoirlalibérationdecesemplacementslorsquel’objet est détruit. Il faudra donc prévoir un destructeur, chargé de détruire lesdifférents nœuds de la liste. À ce propos, notez qu’il n’est pas possible ici dedemanderaudestructeurdedétruireégalementlesinformationsassociées;eneffet,cen’estpas l’objetde type liste qui a alloué ces emplacements : ils sont sous laresponsabilitédel’utilisateurdelaclasseliste.
Voicicequepourraitêtrenotreclasselistecomplète:
270
#include<stdlib.h>//pourNULL
structelement//structured'unélémentdeliste
{element*suivant;//pointeursurl'élémentsuivant
void*contenu;//pointeursurunobjetquelconque
};
classliste
{element*debut;//pointeursurpremierélément
element*courant;//pointeursurélémentcourant
public:
liste()//constructeur
{debut=NULL;
courant=debut;//parsécurité
}
~liste();//destructeur
voidajoute(void*);//ajouteunélémentendébutdeliste
void*premier()//positionnesurpremierélément
{courant=debut;
if(courant!=NULL)return(courant->contenu);
elsereturnNULL;
}
void*prochain()//positionnesurprochainélément
{if(courant!=NULL)
{courant=courant->suivant;
if(courant!=NULL)return(courant->contenu);
}
returnNULL;
}
intfini(){return(courant==NULL);}
};
liste::~liste()
{element*suiv;
courant=debut;
while(courant!=NULL)
{suiv=courant->suivant;deletecourant;courant=suiv;}
}
voidliste::ajoute(void*chose)
{element*adel=newelement;
adel->suivant=debut;
adel->contenu=chose;
debut=adel;
}
2. Comme nous le demande l’énoncé, nous allons donc créer une classe liste_pointspar:classliste_points:publicliste,publicpoint
Notezquecethéritage,apparemmentnaturel,conduitnéanmoinsàintroduire,danslaclasseliste_points,deuxmembresdonnée(xety)n’ayantaucunintérêtparlasuite.
En revanche, la création des fonctions membre demandées devient extrêmementsimple. En effet, la fonction d’insertion d’un point en début de liste peut être lafonction ajoute de la classe liste : nous n’aurons donc même pas besoin de lasurdéfinir.Encequiconcernelafonctiond’affichagedetouslespointsdelaliste(quenousnommeronségalementaffiche),illuisuffiradefaireappel:
–auxfonctionspremier,prochainetfinidelaclasselistepourleparcoursdelalistedepointsþ;
–àlafonctionaffichedelaclassepointpourl’affichaged’unpoint.
271
Nousaboutissonsàceci:classliste_points:publicliste,publicpoint
{public:
liste_points(){}
voidaffiche();
};
voidliste_points::affiche()
{point*ptr=(point*)premier();
while(!fini()){ptr->affiche();ptr=(point*)prochain();}
}
3.Exempledeprogrammed’essai:#include"listepts.h"
main()
{liste_pointsl;
pointa(2,3),b(5,9),c(0,8);
l.ajoute(&a);l.affiche();cout<<"---------\n";
l.ajoute(&b);l.affiche();cout<<"---------\n";
l.ajoute(&c);l.affiche();cout<<"---------\n";
}
Àtitreindicatif,voicilesrésultatsfournisparceprogramme:Coordonnées:23
---------
Coordonnées:59
Coordonnées:23
---------
Coordonnées:08
Coordonnées:59
Coordonnées:23
---------
272
Chapitre15Lesfonctionsvirtuelles
273
Rappels
Typagestatiquedesobjets(ouligaturedynamiquedesfonctions)Lesrèglesdecompatibilitéentreuneclassedebaseetuneclassedérivéepermettentd’affecteràunpointeursuruneclassedebase lavaleurd’unpointeursuruneclassedérivée. Toutefois, par défaut, le type des objets pointés est défini lors de lacompilation.Parexemple,avec:
classAclassB:publicA
{.....{.....
public:public:
voidfct(...);voidfct(...);
..........
};};
A*pta;
B*ptb;
uneaffectationtellequepta=ptbestautorisée.Néanmoins,quelquesoitlecontenudepta(autrementdit,quelquesoitl’objetpointéparpta),pta->fct(...)appelletoujourslafonctionfctdelaclasseA.
LesfonctionsvirtuellesL’emploi des fonctions virtuelles permet d’éviter les problèmes inhérents au typagestatique.Lorsqu’une fonction est déclarée virtuelle (mot-clé virtual) dans une classe,les appels à une telle fonction ou à n’importe laquelle de ses redéfinitions dans desclasses dérivées sont « résolus » aumoment de l’exécution, selon le typede l’objetconcerné. On parle de typage dynamique des objets (ou de ligature dynamique desfonctions).Parexemple,avec:
classAclassB:publicA
{.....{.....
public:public:
virtualvoidfct(...);voidfct(...);
..........
};};
A*pta;
l’instruction pta->fct (...) appellera la fonction fct de la classe correspondantréellementautypedel’objetpointéparpta.
N.B. Ilpeutyavoir ligaturedynamiquemêmeendehorsde l’utilisationdepointeurs
274
(voyez,parexemple,l’exercice65).
Règles Le mot-clé virtual ne s’emploie qu’une fois pour une fonction donnée ; plusprécisément,ilnedoitpasaccompagnerlesredéfinitionsdecettefonctiondanslesclassesdérivées.
Uneméthodedéclaréevirtuelledansuneclassedebasepeutnepasêtre redéfiniedanssesclassesdérivées.
Unefonctionvirtuellepeutêtresurdéfinie(chaquefonctionsurdéfiniepouvantêtreounepasêtrevirtuelle).
Unconstructeurnepeutpasêtrevirtuel,undestructeurpeutl’être.
Parsanaturemême,lemécanismedeligaturedynamiqueestlimitéàunehiérarchiede classes ; souvent, pour qu’il puisse s’appliquer à toute une bibliothèque declasses, on sera amené à faire hériter toutes les classes de la bibliothèque d’unemêmeclassedebase.
LesfonctionsvirtuellespuresUne«fonctionvirtuellepure»sedéclareavecuneinitialisationàzéro,commedans:
virtualvoidaffiche()=0;
Lorsqu’uneclassecomporteaumoinsune fonctionvirtuellepure,elleestconsidéréecomme«abstraite»,c’est-à-direqu’iln’estpluspossibledecréerdesobjetsdesontype.
Unefonctiondéclaréevirtuellepuredansuneclassedebasepeutnepasêtredéclaréedans une classe dérivée et, dans ce cas, elle est à nouveau implicitement fonctionvirtuellepuredecetteclassedérivée(avantlaversion3.0,unefonctionvirtuellepuredevait obligatoirement être redéfinie dans une classe dérivée ou déclarée à nouveauvirtuellepure).
275
Exercice115
ÉnoncéQuelsrésultatsproduiraceprogramme:
#include<iostream>
usingnamespacestd;
classpoint
{protected://pourquexetysoientaccessiblesàpointcol
intx,y;
public:
point(intabs=0,intord=0){x=abs;y=ord;}
virtualvoidaffiche()
{cout<<"Jesuisunpoint\n";
cout<<"mescoordonnéessont:"<<x<<""<<y<<"\n";
}
};
classpointcol:publicpoint
{shortcouleur;
public:
pointcol(intabs=0,intord=0,shortcl=1):point(abs,ord)
{couleur=cl;
}
voidaffiche()
{cout<<"Jesuisunpointcoloré\n";
cout<<"mescoordonnéessont:"<<x<<""<<y;
cout<<"etmacouleurest:"<<couleur<<"\n";
}
};
main()
{
pointp(3,5);point*adp=&p;
pointcolpc(8,6,2);pointcol*adpc=&pc;
adp->affiche();adpc->affiche();//instructions1
cout<<"-----------------\n";
adp=adpc;
adp->affiche();adpc->affiche();//instructions2
}
Danslesinstructions1,adp(detypepoint*)pointesurunobjetdetypepoint,tandisqueadpc(detypepointcol*)pointesurunobjetdetypepointcol.Ilyaappeldelafonctionaffiche, respectivement de point et de pointcol ; l’existence du « typage dynamique »n’apparaît pas clairement puisque,même en son absence (c’est-à-dire si la fonctionaffichen’avaitpasétédéclaréevirtuelle),onauraitobtenulemêmerésultat.
Enrevanche,danslesinstructions2,adp(detypepoint*)pointemaintenantsurunobjetde type pointcol. Grâce au typage dynamique, adp->affiche() appelle bien la fonctionaffichedutypepointcol.
276
Voicilesrésultatscompletsfournisparceprogramme:Jesuisunpoint
mescoordonnéessont:35
Jesuisunpointcoloré
mescoordonnéessont:86etmacouleurest:2
-----------------
Jesuisunpointcoloré
mescoordonnéessont:86etmacouleurest:2
Jesuisunpointcoloré
mescoordonnéessont:86etmacouleurest:2
277
Exercice116
ÉnoncéQuelsrésultatsproduiraceprogramme:
#include<iostream>
usingnamespacestd;
classpoint
{intx,y;
public:
point(intabs=0,intord=0){x=abs;y=ord;}
virtualvoididentifie()
{cout<<"Jesuisunpoint\n";
}
voidaffiche()
{identifie();
cout<<"Mescoordonnéessont:"<<x<<""<<y<<"\n";
}
};
classpointcol:publicpoint
{shortcouleur;
public:
pointcol(intabs=0,intord=0,intcl=1):point(abs,ord)
{couleur=cl;}
voididentifie()
{cout<<"Jesuisunpointcolorédecouleur:"<<couleur<<"\n";
}
};
main()
{pointp(3,4);
pointcolpc(5,9,5);
p.affiche();
pc.affiche();
cout<<"---------------\n";
point*adp=&p;
pointcol*adpc=&pc;
adp->affiche();adpc->affiche();
cout<<"---------------\n";
adp=adpc;
adp->affiche();adpc->affiche();
}
Dans la fonction affiche de point, l’appel de identifie fait l’objet d’une ligaturedynamique (puisquecettedernière fonctionaétédéclaréevirtuelle).Lorsqu’unobjetdetypepointcolappelleunefonctionaffiche,ceserabienlafonctionaffichedepointquiseraappelée(puisqueaffichen’estpasredéfiniedanspointcol).Maiscettedernièreferaappel,danscecas,àlafonctionidentifiedepointcol.Bienentendu,lorsqu’unobjetdetypepoint appelle affiche, cette dernière fera toujours appel à la fonction identifie depoint(lemêmerésultatseraitobtenusansligaturedynamique).
278
Voicilesrésultatscompletsfournisparnotreprogramme:Jesuisunpoint
Mescoordonnéessont:34
Jesuisunpointcolorédecouleur:5
Mescoordonnéessont:59
---------------
Jesuisunpoint
Mescoordonnéessont:34
Jesuisunpointcolorédecouleur:5
Mescoordonnéessont:59
---------------
Jesuisunpointcolorédecouleur:5
Mescoordonnéessont:59
Jesuisunpointcolorédecouleur:5
Mescoordonnéessont:59
279
Exercice117
ÉnoncéOn souhaite créer une classe nommée ens_heter permettant de manipuler desensembles dont le typedes éléments est non seulement inconnude la classe,maiségalement susceptible de varier d’un élément à un autre. Pour que la chose soitpossible,on imposera simplement lacontrainte suivante : tous les typesconcernésdevrontdériverd’unmêmetypedebasenommébase.Letypebaseserasupposéconnuaumomentoùl’ondéfinitlaclasseens_heter.
Laclassebasedisposeraaumoinsd’unefonctionvirtuellepurenomméeaffiche;cettefonction devra être redéfinie dans les classes dérivées pour afficher lescaractéristiquesdel’objetconcerné.
Laclasseens_heterdisposeradesfonctionsmembresuivantes:
•ajoutepourajouterunnouvelélémentà l’ensemble(elledevras’assurerqu’iln’existepasdéjà)þ;
•appartientpourtesterl’appartenanced’unélémentàl’ensembleþ;
•cardinalquifourniralenombred’élémentsdel’ensemble.
Deplus,laclassedevraêtremunied’un«itérateur»,c’est-à-dired’unmécanismepermettant de parcourir les différents éléments de l’ensemble. On prévoira 3fonctions:
•initpourinitialiserlemécanismed’itérationþ;
• suivant qui fournira en retour le prochain élément (objet d’un type dérivé debase)þ;
•existepourprécisers’ilexisteencoreunélémentnonexaminé.
Enfin,unefonctionnomméelistepermettrad’afficherlescaractéristiquesdetouslesélémentsdel’ensemble(ellefera,biensûr,appelauxfonctionsaffichedesdifférentsobjetsconcernés).
Onréaliseraensuiteunpetitprogrammed’essaide laclasseens_heter,encréantunensemblecomportantdesobjetsdetypepoint(deuxcoordonnéesentières)etcomplexe(unepartieréelleetunepartieimaginaire,toutesdeuxdetypefloat).Naturellement,pointetcomplexedevrontdériverdebase.
280
On ne se préoccupera pas des éventuels problèmes posés par l’affectation ou latransmissionparvaleurd’objetsdutypeens_heter.
N.B. Pour ne pas trop compliquer le programme, on fondera l’égalité de deuxélémentsd’unensemblesurl’égalitédeleursadressesetnonpasdeleursvaleurs.Autrementdit,deuxélémentsdemêmetypeetdemêmevaleurpourrontapparteniràunmêmeensemble,àconditionqu’ils’agissebiendedeuxobjetsdifférents.
Manifestement, la classe ens_heter ne contiendra pas les objets correspondant auxéléments de l’ensemble,mais seulement des pointeurs sur ces différents éléments.Àmoinsdefixerlenombremaximald’élémentsapriori, ilestnécessairedeconserverces pointeurs dans un emplacement alloué dynamiquement par le constructeur ; cedernier comportera donc un argument permettant de préciser le nombre maximald’élémentsrequispourl’ensemble.
Outrel’adresse(adel)decetableaudepointeurs,ontrouveraenmembresdonnée:
lenombremaximald’éléments(nmax);
lenombrecourantd’éléments(nelem);
unentier(courant)quiserviraaumécanismed’itération(ildésignerauneadressedutableaudepointeurs).
En ce qui concerne les fonctions ajoute et appartient, elles devront manifestementrecevoir en argument un objet d’un type dérivé de base. Puisqu’on ne fait aucunehypothèse a priori sur la nature de tels objets, il est préférable d’éviter unetransmission d’argument par valeur. Par souci de simplicité, nous choisirons unetransmissionparréférence.
Lesmêmesremarquess’appliquentàlavaleurderetourdelafonctionsuivant.
Voici,endéfinitive,ladéclarationdenotreclasseens_heter:/*fichierensheter.H*/
/*déclarationdelaclasseens_heter*/
classbase;
classens_heter
{intnmax;//nombremaximald'éléments
intnelem;//nombrecourantd'éléments
base**adel;//adressezonedepointeurssurlesobjetséléments
intcourant;//numérod'élémentcourant(pourl'itération)
public:
ens_heter(int=20);//constructeur
~ens_heter();//destructeur
voidajoute(base&);//ajoutd'unélément
intappartient(base&);//appartenanced'unélément
intcardinal();//cardinaldel'ensemble
voidinit();//initialisationitération
281
base&suivant();//passageélémentsuivant
intexiste();//
voidliste();//afficheles"valeurs"desdifférentséléments
};
Laclassebase(déclarationetdéfinition)découledirectementdel'énoncé:classbase
{public:
virtualvoidaffiche()=0;
};
Voici la définition des fonctions membre de ens_heter (notez que leur compilationnécessiteladéclaration(définition)précédentedelaclassebase(etnonpasseulementunesimpleindicationdelaformeclassbase):
/*définitiondelaclasseens_heter*/
#include"ensheter.h"
ens_heter::ens_heter(intdim)
{nmax=dim;
adel=newbase*[dim];
nelem=0;
courant=0;//précaution
}
ens_heter::~ens_heter()
{deleteadel;
}
voidens_heter::ajoute(base&obj)
{if((nelem<nmax)&&(!appartient(obj)))adel[nelem++]=&obj;
}
intens_heter::appartient(base&obj)
{inttrouve=0;
init();
while(existe()&&!trouve)if(&suivant()==&obj)trouve=1;
returntrouve;
}
intens_heter::cardinal()
{returnnelem;
}
voidens_heter::init()
{courant=0;
}
base&ens_heter::suivant()
{if(courant<nelem)return(*adel[courant++]);
//enpratique,ilfaudraitrenvoyerunobjet"bidon"sifin
//ensembleatteinte
}
intens_heter::existe()
{return(courant<nelem);
}
voidens_heter::liste()
{init();
while(existe())
suivant().affiche();
}
Voiciunpetitprogrammequidéfinitdeuxclassespointetcomplexe,dérivéesdebaseetquicréeunpetitensemblehétérogène(sacompilationnécessiteladéclarationdelaclassebase).Àlasuitefigurelerésultatdesonexécution:
282
#include"ens_heter.h"
#include"base.h"
#include<iostream>
usingnamespacestd;
classpoint:publicbase
{intx,y;
public:
point(intabs=0,intord=0)
{x=abs;y=ord;
}
voidaffiche()
{cout<<"Pointdecoordonnées:"<<x<<""<<y<<"\n";
}
};
classcomplexe:publicbase
{floatre,im;
public:
complexe(floatreel=0.0,floatimag=0.0)
{re=reel;im=imag;
}
voidaffiche()
{cout<<"Complexe-partieréelle:"<<re
<<",partieimaginaire:"<<im<<"\n";
}
};
/*utilisationdelaclasseens_heter*/
main()
{pointp(1,3);
complexez(0.5,3);
ens_hetere;
cout<<"cardinaldee:"<<e.cardinal()<<"\n";
cout<<"contenudee\n";
e.liste();
e.ajoute(p);
cout<<"cardinaldee:"<<e.cardinal()<<"\n";
cout<<"contenudee\n";
e.liste();
e.ajoute(z);
cout<<"cardinaldee:"<<e.cardinal()<<"\n";
cout<<"contenudee\n";
e.liste();
e.init();intn=0;
while(e.existe()){e.suivant();
n++;
}
cout<<"avecl'itérateur,ontrouve:"<<n<<"éléments\n";
}
cardinaldee:0
contenudee
cardinaldee:1
contenudee
Pointdecoordonnées:13
cardinaldee:2
contenudee
Pointdecoordonnées:13
Complexe-partieréelle:0.5,partieimaginaire:3
avecl'itérateur,ontrouve:2éléments
283
Sil’oncherchaitàrésoudrelesproblèmesposésparl’affectationetlatransmissionparvaleurd’objetsdetypeens_heter,onseraitamenéàeffectuerdes«copiescomplètes»(onditsouvent«profondes»)del’objet,c’est-à-diretenantcomptenonseulementdesesmembres,maisaussidesespartiesdynamiques.
Cela conduirait effectivement à recopier la partie dynamiquede l’ensemble, c’est-à-dire le tableau de pointeurs d’adresse base *. Mais que faudrait-il faire pour leséléments eux-mêmes (pointés par les différentes valeurs du tableau) ?Même si l’onadmetqu’ilfautégalementlesdupliquer,iln’estpaspossibledelefairesansconnaîtrela«structure»desobjetsconcernés.
D’unemanièregénérale,vousvoyezquecetexemple,parlesquestionsqu’ilsoulève,plaidelargementenfaveurdelaconstitutiondebibliothèquesd’objets,danslesquellessont définies, a priori, un certain nombre de règles communes. Parmi ces règles, onpourraitnotammentimposeràchaqueobjetdeposséderunefonction(denomunique)enassurant lacopieprofonde ;naturellement,pourpouvoir l’utilisercorrectement, ilfaudrait que la ligature dynamique soit possible, c’est-à-dire que tous les objetsconcernésdériventd’unmêmeobjetdebase.
284
Chapitre16Lesflotsd’entréeetdesortie
285
Rappels
Unflotestuncanal recevant (flotd’«entrée»)oufournissant (flotde«sortie»)del’information.Cecanalestassociéàunpériphériqueouàunfichier.Unflotd’entréeestunobjetdetypeistreamtandisqu’unflotdesortieestunobjetdetypeostream.Leflotcoutestunflotdesortieprédéfini,connectéàlasortiestandardstdout;demême,leflotcinestunflotd’entréeprédéfini,connectéàl’entréestandardstdin.
LaclasseostreamEllesurdéfinitl’opérateur<<souslaformed’unefonctionmembre:
ostream&operator<<(expression)
L’expression correspondant à son deuxième opérande peut être d’un type de basequelconque,ycomprischar,char* (onobtient lachaînepointée)ouunpointeursuruntypequelconqueautrequechar(onobtientlavaleurdupointeur);pourobtenirlavaleurdel’adressed’unechaîne,onlaconvertitartificiellementenunpointeurdetypevoid*.
Fonctionsmembres:
ostream&put(charc)transmetauflotcorrespondantlecaractèrec.
ostream & write (void * adr, int long) envoie long caractères, prélevés à partir del’adresseadr.
LaclasseistreamEllesurdéfinitl’opérateur>>souslaformed’unefonctionmembre:
istream&operator>>(type_de_base&)
Letype_de_basepeutêtrequelconque,pourpeuqu’ilnes’agissepasd’unpointeur(char*est cependant accepté ; il correspond à l’entrée d’une chaîne de caractères, et nond’uneadresse).
Les«espacesblancs»(espace,tabulationhorizontale\touverticale\v,findeligne\net changement depage \f) servent de«délimiteurs » (commedans scanf), y comprispourleschaînesdecaractères.
Principalesfonctionsmembres:
istream&get(char&c)extraituncaractèreduflotd’entréeetlerangedansc.
286
intget()extraituncaractèreduflotd’entréeetenrenvoielavaleur(sousformed’unentier);fournitEOFencasdefindefichier.
istream&read(void*adr,inttaille)littaillecaractèressurleflotetlesrangeàpartirdel’adresseadr.
LaclasseiostreamElle est dérivée de istream et ostream. Elle permet de réaliser des entrées sorties«conversationnelles».
Lestatutd’erreurd’unflotÀchaqueflotestassociéunensembledebitsd’unentierformantle«statutd’erreurduflot».
Lesbitsd’erreur
Laclasseios(dontdériventistreametostream)définitlesconstantessuivantes:
eofbit:findefichier(leflotn’aplusdecaractèresdisponibles);
failbit:laprochaineopérationsurleflotnepourrapasaboutir;
badbit:leflotestdansunétatirrécupérable;
goodbit(valant,enfait0):aucunedeserrreursprécédentes.
Une opération sur le flot a réussi lorsqu’un des bits goodbit ou eofbit est activé. Laprochaineopérationsurleflotnepourraréussirquesigoodbitestactivé(maisiln’estpascertainqu’elleréussisse!).
Lorsqu’un flot est dansun état d’erreur, aucuneopérationnepeut aboutir tant que laconditiond’erreurn’apasétécorrigéeetquelebitd’erreurcorrespondantn’apasétéremisàzéro(àl’aidedelafonctionclear).
Accèsauxbitsd’erreur
Laclasseioscontientcinqfonctionsmembre:
eof():valeurdeeofbit;
bad():valeurdebadbit;
fail():valeurdefailbit;
287
good():1siaucunbitdustatutd’erreurn’estactivé;
rdstate():valeurdustatutd’erreur(entier).
Modificationdustatutd’erreur
void clear (int i=0) donne la valeur i au statut d’erreur. Pour activer un seul bit (parexemplebadbit),onprocéderaainsi(flétantunflot):
fl.clear(ios::badbit|flrdstate());
Surdéfinitionde()etde!
Siflestunflot,(fl)estvraisiaucundesbitsd’erreurn’estactivé(c’est-à-diresigoodestvrai);demême,!flestvraisiundesbitsd’erreurprécédentsestactivé(c’est-à-diresigoodestfaux).
Surdéfinitionde<<et>>pourdestypesclasseOnsurdéfinira<<et>>pouruneclassequelconque,sousformedefonctionsamies,enutilisantces«canevas»:
ostream&operator<<(ostreamsortie,type_classeobjet1)
{
//Envoisurleflotsortiedesmembresdeobjetenutilisant
//lespossibilitésclassiquesde<<pourlestypesdebase
//c'est-à-diredesinstructionsdelaforme:
//sortie<<.....;
returnsortie;
}
istream&operator>>(istream&entree,type_de_base&objet)
{
//Lecturedesinformationscorrespondantauxdifférentsmembresdeobjet
//enutilisantlespossibilitésclassiquesde>>pourlestypesdebase
//c'est-à-diredesinstructionsdelaforme:
//entree>>.....;
returnentree;
}
Lemotd’étatdustatutdeformatageÀchaqueflot,estassociéun«statutdeformatage»constituéd’unmotd’étatetdetroisvaleursnumériques(gabarit,précisionetcaractèrederemplissage).
Voicilesprincipauxbitsdumotd’état:
Lemotd’étatdustatutdeformatage(partiel)
288
Nomdechamp Nomdubit(s’ilexiste)
Signification(quandactivé)
ios::basefield
ios::dec conversiondécimaleios::oct conversionoctaleios::hex conversionhexadécimale
ios::showbaseaffichageindicateurdebase(ensortie)
ios::showpointaffichagepointdécimal(ensortie)
ios::floatfield
ios::scientific notation«þscientifiqueþ»ios::fixed notation«þpointfixeþ»
ActionsurlestatutdeformatageOn peut utiliser, soit des « manipulateurs » qui peuvent être « simples » ou«paramétriques»,soitdesfonctionsmembre.
a.Lesmanipulateursnonparamétriques
Ilss’emploientsouslaforme:flot<<manipulateur
flot>>manipulateur
Lesprincipauxmanipulateursnonparamétriquessont:
Lesprincipauxmanipulateursnonparamétriques
Manipulateur Utilisation ACTIONdec Entrée/Sortie Activelebitdeconversiondécimale
hex Entrée/SortieActivelebitdeconversionhexadécimale
oct Entrée/Sortie Activelebitdeconversionoctale
endl SortieInsèreunsautdeligneetvideletampon
ends SortieInsèreuncaractèredefindechaîne(\0)
289
b.Lesmanipulateursparamétriques
Ilss’utilisentsouslaforme:istream&manipulateur(argument)
ostream&manipulateur(argument)
Voicilesprincipauxmanipulateursparamétriques:
Lesprincipauxmanipulateursparamétriques
Manipulateur Utilisation Rôlesetbase(int) Entrée/Sortie Définitlabasedeconversion
setprecision(int) Entrée/SortieDéfinitlaprécisiondesnombresflottants
setw(int) Entrée/SortieDéfinitlegabarit.Ilretombeà0aprèschaqueopération
L’utilisation des manipulateurs paramétriques nécessite l’inclusion du fichier en-têteiomanip.Danscertainesimplémentations,ilpeutencores’agirdeiomanip.h(associéalorsàiostream.hetnonàiostream,etsansl’utilisationdel’espacedenomsstd).
Associationd’unflotàunfichierLa classe ofstream, dérivant de ostream, permet de créer un flot de sortie associé à unfichier:
ofstreamflot(char*nomfich,mode_d_ouverture)
Lafonctionmembreseekp(déplacement,origine)permetd’agirsurlepointeurdefichier.
Demême,laclasseifstream,dérivantdeistream,permetdecréerunflotd’entréeassociéàunfichier:
ifstreamflot(char*nomfich,mode_d_ouverture)
Lafonctionmembreseekg(déplacement,origine)permetd’agirsurlepointeurdefichier.
Danstouslescas,lafonctionclosepermetdefermerlefichier.
L’utilisationdesclassesofstreametifstreamdemandel’inclusiondufichierfstream.Danscertainesimplémentations,ilpeutencores’agirdefstream.h(associéalorsàiostream.hetnonàiostream,etsansl’utilisationdel’espacedenomsstd).
290
Modesd’ouvertured’unfichier
Lesdifférentsmodesd’ouvertured’unfichier
Bitdemoded’ouverture Action
ios::in Ouvertureenlecture(obligatoirepourlaclasseifstream)ios::out Ouvertureenécriture(obligatoirepourlaclasseofstream)ios::app Ouvertureenajoutdedonnées(écritureenfindefichier)ios::ate Seplaceenfindefichieraprèsouverture
ios::truncSilefichierexiste,soncontenuestperdu(obligatoiresiios::outestactivésansios::ateniios::app)
ios::binary(Utiledanscertainesimplémentationsuniquement.)Lefichierestouvertenmodedit«binaire»ouencore«nontranslaté»
291
Exercice118
ÉnoncéÉcrire un programme qui lit un nombre réel et qui en affiche le carré sur un«gabarit»minimalde12caractères,de22façonsdifférentes:
•en«pointfixe»,avecunnombrededécimalesvariantde0à10,
•ennotationscientifique,avecunnombrededécimalesvariantde0à10.
Danstouslescas,onafficheralesrésultatsaveccetteprésentation:précisiondexxchiffres:cccccccccccc
Il faut donc activer d’abord le bit fixed, ensuite le bit scientific du champ floatfield.Nousutiliseronslafonctionsetf,membredelaclasseios.Notezbienqu’ilfautéviterd’écrire,parexemple:
setf(ios::fixed);
Eneffet,celaactiveraitlebitfixed,sansmodifier lesautresdonc,enparticulier,sansmodifierlesautresbitsduchampfloatfield.
Le gabarit d’affichage est déterminé par le manipulateur setw. Notez qu’il fauttransmettre ce manipulateur au flot concerné, juste avant d’afficher l’informationvoulue.
#include<iomanip>//pourles"manipulateursparamétriques"
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
main()
{floatval,carre;
cout<<"donnezunnombreréel:";
cin>>val;
carre=val*val;
cout<<"Voicisoncarré:\n";
inti;
cout<<"ennotationpointfixe:\n";
cout.setf(ios::fixed,ios::floatfield);//metà1lebitios::fixed
//duchampios::floatfield
for(i=0;i<10;i++)
cout<<"précisionde"<<setw(2)<<i<<"chiffres:"
<<setprecision(i)<<setw(12)<<carre<<"\n";
cout<<"ennotationscientifique:\n";
cout.setf(ios::scientific,ios::floatfield);
for(i=0;i<10;i++)
cout<<"précisionde"<<setw(2)<<i<<"chiffres:"
292
<<setprecision(i)<<setw(12)<<carre<<"\n";
}
Àtitreindicatif,voiciunexempled’exécutiondeceprogramme:donnezunnombreréel:12.3456
Voicisoncarré:
ennotationpointfixe:
précisionde0chiffres:152
précisionde1chiffres:152.4
précisionde2chiffres:152.41
précisionde3chiffres:152.414
précisionde4chiffres:152.4138
précisionde5chiffres:152.41385
précisionde6chiffres:152.413849
précisionde7chiffres:152.4138489
précisionde8chiffres:152.41384888
précisionde9chiffres:152.413848877
ennotationscientifique:
précisionde0chiffres:1.524138e+002
précisionde1chiffres:1.5e+002
précisionde2chiffres:1.52e+002
précisionde3chiffres:1.524e+002
précisionde4chiffres:1.5241e+002
précisionde5chiffres:1.52414e+002
précisionde6chiffres:1.524138e+002
précisionde7chiffres:1.5241385e+002
précisionde8chiffres:1.52413849e+002
précisionde9chiffres:1.524138489e+002
293
Exercice119
ÉnoncéSoitlaclassepointsuivante:
classpoint
{intx,y;
public:
//fonctionsmembre
};
Surdéfinirlesopérateurs<<et>>demanièrequ’ilsoitpossibledelireunpointsurunflotd’entréeoud’écrireunpointsurun flotdesortie.Onprévoiraqu’un telpointsoitreprésentésouslaforme:
<entier,entier>
avecéventuellementdesséparateurs«espaces_blancs»supplémentaires,departetd’autredesnombresentiers.
Nousdevonsdonc surdéfinir lesopérateurs << et >> pour qu’ils puissent recevoir, endeuxième opérande, un argument de type point. Il ne pourra s’agir que de fonctionsamies,dontlesprototypesseprésenterontainsi:
ostream&operator<<(ostream&,point);
istream&operator>>(istream&,point);
L’écriture de operator << ne présente pas de difficultés particulières : on se contented’écrire,surleflotconcerné,lescoordonnéesdupoint,accompagnéesdessymboles<et>.
Enrevanche,l’écrituredeoperator>>nécessiteunpeuplusd’attention.Eneffet,ilfauts’assurerquel’informationseprésentebiensouslaformerequiseet,sicen’estpaslecas, prévoirdedonner au flot concerné l’état bad, afinque l’utilisateurpuisse savoirquel’opérations’estmaldéroulée(entestant«naturellement»l’étatduflot).
Voici ce que pourrait être la déclaration de notre classe (nous l’avons simplementmunied’unconstructeur)etladéfinitiondesdeuxfonctionsamiesvoulues:
#include<iostream>
usingnamespacestd;
classpoint
{
intx,y;
294
public:
point(intabs=0,intord=0)
{x=abs;y=ord;}
intabscisse(){returnx;}
friendostream&operator<<(ostream&,point);
friendistream&operator>>(istream&,point&);
};
ostream&operator<<(ostream&sortie,pointp)
{
sortie<<"<"<<p.x<<","<<p.y<<">";
returnsortie;
}
istream&operator>>(istream&entree,point&p)
{
charc='\0';
floatx,y;
intok=1;
entree>>c;
if(c!='<')ok=0;
else
{entree>>x>>c;
if(c!=',')ok=0;
else
{entree>>y>>c;
if(c!='>')ok=0;
}
}
if(ok){p.x=x;p.y=y;}//onn'affecteàpquesitoutestOK
elseentree.clear(ios::badbit|entree.rdstate());
returnentree;
}
À titre indicatif, voici un petit programme d’essai, accompagné d’un exempled’exécution:
main()
{
charligne[121];
pointa(2,3),b;
cout<<"pointa:"<<a<<"pointb:"<<b<<"\n";
do
{cout<<"donnezunpoint:";
if(cin>>a)cout<<"mercipourlepoint:"<<a<<"\n";
else{cout<<"**informationincorrecte\n";
cin.clear();
cin.getline(ligne,120,‘\n’);
}
}
while(a.abscisse());
}
pointa:<2,3>pointb:<0,0>
donnezunpoint:4,5
**informationincorrecte
donnezunpoint:<4,5<
**informationincorrecte
donnezunpoint:<4,5>
mercipourlepoint:<4,5>
donnezunpoint:<8,9>
mercipourlepoint:<8,9>
donnezunpoint:bof
**informationincorrecte
donnezunpoint:<0,0>
295
mercipourlepoint:<0,0>
296
Exercice120
ÉnoncéÉcrireunprogrammequienregistre (sous forme«binaire»,etnonpas formatée),dansunfichierdenomfourniparl’utilisateur,unesuitedenombresentiersfournissurl’entréestandard.Onconviendraquel’utilisateurfourniralavaleur0(quineserapasenregistréedanslefichier)pourpréciserqu’iln’aplusd’entiersàentrer.
Sinomfichdésigneunechaînedecaractères,ladéclaration:ofstreamsortie(nomfich,ios::out);
permetdecréerunflotdenomsortie,del’associeraufichierdontlenomfiguredansnomfichetd’ouvrircefichierenécriture.
L’écriture dans le fichier en question se fera par la fonction write, appliquée au flotsortie.
Voicileprogrammedemandé:constintLGMAX=20;
#include<cstdlib>//pourexit
#include<fstream>
#include<iomanip>
#include<iostream>
usingnamespacestd;
main()
{
charnomfich[LGMAX+1];
intn;
cout<<"nomdufichieràcréer:";
cin>>setw(LGMAX)>>nomfich;
ofstreamsortie(nomfich,ios::out);
if(!sortie){cout<<"créationimpossible\n";
exit(1);
}
do
{cout<<"donnezunentier:";
cin>>n;
if(n)sortie.write((char*)&n,sizeof(int));
}
while(n&&sortie);
sortie.close();
}
Notezque if(!sortie) est équivalent à if(!sortie.good()) et que while (n && sortie) estéquivalentàwhile(n&&sortie.good()).
297
Exercice121
ÉnoncéÉcrireunprogrammepermettantdelister(surlasortiestandard)lesentierscontenusdansunfichiertelqueceluicrééparl’exerciceprécédent.
constintLGMAX=20;
#include<cstdlib>//pourexit
#include<fstream>
#include<iomanip>
#include<iostream>
usingnamespacestd;
main()
{
charnomfich[LGMAX+1];
intn;
cout<<"nomdufichieràlister:";
cin>>setw(LGMAX)>>nomfich;
ifstreamentree(nomfich,ios::in);
if(!entree){cout<<"ouvertureimpossible\n";
exit(1);
}
while(entree.read((char*)&n,sizeof(int)))
cout<<n<<"\n";
entree.close();
}
298
Exercice122
ÉnoncéÉcrireunprogrammepermettantàunutilisateurderetrouver,dansunfichiertelqueceluicréédansl’exercice120,lesentiersdontilfournitle«rang».Onconviendraqu’unrangégalà0signifiequel’utilisateursouhaitemettrefinauprogramme.
constintLGMAX_NOM_FICH=20;
#include<cstdlib>//pourexit
#include<fstream>
#include<iomanip>
#include<iostream>
usingnamespacestd;
main()
{
charnomfich[LGMAX_NOM_FICH+1];
intn,num;
cout<<"nomdufichieràconsulter:";
cin>>setw(LGMAX_NOM_FICH)>>nomfich;
ifstreamentree(nomfich,ios::in);
if(!entree){cout<<"Ouvertureimpossible\n";
exit(1);
}
do
{cout<<"Numérodel'entierrecherché:";
cin>>num;
if(num)
{entree.seekg(sizeof(int)*(num-1),ios::beg);
entree.read((char*)&n,sizeof(int));
if(entree)cout<<"--Valeur:"<<n<<"\n";
else{cout<<"--Erreur\n";
entree.clear();
}
}
}
while(num);
entree.close();
}
1.Ici,latransmissionpeutsefaireparvaleurouparréférence.
299
Chapitre17Lespatronsdefonctions
300
Rappels
Introduiteparlaversion3,lanotiondepatrondefonctionspermetdedéfinircequ’onnommesouventdes«fonctionsgénériques».Plusprécisément,àl’aided’uneuniquedéfinition comportant des « paramètres de type », on décrit toute une famille defonctions; lecompilateur«fabrique»(onditaussi«instancie»)laoulesfonctionsnécessairesàlademande(onnommesouventcesinstances«fonctionspatron»).
Laversion3limitaitlesparamètresd’unpatrondefonctionsàdesparamètresdetype.LanormeANSIa,enoutre,introduitles«paramètresexpression».
Définitiond’unpatrondefonctionsOn précise les paramètres (muets) de type, en faisant précéder chacun du mot(relativement arbitraire) class sous la forme template <class ..., class ..., ...>. Ladéfinitiondelafonctionestclassique,hormislefaitquelesparamètresmuetsdetypepeuventêtreemployésn’importeoùuntypeeffectifestpermis.
Parexemple:template<classT,classU>voidfct(Ta,T*b,Uc)
{
Tx;//variablelocalexdetypeT
U*adr;//variablelocaleadrdetypeU*
...
adr=newT[10];//allocationtableaude10élémentsdetypeT
...
n=sizeof(T);//uneinstructionutilisantletypeT
...
}
Uneinstructiontelleque(Tdésignantuntypequelconque):Tx(3);
estlégalemêmesiTn'estpasuntypeclasse;danscederniercas,elleestsimplementéquivalenteà:
Tx=3;
Instanciationd’unefonctionpatron
301
Chaquefoisqu’onutiliseunefonctionayantunnomdepatron,lecompilateurchercheàutiliser ce patron pour créer (instancier) une fonction adéquate. Pour ce faire, ilchercheà réaliserunecorrespondanceabsolue des types : aucune conversion, qu’ils’agisse depromotionnumériqueoude conversion standardn’est permise ; qui plusest,lesqualifieursconstetvolatiledoiventêtreexactementlesmêmes.
Voicidesexemplesutilisantnotrepatronprécédent:intn,p;floatx;charc;
int*adi;float*adf;
classpoint;pointp;point*adp;
fct(n,adi,x);//instancielafonctionvoidfct(int,int*,float)
fct(n,adi,p)//instancielafonctionvoidfct(int,int*,int)
fct(x,adf,p);//instancielafonctionvoidfct(float,float*,int)
fct(c,adi,x);//erreurcharetint*necorrespondentpasàTetT*
//(pasdeconversion)
fct(&n,&adi,x);//instancielafonctionvoidfct(int*,int**,float)
fct(p,adp,n);//instancielafonctionvoidfct(point,point*,int)
D’unemanièregénérale, ilestnécessairequechaqueparamètredetypeapparaisseaumoinsunefoisdansl’en-têtedupatron.
Ladéfinitiond’unpatrondefonctionsnepeutpasêtrecompiléeseule;detoutefaçon,elle doit être connue du compilateur pour qu’il puisse instancier la bonne fonctionpatron.Engénéral,lesdéfinitionsdepatronsdefonctionsfigurerontdansdesfichiersd’extensionh,defaçonàéviterd’avoiràenfournirsystématiquementlaliste.
Lesparamètresexpressiond'unpatrondefonctionsIlsontétéintroduitsparlanorme.Unparamètreexpressiond’unpatrondefonctionsseprésente comme un argument usuel de fonction ; il n’apparaît pas dans la liste deparamètresdetype(template)etildoitapparaîtredansl’en-têtedupatron.Parexemple:
template<classT>intcompte(T*tab,intn)
{//ici,onpeutseservirdelavaleurdel'entiern
//commeonleferaitdansn'importequellefonctionordinaire
}
Unpatrondefonctionspeutdisposerd’unoudeplusieursparamètresexpression.Lorsde l’appel, leur typen’aplusbesoinde correspondre exactement à celui attendu : ilsuffit qu’il soit acceptable par affectation, comme dans n’importe quel appel d’unefonctionordinaire.
Surdéfinitiondepatronsdefonctionsetspécialisationde
302
fonctionsdepatronsOnpeutdéfinirplusieurspatronsdemêmenom,possédantdesparamètres(detypeouexpression) différents. La seule règle à respecter dans ce cas est que l’appel d’unefonctiondecenomnedoitpasconduireàuneambiguïté:unseulpatrondefonctionsdoitpouvoirêtreutiliséàchaquefois.
Par ailleurs, il est possible de fournir la définition d’une ou plusieurs fonctionsparticulièresquiserontutiliséesenlieuetplacedecelleinstanciéeparunpatron.Parexemple,avec:
template<classT>Tmin(Ta,Tb)//patrondefonctions
{...}
char*min(char*cha,char*chb)//versionspécialiséepourletypechar*
{...}
intn,p;
char*adr1,*adr2;
min(n,p)//appellelafonctioninstanciéeparlepatrongénéral
//soitici:intmin(int,int)
min(adr1,adr2)//appellelafonctionspécialisée
//char*min(char*,char*)
Algorithmed’instanciationoud’appeld’unefonctionPrécisons comment doivent être aménagées les règles de recherche d’une fonctionsurdéfinie,danslecasoùilexisteunouplusieurspatronsdefonctions.
Lorsd’unappeldefonction,lecompilateurrecherchetoutd’abordunecorrespondanceexacte avec les fonctions « ordinaires ». S’il y a ambiguïté, la recherche échoue(comme à l’accoutumée). Si aucune fonction « ordinaire » ne convient, on examinealors tous les patrons ayant le nom voulu (en ne considérant que les paramètres detype).Siuneseulecorrespondanceexacteest trouvée, la fonctioncorrespondanteestinstanciée(dumoins,siellenel’apasdéjàété)etleproblèmeestrésolu.S’ilyenaplusieurs,larechercheéchoue.
Enfin, si aucun patron de fonction ne convient, on examine à nouveau toutes lesfonctions « ordinaires » en les traitant cette fois comme de simples fonctionssurdéfinies(promotionsnumériques,conversionsstandard…).
303
Exercice123
ÉnoncéCréer un patron de fonctions permettant de calculer le carré d’une valeur de typequelconque(lerésultatposséderalemêmetype).Écrireunpetitprogrammeutilisantcepatron.
Ici,notrepatronnecomporteraqu’unseulparamètredetype(correspondantàlafoisàl’uniqueargumentetàlavaleurderetourdelafonction).Sadéfinitionneposepasdeproblèmeparticulier.
#include<iostream>
usingnamespacestd;
template<classT>Tcarre(Ta)
{returna*a;
}
main()
{intn=5;
floatx=1.5;
cout<<"carrede"<<n<<"="<<carre(n)<<"\n";
cout<<"carrede"<<x<<"="<<carre(x)<<"\n";
}
304
Exercice124
ÉnoncéSoitcettedéfinitiondepatrondefonctions:
template<classT,classU>Tfct(Ta,Ub,Tc)
{.....
}
Aveclesdéclarationssuivantes:
intn,p,q;
floatx;
chart[20];
charc;
Quelssontlesappelscorrectset,danscecas,quelssontlesprototypesdesfonctionsinstanciées?
fct(n,p,q);//appelI
fct(n,x,q);//appelII
fct(x,n,q);//appelIII
fct(t,n,&c);//appelIV
a.L’appelIestcorrect;ilinstancielafonction:intfct(int,int,int)
b.L’appelIIestcorrect;ilinstancielafonction:intfct(int,float,int)
c.L’appelIIIestincorrect.d.L’appelIVestcorrectselonlanorme.Ilinstancielafonction:
char*fct(char*,int,char*)
L’appelþIVn’étaitpasacceptédanslaversion3quineconsidéraitpaschar*commeunecorrespondanceabsoluepourchar[20].
305
Exercice125
ÉnoncéCréer un patron de fonctions permettant de calculer la somme d’un tableaud’éléments de type quelconque, le nombre d’éléments du tableau étant fourni enparamètre (on supposera que l’environnement utilisé accepte les « paramètresexpression»).Écrireunpetitprogrammeutilisantcepatron.
//définitiondupatrondefonctions
template<classT>Tsomme(T*tab,intnelem)
{Tsom;
inti;
som=0;
for(i=0;i<nelem;i++)som=som+tab[i];
returnsom;
}
//exempled'utilisation
#include<iostream>
usingnamespacestd;
main()
{intti[]={3,5,2,1};
floattf[]={2.5,3.2,1.8};
chartc[]={'a','e','i','o','u'};
cout<<somme(ti,4)<<"\n";
cout<<somme(tf,3)<<"\n";
cout<<somme(tc,5)<<"\n";
}
1.telqu’ilaétéconçu,lepatronsommenepeutêtreappliquéqu’àuntypeTpourlequel:
–l’opérationd’additionaunsens;celasignifiedoncqu’ilnepeutpass’agird’untype pointeur ; il peut s’agir d’un type classe, à condition que cette dernière aitsurdéfinil’opérateurd’addition;
–ladéclarationTsomestcorrecte ;celasignifiequesiTestun typeclasse, ilestnécessairequ'ildisposed'unconstructeursansargument;
–l’affectationsom=0estcorrecte ;celasignifiequesiTestun typeclasse, ilestnécessairequ’ilaitsurdéfinil’affectation.
À ce propos, notons qu’il est possible d’initialiser som lors de sa déclaration, enprocédantainsi:
306
Tsom(0);
CelaestéquivalentàTsom=0siTestuntypeprédéfini.Enrevanche,siTestdetypeclasse,celaprovoquel'appeld'unconstructeurà1argumentdeT,enluitransmettantlavaleur0;leproblèmerelatifàl'affectationsom=0neseposeplusalors.
2.L’exécutionde l’exempleproposéfournidesrésultatspeusatisfaisantsdans lecasoùl’onappliquesommeàuntableaudecaractères,comptetenudelacapacitélimitéedecetype.Onpourraitaméliorerlasituationen«spécialisant»notrepatronpourlestableauxdecaractères(enprévoyant,parexemple,unevaleurderetourdetypeint).
307
Exercice126
ÉnoncéSoientlesdéfinitionssuivantesdepatronsdefonctions:
template<classT,classU>voidfct(Ta,Ub){...}//patronI
template<classT,classU>voidfct(T*a,Ub){...}//patronII
template<classT>voidfct(T,T,T){...}//patronIII
voidfct(inta,floatb){.....}//fonctionIV
Aveccesdéclarations:intn,p,q;
floatx,y;
doublez;
Quelssont lesappelscorrectset,danscecas,quelssont lespatronsutiliséset lesprototypesdesfonctionsinstanciées?
fct(n,p);//appelI
fct(x,y);//appelII
fct(n,x);//appelIII
fct(n,z);//appelIV
fct(&n,p);//appelV
fct(&n,x);//appelVI
fct(&n,&p,&q)//appelVII
Ici,onfaitappelàlafoisàunesurdéfinition(patronsI,IIetIII)etàunespécialisationdepatron(fonctionIV).
I)patronIvoidfct(int,int);
II)patronIvoidfct(float,float);
III)fonctionIVvoidfct(int,float);
IV)patronIvoidfct(int,double);
V)erreur:ambigüitéentrefct(T,U)etfct(T*,U)
VI)erreur:ambigüitéentrefct(T,U)etfct(T*,U)
VII)patronIIIvoidfct(int*,int*,int*);
Lepatron II ne peut jamais être utilisé.En effet, chaque fois qu’il pourrait l’être, lepatronIpeutl’êtreégalement,desortequ’ilyaambiguïté.LepatronIIestdonc,ici,parfaitementinutile.
Notezquesinousavionsdéfinisimultanémentlesdeuxpatrons:template<classT,classU>voidfct(Ta,Ub);
template<classT>voidfct(Ta,Tb)
308
lemêmephénomèned’ambiguïté (entrecesdeuxpatrons)seraitapparu lorsd’appelstelsquefct(n,p)oufct(x,y).
Rappelonsquel’ambiguïtén’estdétectéequelorsquelecompilateurdoitinstancierunefonctionetnonsimplementauvudesdéfinitionsdepatronselles-mêmes:cesdernièresrestentdoncacceptéestantquel’ambiguïtén’estpasmiseenévidenceparunappellarévélant.
309
Chapitre18Lespatronsdeclasses
310
Rappels
Introduiteparlaversion3,lanotiondepatrondeclassespermetdedéfinircequel’onnomme aussi des « classes génériques ». Plus précisément, à l’aide d’une seuledéfinitioncomportantdesparamètresde typeetdesparamètresexpression,ondécrittoute une famille de classes ; le compilateur fabrique (instancie) la ou les classesnécessairesàlademande(onnommesouventcesinstancesdes«classespatron»).
Cette fois, les paramètres expression étaient déjà prévus par la version 3 alors que,danslecasdespatronsdefonctions,ilsn'ontétéintroduitsqueparlanormeANSI.
Définitiond'unpatrondeclassesOn précise les paramètres de type en les faisant précéder du mot clé class et lesparamètresexpressionenmentionnantleurtypedansunelistedeparamètresintroduiteparlemottemplate(commepourlespatronsdefonctions,aveccettedifférencequ’ici,touslesparamètres–typeouexpression–apparaissent).
Parexemple:template<classT,classU,intn>classgene
{//ici,Tdésigneuntypequelconque,nunevaleurentièrequelconque
};
Siunefonctionmembreestdéfinie(cequiestlecasusuel)àl’extérieurdeladéfinitiondupatron, il faut rappeler aucompilateur la listedeparamètres (template) et préfixerl’en-têtedelafonctionmembredunomdupatronaccompagnédesesparamètres.(Entouterigueur,ils’agitd’uneredondanceconstatée,maisnonjustifiée,parlefondateurdulangagelui-même,Stroustrup.)Parexemple,pourunconstructeurdenotrepatrondeclassesprécédent:
template<classT,classU,intn>gene<T,U,n>::gene(...)
{.....}
Instanciationd’uneclassepatronOndéclareuneclassepatronenfournissantàlasuitedunomdepatronunnombredeparamètres effectifs (noms de types ou expressions) correspondant aux paramètres
311
figurantdanslaliste(template).Lesparamètresexpressiondoiventobligatoirementêtredesexpressionsconstantesdumêmetypequeceluifigurantdanslaliste.Parexemple,avecnotreprécédentpatron(onsupposequeptestuneclasse):
classgene<int,float,5>c1;//T=int,U=float,n=5
classgene<int,int,12>c2;//T=int,U=int,n=12
constintNV=100;
classgene<pt,double,NV>c3;//T=pt,U=double,n=100
intn=5;
classgene<int,double,n>c4;//erreur:nn'estpasconstant
constcharC='e';
classgene<int,double,C>c5;//erreur:Cdetypecharetnonint
Un paramètre de type effectif peut lui-même être une classe patron. Par exemple, sinousavonsdéfiniunpatrondeclassespointpar:
template<classT>classpoint{.....};
Voicidesinstancespossiblesdegene:classgene<point<int>,float,10>c5;//T=point<int>,U=float,n=10
classgene<point<char>,point<float>,5>c6;//T=point<int>,U=point<float>,n=5
Unpatronde classespeut comporterdesmembres (donnéesou fonctions) statiques ;dans ce cas, chaque instance de la classe dispose de son propre jeu de membresstatiques.
Spécialisationd’unpatrondeclassesUnpatrondeclassesnepeutpasêtresurdéfini(onnepeutpasdéfinirdeuxpatronsdemême nom). En revanche, on peut spécialiser un patron de classes de différentesmanières.
--Enspécialisantunefonctionmembre
Parexemple,aveccepatron:template<classT,intn>classtableau{.....};
Onpourraécrireuneversionspécialiséedeconstructeurpour lecasoùT est le typepointetoùnvaut10enprocédantainsi:
tableau<point,10>::tableau(...){.....}
--Enspécialisantuneclasse
Dans ce cas, on peut éventuellement spécialiser tout ou une partie des fonctionsmembre,maiscen’estpasnécessaire.Parexemple,aveccepatron:
template<classT>classpoint{.....};
onpeut fourniruneversionspécialiséepour lecasoùTest le typechar enprocédant
312
ainsi:classpoint<char>
{//nouvelledéfinitiondelaclassepointpourlescaractères
};
IdentitédeclassespatronOnnepeutaffecterentreeuxquedeuxobjetsdemêmetype.Danslecasd’objetsd’untypeclassepatron,onconsidèrequ’ilya identitéde type lorsque leursparamètresdetypessontidentiquesetquelesparamètresexpressionontlesmêmesvaleurs.
ClassespatronethéritageOn peut « combiner » de plusieurs façons l’héritage avec la notion de patron declasses:
Classe«ordinaire»dérivéed’uneclassepatron;parexemple,siAestuneclassepatrondéfiniepartemplate<classT>A:classB:publicA<int>//BdérivedelaclassepatronA<int>
OnobtientuneseuleclassenomméeB.
Patron de classes dérivé d’une classe « ordinaire » ; par exemple, si A est uneclasseordinaire:template<classT>classB:publicA
Onobtientunefamilledeclasses(deparamètredetypeT).
Patrondeclassesdérivéd’unpatrondeclasses;parexemple,siAestuneclassepatrondéfiniepartemplate<classT>A,onpeut:
–définirunenouvellefamilledefonctionsdérivéespar:template<classT>classB:publicA<T>
Danscecas, ilexisteautantdeclassesdérivéespossiblesquedeclassesdebasepossibles.
–définirunenouvellefamilledefonctionsdérivéespar:template<classT,classU>classB:publicA<T>
Dansce cas,onpeutdirequechaqueclassedebasepossiblepeut engendrerunefamilledeclassesdérivées(deparamètredetypeU).
313
Exercice127
ÉnoncéSoitladéfinitionsuivanted’unpatrondeclasses:
template<classT,intn>classessai
{Ttab[n];
public:
essai(T);//constructeur
};
a.Donnezladéfinitionduconstructeuressai,ensupposant:
•qu’elleestfournie«l'extérieur»deladéfinitionprécédente;
• que le constructeur recopie la valeur reçue en argument dans chacun desélémentsdutableautab.
b.Disposantainsideladéfinitionprécédentedupatronessai,desonconstructeuretdecesdéclarations:constintn=3;
intp=5;
Quelles sont les instructions correctes et les classes instanciées ? On en fournira(dans chaque cas) une définition équivalente sous la forme d’une « classeordinaire»,c’est-à-diredanslaquellelanotiondeparamètreadisparu.
essai<int,10>ei(3);//I
essai<float,n>ef(0.0);//II
essai<double,p>ed(2.5);//III
a.Ladéfinitionduconstructeurestanalogueàcellequel’onauraitécrite«enligne»;ilfautsimplement«préfixer»sonen-têted’unelistedeparamètresintroduitepartemplate.Deplus, ilfautpréfixerl’en-têtedelafonctionmembredunomdupatronaccompagnédesesparamètres(bienquecelasoitredondant):template<classT,intn>essai<T,n>::essai(Ta)
{inti;
for(i=0;i<n;i++)tab[i]=a;
}
b.AppelI:correct.classessai
{inttab[10];
public:
essai(int);//constructeur
314
};
essai::essai(inta)
{inti;
for(i=0;i<n;i++)tab[i]=a;
}
b.AppelII:correct.classessai
{floattab[n];
public:
essai(float);//constructeur
};
essai::essai(floata)
{inti;
for(i=0;i<n;i++)tab[i]=a;
}
b.AppelIII:incorrectcarpn’estpasuneexpressionconstante.
315
Exercice128
Énoncéa. Créer un patron de classes nommé pointcol, tel que chaque classe instanciée
permettedemanipulerdespointscolorés(deuxcoordonnéesetunecouleur)pourlesquels on puisse « choisir » à la fois le type des coordonnées et celui de lacouleur.Onselimiteraàdeuxfonctionsmembre:unconstructeurpossédanttroisarguments (sans valeur par défaut) et une fonction affiche affichant lescoordonnéesetlacouleurd’un«pointcoloré».
b. Dans quelles conditions peut-on instancier une classe patron pointcol pour desparamètresdetypeclasse?
a.Voicicequepourraitêtreladéfinitiondupatrondemandé,enprévoyantlesfonctionsmembre«enligne»:template<classT,classU>classpointcol
{
Tx,y;//coordonnees
Ucoul;//couleur
public:
pointcol(Tabs,Tord,Ucl)
{x=abs;y=ord;coul=cl;
}
voidaffiche()
{cout<<"pointcolore-coordonnees"<<x<<""<<y
<<"couleur"<<coul<<"\n";
}
};
À titre indicatif, voici un exemple d’utilisation (on suppose que la définitionprécédentefiguredanspointcol.h):
#include"pointcol.h"
#include<iostream>
usingnamespacestd;
main()
{pointcol<int,shortint>p1(5,5,2);p1.affiche();
pointcol<float,int>p2(4,6,2);p2.affiche();
pointcol<double,unsignedshort>p3(1,5,2);p3.affiche();
}
b. Il suffit que le type classe en question ait convenablement surdéfini l’opérateur <<,afind’assurerconvenablementl’affichagesurcoutdesinformationscorrespondantes.
316
Exercice129
ÉnoncéOnadéfinilepatrondeclassessuivant:
template<classT>classpoint
{Tx,y;//coordonnees
public:
point(Tabs,Tord){x=abs;y=ord;}
voidaffiche();
};
template<classT>voidpoint<T>::affiche()
{cout<<"Coordonnees:"<<x<<""<<y<<"\n";
}
a.Quesepasse-t-ilaveccesinstructions:point<char>p(60,65);
p.affiche();
b.Comment faut-ilmodifier ladéfinitiondenotrepatronpourque les instructionsprécédentesaffichentbien:Coordonnees:6065
a. On obtient l’affichage des caractères de code 60 et 65 (c’est-à-dire dans uneimplémentationutilisantlecodeASCII:<etA)etnonlesnombres60et65.
b.IlfautspécialisernotrepatronpointpourlecasoùletypeTestletypechar.Pourcefaire,onpeut:
soitfournirunedéfinitioncomplètedepoint<char>,avecsesfonctionsmembre;
soit,puisqu’iciseule lafonctionafficheestconcernée,secontenterdesurdéfinir lafonction point<char>::affiche, ce qui conduit à cette nouvelle définition de notrepatron://définitiongeneraledupatronpoint
template<classT>classpoint
{
Tx,y;//coordonnees
public:
point(Tabs,Tord){x=abs;y=ord;}
voidaffiche();
};
template<classT>voidpoint<T>::affiche()
{cout<<"Coordonnees:"<<x<<""<<y<<"\n";
}
317
//versionspecialiseedelafonctionaffichepourletypechar
voidpoint<char>::affiche()
{cout<<"Coordonnees:"<<(int)x<<""<<(int)y<<"\n";
}
318
Exercice130
ÉnoncéCréerunpatrondeclassespermettantde représenterdes«vecteursdynamiques»c’est-à-dire des vecteurs dont la dimension peut ne pas être connue lors de lacompilation (ce n’est donc pas obligatoirement une expression constante commedans le cas de tableaux usuels). On prévoira que les éléments de ces vecteurspuissentêtredetypequelconque.
On surdéfinira convenablement l’opérateur [] pour qu’il permette l’accès auxélémentsduvecteur(aussibienenconsultationqu’enmodification)etons’arrangerapourqu’il n’existe aucun risquede«débordementd’indice».En revanche,onnecherchera pas à régler les problèmes posés éventuellement par l’affectation ou latransmissionparvaleurd’objetsdutypeconcerné.
N.B. Il ne faut pas chercher à utiliser les composants standard introduits par lanorme.Eneffet,lepatronvectorrépondraitintégralementàlaquestion.
En généralisant ce qui a été fait dans l’exercice 90 (sans toutefois initialiser leséléments du vecteur lors de sa construction), nous aboutissons au patron de classessuivant:
template<classT>classvect
{intnelem;//nombred'elements
T*adr;//adressezonedynamiquecontenantleselements
public:
vect(int);//constructeur
~vect();//destructeur
T&operator[](int);//operateurd'accesaunelement
};
template<classT>vect<T>::vect(intn)
{adr=newT[nelem=n];
}
template<classT>vect<T>::~vect()
{deleteadr;
}
template<classT>T&vect<T>::operator[](inti)
{if((i<0)||(i>nelem))i=0;//protectionindicehorslimites
returnadr[i];
}
Ladéfinitiondupatronde classes serait plus simple si les fonctionsmembre étaient
319
«enligne».
Notezque, iciencore,nousavons faitensortequ’une tentatived’accèsàunélémentsituéendehorsduvecteurconduiseàaccéderàl’élémentderang0.Danslapratique,onauraintérêtàutiliserdesprotectionsplusélaborées.
À titre indicatif, voici un petit programme, accompagné du résultat fourni par sonexécution,utilisantcepatron(dontonsupposequeladéfinitionfiguredansvectgen.h):
#include"vectgen.h"
#include<iostream>
usingnamespacestd;
main()
{vect<int>vi(10);
vi[5]=5;vi[2]=2;
cout<<vi[2]<<""<<vi[5]<<"\n";
vect<double>vd(3);
vd[0]=0.0;vd[1]=0.1;vd[2]=0.2;
cout<<vd[0]<<""<<vd[1]<<""<<vd[2]<<"\n";
cout<<vd[12]<<"\n";
vd[12]=1.2;cout<<vd[12]<<""<<vd[0];
}
25
00.10.2
0
1.21.2
Notre patron vect permet d’instancier des vecteurs dynamiques dans lesquels lesélémentssontdetypeabsolumentquelconque,enparticulierdetypeclasse(pourvuqueladiteclassedisposed’unconstructeursansargument). Iln’enseraitpasalléainsisinousavionsinitialisélesélémentsdutableaulorsdeleurconstructionpar:
inti;
for(i=0;i<nelem;i++)adr[i]=0;
En effet, dans ce cas, ces instructions auraient convenablement fonctionné pourn’importe quel type de base (par conversion de l’entier 0 dans le type voulu). Enrevanche,pourêtreapplicableàdesélémentsde typeclasse, il aurait fallu, enoutre,quelaclasseconcernéedisposed’uneconversiond’unintdanscetypeclasse,c’est-à-dired’unconstructeuràunargumentdetypenumérique.
320
Exercice131
ÉnoncéComme dans l’exercice précédent, réaliser un patron de classes permettant demanipulerdesvecteursdontlesélémentssontdetypequelconquemaispourlesquelsladimension,supposéeêtrecettefoisuneexpressionconstante,apparaîtracommeunparamètre(expression)dupatron.Hormiscettedifférence,les«fonctionnalités»dupatronresterontlesmêmes.
Il n’est plus nécessaire d’allouer un emplacement dynamique pour notre vecteur quipeutdoncfigurerdirectementdanslesmembresdonnéedenotrepatron.Leconstructeurn’est plus nécessaire (voir toutefois la remarque ci-dessous), pas plus que ledestructeur.Voicicequepourraitêtreladéfinitiondenotrepatron:
template<classT,intn>classvect
{Tv[n];//vecteurdenelementsdetypeT
public:
T&operator[](int);//operateurd'accesaunelement
};
template<classT,intn>T&vect<T,n>::operator[](inti)
{if((i<0)||(i>n))i=0;//protectionindicehorslimites
returnv[i];
}
Voici,toujoursàtitreindicatif,cequedeviendraitlepetitprogrammed’essai:#include"vectgen1.h"
#include<iostream>
usingnamespacestd;
main()
{vect<int,10>vi;
vi[5]=5;vi[2]=2;
cout<<vi[2]<<""<<vi[5]<<"\n";
vect<double,3>vd;
vd[0]=0.0;vd[1]=0.1;vd[2]=0.2;
cout<<vd[0]<<""<<vd[1]<<""<<vd[2]<<"\n";
cout<<vd[12]<<"\n";
vd[12]=1.2;cout<<vd[12]<<""<<vd[0];
}
Ici,nousn’avonspaseubesoindefairedunombred’élémentsunmembredonnéedenosclassespatron:eneffet,lorsqu’onenabesoin,onl’obtientcommeétantlavaleurdusecondparamètrefournilorsdel’instanciation.Sinousavionsvouluconserverce
321
nombred’élémentssousformed’unmembredonnée,ilauraitéténécessairedeprévoirunconstructeur,parexemple:
template<classT,intn>classvect
{intnelem;//nombred'elements
Tv[n];//vecteurdenelementsdetypeT
public:
vect();
T&operator[](int);//operateurd'accesaunelement
};
template<classT,intn>vect<T,n>::vect()
{nelem=n;
}
322
Exercice132
ÉnoncéOndisposedupatrondeclassessuivant:
template<classT>classpoint
{Tx,y;//coordonnees
public:
point(Tabs,Tord){x=abs;y=ord;}
voidaffiche()
{cout<<"Coordonnees:"<<x<<""<<y<<"\n";
}
};
a.Créer, pardérivation,unpatronde classes pointcol permettant demanipuler des«pointscolorés»danslesquelslescoordonnéesetlacouleursontdemêmetype.On redéfinira convenablement les fonctionsmembre en réutilisant les fonctionsmembredelaclassedebase.
b.Mêmequestion,maisenprévoyantquelescoordonnéesetlacouleurpuissentêtrededeuxtypesdifférents.
c.Toujourspardérivation,créercettefoisune«classeordinaire»(c’est-à-direuneclassequinesoitplusunpatrondeclasses,autrementditquinedépendeplusdeparamètres...) dans laquelle les coordonnées sont de type int, tandis que lacouleurestdetypeshort.
a. Aucun problème particulier ne se pose ; il suffit de faire dériver pointcol<T> depoint<T>.Voicicequepeutêtreladéfinitiondenotrepatron(ici,nousavonslaisséleconstructeur«enligne»maisnousavonsdéfiniafficheendehorsdelaclasse):template<classT>classpointcol:publicpoint<T>
{Tcl;
public:
pointcol(Tabs,Tord,Tcoul):point<T>(abs,ord)
{cl=coul;
}
voidaffiche();
};
template<classT>voidpointcol<T>::affiche()
{point<T>::affiche();
cout<<"couleur:"<<cl<<"\n";
}
Voici un petit exemple d’utilisation (il nécessite les déclarations appropriées ou
323
l’incorporationdesfichiersen-têtecorrespondants):main()
{pointcol<int>p1(2,5,1);p1.affiche();
pointcol<float>p2(2.5,5.25,4);p2.affiche();
}
b.Cettefois,laclassedérivéedépenddedeuxparamètres(nommésiciTetU).Voiciceque pourrait être la définition de notre patron (avec, toujours, un constructeur enligneetunefonctionaffichedéfinieàl’extérieurdelaclasse):template<classT,classU>classpointcol:publicpoint<T>
{Ucl;
public:
pointcol(Tabs,Tord,Ucoul):point<T>(abs,ord)
{cl=coul;
}
voidaffiche();
};
template<classT,classU>voidpointcol<T,U>::affiche()
{point<T>::affiche();
cout<<"couleur:"<<cl<<"\n";
}
Voici un exemple d’utilisation (on suppose qu’il est muni des déclarationsappropriées):main()
{
pointcol<int,short>p1(2,5,1);p1.affiche();
pointcol<float,int>p2(2.5,5.25,4);p2.affiche();
}
c.Cettefois,pointcolestunesimpleclasse,nedépendantplusd’aucunparamètre.Voicicequepourraitêtresadéfinition:classpointcol:publicpoint<int>
{
shortcl;
public:
pointcol(intabs,intord,shortcoul):point<int>(abs,ord)
{cl=coul;
}
voidaffiche()
{point<int>::affiche();
cout<<"couleur:"<<cl<<"\n";
}
};
Etunpetitexempled’utilisation:main()
{
pointcolp1(2,5,1);p1.affiche();
pointcolp2(2.5,5.25,4);p2.affiche();
}
324
Exercice133
ÉnoncéOndisposedumêmepatrondeclassesqueprécédemment:
template<classT>classpoint
{
Tx,y;//coordonnees
public:
point(Tabs,Tord){x=abs;y=ord;}
voidaffiche()
{cout<<"Coordonnees:"<<x<<""<<y<<"\n";
}
};
a.LuiajouteruneversionspécialiséedeaffichepourlecasoùTestletypecaractère.
b. Comme dans la questiona de l’exercice précédent, créer un patron de classespointcol permettant de manipuler des « points colorés » dans lesquels lescoordonnéeset lacouleursontdemêmetype.Onredéfiniraconvenablementlesfonctionsmembreenréutilisantlesfonctionsmembredelaclassedebaseetl’onprévoira une version spécialisée de affiche de pointcol dans le cas du typecaractère.
a.voidpoint<char>::affiche()
{cout<<"Coordonnees:"<<(int)x<<""<<(int)y<<"\n";
}
b.template<classT>classpointcol:publicpoint<T>
{Tcl;
public:
pointcol(Tabs,Tord,Tcoul):point<T>(abs,ord)
{cl=coul;
}
voidaffiche()
{point<T>::affiche();
cout<<"couleur:"<<cl<<"\n";
}
};
voidpointcol<char>::affiche()
{point<char>::affiche();
cout<<"couleur:"<<(int)cl<<"\n";
}
325
Seule la question a de l’exercice 132 se prêtait à une spécialisation pour le typecaractère.Eneffet,pour laclassedemandéeenc,n’ayantplusaffaireàunpatrondeclasses,laquestionn’auraitaucunsens.Encequiconcernelaclassedemandéeenb,enrevanche,onsetrouveenprésenced’uneclassedérivéedépendantde2paramètresTetU.Ilfaudraitalorspouvoirspécialiseruneclasse,nonpluspourdesvaleursdonnéesdetous les (deux) paramètres,mais pour une valeur donnée (char) de l’un d’entre eux ;cettenotiondefamilledespécialisationn’existepasdanslanormeactuelledeC++.
326
Exercice134
ÉnoncéOndisposedupatrondeclassessuivant:
template<classT>classpoint
{Tx,y;//coordonnees
public:
point(Tabs,Tord){x=abs;y=ord;}
voidaffiche()
{cout<<"Coordonnees:"<<x<<""<<y<<"\n";
}
};
Onsouhaitecréerunpatrondeclasses cercle permettantdemanipulerdes cercles,définisparleurcentre(detypepoint)etunrayon.Onn’yprévoira,commefonctionsmembre, qu’un constructeur et une fonction affiche se contentant d’afficher lescoordonnéesducentreetlavaleurdurayon.
a.Lefaireparhéritage(uncercleestunpointquipossèdeunrayon).
b. Le faire par composition d’objets membre (un cercle possède un point et unrayon).
a.Ladémarcheestanalogueàcelledelaquestionadel’exercice132.Onaaffaireàunpatrondépendantdedeuxparamètres(iciTetU):template<classT,classU>classcercle:publicpoint<T>
{Ur;//rayon
public:
cercle(Tabs,Tord,Uray):point<T>(abs,ord)
{r=ray;
}
voidaffiche()
{point<T>::affiche();
cout<<"rayon:"<<r;
}
};
b.Lepatrondépendtoujoursdedeuxparamètres(TetU)mais iln’yaplusdenotiond’héritage:template<classT,classU>classcercle
{point<T>c;//centre
Ur;//rayon
public:
cercle(Tabs,Tord,Uray):c(abs,ord)//pourraitr(ray)
{r=ray;
}
327
voidaffiche()
{c.affiche();
cout<<"rayon:"<<r;
}
};
Notezque,dansladéfinitionduconstructeurcercle,nousavonstransmislesargumentsabsetordàunconstructeurdepointpourlemembrec.Nousaurionspuutiliserlamêmenotationpourr,bienquecemembresoitd’un typedebaseetnond’un typeclasse ;celanousauraitconduitauconstructeursuivant(decorpsvide):
cercle(Tabs,Tord,Uray):c(abs,ord),r(ray)
{}
328
Chapitre19Gestiondesexceptions
329
Rappels
LemécanismegénéralDepuislaversion3,C++disposed’unmécanismeditdegestiondesexceptions.Uneexceptionestunerupturedeséquence(pasunappeldefonction!)déclenchée(onditaussi « levée ») par un programme à l’aide de l’instruction throw dans laquelle onmentionne une expression quelconque. Il y a alors branchement à un ensembled’instructions, dit « gestionnaire d’exceptions », choisi en fonction de la nature del’expressionindiquéeàthrow.
Pourqu’uneportiondeprogrammepuisse intercepteruneexception, ilestnécessairequ’ellefigureàl’intérieurd’unblocprécédédumot-clétry.Cederniersoitêtresuivid’une ou de plusieurs instructions catch représentant les différents gestionnairescorrespondants,commedansceschéma:
try
{.....//instructionssusceptiblesdeleveruneexception,soit
//directementparthrow(exp),soitparlebiaisdefonctions
//appelées
}
catch(type_a...)
{.....//traitementdel'exceptioncorrespondantautypetype_a
}
catch(type_b...)
{.....//traitementdel'exceptioncorrespondantautypetype_b
}
.....
catch(type_n...)
{.....//traitementdel'exceptioncorrespondantautypetype_n
}
Ungestionnaired’exceptionspeutcontenirdesinstructionsexitouabortquimettentfinàl’exécution du programme.Une instruction return fait sortir de la fonction ayant levél’exception.Danslesautrescas(rarementutilisés),onpasseauxinstructionssuivantlebloctryconcerné.
Algorithmedechoixd’ungestionnaired’exceptionsLorsqu’uneexceptionestlevéeparthrow,avecletypeT,onrecherche,danscetordre,un gestionnaire correspondant à l’un des types suivants : type T, type de base de T,pointeur sur une classe dérivée (si T est d’un type pointeur sur une classe), typeindéterminé(indiquéparcatch(...))danslegestionnaire.
330
CheminementdesexceptionsLorsqu’une exception est levée par une fonction, on cherche tout d’abord ungestionnairedansl’éventuelbloctryassociéàcettefonction;si l’onn’entrouvepas(ousiaucunbloctryn’estassocié),onpoursuitlarecherchedansunéventuelbloctryassociéàune fonctionappelanteetainsidesuite.Siaucungestionnaired’exceptionsn’est trouvé, on appelle la fonction terminate qui, par défaut appelle abort, laquellefournitunmessagedugenreabnormalprogramtermination.
Spécificationd’interfaceUnefonction(ycomprismain)peut spécifier lesexceptionsqu’elleest susceptibledeprovoquer(elle-même,oudanslesfonctionsqu’elleappelleàsontour).Ellelefaitàl’aidedumot-cléthrow,suivi,entreparenthèses,delalistedesexceptionsconcernées.Danscecas,touteexceptionnonprévueetlevéeàl’intérieurdelafonction(oud’unefonction appelée) entraîne l’appel d’une fonction particulière nommée unexpected. Pardéfaut,cettefonctionappellelafonctionterminate.
331
Exercice135
ÉnoncéQuelsrésultatsfourniraceprogrammelorsqu’onluifournitcommedonnées:
a.lavaleur1þ?
b.lavaleur0?#include<iostream>
usingnamespacestd;
main()
{intn;
cout<<"donnezunentier:";
cin>>n;
try
{cout<<"debutpremierbloctry\n";
if(n)thrown;
cout<<"finpremierbloctry\n";
}
catch(intn)
{cout<<"catch1-n="<<n<<"\n";
}
cout<<"suiteprogramme\n";
try
{cout<<"debutsecondbloctry\n";
thrown;
cout<<"finsecondbloctry\n";
}
catch(intn)
{cout<<"catch2-n="<<n<<"\n";
}
cout<<"finprogramme\n";
}
a.Aveclavaleur1:donnezunentier:1
debutpremierbloctry
catch1-n=1//fourniparlebloccatch(int)associéaupremier
//bloctry
suiteprogramme
debutsecondbloctry
catch2-n=1//fourniparlebloccatch(int)associéausecondbloc
//try
finprogramme
On notera bien que, dans le premier bloc try, l’exception de type int provoque unbranchement au bloc catch(int) correspondant. Comme ce dernier ne comporte pasd’instruction d’interruption de programme ou de fonction, on passe aux instructions
332
suivantlederniergestionnaireassocié,c’est-à-direiciaubloctrysuivant.Làencore,une exception de type int provoque le branchement au bloc catch(int) correspondant(différentduprécédent).Puis l’onpasse aux instructions suivantes, c’est-à-dire ici àl’instructionaffichantfinprogramme.
b.Aveclavaleur0:donnezunentier:0
debutpremierbloctry
finpremierbloctry
suiteprogramme
debutsecondbloctry
catch2-n=0
finprogramme
Ici,contrairementàcequiseproduisaitdansl’exécutionprécédente, lepremierbloctry ne déclenche plus d’exception. Son exécution se poursuit donc en entier, d’où lemessagefinpremierbloctry.Lasuiteestidentique.
333
Exercice136
ÉnoncéQuelsrésultatsdonneceprogrammelorsqu’onluifournitcommedonnées:
a.lavaleur1þ?
b.lavaleur2þ?
c.lavaleur3þ?
d.lavaleur4þ?#include<stdlib.h>//pourexit
#include<iostream>
usingnamespacestd;
main()
{intn;floatx;doublez;
cout<<"donnezunentier:";
cin>>n;
try
{switch(n)
{case1:thrown;break;
case2:x=n;throwx;break;
case3:z=n;throwz;break;
}
}
catch(intn)
{cout<<"catchentier-n="<<n<<"\n";
}
catch(floatx)
{cout<<"catchflottant-x="<<x<<"\n";
exit(-1);
}
cout<<"suiteetfinduprogramme\n";
}
a.donnezunentier:1
catchentier-n=1
suiteetfinduprogramme
L’exception de type int levée dans le bloc try est interceptée par le gestionnairecatch(int)quiafficheunmessage.Onpassealorsàl’instructionsuivantlebloctry,quiaffichelemessagesuiteetfinduprogramme.
b.donnezunentier:2
catchflottant-x=2
334
L’exception de type float levée dans le bloc try est interceptée par le gestionnairecatch(float)quiafficheunmessageavantdemettrefinàl’exécutionduprogramme.
c.donnezunentier:3
abnormalprogramtermination/*ouquelquechosedesemblable*/
L’exception de type double levée dans le bloc try ne dispose d’aucun gestionnaireapproprié (ilauraitdûêtredu typecatch(double).Onappelledonc la fonctionterminatequi,pardéfaut,appellelafonctionabort, laquellemetfinàl’exécutionduprogrammeenaffichantunmessageapproprié.
d.donnezunentier:4
suiteetfinduprogramme
Ici,aucuneexceptionn’aétélevéeparlebloctryetonexécutel’instructionquilesuit,laquelleaffichelemessagedefindeprogramme.
335
Exercice137
ÉnoncéQuelsrésultatsproduiraceprogramme?
#include<iostream>
usingnamespacestd;
classerreur
{public:
intnum;
};
classerreur_d:publicerreur
{public:
intcode;
};
classA
{public:
A(intn)
{if(n==1){erreur_derd;erd.num=999;
erd.code=12;throwerd;}
}
};
main()
{
try
{Aa(1);
cout<<"aprescreationa(1)\n";
}
catch(erreurer)
{cout<<"exceptionerreur:"<<er.num<<"\n";
}
catch(erreur_derd)
{cout<<"exceptionerreur_d:"<<erd.num<<""<<erd.code<<"\n";
}
cout<<"suite\n";
try
{Ab(1);
cout<<"aprescreationb(1)\n";
}
catch(erreur_derd)
{cout<<"exceptionerreur_d:"<<erd.num<<""<<erd.code<<"\
n";
}
catch(erreurer)
{cout<<"exceptionerreur:"<<er.num<<"\n";
}
}
exceptionerreur:999
suite
exceptionerreur_d:99912
336
Dans le premier bloc try, la construction de l’objet a(1) lève une exception de typeerreur_d,entransmettantuneexpression(erd)decetypedanslaquelleleschampsnumetcode valent respectivement 999 et 12. Le premier gestionnaire trouvé (catch(erreur er))
convientpuisqueerreurestuntypedebasedeerreur_d.C’estdoncluiquiestexécuté,cequi explique que la valeur du champ code ne soit pas affichée, et ceci malgrél’existence,unpeuplusloin,d’ungestionnairemieuxapproprié(catch(erreur_d)).
Dans lesecondbloctry, en revanche, lesmêmesgestionnairesont étédisposésdansl’ordre inverse. L’exception levée par la construction de b est alors traitée par legestionnaireappropriéetlavaleurduchampcodeesteffectivementaffichée.
337
Exercice138
ÉnoncéSoitladéfinitionsuivantedesclasseserreuretA:
classerreur
{public:
intnum;
};
classA
{public:
A(intn)
{if(n==1){erreurer;er.num=999;thrower;}
}
};
1.Quelsrésultatsfourniraceprogrammeutilisantcesdeuxclasses:#include<iostream>
usingnamespacestd;
main()
{voidf();
try
{f();
}
catch(erreurer)
{cout<<"dansmain:"<<er.num<<"\n";
}
cout<<"suitemain\n";
}
voidf()
{try
{Aa(1);
}
catch(erreurer)
{cout<<"dansf:"<<er.num<<"\n";
}
cout<<"suitef\n";
}
2.Mêmequestionenremplaçantladéfinitiondefpar:voidf()
{try
{Aa(1);
}
catch(erreurer)
{cout<<"dansf:"<<er.num<<"\n";
return;
}
cout<<"suitef\n";
}
3.Mêmequestionenremplaçantladéfinitiondefpar:voidf()
{Aa(1);
338
}
1.dansf:999
suitef
suitemain
L’exception levée par la construction dans f de a(1) est traitée par le gestionnairecatch(erreur) associé au bloc try correspondant de la fonction elle-même.On exécuteensuitel’instructionsuivantcebloc,laquelleaffichesuitef,avantd’effectuerunretourclassiquedelafonctionfdansmain.Onnoteraquelebloctrydemainn’intervientpasici.
2.dansf:999
suitemain
Là encore, l’exception levée f par la construction dans f de a(1) est traitée par legestionnairecatch(erreur) associéaubloc try correspondant de la fonction elle-même.Mais cette fois, celui-ci se termine par une instruction return, laquelle provoque leretourdelafonctionconcernée,àsavoirf(attention,paslegestionnaired’exceptions,quin’estpasunefonction!).
3.dansmain:999
suitemain
Cettefois,laconstructiondea(1)dansfnefigurepasdansunbloctry.Larecherchedugestionnairedel’exceptionalorslevéesefaitdansunbloctryenglobant,àsavoiriciceluidanslequelaeulieul’appeldef.
339
Exercice139
ÉnoncéQuelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
classA
{public:
A(intn)
{try
{if(n==1)thrown;
}
catch(intn)
{cout<<"dansconstructeurA:"<<n<<"\n";
throw;
}
}
};
main()
{voidf();
try
{f();
}
catch(intn)
{cout<<"dansmain:"<<n<<"\n";
}
cout<<"finmain\n";
}
voidf()
{
try
{Aa(1);
}
catch(intn)
{cout<<"dansf:"<<n<<"\n";
throw;
}
cout<<"suitef\n";
}
dansconstructeurA:1
dansf:1
dansmain:1
finmain
L’exceptionlevéeparlaconstructiondel’objeta(1)esttoutd’abordinterceptéeparlegestionnaire associé au bloc try du constructeur, d’où le premier message. Maisl’instruction throw qu’il contient demande de transmettre l’exception au bloc try
englobant, à savoir ici celui figurant dans la fonction f d’où le secondmessage. Làencore,uneinstructionthrowretransmetl’exceptionauniveausupérieur,àsavoirlebloc
340
trydemain.
341
Exercice140
ÉnoncéÉcrireuneclassepoint(àdeuxcoordonnéesentières)quidisposed’unconstructeuràdeuxarguments levantuneexceptionlorsquelesdeuxcomposantessontégales.Deplus,l’appeld’unconstructeursansargumentouàunseulargumentdevraégalementleverun autre typed’exception. Ici, on limitera les fonctionsmembrede point auxseulsconstructeurs.
Écrireunprogrammed’utilisationdelaclassepoint,interceptantconvenablementlesexceptionsprévues,enmentionnantlacause.
Il est préférable d’associer un type classe à chacune des deux sortes d’exceptionsprévues. Nous choisirons er_compos pour l’exception déclenchée en cas d’égalité descomposanteseter_constrpourcelledéclenchéeencasd’appeld’unconstructeurà0ou1argument ; la distinction entre les deux se fera par une valeur entière transmise auconstructeur et conservée ici dans un champ public. Voici ce que pourrait être ladéfinitiondenotreclasse:
classer_compos
{};
classer_constr
{public:
intnum;
er_constr(intn){num=n;}
};
classpoint
{intx,y;
public:
point(intabs,intord)
{if(abs==ord)thrownewer_compos;
x=abs;y=ord;
}
point()
{thrownewer_constr(0);
}
point(intabs)
{thrownewer_constr(1);
}
};
Voiciunexempledeprogrammequisecontentedesignalerlesexceptionsinterceptéesen interrompant l’exécution. On notera que les trois constructions proposéesprovoquent effectivement une exception qui, au bout du compte, interrompt le
342
programme.Autrementdit,pourpercevoir l’effetdelasecondeoudelatroisième,ilfaudraitlaplaceravantlesautres.
#include<iostream>
usingnamespacestd;
main()
{
try
{//.....
pointb(1,1);//afficherait:exceptioncreationpoint:
//composantesegales
point();//afficherait:exceptioncreationpoint:
//constructeur0arg
point(3);//afficherait:exceptioncreationpoint:
//constructeur1arg
//.....
}
catch(er_compose)
{cout<<"exceptioncreationpoint:composantesegales\n";
exit(-1);
}
catch(er_constrec)
{switch(ec.num)
{case1:cout<<"exceptioncreationpoint:constructeur0arg\n";
break;
case2:cout<<"exceptioncreationpoint:constructeur1arg\n";
break;
}
exit(-1);
}
}
343
Exercice141
Énoncé1.Quelsrésultatsfourniraceprogramme:
#include<iostream>
usingnamespacestd;
main()
{voidf();
try
{f();
}
catch(intn)
{cout<<"exceptintdansmain:"<<n<<"\n";
}
catch(...)
{cout<<"exceptionautrequeintdansmain\n";
}
cout<<"finmain\n";
}
voidf()
{
try
{intn=1;thrown;
}
catch(intn)
{cout<<"exceptintdansf:"<<n<<"\n";
throw;
}
}
2.Mêmequestionsil’onremplacelafonctionfpar:voidf()
{
try
{floatx=2.5;throwx;
}
catch(intn)
{cout<<"exceptintdansf:"<<n<<"\n";
throw;
}
}
1.exceptintdansf:1
exceptintdansmain:1
finmain
L’exception de type int levée dans f est traitée par le gestionnaire catch(int)
correspondant.Elle est retransmiseàunblocenglobantpar throw, donc traitée par le
344
gestionnairecatch(int)dubloctrydumain.
2.exceptionautrequeintdansmain
finmain
Cettefois,aucungestionnaireapproprién’existepourtraiter l’exceptiondetypefloatlevée dans la fonction f. On recherche un gestionnaire approprié dans un bloc tryenglobant,cequiconduiticiàcatch(...).
345
Chapitre20Exercicesdesynthèse
346
Exercice142
ÉnoncéRéaliser une classe nommée set_int permettant de manipuler des ensembles denombresentiers.Lenombremaximald’entiersquepourracontenirl’ensembleseraprécisé au constructeur qui allouera dynamiquement l’espace nécessaire. Onprévoiralesopérateurssuivants(edésigneunélémentdetypeset_intetnunentier:
•<<,telquee<<najoutel’élémentnàl'ensembleeþ;
•%,telquen%evale1sinappartientàeet0sinonþ;
•<<,telqueflot<<eenvoielecontenudel’ensembleesurleflotindiqué,souslaforme:
[entier1,entier2,...entiern]
Lafonctionmembrecardinalfourniralenombred’élémentsdel’ensemble.Enfin,ons’arrangera pour que l’affectation ou la transmission par valeur d’objets de typeset_intneposeaucunproblème(onaccepteraladuplicationcomplèted’objets).
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.
Naturellement,notreclassecomportera,enmembresdonnée,lenombremaximal(nmax)d’éléments de l’ensemble, le nombre courant d’éléments (nelem) et un pointeur surl’emplacementcontenantlesvaleursdel’ensemble.
Comme notre classe comporte une partie dynamique, il est nécessaire, pour quel’affectationet la transmissionparvaleur sedéroulent convenablement,de surdéfinirl’opérateur d’affectation et demunir notre classe d’un constructeur par recopie. Lesdeuxfonctionsmembre(operator=etset_int )devrontprévoirune«copieprofonde»desobjets.Nousutiliseronspourcelauneméthodequenousavonsdéjàrencontréeetqui consiste à considérer que deux objets différents disposent systématiquement dedeuxpartiesdynamiquesdifférentes,mêmesiellespossèdentlemêmecontenu.
L’opérateur % doit être surdéfini obligatoirement sous la forme d’une fonction amie,puisquesonpremieropéranden’estpasde typeclasse.L’opérateurdesortiedansunflot doit, lui aussi, être surdéfini sous la forme d’une fonction amie, mais pour une
347
raisondifférente:sonpremierargumentestdetypeostream.
Voiciladéclarationdenotreclasseset_int:/*fichierSETINT.H:déclarationdelaclasseset_int*/
#include<iostream>
usingnamespacestd;
classset_int
{int*adval;//adressedutableaudesvaleurs
intnmax;//nombremaxid'éléments
intnelem;//nombrecourantd'éléments
public:
set_int(int=20);//constructeur
set_int(set_int&);//constructeurparrecopie
//voirremarque1ci-après
set_int&operator=(set_int&);//opérateurd'affectation
//voirremarque2ci-après
~set_int();//destructeur
intcardinal();//cardinaldel'ensemble
set_int&operator<<(int);//ajoutd'unélément
friendintoperator%(int,set_int&);//appartenanced'unélément
//voirremarque3ci-après
//envoiensembledansunflot,voirremarque4
friendostream&operator<<(ostream&,set_int&);
};
Voici ce que pourrait être la définition de notre classe (les points délicats sontcommentésauseinmêmedesinstructions):
#include"setint.h"
#include<iostream>
usingnamespacestd;
/***************constructeur********************/
set_int::set_int(intdim)
{adval=newint[nmax=dim];//allocationtableaudevaleurs
nelem=0;
}
/******************destructeur******************/
set_int::~set_int()
{deleteadval;//libérationtableaudevaleurs
}
/**********constructeurparrecopie*************/
set_int::set_int(set_int&e)
{adval=newint[nmax=e.nmax];//allocationnouveautableau
nelem=e.nelem;
inti;
for(i=0;i<nelem;i++)//copieancientableaudansnouveau
adval[i]=e.adval[i];
}
/************opérateurd'affectation************/
set_int&set_int::operator=(set_int&e)//commentairesfaitpourb=a
{if(this!=&e)//onnefaitrienpoura=a
{deleteadval;//libérationpartiedynamiquedeb
adval=newint[nmax=e.nmax];//allocationnouvelensemblepoura
nelem=e.nelem;//danslequelonrecopie
inti;//entièrementl'ensembleb
for(i=0;i<nelem;i++)//avecsapartiedynamique
adval[i]=e.adval[i];
}
return*this;
}
/************fonctionmembrecardinal***********/
intset_int::cardinal()
{returnnelem;
348
}
/************opérateurd'ajout<<***************/
set_int&set_int::operator<<(intnb)
{//onexaminesinbappartientdéjààl'ensemble
//enutilisantl'opérateur%
//s'iln'yappartientpas,ets'ilyaencoredelaplace
//onl'ajouteàl'ensemble
if(!(nb%*this)&&nelem<nmax)adval[nelem++]=nb;
return(*this);
}
/***********opérateurd'appartenance%**********/
intoperator%(intnb,set_int&e)
{inti=0;
//onexaminesinbappartientdéjààl'ensemble
//(danscecasivaudraneleenfindeboucle)
while((i<e.nelem)&&(e.adval[i]!=nb))i++;
return(i<e.nelem);
}
/******opérateur<<poursortiesurunflot*****/
ostream&operator<<(ostream&sortie,set_int&e)
{sortie<<"[";
inti;
for(i=0;i<e.nelem;i++)
sortie<<e.adval[i]<<"";
sortie<<"]";
returnsortie;
}
1. On pourrait ajouter le qualificatif const au constructeur par recopie, ce quiautoriserait l’initialisationd’unobjetparunobjet constant.Mais compte tenudespossibilitésd’utilisationdel’autreconstructeurdansdesconversionsimplicites,onautoriseraitdumêmecoup l’initialisationparunentierouun flottant, cequin’estguèresatisfaisant;onpourraitcependantinterdiredetellespossibilitésenutilisantlemot-cléexplicitdansladéclarationduconstructeur.
2.Latransmissiondelavaleurderetourdel’opérateurd’affectationn’estutilequesil’on souhaite permettre les affectations multiples. Il n’est pas indispensable detransmettre l’argument et la valeur de retour par référence, mais cela évite lesrecopies. On pourrait ici déclarer constant l’unique argument, ce qui autoriseraitl’utilisationd’unobjetconstantensecondopérandedel’affectation(moyennantunerecopie). Mais, du même coup, compte tenu des possibilités de conversionsimplicites,onautoriseraitégalementl’utilisationd’unentieroud’unflottant,cequin’est pas nécessairement souhaité ; là encore, ces possibilités pourraient êtreinterdites,moyennantl’utilisationappropriéedumot-cléexplicit.
3. On pourrait ajouter le qualificatif const à l’opérateur %, ce qui permettrait detravaillersurunensembleconstant;néanmoins,danscecas,ilyauraittransmissiond’unecopiedecetensembleàlafonction,cequin'estplustrèsefficace!
349
4.Laremarqueprécédentes’appliqueàl’opérateur<<.
5.Notezqu’iciiln’estpaspossibled’agrandirl’ensembleau-delàdelalimitequiluiaétéimpartielorsdesaconstruction.Ilseraitassezfacilederemédieràcettelacuneenmodifiantsensiblementlafonctiond’ajoutd’unélément(operator<<).Ilsuffirait,en effet, qu’elle prévoie, lorsque la limite est atteinte, d’allouer un nouvelemplacement dynamique, par exemple d’une taille double de l’emplacementexistant, d’y recopier l’actuel contenu et de libérer l’ancien emplacement (enactualisantconvenablementlesmembresdonnéedel’objet).
Voici un exemple de programme utilisant la classe set_int, accompagné du résultatfourniparsonexécution:
/*************testdelaclasseset_int*********/
#include"setint.h"
#include<iostream>
usingnamespacestd;
main()
{voidfct(set_int);
voidfctref(set_int&);
set_intens;
cout<<"donnez10entiers\n";
inti,n;
for(i=0;i<10;i++)
{cin>>n;
ens<<n;
}
cout<<"ilya:"<<ens.cardinal()<<"entiersdifférents\n";
cout<<"quiformentl\'ensemble:"<<ens<<"\n";
fct(ens);
cout<<"auretourdefct,ilyena"<<ens.cardinal()<<"\n";
cout<<"quiformentl\'ensemble:"<<ens<<"\n";
fctref(ens);
cout<<"auretourdefctref,ilyena"<<ens.cardinal()<<"\n";
cout<<"quiformentl\'ensemble:"<<ens<<"\n";
cout<<"appartenancede-1:"<<-1%ens<<"\n";
cout<<"appartenancede500:"<<500%ens<<"\n";
set_intensa,ensb;
ensa=ensb=ens;
cout<<"ensemblea:"<<ensa<<"\n";
cout<<"ensembleb:"<<ensb<<"\n";
}
voidfct(set_inte)
{cout<<"ensemblereçuparfct:"<<e<<"\n";
e<<-1<<-2<<-3;
}
voidfctref(set_int&e)
{cout<<"ensemblereçuparfctref:"<<e<<"\n";
e<<-1<<-2<<-3;
}
donnez10entiers
3531851773
ilya:5entiersdifférents
quiformentl'ensemble:[35187]
ensemblereçuparfct:[35187]
350
auretourdefct,ilyena5
quiformentl'ensemble:[35187]
ensemblereçuparfctref:[35187]
auretourdefctref,ilyena8
quiformentl'ensemble:[35187-1-2-3]
appartenancede-1:1
appartenancede500:0
ensemblea:[35187-1-2-3]
ensembleb:[35187-1-2-3]
351
Exercice143
ÉnoncéCréeruneclassevectpermettantdemanipulerdes«vecteursdynamiques»d’entiers,c’est-à-diredestableauxd’entiersdontladimensionpeutêtredéfinieaumomentdeleurcréation(unetelleclasseadéjàétépartiellementréaliséedansl’exercice39).Cetteclassedevradisposerdesopérateurssuivants:
•[] pour l’accès à une des composantes du vecteur, et cela aussi bien au seind’uneexpressionqu’àgauched’uneaffectation(maiscettedernièresituationnedevrapasêtreautoriséesurdes«vecteursconstants»);
•==,telquesiv1etv2sontdeuxobjetsdetypevect,v1==v2prennelavaleur1siv1etv2sontdemêmedimensionetontlesmêmescomposantesetlavaleur0danslecascontraire;
•<<,telqueflot<<venvoielevecteurvsurleflotindiqué,souslaforme:<entier1,entier2,...,entiern>
Deplus,ons’arrangerapourquel’affectationetlatransmissionparvaleurd’objetsde type vect ne pose aucun problème ; pour ce faire, on acceptera de dupliquercomplètementlesobjetsconcernés.
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.
Rappelons que lorsqu’on définit des objets constants, il n’est pas possible de leurappliquerunefonctionmembrepublique,saufsicettedernièreaétédéclaréeaveclequalificatifconst (auquel cas une telle fonctionpeut indifféremment être utilisée avecdesobjetsconstantsounonconstants).Pourobtenirl’effetdemandédel’opérateur[],lorsqu’il est appliqué à un vecteur constant, il est nécessaire d’en prévoir deuxdéfinitionsdontl’unes’appliqueauxvecteursconstants;pouréviterqu’onnepuisse,danscecas,l’utiliseràgauched’uneaffectation,ilestnécessairequ’ellerenvoiesonrésultat par valeur (et non par adresse comme le fera la fonction applicable auxvecteursnonconstants).
Voiciladéclarationdenotreclasse:#include<iostream>
352
usingnamespacestd;
classvect
{intnelem;//nombredecomposantesduvecteur
int*adr;//pointeursurpartiedynamique
public:
vect(intn=1);//constructeur"usuel"
vect(vect&v);//constructeurparrecopie,
//voirremarque1ci-après
~vect();//destructeur
friendostream&operator<<(ostream&,vect&);//sortiesurunflot
vect&operator=(vect&v);//surdéfinitionopérateuraffectation
//voirremarque2ci-après
int&operator[](inti);//surdef[]pourvectnonconstants
intoperator[](inti)const;//surdef[]pourvectconstants
};
1. On pourrait ajouter le qualificatif const au constructeur par recopie, ce quiautoriserait l’initialisationd’unvecteurparunvecteurconstant.Maiscompte tenudes possibilités de l’autre constructeur dans des conversions implicites, onautoriseraitdumêmecoup l’initialisationparunentierouun flottant, cequin’estguèresatisfaisant;onpourraitcependantinterdiredetellespossibilitésenutilisantlemot-cléexplicitdansladéclarationduconstructeur.
2.Latransmissiondelavaleurderetourdel’opérateurd’affectationn’estutilequesil’on souhaite permettre les affectations multiples. Il n’est pas indispensable detransmettre l’argument et la valeur de retour par référence, mais cela évite lesrecopies. On pourrait ici déclarer constant l’unique argument, ce qui autoriseraitl’utilisationd’unobjetconstantensecondopérandedel’affectation(moyennantunerecopie). Mais, du même coup, compte tenu des possibilités de conversionsimplicites,onautoriseraitégalementl’utilisationd’unentieroud’unflottant,cequin’est pas nécessairement souhaité ; là encore, ces possibilités pourraient êtreinterdites,moyennantl’utilisationappropriéedumot-cléexplicit.
Voiciladéfinitiondesdifférentesfonctions:#include"vect.h"
#include<iostream>
usingnamespacestd;
vect::vect(intn)//constructeur"usuel"
{adr=newint[nelem=n];
}
vect::vect(vect&v)//constructeurparrecopie
{adr=newint[nelem=v.nelem];
inti;
for(i=0;i<nelem;i++)
adr[i]=v.adr[i];
}
vect::~vect()//destructeur
{deleteadr;
}
vect&vect::operator=(vect&v)//surdéfinitionopérateuraffectation
353
{if(this!=&v)//onnefaitrienpoura=a
{deleteadr;
adr=newint[nelem=v.nelem];
inti;
for(i=0;i<nelem;i++)
adr[i]=v.adr[i];
}
return*this;
}
int&vect::operator[](inti)//surdéfinitionopérateur[]
{returnadr[i];
}
intvect::operator[](inti)const//surdéfinitionopérateur[]pourcst
{returnadr[i];
}
ostream&operator<<(ostream&sortie,vect&v)
{sortie<<"<";
inti;
for(i=0;i<v.nelem;i++)sortie<<v.adr[i]<<"";
sortie<<">";
returnsortie;
}
À titre indicatif, voici un petit programme utilisant la classe vect, accompagné durésultatfourniparsonexécution:
#include"vect.h"
#include<iostream>
usingnamespacestd;
main()
{inti;
vectv1(5),v2(10);
for(i=0;i<5;i++)v1[i]=i;
cout<<"v1="<<v1<<"\n";
for(i=0;i<10;i++)v2[i]=i*i;
cout<<"v2="<<v2<<"\n";
v1=v2;
cout<<"v1="<<v1<<"\n";
vectv3=v1;
cout<<"v3="<<v3<<"\n";
vectv4=v2;
cout<<"v4="<<v4<<"\n";
//constvectw(3);w[2]=5;//conduitbienàerreurcompilation
}
v1=<01234>
v2=<0149162536496481>
v1=<0149162536496481>
v3=<0149162536496481>
v4=<0149162536496481>
354
Exercice144
ÉnoncéRéaliseruneclassenommée bit_array permettantdemanipulerdes tableauxdebits(autrementdit,destableauxdanslesquelschaqueélémentnepeutprendrequel’unedesdeuxvaleurs0ou1).Latailled’untableau(c’est-à-direlenombredebits)seradéfinielorsdesacréation(parunargumentpasséàsonconstructeur).Onprévoiralesopérateurssuivants:
•+=,telquet+=nmetteà1lebitderangndutableaut;
•-=,telquet-=nmetteà0lebitderangndutableaut;
•[],telquel'expressiont[i]fournisselavaleurdubitderangidutableaut(onne prévoira pas, ici, de pouvoir employer cet opérateur à gauche d'uneaffectation,commedanst[i]=...);
•++,telquet++metteà1touslesbitsdet;
•--,telquet--metteà0touslesbitsdet;
•<<,telqueflot<<tenvoielecontenudetsurleflotindiqué,souslaforme:<*bit1,bit2,...bitn*>
On fera en sorte que l’affectation et la transmission par valeur d’objets du typebit_arrayneposeaucunproblème.
N.B. Le chapitre 21 vous montrera comment résoudre cet exercice à l’aide descomposantsstandardintroduitsparlanorme,qu’ilnefautpaschercheràutiliserici.
Sil’onchercheàminimiserl’emplacementmémoireutilisépourlesobjetsdetypebit_array, il est nécessaire den’employer qu’un seul bit pour représenter un« élément »d’un tableau.Ces bits devront donc être regroupés, par exemple à raison de CHAR_BIT(définidanslimits.h)bitsparcaractère.
Manifestement, il faut prévoir que l’emplacement destiné à ces différents bits soitallouédynamiquementenfonctiondelavaleurfournieauconstructeur:pournbits,ilfaudran/CHAR_BIT+1caractères.
En membres donnée, il nous suffit de disposer d’un pointeur sur l’emplacement
355
dynamique en question, ainsi que du nombre de bits du tableau. Pour simplifiercertainesdesfonctionsmembre,nousprévoironségalementdeconserverlenombredecaractèrescorrespondant.
Lesopérateurs+=,-=,++et--peuventêtredéfinis indifféremmentsous la formed’unefonctionmembreoud’unefonctionamie.Ici,nousavonschoisidesfonctionsmembre.L’énoncé ne précise rien quant au résultat fourni par ces 4 opérateurs. En fait, onpourrait prévoir qu’ils restituent le tableau après qu’ils y ont effectué l’opérationvoulue,maisenpratique,celasembledepeud’intérêt.Ici,nousavonsdoncsimplementprévuquecesopérateursnefourniraientaucunrésultat.
Pourque[]nesoitpasutilisabledansuneaffectationdelaformet[i]=...,ilsuffitdeprévoir qu’il fournisse son résultat par valeur (et non par référence comme on agénéralementl’habitudedelefaire).
Naturellement,iciencore,l’énoncénousimposedesurdéfinirl’opérateurd’affectationetdeprévoirunconstructeurparrecopie.
Voicicequepourraitêtreladéclarationdenotreclassebit_array:/*fichierbitarray.h:déclarationdelaclassebit_array*/
#include<iostream>
usingnamespacestd;
classbit_array
{intnbits;//nombrecourantdebitsdutableau
intncar;//nombredecaractèresnécessaires(redondant)
char*adb;//adressedel'emplacementcontenantlesbits
public:
bit_array(int=16);//constructeurusuel
bit_array(bit_array&);//constructeurparrecopie,
//voirremarque1ci-après
~bit_array();//destructeur
//lesopérateursbinaires
bit_array&operator=(bit_array&);//affectation,
//voirremarque2ci-après
intoperator[](int);//valeurd'unbit
voidoperator+=(int);//activationd'unbit
voidoperator-=(int);//désactivationd'unbit
//envoisurflot
friendostream&operator<<(ostream&,bit_array&);
//lesopérateursunaires
voidoperator++();//miseà1
voidoperator--();//miseà0
voidoperator~();//complémentà1
};
1. On pourrait ajouter le qualificatif const au constructeur par recopie, ce quiautoriserait l’initialisationd’unobjet bit_array par un objet constant.Mais comptetenu des possibilités de conversions implicites, on autoriserait du même coupl’initialisation par un entier ou par un flottant, ce qui n’est guère satisfaisant ; on
356
pourraitcependantinterdiredetellespossibilitésenutilisantlemot-cléexplicitdansleconstructeuràunargumentdetypeint.
2.Laprésenced’unevaleurderetourdansl’opérateurd’affectationn’estutilequepourpermettre les affectations multiples. La transmission par référence de l’uniqueargumentn'estpasobligatoire.Onpourraitajouterlequalificatifconstpourautoriserl’affectation d’un objet constant (moyennant alors une recopie). Mais, du mêmecoup, compte tenu des possibilités de conversions implicites, on autoriseraitégalement l’utilisationd’unentieroud’unflottant,cequin’estpasnécessairementsatisfaisant ; là encore, ces possibilités pourraient être interdites, moyennantl’utilisationappropriéedumot-cléexplicit.
Voiciladéfinitiondesdifférentesfonctions.#include"bitarray.h"
#include<climits>
#include<iostream>
usingnamespacestd;
bit_array::bit_array(intnb)
{nbits=nb;
ncar=nbits/CHAR_BIT+1;
adb=newchar[ncar];
inti;
for(i=0;i<ncar;i++)adb[i]=0;//raz
}
bit_array::bit_array(bit_array&t)
{nbits=t.nbits;ncar=t.ncar;
adb=newchar[ncar];
inti;
for(i=0;i<ncar;i++)adb[i]=t.adb[i];
}
bit_array::~bit_array()
{deleteadb;
}
bit_array&bit_array::operator=(bit_array&t)
{if(this!=&t)//onnefaitrienpourt=t
{deleteadb;
nbits=t.nbits;ncar=t.ncar;
adb=newchar[ncar];
inti;
for(i=0;i<ncar;i++)
adb[i]=t.adb[i];
}
return*this;
}
intbit_array::operator[](inti)
{//lebitderangis'obtientenconsidérantlebit
//derangi%CHAR_BITducaractèrederangi/CHAR_BIT
intcarpos=i/CHAR_BIT;
intbitpos=i%CHAR_BIT;
return(adb[carpos]>>CHAR_BIT-bitpos-1)&0x01;
}
voidbit_array::operator+=(inti)
{intcarpos=i/CHAR_BIT;
if(carpos<0||carpos>=ncar)return;//protection
intbitpos=i%CHAR_BIT;
adb[carpos]|=(1<<(CHAR_BIT-bitpos-1));
357
}
voidbit_array::operator-=(inti)
{intcarpos=i/CHAR_BIT;
if(carpos<0||carpos>=ncar)return;//protection
intbitpos=i%CHAR_BIT;
adb[carpos]&=~(1<<CHAR_BIT-bitpos-1);
}
ostream&operator<<(ostream&sortie,bit_array&t)
{sortie<<"<*";
inti;
for(i=0;i<t.nbits;i++)
sortie<<t[i]<<"";
sortie<<"*>";
returnsortie;
}
voidbit_array::operator++()
{inti;
for(i=0;i<ncar;i++)adb[i]=0xFFFF;
}
voidbit_array::operator--()
{inti;
for(i=0;i<ncar;i++)adb[i]=0;
}
voidbit_array::operator~()
{inti;
for(i=0;i<ncar;i++)adb[i]=~adb[i];
}
Voiciunprogrammed’essaide la classe bit_array, accompagné du résultat fourni parsonexécution:
/*programmed'essaidelaclassebit_array*/
main()
{bit_arrayt1(34);
cout<<"t1="<<t1<<"\n";
t1+=3;t1+=0;t1+=8;t1+=15;t1+=33;
cout<<"t1="<<t1<<"\n";
t1--;
cout<<"t1="<<t1<<"\n";
t1++;
cout<<"t1="<<t1<<"\n";
t1-=0;t1-=3;t1-=8;t1-=15;t1-=33;
cout<<"t1="<<t1<<"\n";
cout<<"t1="<<t1<<"\n";
bit_arrayt2(11),t3(17);
cout<<"t2="<<t2<<"\n";
t2=t3=t1;
cout<<"t3="<<t3<<"\n";
}
t1=<*00000000000000000000000000000000
00*>
t1=<*10010000100000010000000000000000
01*>
t1=<*00000000000000000000000000000000
00*>
t1=<*11111111111111111111111111111111
11*>
t1=<*01101111011111101111111111111111
10*>
t1=<*01101111011111101111111111111111
10*>
358
t2=<*00000000000*>
t3=<*01101111011111101111111111111111
10*>
359
Exercice145
ÉnoncéLacapacitédesnombresentiersest limitéepar la tailledu type longint.Créer uneclasse big_int permettant demanipuler des nombres entiers de valeur absolumentquelconque.
Pour ne pas alourdir l’exercice, on se limitera à des nombres sans signe et àl’opération d’addition ; on s’arrangera toutefois pour que des expressions mixtes(c’est-à-diremélangeantdesobjetsdetypelong_intavecdesentiersusuels)aientunsens.
Ondéfiniral’opérateur<<pourqu’ilpermetted’envoyerunobjetdetypebig_int surun flot. Parmi les différents constructeurs, on en prévoira un avec un argument detypechaînedecaractères,correspondantauxchiffresd’un«grandentier».
On fera en sorte que l’affectation et la transmission par valeur d’objets de typebig_intneposentaucunproblème.
Pour représenterun«grandentier», ladémarche laplusnaturelle (maispas lapluséconomiqueenplacemémoire!)consisteàconserverlenombresousformedécimale,àchaquechiffreétantassociéuncaractère.Pourcefaire,onpeutchoisirde«coder»un telchiffrepar lecaractèrecorrespondant(‘0’pour0,‘1’pour1…);onpeutaussichoisir de placer une valeur égale au chiffre lui-même (0 pour 0, 1 pour 1…). Ladernièresolutionobligeàeffecteurun« transcodage» lorsquel’ondoitpasserdelaforme chaîne de caractères à la forme big_int (dans le constructeur correspondant,notamment)ou,inversement,lorsqu’ondoitpasserdelaformebig_intàlaformesuitedecaractères(pourl’affichage).Enrevanche,ellesimplifiequelquepeul’algorithmed’addition,etc’estellequenousavonschoisie.
L’emplacementpermettantdeconserverungrandentierseraallouédynamiquement;sataille sera, naturellement, adaptée à la valeur du nombre qui s’y trouvera. Onconserveraégalement lenombrecourantdechiffresde l’entier ;onpourrait,en touterigueur, s’en passer mais nous verrons que sa présence simplifie quelque peu laprogrammation. En ce qui concerne l’ordre de rangement des chiffres au sein del’emplacementcorrespondant,ilyamanifestementdeuxpossibilités.Chacunepossèdedesavantagesetdesinconvénients ;nousavonsicichoisiderangerleschiffresdans
360
l’ordreinversedeceluioùonlesécrit(unités,dizaines,centaines...).
Pourpouvoiraccepterlesexpressionsmixtes,ondisposedeplusieurssolutions:
soitsurdéfinirl’opérateur+pourtouslescaspossibles;
soit surdéfinir + uniquement lorsqu’il porte sur des grands entiers et prévoir unconstructeur recevant un argument de type unsigned long ; il permettra ainsi laconversionenbig_intden'importequeltypenumérique.
C’est la seconde solution que nous avons adoptée. Notez toutefois que, si elle a lemérite d’être la plus simple à programmer, elle n’est pas la plus efficace en tempsd’exécution.
Parailleurs,pourquelesconversionsenvisagéess’appliquentaupremieropérandedel’addition,ilestnécessairedesurdéfinirl’opérateur+commeunefonctionamie.
Voiciladéclarationdenotreclassebig_int(laprésenced’unconstructeurprivéàdeuxargumentsentiersserajustifiéeunpeuplusloin):
/*fichierbigint.h:déclarationdelaclassebig_int*/
#defineNCHIFMAX32//nombremaxidechiffresd'unentier(dépendde
//l'implémentation)
#include<iostream>
usingnamespacestd;
classbig_int
{intnchif;//nombredechiffres
char*adchif;//adresseemplacementcontenantleschiffres
big_int(int,int);//constructeurprivé(àusageinterne)
public:
big_int(unsignedlong=0);//constructeuràpartird'unnombreusuel
big_int(char*);//constructeuràpartird'unechaîne
big_int(big_int&);//constructeurparrecopie,voirremarque1
big_int&operator=(constbig_int&);//affectation,
//voirremarque2
friendbig_intoperator+(constbig_int&,constbig_int&);
//opérateur+,
//voirremarque3
friendostream&operator<<(ostream&,big_int&);//opérateur<<
};
1.Onpourraitajouterlequalificatifconstauconstructeurparrecopie,cequiauraitpoureffet d’autoriser l’initialisation d’un grand entier par un grand entier constant ouencore(comptetenudespossibilitésdeconversionimplicitedel’opérande)parunentier,voireunflottant(parconversionenentier).
2. La transmission par référence du résultat de l’opérateur d’affectation autorise lesaffectationsmultiples.Latransmissionparréférencedel’uniqueargumentn’estpasobligatoire. On a ajouté le qualificatif const pour autoriser l’affectation d’uneexpression (notamment la somme de deux grands entiers) ; compte tenu des
361
possibilités de conversions implicites, cela autorise du même coup l’affectationd’unentier,d’unflottant(parconversionenentier)oud’unpointeurdetypechar*(encore faut-il, comme ici, ne pas avoir utilisé le mot-clé explicit dans lesconstructeurscorrespondants).
3. Pour les arguments de l’opérateur +, le qualificatif const est indispensable si l’onsouhaitebénéficierdesconversionsimplicitesdesopérandes,notammentd’unentierenungrandentier.Ducoup,comptetenudespossibilitésdeconversionsimplicitesdes opérandes, on autorise également l’utilisation de flottants ou de pointeurs detypechar*.
L’opérateur + commence par créer un emplacement temporaire pouvant recevoir unnombrecomportantunchiffredeplusqueleplusgranddesesdeuxopérandes(onnesaitpasencorecombiendechiffrescomporteraexactementlerésultat).Onycalculelasommesuivantunalgorithmecalquésurleprocessusmanueld’addition.
Puisoncréeunobjetdetypebig_intenutilisantunconstructeurparticulier:big_int(int,int). En fait, nous avons besoin d’un constructeur créant un big_int comportant unnombre de chiffres donné, ce dont nous ne disposons pas dans les constructeurspublics.Deplus,nousnepouvonspasutiliserunconstructeurdelaformebig_int(int)car,alors,lesadditionsmixtesfaisantintervenirdesentierschercheraientàl’employerpoureffectueruneconversion!C’estpourquoinousavonsprévuunconstructeuràdeuxarguments,lesecondétantfictif;deplus,nousl’avonsrenduprivé,cariln’anullementbesoind’êtreaccessibleàunutilisateurdelaclasse.
Voiciladéfinitiondesfonctionsdelaclassebig_int:/*définitiondesfonctionsdelaclassebig_int*/
#include<string.h>
#include"bigint.h"
#include<iostream>
usingnamespacestd;
big_int::big_int(intn,intp)//l'argumentpestfictif
{nchif=n;
adchif=newchar[nchif];
}
big_int::big_int(char*ch)
{
nchif=strlen(ch);
adchif=newchar[nchif];
inti;charc;
for(i=0;i<nchif;i++)
{c=ch[i]-'0';
if(c<0||c>9)c=0;//précaution
adchif[nchif-i-1]=c;//attentionàl'ordredeschiffres!
}
}
big_int::big_int(unsignedlongn)
{//oncréelegrandentiercorrespondantdansunemplacementtemporaire
362
char*adtemp=newchar[NCHIFMAX];
inti=0;
while(n)
{adtemp[i++]=n%10;
n/=10;
}
//iciicontientlenombreexactdechiffres
nchif=i;
adchif=newchar[nchif];
for(i=0;i<nchif;i++)
adchif[i]=adtemp[i];
//onlibèrel'emplacementtemporaire
deleteadtemp;
}
big_int::big_int(big_int&n)
{nchif=n.nchif;
adchif=newchar[nchif];
inti;
for(i=0;i<nchif;i++)
adchif[i]=n.adchif[i];
}
big_int&big_int::operator=(constbig_int&n)
{if(this!=&n)
{deleteadchif;
nchif=n.nchif;
adchif=newchar[nchif];
inti;
for(i=0;i<nchif;i++)
adchif[i]=n.adchif[i];
}
return*this;
}
big_intoperator+(constbig_int&n,constbig_int&p)//voirremarque
{intnchifmax=(n.nchif>p.nchif)?n.nchif:p.nchif;
intncar=nchifmax+1;
//préparationdurésultatdanszonetemporairedetaillencar
char*adtemp=newchar[ncar];
inti,s,chif1,chif2;
intret=0;
for(i=0;i<nchifmax;i++)
{chif1=(i<n.nchif)?n.adchif[i]:0;
chif2=(i<p.nchif)?p.adchif[i]:0;
s=chif1+chif2+ret;
if(s>=10){s-=10;
ret=1;
}
elseret=0;
adtemp[i]=s;
}
if(ret==1)adtemp[ncar-1]=1;
elsencar--;
//constructiond'unobjetdetypebig_intoùl'onrecopielerésultat
big_intres(ncar,0);//secondargumentfictif
res.nchif=ncar;
for(i=0;i<ncar;i++)
res.adchif[i]=adtemp[i];
deleteadtemp;
returnres;
}
ostream&operator<<(ostream&sortie,big_int&n)
{inti;
for(i=n.nchif-1;i>=0;i--)//attentionàl'ordre!
sortie<<(int)n.adchif[i];
363
returnsortie;
}
Certainscompilateursimposentl’attributconstpourlesargumentsdeoperator+.
Voici un petit programme d’utilisation de la classe big_int, accompagné du résultatfourniparsonexécution:
/*programmed'essai*/
#include"bigint.h"
#include<iostream>
usingnamespacestd;
main()
{big_intn1(12);big_intn2(35);big_intn3;
n3=n1+n2;
cout<<n1<<"+"<<n2<<"="<<n3<<"\n";
big_intn4("1234567890123456789"),n5("9876543210987654321"),n6;
n6=n4+n5;
cout<<n4<<"+"<<n5<<"="<<n6<<"\n";
cout<<n6<<"+"<<n1<<"="<<n6+n1<<"\n";
n2=n4+5;//seraitrejetéesioperator=n'avaitpasargumentconst
cout<<n4<<"+5="<<n2<<"\n";
//iciuneexpressioncommen1+"123"seraitcorrecteetdetypebig_int
//iciuneexpressioncommen2+5.69seraitcorrecteetdetypebig_int
}
12+35=47
1234567890123456789+9876543210987654321=11111111101111111110
11111111101111111110+12=11111111101111111122
1234567890123456789+5=1234567890123456794
364
Exercice146
ÉnoncéCréerunpatrondeclassesnomméstack_gene,permettantdemanipulerdespilesdontles éléments sont de type quelconque. Ces derniers seront conservés dans unemplacementallouédynamiquementetdontladimensionserafournieauconstructeur(il ne s’agira donc pas d’un paramètre expression du patron). La classe devracomporterlesopérateurssuivants:
•<<,telquep<<najoutel'élémentnàlapilep(silapileestpleine,ilnesepasserarien);
•>>,telquep>>nplacedansnlavaleurduhautdelapilep,enlasupprimantdelapile(silapileestvide,ilnesepasserarien);
•++,telque++pvale1silapilepestpleineet0danslecascontraire;
•--,telque--pvale1silapilepestvideet0danslecascontraire;
•<<,telque,flotétantunflotdesortie,flot<<paffichelecontenudelapilepsurleflotsouslaforme://valeur_1valeur_2...valeur_n//.
On supposera que les objets de type stack_gene ne seront jamais soumis à destransmissionsparvaleurouàdesaffectations;onnechercheradoncpasàsurdéfinirleconstructeurparrecopieoul’opérateurd’affectation.
Enfait,onpeuts’inspirerdecequiaétéfaitdansl’exercice93pourréaliserunepiled’entiersenfaisantensortequeintsoitremplacéparunparamètredetype.
Voicicequepourraitêtreladéfinitiondenotrepatrondeclasses:#include<stdlib.h>
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
template<classT>classstack_gene
{intnmax;//nombremaximumdelavaleurdelapile
intnelem;//nombrecourantdevaleursdelapile
T*adv;//pointeursurlesvaleurs
public:
stack_gene(int=20);//constructeur
~stack_gene();//destructeur
stack_gene&operator<<(T);//opérateurd'empilage
stack_gene&operator>>(T&);//opérateurdedépilage
365
//(attentionT&)
intoperator++();//opérateurdetestpilepleine
intoperator--();//opérateurdetestpilevide
//opérateur<<pourflotdesortie
friendostream&operator<<(ostream&,stack_gene<T>&);
};
template<classT>stack_gene<T>::stack_gene(intn)
{nmax=n;
adv=newT[nmax];
nelem=0;
}
template<classT>stack_gene<T>::~stack_gene()
{deleteadv;
}
template<classT>stack_gene<T>&stack_gene<T>::operator<<(Tn)
{if(nelem<nmax)adv[nelem++]=n;
return(*this);
}
template<classT>stack_gene<T>&stack_gene<T>::operator>>(T&n)
{if(nelem>0)n=adv[--nelem];
return(*this);
}
template<classT>intstack_gene<T>::operator++()
{return(nelem==nmax);
}
template<classT>intstack_gene<T>::operator--()
{return(nelem==0);
}
template<classT>ostream&operator<<(ostream&sortie,stack_gene<T>&p)
{sortie<<"//";
inti;
for(i=0;i<p.nelem;i++)sortie<<p.adv[i]<<"";
sortie<<"//";
returnsortie;
}
Àtitreindicatif,voiciunpetitprogrammed’utilisationdenotrepatrondeclasses(dontonsupposequeladéfinitionfiguredansstackg.h).Ilestaccompagnédurésultatfourniparsonexécution.
/************programmed'essaidestack_gene*********/
#include"stackg.h"
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
main()
{stack_gene<int>pi(20);//pilede20entiersmaxi
cout<<"pipleine:"<<++pi<<"vide:"<<--pi<<"\n";
pi<<2<<3<<12;
cout<<"pi="<<pi<<"\n";
stack_gene<float>pf(10);//pilede10flottantsmaxi
pf<<3.5<<4.25<<2;//2seraconvertienfloat
cout<<"pf="<<pf<<"\n";
floatx;pf>>x;
cout<<"hautdelapilepf="<<x;
cout<<"pf="<<pf<<"\n";
}
pipleine:0vide:1
pi=//2312//
366
pf=//3.54.252//
hautdelapilepf=2pf=//3.54.25//
367
Exercice147
ÉnoncéEns’inspirantdel’exercice143,créerunpatrondeclassespermettantdemanipulerdesvecteursdynamiquesdontlesélémentssontdetypequelconque.
Voiciladéclarationdenotrepatron:#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
usingnamespacestd;
template<classT>classvect
{intnelem;//nombredecomposantesduvecteur
T*adr;//pointeursurpartiedynamique
public:
vect(intn=1);//constructeur"usuel"
vect(vect&v);//constructeurparrecopie
~vect();//destructeur
friendostream&operator<<(ostream&,vect<T>&);
vect<T>operator=(vect<T>&v);//surdéfinitionopérateuraffectation
T&operator[](inti);//surdef[]pourvectnonconstants
Toperator[](inti)const;//surdef[]pourvectconstants
};
Voiciladéfinitiondesdifférentesfonctionsmembre:#include"vectgen.h"
#include<iostream>
usingnamespacestd;
template<classT>vect<T>::vect(intn)//constructeur"usuel"
{adr=newT[nelem=n];
}
template<classT>vect<T>::vect(vect<T>&v)//constructeurparrecopie
{adr=newT[nelem=v.nelem];
inti;
for(i=0;i<nelem;i++)
adr[i]=v.adr[i];
}
template<classT>vect<T>::~vect()
{deleteadr;
}
template<classT>vect<T>vect<T>::operator=(vect<T>&v)
{if(this!=&v)//onnefaitrienpoura=a
{deleteadr;
adr=newT[nelem=v.nelem];
inti;
for(i=0;i<nelem;i++)
adr[i]=v.adr[i];
}
return*this;
}
template<classT>T&vect<T>::operator[](inti)
{returnadr[i];
368
}
template<classT>Tvect<T>::operator[](inti)const
{returnadr[i];
}
template<classT>ostream&operator<<(ostream&sortie,vect<T>&v)
{sortie<<"<";
inti;
for(i=0;i<v.nelem;i++)sortie<<v.adr[i]<<"";
sortie<<">";
returnsortie;
}
Àtitreindicatif,voiciunpetitprogrammeutilisantnotrepatron,accompagnédurésultatfourniparsonexécution:
#include"vectgen.h"
#include<iostream>
usingnamespacestd;
main()
{inti;
vect<int>v1(5);vect<int>v2(10);
for(i=0;i<5;i++)v1[i]=i;
cout<<"v1="<<v1<<"\n";
for(i=0;i<10;i++)v2[i]=i*i;
cout<<"v2="<<v2<<"\n";
v1=v2;
cout<<"v1="<<v1<<"\n";
vect<int>v3=v1;
//vect<double>v3=v1;//seraitrejeté
cout<<"v3="<<v3<<"\n";
//constvect<float>w(3);w[2]=5;//conduitbienàerreurcompilation
//vect<float>v4(5);v4=v1;//conduitbienàerreurcompilation
}
v1=<01234>
v2=<0149162536496481>
v1=<0149162536496481>
v3=<0149162536496481>
369
Chapitre21Lescomposantsstandard
LesexercicesdesprécédentschapitresontétévolontairementrésolussansrecourirauxcomposantsstandardintroduitsparlanormedeC++.Manifestement,l’existencedecescomposants influe sur certains des exercices, soit en rendant leur solution quasimenttriviale,soitenlasimplifiantnotablement.C’estcequenousproposonsd’examinerici.Pour faciliter les choses, nous avons systématiquement reproduit le texte intégral del’énoncécorrespondant,avecsonanciennenumérotation.
Notezque, contrairement auxautres chapitres, celui-cin’apas étédotéd’un résumé.D’une part, l’ampleur de la bibliothèque de composants standard l’aurait rendurelativementvolumineux(onentrouverauneétudedétailléedansl’undenosouvragesconsacrésaulangageC++etpubliéségalementauxÉditionsEyrolles).D’autrepart,ilneconstituepasàproprementparlerunensemblecompletd’exercicessurlesujet.
370
Exercice148(67revisité)
AncienénoncéRéaliser une classe nommée set_char permettant de manipuler des ensembles decaractères.Ondevrapouvoirréalisersuruntelensemblelesopérationsclassiquessuivantes : lui ajouter un nouvel élément, connaître son « cardinal » (nombred’éléments),savoirsiuncaractèredonnéluiappartient.
Ici,onn’effectueraaucuneallocationdynamiqued’emplacementsmémoire.Ilfaudradoncprévoir,enmembredonnée,untableaudetaillefixe.
Écrire,enoutre,unprogramme(main)utilisant laclasse set_char pourdéterminer lenombredecaractèresdifférentscontenusdansunmotluendonnée.
CommentairesLeconteneurset<char>peutjouerlerôledelaclassedemandéeset_char,àconditiondesupprimer de l’énoncé la contrainte relative à l’absence d’allocation dynamique. Eneffet,ellen’aplusderaisond’être,lesfonctionsmembredelaclasseset<char>allouantautomatiquementlaplacenécessaireaufuretàmesuredesbesoins.
Voici ce que pourrait devenir le programme de test fourni précédemment dans lechapitre 3. On notera que la fonction membre size fournit le nombre d’éléments del’ensemble, tandisque la fonctionmembreinsertpermet toutnaturellement l’insertiond’un élément.Quant à la fonction count, elle fournit le nombre d’éléments de valeurdonnéefigurantdansl’ensemble;sonrésultatestdonctoujourssoit0,soit1.
#include<iostream>
#include<set>//pourlaclasseset
usingnamespacestd;
main()
{set<char>ens;//voirremarque1ci-après
charmot[81];
cout<<"donnezunmot:";
cin>>mot;
inti;
for(i=0;i<strlen(mot);i++)ens.insert(mot[i]);
cout<<"ilcontient"<<ens.size()<<"caracteresdifferents\n";
if(ens.count('e'))cout<<"lecaractereeestpresent\n";
elsecout<<"lecaractereen'estpaspresent\n";
}
donnezunmot:bonjour
ilcontient6caracteresdifferents
371
lecaractereen'estpaspresent
1.Certainscompilateursimposentlaprésenced’unsecondparamètredetypeprécisantlarelationd’ordreutiliséepourordonnerl’ensemble.Danscecas,ilfaudraécrire:set<char,less<char>>ens.
2. Ilexisteunautreconteneur,multiset, semblableàset,dans lequelunemêmevaleurpeut apparaître plusieurs fois. Il ne correspond plus à la notion mathématiqued’ensemble. On notera que la fonctionmembre count, présente également dans ceconteneur,voitalorssonnomnettementplusjustifiéquedanslecasdeset.
372
Exercice149(68revisité)
AncienénoncéModifierlaclasseset_charprécédente,demanièreàdisposerdecequel’onnommeun«itérateur»sur lesdifférentsélémentsdel’ensemble.Ils’agitd’unmécanismepermettant d’accéder séquentiellement aux différents éléments. On prévoira troisnouvellesfonctionsmembre:init,quiinitialiseleprocessusd’exploration;prochain,quifournitl’élémentsuivantlorsqu’ilexisteetexiste,quiprécises’ilexisteencoreunélémentnonexploré.
Oncompléteraalorsleprogrammed’utilisationprécédent,demanièrequ’ilaffichelesdifférentscaractèrescontenusdanslemotfourniendonnée.
CommentairesIci encore,onpeututiliser le composant standard set<char> qui disposed’un itérateurintégréset<char>::iterator.Bien entendu, il n’y a plus de raison d’imposer l’existencedes fonctions init,prochain et existe. Les fonctionsmembre begin et end fournissent lesvaleursinitialesetfinalesàutiliserpourexplorerl’ensembleàl’aided’untelitérateur.Onprendragardeau faitque end pointenonpas sur le dernier élémentdu conteneur,maisjusteaprès.L’avancementdel’itérateurs’obtientparl’opérateur++.
Voicicequepourraitdevenir leprogrammede test fourniprécédemment.Lanotation*iecorrespondàl’élémentdésignéparlavaleurcourantedel’itérateurie.
#include<iostream>
#include<string.h>
#include<set>
usingnamespacestd;
main()
{set<char>ens;//voirremarque1ci-après
charmot[81];
cout<<"donnezunmot:";
cin>>mot;
inti;
for(i=0;i<strlen(mot);i++)ens.insert(mot[i]);
cout<<"ilcontient"<<ens.size()<<"caracteresdifferents"
<<"quisont:\n";
set<char>::iteratorie;//iterateursurunensembledecaracteres
for(ie=ens.begin();ie!=ens.end();ie++)
cout<<*ie<<"";
}
donnezunmot:bonjour
373
ilcontient6caracteresdifferentsquisont:
bjnoru
1.Certainscompilateursimposentlaprésenced’unsecondparamètredetypeprécisantlarelationd’ordreutiliséepourordonnerl’ensemble.Danscecas,ilfaudraécrire:set<char,less<char>>ens.
2. Il existe plusieurs sortes d’itérateurs. Les plus courants sont les itérateursbidirectionnelsquipeuventêtreincrémentés(++)oudécrémentés(--)d’unepositionàlafois,et lesitérateursàaccèsdirectquipermettentl’accèsdirectàunélémentquelconque. Tous les conteneurs disposent des fonctions membre begin et end
permettantleurparcoursparunitérateurbidirectionnel.
374
Exercice150(77revisité)
Ancienénoncé
1. Réaliser une classe nommée set_int permettant de manipuler des ensembles denombres entiers. On devra pouvoir réaliser sur un tel ensemble les opérationsclassiques suivantes : lui ajouter un nouvel élément, connaître son cardinal(nombred’éléments),savoirsiunentierdonnéluiappartient.
Ici, on conservera les différents éléments de l’ensemble dans un tableau allouédynamiquement par le constructeur.Un argument (auquel on pourra prévoir unevaleurpardéfaut)luipréciseralenombremaximald’élémentsdel’ensemble.
2.Écrire,enoutre,unprogramme(main)utilisantlaclasseset_intpourdéterminerlenombred’entiersdifférentscontenusdansuntableaud’entierslusendonnées.
3. Que faudrait-il faire pour qu’un objet du type set_int puisse être transmis parvaleur, soit comme argument d’appel, soit comme valeur de retour d’unefonction?
CommentairesOnpeututiliserlecomposantset<int>,àconditiondenepluspréciserqu’onconservelesélémentsdansuntableaudynamique.Laquestion3n’aplusderaisond’êtrecarlaclassesetdisposed’unconstructeurparrecopie.
Voicicequedevientl’exempledeprogrammedemandédanslasecondequestion:#include<iostream>
#include<set>//pourlaclasseset
usingnamespacestd;
main()
{set<int>ens;//voirremarqueci-après
cout<<"donnez20entiers\n";
inti,n;
for(i=0;i<20;i++)
{cin>>n;
ens.insert(n);
}
cout<<"ilya:"<<ens.size()<<"entiersdifferents\n";
}
375
Certainscompilateursimposentlaprésenced’unsecondparamètredetypeprécisantlarelation d’ordre utilisée pour ordonner l’ensemble. Dans ce cas, il faudra écrire :set<char,less<char>>ens.
376
Exercice151(78revisité)
N.B.Cetexercicen’estrappeléiciqueparcequ’ilestutileàl’énoncésuivant.
Ancienénoncé
Modifier l’implémentation de la classe précédente (avec son constructeur parrecopie)defaçonquel’ensembled’entierssoitmaintenantreprésentéparune listechaînée (chaqueentierest rangédansunestructurecomportantunchampdestinéàcontenir un nombre et un champ destiné à contenir un pointeur sur la structuresuivante).L’interfacedelaclasse(lapartiepubliquedesadéclaration)devraresterinchangée, ce qui signifie qu’un client de la classe continuera à l’employer de lamêmefaçon.
CommentairesIl s’agit ici d’une demande de modification d’implémentation d’une classe qui n’amanifestementaucunsensdanslecasd’uncomposantstandard.
377
Exercice152(79revisité)
AncienénoncéModifierlaclasseset_intprécédente(implémentéesouslaformed’unelistechaînée,avec ou sans son constructeur par recopie) pour qu’elle dispose de ce que l’onnommeun« itérateur» sur lesdifférents élémentsde l’ensemble.Rappelonsqu’ils’agitd’unmécanismepermettantd’accéderséquentiellementauxdifférentsélémentsdel’ensemble.Onprévoiratroisnouvellesfonctionsmembre:init,pourinitialiserleprocessusd’itération ; prochain, pour fournir l’élément suivant lorsqu’il existe etexiste,pourtesters’ilexisteencoreunélémentnonexploré.
On complétera alors le programme d’utilisation précédent (en fait, celui del’exercice 26), de manière qu’il affiche les différents entiers contenus dans lesvaleursfourniesendonnée.
CommentairesIciencore,iln’yaaucuneraisondevouloirmodifierl’implémentationd’uncomposantstandard.Voici cequepourrait devenir l’exempledeprogrammed’utilisation si l’onutilisaitlecomposantset<int>etl’itérateurassociéset<int>::iterator:
#include<iostream>
#include<set>//pourlaclasseset
usingnamespacestd;
main()
{set<int>ens;
cout<<"donnez20entiers\n";
inti,n;
for(i=0;i<20;i++)
{cin>>n;
ens.insert(n);
}
cout<<"ilya:"<<ens.size()<<"entiersdifferents\n";
cout<<"Cesont:\n";
set<int>::iteratoris;
for(is=ens.begin();is!=ens.end();is++)
cout<<*is<<"";
}
378
Exercice153(90revisité)
AncienénoncéDéfiniruneclassevectpermettantdereprésenterdes«vecteursdynamiques»,c’est-à-dire dont la dimension peut ne pas être connue lors de la compilation. Plusprécisément, on prévoira de déclarer de tels vecteurs par une instruction de laforme:
vectt(exp);
danslaquelleexpdésigneuneexpressionquelconque(detypeentier).
Ondéfinira,defaçonappropriée,l’opérateur[]demanièrequ’ilpermetted’accéderà des éléments d’un objet d’un type vect comme on le ferait avec un tableauclassique.
Onnechercherapasàrésoudrelesproblèmesposéséventuellementparl’affectationoulatransmissionparvaleurd’objetsdetypevect.Enrevanche,ons’arrangerapourqu’aucunrisquede«débordement»d’indicen’existe.
CommentairesLaclassevector<int> fait l’affaire,ycomprispour l’affectationou la transmissionparvaleur.Elledisposed’un itérateuràaccèsdirect (nommétoujoursiterator).Mais, deplus,l’opérateur[]yestsurdéfinidesortequ’ilfournituneécritureplusconcisepourl’accès direct à un élément. Si v est un objet de type vector<int>, la notation v[i] estéquivalenteà*(v.begin()+i).
Toutefois,cetopérateur[]n’estpasprotégécontrelesdébordementsd’indice.Onpeutrésoudreleproblèmeenutilisant,àsaplace,lafonctionmembreatquidéclencheuneexceptionstandardout_of_rangeencasdedébordementd’indice.Sil’onveutabsolumentseteniràl’interfaceimposéeparl’énoncé,onpeutégalementcréerartificiellementuneclasse vect dérivée de vector<int>, dans laquelle on définit l’opérateur [] de façonappropriée. Cette dernière démarche a le mérite d’offrir toute latitude quant autraitementàmettreenœuvreencasdedébordement:déclenchementd’uneexception,modificationautoritairedelavaleurdel’indicecommeonl’afaitdanslasolutiondel’exercice39,etc.
Voici un premier exemple où l’on se contente d’utiliser la classe vector<int> et son
379
opérateur[]:#include<iostream>
#include<vector>//pourlaclassevector
usingnamespacestd;
main()
{vector<int>v(6);
inti;
for(i=0;i<6;i++)v[i]=i;
for(i=0;i<6;i++)cout<<v[i]<<"";
}
Voiciunsecondexemple(accompagnédurésultatfourniparsonexécution)quimontrecommentutiliser la fonction atde laclasse vector<int> et traitantde façonappropriéel’exceptionstandardout_of_range:
#include<iostream>
#include<vector>//pourlaclassevector
#include<stdexcept>//pourlaclasseexceptionout_of_range
usingnamespacestd;
main()
{try
{vector<int>v(6);
inti;
for(i=0;i<6;i++)v.at(i)=i;
for(i=0;i<8;i++)cout<<v.at(i)<<"";//iciondebordedev
}
catch(out_of_rangeoor)
{cout<<"exceptionoutofrange\n";
exit(-1);
}
}
012345exceptionoutofrange
Voiciun troisièmeexempledans lequeloncréeuneclassevectdérivéedevector<int>.Ici, ondéclencheune exception out_of_range en casd’indice incorrect.Notezqu’il estnécessairederedéfinirleconstructeuràunargumententierdevect,bienquesoncorpssoitvide,ceciafindetransmettreladimensionauconstructeurdelaclassedebase.Ilfaudraitd’ailleursfairedemêmepour toutconstructeurdevectavecargumentsqu’onsouhaiteraitpouvoirutiliser.
#include<iostream>//voirN.B.duparagrapheNouvellespossibilités
//d'entrées-sortiesduchapitre2
#include<vector>//pourlaclassevector
#include<stdexcept>//pourlaclasseexceptionout_of_range
usingnamespacestd;
classvect:publicvector<int>
{public:
vect(intdim):vector<int>(dim){}//indispensable
//surdéfinitiondel'opérateur[]
int&operator[](inti)
{//onpourraitaussichercheràmodifierautoritairementlavaleur
//deipar:
//if((i<0)||(i>=(*this).size()))i=0;
return(*this).at(i);
380
}
};
main()
{try
{vectv(6);
inti;
for(i=0;i<6;i++)v[i]=i;
for(i=0;i<8;i++)cout<<v[i]<<"";//iciondebordedev
}
catch(out_of_rangeoor)
{cout<<"exceptionoutofrange\n";
exit(-1);
}
}
012345exceptionoutofrange
381
Exercice154(91revisité)
AncienénoncéEns’inspirantdel’exerciceprécédent,onsouhaitecréeruneclasseint2dpermettantde représenterdes tableauxdynamiquesd’entiers àdeux indices, c’est-à-diredontlesdimensionspeuventnepasêtreconnueslorsdelacompilation.Plusprécisément,onprévoiradedéclarerdetelstableauxparunedéclarationdelaforme:
int2dt(exp1,exp2);
danslaquelleexp1etexp2désignentuneexpressionquelconque(detypeentier).
On surdéfinira l’opérateur (), demanière qu’il permette d’accéder à des élémentsd’unobjetd’untypeint2dcommeonleferaitavecuntableauclassique.
Làencore,onnechercherapasàrésoudrelesproblèmesposéséventuellementparl’affectation ou la transmission par valeur d’objets de type int2d. En revanche, ons’arrangerapourqu’iln’existeaucunrisquededébordementd’indice.
CommentairesOnpeut facilement généraliser ce qui a été fait dans l’exercice précédent.La classevector<vector<int> > fait l’affaire, y compris pour l’affectation ou la transmission parvaleur. Mais, là encore, l’opérateur [] n’est pas protégé contre les débordementsd’indice.Onpeutrésoudreleproblèmeenutilisant,àsaplace,lafonctionmembreatquidéclencheuneexceptionstandardout_of_rangeencasdedébordementd’indice.
Dans le programme source, il faudra absolument laisser un espace entre les deuxsymboles>,afind’évitertouteconfusionavecl’opérateur>>.
Voiciunpremierexemple(accompagnédurésultatfourniparsonexécution)oùl’onsecontented’utiliserlaclassevector<vector<int>>etsonopérateur[]:
#include<iostream>
#include<vector>
usingnamespacestd;
main()
{vector<vector<int>>t1(4);//vecteurde4vecteurs
382
vector<int>v(3);//vecteurde3entiers,noninitialise
inti,j;
for(i=0;i<4;i++)
t1[i]=v;
for(i=0;i<4;i++)
for(j=0;j<3;j++)
t1[i][j]=i+j;
for(i=0;i<4;i++)
{for(j=0;j<3;j++)
cout<<t1[i][j]<<"";
cout<<"\n";
}
}
012
123
234
345
Onnoteraqu’onaquandmêmedûcréerunobjettemporairev,detypevector<int>,afind’initialiser les différents vecteurs de t1. Mais les choses restent néanmoins plussimplesquecequiaétéfaitauchapitre7,sansrecourirauxcomposantsstandard.
Voici un second exemple qui montre comment utiliser la fonction at des classesvector<int>etvector<vector<int>>ettraitantdefaçonappropriéel’exceptionout_of_range:
#include<iostream>
#include<vector>
usingnamespacestd;
main()
{try
{vector<vector<int>>t1(4);//vecteurde4vecteurs-espace
//entre>et>
vector<int>v(3);//vecteurde3entiers,noninitialisé
inti,j;
for(i=0;i<4;i++)
t1.at(i)=v;
for(i=0;i<4;i++)
for(j=0;j<3;j++)
(t1.at(i)).at(j)=i+j;
for(i=0;i<4;i++)
{for(j=0;j<3;j++)
cout<<(t1.at(i)).at(j)<<"";
cout<<"\n";
}
}
catch(out_of_rangeoor)
{cout<<"exceptionoutofrange\n";
exit(-1);
}
}
Onpeutaussi,àl’imagedecequel’onafaitdansl’exerciceprécédent,chercheràsetenir à l’interface imposée par l’énoncé. Dans ce cas, on crée artificiellement uneclasse int2d, dérivée de vector<vector<int>, classe de base dont on exploite lesfonctionnalitéscommelemontrel’exemplesuivant.Ici,nousavonsattribuédesvaleurs
383
arbitrairesauxindicesencasdedébordement,commeauchapitre7.#include<iostream>
#include<vector>
usingnamespacestd;
/********déclarationdelaclasseint2d********/
classint2d:publicvector<vector<int>>
{intnlig;//nombrede"lignes"
intncol;//nombrede"colonnes"
public:
int2d(intnl,intnc);//constructeur
int&operator()(int,int);//accèsàunélément,parses2"indices"
};
/***********définitionduconstructeur**********/
int2d::int2d(intnl,intnc):vector<vector<int>>(nl)
{nlig=nl;ncol=nc;
vector<int>v(nc);
inti;
for(i=0;i<nl;i++)(*this)[i]=v;
}
/**********définitiondel'opérateur()*********/
int&int2d::operator()(inti,intj)
{if((i<0)||(i>=nlig))i=0;//protectionssurpremierindice
if((j<0)||(j>=ncol))j=0;//protectionssursecondindice
return(*this)[i][j];
}
main()
{int2dt1(4,3);
inti,j;
for(i=0;i<4;i++)
for(j=0;j<3;j++)
t1(i,j)=i+j;
for(i=0;i<4;i++)
{for(j=0;j<3;j++)
cout<<t1(i,j)<<"";
cout<<"\n";
}
}
Lacomparaisonentre cette solutionet celleduchapitre7montre que le gain obtenuavecl’utilisationdescomposantsstandardresteassezrelatif.Toutefois,ilfautvoirquedans une situation réelle, la solution du chapitre 7 nécessiterait la redéfinition del’affectationetduconstructeurparrecopiedeint2d.Celaneseraitpasnécessaire ici,les fonctionscorrespondantesde la classedebase faisant l’affairepuisque la classedérivéenecomporteaucunepartiedynamiquesupplémentaire.
384
Exercice155(93revisité)
AncienénoncéRéaliser une classe nommée stack_int permettant de gérer une pile d’entiers. Cesderniers seront conservés dans un emplacement alloué dynamiquement ; sadimensionseradéterminéeparl’argumentfourniàsonconstructeur(onluiprévoiraunevaleurpardéfautde20).Cetteclassedevracomporter lesopérateurssuivants(noussupposonsquepestunobjetdetypestack_intetnunentier):
• <<, tel que p<<n ajoute l’entier n à la pile p (si la pile est pleine, rien ne sepasse);
•>>,telquep>>nplacedansnlavaleurduhautdelapilep,enlasupprimantdelapile(silapileestvide,lavaleurdenneserapasmodifiée);
•++,telquep++vale1silapilepestpleineet0danslecascontraire;
•--,telquep--vale1silapilepestvideet0danslecascontraire.
On prévoira que les opérateurs << et >> pourront être utilisés sous les formessuivantes(n1,n2etn3étantdesentiers):
p<<n1<<n2<<n3;p>>n1>>n2<<n3;
Onferaensortequ’ilsoitpossibledetransmettreunepileparvaleur.Enrevanche,l’affectation entre piles ne sera pas permise, et on s’arrangera pour que cettesituationaboutisseàunarrêtdel’exécution.
CommentairesSil’onnes’intéressequ’auxseulesfonctionnalités(ajout,extraction,suppression,testpilepleineoupilevide)delaclassequ’ondemandedecréer,ilexisteuncomposantquifaitl’affaire.Ils’agitdestack<int,vector<int>>.Ici,ons’attendraitplussimplementàstack<int>.Enfait, lepatronstackestnonpasunconteneuràpartentière,maiscequel’onappelleunadaptateurdeconteneur.Ils’agitd’unpatrondeclasses,fondésurunconteneur d’un type donné (ici vector<int>) qui enmodifie l’interface, à la fois en larestreignantetenl’adaptantàdesfonctionnalitésdonnées,àsavoirici:
testpilevide(fonctionempty);
accèsàl’informationsituéeausommentdelapile(fonctiontop) :cettefonctionne
385
modifiepaslavaleurdusommet;
dépôtd’unevaleursurlapile(fonctionpush);
suppressionde lavaleursituéeausommetde lapile (fonctionpop) ;onnoteraquepourvéritablement«dépiler»unevaleur,ilfauteffectuerdeuxappels:toppuispop.
Cetadaptateur,vector<int>,peut sebaser sur vector,dequeoulist.Nous ne justifieronspas ici le choix de vector, dicté uniquement par des détails d’implémentation etd’efficacité.
Onnoteraquelanotiondepilepleinen’existeplus,àproprementparler,comptetenudel’aspectdynamiquedececomposant.Parailleurs,iln’yaplusderaisond’interdirel’affectation.
Voicicequedevientleprogrammed’essaidel’exercice42danscecas:#include<iostream>
#include<stack>
#include<vector>
usingnamespacestd;
main()
{
voidfct(stack<int,vector<int>>);
stack<int,vector<int>>pile;//ilfautunconteneurdebase,icivector
cout<<"vide:"<<pile.empty()<<"\n";//ici,onafficheunbooléen
pile.push(1);pile.push(2);pile.push(3);pile.push(4);
fct(pile);
intn,p;
n=pile.top();pile.pop();p="pile.top();pile.pop();//depile
//2valeurs
cout<<"hautdelapileauretourdefct:"<<n<<""<<p<<"\n";
stack<int,vector<int>>pileb;
pileb=pile;//icil'affectationfonctionne!!!
}
voidfct(stack<int,vector<int>>pl)
{
cout<<"hautdelapilerecueparfct:";
intn,p;
n=pl.top();pl.pop();p=pl.top();pl.pop();//ondepile2valeurs
cout<<n<<""<<p<<"\n";
pl.push(12);//onenajouteune
}
vide:1
hautdelapilerecueparfct:43
hautdelapileauretourdefct:43
386
Exercice156(142revisité)
AncienénoncéRéaliser une classe nommée set_int permettant de manipuler des ensembles denombresentiers.Lenombremaximald’entiersquepourracontenirl’ensembleseraprécisé au constructeur qui allouera dynamiquement l’espace nécessaire. Onprévoiralesopérateurssuivants(edésigneunélémentdetypeset_intetnunentier:
•<<,telquee<<najoutel’élémentnàl’ensemblee;
•%,telquen%evale1sinappartientàeet0sinon;
•<<,telqueflot<<eenvoielecontenudel’ensembleesurleflotindiqué,souslaforme:
[entier1,entier2,...entiern]
Lafonctionmembrecardinalfourniralenombred’élémentsdel’ensemble.Enfin,ons’arrangera pour que l’affectation ou la transmission par valeur d’objets de typeset_intneposeaucunproblème(onaccepteraladuplicationcomplèted’objets).
CommentairesSil’onnes’intéressequ’auxseulesfonctionnalitésdelaclasse,endehorsdelasortiesurunflot,celles-cisontfourniesintégralementparlecomposantstandardset<int>.Encequiconcernelasortiesurunflot,ondisposedeplusieursdémarches.Onpeutbiensûr la programmer au fur et à mesure des besoins, en écrivant à chaque fois lesquelquesinstructionsdeparcoursdel’ensemble:
set<int>ens;
set<int>::iteratosie;
.....
for(ie=ens.begin();ie!=ens.end();ie++)cout<<*ie<<"";
On peut aussi en faire une fonction ordinaire, comme dans cet exemple, analogue àl’exempled’utilisationdel’exerciceduchapitre16:
#include<iostream>
#include<set>
usingnamespacestd;
voidaffiche(set<int>);
main()
{voidfct(set<int>);
voidfctref(set<int>&);
387
set<int>ens;
cout<<"donnez10entiers\n";
inti,n;
for(i=0;i<10;i++)
{cin>>n;
ens.insert(n);
}
cout<<"ilya:"<<ens.size()<<"entiersdifferents\n";
cout<<"quiformentl\'ensemble:";affiche(ens);
fct(ens);
cout<<"auretourdefct,ilyena"<<ens.size()<<"\n";
cout<<"quiformentl\'ensemble:";affiche(ens);
fctref(ens);
cout<<"auretourdefctref,ilyena"<<ens.size()<<"\n";
cout<<"quiformentl\'ensemble:";affiche(ens);
cout<<"appartenancede-1:"<<ens.count(-1)<<"\n";
cout<<"appartenancede500:"<<ens.count(500)<<"\n";
set<int>ensa,ensb;
ensa=ensb=ens;
cout<<"ensemblea:";affiche(ensa);
cout<<"ensembleb:";affiche(ensb);
}
voidfct(set<int>e)
{cout<<"ensemblereçuparfct:";affiche(e);
e.insert(-1);e.insert(-2);e.insert(-3);
}
voidfctref(set<int>&e)
{cout<<"ensemblerecuparfctref:";affiche(e);
e.insert(-1);e.insert(-2);e.insert(-3);
}
voidaffiche(set<int>e)
{set<int>::iteratorie;
cout<<"[";
for(ie=e.begin();ie!=e.end();ie++)
cout<<*ie<<"";
cout<<"]\n";
}
donnez10entiers
3531851773
ilya:5entiersdifferents
quiformentl'ensemble:[13578]
ensemblerecuparfct:[13578]
auretourdefct,ilyena5
quiformentl'ensemble:[13578]
ensemblerecuparfctref:[13578]
auretourdefctref,ilyena8
quiformentl'ensemble:[-3-2-113578]
appartenancede-1:1
appartenancede500:0
ensemblea:[-3-2-113578]
ensembleb:[-3-2-113578]
On peut également créer artificiellement une classe set_int, dérivée de set<int>, danslaquelle on surdéfinit l’opérateur <<, comme dans cet exemple qui fournit lesmêmesrésultatsqueleprécédent:
#include<iostream>
#include<set>
usingnamespacestd;
388
/*************déclarationdelaclasseset_int*********/
classset_int:publicset<int>
{public:
//envoiensembledansunflot
friendostream&operator<<(ostream&,set_int&);
};
/*************définitiondelaclasseset_int*********/
ostream&operator<<(ostream&sortie,set_int&e)//voirremarque1
{sortie<<"[";
set<int>::iteratorie;
for(ie=e.begin();ie!=e.end();ie++)
sortie<<*ie<<"";
sortie<<"]";
returnsortie;
}
/*************testdelaclasseset_int*********/
main()
{
voidfct(set_int);
voidfctref(set_int&);
set_intens;
cout<<"donnez10entiers\n";
inti,n;
for(i=0;i<10;i++)
{cin>>n;
ens.insert(n);
}
cout<<"ilya:"<<ens.size()<<"entiersdifferents\n";
cout<<"quiformentl\'ensemble:"<<ens<<"\n";
fct(ens);
cout<<"auretourdefct,ilyena"<<ens.size()<<"\n";
cout<<"quiformentl\'ensemble:"<<ens<<"\n";
fctref(ens);
cout<<"auretourdefctref,ilyena"<<ens.size()<<"\n";
cout<<"quiformentl\'ensemble:"<<ens<<"\n";
cout<<"appartenancede-1:"<<ens.count(-1)<<"\n";
cout<<"appartenancede500:"<<ens.count(500)<<"\n";
set_intensa,ensb;
ensa=ensb=ens;
cout<<"ensemblea:"<<ensa<<"\n";
cout<<"ensembleb:"<<ensb<<"\n";
}
voidfct(set_inte)
{cout<<"ensemblereçuparfct:"<<e<<"\n";
e.insert(-1);e.insert(-2);e.insert(-3);
}
voidfctref(set_int&e)
{cout<<"ensemblereçuparfctref:"<<e<<"\n";
e.insert(-1);e.insert(-2);e.insert(-3);
}
1.Certainsenvironnements imposentque l’onmentionne l’espacedenomstd dans lasurdéfinitiondel’opérateur<<enécrivantstd::operator<<.
2.Ici,iln’estpasnécessairederedéfinirl’affectationouleconstructeurparrecopie.En effet, compte tenu des règles relatives à l’héritage, les fonctions par défautappellentbienlesfonctionsvouluesdanslaclassedebase;celasuffiticipuisquelaclassedérivéen’introduitaucunepartiedynamiquesupplémentaire.
389
390
Exercice157(143revisité)
AncienénoncéCréeruneclassevectpermettantdemanipulerdes«vecteursdynamiques»d’entiers,c’est-à-diredestableauxd’entiersdontladimensionpeutêtredéfinieaumomentdeleurcréation(unetelleclasseadéjàétépartiellementréaliséedansl’exercice80).Cetteclassedevradisposerdesopérateurssuivants:
•[] pour l’accès à une des composantes du vecteur, et cela aussi bien au seind’uneexpressionqu’àgauched’uneaffectation(maiscettedernièresituationnedevrapasêtreautoriséesurdes«vecteursconstants»);
•==,telquesiv1etv2sontdeuxobjetsdetypevect,v1==v2prennelavaleur1siv1etv2sontdemêmedimensionetontlesmêmescomposantesetlavaleur0danslecascontraire;
•<<,telqueflot<<venvoielevecteurvsurleflotindiqué,souslaforme:<entier1,entier2,...,entiern>
Deplus,ons’arrangerapourquel’affectationetlatransmissionparvaleurd’objetsde type vect ne pose aucun problème ; pour ce faire, on acceptera de dupliquercomplètementlesobjetsconcernés.
CommentairesEn dehors de la sortie sur un flot, le composant vector<int> répond parfaitement à laquestion(avec,ici,lesmêmesnomsd’opérateurs[]et==).Encequiconcernelasortiesurunflot,ondisposedeplusieursdémarches,commedansl’exerciceprécédent.Onpeutbiensûrlaprogrammeraufuretàmesuredesbesoins,enécrivantàchaquefoislesquelquesinstructionsdeparcoursdel’ensemble:
vector<int>v;
vector<int>::iteratosiv;
.....
for(iv=v.begin();iv!=v.end();iv++)cout<<*iv<<"";
On peut aussi en faire une fonction ordinaire, comme dans cet exemple, analogue àl’exempled’utilisationdel’exerciceduchapitre16:
#include<iostream>
#include<vector>
usingnamespacestd;
391
main()
{voidaffiche(vector<int>);
inti;
vector<int>v1(5),v2(10);
for(i=0;i<5;i++)v1[i]=i;
cout<<"v1=";affiche(v1);
for(i=0;i<10;i++)v2[i]=i*i;
cout<<"v2=";affiche(v2);
v1=v2;
cout<<"v1=";affiche(v1);
vector<int>v3=v1;
cout<<"v3=";affiche(v3);
vector<int>v4=v2;
cout<<"v4=";affiche(v4);
//constvector<int>w(3);w[2]=5;//conduitbienàerreurcompilation
}
voidaffiche(vector<int>v)
{vector<int>::iteratoriv;
for(iv=v.begin();iv!=v.end();iv++)
cout<<*iv<<"";
cout<<"\n";
}
v1=01234
v2=0149162536496481
v1=0149162536496481
v3=0149162536496481
v4=0149162536496481
On peut également créer artificiellement une classe vect, dérivée de vector<int>, danslaquelle on surdéfinit l’opérateur <<, commedans cet exemplequi fournit lesmêmesrésultatsqueleprécédent:
#include<iostream>
#include<vector>
usingnamespacestd;
classvect:publicvector<int>
{public:
vect(intn):vector<int>(n){}//indispensable!!!!!!!
friendostream&operator<<(ostream&,vect&);
};
ostream&operator<<(ostream&sortie,vect&v)//voirremarqueci-après
{sortie<<"<";
vector<int>::iteratoriv;
for(iv=v.begin();iv!=v.end();iv++)sortie<<*iv<<"";
sortie<<">";
returnsortie;
}
main()
{inti;
vectv1(5),v2(10);
for(i=0;i<5;i++)v1[i]=i;
cout<<"v1="<<v1<<"\n";
for(i=0;i<10;i++)v2[i]=i*i;
cout<<"v2="<<v2<<"\n";
v1=v2;
cout<<"v1="<<v1<<"\n";
vectv3=v1;
cout<<"v3="<<v3<<"\n";
vectv4=v2;
cout<<"v4="<<v4<<"\n";
392
//constvectw(3);w[2]=5;//conduitbienàerreurcompilation
}
Certains environnements imposent que l’on mentionne l’espace de nom std dans lasurdéfinitiondel’opérateur<<enécrivantstd::operator<<.
393
Exercice158(94revisité)
AncienénoncéRéaliseruneclassenommée bit_array permettantdemanipulerdes tableauxdebits(autrementdit,destableauxdanslesquelschaqueélémentnepeutprendrequel’unedesdeuxvaleurs0ou1).Latailled’untableau(c’est-à-direlenombredebits)seradéfinielorsdesacréation(parunargumentpasséàsonconstructeur).Onprévoiralesopérateurssuivants:
•+=,telquet+=nmetteà1lebitderangndutableaut;
•-=,telquet-=nmetteà0lebitderangndutableaut;
•[],telquel’expressiont[i]fournisselavaleurdubitderangidutableaut(onne prévoira pas, ici, de pouvoir employer cet opérateur à gauche d’uneaffectation,commedanst[i]=...);
•++,telquet++metteà1touslesbitsdet;
•--,telquet--metteà0touslesbitsdet;
•<<,telqueflot<<tenvoielecontenudetsurleflotindiqué,souslaforme:<*bit1,bit2,...bitn*>
On fera en sorte que l’affectation et la transmission par valeur d’objets du typebit_arrayneposeaucunproblème.
CommentairesSil’onnes’intéressequ’auxseulesfonctionnalitésdelaclassequ’ondemanded’écrireetquel’onfaitabstractiondel’opérateurdesortiesurunflot, lecomposantstandardvector<bool> fera l’affaire. On notera cependant qu’alors, l’opérateur [] pourra êtreemployéàgauched’uneaffectation.
Si l’on tient absolument à ce que ces fonctionnalités soient mises en œuvre parl’intermédiaire des opérateurs proposés, on peut quand même s’appuyer sur lesfonctionnalités de la classe vector<bool> en créant une classe dérivée qu’on adapte defaçonappropriée.Voici cequepourrait être ladéfinitiond’une telle classe,nomméebit_array,accompagnéedumêmeexempled’utilisationquedansl’exercice94:
#include<iostream>
394
#include<vector>
#include<limits.h>
usingnamespacestd;
/*déclarationdelaclassebit_array*/
classbit_array:publicvector<bool>
{public:
bit_array(int=16);
intoperator[](int)const;//valeurd'unbit
voidoperator+=(int);//activationd'unbit
voidoperator-=(int);//désactivationd'un//bit
//envoisurflot
friendostream&operator<<(ostream&,bit_array&);
//lesopérateursunaires
voidoperator++();//miseà1
voidoperator--();//miseà0
voidoperator~();//complémentà1
};
/*définitiondesfonctionsdelaclassebit_array*/
bit_array::bit_array(intnb):vector<bool>(nb){}
voidbit_array::operator+=(inti)
{(*this).vector<bool>::operator[](i)=true;
}//vector<bool>::operator[]pourforcerl'emploide[]declassedebase
voidbit_array::operator-=(inti)
{(*this).vector<bool>::operator[](i)=false;
}//vector<bool>::operator[]pourforcerl'emploide[]declassedebase
ostream&operator<<(ostream&sortie,bit_array&t)//voirremarque
{sortie<<"<*";
vector<bool>::iteratorie;
for(ie=t.begin();ie!=t.end();ie++)
sortie<<*ie<<"";
sortie<<"*>";
returnsortie;
}
voidbit_array::operator++()
{vector<bool>::iteratorie;
for(ie=(*this).begin();ie!=(*this).end();ie++)
*ie=true;
}
voidbit_array::operator--()
{vector<bool>::iteratorie;
for(ie=(*this).begin();ie!=(*this).end();ie++)
*ie=false;
}
voidbit_array::operator~()
{vector<bool>::iteratorie;
for(ie=(*this).begin();ie!=(*this).end();ie++)
*ie=!(*ie);
}
/*programmed'essaidelaclassebit_array*/
main()
{bit_arrayt1(34);
cout<<"t1="<<t1<<"\n";
t1+=3;t1+=0;t1+=8;t1+=15;t1+=33;
cout<<"t1="<<t1<<"\n";
t1--;
cout<<"t1="<<t1<<"\n";
t1++;
cout<<"t1="<<t1<<"\n";
t1-=0;t1-=3;t1-=8;t1-=15;t1-=33;
cout<<"t1="<<t1<<"\n";
cout<<"t1="<<t1<<"\n";
395
bit_arrayt2(11),t3(17);
cout<<"t2="<<t2<<"\n";
t2=t3=t1;
cout<<"t3="<<t3<<"\n";
}
Certains environnements imposent que l’on mentionne l’espace de nom std dans lasurdéfinitiondel’opérateur<<enécrivantstd::operator<<.
La comparaison avec l’exercice correspondant du chapitre 16 montre que le gainobtenuavecl’utilisationdescomposantsstandardresteassezrelatif.L’essentielvientde ce qu’il n’est plus besoin ici de surdéfinir l’opérateur d’affectation et leconstructeurparrecopie.
396
PoursuivretouteslesnouveautésnumériquesduGroupeEyrolles,retrouvez-noussurTwitteretFacebook
@ebookEyrolles
EbooksEyrolles
Etretrouveztouteslesnouveautéspapiersur
@Eyrolles
Eyrolles
397
Recommended