programmer en c++ moderne - fnac-static.com

123
Claude Delannoy De C++11 à C++20 Programmer en C++ moderne

Upload: others

Post on 03-Nov-2021

9 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Programmer en C++ moderne - fnac-static.com

Claude Delannoy

De C++11 à C++20

Programmer

en C++ moderne

Acquérir une parfaite maîtrise du C++ et de la programmation objetLes versions C++11, C++14 et C++17 ont apporté au langage C++ plus que de nouvelles fonctionnalités : une nouvelle façon de programmer. Dès lors, une refonte complète du classique Programmer en langage C++ de Claude Delannoy s’imposait. C’est à cette tâche que s’est attelé l’auteur à l’occasion de cette 10e édition de l’ouvrage, en réécrivant les exemples de code et en préconisant de bonnes pratiques de programmation dans l’esprit de ce C++ moderne.

L’ouvrage ainsi remanié commence par une présentation détaillée de la syntaxe de base du langage, s’appuyant dès que possible sur les structures de données de la bibliothèque standard (types string et vector) et sur la déclaration automatique (C++11). Puis il expose les techniques de gestion dynamique basées sur les « pointeurs intelligents » introduits par C++11 et C++14.

L’auteur insiste ensuite sur la bonne compréhension des concepts objet et de la programmation générique à l’aide des « patrons ». Un chapitre est consacré à la « sémantique de déplacement » introduite par C++11. Plusieurs chapitres sont dédiés aux conteneurs et aux algorithmes de la STL (Standard Template LIbrary). Les nouveautés de C++20 (concepts et contraintes, modules, coroutines…) sont présentées en annexe.

Chaque notion nouvelle et chaque fonction du langage est illustrée de programmes complets écrits en C++ moderne, dont le code source est fourni sur le site www.editions-eyrolles.com. Un équivalent en C++03 est proposé quand nécessaire pour les lecteurs amenés à exploiter d’anciens programmes.

38 E

Conception de couverture : Studio Eyrolles © Éditions Eyrolles

Cod

e éd

iteur

: G

6789

5IS

BN

: 97

8-2-

212-

6789

5-2

Ingénieur informaticien au CNRS, Claude Delannoy possède une grande pratique de la formation continue et de l’enseignement supérieur. Réputés pour la qualité de leur démarche pédagogique, ses ouvrages sur les langages et la programmation totalisent plus de 500 000 exemplaires vendus.

Au sommaire Présentation du langage • Les types de base du C++ • Opérateurs et expressions • Les entrées-sorties conversationnelles • Les instruc-tions de contrôle • Les fonctions • Le type string • Les pointeurs natifs • La gestion dynamique • Les vecteurs et les tableaux natifs • Classes et objets • Les propriétés des fonctions membres • Construction, destruction et initialisation des objets • Les fonctions amies • La surdéfinition d’opérateurs • Les conversions de type définies par l’utilisateur • Les patrons de fonctions • Les patrons de classes • L’héritage simple • L’héritage multiple • Les fonctions virtuelles et le polymorphisme • Optimisation par déplacement • Les flots • La gestion des exceptions • Généralités sur la bibliothèque standard (STL) • Les conteneurs séquentiels • Les conteneurs associatifs • Les algorithmes standards • La classe string • Les outils numériques • Les espaces de noms • Le préprocesseur et l’instruction typedef et using • Énumérations, champs de bits et unions •  Introduction aux threads • Annexes. 

À qui s’adresse ce livre ? • Aux étudiants de cursus universitaires (DUT, licence, master), ainsi qu’aux élèves des écoles d’ingénieurs.• À tout programmeur ayant déjà une expérience de la programmation (C, C#, Java, Python, PHP…) et souhaitant s’initier

au langage C++.

P

rogr

amme

r en C

++ m

oder

ne

C.

Del

anno

y

G67386_Programmer avec langage C++OK.indd 1 02/10/2019 13:54

Page 2: Programmer en C++ moderne - fnac-static.com

Claude Delannoy

De C++11 à C++20

Programmer

en C++ moderne

Acquérir une parfaite maîtrise du C++ et de la programmation objetLes versions C++11, C++14 et C++17 ont apporté au langage C++ plus que de nouvelles fonctionnalités : une nouvelle façon de programmer. Dès lors, une refonte complète du classique Programmer en langage C++ de Claude Delannoy s’imposait. C’est à cette tâche que s’est attelé l’auteur à l’occasion de cette 10e édition de l’ouvrage, en réécrivant les exemples de code et en préconisant de bonnes pratiques de programmation dans l’esprit de ce C++ moderne.

L’ouvrage ainsi remanié commence par une présentation détaillée de la syntaxe de base du langage, s’appuyant dès que possible sur les structures de données de la bibliothèque standard (types string et vector) et sur la déclaration automatique (C++11). Puis il expose les techniques de gestion dynamique basées sur les « pointeurs intelligents » introduits par C++11 et C++14.

L’auteur insiste ensuite sur la bonne compréhension des concepts objet et de la programmation générique à l’aide des « patrons ». Un chapitre est consacré à la « sémantique de déplacement » introduite par C++11. Plusieurs chapitres sont dédiés aux conteneurs et aux algorithmes de la STL (Standard Template LIbrary). Les nouveautés de C++20 (concepts et contraintes, modules, coroutines…) sont présentées en annexe.

Chaque notion nouvelle et chaque fonction du langage est illustrée de programmes complets écrits en C++ moderne, dont le code source est fourni sur le site www.editions-eyrolles.com. Un équivalent en C++03 est proposé quand nécessaire pour les lecteurs amenés à exploiter d’anciens programmes.

Conception de couverture : Studio Eyrolles © Éditions Eyrolles

Cod

e éd

iteur

: G

6789

5IS

BN

: 97

8-2-

212-

6789

5-2

Ingénieur informaticien au CNRS, Claude Delannoy possède une grande pratique de la formation continue et de l’enseignement supérieur. Réputés pour la qualité de leur démarche pédagogique, ses ouvrages sur les langages et la programmation totalisent plus de 500 000 exemplaires vendus.

Au sommaire Présentation du langage • Les types de base du C++ • Opérateurs et expressions • Les entrées-sorties conversationnelles • Les instruc-tions de contrôle • Les fonctions • Le type string • Les pointeurs natifs • La gestion dynamique • Les vecteurs et les tableaux natifs • Classes et objets • Les propriétés des fonctions membres • Construction, destruction et initialisation des objets • Les fonctions amies • La surdéfinition d’opérateurs • Les conversions de type définies par l’utilisateur • Les patrons de fonctions • Les patrons de classes • L’héritage simple • L’héritage multiple • Les fonctions virtuelles et le polymorphisme • Optimisation par déplacement • Les flots • La gestion des exceptions • Généralités sur la bibliothèque standard (STL) • Les conteneurs séquentiels • Les conteneurs associatifs • Les algorithmes standards • La classe string • Les outils numériques • Les espaces de noms • Le préprocesseur et l’instruction typedef et using • Énumérations, champs de bits et unions •  Introduction aux threads • Annexes. 

À qui s’adresse ce livre ? • Aux étudiants de cursus universitaires (DUT, licence, master), ainsi qu’aux élèves des écoles d’ingénieurs.• À tout programmeur ayant déjà une expérience de la programmation (C, C#, Java, Python, PHP…) et souhaitant s’initier

au langage C++.

P

rogr

amme

r en C

++ m

oder

ne

C.

Del

anno

y

G67386_Programmer avec langage C++OK.indd 1 02/10/2019 13:54

Page 3: Programmer en C++ moderne - fnac-static.com

Programmer

en C++ moderne

67895-ProgrammerEnLangageC++-PDT.indd 1 23/09/2019 16:55

Page 4: Programmer en C++ moderne - fnac-static.com

CHEZ LE MÊME ÉDITEUR

Du même auteurC. Delannoy. – Exercices en langage C++ - 178 exercices corrigés. N°67663, 4e édition, 2018, 396 pages.

C. Delannoy. – Programmer en Java (couvre Java 9). N°67536, 10e édition, 2017, 920 pages.C. Delannoy. – Exercices en Java (couvre Java 8). N°67385, 4e édition, 2017, 346 pages.C. Delannoy. – Programmer en langage C Cours et exercices corrigés N°11825, 5e édition, 2016, 268 pages.C. Delannoy. – Le guide complet du langage C. N°14012, 2014, 844 pages.C. Delannoy. – S’initier à la programmation et à l’orienté objet Avec des exemples en C, C++, C#, Python, Java et PHP N°11826, 2e édition, 2016, 360 pages.

Dans la même collection

A. Tasso. – Le livre de Java premier langage Avec 109 exercices corrigés. N°67840, 13e édition, 2019, 600 pages.H. Bersini, P. Alexis, G. Degols. – Apprendre la programmation web avec Python et Django. N°67515, 2018, 396 pages.H. Bersini, I. Wellesz. – La programmation orientée objet. Cours et exercices en UML 2 avec Java, C#, C++, Python, PHP et LinQ. N°67399, 7e édition, 2017, 696 pages.J. Engels. – PHP 7. N°67360, 2017, 585 pages.P. Roques. – UML 2.5 par la pratique. Études de cas et exercices corrigés. N°67565, 8e édition, 2018, 408 pages.C. Soutou. – Programmer avec MySQL. N°67379, 5e édition, 2017, 522 pages.G. Swinnen. – Apprendre à programmer avec Python 3. N°13434, 3 édition, 2012, 435 pages.e

pII Delannoy Programmer en C++ moderne G0067895

CHEZ LE MÊME ÉDITEUR

Du même auteur

C. Delannoy. – Exercices en langage C++ - 178 exercices corrigés. N°67663, 4e édition, 2018, 396 pages.

C. Delannoy. – Programmer en Java (couvre Java 9). N°67536, 10e édition, 2017, 920 pages.

C. Delannoy. – Exercices en Java (couvre Java 8). N°67385, 4e édition, 2017, 346 pages.

C. Delannoy. – Programmer en langage C Cours et exercices corrigés N°11825, 5e édition, 2016, 268 pages.

C. Delannoy. – Le guide complet du langage C. N°14012, 2014, 844 pages.

C. Delannoy. – S’initier à la programmation et à l’orienté objet Avec des exemples en C, C++, C#, Python, Java et PHP N°11826, 2e édition, 2016, 360 pages.

Dans la même collection

A. Tasso. – Le livre de Java premier langage Avec 109 exercices corrigés. N°67840, 13e édition, 2019, 600 pages.

H. Bersini, P. alexis, G. DeGols. – Apprendre la programmation web avec Python et Django. N°67515, 2018, 396 pages.

H. Bersini, i. Wellesz. – La programmation orientée objet. Cours et exercices en UML 2 avec Java, C#, C++, Python, PHP et LinQ. N°67399, 7e édition, 2017, 696 pages.

J. enGels. – PHP 7. N°67360, 2017, 585 pages.

P. roques. – UML 2.5 par la pratique. Études de cas et exercices corrigés. N°67565, 8e édition, 2018, 408 pages.

C. souTou. – Programmer avec MySQL. N°67379, 5e édition, 2017, 522 pages.

G. sWinnen. – Apprendre à programmer avec Python 3. N°13434, 3e édition, 2012, 435 pages.

67895-ProgrammerEnLangageC++-PDT.indd 2 23/09/2019 16:55

Page 5: Programmer en C++ moderne - fnac-static.com

Claude Delannoy

De C++11 à C++20

Programmer

en C++ moderne

67895-ProgrammerEnLangageC++-PDT.indd 3 23/09/2019 16:55

Page 6: Programmer en C++ moderne - fnac-static.com

En application de la loi du 11 mars 1957, il est interdit de reproduire intégralement ou partiellement le présent ouvrage, sur quelque support que ce soit, sans autorisation de l’Éditeur ou du Centre Français d’Exploitation du Droit de copie, 20, rue des Grands-Augustins, 75006 Paris.

Éditions Eyrolles61, bd Saint-Germain75240 Paris Cedex 05

www.editions-eyrolles.com

À l’occasion de cette 10e édition, l’ouvrage Programmer en langage C++ de Claude Delannoy change de titre pour s’appeler désormais Programmer en C++ moderne.© Éditions Eyrolles, 1993- 2019ISBN : 978-2-212-67895-2

67895-ProgrammerEnLangageC++-PDT.indd 4 27/09/2019 09:15

Page 7: Programmer en C++ moderne - fnac-static.com

Avant-propos

Depuis sa conception dans les années 1980 par B. Stroustrup, le C++ a tout naturellementévolué à travers diverses normalisations. Mais, avec la version C++11, le langage a été suffi-samment modifié pour qu’on se mette à parler de « C++ moderne ». Le présent ouvrageconstitue un remaniement profond des éditions précédentes ; non seulement, il intègre lesnouvelles possiblités offertes par les versions C++11, C++14, C++17 et C++20 mais, de sur-croît, il présente les nouvelles pratiques de progammation induites par ce changement radi-cal. Les exemples de codes ont tous été réécrits dans ce sens.

1 À qui s’adresse l’ouvrageDestiné à tous ceux qui souhaitent maîtriser l’ensemble des facettes de la programmation enC++, cet ouvrage s’adresse aussi bien aux étudiants qu’aux développeurs ou aux enseignantsen informatique.

Il ne requiert aucune connaissance en programmation orientée objet dont les principes serontprésentés en détail au fil de l’étude. Il en va de même pour le langage C dans la mesure où lesspécificités de C++ issues du C seront exposées avec le même niveau de détail que les autres.

En revanche, il reste préférable que le lecteur possède déjà une petite expérience de la pro-grammation procédurale (ou structurée), c’est-à-dire dire qu’il soit familiarisé avec lesnotions de variables, de types, d’affectation, de structures de contrôle, de fonctions, etc.,communes à la plupart des langages en usage aujourd’hui (C/C++, Python, JavaScript, Java,PHP...).

LIVRE.book Page V Jeudi, 12. septembre 2019 6:19 18

Page 8: Programmer en C++ moderne - fnac-static.com

Programmer en langage C++VI

2 Structure et contenu de l’ouvrageL’ouvrage commence par un chapitre très général présentant de façon globale les différentescaractéristiques du langage, montrant ainsi comment y sont intégrés les différents conceptsintervenant en programmation procédurale ou en programmation orientée objet (P.O.O.) et enquoi C++ se démarque des autres langages.

La programmation procédurale (chapitres 2 à 11)Après un chapitre présentant de façon informelle les rudiments les plus indispensables àl’écriture de programmes complets, on aborde l’ensemble des possibilités de programmationprocédurale : types de variables, opérateurs et expressions, instructions de contrôle, fonctions(y compris les expressions lambdas). La déclaration automatique (auto) est présentée à ce niveau de façon à être exploitée dans lasuite de l’ouvrage lorsqu’elle offre un intérêt.On s’appuie très tôt sur le type string dont on présente les principales propriétés, et ceci alorsmême que la notion de classe n’a pas encore été exposée.On trouvera ensuite la présentation des pointeurs natifs et celle de la gestion dynamique danslaquelle on introduit les pointeurs intelligents (en se limitant à ce niveau aux types de base).Cette partie s’achève sur :• l’étude en parallèle des tableaux natifs et des principales propriétés des vecteurs (alors

même que, là encore, les proprités des patrons et des conteneurs ne seront étudiées en détailqu’ultérieurement) ;

• l’étude succinte des chaînes de style C dont la connaissance reste nécessaire dans certainessituations.

Le chapitre 5 propose une première approche des entrées/sorties conversationnelles, de façonà pouvoir les employer dans un code, avant même que n’aient été étudiées leurs fonctionnali-tés détaillées.

La programmation orientée objet (chapitres 12 à 25)Les aspects orientés objet sont ensuite abordés de façon progressive, mais sans pour autantnuire à l’exhaustivité de l’ouvrage. Nous y traitons, non seulement les purs concepts deP.O.O. (classe, constructeur, destructeur, héritage, redéfinition, polymorphisme, programma-tion générique), mais aussi les aspects très spécifiques au langage (surdéfinition d’opérateurs,fonctions amies). Nous pensons ainsi permettre au lecteur de devenir parfaitement opération-nel dans la conception, le développement et la mise au point de ses propres classes. C’estainsi, par exemple, que nous avons largement insisté sur le rôle du constructeur de recopie,ainsi que sur la redéfinition de l’opérateur d’affectation, éléments qui conduisent à la notionde « classe canonique ». Toujours dans le même esprit, nous avons pris soin de bien dévelop-per les notions avancées mais indispensables que sont la ligature dynamique et les classes

LIVRE.book Page VI Jeudi, 12. septembre 2019 6:19 18

Page 9: Programmer en C++ moderne - fnac-static.com

Avant-propos VII

abstraites, lesquelles débouchent sur la notion la plus puissante du langage (et de la P.O.O.)qu’est le polymorphisme.Tout ce qui est lié à la sémantique de déplacement introduite par C++11 (références à desrvalue, construction et affectation par déplacement, références universelles, fonctionforward...) a été regroupé dans un chapitre spécifique (23), de façon à permettre au lecteurd’en aborder l’étude que lorsqu’il le juge nécessaire.Cette partie s’achève par deux chapitres (24 et 25) plus techniques consacrés aux flots et à lagestion des exceptions.

Les structures de données et les algorithmes (chapitres 26 à 31)On étudie ensuite en détail les principaux éléments de la S.T.L. (Standard Template Library)après avoir pris soin d’exposer préalablement d’une part les notions de classes et de fonctionsgénériques (patrons), d’autre part celles de conteneur, d’itérateur et d’algorithmes qui condi-tionnent la bonne utilisation de la plupart de ses composants.C’est naturellement là qu’on trouvera l’étude détaillée des classes string et vector présentéessuccinctement auparavant.

Les chapitres finaux et les annexesLes derniers chapitres sont consacrés :

• au préprocesseur ;

• aux énumérations et aux structures de bas niveau héritées du C (champs de bits et unions) ;

• à une introduction aux threads.

Enfin quelques annexes fournissent des récapitulatifs ou des compléments. On y trouveranotamment :

• un récapitulatif des règles de recherche d’une fonction surdéfinie ;

• la liste complète des algorithmes standards.

Le C++ moderne : sa place dans le livreLes nouvelles fonctionnalités du C++ moderne sont tout naturellement intégrées au fil dutexte. Par exemple :

• les déclarations automatiques (auto) et la nouvelle syntaxe d’initialisation (entre accolades)sont présentées dans le chapitre 3 pour les types de base (leur intérêt étant alors limité) etelles seront complétées au fur et à mesure de la rencontre de nouveaux types ;

• les pointeurs intelligents sont présentés au chapitre 10 et seront ensuite utilisés à chaque foisqu’on devra recourir à l’allocation dynamique ;

LIVRE.book Page VII Jeudi, 12. septembre 2019 6:19 18

Page 10: Programmer en C++ moderne - fnac-static.com

Programmer en langage C++VIII

• le type initializer_list est présenté au chapitre 7 et on le retrouvera ensuite tout au long del’ouvrage et l’on verra les problèmes qu’il peut poser avec l’initialisation avec accolades ;

• la boucle for pour les séquences est introduite au chapitre 8 pour le type string et on la re-trouvera ensuite pour les autres formes de séquences ;

• la sémantique de déplacement sera exposée dans son intégralité dans le chapitre 23.

D’une manière générale, le C++ moderne offre des avantages considérables, tant sur le plande l’uniformisation du langage que sur celui de l’efficacité de la bibliothèque standard ouencore de l’optimisation du code. Il n’en reste pas moins que le respect strict de la compatibi-lité avec les versions antérieures introduit de nouvelles difficultés liées :

• à la multiplicité des rédactions possibles qui s’offre au programmeur ;

• à quelques incohérences internes ; par exemple, dans de rares cas, auto n’est pas utilisableou ne fournit pas le type escompté.

De plus, l’universalité des déclarations souhaitées par les concepteurs des nouvelles normesn’est pas toujours au rendez-vous ; en particulier, elle est souvent perturbée par l’introductiondu type initializer_list.Fidèle à nos habitudes, ces difficultés sont clairement identifiées au fil du texte. Ainsi, le lec-teur restera en mesure d’effectuer ses propres choix sur ces situations délicates.

3 Guide d’utilisationL’ouvrage est conçu sous la forme d’un cours progressif. Celui qui aborde le C++ pour lapremière fois devra tenter de l’étudier de façon séquentielle. Néanmoins certaines partiespeuvent ne pas être utiles à la poursuite de l’étude : elles sont alors soit placées dans unerubrique « informations complémentaires » pour les plus brèves, soit indiquées clairementpar une mention en italique figurant au début du paragraphe correspondant.Chaque notion fondamentale est illustrée d’un programme simple mais complet, accompagnéd’un exemple d’exécution. Le code correspondant peut être trouvé sur le site de l’éditeurwww.editions-eyrolles.com.Outre son caractère didactique, l’ouvrage est conçu d’une manière très structurée, de façon àen permettre la consultation au delà de la phase d’apprentissage. C’est ainsi qu’il est dotéd’une table des matières très détaillée et d’un index fourni. Les exemples complets peuventservir à une remémoration rapide du concept qu’ils illustrent. Des encadrés permettent deretrouver rapidement la syntaxe d’une instruction ou certaines règles fondamentales.Bien que fondé sur le C++ moderne, l’ouvrage propose différents outils permettant, le caséchéant, de bien distinguer l’apport des versions récentes :

• des pictogrammes appropriés mentionnent les apports de chacune des versions récentes(C++11, C++14, C++17 et C++20) ;

LIVRE.book Page VIII Jeudi, 12. septembre 2019 6:19 18

Page 11: Programmer en C++ moderne - fnac-static.com

Avant-propos IX

• lorsque tout un paragraphe est concerné par l’une de ces versions, outre le pictogramme évo-qué, le titre en mentionnera le numéro correspondant ;

• les codes, bien qu’écrits en C++ moderne, fournissent systématiquement en commentairesune formulation pour C++98 ; en outre lorsqu’une partie de code s’appuie sur C++14 ouC++17, une formulation C++11 équivalente est proposée.

Ces outils devraient permettre au développeur d’exploiter d’anciens codes. L’enseignant quile souhaiterait pourra se bâtir un cours simplifié fondé sur un C++ réduit plus proche du C++historique que du C++ moderne.Par ailleurs, le programmeur C abordant l’étude du C++ pourra tirer profit des remarquestitrées « En C », lesquelles viennent signaler les différences les plus importantes existantentre C et C++.Enfin, compte tenu de la popularité du langage Java, nous avons introduit de nombreusesremarques titrées « En Java ». Elles mettent l’accent sur les différences majeures existantentre Java et C++. Elles seront utiles non seulement au programmeur Java qui apprend ici leC++, mais également au lecteur qui, après la maîtrise du C++, souhaitera aborder l’étude deJava.

4 Ce que vous trouverez sur le site de l’éditeurSur le site www.editions-eyrolles.com, outre les codes sources des différents exemples, voustrouverez deux chapitres qui figuraient dans les précédentes éditions :• la description des principales fonctions héritées du langage C ;• l’étude des principaux « design patterns » et leur programmation en C++, ce chapitre étant

été lui aussi remanié pour le C++ moderne.

LIVRE.book Page IX Jeudi, 12. septembre 2019 6:19 18

Page 12: Programmer en C++ moderne - fnac-static.com

LIVRE.book Page X Jeudi, 12. septembre 2019 6:19 18

Page 13: Programmer en C++ moderne - fnac-static.com

Table des matières

Chapitre 1 : Présentation du langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 - Historique du langage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 - Programmation structurée et programmation orientée objet. . . . . . . . . . . . . . . . . . . . . . . . . . . 2

2.1 Problématique de la programmation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22.2 La programmation structurée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32.3 Les apports de la programmation orientée objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2.3.1 Objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32.3.2 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32.3.3 Classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42.3.4 Héritage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42.3.5 Polymorphisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2.4 P.O.O., langages de programmation et C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 - C++ et la programmation structurée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 - C++ et la programmation orientée objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 - C et C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 - C++ et les bibliothèques standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 - A propos du C++ moderne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

Chapitre 2 : Généralités sur le langage C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 - Présentation par l’exemple de quelques instructions du langage C++ . . . . . . . . . . . . . . . . . . . 12

1.1 Un exemple de programme en langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121.2 Structure d’un programme en langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.3 Déclarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131.4 Pour écrire des informations : utiliser le flot cout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.5 Pour faire une répétition : l’instruction for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141.6 Pour lire des informations : utiliser le flot cin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151.7 Pour faire des choix : l’instruction if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151.8 Les directives à destination du préprocesseur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.9 L’instruction using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.10 Exemple de programme utilisant le type caractère. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2 - Quelques règles d’écriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.1 Les identificateurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.2 Les mots-clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.3 Les séparateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

LIVRETDM2.fm Page XI Mardi, 26. novembre 2019 3:39 15

Page 14: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CX

2.4 Le format libre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192.5 Les commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

2.5.1 Les commentaires libres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.5.2 Les commentaires de fin de ligne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3 - Création d’un programme en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223.1 L’édition du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223.2 La compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223.3 L’édition de liens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223.4 Les fichiers en-tête. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Chapitre 3 : Les types de base de C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 - La notion de type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 - Les types entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

2.1 Les différents types usuels d’entiers prévus par C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262.2 Leur représentation en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272.3 Les types entiers non signés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282.4 Notation des constantes littérales entières . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

3 - Les types flottants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293.1 Les différents types et leur représentation en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293.2 Notation des constantes littérales flottantes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

4 - Les types caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.1 La notion de caractère en langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.2 Notation des constantes littérales de type caractère . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

5 - Le type bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 - Déclaration des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 - Initialisation des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

7.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347.2 Notations de l’initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

7.2.1 La notation parenthésée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357.2.2 La notation avec accolades (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

8 - Constantes et expressions constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368.1 Le modificateur const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368.2 Le modificateur constexpr (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

9 - Déclarations automatiques (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379.1 Le mot-clé auto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379.2 Le mot-clé decltype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

10 - Le mot-clé volatile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

Chapitre 4 : Opérateurs et expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 - Originalité des notions d’opérateur et d’expression en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 - Les opérateurs arithmétiques en C++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

2.1 Présentation des opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

LIVRE.book Page X Jeudi, 12. septembre 2019 6:19 18

Page 15: Programmer en C++ moderne - fnac-static.com

Table des matières XI

2.2 Les priorités relatives des opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422.3 Comportement des opérateurs en cas d’opération impossible . . . . . . . . . . . . . . . . . . . . . . . . . 42

3 - Les conversions implicites pouvant intervenir dans un calcul d’expression . . . . . . . . . . . . . . 443.1 Notion d’expression mixte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443.2 Les conversions usuelles d’ajustement de type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443.3 Les promotions numériques usuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

3.3.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453.3.2 Cas du type char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463.3.3 Cas du type bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

3.4 Les conversions en présence de types non signés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473.4.1 Cas des entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473.4.2 Cas des caractères. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

4 - Les opérateurs relationnels. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495 - Les opérateurs logiques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

5.1 Rôle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.2 Court-circuit dans l’évaluation de && et ||. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

6 - L’opérateur d’affectation ordinaire. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536.1 Notion de lvalue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536.2 L’opérateur d’affectation possède une associativité de droite à gauche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546.3 L’affectation peut entraîner une conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

7 - Opérateurs d’incrémentation et de décrémentation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

7.1 Leur rôle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547.2 Leurs priorités. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567.3 Leur intérêt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

8 - Les opérateurs d’affectation élargie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569 - Les conversions forcées par une affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

9.1 Cas usuels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 579.2 Prise en compte d’un attribut de signe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

10 - L’opérateur de cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5811 - L’opérateur conditionnel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5912 - L’opérateur séquentiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6013 - L’opérateur sizeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6214 - Les opérateurs de manipulation de bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

14.1 Présentation des opérateurs de manipulation de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6214.2 Les opérateurs bit à bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6314.3 Les opérateurs de décalage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6414.4 Exemples d’utilisation des opérateurs de bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

15 - Récapitulatif des priorités de tous les opérateurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

LIVRE.book Page XI Jeudi, 12. septembre 2019 6:19 18

Page 16: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXII

Chapitre 5 : Les entrées-sorties conversationnelles de C++ . . . . . . . . . . . . 671 - Affichage à l’écran . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

1.1 Exemple 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 681.2 Exemple 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 681.3 Les possibilités d’écriture sur cout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69

2 - Lecture au clavier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702.2 Les différentes possibilités de lecture sur cin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712.3 Notions de tampon et de caractères séparateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712.4 Premières règles utilisées par >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722.5 Présence d’un caractère invalide dans une donnée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722.6 Les risques induits par la lecture au clavier. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

2.6.1 Manque de synchronisme entre clavier et écran . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732.6.2 Blocage de la lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 742.6.3 Boucle infinie sur un caractère invalide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

Chapitre 6 : Les instructions de contrôle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771 - Les blocs d’instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78

1.1 Blocs d’instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 781.2 Déclarations dans un bloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

2 - L’instruction if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 792.1 Syntaxe de l’instruction if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802.2 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 802.3 Imbrication des instructions if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

3 - L’instruction switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 833.1 Exemples d’introduction de l’instruction switch. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 833.2 Syntaxe de l’instruction switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

4 - L’instruction do... while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 884.1 Exemple d’introduction de l’instruction do... while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 884.2 Syntaxe de l’instruction do... while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

5 - L’instruction while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905.1 Exemple d’introduction de l’instruction while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905.2 Syntaxe de l’instruction while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

6 - L’instruction for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 926.1 Exemple d’introduction de l’instruction for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 926.2 L’instruction for en général . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 936.3 Syntaxe de l’instruction for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

7 - Les instructions de branchement inconditionnel : break, continue et goto . . . . . . . . . . . . . . . 977.1 L’instruction break. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 977.2 L’instruction continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 987.3 L’instruction goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

8 - Initialisation dans les instructions if et switch (C++17) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

LIVRE.book Page XII Jeudi, 12. septembre 2019 6:19 18

Page 17: Programmer en C++ moderne - fnac-static.com

Table des matières XIII

Chapitre 7 : Les fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1031 - Exemple de définition et d’utilisation d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1042 - Quelques règles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

2.1 Arguments muets et arguments effectifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1062.2 L’instruction return. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1062.3 Cas des fonctions sans valeur de retour ou sans arguments . . . . . . . . . . . . . . . . . . . . . . . . . . 107

3 - Les fonctions et leurs déclarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1093.1 Les différentes façons de déclarer une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1093.2 Où placer la déclaration d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1093.3 Contrôles et conversions induites par le prototype. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

4 - Transmission des arguments par valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1104.1 Cas général . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1104.2 Transmission par valeur et constance des arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

4.2.1 Cas des arguments effectifs constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1124.2.2 Cas des arguments muets constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112

5 - Transmission des arguments par référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1135.1 Exemple de transmission d’argument par référence. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1135.2 Propriétés de la transmission par référence d’un argument . . . . . . . . . . . . . . . . . . . . . . . . . . 1145.3 Référence à un argument muet constant. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1145.4 Induction de risques indirects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115

6 - Les variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1166.1 Exemple d’utilisation de variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1166.2 La portée des variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1176.3 La classe d’allocation des variables globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

7 - Les variables locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1187.1 La portée des variables locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1187.2 Les variables locales automatiques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1197.3 Les variables locales statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1207.4 Variables locales à un bloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1217.5 Le cas des fonctions récursives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

8 - Transmission par référence d’une valeur de retour. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1228.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1238.2 Conséquences dans la définition de la fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1238.3 Conséquences dans l’utilisation de la fonction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1238.4 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1248.5 Valeur de retour constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125

9 - Initialisation des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1259.1 Les variables de classe statique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1269.2 Les variables de classe automatique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126

10 - Les arguments par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12710.1 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12710.2 Les propriétés des arguments par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129

LIVRE.book Page XIII Jeudi, 12. septembre 2019 6:19 18

Page 18: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXIV

11 - Surdéfinition de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12911.1 Mise en œuvre de la surdéfinition de fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13011.2 Exemples de choix d’une fonction surdéfinie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13111.3 Règles de recherche d’une fonction surdéfinie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

11.3.1 Cas des fonctions à un argument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13311.3.2 Cas des fonctions à plusieurs arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134

12 - Les fonctions et la déclaration auto (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13512.1 Déclaration automatique du type des valeurs de retour. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13512.2 Déclaration automatique du type des arguments muets . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13512.3 Combinaison des deux possibilités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

13 - Les fonctions déclarées constexpr (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13614 - La référence d’une manière générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137

14.1 Déclaration de variables de type référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13714.2 Initialisation de référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

15 - Les fonctions à arguments variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13915.1 Le type initializer_list (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13915.2 Application à une fonction à nombre variable d’arguments (C++11). . . . . . . . . . . . . . . . . . 14115.3 Les anciennes fonctions va_start et va_arg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142

16 - Conséquences de la compilation séparée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14416.1 Compilation séparée et prototypes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14416.2 Fonction manquante lors de l’édition de liens. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14516.3 Le mécanisme de la surdéfinition de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14516.4 Compilation séparée et variables globales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147

16.4.1 La portée d’une variable globale – la déclaration extern . . . . . . . . . . . . . . . . . . . . . . 14716.4.2 Les variables globales et l’édition de liens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14716.4.3 Les variables globales cachées – la déclaration static . . . . . . . . . . . . . . . . . . . . . . . . 148

17 - La spécification inline. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14818 - Terminaison d’un programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151

Chapitre 8 : Le type string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1531 - Déclaration et initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1532 - Lecture et écriture de chaînes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1553 - Affectation de chaînes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1564 - Les fonctions size et empty. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1565 - Concaténation de chaînes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1576 - Accès aux caractères d’une chaîne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158

6.1 Accès à un caractère de rang donné. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1586.1.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1586.1.2 Absence de contrôle d’indice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1586.1.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159

6.2 Traitement de tous les caractères d’une chaîne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1606.2.1 Cas général. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

LIVRE.book Page XIV Jeudi, 12. septembre 2019 6:19 18

Page 19: Programmer en C++ moderne - fnac-static.com

Table des matières XV

6.2.2 L’instruction for pour les séquences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1606.2.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

7 - Les chaînes en argument d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1618 - Les autres possibilités du type string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

Chapitre 9 : Les pointeurs natifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1631 - Notion de pointeur – Les opérateurs * et & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164

1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1641.2 Déclarations multiples et emploi des opérateurs * et & . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1651.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

2 - Affectation et comparaison de pointeurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1682.1 Affectation de pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1682.2 Comparaisons de pointeurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1682.3 Le pointeur nul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1682.4 Conversion implicite en bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

3 - Les conversions entre pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1704 - Les pointeurs génériques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1705 - Pointeurs et constance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171

5.1 Pointeur sur un élément constant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1715.2 Pointeur constant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1725.3 Pointeur constant sur un élément constant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1735.4 constexpr et les pointeurs (C++11). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

6 - Comment simuler une transmission par adresse avec un pointeur . . . . . . . . . . . . . . . . . . . . 1747 - Pointeurs et surdéfinition de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1768 - Utilisation de pointeurs sur des fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

8.1 Paramétrage d’appel de fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1778.2 Fonctions transmises en argument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

9 - Les expressions lambdas (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1799.1 Exemple introductif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1799.2 La liste de capture d’une expression lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180

9.2.1 Expressions lambdas nommées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1819.2.2 Liste de capture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

Chapitre 10 : La gestion dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1831 - Les opérateurs new et delete pour les types scalaires. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

1.1 Présentation de new et delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1841.2 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185

1.2.1 Exemple 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1851.2.2 Exemple 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1861.2.3 Exemple 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

1.3 En cas de manque de mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1872 - Les pointeurs intelligents (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187

LIVRE.book Page XV Jeudi, 12. septembre 2019 6:19 18

Page 20: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXVI

3 - Le type unique_ptr (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1883.1 Présentation générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1883.2 Fiabilisation des schémas précédents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188

3.2.1 Exemple 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1883.2.2 Exemple 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189

3.3 Initialisation d’un unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1893.3.1 Initialisation avec new . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1893.3.2 Initialisation avec make_unique. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1903.3.3 Récapitulatif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191

3.4 Propriétés des unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1913.4.1 Adresse contenue dans un unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1913.4.2 Comparaisons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

3.5 Transfert de propriété . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1923.5.1 Par affectation avec move . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1923.5.2 Par appel de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1943.5.3 Cas particulier de la valeur de retour d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . 195

4 - Le type shared_ptr (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1974.1 Déclaration et initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1974.2 Utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1984.3 L’affectation et le compteur de références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1984.4 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2004.5 Transmission par valeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2014.6 Conversion entre shared_ptr et unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202

5 - Quelques précautions (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2036 - Pointeurs intelligents et cycles (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2037 - Les suppresseurs (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2048 - Le type auto_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205

Chapitre 11 : Les vecteurs, les tableaux natifs et les chaînes C . . . . . . 2071 - Les vecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208

1.1 Exemple de présentation des vecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2081.2 Les éléments et les indices d’un vecteur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

1.2.1 Quelques règles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2091.2.2 Absence de contrôle d’indice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210

1.3 Initialisation d’un vecteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2111.4 Parcours d’un vecteur avec for pour les séquences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2121.5 Affectation de vecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2131.6 Vecteurs transmis en argument d’une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214

1.6.1 Par défaut, la transmission se fait par valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2141.6.2 Transmission d’un vecteur par référence ou par pointeur. . . . . . . . . . . . . . . . . . . . . . . 215

1.7 Autres possibilités du type vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2162 - Les tableaux natifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216

2.1 Les tableaux natifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2162.1.1 Exemple d’utilisation d’un tableau natif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216

LIVRE.book Page XVI Jeudi, 12. septembre 2019 6:19 18

Page 21: Programmer en C++ moderne - fnac-static.com

Table des matières XVII

2.1.2 Quelques règles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2172.1.3 Parcours des éléments avec for pour les séquences (C++11) . . . . . . . . . . . . . . . . . . . 218

2.2 Les tableaux natifs à plusieurs indices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2192.2.1 Leur déclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2192.2.2 Arrangement en mémoire des tableaux à plusieurs indices . . . . . . . . . . . . . . . . . . . . . 2192.2.3 Parcours des éléments d’un tableau à plusieurs indices (C++11). . . . . . . . . . . . . . . . 220

2.3 Initialisation des tableaux natifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2202.3.1 Initialisation de tableaux natifs à un indice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2202.3.2 Initialisation de tableaux natifs à plusieurs indices . . . . . . . . . . . . . . . . . . . . . . . . . . . 2212.3.3 Initialiseurs et classe d’allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222

2.4 L’équivalence entre tableau natif et pointeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2222.4.1 Incrémentation de pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2222.4.2 Comparaison et soustraction de pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2232.4.3 Equivalence tableau à un indice et pointeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223

2.5 Équivalence tableau à plusieurs indices et pointeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2242.6 Transmission de tableaux natifs en argument. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226

2.6.1 Cas des tableaux à un indice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2262.6.2 Cas des tableaux à plusieurs indices. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228

2.7 Les tableaux dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2302.7.1 Avec des pointeurs natifs et les opérateurs new[] et delete[] . . . . . . . . . . . . . . . . . . . . 2302.7.2 Avec le type unique_ptr (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2312.7.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2312.7.4 Initialisation de tableaux dynamiques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232

3 - Les vecteurs multidimensionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2334 - Les chaînes de style C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234

4.1 Représentation des chaînes de style C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2344.2 Lecture et écriture de chaînes de style C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2354.3 Initialisation de tableaux natifs par des chaînes de style C . . . . . . . . . . . . . . . . . . . . . . . . . . 236

4.3.1 Initialisation de tableaux de caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2364.3.2 Initialisation de tableaux natifs de pointeurs natifs sur des chaînes . . . . . . . . . . . . . . 237

4.4 Les arguments transmis à la fonction main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2384.5 Généralités sur les fonctions traitant des chaînes de style C . . . . . . . . . . . . . . . . . . . . . . . . . 239

4.5.1 Ces fonctions travaillent toujours sur des adresses . . . . . . . . . . . . . . . . . . . . . . . . . . . 2394.5.2 La fonction strlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2404.5.3 Le cas des fonctions de concaténation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240

4.6 Quelques précautions à prendre avec les chaînes de style C . . . . . . . . . . . . . . . . . . . . . . . . . 2404.6.1 Une chaîne de style C possède une vraie fin, mais pas de vrai début. . . . . . . . . . . . . . 2404.6.2 Les risques de modification des chaînes constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . 241

Chapitre 12 : Classes et objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2431 - La notion de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244

1.1 Définition d’une classe point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2441.1.1 Déclaration de la classe point. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2441.1.2 Définition des fonctions membres de la classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245

1.2 Utilisation de notre classe point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246

LIVRE.book Page XVII Jeudi, 12. septembre 2019 6:19 18

Page 22: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXVIII

1.3 Exemple récapitulatif. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2471.4 La déclaration d’une classe d’une manière générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248

2 - Les structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2493 - Affectation d’objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2504 - Notions de constructeur et de destructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251

4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2514.2 Exemple de classe comportant un constructeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2524.3 Initialisation des membres dans l’en-tête du constructeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . 2544.4 Construction et destruction des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2564.5 Rôles du constructeur et du destructeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2574.6 Quelques règles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258

5 - Objets transmis en argument d’une fonction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2595.1 Cas de la transmission par valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2595.2 Cas de la transmission par référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2605.3 Cas de la transmission par pointeur : l’opérateur -> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261

6 - Les membres données statiques. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2626.1 Le qualificatif static pour un membre donnée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2626.2 Initialisation des membres données statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2636.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264

7 - Exploitation d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2657.1 La classe comme composant logiciel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2657.2 Protection contre les inclusions multiples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2677.3 Cas des membres données statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2677.4 Modification d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

7.4.1 Notion d’interface et d’implémentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2687.4.2 Modification d’une classe sans modification de son interface. . . . . . . . . . . . . . . . . . . . 2687.4.3 Modification d’une classe avec modification de son interface . . . . . . . . . . . . . . . . . . . 269

Chapitre 13 : Les propriétés des fonctions membres. . . . . . . . . . . . . . . . . . . 2711 - Surdéfinition des fonctions membres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2712 - Arguments par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2743 - Les fonctions membres en ligne. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2754 - Constructeurs délégués (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2775 - Cas des objets transmis en argument d’une fonction membre. . . . . . . . . . . . . . . . . . . . . . . . . 2776 - Mode de transmission des objets en argument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279

6.1 Transmission de l’adresse d’un objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2796.2 Transmission par référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2816.3 Les problèmes posés par la transmission par valeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281

7 - Lorsqu’une fonction renvoie un objet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2828 - Autoréférence : le mot-clé this. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2839 - Les fonctions membres statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284

LIVRE.book Page XVIII Jeudi, 12. septembre 2019 6:19 18

Page 23: Programmer en C++ moderne - fnac-static.com

Table des matières XIX

10 - Fonctions membres et objets constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28610.1 Définition d’une fonction membre constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28610.2 Propriétés d’une fonction membre constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287

11 - Les membres mutables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289

Chapitre 14 : Construction, destruction et initialisation des objets. . . 291

1 - Les objets automatiques et statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2921.1 Durée de vie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2921.2 Appel des constructeurs et des destructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2931.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294

2 - Les objets dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2952.1 Cas d’une classe sans constructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2962.2 Cas d’une classe avec constructeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297

2.2.1 Cas des pointeurs natifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2972.2.2 Avec les pointeurs intelligents (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2982.2.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298

3 - Le constructeur de recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2993.1 Il n’existe pas de constructeur approprié . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3003.2 Il existe un constructeur approprié . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3003.3 Comment interdire la construction par recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300

4 - Exemple de constructeur de recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3024.1 Comportement du constructeur de recopie par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302

4.1.1 Version pointeurs natifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3024.1.2 Version unique_ptr (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304

4.2 Définition d’un constructeur de recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3055 - Initialisation d’un objet lors de sa déclaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308

5.1 Cas d’un constructeur à un seul argument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3085.2 Cas d’un constructeur à plusieurs arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3105.3 Le mot-clé default pour un constructeur (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3115.4 Cas particulier des classes agrégats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3115.5 Constructeur avec initializer_list (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312

6 - Objets membres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3146.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3146.2 Mise en œuvre des constructeurs et des destructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3156.3 Le constructeur de recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317

7 - Les tableaux et vecteurs d’objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3187.1 Notations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3187.2 Constructeurs et initialiseurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3197.3 Cas des tableaux dynamiques d’objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320

8 - Les objets temporaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3219 - Dissocier l’allocation mémoire de la construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323

LIVRE.book Page XIX Jeudi, 12. septembre 2019 6:19 18

Page 24: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXX

Chapitre 15 : Les fonctions amies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325

1 - Exemple de fonction indépendante amie d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3262 - Les différentes situations d’amitié . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328

2.1 Fonction membre d’une classe, amie d’une autre classe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3292.2 Fonction amie de plusieurs classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3302.3 Toutes les fonctions d’une classe amies d’une autre classe . . . . . . . . . . . . . . . . . . . . . . . . . . 331

3 - Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3313.1 Fonction amie indépendante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3323.2 Fonction amie, membre d’une classe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333

4 - Exploitation de classes disposant de fonctions amies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334

Chapitre 16 : La surdéfinition d’opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335

1 - Le mécanisme de la surdéfinition d’opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3361.1 Surdéfinition d’opérateur avec une fonction amie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3371.2 Surdéfinition d’opérateur avec une fonction membre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3381.3 Opérateurs et transmission par référence. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340

2 - La surdéfinition d’opérateurs en général . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3412.1 Se limiter aux opérateurs existants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3412.2 Se placer dans un contexte de classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3432.3 Éviter les hypothèses sur le rôle d’un opérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3432.4 Cas des opérateurs ++ et -- . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3442.5 L’opérateur = possède une signification prédéfinie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3462.6 Les conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3462.7 Choix entre fonction membre et fonction amie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347

3 - Surdéfinition de l’opérateur = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3473.1 Rappels concernant le constructeur par recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3473.2 Cas de l’affectation par défaut. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3483.3 Algorithme proposé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3503.4 Valeur de retour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3513.5 En définitive. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3513.6 Exemple de programme complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3523.7 Lorsqu’on souhaite interdire l’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354

4 - La forme canonique d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3554.1 Cas général. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355

5 - Exemple de surdéfinition de l’opérateur [ ] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3566 - Surdéfinition de l’opérateur (). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3597 - Surdéfinition des opérateurs new et delete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360

7.1 Surdéfinition de new et delete pour une classe donnée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3607.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3617.3 D’une manière générale. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363

LIVRE.book Page XX Jeudi, 12. septembre 2019 6:19 18

Page 25: Programmer en C++ moderne - fnac-static.com

Table des matières XXI

Chapitre 17 : Les conversions de type définies par l’utilisateur . . . . . . 3651 - Les différentes sortes de conversions définies par l’utilisateur . . . . . . . . . . . . . . . . . . . . . . . . 3662 - L’opérateur de cast pour la conversion type classe –> type de base. . . . . . . . . . . . . . . . . . . . 368

2.1 Définition de l’opérateur de cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3682.2 Exemple d’utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3682.3 Appel implicite de l’opérateur de cast lors d’un appel de fonction . . . . . . . . . . . . . . . . . . . . 3702.4 Appel implicite de l’opérateur de cast dans l’évaluation d’une expression . . . . . . . . . . . . . . 3712.5 Conversions en chaîne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3732.6 En cas d’ambiguïté . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375

3 - Le constructeur pour la conversion type de base -> type classe . . . . . . . . . . . . . . . . . . . . . . . 3753.1 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3753.2 Le constructeur dans une chaîne de conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3773.3 Choix entre constructeur ou opérateur d’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3783.4 Emploi d’un constructeur pour élargir la signification d’un opérateur . . . . . . . . . . . . . . . . . 379

4 - Les conversions d’un type classe en un autre type classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3824.1 Exemple simple d’opérateur de cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3824.2 Exemple de conversion par un constructeur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3834.3 Pour donner une signification à un opérateur défini dans une autre classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385

5 - Quelques conseils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387

Chapitre 18 : Les patrons de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3891 - Exemple de création et d’utilisation d’un patron de fonctions . . . . . . . . . . . . . . . . . . . . . . . . 390

1.1 Création d’un patron de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3901.2 Premières utilisations du patron de fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3911.3 Autres utilisations du patron de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392

1.3.1 Application au type char * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3921.3.2 Application à un type classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393

1.4 Contraintes d’utilisation d’un patron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3942 - Les paramètres de type d’un patron de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395

2.1 Utilisation des paramètres de type dans la définition d’un patron . . . . . . . . . . . . . . . . . . . . . 3952.2 Identification des paramètres de type d’une fonction patron . . . . . . . . . . . . . . . . . . . . . . . . . 3962.3 Retour sur la syntaxe d’initialisation des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3972.4 Limitations des patrons de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3982.5 Le type initializer_list et les patrons de fonctions (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . 3992.6 Paramètres de type par défaut (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399

3 - Les paramètres expressions d’un patron de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4004 - Surdéfinition de patrons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401

4.1 Exemples ne comportant que des paramètres de type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4014.2 Exemples comportant des paramètres expressions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404

5 - Spécialisation de fonctions de patron. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405

LIVRE.book Page XXI Jeudi, 12. septembre 2019 6:19 18

Page 26: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXII

5.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4055.2 Les spécialisations partielles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406

6 - Algorithme d’instanciation d’une fonction patron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4067 - Patrons de fonctions à nombre variable de paramètres (C++11) . . . . . . . . . . . . . . . . . . . . . . 408

Chapitre 19 : Les patrons de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4111 - Exemple de création et d’utilisation d’un patron de classes. . . . . . . . . . . . . . . . . . . . . . . . . . . 412

1.1 Création d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4121.2 Utilisation d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4141.3 Contraintes d’utilisation d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4141.4 Exemple récapitulatif. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415

2 - Les paramètres de type d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4172.1 Les paramètres de type dans la création d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . 4172.2 Instanciation d’une classe patron. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417

3 - Les paramètres expressions d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4183.1 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4183.2 Les propriétés des paramètres expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420

4 - Spécialisation d’un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4214.1 Exemple de spécialisation d’une fonction membre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4214.2 Les différentes possibilités de spécialisation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422

4.2.1 On peut spécialiser une fonction membre pour tous les paramètres . . . . . . . . . . . . . . . 4224.2.2 On peut spécialiser une fonction membre ou une classe . . . . . . . . . . . . . . . . . . . . . . . . 4234.2.3 On peut prévoir des spécialisations partielles de patrons de classes . . . . . . . . . . . . . . 423

5 - Paramètres par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4236 - Patrons de fonctions membres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4247 - Identité de classes patrons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4248 - Classes patrons et déclarations d’amitié. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425

8.1 Déclaration de classes ou fonctions « ordinaires » amies . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4258.2 Déclaration d’instances particulières de classes patrons ou de fonctions patrons. . . . . . . . . . 4268.3 Déclaration d’un autre patron de fonctions ou de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4268.4 Déclaration d’amitié d’un paramètre de type (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427

9 - Compilation conditionnelle avec if constepr (C++17) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42710 - Patrons de classes à paramètres variables (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428

Chapitre 20 : L’héritage simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4291 - La notion d’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4302 - Utilisation des membres de la classe de base dans une classe dérivée . . . . . . . . . . . . . . . . . . . 4323 - Redéfinition des membres d’une classe dérivée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435

3.1 Redéfinition des fonctions membres d’une classe dérivée . . . . . . . . . . . . . . . . . . . . . . . . . . . 4353.2 Redéfinition des membres données d’une classe dérivée . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4363.3 Redéfinition et surdéfinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437

LIVRE.book Page XXII Jeudi, 12. septembre 2019 6:19 18

Page 27: Programmer en C++ moderne - fnac-static.com

Table des matières XXIII

4 - Appel des constructeurs et des destructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4394.1 Rappels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4394.2 La hiérarchisation des appels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4394.3 Transmission d’informations entre constructeurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4404.4 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4414.5 Compléments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 442

5 - Contrôle des accès . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4435.1 Les membres protégés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4435.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4445.3 Intérêt du statut protégé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4455.4 Dérivation publique et dérivation privée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445

5.4.1 Rappels concernant la dérivation publique. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4455.4.2 Dérivation privée. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4465.4.3 Les possibilités de dérivation protégée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447

5.5 Récapitulation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4486 - Compatibilité entre classe de base et classe dérivée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449

6.1 Conversion implicite de pointeurs natifs et typage statique . . . . . . . . . . . . . . . . . . . . . . . . . . 4506.2 Conversions de références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4536.3 Cas des pointeurs intelligents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4546.4 Conversion entre objet et objet dérivé . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4546.5 Les risques de violation des protections de la classe de base . . . . . . . . . . . . . . . . . . . . . . . . . 455

7 - Le constructeur de recopie et l’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4557.1 La classe dérivée ne définit pas de constructeur de recopie . . . . . . . . . . . . . . . . . . . . . . . . . . 4567.2 La classe dérivée définit un constructeur de recopie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457

8 - L’opérateur d’affectation et l’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4588.1 La classe dérivée ne surdéfinit pas l’opérateur = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4598.2 La classe dérivée surdéfinit l’opérateur =. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459

9 - Héritage et formes canoniques d’une classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46210 - L’héritage et ses limites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463

10.1 La situation d’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46410.1.1 Le type du résultat de l’appel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46410.1.2 Le type des arguments de f . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464

10.2 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46510.2.1 Héritage dans pointcol d’un opérateur + défini dans point . . . . . . . . . . . . . . . . . . . . 46510.2.2 Héritage dans pointcol de la fonction coincide de point. . . . . . . . . . . . . . . . . . . . . . . 466

11 - Patrons de classes et héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46711.1 Classe « ordinaire » dérivant d’une classe patron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46711.2 Dérivation de patrons avec les mêmes paramètres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46811.3 Dérivation de patrons avec introduction d’un nouveau paramètre. . . . . . . . . . . . . . . . . . . . 469

12 - L’héritage en pratique. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47012.1 Dérivations successives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47012.2 Différentes utilisations de l’héritage. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47212.3 Exploitation d’une classe dérivée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472

LIVRE.book Page XXIII Jeudi, 12. septembre 2019 6:19 18

Page 28: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXIV

Chapitre 21 : L’héritage multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4751 - Mise en œuvre de l’héritage multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4762 - Pour régler les éventuels conflits : les classes virtuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4803 - Appels des constructeurs et des destructeurs : cas des classes virtuelles . . . . . . . . . . . . . . . . 4814 - Exemple d’utilisation de l’héritage multiple et de la dérivation virtuelle . . . . . . . . . . . . . . . . 484

Chapitre 22 : Les fonctions virtuelles et le polymorphisme . . . . . . . . . . . . 4871 - Rappel d’une situation où le typage dynamique est nécessaire . . . . . . . . . . . . . . . . . . . . . . . . 4882 - Le mécanisme des fonctions virtuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4883 - Autre situation où la ligature dynamique est indispensable . . . . . . . . . . . . . . . . . . . . . . . . . . . 4904 - Polymorphisme, pointeurs et références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493

4.1 Polymorphisme et pointeurs intelligents . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4934.2 Polymorphisme et références. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494

5 - Les propriétés des fonctions virtuelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4955.1 Leurs limitations sont celles de l’héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4955.2 La redéfinition d’une fonction virtuelle n’est pas obligatoire . . . . . . . . . . . . . . . . . . . . . . . . . 4965.3 Fonctions virtuelles et surdéfinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497

5.3.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4975.3.2 Contrôle des surdéfinitions de fonctions virtuelles (C++11) : override . . . . . . . . . . . . 4975.3.3 Interdiction de redéfinition d’une fonction virtuelle (C++11) : final . . . . . . . . . . . . . . 497

5.4 Le type de retour d’une fonction virtuelle redéfinie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4985.5 On peut déclarer une fonction virtuelle dans n’importe quelle classe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4995.6 Quelques restrictions et conseils . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499

5.6.1 Seule une fonction membre peut être virtuelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4995.6.2 Un constructeur ne peut pas être virtuel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5005.6.3 Un destructeur peut être virtuel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5005.6.4 Cas particulier de l’opérateur d’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501

6 - Les fonctions virtuelles pures pour la création de classes abstraites . . . . . . . . . . . . . . . . . . . . 5027 - Exemple d’utilisation de fonctions virtuelles : liste hétérogène . . . . . . . . . . . . . . . . . . . . . . . . 5048 - Table des fonctions virtuelles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5089 - Identification de type à l’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510

9.1 Utilisation du champ name de type_info . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5109.2 Utilisation des opérateurs de comparaison de type_info . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5129.3 Exemple avec des références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513

10 - Les cast dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 513

Chapitre 23 : Optimisation par déplacement (C++11) . . . . . . . . . . . . . . . . . . . 5171 - La référence à une rvalue. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 518

1.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5181.2 Nouvelles règles de surdéfinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 519

LIVRE.book Page XXIV Jeudi, 12. septembre 2019 6:19 18

Page 29: Programmer en C++ moderne - fnac-static.com

Table des matières XXV

1.3 Conversion d’une lvalue en une rvalue : fonction move . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5211.4 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522

2 - Application à la construction et à l’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5223 - Exemple d’écriture d’opérations de déplacement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 524

3.1 Le constructeur de déplacement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5243.2 L’opérateur d’affectation par déplacement. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5263.3 Exemple complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 527

4 - Utilisation du déplacement par défaut. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5295 - Sémantique de déplacement et héritage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5306 - Références rvalue et patrons de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531

6.1 Référence « universelle » dans un patron de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5316.2 Référence universelle et surdéfinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5326.3 Quand && dans un patron ne désigne plus une référence universelle . . . . . . . . . . . . . . . . . . 5326.4 La fonction forward . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5336.5 En cas de spécialisation de patrons. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535

7 - Retour sur le type unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536

Chapitre 24 : Les flots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5371 - Présentation générale de la classe ostream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 539

1.1 L’opérateur << . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5391.2 Les flots prédéfinis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5401.3 Quelques possibilités de formatage avec << . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 540

1.3.1 Action sur la base de numération . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5411.3.2 Action sur le gabarit de l’information écrite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5421.3.3 Action sur la précision de l’information écrite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5431.3.4 Choix entre notation flottante ou exponentielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5441.3.5 Un programme de facturation amélioré . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545

1.4 Les opérations non formatées de la classe ostream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5461.5 La fonction put . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5461.6 La fonction write . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547

2 - Présentation générale de la classe istream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5472.1 L’opérateur >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 547

2.1.1 Les types acceptés par >> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5482.2 Les fonctions membres de la classe istream. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5482.3 La fonction get . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5492.4 La fonction read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550

2.4.1 Cas des caractères. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5502.4.2 Autres cas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550

2.5 Les fonctions getline et gcount . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5502.6 Quelques autres fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 552

3 - Statut d’erreur d’un flot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5523.1 Les bits d’erreur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5523.2 Actions concernant les bits d’erreur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553

LIVRE.book Page XXV Jeudi, 12. septembre 2019 6:19 18

Page 30: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXVI

3.2.1 Accès aux bits d’erreur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5533.2.2 Modification du statut d’erreur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 553

3.3 Surdéfinition des opérateurs () et ! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5543.4 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554

4 - Surdéfinition de << et >> pour les types définis par l’utilisateur . . . . . . . . . . . . . . . . . . . . . . 5564.1 Méthode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5564.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 558

5 - Gestion du formatage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5605.1 Le statut de formatage d’un flot. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5605.2 Description du mot d’état du statut de formatage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5615.3 Action sur le statut de formatage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562

5.3.1 Les manipulateurs non paramétriques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5625.3.2 Les manipulateurs paramétriques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5635.3.3 Les fonctions membres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5645.3.4 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566

6 - Connexion d’un flot à un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5666.1 Connexion d’un flot de sortie à un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5676.2 Connexion d’un flot d’entrée à un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5686.3 Les possibilités d’accès direct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5696.4 Les différents modes d’ouverture d’un fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571

7 - Les anciennes possibilités de formatage en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5727.1 La classe ostrstream. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5737.2 La classe istrstream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 574

Chapitre 25 : La gestion des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5771 - Premier exemple d’exception. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578

1.1 Comment lancer une exception : l’instruction throw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5791.2 Utilisation d’un gestionnaire d’exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5791.3 Récapitulatif. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580

2 - Second exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5823 - Le mécanisme de gestion des exceptions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 584

3.1 Poursuite de l’exécution du programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5843.2 Prise en compte des sorties de blocs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 586

4 - Choix du gestionnaire. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5864.1 Le gestionnaire reçoit toujours une copie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5874.2 Règles de choix d’un gestionnaire d’exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5874.3 Le cheminement des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5884.4 Redéclenchement d’une exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 590

5 - Spécification des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5915.1 Spécification d’avant C++11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5915.2 Spécifications avec C++11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5925.3 Exemples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 592

6 - Les exceptions standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 594

LIVRE.book Page XXVI Jeudi, 12. septembre 2019 6:19 18

Page 31: Programmer en C++ moderne - fnac-static.com

Table des matières XXVII

6.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5946.2 Les exceptions déclenchées par la bibliothèque standard . . . . . . . . . . . . . . . . . . . . . . . . . . . 5956.3 Les exceptions utilisables dans un programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5956.4 Cas particulier de la gestion dynamique de mémoire. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 596

6.4.1 L’exception bad_alloc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5966.4.2 L’opérateur new (nothrow) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5976.4.3 Utilisation de set_new_handler. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 598

6.5 Création d’exceptions dérivées de la classe exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5996.5.1 Exemple 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5996.5.2 Exemple 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 600

7 - Exceptions et gestion de ressources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6017.1 Les problèmes posés par les objets dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6017.2 Une solution utilisant les pointeurs intelligents (C++11). . . . . . . . . . . . . . . . . . . . . . . . . . . . 6027.3 La technique de gestion de ressources par initialisation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 603

Chapitre 26 : Généralités sur la bibliothèque standard . . . . . . . . . . . . . . . . 6051 - Notions de conteneur, d’itérateur et d’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605

1.1 Notion de conteneur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6061.2 Notion d’itérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6061.3 Parcours d’un conteneur avec un itérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607

1.3.1 Parcours direct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6071.3.2 Parcours inverse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6081.3.3 Itérateurs constants : const_iterator et const_reverse_iterator . . . . . . . . . . . . . . . . . . 6081.3.4 La déclaration auto et les itérateurs (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608

1.4 Intervalle d’itérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6091.5 Notion d’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6091.6 Itérateurs et pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 610

2 - Les différentes sortes de conteneurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6112.1 Conteneurs et structures de données classiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6112.2 Les différentes catégories de conteneurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 611

3 - Les conteneurs dont les éléments sont des objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6123.1 Construction, copie et affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6123.2 Autres opérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 614

4 - Efficacité des opérations sur des conteneurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6145 - Fonctions, prédicats et classes fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615

5.1 Fonction unaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6155.2 Prédicats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6155.3 Classes et objets fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 616

5.3.1 Utilisation d’objet fonction comme fonction de rappel. . . . . . . . . . . . . . . . . . . . . . . . . 6165.3.2 Classes fonctions prédéfinies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6175.3.3 La classe function (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 618

6 - Conteneurs, algorithmes et relation d’ordre. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6196.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6196.2 Propriétés à respecter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 620

LIVRE.book Page XXVII Jeudi, 12. septembre 2019 6:19 18

Page 32: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXVIII

Chapitre 27 : Les conteneurs séquentiels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6211 - Fonctionnalités communes aux conteneurs vector, list et deque . . . . . . . . . . . . . . . . . . . . . . . 622

1.1 Construction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6221.1.1 Construction d’un conteneur vide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6221.1.2 Construction avec un nombre donné d’éléments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6221.1.3 Construction avec un nombre donné d’éléments de valeur donnée . . . . . . . . . . . . . . . . 6231.1.4 Construction à partir d’une séquence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6231.1.5 Construction à partir d’un autre conteneur de même type . . . . . . . . . . . . . . . . . . . . . . 6241.1.6 Construction à partir d’une liste de valeurs (C++11). . . . . . . . . . . . . . . . . . . . . . . . . . 624

1.2 Modifications globales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6241.2.1 Opérateur d’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6251.2.2 La fonction membre assign. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6251.2.3 La fonction clear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6261.2.4 La fonction swap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626

1.3 Comparaison de conteneurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6261.3.1 L’opérateur == . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6261.3.2 L’opérateur < . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6271.3.3 Exemples avec un vector<int>. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6271.3.4 Exemple avec un vector<point> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627

1.4 Insertion ou suppression d’éléments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6291.4.1 Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6291.4.2 Suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6301.4.3 Cas des insertions/suppressions en fin : pop_back et push_back . . . . . . . . . . . . . . . . . 630

2 - Le conteneur vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6312.1 Accès aux éléments existants. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 631

2.1.1 Accès par itérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6312.1.2 Accès par indice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6322.1.3 Cas de l’accès au dernier élément . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 632

2.2 Insertions et suppressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6322.3 Gestion de l’emplacement mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633

2.3.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6332.3.2 Invalidation d’itérateurs ou de références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6332.3.3 Outils de gestion de l’emplacement mémoire d’un vecteur . . . . . . . . . . . . . . . . . . . . . . 633

2.4 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6342.5 Cas particulier des vecteurs de booléens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 636

3 - Le conteneur deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6363.1 Présentation générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6363.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637

4 - Le conteneur list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6384.1 Accès aux éléments existants. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6384.2 Insertions et suppressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639

4.2.1 Suppression des éléments de valeur donnée. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6394.2.2 Suppression des éléments répondant à une condition . . . . . . . . . . . . . . . . . . . . . . . . . . 639

4.3 Opérations globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 640

LIVRE.book Page XXVIII Jeudi, 12. septembre 2019 6:19 18

Page 33: Programmer en C++ moderne - fnac-static.com

Table des matières XXIX

4.3.1 Tri d’une liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6404.3.2 Suppression des éléments en double . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6404.3.3 Fusion de deux listes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6414.3.4 Transfert d’une partie de liste dans une autre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 642

4.4 Gestion de l’emplacement mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6424.5 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643

5 - Les adaptateurs de conteneur : queue, stack et priority_queue . . . . . . . . . . . . . . . . . . . . . . . 6445.1 L’adaptateur stack. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6445.2 L’adaptateur queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6455.3 L’adaptateur priority_queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 646

6 - Le type array (C++11). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 647

Chapitre 28 : Les conteneurs associatifs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6491 - Le conteneur map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 650

1.1 Exemple introductif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6501.2 Le patron de classes pair. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6521.3 Construction d’un conteneur de type map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 653

1.3.1 Constructions utilisant la relation d’ordre par défaut . . . . . . . . . . . . . . . . . . . . . . . . . 6531.3.2 Choix de l’ordre intrinsèque du conteneur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6541.3.3 Pour connaître la relation d’ordre utilisée par un conteneur. . . . . . . . . . . . . . . . . . . . 6541.3.4 Conséquences du choix de l’ordre d’un conteneur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 655

1.4 Accès aux éléments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6561.4.1 Accès par l’opérateur [ ]. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6561.4.2 Accès par itérateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6561.4.3 Recherche par la fonction membre find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 657

1.5 Insertions et suppressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6571.5.1 Insertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6571.5.2 Suppressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 658

1.6 Gestion mémoire. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6591.7 Autres possibilités. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6591.8 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 660

2 - Le conteneur multimap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6612.1 Présentation générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6612.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 662

3 - Le conteneur set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6643.1 Présentation générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6643.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6643.3 Le conteneur set et l’ensemble mathématique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 665

4 - Le conteneur multiset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6655 - Le type tuple (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6676 - Les tables de hachage (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6677 - Conteneurs associatifs et algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667

LIVRE.book Page XXIX Jeudi, 12. septembre 2019 6:19 18

Page 34: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXX

Chapitre 29 : Les algorithmes standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6691 - Notions générales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669

1.1 Algorithmes et itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6691.2 Les catégories d’itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 670

1.2.1 Itérateur en entrée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6701.2.2 Itérateur en sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6701.2.3 Hiérarchie des catégories d’itérateurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 671

1.3 Algorithmes et séquences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6711.4 Itérateur d’insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6721.5 Itérateur de flot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 674

1.5.1 Itérateur de flot de sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6741.5.2 Itérateur de flot d’entrée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 675

2 - Algorithmes d’initialisation de séquences existantes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6752.1 Copie d’une séquence dans une autre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6762.2 Génération de valeurs par une fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677

3 - Algorithmes de recherche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6793.1 Algorithmes fondés sur une égalité ou un prédicat unaire . . . . . . . . . . . . . . . . . . . . . . . . . . . 6793.2 Algorithmes de recherche de maximum ou de minimum . . . . . . . . . . . . . . . . . . . . . . . . . . . . 681

4 - Algorithmes de transformation d’une séquence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6824.1 Remplacement de valeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6824.2 Permutations de valeurs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 682

4.2.1 Rotation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6824.2.2 Génération de permutations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6834.2.3 Permutations aléatoires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685

4.3 Partitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6865 - Algorithmes dits « de suppression » . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6866 - Algorithmes de tri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6887 - Algorithmes de recherche et de fusion sur des séquences ordonnées . . . . . . . . . . . . . . . . . . . 689

7.1 Algorithmes de recherche binaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6907.2 Algorithmes de fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 690

8 - Algorithmes à caractère numérique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6919 - Algorithmes à caractère ensembliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69310 - Algorithmes de manipulation de tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 694

Chapitre 30 : La classe string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6991 - Généralités. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7002 - Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7003 - Opérations globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7024 - Concaténation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7035 - Recherche dans une chaîne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704

5.1 Recherche d’une chaîne ou d’un caractère . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704

LIVRE.book Page XXX Jeudi, 12. septembre 2019 6:19 18

Page 35: Programmer en C++ moderne - fnac-static.com

Table des matières XXXI

5.2 Recherche d’un caractère présent ou absent d’une suite . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7056 - Insertions, suppressions et remplacements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705

6.1 Insertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7056.2 Suppressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7066.3 Remplacements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707

7 - Les possibilités de formatage en mémoire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7087.1 La classe ostringstream. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7087.2 La classe istringstream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709

7.2.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7097.2.2 Utilisation pour fiabiliser les lectures au clavier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710

Chapitre 31 : Les outils numériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7131 - La classe complex. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7132 - La classe valarray et les classes associées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715

2.1 Constructeurs des classes valarray . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7152.2 L’opérateur [] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7162.3 Affectation et changement de taille . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7162.4 Calcul vectoriel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7172.5 Sélection de valeurs par masque. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7182.6 Sections de vecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7192.7 Vecteurs d’indices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 721

3 - La classe bitset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722

Chapitre 32 : Les espaces de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7251 - Création d’espaces de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725

1.1 Exemple de création d’un nouvel espace de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7261.2 Exemple avec deux espaces de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7271.3 Espace de noms et fichier en-tête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7281.4 Instructions figurant dans un espace de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7281.5 Création incrémentale d’espaces de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 729

2 - Les instructions using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7302.1 La déclaration using pour les symboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 730

2.1.1 Présentation générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7302.1.2 Masquage et ambiguïtés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 732

2.2 La directive using pour les espaces de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7333 - Espaces de noms et recherche de fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7354 - Imbrication des espaces de noms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7375 - Transitivité de la directive using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7386 - Les alias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7387 - Les espaces anonymes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7398 - Espaces de noms et déclaration d’amitié . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 739

LIVRE.book Page XXXI Jeudi, 12. septembre 2019 6:19 18

Page 36: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXXII

Chapitre 33 : Le préprocesseur et les instructions typedef et using . . 741

1 - La directive #include . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7422 - La directive #define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 742

2.1 Définition de symboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7422.2 Définition de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 744

3 - La compilation conditionnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7473.1 Incorporation liée à l’existence de symboles. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7473.2 Incorporation liée à la valeur d’une expression. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7483.3 Compilation conditionnelle avec if constepr (C++17) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 749

4 - La définition de synonymes avec typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7504.1 Définition d’un synonyme de int . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7514.2 Définition d’un synonyme de int * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7514.3 Définition d’un synonyme de int[3] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7524.4 Synonymes et patrons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7534.5 Synonymes et fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 754

5 - Définition de synonymes de types avec using (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 754

Chapitre 34 : Énumérations, champs de bits et unions. . . . . . . . . . . . . . . . . 755

1 - Les énumérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7551.1 Exemples introductifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756

1.1.1 C++98 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7561.1.2 C++11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756

1.2 Propriétés du type énumération . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7572 - Les champs de bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7583 - Les unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 759

Chapitre 35 : Introduction aux threads (C++11) . . . . . . . . . . . . . . . . . . . . . . . . . 761

1 - Thread depuis une fonction ou un objet fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7621.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7621.2 Transmission d’arguments à un thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7631.3 Mise en sommeil d’un thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 764

2 - Thread depuis une fonction membre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7653 - Threads et exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7654 - Partage de données entre threads et verrous mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7675 - Prise en compte des exceptions avec verrou lock_guard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7696 - Les variables atomic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7707 - Transfert d’informations en retour d’un thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771

7.1 Utilisation de la fonction async . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7717.2 Démarche plus générale. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 771

LIVRE.book Page XXXII Jeudi, 12. septembre 2019 6:19 18

Page 37: Programmer en C++ moderne - fnac-static.com

Table des matières XXXIII

Annexe A : Règles de recherche d’une fonction surdéfinie. . . . . . . . . . . 7751 - Détermination des fonctions candidates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7752 - Algorithme de recherche d’une fonction à un seul argument . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 776

2.1 Recherche d’une correspondance exacte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7762.2 Promotions numériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7772.3 Conversions standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7772.4 Conversions définies par l’utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7782.5 Fonctions à arguments variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7782.6 Exception : cas des champs de bits. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 778

3 - Fonctions à plusieurs arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7794 - Fonctions membres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 779

Annexe B : Les différentes sortes de fonctions en C++. . . . . . . . . . . . . . . . 781

Annexe C : Les pointeurs sur des membres . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7831 - Les pointeurs sur des fonctions membres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7832 - Les pointeurs sur des membres données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7843 - L’héritage et les pointeurs sur des membres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785

Annexe D : Les algorithmes standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7871 - Algorithmes d’initialisation de séquences existantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7882 - Algorithmes de recherche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7893 - Algorithmes de transformation d’une séquence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7934 - Algorithmes de suppression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7965 - Algorithmes de tri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7976 - Algorithmes de recherche et de fusion sur des séquences ordonnées . . . . . . . . . . . . . . . . . . . 7997 - Algorithmes à caractère numérique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8018 - Algorithmes à caractère ensembliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8039 - Algorithmes de manipulation de tas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80510 - Algorithmes divers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806

Annexe E : Les incompatibilités entre C et C++ . . . . . . . . . . . . . . . . . . . . . . . . 8091 - Emplacement des déclarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8092 - Prototypes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8093 - Fonctions sans arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8094 - Fonctions sans valeur de retour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8105 - Le qualificatif const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8106 - Les pointeurs de type void * . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 810

LIVRE.book Page XXXIII Jeudi, 12. septembre 2019 6:19 18

Page 38: Programmer en C++ moderne - fnac-static.com

C++ pour programmeurs CXXXIV

7 - Mots-clés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8108 - Les constantes de type caractère . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8119 - Les définitions multiples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81110 - L’instruction goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81211 - Les énumérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81212 - Initialisation de tableaux de caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81213 - Les noms de fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81314 - Tableaux de dimension variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813

Annexe F : C++20 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8151 - Les concepts et les contraintes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 815

1.1 Premier exemple de concept et de contrainte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8161.2 Utilisation de plusieurs contraintes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8181.3 Exemple d’application à un patron de classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8191.4 La bibliothèque type_traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8191.5 D’une manière générale. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 821

1.5.1 Les différentes sortes de contraintes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8211.5.2 Les différentes façons d’utiliser les contraintes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8211.5.3 Concepts variables et concepts fonctions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823

1.6 Concepts standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8232 - Les modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 824

2.1 Premier exemple de module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8242.2 Séparation interface et définition. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8252.3 Partitions de modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 826

2.3.1 Premier exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8262.3.2 Second exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827

2.4 Types de modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8272.5 Eléments de compatibilité avec les versions antérieures. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827

3 - Les coroutines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8284 - Choses diverses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 828

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 831

LIVRE.book Page XXXIV Jeudi, 12. septembre 2019 6:19 18

Page 39: Programmer en C++ moderne - fnac-static.com

7Les fonctions

Dès qu’un programme dépasse quelques pages de texte, il est pratique de pouvoir le décom-poser en des parties relativement indépendantes dont on pourra comprendre facilement lerôle, sans avoir à examiner l’ensemble du code.La programmation procédurale permet un premier pas dans ce sens, grâce à la notion defonction que nous allons aborder dans ce chapitre : il s’agit d’un bloc d’instructions qu’onpeut utiliser à loisir dans un programme en citant son nom et, éventuellement, en lui fournis-sant des « arguments » (on dit aussi « paramètres »).La P.O.O. constituera une seconde étape dans ce processus de décomposition. Chaque classe,définie de façon indépendante, associera des données et des méthodes ; nous verrons que cesméthodes seront rédigées de façon comparable à des fonctions, de sorte que nous serons alorsamenés à utiliser l’essentiel de ce que nous aurons étudié ici.On notera qu’en C++ (comme en C ou en Java), la fonction possède un rôle plus général quela « fonction mathématique ». En effet, une fonction mathématique :• possède des arguments dont on fournit la valeur lors de l’appel (par exemple, x dans sqrt(x)

ou 5.2 dans sqrt(5.2) ;• fournit un résultat (scalaire) désigné simplement par son appel : sqrt(x) désigne le résultat

fourni par la fonction ; on peut l’utiliser directement dans une expression arithmétique com-me y + 2*sqrt(x).

Or, en C++, si une fonction peut effectivement, comme sqrt, jouer le rôle d’une fonctionmathématique, elle pourra aussi :• modifier les valeurs de certains des arguments qu’on lui a transmis ;

LIVRE.book Page 103 Jeudi, 12. septembre 2019 6:19 18

Page 40: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7104

• réaliser une action (autre qu’un simple calcul), par exemple : lire des valeurs, afficher desvaleurs, ouvrir un fichier, établir une connexion...

• fournir un résultat d’un type non scalaire (structures, objets...) ;• fournir une valeur qu’on n’utilisera pas ;• ne pas fournir de valeur du tout.Nous commencerons par vous présenter la notion de fonction sur un exemple, et nous donne-rons quelques règles générales concernant l’écriture des fonctions, leur utilisation et leurdéclaration. Nous verrons ensuite que, par défaut, les arguments sont transmis par valeur etnous apprendrons à demander explicitement une transmission par référence. Nous parleronssuccinctement des variables globales, surtout pour en déconseiller l’utilisation. Nous feronsensuite le point sur la classe d’allocation et l’initialisation des variables locales. Nous appren-drons à définir des valeurs par défaut pour certains arguments d’une fonction. Puis nous étu-dierons l’importante notion de surdéfinition qui permet de définir plusieurs fonctions demême nom, mais ayant des arguments différents. Nous verrons également comment réaliserdes fonctions dites « à arguments variables », ce qui nous amènera au passage à parler dutype initializer_list introduit par C++11. Nous donnerons ensuite quelques éléments concer-nant les possibilités de compilation séparée de C++. Enfin, nous verrons comment définir des« fonctions en ligne ».

1 Exemple de définition et d’utilisation d’une fonction

Pour vous montrer comment définir et utiliser une fonction en C++, nous commencerons parun exemple simple correspondant en fait à une fonction mathématique, c’est-à-dire recevantdes arguments et fournissant une valeur.

//ExempleFonction

#include <iostream>

using namespace std ;

float fexple (float, int, int) ; // declaration de fonction fexple

/***** le programme principal (fonction main) ******/

int main ()

{ float x = 1.5 ;

int n = 3, p = 5, q = 10 ;

/* appel de fexple avec les arguments x, n et p */

float y = fexple (x, n, p) ; // ou (C++11) : auto y = fexple (x, n, p) ;

cout << "valeur de y : " << y << endl ;

/* appel de fexple avec les arguments x+0.5, q et n-1 */

float z = fexple (x+0.5, q, n-1) ; // ou (C++11) : auto z = fexple (x+0.5, q, n-1) ;

cout << "valeur de z : " << z << endl ;

}

LIVRE.book Page 104 Jeudi, 12. septembre 2019 6:19 18

Page 41: Programmer en C++ moderne - fnac-static.com

1 - Exemple de définition et d’utilisation d’une fonction 105

/*************** la fonction fexple ****************/float fexple (float x, int b, int c){ float val ; // declaration d’une variable "locale" à fexple val = x * x + b * x + c ; return val ;}

valeur de y : 11.75valeur de z : 26

Exemple de définition et d’utilisation d’une fonction

Nous y trouvons tout d’abord, de façon désormais classique, un programme principal forméd’un bloc. Mais, cette fois, à sa suite, apparaît la définition d’une fonction. Celle-ci possèdeune structure voisine de la fonction main, à savoir un en-tête et un corps délimité par desaccolades ({ et }). Mais l’en-tête est plus élaboré que celui de la fonction main. On y trouve lenom de la fonction (fexple), une liste d’arguments (nom + type), ainsi que le type de la valeurqui sera fournie par la fonction (on la nomme indifféremment « résultat », « valeur de lafonction », « valeur de retour »...) :

float fexple (float x, int b, int c) | | | | |type de la nom de la premier deuxième troisième"valeur fonction argument argument argumentde retour" (type float) (type int) (type int)

Les noms des arguments n’ont d’importance qu’au sein du corps de la fonction. Ils servent àdécrire le travail que devra effectuer la fonction quand on l’appellera en lui fournissant troisvaleurs.Si on s’intéresse au corps de la fonction, on y rencontre tout d’abord une déclaration :

float val ;

Celle-ci précise que, pour effectuer son travail, notre fonction a besoin d’une variable de typefloat nommée val. On dit que val est une variable locale à la fonction fexple, de même que lesvariables telles que n, p, y... sont des variables locales à la fonction main (mais commejusqu’ici nous avions affaire à un programme constitué d’une seule fonction, cette distinctionn’était pas utile). Un peu plus loin, nous examinerons plus en détail cette notion de variablelocale et celle de portée qui s’y attache.L’instruction suivante de notre fonction fexple est une affectation classique (faisant toutefoisintervenir les valeurs des arguments x, n et p).Enfin, l’instruction return val précise la valeur que fournira la fonction à la fin de son travail.En définitive, on peut dire que fexple est une fonction telle que fexple (x, b, c) fournit la valeurde l’expression x2 + bx + c. Notez bien l’aspect arbitraire du nom des arguments ; on obtien-drait la même définition de fonction avec, par exemple :

LIVRE.book Page 105 Jeudi, 12. septembre 2019 6:19 18

Page 42: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7106

float fexple (float z, int coef, int n){ float val ; // declaration d’une variable "locale" à fexple val = z * z + coef * z + n ; return val ;}

Notez qu’avant la fonction main, on trouve une déclaration : float fexple (float, int, int) ;

Elle sert à prévenir le compilateur que fexple est une fonction, et elle lui précise le type de sesarguments ainsi que celui de sa valeur de retour. Nous reviendrons plus loin en détail sur lerôle d’une telle déclaration, ainsi que sur les endroits où elle peut figurer.Quant à l’utilisation de notre fonction fexple au sein de la fonction main, elle est classique etcomparable à celle d’une fonction prédéfinie telle que sqrt. Ici, nous nous sommes contentésd’appeler notre fonction à deux reprises avec des arguments différents.

2 Quelques règles

2.1 Arguments muets et arguments effectifsLes noms des arguments figurant dans l’en-tête de la fonction se nomment des « argumentsmuets », ou encore « arguments formels » ou « paramètres formels » (de l’anglais : formalparameter). Leur rôle est de permettre, au sein du corps de la fonction, de décrire ce qu’elledoit faire.Les arguments fournis lors de l’utilisation (l’appel) de la fonction se nomment des« arguments effectifs » (ou encore « paramètres effectifs »). Comme le laisse deviner l’exem-ple précédent, on peut utiliser n’importe quelle expression comme argument effectif ; au boutdu compte, c’est la valeur de cette expression qui sera transmise à la fonction lors de sonappel. Notez qu’une telle « liberté » n’aurait aucun sens dans le cas des paramètres formels :il serait impossible d’écrire un en-tête de fexple sous la forme float fexple (float, a+b, ...), pasplus qu’en mathématiques vous ne définiriez une fonction f par f (x + y) = 5 !

2.2 L’instruction returnVoici quelques règles générales concernant cette instruction.• L’instruction return peut mentionner n’importe quelle expression. Ainsi, nous aurions pu

définir la fonction fexple précédente de cette manière : float fexple (float x, int b, int c) { return (x * x + b * x + c) ; }

• L’instruction return peut apparaître à plusieurs reprises dans une fonction, comme dans cetautre exemple :

LIVRE.book Page 106 Jeudi, 12. septembre 2019 6:19 18

Page 43: Programmer en C++ moderne - fnac-static.com

2 - Quelques règles 107

double absom (double u, double v)

{ double s ;

s = a + b ;

if (s>0) return (s) ;

else return (-s)

}

Notez bien que non seulement l’instruction return définit la valeur du résultat, mais, enmême temps, elle interrompt l’exécution de la fonction en revenant dans la fonction qui l’aappelée (en l’occurrence, ici, la fonction main). Nous verrons qu’une fonction peut nefournir aucune valeur : elle peut alors disposer de une ou plusieurs instructions return sansexpression, interrompant simplement l’exécution de la fonction ; mais elle peut aussi, dansce cas, ne comporter aucune instruction return, le retour étant alors mis en place automati-quement par le compilateur à la fin de la fonction.

• Si le type de l’expression figurant dans return est différent du type du résultat tel qu’il a étédéclaré dans l’en-tête, le compilateur mettra automatiquement en place des instructions deconversion : les conversions légales sont celles qui sont autorisées par affectation (avec lesmêmes risques de conversions dégradantes, par exemple de double en int).

Il est toujours possible de ne pas utiliser le résultat d’une fonction, même si elle en produitun. Bien entendu, cela n’a d’intérêt que si la fonction fait autre chose que calculer un résul-tat. En revanche, il est interdit d’utiliser la valeur d’une fonction ne fournissant pas de ré-sultat (si certains compilateurs l’acceptent, vous obtiendrez, lors de l’exécution, une valeuraléatoire !).

2.3 Cas des fonctions sans valeur de retour ou sans argumentsQuand une fonction ne renvoie pas de résultat, on le précise, à la fois dans l’en-tête et dans sadéclaration, à l’aide du mot-clé void. Par exemple, voici l’en-tête d’une fonction recevant unargument de type int et ne fournissant aucune valeur :

void sansval (int n)

et voici quelle serait sa déclaration :void sansval (int) ;

Naturellement, la définition d’une telle fonction ne doit, en principe, contenir aucune instruc-tion return. Certains compilateurs ne détecteront toutefois pas l’erreur.Quand une fonction ne reçoit aucun argument, on se contentera de ne rien mentionner dans laliste d’arguments. Voici l’en-tête d’une fonction ne recevant aucun argument et renvoyantune valeur de type float (il pourrait s’agir, par exemple, d’une fonction fournissant un nombrealéatoire !) :

float tirage ()

Sa déclaration serait très voisine (elle ne diffère que par la présence du point-virgule !) :float tirage () ;

LIVRE.book Page 107 Jeudi, 12. septembre 2019 6:19 18

Page 44: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7108

Enfin, rien n’empêche de réaliser une fonction ne possédant ni argument ni valeur de retour.Dans ce cas, son en-tête sera de la forme :

void message ()

et sa déclaration sera :void message () ;

Voici un exemple illustrant deux des situations évoquées. Nous y définissons une fonctionaffiche_carres qui affiche les carrés des nombres entiers compris entre deux limites fourniesen arguments, et une fonction erreur qui se contente d’afficher un message d’erreur (il s’agitde notre premier exemple de programme source contenant plus de deux fonctions).

#include <iostream>using namespace std ;void affiche_carres (int, int) ; // declaration de affiche_carresvoid erreur () ; // declaration de erreurint main (){ int debut = 5, fin = 10 ; ..... affiche_carres (debut, fin) ; ..... if (...) erreur () ;}void affiche_carres (int d, int f){ int i ; for (i=d ; i<=f ; i++) cout << i << " a pour carre " << i*i << "\n" ;}void erreur (){ cout << "*** erreur ***\n" ; }

RemarqueEn toute rigueur, l’en-tête de la fonction main montre l’existence d’une valeur de retourde type int. Effectivement, il est possible d’introduire, dans cette fonction main, une ouplusieurs instructions return accompagnées d’une expression de type int. Cette valeur estsusceptible d’être utilisée par l’environnement de programmation. Il est convenu que lavaleur 0 indique un bon déroulement du programme. Si aucune instruction return nefigure dans main, tout se passe comme si on trouvait return 0 à la fin de son exécution.

En CLe langage C est beaucoup plus tolérant (à tort) que C++ dans les déclarations defonctions ; on peut omettre le type des arguments (quels que soient leurs types) ou celuide la valeur de retour (s’il s’agit d’un int). Mais les règles employées par C++ restent

LIVRE.book Page 108 Jeudi, 12. septembre 2019 6:19 18

Page 45: Programmer en C++ moderne - fnac-static.com

3 - Les fonctions et leurs déclarations 109

valides (et même conseillées) en C. Une seule incompatibilité existe dans le cas des fonc-tions sans argument : C utilise le mot void, là où C++ demande une liste vide.

3 Les fonctions et leurs déclarations

3.1 Les différentes façons de déclarer une fonctionDans notre exemple du paragraphe 1, nous avions fourni la définition de la fonction fexpleaprès celle de la fonction main. Mais nous aurions pu tout aussi bien faire l’inverse :

float fexple (float, int, int) ; // declaration de la fonction fexple

float fexple (float x, int b, int c)

{

....

}

int main ()

{ .....

y = fexple (x, n, p) ;

.....

}

En toute rigueur, dans ce cas, la déclaration de la fonction fexple est facultative car, lorsqu’iltraduit la fonction main, le compilateur connaît déjà la fonction fexple. Néanmoins, nous vousdéconseillons d’omettre la déclaration de fexple dans ce cas (d’ailleurs, bon nombre de com-pilateurs fourniront un message d’avertissement) ; en effet, il est tout à fait possible qu’ulté-rieurement vous soyez amené à modifier votre programme source ou même à l’éclater enplusieurs fichiers source comme l’autorisent les possibilités de compilation séparée de C++.La déclaration d’une fonction porte le nom de prototype. Il est possible, dans un prototype,de faire figurer des noms d’arguments, lesquels sont alors totalement arbitraires ; cette possi-bilité a pour seul intérêt de pouvoir écrire des prototypes qui sont identiques à l’en-tête de lafonction (au point-virgule près), ce qui peut en faciliter la création automatique. Dans notreexemple du paragraphe 1, notre fonction fexple aurait pu être déclarée ainsi :

float fexple (float x, int b, int c) ;

3.2 Où placer la déclaration d’une fonctionDans notre exemple du paragraphe 1, nous avions placé la déclaration de la fonction fexpleavant la définition de la fonction (main) qui y faisait appel. Nous avions donc affaire à unedéclaration globale dont la portée s’étendait à l’ensemble du fichier source, ce qui pourraitpermettre, le cas échéant, d’utiliser fexple dans d’autres fonctions du même fichier. Il s’agit làde la démarche la plus utilisée, notamment dans les programmes conséquents. Mais, on peutthéoriquement recourir également à des déclarations locales à une fonction. Ainsi, notreexemple du paragraphe 1 aurait pu utiliser ce canevas :

LIVRE.book Page 109 Jeudi, 12. septembre 2019 6:19 18

Page 46: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7110

int main(){ float fexple (float, int, int) ; // declaration de fexple - portee limitée au main .....}

Par ailleurs, nous verrons, au paragraphe 16.1 que, dans les programmes de quelqueimportance, les déclarations de fonctions sont en fait placées dans des fichiers en-têtes,dont l’inclusion se fait alors préférentiellement à un niveau global.

3.3 Contrôles et conversions induites par le prototypeLa déclaration d’une fonction peut être utilisée par le compilateur, de deux façons complète-ment différentes.1 Si la définition de la fonction se trouve dans le même fichier source (que ce soit avant ou

après la déclaration), il s’assure que les arguments muets ont bien le type défini dans leprototype. Dans le cas contraire, il signale une erreur.

2 Lorsqu’il rencontre un appel de la fonction, il met en place d’éventuelles conversions desvaleurs des arguments effectifs dans le type indiqué dans le prototype. Par exemple, avecnotre fonction fexple du paragraphe 1, un appel tel que : fexple (n+1, 2*x, p)

sera traduit par :

– l’évaluation de la valeur de l’expression n+1 (en int) et sa conversion en float ;– l’évaluation de la valeur de l’expression 2*x (en float) et sa conversion en

int (conversion dégradante).

4 Transmission des arguments par valeurJusqu’ici, nous nous sommes contentés de dire que les valeurs des arguments étaient transmisà la fonction au moment de son appel. Nous vous proposons de voir ici ce que cela signifieexactement, et les limitations qui en découlent. Nous examinerons tout d’abord le cas généralsur un exemple, avant de nous intéresser au cas des arguments (muets et effectifs) constants.

4.1 Cas généralVoyez cet exemple :

//TansValeur#include <iostream>using namespace std ;void echange (int a, int b) ;

LIVRE.book Page 110 Jeudi, 12. septembre 2019 6:19 18

Page 47: Programmer en C++ moderne - fnac-static.com

4 - Transmission des arguments par valeur 111

int main (){ int n=10, p=20 ; cout << "-- avant appel : " << n << " " << p << endl ; echange (n, p) ; cout << "-- apres appel : " << n << " " << p << endl ;}void echange (int a, int b){ int c ; cout << "-- debut echange : "<< a << " " << b << endl ; c = a ; a = b ; b = c ; cout << "-- fin echange : " << a << " " << b << endl ;}

-- avant appel : 10 20-- debut echange : 10 20-- fin echange : 20 10-- apres appel : 10 20

Conséquences de la transmission par valeur des arguments

La fonction echange reçoit deux valeurs correspondant à ses deux arguments muets a et b.Elle effectue un échange de ces deux valeurs. Mais, lorsque l’on est revenu dans le pro-gramme principal, aucune trace de cet échange ne subsiste sur les arguments effectifs n et p.En effet, lors de l’appel de echange, il y a eu transmission de la valeur des expressions n et p.On peut dire que ces valeurs ont été recopiées localement dans la fonction echange dans desemplacements nommés a et b. C’est effectivement sur ces copies qu’a travaillé la fonctionechange, de sorte que les valeurs des variables n et p n’ont, quant à elles, pas été modifiées.C’est ce qui explique le résultat constaté.En fait, ce mode de transmission par valeur n’est que le mode utilisé par défaut par C++.Comme nous allons le voir bientôt, le choix explicite d’une transmission par référence per-mettra de réaliser correctement notre fonction echange.

Remarques1 C’est bien parce que la transmission des arguments se fait « par valeur » que les argu-

ments effectifs peuvent prendre la forme d’une expression quelconque. Et, d’ailleurs,nous verrons qu’avec la transmission par référence, les arguments effectifs ne pourrontplus être des expressions, mais simplement des lvalue.

2 La norme n’impose aucun ordre pour l’évaluation des différents arguments d’une fonc-tion lors de son appel. En général, ceci est de peu d’importance, excepté dans une situa-tion (fortement déconseillée !) telle que :

LIVRE.book Page 111 Jeudi, 12. septembre 2019 6:19 18

Page 48: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7112

int i = 10 ; ... f (i++, i) ; // i++ peut se trouver caclulé avant i - l’appel sera : f (10, 11) // ou après i - l’appel sera : f (10, 10)

3 En toute rigueur, la valeur de retour (lorsqu’elle existe) est elle aussi transmise parvaleur, c’est-à-dire qu’elle fait l’objet d’une recopie de la fonction appelée dans lafonction appelante. Ce point peut sembler anodin, mais nous verrons plus tard qu’ilexiste des circonstances où il s’avère fondamental et où, là encore, il faudra recourir àune transmission par référence.

4.2 Transmission par valeur et constance des argumentsNous avons déjà vu la signification de const (et constexpr depuis C++11) dans le cas desdéclarations de variables, lesquelles deviennent alors des lvalue non modifiables. Ici, nousexaminons :• les conséquences de ce type de qualificatif const ou constexpr lorsqu’il concerne un argu-

ment effectif d’une fonction ;• comment il est également possible d’appliquer le qualificatif const à des arguments muets

et quelle en est alors la signification.

4.2.1 Cas des arguments effectifs constantsSi l’on considère la fonction f définie ainsi :

void f (int n) { ..... }

on peut l’appeler avec la valeur de n’importe quelle expression, y compris d’une expressionconstante puisque la valeur en sera recopiée :

const int p = 4 ;f (p) ; // OKconstexpr int NB = 12 ;f(NB) ; // OK

4.2.2 Cas des arguments muets constantsL’utilisation de const sur des aruments muets a une toute autre signification. Il précise que lalvalue corrrespondante ne doit pas être modifiée dans le corps de la fonction. Dans le cascontraire, on obtient une erreur de compilation :

void f (const int n){ ..... n++ ; // rejeté en compilation}

Notez que l’utilisation de constexpr dans cette situation n’a aucune signification.

LIVRE.book Page 112 Jeudi, 12. septembre 2019 6:19 18

Page 49: Programmer en C++ moderne - fnac-static.com

5 - Transmission des arguments par référence 113

5 Transmission des arguments par référenceNous venons de voir que, par défaut, les arguments d’une fonction sont transmis par valeur.Comme nous l’avons constaté avec la fonction echange, ce mode de transmission ne permetpas à une fonction de modifier la valeur d’un argument. Or, C++ dispose de la notion de réfé-rence, laquelle correspond à celle d’adresse : considérer la référence d’une variable revient àconsidérer son adresse, et non plus sa valeur. Ici, nous allons voir comment utiliser cettenotion de référence pour la transmission d’arguments ; au paragraphe 8, nous verrons com-ment l’utiliser également pour une valeur de retour. Enfin, au paragraphe 14. nous verronscomment cette notion de référence possède en fait un caractère plus général mais peu usité.

5.1 Exemple de transmission d’argument par référenceLe programme ci-dessous montre comment utiliser une transmission par référence dans notreprécédente fonction echange :

// TransRef#include <iostream>using namespace std ;void echange (int &, int &) ; int main (){ int n=10, p=20 ; cout << "-- avant appel : " << n << " " << p << endl ; echange (n, p) ; // attention, ici pas de &n, &p cout << "-- apres appel : " << n << " " << p << endl ;}void echange (int & a, int & b){ int c ; cout << "-- debut echange : " << a << " " << b << endl ; c = a ; a = b ; b = c ; cout << "-- fin echange : " << a << " " << b << endl ;}

-- avant appel : 10 20-- debut echange : 10 20-- fin echange : 20 10-- apres appel : 20 10

Utilisation de la transmission d’argument par référence en C++

Dans l’instruction :void echange (int & a, int & b) ;

la notation int & a signifie que a est une information de type int transmise par référence.Notez bien que, dans la fonction echange, on utilise simplement le symbole a pour désignercette variable dont la fonction aura reçu effectivement l’adresse.

LIVRE.book Page 113 Jeudi, 12. septembre 2019 6:19 18

Page 50: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7114

En Java

La notion de référence existe en Java, mais elle est entièrement transparente au program-meur. Plus précisément, les variables d’un type de base sont transmises par valeur, tandisque les objets sont transmis par référence. Il reste cependant possible de créer explicite-ment une copie d’un objet en utilisant une méthode appropriée dite de clonage.

5.2 Propriétés de la transmission par référence d’un argumentLa transmission par référence d’un argument évite une recopie d’information, d’où un gainde temps d’exécution qui, s’il n’est guère sensible dans le cas de variables scalaires, pourradevenir apréciable dans le cas de gros agrégats ou de gros objets. En revanche, il entraîne uncertain nombre de conséquences qui n’existaient pas dans le cas de la transmission parvaleur.Ainsi, dans l’appel d’une fonction recevant un argument par référence, l’argument effectifcorrespondant doit obligatoirement être une lvalue modifiable (une expression n’a pasd’adresse, une constante n’est pas modifiable). En outre, comme il s’agit de transmettrel’adresse de cette lvalue, il n’est pas possible d’en prévoir une quelconque conversion (mêmenon dégradante), puisqu’il faudrait pour cela créer une nouvelle valeur, à une nouvelleadresse :

void f (int &) ; // f reçoit la référence à un entier .....const int n=15 ;int q ;f(q) ; // OKf(2*q+3) ; // erreur : 2*q+3 n’est pas une lvalue modifiablef(3) ; // erreur : 3 n’est pas modifiable (c’est une rvalue)f(n) ; // erreur : n est une lvalue non modifiable .....float x ;f(x) ; // erreur : x n’est pas de type int

La transmission par référence impose donc à un argument effectif d’être une lvaluemodifiable du type prévu pour l’argument muet. Il existe cependant une exception dans lecas des arguments muets constants comme nous allons le voir maintenant.

5.3 Référence à un argument muet constantEn toute rigueur, la transmission par référence, telle que nous venons de l’examiner, possèdedeux propriétés distinctes :• elle évite la recopie d’informations ;• elle permet à une fonction de travailler directement sur l’argument effectif transmis et, donc,

d’en modifier la valeur.

LIVRE.book Page 114 Jeudi, 12. septembre 2019 6:19 18

Page 51: Programmer en C++ moderne - fnac-static.com

5 - Transmission des arguments par référence 115

C++ vous permet de profiter de la première propriété, tout en interdisant la modification del’argument effectif. Il suffit pour cela de prévoir constant l’argument muet correspondantcomme dans cet en-tête :

void f (const int & n) ;

Dans ce cas, la fonction f s’attend à recevoir l’adresse d’une constante et elle ne devra pas,dans sa définition, modifier la valeur de n ; dans le cas contraire, on obtiendra une erreur decompilation.Cette fois, les appels suivants seront corrects :

const int c = 15 ; .....f(3) ; // correct ici f(c) ; // correct ici

L’acceptation de ces instructions se justifie par le fait que f a prévu de recevoir une référenceà quelque chose de constant ; le risque de modification évoqué précédemment n’existe doncplus. Notez d’ailleurs que, dans l’appel f(3), 3 n’est même plus une lvalue non modifiable,mais une rvalue.A priori, C++ aurait pu se limiter à cela. Mais, en fait, on pourra également appeler f en luifournissant en argument une expression quelconque exp, de type int ou même d’un typecompatible avec int. Pour ce faire, il est alors prévu la création d’une variable temporaire quirecevra la valeur de l’expression (ou de sa conversion en int) et dont l’adresse sera fournie àf. Par exemple :

void f (const int &) ; int n ;f (3*n + 1) ; // f reçoit la référence à une variable temporaire // contenant la valeur de l’expression 3*n+1float x ;f (x) ; // correct : f reçoit la référence à une variable temporaire // contenant le résultat de la conversion de x en intf (3*x + 5.2) ; // correct : f reçoit la référence à une variable temporaire // contenant le résultat de la conversion en int de la valeur // de l’expression 3*x +5.2

En définitive, l’utilisation de const pour un argument muet transmis par référence est lourdede conséquences. Certes, comme on s’y attend, cela amène le compilateur à verifier la cons-tance de l’argument concerné au sein de la fonction. Mais, de surcroît, on autorise la créationd’une copie de l’argument effectif (précédée d’une conversion) dès lors que ce dernier n’estplus une lvalue ou une constante du type attendu.Cette remarque prendra encore plus d’acuité dans le cas où l’argument en question sera unobjet volumineux.

5.4 Induction de risques indirectsLe choix du mode de transmission par référence est fait au moment de l’écriture de la fonc-tion concernée. L’utilisateur de la fonction n’a plus à s’en soucier ensuite, si ce n’est au

LIVRE.book Page 115 Jeudi, 12. septembre 2019 6:19 18

Page 52: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7116

niveau de la déclaration du prototype de la fonction (d’ailleurs, ce prototype proviendra engénéral d’un fichier en-tête).En contrepartie, l’emploi de la transmission par référence accroît les risques d’« effets debord » non désirés. En effet, lorsqu’il appelle une fonction, l’utilisateur ne sait plus s’il trans-met, au bout du compte, la valeur ou l’adresse d’un argument (la même notation pouvantdésigner l’une ou l’autre des deux possibilités). Il risque donc de modifier une variable dontil pensait n’avoir transmis qu’une copie de la valeur.

RemarqueNous verrons (paragraphe 6 du chapitre 9) qu’il est également possible de « simuler » unetransmission par référence, par le biais de pointeurs. Dans ce cas, l’utilisateur de la fonc-tion devra transmettre explicitement des adresses : les risques évoqués précédemmentdisparaissent, en contrepartie d’une programmation plus délicate et plus risquée de lafonction elle-même.

6 Les variables globalesNous avons vu comment échanger des informations entre différentes fonctions grâce à latransmission d’arguments et à la récupération d’une valeur de retour.En théorie, en C++, plusieurs fonctions (dont, bien entendu le programme principal main)peuvent partager des variables communes qu’on qualifie alors de globales. Il s’agit cepen-dant là d’une pratique risquée qu’il faudra éviter au maximum. Nous vous la présente-rons cependant ici car :

• vous risquez de rencontrer du code y recourant ;

• la notion de variable globale permet de mieux comprendre la différence entre classe d’allo-cation statique et classe d’allocation dynamique, laquelle prendra toute son importance dansun contexte objet ;

• dans une classe, les champs de données auront un comportement « global » pour les (seules)méthodes de cette classe.

6.1 Exemple d’utilisation de variables globalesVoyez l’exemple de programme ci-après :

// VarGlobales

#include <iostream>

using namespace std ;

int i ; // declaration d’une variable globale

void optimist (void) ;

LIVRE.book Page 116 Jeudi, 12. septembre 2019 6:19 18

Page 53: Programmer en C++ moderne - fnac-static.com

6 - Les variables globales 117

int main (){ for (i=1 ; i<=5 ; i++) optimist() ;}void optimist(void){ cout << "il fait beau " << i << " fois\n" ;}

il fait beau 1 foisil fait beau 2 foisil fait beau 3 foisil fait beau 4 foisil fait beau 5 fois

Exemple d’utilisation de variable globale

La variable i a été déclarée en dehors de la fonction main. Elle est alors connue de toutes lesfonctions qui seront compilées par la suite au sein du même programme source. Ainsi, ici, leprogramme principal affecte à i des valeurs qui se trouvent utilisées par la fonction optimist.Notez qu’ici la fonction optmist se contente d’utiliser la valeur de i mais rien ne l’empêche dela modifier. C’est précisément ce genre de remarque qui doit vous inciter à n’utiliser lesvariables globales que dans des cas limités. En effet, toute variable globale peut être modifiéeinsidieusement par n’importe quelle fonction. Lorsqu’on souhaite qu’une fonction modifie lavaleur d’une variable, il est beaucoup plus judicieux d’en transmettre l’adresse en argument(soit par référence, comme nous avons appris à le faire, soit par pointeur, comme on le verraplus tard). Dans ce cas, l’appel de la fonction indique clairement quelles sont les seules varia-bles susceptibles d’être modifiées.

6.2 La portée des variables globalesLes variables globales ne sont connues du compilateur que dans la partie du programmesource suivant leur déclaration. On dit que leur portée (ou encore leur espace de validité) estlimitée à la partie du programme source qui suit leur déclaration (pour l’instant, nous nouslimitons au cas où l’ensemble du programme est compilé en une seule fois).Ainsi, voyez, par exemple, ces instructions :

int main () { .... } int n ; float x ; void fct1 (...) { .... } void fct2 (...) { .... }

Les variables n et x sont accessibles aux fonctions fct1 et fct2, mais pas au programme princi-pal. En pratique, bien qu’il soit possible effectivement de déclarer des variables globales àn’importe quel endroit du programme source qui soit extérieur aux fonctions, on procéderararement ainsi. En pratique, s’il faut absolument recourir à des variables globales (par exem-

LIVRE.book Page 117 Jeudi, 12. septembre 2019 6:19 18

Page 54: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7118

ple, dans des codes critiques en temps d’exécution), on s’arrangera pour privilégier la lisibi-lité des codes en regroupant en début de programme1 les déclarations de toutes ces variablesglobales.

6.3 La classe d’allocation des variables globalesD’une manière générale, les variables globales existent pendant toute l’exécution du pro-gramme dans lequel elles apparaissent. Leurs emplacements en mémoire sont parfaitementdéfinis lors de l’édition de liens. On traduit cela en disant qu’elles font partie de la classed’allocation statique.De plus, ces variables se voient initialisées à zéro2, avant le début de l’exécution du pro-gramme, sauf, bien sûr, si vous leur attribuez explicitement une valeur initiale au moment deleur déclaration.

7 Les variables localesÀ l’exception de l’exemple du paragraphe précédent, les variables que nous avions rencon-trées jusqu’ici n’étaient pas des variables globales. Plus précisément, elles étaient définies ausein d’une fonction (qui pouvait être main). De telles variables sont dites locales à la fonctiondans laquelle elles sont déclarées.

7.1 La portée des variables localesLes variables locales ne sont connues du compilateur qu’à l’intérieur de la fonction où ellessont déclarées. Leur portée est donc limitée à cette fonction. Les variables locales n’ontaucun lien avec des variables globales de même nom ou avec d’autres variables locales àd’autres fonctions. Voyez cet exemple :

int n ;

int main ()

{ int p ;

....

}

void fct1 ()

{ int p ;

int n ;

}

La variable p de main n’a aucun rapport avec la variable p de fct1. De même, la variable n defct1 n’a aucun rapport avec la variable globale n. En toute rigueur, si l’on souhaite utiliser

1. Ou dans un fichier en-tête séparé.2. Cette notion de « zéro » sera précisée pour les pointeurs et pour les types structurés (tableaux, vecteurs, objets...).

LIVRE.book Page 118 Jeudi, 12. septembre 2019 6:19 18

Page 55: Programmer en C++ moderne - fnac-static.com

7 - Les variables locales 119

dans fct1 la variable globale n, on utilise l’opérateur dit « de résolution de portée » (::) en lanommant ::n.

7.2 Les variables locales automatiquesPar défaut, les variables locales ont une durée de vie limitée à celle d’une exécution de lafonction dans laquelle elles figurent.Plus précisément, leurs emplacements ne sont pas définis de manière permanente commeceux des variables globales. Un nouvel espace mémoire leur est alloué à chaque entrée dansla fonction et libéré à chaque sortie. Il sera donc généralement différent d’un appel au sui-vant.On traduit cela en disant que la classe d’allocation de ces variables est automatique. Nousaurons l’occasion de revenir plus en détail sur ce mode de gestion de la mémoire. Pour l’ins-tant, il est important de noter que la conséquence immédiate de ce mode d’allocation est queles valeurs des variables locales ne sont pas conservées d’un appel au suivant (on dit aussiqu’elles ne sont pas « rémanentes »). Nous reviendrons un peu plus loin (paragraphe 9) surles éventuelles initialisations de telles variables. D’autre part, les valeurs reçues en arguments par une fonction sont traitées de la mêmemanière que les variables locales. Leur durée de vie correspond également à celle de la fonc-tion.

Informations complémentaires

Généralement, on utilise pour les variables automatiques une « pile » de type FIFO (FirstIn, First Out) simulée dans une zone de mémoire. Lors de l’appel d’une fonction, onalloue de l’espace sur la pile à la fois pour la valeur de retour, les valeurs des argumentsou leurs références et pour les différentes variables locales à la fonction.Lors de la sortie de la fonction, ces différents emplacements sont libérés par la fonctionelle-même, hormis celui de la valeur de retour qui sera libéré par la fonction appelante,après qu’elle l’aura utilisé.La gestion de la pile se fait à l’aide d’un pointeur désignant le premier emplacementdisponible. La libération d’un emplacement se fait par une simple modification de lavaleur de ce pointeur ; l’emplacement libéré garde généralement sa valeur, de sorte quesi, par une erreur de programmation, on y accède avant qu’il ait été alloué à une autrevariable, on peut croire, à tort, qu’une variable locale est « rémanente »...On notera que dans bon nombre d’environnements de programmation, l’espacemémoire alloué à la pile souffre de limitations plus importantes que celui alloué au tas.

LIVRE.book Page 119 Jeudi, 12. septembre 2019 6:19 18

Page 56: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7120

7.3 Les variables locales statiquesIl est possible de demander d’attribuer un emplacement permanent à une variable locale et deconserver ainsi sa valeur d’un appel au suivant. Il suffit pour cela de la déclarer à l’aide dumot-clé static. En voici un exemple :

// VarLocStatiques#include <iostream>using namespace std ;void fct() ;int main (){ for ( int n=1 ; n<=5 ; n++) fct() ;}void fct(){ static int i ; i++ ; cout << "appel numero : " << i << endl ;}

appel numero : 1appel numero : 2appel numero : 3appel numero : 4appel numero : 5

Exemple d’utilisation de variable locale statique

La variable locale i a été déclarée de classe « statique ». On constate bien que sa valeur pro-gresse de 1 à chaque appel. De plus, on note qu’au premier appel sa valeur est nulle. En effet,comme pour les variables globales (lesquelles sont aussi de classe statique) : les variableslocales de classe statique sont, par défaut, initialisées à zéro. Notez que nous aurions puinitialiser explicitement i, par exemple :

static int i = 3 ;

Dans ce cas, nos appels auraient porté des numéros allant de 4 à 8.Prenez garde à ne pas confondre une variable locale de classe statique avec une variable glo-bale. En effet, la portée d’une telle variable reste toujours limitée à la fonction dans laquelleelle est définie. Ainsi, dans notre exemple, nous pourrions définir une variable globale nom-mée i qui n’aurait alors aucun rapport avec la variable i de fct.

RemarqueSi i n’avait pas été déclarée avec l’attribut static, il se serait agit d’une variable localeusuelle, non rémanente et, de surcroît, non initialisée. Sa valeur aurait donc été aléatoire.De plus, ici, c’est toujours le même emplacement qui se serait trouvé alloué à i sur la pile,

LIVRE.book Page 120 Jeudi, 12. septembre 2019 6:19 18

Page 57: Programmer en C++ moderne - fnac-static.com

7 - Les variables locales 121

de sorte qu’on afficherait bien une séquence de valeurs (dont la première est aléatoire),donnant l’illusion d’une certaine rémanence de i.

7.4 Variables locales à un blocComme nous l’avions déjà évoqué succinctement, C++ vous permet de déclarer des variableslocales à un bloc. Leur portée est alors tout naturellement limitée à ce bloc ; leur emplace-ment est alloué à l’entrée dans le bloc et il disparaît à la sortie. Il est permis qu’une variablelocale porte le même nom qu’une variable locale d’un bloc englobant.

void f(){ int n ; // n est accessible de tout le bloc constituant f ..... for (...) { int p ; // p n’est connue que dans le bloc de for int n ; // n masque la variable n de portée "englobante" ..... // attention, on ne peut pas utiliser ::n ici qui ..... // désignerait une variable globale (inexistante ici) } ..... { int p ; // p n’est connue que dans ce bloc ; elle est allouée ici ..... // et n’a aucun rapport avec la variable p ci-dessus } // et elle sera désallouée ici .....}

Notez qu’on peut créer artificiellement un bloc, indépendamment d’une quelconqueinstruction structurée comme if, for. C’est le cas du deuxième bloc interne à notre fonction fci-dessus.D’autre part, nous avons déjà vu qu’il était possible d’effectuer une déclaration dans une ins-truction for, par exemple :

for (int i=2, j=4 ; ... ; ...){ // i et j sont considérées comme deux variables locales à ce bloc}

On notera que, même si l’instruction for ne contient aucun bloc explicite, comme dans :for (int i=1, j=1 ; i<4 ; i++) cout << i+j ;

les variables i et j ne seront plus connues par la suite, exactement comme si l’on avait écrit :for (int i=1, j=1 ; i<4 ; i++) { cout << i+j ; }

Informations complémentaires

En toute rigueur, il existe une classe d’allocation un peu particulière, à savoir la classe« registre » : toute variable entrant a priori dans la classe automatique peut être déclaréeexplicitement (jusqu’à C++17) avec le qualificatif register. Celui-ci demande au compila-teur d’utiliser, dans la mesure du possible, un « registre » de la machine pour y ranger lavariable : cela peut amener quelques gains de temps d’exécution. Bien entendu, cette

LIVRE.book Page 121 Jeudi, 12. septembre 2019 6:19 18

Page 58: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7122

possibilité ne peut s’appliquer qu’aux variables d’un type simple. Cette fonctionnalitédisparaît en C++17 mais register reste un mot-clé.

7.5 Le cas des fonctions récursivesC++ autorise la récursivité des appels de fonctions. Celle-ci peut prendre deux aspects :• récursivité directe : une fonction comporte, dans sa définition, au moins un appel à elle-

même ;• récursivité croisée : l’appel d’une fonction entraîne celui d’une autre fonction qui, à son

tour, appelle la fonction initiale (le cycle pouvant d’ailleurs faire intervenir plus de deuxfonctions).

Voici un exemple fort classique (d’ailleurs inefficace sur le plan du temps d’exécution) d’unefonction calculant une factorielle de manière récursive :

long fac (int n){ if (n>1) return (fac(n-1)*n) ; else return(1) ;}

Fonction récursive de calcul de factioriuelle

Il faut bien voir que chaque appel de fac entraîne une allocation d’espace pour les variableslocales et pour son argument n (apparemment, fct ne comporte aucune variable locale ; enréalité, il lui faut prévoir un emplacement destiné à recevoir sa valeur de retour). Or chaquenouvel appel de fac, à l’intérieur de fac, provoque une telle allocation, sans que les emplace-ments précédents soient libérés.Il y a donc un empilement des espaces alloués aux variables locales, parallèlement à un empi-lement des appels de la fonction. Ce n’est que lors de l’exécution de la première instructionreturn que l’on commencera à « dépiler » les appels et les emplacements et donc à libérer del’espace mémoire.

8 Transmission par référence d’une valeur de retour

N.B. L’étude de ce paragraphe peut être différée jusqu’à celle du chapitre sur la surdéfini-tion des opérateurs.

LIVRE.book Page 122 Jeudi, 12. septembre 2019 6:19 18

Page 59: Programmer en C++ moderne - fnac-static.com

8 - Transmission par référence d’une valeur de retour 123

8.1 IntroductionLe mécanisme que nous avons exposé pour la transmission des arguments par références’applique à la valeur de retour d’une fonction. Considérons :

int & f (){ ..... return n ; // on suppose ici n de type int}

Un appel de f provoquera la transmission en retour non plus d’une valeur, mais de la réfé-rence (adresse) de n. Là encore, on évite une recopie, ce qui entraîne un gain de temps d’exé-cution qui deviendra intéressant avec de gros objets. Cependant, cette fois, on voit que n nepeut pas être une variable locale à f car, sinon, elle serait détruite à la sortie de f et l’onrécupérerait dans la fonction appelante, une adresse à quelque chose qui n’existe plus3. Enrevanche, on pourra ainsi fournir en valeur de retour la référence à un objet créé dynamique-ment comme nous apprendrons à le faire plus tard. On pourra également renvoyer une réfé-rence qu’on aura reçue en argument.

8.2 Conséquences dans la définition de la fonctionPuisqu’il s’agit d’une transmission d’adresse, la valeur de retour mentionnée dans l’instruc-tion return ne peut être qu’une lvalue modifiable. Il ne pourra donc pas s’agir d’une expres-sion et toute conversion s’avère impossible (même s’il s’agit d’une conversion nondégradante). La lvalue figurant dans l’instruction return doit donc être du type exact prévudans l’en-tête.Le cas de la valeur de retour constante fera cependant exception à ces règles comme nous leverrons au paragraphe 8.5.

8.3 Conséquences dans l’utilisation de la fonctionDès lors qu’une fonction renvoie une référence, il devient possible d’utiliser son appelcomme une lvalue modifiable. Voyez cet exemple :

int & f (int q) { ..... }int n, p;float x ; .....f(p) = 2 * n + 5 ; // à la référence fournie par f(p), on range la valeur // de l’expression 2*n+5, de type int

3. Cette erreur s’apparente à celle due à la transmission en valeur de retour d’un pointeur sur une variable locale(situation que nous rencontrerons plus tard). Elle est encore plus difficile à détecter dans la mesure où le seul momentoù l’on peut utiliser la référence concernée est l’appel lui-même (alors qu’un pointeur peut être utilisé à volonté...).Dans un environnement ne modifiant pas la valeur d’une variable lors de sa « destruction », aucune erreur ne semanifeste ; ce n’est que lors du portage dans un environnement ayant un comportement différent que les chosesdeviennent catastrophiques.

LIVRE.book Page 123 Jeudi, 12. septembre 2019 6:19 18

Page 60: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7124

f(2*p+1) = x ; // à la référence fournie par f(2*p+1), on range la valeur // de x, apres conversion en int

On voit qu’on se trouve ainsi en présence d’expressions d’une forme inhabituelle, même enmathématiques.Cette propriété sera largement exploitée lorsqu’il s’agira de surdéfinir un opérateur (en fait,une fonction) dont la nature imposera de fournir une lvalue en résultat. Ce sera précisémentle cas de l’opérateur []4.Nous verrons cependant au paragraphe 8.5 qu’il ne sera plus possible d’utiliser l’appel de lafonction comme une lvalue dès lors que la valeur de retour sera une référence à une cons-tante.En revanche, contrairement à ce qui se produisait pour les arguments transmis par référence,aucune contrainte d’exactitude de type ne pèse sur l’utilisation d’une valeur de retour fourniepar référence, car il reste toujours possible de la soumettre à une conversion avant del’utiliser :

inf & f () ;float x ; .....x = f() ; // OK : on convertira en int la valeur située à la référence // reçue en retour de f

8.4 ExempleVoici un exemple, un peu artificiel, d’une fonction recevant deux références d’entiers et ren-voyant l’une d’entre elles. Un indicateur booléen statique permet de choisir une fois la pre-mière, une fois la seconde.

// ValRetourRef#include <iostream>using namespace std ;int & alterne (int &, int &) ;int main (){ int n=1, p=3, q=5 ; alterne (n, p ) = 0 ; // on affecte 0 a la lvalue dont alterne fournit la ref cout << "n = " << n << " p = " << p << endl ; alterne (p, q) = 12 ; cout << "p = " << p << " q = " << q << endl ;}int & alterne (int & n1, int & n2){ static bool indic = true ; if (indic) { indic = false ; return n1 ; } else { indic = true ; return n2 ; }}

4. Toutefois, on emploiera alors pour l’appel une notation de la forme t[i] = n, laquelle semblera alors plus naturelle,malgré son équivalence avec la notation fonctionnelle utilisée ici.

LIVRE.book Page 124 Jeudi, 12. septembre 2019 6:19 18

Page 61: Programmer en C++ moderne - fnac-static.com

9 - Initialisation des variables 125

n = 0 p = 3p = 3 q = 12

Exemple de transmission d’une valeur de retour par référence

8.5 Valeur de retour constanteIl est possible de prévoir qu’une fonction fournisse par référence une valeur de retour cons-tante, comme dans :

const int & f2(.....)

Dans ce cas, dans la définition de la fonction, l’instruction return pourra toujours mentionnerune lvalue modifiable comme auparavant, mais cette fois, il deviendra également possibled’y faire figurer une rvalue (constante ou expression). La norme a en effet prévu qu’on ren-voie alors la référence d’une copie temporaire de cette expression. Qui plus est, puisqu’oncrée une copie, une éventuelle conversion redevient possible (pour peu qu’elle soit légale paraffectation). Ainsi, avec une fonction f2 définie de cette manière :

const int & f2 (.....) // cette fois on renvoie la référence à un entier constant{ int n ; float x ; .....}

voici quelques exemples d’instructions return légales : return 5 ; // OK : on renvoie la référence à une copie temporaire de 5 return n+5 ; // OK : on renvoie la référence à une copie temporaire // de la valeur de l’expression n+5 return n ; // A eviter : on renvoie la référence à une variable locale // (en général, avertissement à la compilation) return x ; // OK : on renvoie la référence à un int temporaire // obtenu par conversion de la valeur de x

En revanche, dans l’appel de la fonction, la valeur de retour (référence à une constante tem-poraire) ne pourra plus être utilisée comme une lvalue ni faire l’objet de conversion (commec’était le cas avec une référence non constante) :

const int & f2 () ;int n ;float x ; .....f2() = 2 * n + 5 ; // erreur : f() n’est pas une lvaluef2() = x ; // idem

9 Initialisation des variablesNous avons vu qu’il était possible d’initialiser explicitement une variable lors de sa décla-ration. Ici, nous allons faire le point sur ces possibilités, lesquelles dépendent en fait de laclasse d’allocation de la variable concernée.

LIVRE.book Page 125 Jeudi, 12. septembre 2019 6:19 18

Page 62: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7126

9.1 Les variables de classe statiqueIl s’agit des variables globales, ainsi que des variables locales déclarées avec l’attribut static.Ces variables sont permanentes. Elles sont initialisées une seule fois avant le début de l’exé-cution du programme. Elles peuvent être initialisées explicitement lors de leur déclaration, àl’aide de constantes ou d’expressions constantes (calculables par le compilateur) d’un typecompatible par affectation avec celui de la variable. On notera bien que les conversionsdégradantes sont acceptées avec l’initialisation utilisant le signe = (seule forme existant enC++98), mais peu conseillées. Avec C++11, on peut utiliser la forme avec accolades quiinterdit les conversions dégradantes. Voici quelques exemples concernant ici des variablesdéclarées statiques dans une fonction f (les mêmes considérations s’appliqueraient à desvariables globales) :

void f (){ const int NB = 5 ; static int limit = 2 * NB + 1 ; // 2*NB+1 est une expression constante static short CTOT1 = 25 ; // 25 de type int est converti en short int static short CTOT2 = 50000 ; // 50000 (de type int) est converti en short int // avec risque de résultat erroné // static short CTOT3 {50000} ; // C++11 : rejeté à la compilation // si 50000 non représentable en short int // (donc dépendant de l’implémentation) // - mêmes remarques avec CTOT3 = {50000} static float XMAX1 = 5 ; // 5 de type int est converti en float static float XMAX2 { 5 } ; // 5 de type int est converti en float // - même chose avec XMAX2 = { 5 } static long YTOT1 = 9.7 ; // 9.7 de type float est converti en long // avec résultat tronqué (9) // static long YTOT2 { 9.7 } ; // C++11 : rejeté à la compilation // - même chose avec YTOT2 = { 9.7 }}

En l’absence d’initialisation explicite, ces variables seront initialisées à zéro5.

9.2 Les variables de classe automatiqueIl s’agit des variables locales à une fonction ou à un bloc. Ces variables ne sont pas initiali-sées par défaut. En revanche, comme les variables de classe statique, elles peuvent être ini-tialisées explicitement lors de leur déclaration. Dans ce cas, la valeur initiale peut être fourniesous la forme d’une expression quelconque (d’un type compatible par affectation), pourpeu que sa valeur soit définie au moment de l’entrée dans la fonction correspondante (il peuts’agir de la fonction main !). N’oubliez pas que ces variables automatiques se trouvent alorsinitialisées à chaque appel de la fonction dans laquelle elles sont définies. En voici un casd’école :

5. Rappelons que la notion de zéro est relative au type concerné. Avec un type simple, il s’agit bien de la valeur nullemais avec un pointeur, il s’agira de la valeur nullptr tandis qu’avec le type string, il s’agira d’une chaîne vide...

LIVRE.book Page 126 Jeudi, 12. septembre 2019 6:19 18

Page 63: Programmer en C++ moderne - fnac-static.com

10 - Les arguments par défaut 127

// ClasseAutomatique#include <iostream>using namespace std ;int n ; // variable globalevoid fct (int r) ;int main (){ for (int p=1 ; p<=5 ; p++) { n = 2*p ; fct(p) ; }}void fct(int r){ int q=n, s=r*n ; // l’initialisation utilise la globale n et l’argument r cout << r << " " << q << " " << s << endl ;}

1 2 22 4 83 6 184 8 325 10 50

Initialisation de variables de classe automatique

10 Les arguments par défaut

10.1 ExemplesJusqu’ici, nos appels de fonction renfermaient autant d’arguments que la fonction en atten-dait effectivement. C++ permet de s’affranchir en partie de cette règle, grâce à un mécanismed’attribution de valeurs par défaut à des arguments non fournis lors de l’appel.

Exemple 1Considérez l’exemple suivant :

// ArgDefaut1#include <iostream>using namespace std ;void fct (int, int=12) ; // prototype avec une valeur par defautint main (){ int n=10, p=20 ; fct (n, p) ; // appel "normal" fct (n) ; // appel avec un seul argument}void fct (int a, int b) // en-tete "habituel"{ cout << "premier argument : " << a << endl ; cout << "second argument : " << b << endl ;}

LIVRE.book Page 127 Jeudi, 12. septembre 2019 6:19 18

Page 64: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7128

premier argument : 10second argument : 20premier argument : 10second argument : 12

Exemple de définition de valeur par défaut pour un argument

La déclaration de fct, ici dans la fonction main, est réalisée par le prototype :void fct (int, int = 12) ;

La déclaration du second argument apparaît sous la forme :int = 12

Celle-ci précise au compilateur que, en cas d’absence de ce second argument dans un éven-tuel appel de fct, il lui faudra « faire comme si » l’appel avait été effectué avec cette valeur.Les deux appels de fct illustrent le phénomène. Notez qu’un appel tel que :

fct ( )

serait rejeté à la compilation puisque ici il n’était pas prévu de valeur par défaut pour le pre-mier argument de fct.

Exemple 2Voici un second exemple, dans lequel nous avons prévu des valeurs par défaut pour tous lesarguments de fct :

// ArgDefaut2#include <iostream>using namespace std ;void fct (int=0, int=12) ; // prototype avec deux valeurs par defautint main (){ int n=10, p=20 ; fct (n, p) ; // appel "normal" fct (n) ; // appel avec un seul argument fct () ; // appel sans argument}void fct (int a, int b) // en-tete "habituel"{ cout << "premier argument : " << a << endl ; cout << "second argument : " << b << endl ;}

premier argument : 10second argument : 20premier argument : 10second argument : 12premier argument : 0second argument : 12

Exemple de définition de valeurs par défaut pour plusieurs arguments

LIVRE.book Page 128 Jeudi, 12. septembre 2019 6:19 18

Page 65: Programmer en C++ moderne - fnac-static.com

11 - Surdéfinition de fonctions 129

10.2 Les propriétés des arguments par défautLorsqu’une déclaration prévoit des valeurs par défaut, les arguments concernés doi-vent obligatoirement être les derniers de la liste.Par exemple, une déclaration telle que :

float fexple (int = 5, long, int = 3) ;

est interdite. En fait, une telle interdiction relève du pur bon sens. En effet, si cette décla-ration était acceptée, l’appel suivant :

fexple (10, 20) ;

pourrait être interprété aussi bien comme :fexple (5, 10, 20) ;

que comme :fexple (10, 20, 3) ;

Notez bien que le mécanisme proposé par C++ revient à fixer les valeurs par défaut dans ladéclaration de la fonction et non dans sa définition. Autrement dit, ce n’est pas le« concepteur » de la fonction qui décide des valeurs par défaut, mais l’utilisateur. Une consé-quence immédiate de cette particularité est que les arguments soumis à ce mécanisme et lesvaleurs correspondantes peuvent varier d’une utilisation à une autre ; en pratique toutefois, cepoint ne sera guère exploité, ne serait-ce que parce que les déclarations de fonctions sont engénéral « figées » une fois pour toutes, dans un fichier en-tête.Nous verrons que les arguments par défaut se révéleront particulièrement précieux lorsqu’ils’agira d’écrire ce que l’on nomme le « constructeur d’une classe ».

RemarqueLes valeurs par défaut ne sont pas nécessairement des expressions constantes. Elles nepeuvent toutefois pas faire intervenir de variables locales6.

En Java

Les arguments par défaut n’existent pas en Java.

11 Surdéfinition de fonctionsD’une manière générale, on parle de « surdéfinition »7 lorsqu’un même symbole possèdeplusieurs significations différentes, le choix de l’une des significations se faisant en fonctiondu contexte. C’est ainsi que la plupart des langages évolués utilisent la surdéfinition d’un cer-tain nombre d’opérateurs. Par exemple, dans une expression telle que :

6. Ni la valeur this pour les fonctions membres (this sera étudié au chapitre 13).7. De overloading, parfois traduit par « surcharge ».

LIVRE.book Page 129 Jeudi, 12. septembre 2019 6:19 18

Page 66: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7130

a + b

la signification du + dépend du type des opérandes a et b ; suivant les cas, il pourra s’agird’une addition d’entiers ou d’une addition de flottants. De même, le symbole * peut désigner,suivant le contexte, une multiplication d’entiers, de flottants (ou, commme nous le verronslorsque nous étudierons les pointeurs, une indirection).Un des grands atouts de C++ est de permettre la surdéfinition de la plupart des opérateurs(lorsqu’ils sont associés à la notion de classe). Lorsque nous étudierons cet aspect, nous ver-rons qu’il repose en fait sur la surdéfinition de fonctions. C’est cette dernière possibilité quenous proposons d’étudier ici pour elle-même.Pour pouvoir employer plusieurs fonctions de même nom, il faut bien sûr un critère (autreque le nom) permettant de choisir la bonne fonction. En C++, ce choix est basé (comme pourles opérateurs cités précédemment en exemple) sur le type des arguments. Nous commence-rons par vous présenter un exemple complet montrant comment mettre en œuvre la surdéfini-tion de fonctions. Nous examinerons ensuite différentes situations d’appel d’une fonctionsurdéfinie avant d’étudier les règles détaillées qui président au choix de la « bonnefonction ».

11.1 Mise en œuvre de la surdéfinition de fonctionsNous allons définir et utiliser deux fonctions nommées sosie. La première possédera un argu-ment de type int, la seconde un argument de type double, ce qui les différencie bien l’une del’autre. Pour que l’exécution du programme montre clairement la fonction effectivementappelée, nous introduisons dans chacune une instruction d’affichage appropriée. Dans le pro-gramme d’essai, nous nous contentons d’appeler successivement la fonction surdéfinie sosie,une première fois avec un argument de type int, une seconde fois avec un argument de typedouble.

// Surdefinition#include <iostream>using namespace std ;void sosie (int) ; // les prototypesvoid sosie (double) ;int main () // le programme de test{ int n=5 ; double x=2.5 ; sosie (n) ; sosie (x) ; }void sosie (int a) // la premiere fonction{ cout << "sosie numero I a = " << a << endl ;}void sosie (double a) // la deuxieme fonction{ cout << "sosie numero II a = " << a << endl ;}

LIVRE.book Page 130 Jeudi, 12. septembre 2019 6:19 18

Page 67: Programmer en C++ moderne - fnac-static.com

11 - Surdéfinition de fonctions 131

sosie numero I a = 5sosie numero II a = 2.5

Exemple de surdéfinition de la fonction sosie

Vous constatez que le compilateur a bien mis en place l’appel de la « bonne fonction » sosie,au vu de la liste d’arguments (ici réduite à un seul).

11.2 Exemples de choix d’une fonction surdéfinieNotre précédent exemple était simple, dans la mesure où nous appelions toujours la fonctionsosie avec un argument ayant exactement l’un des types prévus dans les prototypes (int oudouble). On peut se demander ce qui se produirait si nous l’appelions par exemple avec unargument de type char ou long, ou si l’on avait affaire à des fonctions comportant plusieursarguments...Avant de présenter les règles de détermination d’une fonction surdéfinie, examinons toutd’abord quelques situations assez intuitives.

Exemple 1void sosie (int) ; // sosie Ivoid sosie (double) ; // sosie IIchar c ; float y ; .....sosie(c) ; // appelle sosie I, après conversion de c en intsosie(y) ; // appelle sosie II, après conversion de y en doublesosie('d') ; // appelle sosie I, après conversion de 'd' en int

Exemple 2void essai (int, double) ; // essai Ivoid essai (double, int) ; // essai IIint n, p ; double z ; char c ; .....essai(n,z) ; // appelle essai Iessai(c,z) ; // appelle essai I, après conversion de c en intessai(n,p) ; // erreur de compilation,

Compte tenu de son ambiguïté, le dernier appel conduit à une erreur de compilation. En effet,deux possibilités existent ici : convertir p en double sans modifier n et appeler essai I ou, aucontraire, convertir n en double sans modifier p et appeler essai II.

Exemple 3void test (int n=0, double x=0) ; // test Ivoid test (double y=0, int p=0) ; // test IIint n ; double z ; .....test(n,z) ; // appelle test I

LIVRE.book Page 131 Jeudi, 12. septembre 2019 6:19 18

Page 68: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7132

test(z,n) ; // appelle test IItest(n) ; // appelle test Itest(z) ; // appelle test IItest() ; // erreur de compilation, compte tenu de l’ambiguïté.

Exemple 4Avec ces déclarations :

void truc (int) ; // truc Ivoid truc (const int) ; // truc II

vous obtiendrez une erreur de compilation. En effet, C++ n’a pas prévu de distinguer int deconst int. Cela se justifie par le fait que, les deux fonctions truc recevant une copie de l’infor-mation à traiter, il n’y a aucun risque de modifier la valeur originale. Notez bien qu’icil’erreur tient à la seule présence des déclarations de truc, indépendamment d’un appel quel-conque.

Exemple 5En revanche, considérez maintenant ces déclarations :

void chose (int &) ; // chose Ivoid chose (const int &) ; // chose IIint n = 3 ;const int p = 5 ; // ou constexpr depuis C++11 .....chose (n) ; // appelle chose Ichose (p) ; // appelle chose II

Cette fois, la distinction entre int & et const int & est justifiée. En effet, on peut très bien ima-giner que chose I modifie la valeur de la lvalue dont elle reçoit la référence, tandis que choseII n’en fait rien.

Exemple 6L’exemple précédent a montré comment on pouvait distinguer deux fonctions agissant, l’unesur une référence, l’autre sur une référence à une constante. Mais l’utilisation de référencespossède des conséquences plus subtiles, comme le montrent ces exemples (revoyez éventuel-lement le paragraphe 5.3) :

void chose (int &) ; // chose Ivoid chose (const int &) // chose IIint n : float x ; .....chose (n) ; // appelle chose Ichose (2) ; // appelle chose II, après copie éventuelle de 2 dans un entier8 // temporaire dont la référence sera transmise à chosechose (x) ; // appelle chose II, apres conversion de la valeur de x en un // entier temporaire dont la référence sera transmise à chose

8. Comme l’autorise la norme, l’implémentation est libre de faire ou non une copie dans ce cas.

LIVRE.book Page 132 Jeudi, 12. septembre 2019 6:19 18

Page 69: Programmer en C++ moderne - fnac-static.com

11 - Surdéfinition de fonctions 133

chose (2*n+1) ; // appelle chose II, en lui transmettant la référence de l’empla- // cement temporaire contenant la valeur de l’expression 2*n+1

RemarqueComme le mode de transmission (référence ou valeur) n’intervient pas dans le choixd’une fonction surdéfinie, on pourrait penser que ces déclarations sont refusées : void chose (int &) ; // chose I - argugment de type int void chose (int) ; // chose II - argument d’un type compatible avec int

En réalité, les choses sont plus subtiles que cela puisque le rejet n’a lieu qu’en cas d’appelambigu : int n ; float x ; chose (x) ; // OK : seule chose II convient, après conversion de x en int chose (n) ; // NON - rejeté car chose I et chose II conviennent

Les choses sont encore plus étonnantes avec ces déclarations : void chose (int &) ; // chose I - argugment de type int void chose (const int &) ; // chose II - argugment de type compatible avec int void chose (int) ; // chose III - argument d’un type compatible avec int

Elles sont en effet acceptées mais tout appel de chose (avec un int, une constante ou uneexpression compatible avec int) sera rejeté compte tenu de son ambigüité.En pratique, on se limitera aux surdéfinitions des exemples 5 et 6 (référence et référence àune constante).

En Java

La surdéfinition des fonctions existe en Java. Mais les règles de recherche de la bonnefonction sont beaucoup plus simples qu’en C++, car il existe peu de possibilités de con-versions implicites.

11.3 Règles de recherche d’une fonction surdéfiniePour l’instant, nous vous présenterons plutôt la philosophie générale, ce qui sera suffisantpour l’étude des chapitres suivants. Au cours de cet ouvrage, nous serons amenés à vousapporter des informations complémentaires. De plus, l’ensemble de toutes ces règles sontreprises en Annexe A.

11.3.1 Cas des fonctions à un argumentLe compilateur recherche la « meilleure correspondance » possible. Bien entendu, pour pou-voir définir ce qu’est cette meilleure correspondance, il faut qu’il dispose d’un critère d’éva-luation. Pour ce faire, il est prévu différents niveaux de correspondance :1) Correspondance exacte : on distingue bien les uns des autres les différents types de base,

en tenant compte de leur éventuel attribut de signe9 ; de plus, comme on l’a vu dans les

LIVRE.book Page 133 Jeudi, 12. septembre 2019 6:19 18

Page 70: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7134

exemples précédents, l’attribut const peut intervenir dans le cas de références (il en ira demême pour les pointeurs).

2) Correspondance avec promotions numériques, c’est-à-dire essentiellement :char et short –> intfloat –> double

Rappelons qu’un argument transmis par référence ne peut être soumis à aucune conversion,sauf lorsqu’il s’agit de la référence à une constante.

3) Conversions dites standards : il s’agit des conversions légales en C++, c’est-à-dire de cel-les qui peuvent être imposées par une affectation (sans opérateur de cast) ; cette fois, ilpeut s’agir de conversions dégradantes puisque, notamment, toute conversion d’un typenumérique en un autre type numérique est acceptée.

4) D’autres niveaux sont prévus ; en particulier on pourra faire intervenir ce que l’on nommedes « conversions définies par l’utilisateur » (C.D.U.), qui ne seront étudiées qu’auchapitre 17.Là encore, un argument transmis par référence ne pourra être soumis à aucune conversion,sauf s’il s’agit d’une référence à une constante.

La recherche s’arrête au premier niveau ayant permis de trouver une correspondance, qui doitalors être unique. Si plusieurs fonctions conviennent au même niveau de correspondance, il ya erreur de compilation due à l’ambiguïté rencontrée. Bien entendu, si aucune fonction neconvient à aucun niveau, il y a aussi erreur de compilation.

11.3.2 Cas des fonctions à plusieurs argumentsL’idée générale est qu’il doit se dégager une fonction « meilleure » que toutes les autres. Pource faire, le compilateur sélectionne, pour chaque argument, la ou les fonctions qui réalisentla meilleure correspondance (au sens de la hiérarchie définie ci-dessus). Parmi l’ensembledes fonctions ainsi sélectionnées, il choisit celle (si elle existe et si elle est unique) qui réalise,pour chaque argument, une correspondance au moins égale à celle de toutes les autres fonc-tions10. Si plusieurs fonctions conviennent, là encore, on aura une erreur de compilation due àl’ambiguïté rencontrée. De même, si aucune fonction ne convient, il y aura erreur de compi-lation.Notez que les fonctions comportant un ou plusieurs arguments par défaut sont traitéescomme si plusieurs fonctions différentes avaient été définies avec un nombre croissantd’arguments.

9. Attention : en C++, char est différent de signed char et de unsigned char.10. Ce qui revient à dire qu’il considère l’intersection des ensembles constitués des fonctions réalisant la meilleurecorrespondance pour chacun des arguments.

LIVRE.book Page 134 Jeudi, 12. septembre 2019 6:19 18

Page 71: Programmer en C++ moderne - fnac-static.com

12 - Les fonctions et la déclaration auto (C++11) 135

12 Les fonctions et la déclaration auto (C++11)

12.1 Déclaration automatique du type des valeurs de retourDepuis C++11, on peut effectuer une déclaration automatique du type de la valeur de retour,afin que son type soit déduit automatiquement du type de l’expression fournie à l’instructionreturn, comme dans cet exemple :

auto f(int n) { ..... return n * n ; // le type renvoyé par f sera int }

Ici, le type de la valeur de retour sera déduit par le compilateur comme étant celui del’expression n * n, c’est-à-dire int.Avec cette autre définition :

auto g(int n) { ..... return 5.25 * n ; // le type renvoyé par f sera double }

il serait déduit comme étant double.On notera bien que auto doit alors obligatoirement être utilisé à la fois dans l’en-tête et dansle prototype de la fonction concernée. Ici, il faudra par exemple déclarer f de cette manière :

auto f(int n) ; // OK

et non :int f (int n) ; // NON

La démarche s’applique même lorsqu’il y a plusieurs instructions return, à condition que lesexpressions mentionnées soient du même type ; dans l’exemple suivant, le type déduit pourla valeur de retour sera double :

auto h(int n) { if(n%2==0) return 2*n+1 ; // OK : les deux expressions 2*n+1 else return 2*n ; // et 2*n sont de même type }auto h (int) { if(n%2==0) return 2*n+1 ; // NON : les deux expressions 2*n+1 else return 2.5*n ; // et 2.5*n ne sont pas de même type }

12.2 Déclaration automatique du type des arguments muetsToujours depuis C++11, on peut effectuer une déclaration automatique du type d’un argu-ment, lequel sera alors déduit du type de l’argument effectif fourni lors de l’appel. Voyez cetexemple :

void f(auto n) { ..... }int p = 5 ; double x = 5.2 ;f(p) ; // l’argument muet n sera du type de p : intf(x) ; // l’argument muet x sera du type de x : doublef(2.5*p) ; // l’argument muet n sera du type de l’expression 2.5*p : double

LIVRE.book Page 135 Jeudi, 12. septembre 2019 6:19 18

Page 72: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7136

12.3 Combinaison des deux possibilitésOn peut exploiter ces deux possibilités de déclaration automatique (valeur de retour et argu-ment muet) pour réaliser une fonction partiellement « générique », c’est-à-dire applicable àdifférents types non prévus dans sa définition, comme dans cet exemple :

// AutoFonctionsGeneriques#include <iostream>using namespace std ;auto f(auto n) { return n+5 ; }int main(){ int n = 3 ; cout << "f(3) = " << f(n) << endl ; double x = 2.6 ; cout << "f(2.6) = " << f(x) << endl ; char c = 'a' ; cout << "f('a') = " << f(c) << endl ;}

f(3) = 8f(2.6) = 7.6f('a') = f

Exemple de fonction générique

Notez que la généricité est partielle, car f ne peut être appelée que pour des types disposant del’opération d’addition.

13 Les fonctions déclarées constexpr (C++11)Nous avons déjà vu comment, depuis C++11, on peut distinguer les expressions constantes(qui ne varient pas au sein de la fonction concernée) des expressions calculables à la compi-lation déclarées avec constexpr.Cette notion de possibilité de calcul à la compilation va se généraliser aux fonctions définiespar l’utilisateur.Plus précisément, une fonction déclarée constexpr est une fonction dont le corps ne com-porte qu’une ligne de la forme return x où x, après substitution des valeurs des arguments, estune expression constante, comme dans cet exemple :

constexpr int carre (int n){ return n * n ;}

Voici un exemple d’utilisation de carre :const int n = 6 ; // expression constante, déjà en C++98constexpr int quantite = 12 + carre (n) ; // expression constante avec C++11

LIVRE.book Page 136 Jeudi, 12. septembre 2019 6:19 18

Page 73: Programmer en C++ moderne - fnac-static.com

14 - La référence d’une manière générale 137

Mais une fonction déclarée constexpr peut aussi être appelée avec des arguments non connusà la compilation. Elle se comporte alors comme une fonction ordinaire ; en voici un exempleutilisant la fonction constexpr carre précédente :

int p ; cin >> p ;int q1 = carre (p) ; // OK : p n'est pas constexpr ; la fonction carre // sera utilisée classiquement lors de l’exécutionconstexpr q2 = carre (p) ; // sera rejeté à la compilation // car q2 n’est pas une expression constante

À partir de C++14, les fonctions déclarées constexpr peuvent contenir des variables locales etplusieurs instructions exécutables (affectation, for, if, switch, while, do... while). En voici unexemple :

#include <iostream>using namespace std ;constexpr f (int a, int b){ int c = 0 ; if (a>0) c = a * b ; else c = a + b ; return c ;}int main(){ int n ; cout << "Donnez un entier : " ; cin >> n ; constexpr int val = f(3, 8) ; cout << "Resultat : " << val << endl ;}

Donnez un entier : 4Resultat : 24

Exemple de définition d’une fonction constexpr (depuis C++14)

14 La référence d’une manière généraleN.B. Ce paragraphe peut être ignoré dans un premier temps.L’essentiel concernant la notion de référence réside dans la transmission d’arguments ou devaleur de retour. Cependant, en toute rigueur, la notion de référence peut intervenir en dehorsde ces contextes comme nous allons le voir ici.

14.1 Déclaration de variables de type référenceD’une manière générale, il est possible de déclarer un identificateur comme référence d’uneautre variable. Considérez, par exemple, ces instructions :

int n ;int & p = n ;

LIVRE.book Page 137 Jeudi, 12. septembre 2019 6:19 18

Page 74: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7138

La seconde signifie que p est une référence à la variable n. Ainsi, dans la suite, n et p désigne-ront le même emplacement mémoire. Par exemple, avec :

n = 3 ;cout << p ;

nous obtiendrons la valeur 3.

RemarqueIl ne sera pas possible de définir des pointeurs sur des références, ni des tableaux de réfé-rences.

14.2 Initialisation de référenceLa déclaration :

int & p = n ;

est en fait une déclaration de référence (ici p) accompagnée d’une initialisation (à la réfé-rence de n). D’une façon générale, il n’est pas possible de déclarer une référence sans l’initia-liser, comme dans :

int & p ; // incorrect, car pas d’initialisation

Notez bien qu’une fois déclarée (et initialisée), une référence ne peut plus être modifiée.D’ailleurs, aucun mécanisme n’est prévu à cet effet : si, ayant déclaré int & p=n ; vous écri-vez p=q, il s’agit obligatoirement de l’affectation de la valeur de q à l’emplacement de réfé-rence p, et non de la modification de la référence q.On ne peut pas initialiser une référence avec une constante. La déclaration suivante estincorrecte :

int & n = 3 ; // incorrecte

Cela est logique puisque, si cette instruction était acceptée, elle reviendrait à initialiser n avecune référence à la valeur (constante) 3. Dans ces conditions, l’instruction suivante conduiraità modifier la valeur de la constante 3 :

n = 5 ;

En revanche, il est possible de définir des références à des constantes qui peuvent alors êtreinitialisées par des constantes. Ainsi la déclaration suivante est-elle correcte :

const int & n = 3 ;

Elle génère une variable temporaire (ayant une durée de vie imposée par l’emplacement de ladéclaration) contenant la valeur 3 et place sa référence dans n. On peut dire que tout se passecomme si vous aviez écrit :

int temp = 3 ;int & n = temp ;

avec cette différence que, dans le premier cas, vous n’avez pas explicitement accès à la varia-ble temporaire.Enfin, les déclarations suivantes sont encore correctes :

LIVRE.book Page 138 Jeudi, 12. septembre 2019 6:19 18

Page 75: Programmer en C++ moderne - fnac-static.com

15 - Les fonctions à arguments variables 139

float x ;const int & n = x ;

Elles conduisent à la création d’une variable temporaire contenant le résultat de la conversionde x en int et placent sa référence dans n. Ici encore, tout se passe comme si vous aviez écritceci (sans toutefois pouvoir accéder à la variable temporaire temp) :

float x ; int temp = x ;const int & n = temp ;

Remarques1 En toute rigueur, l’appel d’une fonction conduit à une « initialisation » des arguments

muets. Dans le cas d’une référence, ce sont donc les règles que nous venons de décrire quisont utilisées. Il en va de même pour une valeur de retour. On retrouve ainsi le comporte-ment décrit aux paragraphes 5.2 et 7.5.

2 Au chapitre 23, nous verrons que C++11 a également introduit la notion de référence àune rvalue (variable temporaire).

15 Les fonctions à arguments variablesGrâce à la surdéfinition, nous savons réaliser des fonctions recevant des arguments de typeset de nombre différents. Cependant, les possibilités d’appel en sont alors limitées par le jeudes fonctions prévues et les éventuelles conversions implicites susceptibles d’être mises enoeuvre. Ici, nous allons voir comment une même fonction peut disposer d’arguments quel-conques en nombre et en type.Avant C++11, on pouvait réaliser une fonction recevant un nombre quelconque d’arguments,éventuellement de types différents (mais qui devaient pouvoir être logiquement déduits lorsde l’écriture de la fonction). On recourait pour cela à des fonctions spéciales (va_start etva_arg) dont l’usage est dorénavant déconseillé mais que nous présenterons toutefois à la finde ce paragraphe pour vous permettre d’exploiter d’anciens codes. Depuis C++11, on peutrecourir à un nouveau type initializer_list qui permet de manipuler une liste d’un nombrequelconque de valeurs d’un même type (ou, plus généralement, d’un type compatible avec untype donné). Nous allons l’étudier ici. Plus tard (paragraphe 7 du chapitre 18), nous verronsqu’il existe des patrons de fonctions dits « variadiques » autorisant cette fois des argumentsde types différents, en nombre variable.

15.1 Le type initializer_list (C++11)C++11 définit donc un type dit « générique »11, noté initializer_list<T>, T étant un type quel-conque. Une variable d’un tel type est formée d’une liste de valeurs non modifiables d’untype implicitement compatible avec T.

11. Plus tard, nous verrons qu’il s’agit en fait d’un patron de classes.

LIVRE.book Page 139 Jeudi, 12. septembre 2019 6:19 18

Page 76: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7140

Par exemple, avec ces instructions :int n = 10 ;

initializer_list<int> li = { 1, 3, 5, n, n+5 } ;

on crée une liste de 5 valeurs formée des valeurs entières 1, 3, 5, 10 et 15.Nous verrons plus tard que ce nouveau type s’apparente aux « conteneurs » et qu’il dispose àce titre d’un mécanisme d’itération permettant d’en parcourir les valeurs. Pour l’instant, nousnous contenterons d’utiliser la forme généralisée de l’instruction for, indépendante de lanotion de conteneur, applicable aux séquences de valeurs (nous la retrouverons pour les vec-teurs, les chaînes ou les tableaux). Elle se présente ainsi :

for (int i : li) // ou encore for (auto i : li)

{ // utilisation de i, copie d’une valeur de li

}

Dans la boucle, i représente successivement une copie de chacune des valeurs de la liste li. Sion modifie i, cela n’affecte aucune valeur de la liste li.Voici un exemple de programme utilisant ces fonctionnalités :

// Initialiszer_list

#include <iostream>

using namespace std;

int main()

{ int n = 10 ; float x = 5.25 ;

initializer_list<int> li = {1, 3, 5, n, n+5, 'a'} ; // ’a’ est converti en int

initializer_list<double> ld = {1, 3, x, x*x } ; // 1 et 3 en double

cout << "liste li = " ;

for (int i : li) cout << i << " " ;

cout << endl ;

cout << "liste ld = " ;

for (double d : ld) cout << d << " " ;

cout << endl ;

// for (int & i :li) n++ ; interdit ici - voir remarque

}

liste li = 1 3 5 10 15 97

liste ld = 1 3 5.25 27.5625

Utilisation du type initializer_list

Remarques1 Dans le chapitre consacré au type string, nous verrons que la boucle for généralisée peut

aussi travailler directement avec les références des valeurs concernées et non, comme ici,sur une copie. Cependant, cette possibilité ne s’applique pas au type initializer_list car sesvaleurs ne sont pas modifiables.

LIVRE.book Page 140 Jeudi, 12. septembre 2019 6:19 18

Page 77: Programmer en C++ moderne - fnac-static.com

15 - Les fonctions à arguments variables 141

2 En théorie, la déclaration auto s’applique à des listes d’initialisation. Néanmoins, onconseille dans ce cas d’utiliser alors l’initialisation copie comme dans : auto l1 = {10, 20} ;

En effet, la forme suivante est acceptée en C++11 et rejetée en C++14 : auto l2 {10, 20} ;

Pour une liste à un seul élément, il est indispensable d’utiliser l’initialisation copie sil’on veut obtenir une liste et non une variable d’un type simple : auto l3 = {10} ; // OK auto l4 {10} ; // ici, l4 serait un int et non une initializer_list<int>

Il va de soi que auto ne convient pas pour une liste vide.

15.2 Application à une fonction à nombre variable d’arguments (C++11)

On peut donc utiliser ce type initializer_list pour transmettre à une fonction un nombre varia-ble d’arguments, soit de même type, soit d’un type implicitement compatible avec un typedonné. Ainsi, une fonction d’en-tête :

void f (initializer_list<float> val) // val est une liste de float

pourra être appelée avec une liste de valeur d’un type compatible avec float (char, int, long,float, double, ...).Voyez cet exemple dans lequel nous utilisons la fonction size pour obtenir le nombre d’élé-ments de la liste val (notez qu’on utilise la notation val.size() et non size(val) car, commenous le verrons plus tard, nous avons affaire à une fonction membre d’une classe).

// FonctionsArgVar1#include <iostream>using namespace std;void f (initializer_list<float> val) // val est une liste de float{ cout << val.size() << " Valeurs : " ; for (float v : val) cout << v << " " ; cout << endl ;}int main(){ f({1.5, 2.3}) ; // liste de 2 valeurs de type float f({}) ; // liste vide (attention f {} serait considéré comme f sans arguments) f ({1.25, 5, 9, 3.4, 7 }) ; // liste de 5 valeurs convertibles en float}

2 Valeurs : 1.5 2.30 Valeurs :5 Valeurs : 1.25 5 9 3.4 7

Utilisation d’une liste d’initialisation en argument d’une fonction

LIVRE.book Page 141 Jeudi, 12. septembre 2019 6:19 18

Page 78: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7142

On notera qu’on peut utiliser une liste vide, à condition de bien la noter ({}) et non simple-ment {} (qui serait interprété comme absence d’arguments) ou encore mélanger plusieurstypes dans un initialiseur, pour peu qu’ils soient compatibles par affectation avec le typeprévu.

15.3 Les anciennes fonctions va_start et va_argComme nous l’avons dit en introduction, avant C++11, la seule façon de réaliser une fonctionà arguments variables consistait à utiliser les fonctions particulières va_start et va_arg (dontle prototype figure dans le fichier en-tête cstdarg). La seule contrainte à respecter était que lafonction devait posséder au moins un argument fixe (c’est-à-dire toujours présent). En effet,comme nous allons le voir, c’était le dernier argument fixe qui permettait, en quelque sorte,d’initialiser le parcours de la liste d’arguments.Voici un exemple dans lequel les deux premiers arguments sont fixes, l’un étant de type int,l’autre de type char. Les arguments suivants, de type int, sont en nombre quelconque et l’on asupposé que le dernier d’entre eux était -1. Cette dernière valeur sert donc, en quelque sorte,de « sentinelle ». Par souci de simplification, nous nous sommes contentés, dans cette fonc-tion, de lister les valeurs de ces différents arguments (fixes ou variables), à l’exception dudernier.

// FonctArgVar2#include <iostream>#include <cstdarg> // pour va_arg et va_listusing namespace std ;void essai (int, char, ...) ;int main (){ cout << "++ Premier essai\n" ; essai (125, 'a', 15, 30, 40, -1) ; cout << "++ Deuxieme essai\n" ; essai (6264, 'S', -1) ;}void essai (int par1, char par2, ...){ va_list adpar ; int parv ; cout << " - premier parametre : " << par1 << endl ; cout << " - second parametre : " << par2 << endl ; va_start (adpar, par2) ; while ( (parv = va_arg (adpar, int) ) != -1) cout << " - argument variable : " << parv << endl ;}

++ Premier essai - premier parametre : 125 - second parametre : a - argument variable : 15 - argument variable : 30

LIVRE.book Page 142 Jeudi, 12. septembre 2019 6:19 18

Page 79: Programmer en C++ moderne - fnac-static.com

15 - Les fonctions à arguments variables 143

- argument variable : 40++ Deuxieme essai - premier parametre : 6264 - second parametre : S*/

Arguments en nombre variable, délimités par une sentinelle

Vous constatez la présence, dans l’en-tête de la fonction essai, des deux noms des paramètresfixes par1 et par2, déclarés de manière classique ; les trois points servent à spécifier au com-pilateur l’existence de paramètres en nombre variable.La déclaration :

va_list adpar ;

précise que adpar est un identificateur de liste variable. C’est lui qui nous servira à récupérer,les uns après les autres, les différents arguments variables.Comme à l’accoutumée, une telle déclaration n’attribue aucune valeur à adpar. C’est effecti-vement la fonction va_start qui va permettre de l’initialiser à l’adresse du paramètre variable.Notez bien que cette dernière est déterminée par va_start à partir de la connaissance du nomdu dernier paramètre fixe.Le rôle de la fonction va_arg est double :• d’une part, elle fournit comme résultat la valeur trouvée à l’adresse courante fournie par ad-

par (son premier argument), suivant le type indiqué par son second argument (ici int) ;• d’autre part, elle incrémente l’adresse contenue dans adpar, de manière que celle-ci pointe

alors sur l’argument variable suivant.Ici, une instruction while nous permet de récupérer les différents arguments variables, sachantque le dernier a pour valeur -1.Enfin, la norme prévoit que la macro va_end doit être appelée avant de sortir de la fonctionconcernée. Si vous manquez à cette règle, vous courez le risque de voir un prochain appel à lafonction conduire à un mauvais fonctionnement du programme.

Remarques1 Les arguments variables peuvent être de types différents, à condition toutefois que la

fonction soit en mesure de les connaître, d’une façon ou d’une autre.2 La gestion de la fin de la liste des arguments variables est laissée au bon soin de

l’utilisateur ; en effet, il n’existe aucune fonction permettant de connaître le nombreeffectif de ces arguments. Cette gestion peut se faire :– par sentinelle, comme dans notre précédent exemple ;– par transmission, en argument fixe, du nombre d’arguments variables.

LIVRE.book Page 143 Jeudi, 12. septembre 2019 6:19 18

Page 80: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7144

Informations complémentaires

En toute rigueur, va_start et va_arg ne sont pas de véritables fonctions, mais des« macros » ; cette distinction n’a que peu d’incidence sur leur utilisation effective. Lesmacros, beaucoup moins utilisées en C++ qu’en C, seront présentées au paragraphe 2.2du chapitre 33.

16 Conséquences de la compilation séparée16.1 Compilation séparée et prototypes

Nous avons déjà été amenés à utiliser des fonctions prédéfinies telles que sqrt. Pour ce faire,nous incorporions le fichier en-tête cmath qui contient les déclarations des fonctions mathé-matiques telles que sqrt. Nous savons que le module objet correspondant à cette fonction adéjà été compilé, qu’il figure dans une bibliothèque et qu’il sera incorporé par l’éditeur deliens pour créer le programme exécutable.Cette démarche, dans laquelle on réunit plusieurs modules objets compilés de façon indépen-dante l’une de l’autre (ici votre main d’une part, sqrt d’autre part) peut s’appliquer à desfichiers sources conçus par l’utilisateur. On parle alors de « compilation séparée ». Par exem-ple, vous pourriez tout à fait placer dans des fichiers sources différents les fonctions que nousavons été amenés à créer auparavant (fexple, echange, optimist, fct) et les compiler séparé-ment. Dans ce cas, la définition de la fonction ne figure plus dans le programme l’utilisant.On n’y trouvera que sa déclaration. Si certaines des fonctions que vous développez doiventêtre utilisées par plusieurs programmes, vous serez probablement amené à prévoir un fichieren-tête (nommé par exemple MesFonc) en contenant les déclarations, de façon à éviterd’éventuelles erreurs d’écriture (ou plutôt un fichier en-tête par groupe de fonctions ayant unrapport entre elles). Généralement, un tel fichier portera l’extension .h. Comme pour lesfichiers en-têtes prédéfinis, vous incorporerez votre fichier en-tête par une directive #include.Il faut cependant savoir que la syntaxe en est légèrement modifiée pour les fichiers de l’utili-sateur (utilisation de "…" au lieu de <...>) :

include "MesFonc.h"

Généralement, l’inclusion d’un fichier en-tête se fait à un niveau global comme dans ceschéma :

#include "MesFonc.h" // Attention : "MesFonc.h" et non <MesFonc.h> // pour un fichier en-tête utilisateurint main (){ ... // ici, on dispose des déclarations figurant dans MesFonc.h }void f(){ // ici, également}

LIVRE.book Page 144 Jeudi, 12. septembre 2019 6:19 18

Page 81: Programmer en C++ moderne - fnac-static.com

16 - Conséquences de la compilation séparée 145

Bien que cela soit peu utilisé, il est possible, en théorie, d’effectuer une inclusion à un niveaulocal, comme dans ce schéma :

int main (){ #include "MesFonc.h" ... // ici, on dispose des déclarations figurant dans MesFonc.h }void f(){ // ici, non}

16.2 Fonction manquante lors de l’édition de liensCompte tenu de ces possibilités de compilation séparée, on voit qu’il est tout à fait possibled’écrire un programme dans lequel on a omis la définition d’une fonction (pour peu qu’ellesoit correctement déclarée) :

void f() ; // déclaration de fint main (){ ..... f() ; // utilisation de f .....}

La compilation se déroulera sans problème. En revanche, si lors de l’édition de liens, la défi-nition de f n’est trouvée dans aucun module objet (y compris dans ceux constituant la biblio-thèque standard), on obtiendra une erreur.

RemarqueNe confondez pas les fichiers en-tête qui ne contiennent que les déclarations de fonctionsavec les modules objets qui, quant à eux, contiennent bien le code exécutable correspon-dant à leur définition. L’un ne remplace pas l’autre. Certes, la confusion peut être entrete-nue par les fonctions de la bibliothèque standard dont on a l’impression qu’il suffit deciter les fichiers en-têtes contenant leur déclaration pour en disposer. En fait, le travail derecherche de l’éditeur de liens est totalement indépendant de la compilation et n’a aucunrapport avec l’éventuelle inclusion de fichiers en-tête. Vous pourriez par exemple utilisersqrt, sans incorporer cmath, pour peu que vous en fournissiez la déclaration.

16.3 Le mécanisme de la surdéfinition de fonctionsDans notre étude de la surdéfinition des fonctions du paragraphe 11, nous avions examiné lamanière dont le compilateur faisait le choix de la « bonne fonction », en raisonnant sur unseul fichier source à la fois. Mais on voit maintenant qu’il est tout à fait envisageable :• de compiler dans un premier temps un fichier source contenant les différentes définitions

d’une fonction (telle que sosie ou chose dans nos précédents exemples) ; on peut même écla-ter ces surdéfinitions dans plusieurs fichiers sources ;

LIVRE.book Page 145 Jeudi, 12. septembre 2019 6:19 18

Page 82: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7146

• d’utiliser ultérieurement ces fonctions dans un autre fichier source en nous contentant d’enfournir les prototypes.

Or, pour que cela soit possible, l’éditeur de liens doit être en mesure d’effectuer le lien entrele choix opéré par le compilateur et la « bonne fonction » figurant dans un autre moduleobjet. Cette reconnaissance est fondée sur la modification, par le compilateur, des noms« externes » des fonctions ; celui-ci fabrique un nouveau nom fondé d’une part sur le nominterne de la fonction, d’autre part sur le nombre et la nature de ses arguments.Il est très important de noter que ce mécanisme s’applique à toutes les fonctions, qu’ellessoient surdéfinies ou non (il est impossible de savoir si une fonction compilée dans un fichiersource sera surdéfinie dans un autre). On voit donc qu’un problème se pose, dès que l’on sou-haite utiliser dans un programme C++ une fonction écrite et compilée en C (ou dans un autrelangage utilisant les mêmes conventions d’appel de fonction, notamment l’assembleur ou leFortran). En effet, une telle fonction n’aura pas son nom modifié suivant le mécanisme évo-qué. Une solution existe toutefois : déclarer une telle fonction en faisant précéder son proto-type de la mention extern "C". Par exemple, si nous avons écrit et compilé en C une fonctiond’en-tête :

double fct (int n, char c) ;

et que nous souhaitons l’utiliser dans un programme C++, il nous suffira de fournir son pro-totype de la façon suivante :

extern "C" double fct (int, char) ;

Remarques1 Il existe une forme « collective » de la déclaration extern, qui se présente ainsi :

extern "C" { void exple (int) ; double chose (int, char, float) ; ..... } ;

2 Le problème évoqué pour les fonctions C (assembleur ou Fortran) se pose, a priori,pour toutes les fonctions de la bibliothèque standard C que l’on réutilise en C++. Enfait, dans la plupart des environnements, cet aspect est automatiquement pris en chargeau niveau des fichiers en-tête correspondants (ils contiennent des déclarations externconditionnelles).

3 Il est possible d’employer, au sein d’un même programme C++, une fonction C (assem-bleur ou Fortran) et une ou plusieurs autres fonctions C++ de même nom (mais d’argu-ments différents). Par exemple, nous pouvons utiliser dans un programme C++ lafonction C fct précédente et deux fonctions C++ d’en-tête : void fct (double x) void fct (float y)

en procédant ainsi :

LIVRE.book Page 146 Jeudi, 12. septembre 2019 6:19 18

Page 83: Programmer en C++ moderne - fnac-static.com

16 - Conséquences de la compilation séparée 147

extern "C" void fct (int) ; void fct (double) ; void fct (float) ;

Suivant la nature de l’argument d’appel de fct, il y aura bien appel de l’une des troisfonctions fct. Notez qu’il n’est pas possible de mentionner plusieurs fonctions C denom fct.

16.4 Compilation séparée et variables globalesN.B. Ce paragraphe a surtout un intérêt si vous devez exploiter du code utilisant cette techni-que déconseillée de variables globales. Il peut également servir à distinguer la notion de por-tée (compilation) de celle de lien (édition de liens).

16.4.1 La portée d’une variable globale – la déclaration externA priori, la portée d’une variable globale semble limitée au fichier source dans lequel elle aété définie. Ainsi, supposez que l’on compile séparément ces deux fichiers source :

source 1 source 2 int x ; void fct2() { ..... } int main () { ..... } void fct3() { ..... } void fct1() { ..... }

Il ne semble pas possible, dans les fonctions fct2 et fct3 de faire référence à la variable globalex déclarée dans le premier fichier source (alors qu’aucun problème ne se poserait si l’on réu-nissait ces deux fichiers source en un seul, du moins si l’on prenait soin de placer lesinstructions du second fichier à la suite de celles du premier).En fait, C++ prévoit une déclaration permettant de spécifier qu’une variable globale a déjàété définie dans un autre fichier source. Celle-ci se fait à l’aide du mot-clé extern. Ainsi, enfaisant précéder notre second fichier source de la déclaration :

extern int x ;

il devient possible de mentionner la variable globale x (déclarée dans le premier fichiersource) dans les fonctions fct2 et fct3.

RemarqueCette déclaration extern n’effectue pas de réservation d’emplacement de variable. Ellene fait que préciser que la variable globale x est définie par ailleurs et elle en indique letype.

16.4.2 Les variables globales et l’édition de liensSupposons que nous ayons compilé les deux fichiers source précédents et voyons d’un peuplus près comment l’éditeur de liens est en mesure de rassembler correctement les deuxmodules objets ainsi obtenus. En particulier, examinons comment il peut faire correspondreau symbole x du second fichier source l’adresse effective de la variable x définie dans le pre-mier.

LIVRE.book Page 147 Jeudi, 12. septembre 2019 6:19 18

Page 84: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7148

D’une part, après compilation du premier fichier source, on trouve, dans le module objet cor-respondant, une indication associant le symbole x et son adresse dans le module objet. Autre-ment dit, contrairement à ce qui se produit pour les variables locales, pour lesquelles nesubsiste aucune trace du nom après compilation, le nom des variables globales continue àexister au niveau des modules objets. On retrouve là un mécanisme analogue à ce qui sepasse pour les noms de fonctions, lesquels doivent bien subsister pour que l’éditeur de lienssoit en mesure de retrouver les modules objets correspondants.D’autre part, après compilation du second fichier source, on trouve, dans le module objet cor-respondant, une indication mentionnant qu’une certaine variable de nom x provient de l’exté-rieur et qu’il faudra en fournir l’adresse effective.Ce sera effectivement le rôle de l’éditeur de liens que de retrouver dans le premier moduleobjet l’adresse effective de la variable x et de la reporter dans le second module objet.Ce mécanisme montre que s’il est possible, par mégarde, de réserver des variables globalesde même nom dans deux fichiers source différents, il sera, par contre, en général, impossibled’effectuer correctement l’édition de liens des modules objets correspondants (certains édi-teurs de liens peuvent ne pas détecter cette anomalie). En effet, dans un tel cas, l’éditeur deliens se trouvera en présence de deux adresses différentes pour un même identificateur, ce quiest illogique.

16.4.3 Les variables globales cachées – la déclaration staticIl est possible de « cacher » une variable globale, c’est-à-dire de la rendre inaccessible àl’extérieur du fichier source où elle a été définie (on dit aussi « rendre confidentielle » au lieude « cacher » ; on parle alors de « variables globales confidentielles »). Il suffit pour cela d’utiliserla déclaration static comme dans cet exemple :

static int a ; int main () { ..... } void fct() { ..... }

Sans la déclaration static, a serait une variable globale ordinaire. Par contre, cette décla-ration demande qu’aucune trace de a ne subsiste dans le module objet résultant de ce fichiersource. Il sera donc impossible d’y faire référence depuis une autre source par extern. Mieux,si une autre variable globale apparaît dans un autre fichier source, elle sera acceptée à l’édi-tion de liens puisqu’elle ne pourra pas interférer avec celle du premier source.

17 La spécification inlineComme on peut s’y attendre, le code exécutable correspondant à une fonction est généré uneseule fois par le compilateur. Néanmoins, pour chaque appel de cette fonction, le compilateurdoit prévoir, non seulement le branchement au code exécutable correspondant, mais égale-ment des instructions utiles pour établir la communication entre le programme appelant et lafonction, notamment :• sauvegarde de l’état courant (valeurs de certains registres de la machine, par exemple) ;

LIVRE.book Page 148 Jeudi, 12. septembre 2019 6:19 18

Page 85: Programmer en C++ moderne - fnac-static.com

17 - La spécification inline 149

• allocation d’espace sur la pile et copie des valeurs des arguments ;• branchement à la fonction (dont l’adresse définitive sera en fait fournie par l’éditeur de

liens) ;• recopie de la valeur de retour ;• restauration de l’état courant et retour dans le programme appelant.Dans le cas de « petites fonctions », ces différentes instructions de « service » peuvent repré-senter un pourcentage important du temps d’exécution total de la fonction. Lorsque l’effica-cité du code devient un critère important, C++ vous permet de gagner du temps dans l’appeldes fonctions, au détriment de la taille du code, grâce à la spécification inline.Voyez cet exemple :

// FonctionEnLigne#include <cmath> // pour sqrt#include <iostream>using namespace std ; /******* declaration definition d’une fonction en ligne ********/inline double norme (double vec[3]){ double s = 0 ; for (int i=0 ; i<3 ; i++) s+= vec[i] * vec[i] ; return sqrt(s) ;} /******* exemple d’utilisation d’une fonction en ligne *********/int main () { double v1[3], v2[3] ; for (int i=0 ; i<3 ; i++) { v1[i] = i ; v2[i] = 2*i-1 ; } cout << "norme de v1 : " << norme(v1) << endl ; cout << "norme de v2 : " << norme(v2) << endl ;}

norme de v1 : 2.23607norme de v2 : 3.31662

Exemple de définition et d’utilisation d’une fonction en ligne

La fonction norme a pour but de calculer la norme d’un vecteur à trois composantes qu’on luifournit en argument.La présence du mot inline demande au compilateur de traiter la fonction norme différemmentd’une fonction ordinaire. À chaque appel de norme, le compilateur devra incorporer au seindu programme les instructions correspondantes (en langage machine12). Le mécanisme habi-

12. Notez qu’il s’agit bien ici d’un travail effectué par le compilateur lui-même, alors que dans le cas d’une macro,un travail comparable était effectué par le préprocesseur.

LIVRE.book Page 149 Jeudi, 12. septembre 2019 6:19 18

Page 86: Programmer en C++ moderne - fnac-static.com

Les fonctionsCHAPITRE 7150

tuel de gestion de l’appel et du retour n’existera plus (il n’y a plus besoin de sauvegardes,recopies...), ce qui permet une économie de temps. En revanche, les instructions correspon-dantes seront générées à chaque appel, ce qui consommera une quantité de mémoire croissantavec le nombre d’appels.Il est très important de noter que, par sa nature même, une fonction en ligne doit être définiedans le même fichier source que celui où on l’utilise. Elle ne peut plus être compiléeséparément ! Cette absence de possibilité de compilation séparée constitue une contrepartienotable aux avantages offerts par la fonction en ligne. En effet, pour qu’une même fonctionen ligne puisse être partagée par différents programmes, il faudra absolument la placer dansun fichier en-tête13.En revanche, cette définition d’une fonction en ligne joue bien le rôle d’une déclaration, desorte qu’il n’est pas nécessaire d’en prévoir une déclaration supplémentaire.

Remarques1 La déclaration inline constitue une demande effectuée auprès du compilateur. Ce dernier

peut éventuellement (par exemple, si la fonction est volumineuse) ne pas l’introduire enligne et en faire une fonction ordinaire. De même, si vous utilisez quelque part (au sein dufichier source concerné) l’adresse d’une fonction déclarée inline, le compilateur en feraune fonction ordinaire (dans le cas contraire, il serait incapable de lui attribuer uneadresse et encore moins de mettre en place un éventuel appel d’une fonction située à cetteadresse).

2 Notez que, si nous avions fourni la définition de norme après celle de main, il aurait éténécessaire de déclarer notre fonction, comme n’importe quelle autre, en utilisant égale-ment le mot-clé inline : inline double norme (double [3])

Nous avons volontairement évité ici de procéder de cette manière car, dès lors qu’unefonction en ligne figure dans un fichier en-tête, son incorporation précédera son utilisa-tion.

Informations complémentaires

C++ a hérité de C la possibilité de définir des « macros ». Il s’agit d’instructions fourniesau préprocesseur qui effectue alors des substitutions paramétrées de texte. La macros’appelle comme une fonction (d’ailleurs, certaines des « fonctions » de la bibliothèquestandard du C sont des macros) et elle présente quelques similitudes avec l’emploi deinline :

13. À moins d’en écrire plusieurs fois la définition, ce qui ne serait pas « raisonnable », compte tenu des risquesd’erreurs que cela comporte.

LIVRE.book Page 150 Jeudi, 12. septembre 2019 6:19 18

Page 87: Programmer en C++ moderne - fnac-static.com

18 - Terminaison d’un programme 151

– le code correspondant est introduit à chaque appel (au niveau du préprocesseur, cettefois, et non plus au niveau du compilateur) ;

– on obtient un gain de temps d’exécution, en contrepartie d’une perte d’espace mémoi-re.

Mais la ressemblance s’arrête là, car l’emploi des macros présente de très grands ris-ques (notamment d’effets de bord). C’est ce qui explique que les macros soient décon-seillées en C++ (inline n’existe pas en C !). Nous étudierons les macros au paragraphe2.2 du chapitre 33.

18 Terminaison d’un programmeNous avons déjà vu qu’on peut arrêter le déroulement de la fonction main par une instructionreturn, accompagnée d’une valeur entière indiquant si l’exécution s’est déroulée convenable-ment (valeur 0) ou non (valeur non nulle). On peut placer autant d’instructions return dans lemain qu’on le ferait dans une fonction usuelle.Mais, si une instruction return dans le main met fin à l’exécution du programme, il n’en vaplus de même pour une instruction return figurant dans une fonction autre que main. Or, danscertaines situations, en général en cas d’erreur importante, on aimera pouvoir, depuis unefonction, mettre fin à l’exécution du programme, sans devoir « remonter » la pile des diffé-rents appels. Il est alors possible de faire appel à la fonction standard exit (prototype danscstdlib), à laquelle on fournit, ici encore, un entier utilisable comme compte rendu du dérou-lement du programme.

Informations complémentaires

En toute rigueur, la fonction exit ferme les fichiers ouverts, détruit convenablement lesobjets dynamiques, mais pas les objets automatiques. Néanmoins, on considère quel’appel de cette fonction correspond à une « fin normale » du programme. La valeur del’argument précise s’il s’agit d’une fin sans aucun échec (0) ou d’une fin avec échec.

Il existe une autre fonction abort (prototype dans cstdlib), utilisée pour des fins anor-males, qui peut être appelée par certaines fonctions de la bibliothèque en cas de diffi-cultés majeures compromettant la bonne poursuite de l’exécution (nous en verrons unexemple dans le cadre de la gestion des exceptions). Celle-ci met fin brutalement auprogramme, sans aucune intervention au niveau des fichiers ou des objets.

Enfin, il est possible de demander qu’une ou plusieurs fonctions de son choix soientexécutées lors de la fin d’un programme, en utilisant la fonction atexit14.

14. On trouvera plus d’informations sur ces fonctions, héritées du langage C, dans Le guide complet du langage C,du même auteur, chez le même éditeur.

LIVRE.book Page 151 Jeudi, 12. septembre 2019 6:19 18

Page 88: Programmer en C++ moderne - fnac-static.com

LIVRE.book Page 152 Jeudi, 12. septembre 2019 6:19 18

Page 89: Programmer en C++ moderne - fnac-static.com

11 Les vecteurs, les tableaux natifs

et les chaînes C

En programmation, on parle de tableau pour représenter un ensemble d’éléments de mêmetype, désigné par un nom unique, chaque élément étant repéré par un « indice » précisant saposition au sein de l’ensemble.Comme tous les langages, C++ permet de manipuler des tableaux. Mais les outils pour lefaire se sont enrichis au fil des différentes versions du langage. Avant la norme C++98, ondisposait de ce que nous appelerons les « tableaux natifs » ; il s’agissait de tableaux dont lataille (nombre d’éléments) était fixe et définie à la compilation. Comme nous le verrons, ilssouffraient de graves lacunes en ce qui concerne leur transmission en argument d’une fonc-tion. Avec la norme C++98 est apparue la bibliothèque standard, laquelle dispose d’un typevector qui présente l’avantage d’être à la fois de taille modifiable et facilement transmissibleà une fonction.Nous commencerons par ce dernier en l’introduisant sur un exemple simple qui en montrel’intérêt. Nous étudierons ensuite les principales propriétés de ces vecteurs (le chapitre 27 enfera une présentation plus approfondie).Puis nous aborderons les tableaux natifs à une ou plusieurs dimensions et la manière de lesinitialiser lors de leur déclaration. Nous verrons en quoi ils sont liés aux pointeurs natifs, àsavoir qu’un nom de tableau est un pointeur constant, ce qui nous amènera, au passage, àfournir quelques compléments sur l’arithmétique des pointeurs natifs. Nous montreronsensuite comment exploiter cette particularité pour transmettre un tableau natif en argument

LIVRE.book Page 207 Jeudi, 12. septembre 2019 6:19 18

Page 90: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11208

d’une fonction. Nous aborderons alors la gestion de tableaux dynamiques, soit à l’aide depointeurs natifs, soit à l’aide de pointeurs intelligents.Nous étudierons ensuite ce que l’on nomme généralement les « chaînes de style C », à savoirune convention de représentation de chaînes, héritée du langage C, dont il subsiste encorequelques traces dans C++, malgré une apparente redondance avec le type string.

1 Les vecteurs

1.1 Exemple de présentation des vecteursSupposons que nous souhaitions déterminer, à partir de notes d’élèves, fournies en don-nées, combien d’entre elles sont supérieures à la moyenne de la classe.S’il ne s’agissait que de calculer simplement la moyenne de ces notes, il nous suffirait d’encalculer la somme, en les cumulant dans une variable, au fur et à mesure de leur lecture.Mais, ici, il nous faut à nouveau pouvoir consulter les notes pour déterminer combien d’entreelles sont supérieures à la moyenne ainsi obtenue. Il est donc nécessaire de pouvoir mémori-ser ces notes.Pour ce faire, il n’est pas possible de prévoir autant de variables scalaires différentes qu’il y ade notes puisque nous n’en connaissons pas le nombre. Et même si ce nombre était connu,cette démarche ne serait guère envisageable dès que le nombre de notes est important. Levecteur va offrir une solution convenable à ce problème en donnant un nom unique àl’ensemble des notes et en repérant chaque note par un numéro nommé indice. C’est ce quemontre le programme suivant (notez l’inclusion du fichier en-tête vector) :

// ExempleVector

#include <iostream>

#include <vector> // pour le type vector

using namespace std ;

int main()

{ unsigned int nb ; // nombre de notes

cout << "Donnez le nombre de notes : " ; cin >> nb ;

vector <float> notes (nb) ; // pour les nb notes

for (unsigned int i=0 ; i<nb ; i++) // lecture des notes

{ cout << "Donnez la note numero " << i+1 << " : " ;

cin >> notes[i] ;

}

float som = 0 ; // calcul de la somme des notes

for (unsigned int i=0 ; i<nb ; i++) som += notes[i] ;

// ou (C++11 - voir § 1.4) : for (auto note:notes) som += note ;

float moy = som / nb ;

cout << "Moyenne de la classe : " << moy << endl ;

LIVRE.book Page 208 Jeudi, 12. septembre 2019 6:19 18

Page 91: Programmer en C++ moderne - fnac-static.com

1 - Les vecteurs 209

int nbm = 0 ; // calcul nb notes >= moyenne

for (unsigned int i=0 ; i<nb ; i++ ) if (notes[i] > moy) nbm++ ;

// ou (C++11 - voir § 1.4) : for (auto note:notes) if (note>moy) nbm ++ ;

cout << nbm << " eleves ont plus de cette moyenne" << endl;

}

Donnez le nombre de notes : 6

Donnez la note numero 1 : 12.5

Donnez la note numero 2 : 11

Donnez la note numero 3 : 8.25

Donnez la note numero 4 : 13.5

Donnez la note numero 5 : 15.25

Donnez la note numero 6 : 13

Moyenne de la classe : 12.25

4 eleves ont plus de cette moyenne

Exemple d’utilisation d’un vecteur

L’exécution de l’instruction :vector <float> notes (nb) ;

crée un vecteur de nb éléments (on dit aussi de dimension nb) de type float. Chaque élémentde ce vecteur est repéré par un « indice » indiquant sa position dans le vecteur. Convention-nellement, comme pour les chaînes, le premier élément porte le numéro 0. La première notesera donc désignée par notes[0], la troisième par notes[2], la dernière par notes[nb-1]. Plusgénéralement, notes[i] désigne un élément dont la position est fournie par la valeur de i etreprésente donc une valeur de type float.Notez bien que le vecteur est créé avec un nombre d’éléments égal à la valeur de nb aumoment où l’on exécute l’instruction de déclaration. Si la valeur de nb évolue par la suite,cela ne modifiera nullement le nombre d’éléments du vecteur.

1.2 Les éléments et les indices d’un vecteur1.2.1 Quelques règles

Comme on le voit, le type vecteur se définit à l’aide du mot vector, suivi entre crochets angu-laires du type de ses éléments, lesquels sont donc tous d’un même type. La dimension (nom-bre d’éléments) est fournie sous forme d’une expression entière dont la valeur est calculée aumoment de l’exécution de l’instruction de déclaration correspondante.Un élément de vecteur est une lvalue modifiable. Il peut donc apparaître à gauche d’un opé-rateur d’affectation comme dans (t étant supposé de type vector<int>) :

t[2] = 5

Il peut aussi apparaître comme opérande d’un opérateur d’incrémentation, comme dans :t[3]++ --t[i]

LIVRE.book Page 209 Jeudi, 12. septembre 2019 6:19 18

Page 92: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11210

Notez toutefois, que les éléments de t ne seraient pas modifiables si l’on avait déclaré :const vector<int> t

Un indice peut prendre la forme de n’importe quelle expression arithmétique d’un type entierquelconque (int, short, long avec leurs variantes non signées). Par exemple, si n, p, k et j sontde type int et t de type vector<int>, ces notations sont correctes :

t[n-3]t[3*p-2*k+j%l]

Il en va de même, si c1 et c2 sont de type char, de :t[c1+3]t[c2-c1]

puisque les expressions correspondantes sont de type int.En théorie, un indice peut également être de type caractère ; dans ce cas, sa valeur sera sim-plement convertie en int suivant les règles habituelles (attention à l’attribut de signe !). Enrevanche, la tentative d’utilisation d’indices de type flottant conduira à une erreur de compi-lation.

1.2.2 Absence de contrôle d’indiceIl est très important de noter qu’aucun contrôle n’est effectué sur la valeur de l’indice lorsquel’on cherche à accéder à un vecteur à l’aide de l’opérateur [] comme nous l’avons faitjusqu’ici. Notez, que, pour être efficace, ce contrôle d’indice devrait pouvoir se faire, nonseulement dans le cas où l’indice est une constante, mais également dans tous les cas où ils’agit d’une expression quelconque. Cela nécessiterait l’incorporation, dans le programmeobjet, d’instructions supplémentaires assurant cette vérification lors de l’exécution, ce quiconduirait à une perte de temps. Pour comprendre les conséquences d’un tel risque de« débordement », il faut savoir que les éléments d’un vecteur sont rangés en mémoire defaçon consécutive. Lorsque le compilateur rencontre une lvalue telle que t[i], il en déterminel’adresse en ajoutant à l’adresse de début du vecteur t, un décalage proportionnel à la valeurde i (et aussi proportionnel à la taille de chaque élément du vecteur).Voici un exemple de programme quelque peu disuasif :

//DebordementVector#include <iostream>using namespace std;#include <vector>int main(){ const int N = 10 ; // ou constexpr depuis C++11 vector<int> t(N) ; t[12] = 5 ; // on modifie un emplacement situe hors du tableau cout << "t[12] = " << t[12] << endl ; cout << "t[100] = " << t[100] << endl ; // t[100] est en dehors de t cout << "t[-3] = " << t[-3] << endl ; // t[-3] est en dehors de t}

t[12] = 5

LIVRE.book Page 210 Jeudi, 12. septembre 2019 6:19 18

Page 93: Programmer en C++ moderne - fnac-static.com

1 - Les vecteurs 211

t[100] = 0t[-3] = 196800

Conséquences de l’absence de contrôle d’indice de l’opérateur []

Le vecteur t a été créé ici avec 10 éléments. Le programme peut donner l’illusion qu’il enpossède d’avantage, mais il n’en est rien. Les éléments d’indices 12, 100 et -3 se trouventsimplement « quelque part » en dehors de la zone prévue pour le tableau.

Remarques1 Il existe une autre manière d’accéder aux éléments d’un vecteur dont nous parlerons au

chapitre 27 (utilisation de la fonction at), laquelle conduit bien à un contrôle des indices.2 Voyez l’instruction suivante (dans laquelle nous avons malencontreusement remplacé

les crochets par des parenthèses) : vector<int> t[10] ;

Elle ne provoque par d’erreur de compilation car, comme nous le verrons plus loin, tsera alors un tableau natif de 10 éléments, chacun du type vector<int> et vide (0 élé-ments).

3 Nous avons déjà eu l’occasion de distinguer les données statiques, automatiques etdynamiques. A priori, on peut penser qu’un vecteur déclaré dans une fonction commedans nos précédents exemples, est de classe automatique. En fait, l’emplacement néces-saire à ses éléments est alloué dynamiquement dans le tas (et libéré à la sortie de lafonction) et sur la pile, on ne trouve que deux informations : dimension du vecteur,adresse du premier élément dans le tas. Les vecteurs permettent donc de réaliser impli-citement une gestion dynamique, sans avoir à la programmer explicitement. La mêmeremarque s’appliquerait à un vecteur statique dont les éléments seraient alloués dyna-miquement en début de programme et libérés à sa fin.

1.3 Initialisation d’un vecteurLorsque vous exécutez la déclaration :

vector<int> v(10) ;

le vecteur v voit ses éléments initialisés à 0. Il s’agit là d’une différence importante avec lesvariables simples qui, comme nous l’avons vu, n’étaient pas initialisées. Nous verrons plustard que cela tient à ce qu’un vecteur est un objet, lequel est initialisé par appel de ce que l’onnomme un constructeur.Il existe d’autres façons de créer un vecteur que nous examinons maintenant.• On peut créer un vecteur de taille donnée, en initialisant tous ses éléments avec une valeur

donnée (compatible avec le type déclaré pour les éléments), comme dans (n étant de typeint, x une valeur de type double ou compatible) :vector<double> v (n, 0.5) ; // n éléments de type double initialisés à 0.5vector<string> vx (3, "xxx") ; // 3 éléments de type string initialisés à "xxx"

LIVRE.book Page 211 Jeudi, 12. septembre 2019 6:19 18

Page 94: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11212

Notez que, comme pour les chaînes, l’utilisation des accolades (C++11) ici conduirait à unetoute autre signification (une liste de valeurs) comme nous le verrons un peu plus loin.

• On peut également créer un vecteur « vide », c’est-à-dire ne contenant aucun élément :vector <string> v ; // vecteur de chaînes, ne contenant aucun élément

Bien entendu, cette dernière possibilité ne présentera d’intérêt que dans l’un des cassuivants :

– on sera amené par la suite à affecter à v la valeur d’un autre vecteur (de type vec-tor<string>) comme nous le verrons au paragraphe 1.5

– on sera amené à ajouter des éléments à ce vecteur, comme nous apprendrons à le fairedans le chapitre 27.

• Depuis C++11, on peut fournir explicitement toutes les valeurs initiales (d’un type im-plicitement compatible avec le type des éléments) : // vecteur de 4 éléments de type doublevector<double> v {1, 2.5, 8, 3.2} // initialisation directevector<double> v = {1, 2.5, 8, 3.2} // initialisation copie // vecteur de 2 éléments de type stringvector <string> vs {"bonjour", "bonsoir"} ; // initialisation directevector <string> vs = {"bonjour", "bonsoir"} ; // initialisation copie

RemarqueComme avec les chaînes, on notera bien que : vector<int> v (5) ;

correspond à un vecteur de 5 éléments tandis que : vector<int> v {5} ; // C++11

correspond à un vecteur d’un seul élément de valeur 5.

1.4 Parcours d’un vecteur avec for pour les séquencesSi nous avons déclaré :

vector<int> v(n) ;

nous pouvons bien sûr parcourir tous les éléments de v avec une boucle for classique :for (unsigned int i=0 ; i<n ; i++) { // traitement de v[i] }

Notez que, comme pour le type string, on dispose d’une fonction size (v.size() représente lenombre d’éléments de v), de sorte que cette boucle peut aussi s’écrire :

for (unsigned int i=0 ; i<v.size() ; i++) { // traitement de v[i] }

Mais, comme nous l’avons déjà vu, C++11 a introduit une version de for adaptée aux« séquences » d’éléments comme le type string ou le type vector. Aussi, pouvons-nous pro-céder de cette manière pour notre vecteur v :

LIVRE.book Page 212 Jeudi, 12. septembre 2019 6:19 18

Page 95: Programmer en C++ moderne - fnac-static.com

1 - Les vecteurs 213

for (int e : v) // ou for (auto e : v){ // traitement de e (copie d’un élément de v)}

Cela signifie que les instructions de la boucle, écrites ici pour un élément e, seront appliquéesà chacun des éléments du vecteur v. Mais, alors que dans la première formulation, v[i] dési-gnait bien la lvalue modifiable correspondant à un élément d’indice i, dans la seconde formu-lation, cette fois, e désigne une copie d’un élément de v. Il est toufefois possible dedemander de travailler directement sur l’élément lui-même en utilisant une référence commedans :

for (int & e : v) // ou for (auto & e : v){ // traitement de e (référence à un élément de v)}

Voici un petit programme (C++11) illustrant ces possibilités :

// VectorForGene#include <iostream>#include<vector>using namespace std;int main(){ vector<int> v { 1, 3, 5 } ; cout << "En A, v = " ; for (auto e : v) cout << e << " " ; cout << endl ; for (unsigned int i=0 ; i<v.size() ; i++) v[i]++ ; cout << "En B, v = " ; for (auto e : v) cout << e << " " ; cout << endl ; for (auto e : v) e++ ; cout << "En C, v = " ; for (auto e : v) cout << e << " " ; cout << endl ; for (auto & e : v) e++ ; cout << "En D, v = " ; for (int e : v) cout << e << " " ; cout << endl ; }

En A, v = 1 3 5En B, v = 2 4 6En C, v = 2 4 6En D, v = 3 5 7

Différentes façons de parcourir les éléments d’un vecteur

1.5 Affectation de vecteursSi l’on a déclaré deux vecteurs v1 et v2 ayant exactement le même type d’éléments (maispas nécessairement le même nombre), comme dans

vector<int> v1 { 1, 3, 5 } ; // Avant C++11, il faudrait initialiservector<int> v2 { 2, 4, 6, 8 } ; // individuellement chacun des éléments

LIVRE.book Page 213 Jeudi, 12. septembre 2019 6:19 18

Page 96: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11214

il est possible d’écrire une affectation telle que :v1 = v2 ;

Elle conduit bien à un vecteur v1 qui contient maintenant 4 valeurs, copies de celles de v2.C’est ce que montre ce petit programme (ici, par souci de simplicité, nous utilisons la bouclefor adaptée aux séquences introduite par C++11 et présentée au paragraphe 1.4) :

// AffectationVector#include <iostream>#include <vector>using namespace std;int main(){ vector<int> v1 { 1, 3, 5 } ; vector<int> v2 { 2, 4, 6, 8 } ; cout << "En A, les " << v1.size() << " valeurs de v1 sont : " ; for (auto v : v1) cout << v << " " ; cout << endl ; // C++11 (voir § 1.4) cout << "En A, les " << v2.size() << " valeurs de v2 sont : " ; for (auto v : v2) cout << v << " " ; cout << endl ; // C++11 (voir §1.4) v1 = v2 ; cout << "En B, les " << v1.size() << " valeurs de v1 sont : " ; for (auto v : v1) cout << v << " " ; cout << endl ; // C++11 (voir § 1.4) cout << "En B, les " << v2.size() << " valeurs de v2 sont : " ; for (auto v : v2) cout << v << " " ; cout << endl ; // C++11 (voir § 1.4)}

En A, les 3 valeurs de v1 sont : 1 3 5En A, les 4 valeurs de v2 sont : 2 4 6 8En B, les 4 valeurs de v1 sont : 2 4 6 8En B, les 4 valeurs de v2 sont : 2 4 6 8

Affectation (globale) de vecteurs

Ce comportement, pour naturel qu’il paraisse, conduit ici (implicitement) à allouer dynami-quement un nouvel emplacement pour les 4 nouvelles valeurs de v1 et à libérer l’ancienemplacement de 3 valeurs.

RemarqueNous verrons que cette affectation globale n’existera pas pour les tableaux natifs.

1.6 Vecteurs transmis en argument d’une fonction1.6.1 Par défaut, la transmission se fait par valeur

Nous avons vu comment, par défaut, les variables d’un type simple ou d’un type stringétaient transmises par valeur. Il en va de même pour les vecteurs, ce qui implique donc unerecopie de leurs valeurs, comme le montre cet exemple :

LIVRE.book Page 214 Jeudi, 12. septembre 2019 6:19 18

Page 97: Programmer en C++ moderne - fnac-static.com

1 - Les vecteurs 215

// vecteurArgument#include <iostream>#include <vector>using namespace std ;void f (vector<int> v) ;void f(vector<int> v){ cout << "En 1 de f, v = " ; for (auto n : v) cout << n << " " ; cout << endl ; // C++11 (voir § 1.4) for (auto & n : v) n++ ; // C++11 (voir § 1.4) cout << "En 2 de f, v = " ; for (auto n : v) cout << n << " " ; cout << endl ; // C++11 (voir § 1.4)}int main(){ vector<int> v1 {1, 3, 5 } ; cout << "En A, v = " ; for (auto n : v1) cout << n << " " ; cout << endl ; // C++11 (voir § 1.4) f (v1) ; cout << "En B, v = " ; for (auto n : v1) cout << n << " " ; cout << endl ; // C++11 (voir § 1.4)}

En A, v = 1 3 5En 1 de f, v = 1 3 5En 2 de f, v = 2 4 6En B, v = 1 3 5

Transmission d’un vecteur par valeur

Les valeurs de v1 ont été recopiées dans un vecteur v, local à f1. Les modifications effectuéessur v ne se retrouvent pas sur v1, après retour dans la fonction main.

1.6.2 Transmission d’un vecteur par référence ou par pointeurLe mode de transmission par valeur sera rarement utilisé pour d’évidentes raisons d’effica-cité. Mais il est naturellement possible d’utiliser une transmission par référence ou par poin-teur.Ainsi, en modifiant simplement de cette manière l’en-tête (et la déclaraton) de f dans le pré-cédent programme :

void f (vector<int> & v)

nous obtiendrons les résultats suivants, montrant bien que les modifications ont été opéréespar f directement sur v1 :

En A, v = 1 3 5En 1 de f, v = 1 3 5En 2 de f, v = 2 4 6En B, v = 2 4 6

1. En toute rigueur, seules les informations de taille et d ’adresse des éléments sont de type automatique ;l’emplacement pour les copies des valeurs étant alloué dynamiquement à l’entrée dans f et libéré à la sortie.

LIVRE.book Page 215 Jeudi, 12. septembre 2019 6:19 18

Page 98: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11216

Nous obtiendrions exactement les mêmes résultats, avec une transmission par pointeur, enécrivant f de cette manière :

void f (vector<int> * v){ cout << "En 1 de f, v = " ; for (auto n : *v) cout << n << " " ; cout << endl ; for (auto & n : *v) n++ ; cout << "En 2 de f, v = " ; for (auto n : *v) cout << n << " " ; cout << endl ;}

et en remplaçant l’appel f(v1) par f(&v1).

1.7 Autres possibilités du type vectorComme nous l’avons dit en introduction, le type vector sera étudié en détail au chapitre 27.Nous y verrons qu’il possède les propriétés générales des conteneurs séquentiels en ce quiconcerne notamment :• la notion d’itérateur ;• les possibilités de comparaison ;• l’insertion et la suppression d’éléments.Nous y verrons également que les vecteurs disposent d’une fonction pushback d’ajout d’unélément en fin de vecteur et d’outils spécifiques d’optimisation de la gestion de la mémoirequi leur est allouée.

2 Les tableaux natifsBien que le type vector existe depuis C++98, les tableaux natifs restent utilisés dans descodes postérieurs à cette date pour des raisons diverses : habitude, efficacité, simplicitéd’apprentissage. Aussi, bien qu’il soit possible d’écrire des codes sans recourir aux tableauxnatifs, il est difficile de les ignorer, d’où la présence de ce grand paragraphe.N.B. Seuls les paragraphes 2.1, 2.3, 2.4 et 2.7 seront utilisés dans la suite de cet ouvrage.

2.1 Les tableaux natifs2.1.1 Exemple d’utilisation d’un tableau natif

Comme les vecteurs, le tableaux natifs sont définis par le type de leurs éléments et leur nom-bre (dimension). Mais, et c’est déjà là une lacune par rapport aux vecteurs, cette dimensiondoit être connue à la compilation. Voici une adaptation du programme du paragraphe 1.1 oùnous avons dû prévoir cette fois un nombre donné de notes (ici 6) :

LIVRE.book Page 216 Jeudi, 12. septembre 2019 6:19 18

Page 99: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 217

// ExempleTableauNatif#include <iostream>using namespace std ;int main(){ const int NB = 6 ; // nombre de notes float notes[NB] ; // pour les NB notes for (int i=0 ; i<NB ; i++) // simplifiable avec C++11 - voir & 2.1.3 { cout << "Donnez la note numero " << i+1 << " : " ; cin >> notes[i] ; } float som = 0 ; for (int i=0 ; i<NB ; i++) som += notes[i] ; // Avec C++11 - voir & 2.1.3 float moy = som / NB ; cout << "Moyenne de la classe : " << moy << endl ; int nbm = 0 ; for (int i=0 ; i<NB ; i++ ) // simplifiable avec C++11 - voir & 2.1.3 if (notes[i] > moy) nbm++ ; cout << nbm << " eleves ont plus de cette moyenne" ;}

Donnez la note numero 1 : 12.5Donnez la note numero 2 : 11Donnez la note numero 3 : 8.25Donnez la note numero 4 : 13.5Donnez la note numero 5 : 15.25Donnez la note numero 6 : 13Moyenne de la classe : 12.254 eleves ont plus de cette moyenne

Exemple d’utilisation d’un tableau

La déclaration (notez bien l’emploi de crochets ici et non de parenthèses) :float notes[NB] ; // pour les NB notes

réserve l’emplacement pour NB éléments de type float. Cette fois, contrairement à ce qui seproduisait avec les vecteurs, nous avons affaire à une allocation automatique (sur la pile)2. Làencore, les éléments sont repérés par un indice, fourni sous forme d’une expression entière, lepremier élément portant le numéro 0.

2.1.2 Quelques règlesLa dimension d’un tableau natif (son nombre d’éléments) doit donc être connue au momentde la compilation. Ces déclarations sont correctes :

const int N = 50 ; // ou , depuis C++11 : constexpr int N = 50 ;int t[N] ;float h [2*N+1] ;

2. Du moins pour les tableaux déclarés dans une fonction, car il peut exister des tableaux de classe statique.

LIVRE.book Page 217 Jeudi, 12. septembre 2019 6:19 18

Page 100: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11218

En revanche, cette construction est incorrecte :int nel ; .....cin >> nel ;int u[nel] ; // non : nel n’est pas connu du compilateur

Les propriétés des indices et des éléments de tableau sont exactement les mêmes que pour lesvecteurs :• l’indice peut prendre la forme de n’importe quelle expression arithmétique entière ;• si t est un tableau et exp une expression entière, une notation telle que t[exp] est une lvalue

modifiable (sauf, bien sûr, si t a été déclaré constant).Comme avec les vecteurs, aucun contrôle d’indice n’est mis en place. En revanche, iln’existe pas l’équivalent de la fonction at. En outre, il n’existe pas d’affectation globale entretableaux de même type (même s’ils sont de même dimension) :

int t1[10] ;int t2[10] ; .....t1 = t2 ; // erreur de compilation

2.1.3 Parcours des éléments avec for pour les séquences (C++11)On peut parcourir l’ensemble des éléments d’un tableau avec l’instruction for comme nousl’avons fait dans notre exemple. Mais on peut également, depuis C++11, utiliser l’instructionfor adaptée aux séquences, en n’oubliant pas de prévoir une référence dans le cas où l’on sou-haite modifier la valeur courante dans la boucle. Par exemple, les instructions de lecture desnotes de notre exemple pourraient s’écrire (notez bien ici le &) :

for (auto & note:notes) // depuis C++11 { cout << "donnez une note " << " : " ; cin >> note ; }

Celles de calcul de la somme pourraient s’écrire ainsi (cette fois, le & n’est pas utile) :for (auto note:notes) som += note ;

RemarqueAttention, cette forme d’instruction for adaptée aux séquences ne pourra s’appliquer quelorsque la dimension du tableau est connue du compilateur, ce qui ne sera plus le caspour des tableaux transmis en argument d’une fonction. C’est une des raisons pourlaquelle nous la mettrons moins en évidence dans la suite de ce paragraphe consacré auxtableaux natifs.

LIVRE.book Page 218 Jeudi, 12. septembre 2019 6:19 18

Page 101: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 219

2.2 Les tableaux natifs à plusieurs indices2.2.1 Leur déclaration

Comme la plupart des langages, C++ autorise les tableaux à plusieurs indices (on dit aussi àplusieurs dimensions).Par exemple, la déclaration :

int t[5][3]

réserve un tableau de 15 (5 x 3) éléments. Un élément quelconque de ce tableau se trouvealors repéré par deux indices comme dans ces notations :

t[3][2] t[i][j] t[i-3][i+j]

Notez bien que la notation désignant un élément d’un tel tableau est une lvalue modifiable. Iln’en ira toutefois pas de même de notations telles que t[3] ou t[j] bien que, comme nous leverrons un peu plus tard, de telles notations aient un sens en C++.Aucune limitation ne pèse sur le nombre d’indices que peut comporter un tableau. Seules leslimitations de taille mémoire liées à un environnement donné risquent de se faire sentir.

2.2.2 Arrangement en mémoire des tableaux à plusieurs indicesLes éléments d’un tableau sont rangés suivant l’ordre obtenu en faisant varier le dernierindice en premier. Ainsi, le tableau t déclaré précédemment verrait ses éléments ordonnéscomme suit :

t[0][0] t[0][1] t[0][2] t[1][0] t[1][1] t[1][2] .... t[4][0] t[4][1] t[4][2]

Nous verrons que cet ordre a une incidence dans au moins trois circonstances :• lorsque l’on omet de préciser certaines dimensions d’un tableau ;• lorsque l’on souhaite accéder à l’aide d’un pointeur aux différents éléments d’un tableau ;• lorsque l’un des indices « déborde ». Suivant l’indice concerné et les valeurs qu’il prend, il

peut y avoir débordement d’indice sans sortie du tableau. Par exemple, toujours avec notretableau t de 5 x 3 éléments, vous voyez que la notation t[0][5] désigne en fait l’élémentt[1][2]. En revanche, la notation t[5][0] désigne un emplacement situé juste au-delà du ta-bleau.

LIVRE.book Page 219 Jeudi, 12. septembre 2019 6:19 18

Page 102: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11220

RemarqueBien entendu, les différents points évoqués, dans le paragraphe 1.2, à propos des tableauxà une dimension, restent valables dans le cas des tableaux à plusieurs dimensions.

2.2.3 Parcours des éléments d’un tableau à plusieurs indices (C++11)Si l’on a déclaré :

int t[5][3]

on pourra naturellement parcourir chacun des éléments du tableau en utilisant des instruc-tions imbriquées, par exemple :

for (int i=0 ; i<5 ; i++) for (int j=0 ; j<3 ; j++) { // traitement de l’élément t[i][j] }

Notez qu’on peut tout à fait inverser l’ordre des instructions for, ce qui conduirait simplementà un autre ordre de parcours.Depuis C++11, on peut utiliser l’instruction for pour les séquences, de la manière suivante :

for (auto & col : t) for (auto e : col) // ou auto & e : col si on doit modifier e { // traitement de l’élément e }

Cette fois, il est obligatoire de considérer le tableau t comme un tableau de tableaux de 3entiers. La première instruction parcourt les 5 tableaux de 3 entiers de t. La transmission doitalors obligatoirement se faire par référence (d’où le &). Pour la seconde instruction, le & nesera nécessaire que si les valeurs de t doivent être modifiées. On notera bien que, cette fois,on ne dispose plus d’aucune latitude quant à l’ordre de parcours.Enfin, on ne perdra pas de vue que cette démarche n’est plus applicable aux tableaux reçus enargument par une fonction.

2.3 Initialisation des tableaux natifsComme les variables scalaires, les tableaux peuvent être, suivant leur déclaration, de classestatique ou automatique. Les tableaux de classe statique sont, par défaut, initialisés à« zéro »3 ; les tableaux de classe automatique ne sont pas initialisés implicitement.Il est possible, comme on le fait pour une variable scalaire, d’initialiser (partiellement outotalement) un tableau lors de sa déclaration. En voici d’abord quelques exemples.

2.3.1 Initialisation de tableaux natifs à un indiceLa déclaration :

int tab[5] = { 10, 20, 5, 0, 3 } ; // on peut omettre = depuis C++11

3. Rappelons que « zéro » est un terme générique pouvant représenter différentes choses suivant le type concerné :valeur entière 0, valeur flottante 0, pointeur nul, appel du constructeur par défaut pour un objet...

LIVRE.book Page 220 Jeudi, 12. septembre 2019 6:19 18

Page 103: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 221

place les valeurs 10, 20, 5, 0 et 3 dans chacun des cinq éléments du tableau tab.Il est possible de ne mentionner dans les accolades que les premières valeurs, comme dansces exemples :

int tab[5] = { 10, 20 } ; // on peut omettre = depuis C++11int tab[5] = { 10, 20, 5 } ; // on peut omettre = depuis C++11

Les valeurs manquantes seront, suivant la classe d’allocation du tableau, initialisées à zéro(statique) ou aléatoires (automatique).De plus, il est possible d’omettre la dimension du tableau, celle-ci étant alors déterminée parle compilateur par le nombre de valeurs énumérées dans l’initialisation. Ainsi, la premièredéclaration de ce paragraphe est équivalente à :

int tab[] = { 10, 20, 5, 0, 3 } ;

On peut déclarer un tableau constant et l’initialiser comme dans :const char voyelles [] = {'a', 'e', 'i', 'o', 'u', 'y'} ;

Bien entendu, toute tentative ultérieure de modification du tableau sera rejetée :voyelles [2] = 'u' ; // interdit

Remarques1 La notation {} est obligatoire ici.2 Nous verrons (paragraphe 4.3.1) qu’il existe un cas particulier d’initialisation d’un

tableau de caractères par une chaîne littérale.3 Contrairement aux vecteurs, les tableaus natifs ne sont pas des objets, ce qui explique

que leurs initialisations soient différentes.

2.3.2 Initialisation de tableaux natifs à plusieurs indicesVoyez ces deux exemples équivalents (nous avons volontairement choisi des valeurs consécuti-ves pour qu’il soit plus facile de comparer les deux formulations) :

int tab [3] [4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9,10,11,12 } } int tab [3] [4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 } ;

La première forme revient à considérer notre tableau comme composé de trois tableaux dequatre éléments chacun. La seconde exploite la manière dont les éléments sont effectivementrangés en mémoire, et elle se contente d’énumérer les valeurs du tableau suivant cet ordre.Là encore, à chacun des deux niveaux, les dernières valeurs peuvent être omises. Les déclara-tions suivantes sont correctes (mais non équivalentes) :

int tab [3] [4] = { { 1, 2 } , { 3, 4, 5 } } ; int tab [3] [4] = { 1, 2 , 3, 4, 5 } ;

LIVRE.book Page 221 Jeudi, 12. septembre 2019 6:19 18

Page 104: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11222

2.3.3 Initialiseurs et classe d’allocationLes initialiseurs utilisables pour les éléments d’un tableau suivent les mêmes règles que lesinitialiseurs des variables scalaires, à savoir :• Pour un tableau statique, les valeurs d’initialisation doivent être des expressions constantes,

calculables à la compilation, d’un type compatible par affectation avec le type des élémentsdu tableau ; on peut y faire apparaître des variables statiques ou des variables automatiquesdéclarées avec l’attribut const ; en voici un exemple :void f(){ const int N = 10 ; // ou constexpr depuis C++11 static int delta = 3 ; ..... int tab[5] = { 2*N-1, N-1, N, N+1, 2*N+1} ; int t[3] = { 0, delta, 2*delta } ;}

• Pour un tableau automatique, on peut utiliser n’importe quelle expression d’un type compa-tible par affectation avec le type des éléments du tableau. En voici un exemple d’école :const int NEL = 10 ;void fct (int p){ int n ; ..... int tab[] = {NEL, p, 2*p, n+1, n+p} ; .....}

2.4 L’équivalence entre tableau natif et pointeurEn C++, l’identificateur d’un tableau, lorsqu’il est employé seul (sans indices à sa suite), estconsidéré comme un pointeur (constant) sur le début du tableau. Nous allons en examiner lesconséquences en commençant par le cas des tableaux à un indice. Mais auparavant, il nousfaut apporter quelques compléments d’information sur les opérations applicables aux poin-teurs (incrémentation et comparaison).

2.4.1 Incrémentation de pointeursDans le chapitre consacré aux pointeurs, nous nous sommes contentés de manipuler, non pasles variables pointeurs elles-mêmes, mais les valeurs pointées. Or, si une variable pointeur ada été déclarée ainsi :

int * ad ;

une expression telle que :ad + 1

a un sens pour C++.En effet, ad est censée contenir l’adresse d’un entier et, pour C++, l’expression ci-dessusreprésente l’adresse de l’entier suivant. Certes, dans notre exemple, cela n’a guère d’intérêt,

LIVRE.book Page 222 Jeudi, 12. septembre 2019 6:19 18

Page 105: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 223

car nous ne savons pas avec certitude ce qui se trouve à cet endroit. Mais cela va s’avérerutile dans le traitement de tableaux natifs.Notez bien qu’il ne faut pas confondre un pointeur avec un nombre entier. En effet, l’expres-sion précédente ne représente pas l’adresse de ad augmentée de 1 (octet). Plus précisément, ladifférence entre ad+1 et ad est ici de sizeof(int) octets (l’opérateur sizeof fournit la taille, enoctets, d’un type donné). Si ad avait été déclarée par :

double * ad ;

cette différence serait de sizeof(double) octets.De manière comparable, l’expression :

ad++

incrémente donc l’adresse contenue dans ad de manière qu’elle désigne l’élément suivant.Notez bien que des expressions telles que ad+1 ou ad++ sont, en général, valides, quelle quesoit l’information se trouvant réellement à l’emplacement correspondant. Par ailleurs, il estpossible d’incrémenter ou de décrémenter un pointeur de n’importe quelle quantité entière.Par exemple, avec la déclaration précédente de ad, nous pourrions écrire ces instructions :

ad += 10 ; ad -= 25 ;

RemarqueIl existe une exception à ces possibilités, à savoir le cas des pointeurs sur des fonctions :on comprend bien qu’incrémenter un pointeur d’une quantité correspondant à la tailled’une fonction n’a pas de sens en soi.

2.4.2 Comparaison et soustraction de pointeursNous avons déjà vu que l’on pouvait appliquer les opérateurs == et != aux pointeurs natifs.Mais il est possible de les comparer avec les opérateurs <, >, <= ou >=. Dans ce cas, la com-paraison porte tout simplement sur les adresses. Si cela n’a guère de sens dans le cas de varia-bles simples, nous verrons que cela peut avoir un intérêt dans le cas où les deux pointeursdésignent des éléments d’un même tableau.Enfin, il est théoriquement possible d’efffectuer la différence entre deux pointeurs de mêmetype, ce qui fournit le nombre d’éléments du type en question, situés entre les deux adressescorrespondantes. Cette possibilité est rarement utilisée.

2.4.3 Équivalence tableau à un indice et pointeurSupposons que l’on effectue la déclaration suivante :

int t[10] ;

La notation t est alors totalement équivalente à &t[0].L’identificateur t est considéré comme étant de type « pointeur sur le type correspondant auxéléments du tableau », c’est-à-dire, ici, int * (et même plus précisément const int *). Ainsi,voici quelques exemples de notations équivalentes :

LIVRE.book Page 223 Jeudi, 12. septembre 2019 6:19 18

Page 106: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11224

t+1 &t[1]t+i &t[i]t[i] * (t+i)

Pour illustrer ces nouvelles possibilités de notation, voici deux façons de placer la valeur 1dans chacun des 10 éléments de notre tableau t :

for (int i=0 ; i<10 ; i++) *(t+i) = 1 ;

int i ;int *p :for (p=t, i=0 ; i<10 ; i++, p++) *p = 1 ;

Dans la seconde façon, nous avons dû recopier la valeur représentée par t dans un pointeurnommé p. En effet, il ne faut pas perdre de vue que le symbole t représente une adresse cons-tante (t est une constante de type pointeur sur des entiers). Autrement dit, une expression telleque t++ aurait été invalide, au même titre que, par exemple, 3++. Un nom de tableau est unpointeur constant ; ce n’est pas une lvalue modifiable.Nous pouvons également utiliser la possibilité de comparer des pointeurs :

int t[10] ; for (int *p=t ; p<t+10 ; p++) *p = 1 ;

Remarques1 Les trois formulations précédentes sont équivalentes (en C++11) à :

for (auto & v : t) v = 1 ;

2 Nous venons de voir que la notation t[i] est équivalente à *(t+i) lorsque t est déclarécomme un tableau. En fait, cela reste vrai, quelle que soit la manière dont t a étédéclaré. Ainsi, avec : int *t ;

les deux notations précédentes resteraient équivalentes. Autrement dit, on peut utilisert[i] dans un programme où t est simplement déclaré comme un pointeur (encore faudra-t-il, toutefois, disposer à cette adresse de l’espace mémoire nécessaire).

2.5 Équivalence tableau à plusieurs indices et pointeurComme pour les tableaux à un indice, l’identificateur d’un tableau, employé seul, représentetoujours son adresse de début. Toutefois, si l’on s’intéresse à son type exact, il ne s’agit plusd’un pointeur sur des éléments du tableau. En pratique, ce point n’a d’importance que lorsquel’on effectue des calculs arithmétiques avec ce pointeur (ce qui est assez rare) ou lorsque l’ondoit transmettre ce pointeur en argument d’une fonction ; dans ce dernier cas, cependant,nous verrons que le problème est automatiquement résolu par la mise en place de conver-sions, de sorte qu’on peut ne pas s’en préoccuper.

LIVRE.book Page 224 Jeudi, 12. septembre 2019 6:19 18

Page 107: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 225

À simple titre indicatif, nous vous présentons ici les règles employées par C++, en nouslimitant au cas de tableaux à deux indices.

Lorsque le compilateur rencontre une déclaration telle que : int t[3] [4] ;

il considère en fait que t désigne un tableau de 3 éléments, chacun de ces éléments étant lui-même un tableau de 4 entiers. Autrement dit, si t représente bien l’adresse de début de notretableau t, il n’est plus de type int * (comme c’était le cas pour un tableau à un indice) maisd’un type « pointeur sur des blocs de 4 entiers », type qui devrait se noter théoriquement(vous n’aurez probablement jamais à utiliser cette notation) :

int [4] *

Dans ces conditions, une expression telle que t+1 correspond à l’adresse de t, augmentée de 4entiers (et non plus d’un seul !). Ainsi, les notations t et &t[0][0] correspondent toujours à lamême adresse, mais l’incrémentation de 1 n’a pas la même signification pour les deux.

Par ailleurs, les notations telles que t[0], t[1] ou t[i] ont un sens. Par exemple, t[0] représentel’adresse de début du premier bloc (de 4 entiers) de t, t[1], celle du second bloc... Cette fois,il s’agit bien de pointeurs de type int *. Autrement dit, les notations suivantes sont totalementéquivalentes (elles correspondent à la même adresse et elles sont de même type) :

t[0] &t[0][0]

t[1] &t[1][0]

Voici un schéma illustrant ce que nous venons de dire :

Remarquet[1] est une constante ; ce n’est pas une lvalue modifiable. L’expression t[1]++ est inva-lide. En revanche, t[1][2] est bien une lvalue modifiable.

t

t+1

type int [4] * type int *

&t[0][0] ou t[0]&t[0][2]

&t[1][0] ou t[1]

&t[2][0] ou t[2]t+2

LIVRE.book Page 225 Jeudi, 12. septembre 2019 6:19 18

Page 108: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11226

Informations complémentaires

La notion de référence à un tableau n’a pas de sens en C++. Une déclaration telle queint & t[10] est interdite (elle représenterait en théorie un tableau de références, ce quin’est pas accepté par C++). Quant à la notation int * & t, elle représente la référence à unpointeur sur un tableau d’entiers et elle est correcte.

2.6 Transmission de tableaux natifs en argumentNous avons vu que le type vector pouvait être transmis par valeur (cas par défaut) ou paradresse (référence ou pointeur). En revanche, lorsque l’on place le nom d’un tableau en argu-ment effectif de l’appel d’une fonction, on transmet obligatoirement (la valeur de) l’adressedu tableau à la fonction, ce qui lui permet d’effectuer toutes les manipulations voulues sur seséléments, qu’il s’agisse d’utiliser leur valeur ou de la modifier. Tout se passe donc comme sil’on avait affaire à une transmission par pointeur et il n’existe aucun autre mode. En outre,contrairement à ce qui se passait pour les vecteurs, si la fonction à laquelle on transmet untableau a besoin d’en connaître la dimension, cette dernière devra également lui être trans-mise. Voyons quelques exemples pratiques.

2.6.1 Cas des tableaux à un indicea) Premier exemple : tableau de taille fixeVoici un exemple de fonction qui met la valeur 1 dans tous les éléments d’un tableau de10 éléments, l’adresse de ce tableau étant transmise en argument.

void fct (int t[10]){ for (int i=0 ; i<10 ; i++) t[i]=1 ; // for (int & n : t) n=1 serait refuse - voir remarque}

Exemple de tableau à un indice transmis en argument d’une fonction

Voici deux exemples d’appels possibles de cette fonction :int t1[10], t2[10] : .....fct(t1) ; ....fct(t2) ;

L’en-tête de fct peut être indifféremment écrit de l’une des manières suivantes :void fct (int t[10]) void fct (int * t)void fct (int t[])

La dernière écriture se justifie par le fait que t désigne un argument muet. La réservation del’emplacement mémoire du tableau dont on recevra ici l’adresse est réalisée par ailleurs dans

LIVRE.book Page 226 Jeudi, 12. septembre 2019 6:19 18

Page 109: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 227

la fonction appelante (de plus cette adresse peut changer d’un appel au suivant). De plus, laconnaissance de la taille exacte du tableau n’est pas indispensable au compilateur ; il est eneffet capable de déterminer l’adresse d’un élément quelconque, à partir de son rang et del’adresse de début du tableau (nous verrons qu’il n’en ira plus de même pour les tableaux àplusieurs indices). Dans ces conditions, on comprend qu’il soit tout à fait possible de ne pasmentionner la dimension du tableau dans l’en-tête de la fonction. En fait, le 10 qui figuredans le premier en-tête n’a d’intérêt que pour le lecteur du programme, afin de lui rappeler ladimension effective du tableau sur lequel travaillait notre fonction.Par ailleurs, comme d’habitude, quel que soit l’en-tête employé, on peut, dans la définitionde la fonction, utiliser indifféremment le formalisme tableau ou le formalisme pointeur. Voiciplusieurs écritures possibles de fct qui s’accommodent de n’importe lequel des trois en-têtesprécédents :

for (int i=0 ; i<10 ; i++) t[i] = 1 ;for (int i=0 ; i<10 ; i++, t++) *t = 1 ;for (int i=0 ; i<10 ; i++) *(t+i) = 1 ;for (int i=0 ; i<10 ; i++) t[i] = 1 ;

Ici encore, l’expression t++ ne pose aucun problème car t représente une copie de l’adressed’un tableau ; t est donc bien une lvalue et elle peut donc être incrémentée.Voici enfin une dernière possibilité dans laquelle nous recopions l’adresse t dans un pointeurp, et où nous utilisons les possibilités de comparaison de pointeurs :

for (int * p = t ; p<t+10 ; p++) *p = 1 ;

Remarques1 Notez bien que, quelle que soit la forme d’en-tête utilisée, il n’est pas possible

d’employer l’instruction for généralisée aux séquences en écrivant : for (int & n : t) n=1 ; // erreur

Ceci est dû à ce que l’information de dimension de t n’est pas transmise à f.2 N’oubliez pas que si vous définissiez fct avec l’un de ces en-têtes :

void fct (const int * t) void fct (const int t[])

t serait un pointeur sur des entiers constants. Il ne serait alors plus possible dans fct demodifier les valeurs du tableau reçu en argument.

3 Vous pouvez penser à utiliser pour fct l’en-tête : void fct (int * const t)

Dans ce cas, t est un pointeur constant sur des entiers. Ce n’est donc que la valeur de tqui ne peut pas être modifiée, alors que les entiers du tableau peuvent toujours l’être.Cette possibilité a généralement peu d’intérêt : elle interdit d’incrémenter directementla valeur de t dans fct, laquelle n’est, de toute façon, qu’une copie de la valeur del’argument effectif...

LIVRE.book Page 227 Jeudi, 12. septembre 2019 6:19 18

Page 110: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11228

b) Second exemple : tableau de taille variableComme nous venons de le voir, lorsqu’un tableau à un seul indice apparaît en argumentd’une fonction, le compilateur n’a pas besoin d’en connaître la taille exacte. Il est ainsi facilede réaliser une fonction capable de travailler avec un tableau de dimension quelconque, àcondition de lui en transmettre la taille en argument. Voici, par exemple, une fonction qui cal-cule la somme des éléments d’un tableau d’entiers de taille quelconque :

int som (int t[],int nb)

{ int s = 0 ;

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

s += t[i] ;

return (s) ;

}

Fonction travaillant sur un tableau à une dimension de taille variable

Voici quelques exemples d’appels de cette fonction : int main()

{ int t1[30], t2[15], t3[10] ;

int s1, s2, s3 ;

.....

s1 = som(t1, 30) ;

s2 = som(t2, 15) + som(t3, 10) ;

.....

}

2.6.2 Cas des tableaux à plusieurs indicesa) Premier exemple : tableau de taille fixeVoici un exemple d’une fonction qui place la valeur 1 dans chacun des éléments d’un tableaude dimensions 10 et 15 :

void raun (int t[10][15])

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

for (int j=0 ; j<15 ; j++)

t[i][j] = 1 ;

}

Exemple de transmission en argument d’un tableau à deux dimensions (fixes)

Ici, on pourrait, par analogie avec ce que nous avons dit pour un tableau à un indice, utiliserd’autres formes de l’en-tête. Toutefois, il faut bien voir que, pour trouver l’adresse d’un élé-ment quelconque d’un tableau à deux indices, le compilateur ne peut plus se contenter deconnaître son adresse de début ; il doit également connaître la seconde dimension du tableau(la première n’étant pas nécessaire compte tenu de la manière dont les éléments sont disposés

LIVRE.book Page 228 Jeudi, 12. septembre 2019 6:19 18

Page 111: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 229

en mémoire : revoyez le paragraphe 2.6.1). Ainsi, l’en-tête de notre fonction aurait pu êtrerau (int t[][15]) mais pas rau (int t[][]).En revanche, cette fois, quel que soit l’en-tête utilisé, cette fonction ne convient plus pour untableau de dimensions différentes de celles pour lesquelles elle a été prévue. Plus précisé-ment, nous pourrons certes toujours l’appeler, comme dans cet exemple :

int mat [12][20] ; ..... raun (mat) ; .....

Mais, bien qu’aucun diagnostic ne nous soit fourni par le compilateur, l’exécution de ces ins-tructions placera 150 fois la valeur 1 dans certains des 240 emplacements de mat. Qui plusest, avec des tableaux dont la deuxième dimension est inférieure à 15, notre fonction placeraitdes 1... en dehors de l’espace attribué au tableau !

RemarqueOn pourrait songer, par analogie avec ce qui a été fait pour les tableaux à un indice, àmélanger le formalisme pointeur et le formalisme tableau, à la fois dans l’en-tête et dansla définition de la fonction ; cela pose toutefois quelques problèmes que nous allons évo-quer dans l’exemple suivant consacré à un tableau de dimensions variables (et dans lequelle formalisme précédent n’est plus applicable).

b) Second exemple : tableau de dimensions variablesSupposons que nous cherchions à écrire une fonction qui place la valeur 0 dans chacun deséléments de la diagonale d’un tableau carré de taille quelconque. Une façon de résoudre ceproblème consiste à adresser les éléments voulus par des pointeurs en effectuant le calculd’adresse approprié.

void diag (int * p, int n){ for (int i=0 ; i<n ; i++) { * p = 0 ; p += n+1 ; }}

Fonction travaillant sur un tableau carré de taille variable

Notre fonction reçoit donc, en premier argument, l’adresse du premier élément du tableau,sous forme d’un pointeur de type int *. Ici, nous avons tenu compte de ce que deux élémentsconsécutifs de la diagonale sont séparés par n éléments. Ainsi, si un pointeur désigne un élé-ment de la diagonale, pour pointer sur le suivant il suffit d’incrémenter ce pointeur de n+1unités (l’unité étant ici la taille d’un entier).

LIVRE.book Page 229 Jeudi, 12. septembre 2019 6:19 18

Page 112: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11230

Remarques1 Un appel de notre fonction diag se présentera ainsi :

int t[30] [30] ; diag (t, 30)

Or l’argument effectif t est, certes, l’adresse de t, mais d’un type pointeur sur des blocsde 10 entiers et non pointeur sur des entiers. En fait, la présence d’un prototype pourdiag fera qu’il sera converti en un int *. Ici, il n’y a aucun risque de modificationd’adresse liée à des contraintes d’alignement, car on passe de l’adresse d’un objet detaille 10n à l’adresse d’un objet de taille n. Il n’en irait pas de même avec la conversioninverse.

2 Cette fonction pourrait également s’écrire en y déclarant un tableau à une seule dimen-sion dont la taille (n*n) devrait alors être fournie en argument (en plus de n). Le mêmemécanisme d’incrémentation de n+1 s’appliquerait alors, non plus à un pointeur, mais àla valeur d’un indice.

2.7 Les tableaux dynamiquesNous avons déjà appris à allouer et à libérer dynamiquement (dans le tas) des emplacementspour des valeurs de type simple, soit en utilisant des pointeurs natifs, soit à l’aide de poin-teurs intelligents.Ces possibilités s’appliquent aux tableaux natifs, moyennant une adaptation des opérateursnew et delete qui deviennent les opérateurs new[] et delete[]. Avec C++14, on peut égale-ment utiliser make_unique.C’est ce que nous allons examiner ici, sachant que ces connaissances seront surtout utilespour exploiter d’anciens codes ; en effet, la plupart du temps, les types string et vector offri-ront des fonctionnalités comparables, de façon plus fiable.

2.7.1 Avec des pointeurs natifs et les opérateurs new[] et delete[]L’opérateur new[] accepte une syntaxe de la forme :

new type [n]où n désigne une expression entière quelconque (non négative). Cette instruction alloue alorsl’emplacement nécessaire pour n éléments du type indiqué ; si l’opération a réussi, elle four-nit en résultat un pointeur (toujours de type type *) sur le premier élément de ce tableau. Lecomportement en cas de manque de mémoire est le même que celui de new, présenté auparagraphe 1.3 du chapitre 10.Par exemple, avec :

int n ; .....int * ad = new int[n] ; // ou auto ad = new int[n] ; depuis C++11

LIVRE.book Page 230 Jeudi, 12. septembre 2019 6:19 18

Page 113: Programmer en C++ moderne - fnac-static.com

2 - Les tableaux natifs 231

on allouera un emplacement pour un tableau de n éléments de type int et on placera sonadresse (c’est-à-dire l’adresse du premier élément) dans ad.L’accès aux différents éléments de ce tableau pourra se faire avec l’opérateur [], compte tenude l’équivalence entre nom de tableau et pointeur natif :

for (int i=0 ; i<n ; i++){ // traitement de ad[i] (lvalue modifiable) }

En revanche, il n’est pas possible ici d’utiliser l’instruction for généralisée aux séquences :for (auto b : ad) {.....} // n’a pas de sens ici

L’emplacement ainsi alloué devra être libéré en utilisant l’opérateur delete[] et non simple-ment delete qui ne libèrerait que le premier élément...

2.7.2 Avec le type unique_ptr (C++11)Le type unique_ptr permet de manipuler des tableaux dynamiques (ce n’est pas le cas du typeshared_ptr). L’exemple précédent pourra s’écrire :

unique_ptr<int[]> adu ( new int[n]) ; // attention, avec auto, on obtiendrait le type int * et non int[] *

Cette fois, le recours à delete est inutile (et interdit) : le tableau désigné par adu sera libéréquand adu sera détruit.De plus, depuis C++14, la fonction make_unique peut s’appliquer à des tableaux, de sorteque l’instruction précédente peut être avantageusement remplacée par :

unique_ptr<int[]> adu = make_unique<int[]> (n) ;

ou, mieux :auto adu = make_unique<int[]> (n) ; // ou : auto adu (make_unique<int[]>(n)) ;

Notez que, si l’accès au tableau peut toujours se faire avec l’opérateur [], comme dans adu[i],il n’est plus possible d’employer l’équivalence tableau/pointeur natif en utilisant à laplace *(adu + i).

2.7.3 ExempleVoici un exemple d’école reprenant les diverses possibilités évoquées.

// GestDynTableauxNatifs#include <iostream>#include <memory> // pour unique_ptrusing namespace std ;int main(){ cout << "Combien de valeurs : " ; int nb ; cin >> nb ; //----------- allocation C++03 d'un emplacement pour nb entiers ------------ // dans lequel on place les carrres des nombres 1 à nb int * adi = new int [nb] ; cout << "Allocation de " << nb << " int en : " << adi << endl ; for (int i=0 ; i<nb ; i++) adi[i]=(i+1)*(i+1) ; // ou *(adi+i) au lieu de adi[i]

LIVRE.book Page 231 Jeudi, 12. septembre 2019 6:19 18

Page 114: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11232

cout << "Voici les carres des nombres de 1 a " << nb << " : \n" ; for (int i=0 ; i<nb ; i++) cout << adi[i] << " " ; // ou : for (int *adibis=adi ; adibis<adi+nb ; adibis++) cout << *adibis << " " ; cout << endl ; delete [] adi ; // liberation des nb entiers cout << "Liberation des " << nb << " entiers" << endl ; //----------------- Le même tableau en C++11 ------------------------------ unique_ptr<int[]> upi (new int [nb]) ; // auto upi = make_unique<int[]>(nb) ; C++14 uniquement cout << "Allocation de nb entiers en : " << upi.get() << "\n" ; for (int i = 0 ; i<nb ; i++) upi[i] = (i+1)*(i+1) ; // *(upi+i) = (i+1) * (i+1) ne fonctionnerait pas ici // *(upi.get()+i) = (i+1) * (i+1) fonctionnerait - mais deconseille cout << "Voici les carres des nombres de 1 a " << nb << " : \n" ; for (int i=0 ; i<nb ; i++) cout << adi[i] << " " ; // le tableau sera libere lorsque upi sera detruit, ici en fin de main}

Combien de valeurs : 9Allocation de 9 int en : 0x170e40Voici les carres des nombres de 1 a 9 :1 4 9 16 25 36 49 64 81Liberation des 9 entiersAllocation de nb entiers en : 0x170e40Voici les carres des nombres de 1 a 9 :1 4 9 16 25 36 49 64 81

Allocation dynamique de tableaux natifs avec pointeurs natifs ou avec unique_ptr

2.7.4 Initialisation de tableaux dynamiquesNous avons vu comment on peut initialiser les tableaux natifs (statiques ou automatiques)lors de leur déclaration. Les choses sont plus restrictives dans le cas des tableaux dynami-ques.

Tout d’abord, lorsque l’on utilise l’opérateur new, comme nous l’avons fait précédemment,sous la forme new T[n], sans autres paramètres (aussi bien avec un pointeur natif qu’ununique_ptr), les éléments du tableau reçoivent l’initialisation par défaut prévue pour le typeT, à savoir : pas d’initialisation pour les types de base, y compris les pointeurs natifs, chaînevide pour string, vecteur vide pour vector...

Si l’on utilise une syntaxe de la forme new T[n] (), les éléments sont initialisés à « zéro »(terme générique représentant la valeur 0 pour des types numériques, nullptr ou 0 pour unpointeur, mais la chaîne vide pour string...).

Depuis C++11, on peut spécifier, entre accolades, des valeurs initiales sous forme d’expres-sion d’un type compatible avec T, comme dans :

int * ad = new int [5] {2, 4, 6, 8, 10} ;

unique_ptr<int[]> up1 (new int [5] {2, 4, 6, 8, 10}) ;

En revanche, il n’existe aucune possibilité comparable lorsque l’on utilise make_unique.

LIVRE.book Page 232 Jeudi, 12. septembre 2019 6:19 18

Page 115: Programmer en C++ moderne - fnac-static.com

3 - Les vecteurs multidimensionnels 233

3 Les vecteurs multidimensionnelsN.B. Ce paragraphe peut être ignoré dans un premier temps.Nous avons vu comment réaliser des tableaux natifs à plusieurs dimensions. Le type vectorpeut, lui aussi, se « composer » pour aboutir à une situation semblable, mais non identique.Considérons cette déclaration d’un tableau natif qui correspond à un tableau de 3 tableaux de4 éléments de type int :

int t[3] [4] ;

Nous pouvons, par analogie, chercher à utiliser :vector<vector<int>> v(3)

qui correspond à un vecteur de 3 éléments de type vector<int>.Mais, pour l’instant ces éléments sont vides. Pour leur attribuer une valeur, il faut faire appelaux possibilités d’extension dynamiques des vecteurs (fonction pushback). Il est tout à faitpossible que chacun de ces éléments possède une dimension différente... En outre, chacundes 3 éléments de notre vecteur v, c’est-à-dire chacun des 3 vecteurs est alloué de façonindépendante ; ils ne seront plus contigüs comme le sont ceux d’un tableau natif.Depuis C++11, il est possible d’initialiser un vecteur dans sa déclaration, de sorte qu’il estpossible de définir :

vector<vector<int>> v = { {1, 2, 3, 4}, {2, 3, 4, 5}, {6, 7, 8, 9} } ;

On obtiendra ici un vecteur de 3 vecteurs de 4 éléments. Mais, il est tout aussi possible dedéfinir

vector<vector<int>> v = { {1, 2}, {2, 3, 4, 5}, {6, 7, 8} } ;

qui conduit à un vecteur de 3 éléments comportant chacun respectivement 2, 4 et 3 éléments.Voici un petit exemple d’utilisation d’un vecteur bidimensionnel :

// Vector2D

#include <iostream>

#include <vector>

using namespace std;

int main()

{ vector<vector<int>> v = { {1, 2}, {2, 3, 4, 5}, {6, 7, 8} } ; // C++11

for (auto l : v)

{ for (auto e : l) cout << e << " " ;

cout << endl ;

}

}

1 2

2 3 4 5

6 7 8

Vecteur bidimensionnel

LIVRE.book Page 233 Jeudi, 12. septembre 2019 6:19 18

Page 116: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11234

4 Les chaînes de style CDepuis C++98, nous pouvons manipuler des chaînes de caractères en utilisant le type stringdont nous avons présenté les propriétés les plus importantes au chapitre 11. Auparavant, C++représentait les chaînes d’une manière artificielle consistant à les placer dans des tableaux decaractères et à marquer leur fin par un caractère de code nul (il n’existait donc aucune infor-mation de longueur !). Pour des raisons historiques, cette convention ne peut pas être totale-ment ignorée du C++ moderne puisqu’elle reste utilisée :• par le compilateur pour représenter les chaînes littérales (telles que "bonjour"), ce qui a une

incidence en cas de « mélange » entre chaînes littérales et valeurs de type string ;• pour les éventuels arguments que l’on peut transmettre à la fonction main.Ce sont donc les propriétés de ces pseudo-chaînes (que nous nommerons chaînes de style C)que nous allons étudier ici. Nous en profiterons pour apporter quelques informations généra-les concernant les fonctions (héritées du C) manipulant ces chaînes, afin de vous faciliterl’éventuelle utilisation d’anciens codes.

4.1 Représentation des chaînes de style CUne chaîne de style C est représentée par une suite d’octets correspondant à chacun de sescaractères (plus précisément à chacun de leurs codes), le tout étant terminé par un octet sup-plémentaire de code nul. Cela signifie que, d’une manière générale, une chaîne de n caractè-res occupe en mémoire un emplacement de n+1 octets. C’est cette convention qu’utilise le compilateur pour représenter les chaînes littérales écritesdans vos programmes sous la forme :

"bonjour"

De plus, une telle notation sera traduite par le compilateur en un pointeur de type char * surle premier caractère de la chaîne.Voici un programme illustrant ces deux particularités :

#include <iostream>using namespace std ;int main(){ char * adr = "bonjour" ; while (*adr) { cout << *adr ; adr++ ; }}

bonjour

Convention de représentation des chaînes

LIVRE.book Page 234 Jeudi, 12. septembre 2019 6:19 18

Page 117: Programmer en C++ moderne - fnac-static.com

4 - Les chaînes de style C 235

La déclaration :char *adr ;

réserve simplement l’emplacement pour un pointeur sur un caractère (ou une suite de caractè-res). En ce qui concerne la constante :

"bonjour"

le compilateur a créé en mémoire la suite d’octets correspondants mais, dans l’affectation :adr = "bonjour"

la notation bonjour a comme valeur, non pas la valeur de la chaîne elle-même, mais sonadresse ; on retrouve là le même phénomène que pour les tableaux.La boucle while permet ensuite de parcourir chacun des caracètres de la chaîne.

4.2 Lecture et écriture de chaînes de style CNous avons déjà vu comment lire au clavier et afficher à l’écran des valeurs des différentstypes de base et du type string. Dans le cas des chaînes de style C, il n’y a pas de type spéci-fique. Il faut prévoir un emplacement pour « héberger » ces chaînes. Un tableau de caractères(dont le nom est, rappelons-le un pointeur constant de type char *) convient parfaitement.Voyez cet exemple dans lequel les chaînes C lues au clavier sont rangées dans les tableauxnatifs nom, prenom et ville :

// LectureChainesC#include <iostream>using namespace std ;int main(){ char nom [20], prenom [20], ville [25] ; cout << "quelle est votre ville : " ; cin >> ville ; cout << "donnez votre nom et votre prenom : " ; cin >> nom >> prenom ; cout << "bonjour cher " << prenom << " "<< nom << " qui habitez a " << ville ;}

quelle est votre ville : Parisdonnez votre nom et votre prenom : Dupont Yvesbonjour cher Yves Dupont qui habitez a Paris

Lecture et écriture de chaînes de style C

Remarques1 Rappelons que les informations lues sur cin sont délimitées par des caractères sépara-

teurs. Cette remarque vaut pour les chaînes de style C et il n’est donc pas possible de lireune chaîne renfermant un espace ou une fin de ligne. Nous verrons comment y parvenirau paragraphe 7.2 du chapitre 30.

LIVRE.book Page 235 Jeudi, 12. septembre 2019 6:19 18

Page 118: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11236

2 Notez bien que la lecture de n caractères implique le stockage en mémoire de n+1caractères. Par exemple, ici, le nom fourni par l’utilisateur ne doit pas contenir plus de19 caractères. Dans la pratique, pour éviter tout débordement du tableau accueillant lachaîne, on pourrait limiter la taille des informations lues sur le flot en recourant au« manipulateur paramétrique » setw qui sera étudié dans le chapitre consacré aux flots.

3 Jusqu’ici, nous avions affiché intuitivement la valeur de chaînes constantes dans desinstructions du genre : cout << "bonjour" ;

À la compilation, un emplacement est réservé pour la chaîne "bonjour". Lors de l’exé-cution, son adresse est transmise à l’opérateur << qui envoie sur le flot cout tous lescaractères trouvés à partir de cette adresse, jusqu’à la rencontre d’un caractère de codenul.

4.3 Initialisation de tableaux natifs par des chaînes de style C4.3.1 Initialisation de tableaux de caractères

Nous venons de voir comment placer des chaînes de style C dans des tableaux de caractères.Toutefois, si vous déclarez par exemple :

char ch[20] ;

vous ne pourrez pas pour autant transférer une chaîne constante dans ch, en écrivant uneaffectation du genre :

ch = "bonjour" ;

En effet, ch est une constante pointeur qui correspond à l’adresse que le compilateur a attri-buée au tableau ch ; ce n’est pas une lvalue ; il n’est donc pas question de lui attribuer uneautre valeur (ici, il s’agirait de l’adresse attribuée par le compilateur à la constante chaîne" bonjour" ).

En revanche, C++ vous autorise à initialiser votre tableau de caractères à l’aide d’une chaîneconstante. Ainsi, vous pourrez écrire :

char ch[20] = "bonjour" ;

Cela sera parfaitement équivalent à une initialisation de ch réalisée par une énumération decaractères (en n’omettant pas le code zéro – noté \0) :

char ch[20] = { ’b’,’o’,’n’,’j’,’o’,’u’,’r’,’\0’ }

N’oubliez pas que, dans ce dernier cas, les 12 caractères non initialisés explicitement seront :

• soit initialisés à zéro (pour un tableau de classe statique) : on voit que, dans ce cas, l’omis-sion du caractère \0 ne serait (ici) pas grave (sauf si l’on avait fourni 20 caractères !) ;

• soit aléatoires (pour un tableau de classe automatique) : dans ce cas, l’omission du caractère\0 serait nettement plus gênante.

LIVRE.book Page 236 Jeudi, 12. septembre 2019 6:19 18

Page 119: Programmer en C++ moderne - fnac-static.com

4 - Les chaînes de style C 237

De plus, comme C++ autorise l’omission de la dimension d’un tableau lors de sa déclaration,lorsqu’elle est accompagnée d’une initialisation, il est possible d’écrire une instruction telleque :

char message[] = "bonjour" ;

Celle-ci réserve un tableau, nommé message, de 8 caractères (compte tenu du 0 de fin).

Remarques1 Nous avons vu que la déclaration :

auto ch = "bonjour" ;

pouvait poser problème car ch est en fait un pointeur de type char * qui reçoit l’adressede la chaîne C "bonjour". Ce n’est donc pas une lvalue de type string, contrairement àce qui se passe avec : string ch = "bonjour" ;

2 Nous avons vu qu’il est possible de concaténer (opérateur +) une chaîne de type stringavec une chaîne littérale. En revanche, il n’est pas possible de concaténer deux littérauxcar une expression telle que "bonjour" + "monsieur" correspondrait à l’addition dedeux pointeurs de type char * et elle serait donc illégale.

4.3.2 Initialisation de tableaux natifs de pointeurs natifs sur des chaînesNous avons vu qu’une chaîne constante était traduite par le compilateur en une adresse quel’on pouvait, par exemple, affecter à un pointeur sur une chaîne. Cela peut se généraliser à untableau natif de pointeurs, comme dans :

char * jour[7] = { "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche" } ;

Cette déclaration réalise donc à la fois la création des 7 chaînes littérales correspondant aux 7jours de la semaine et l’initialisation du tableau jour avec les 7 adresses de ces 7 chaînes.Voici un exemple employant cette déclaration :

// InitTabPtrChainesC#include <iostream>using namespace std ;int main(){ char * jour[7] = { "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche" } ; cout << "donnez un entier entre 1 et 7 : " ; int i ; cin >> i ; cout << "le jour numero " << i << " de la semaine est " << jour[i-1] ;}

donnez un entier entre 1 et 7 : 6le jour numéro 6 de la semaine est samedi

Initialisation d’un tableau de pointeurs sur des chaînes de style C

LIVRE.book Page 237 Jeudi, 12. septembre 2019 6:19 18

Page 120: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11238

4.4 Les arguments transmis à la fonction mainLa fonction main peut récupérer les valeurs des arguments fournis au programme lors de sonlancement. Le mécanisme utilisé par l’utilisateur pour fournir ces informations dépend del’environnement. Il peut s’agir de commandes de menus pour des environnements dits gra-phiques ou intégrés. Dans les environnements fonctionnant en mode texte (on parle souventde « fenêtre de commande »), il s’agit de valeurs associées à la commande de lancement duprogramme (d’où le terme d’arguments de la ligne de commande encore utilisé parfois pourdécrire ce mécanisme). En voici un exemple où l’on demande l’exécution du programmenommé test, en lui transmettant les arguments arg1, arg2 et arg3 :

test arg1 arg2 arg3

Ces paramètres sont toujours des chaînes de style C (lorsqu’ils sont fournis dans une com-mande de lancement du programme, ils sont séparés par des espaces). Leur transmission à lafonction main (réalisée par le système) se fait selon les conventions suivantes :

• le premier argument reçu par main sera de type int et il représentera le nombre total de pa-ramètres fournis dans la ligne de commande (le nom du programme compte lui-même pourun paramètre) ;

• le second argument reçu par main sera l’adresse d’un tableau de pointeurs, chaque pointeurdésignant la chaîne correspondant à chacun des paramètres, le premier étant le nom duprogramme.

Ainsi, en remplaçant l’en-tête de la fonction main par celui-ci :int main (int nbarg, char * argv[])

nous obtiendrons :

• dans nbarg, le nombre total de paramètres ;

• à l’adresse argv[0], le premier paramètre, c’est-à-dire le nom du programme (dans notreexemple précédent, il s’agirait donc de la chaîne test) ;

• à l’adresse argv[1], le second paramètre (dans notre exemple, il s’agirait donc de la chaînearg1) ;

• etc.

Voici un exemple de programme utilisant ces possibilités. Il est accompagné de trois exem-ples d’exécution. Nous avons supposé que notre programme se nommait ArgLigneCom-mande, et nous l’avons exécuté à trois reprises en introduisant, dans une fenêtre decommandes, les commandes suivantes (suivant les implémentations, le nom de programmeaffiché en résultat pourra différer quelque peu) :

ArgLigneCommande

ArgLigneCommande parametre

ArgLigneCommande donnes.dat sortie.txt 25 septembre 2018

LIVRE.book Page 238 Jeudi, 12. septembre 2019 6:19 18

Page 121: Programmer en C++ moderne - fnac-static.com

4 - Les chaînes de style C 239

// ArgLigneCommande#include <iostream>using namespace std ;int main (int nbarg, char * argv[]){ int i ; cout << "Nom programme : " << argv[0]<< "\n" ; if (nbarg>1) for (i=1 ; i<nbarg ; i++) cout << "argument numero " << i << " : " << argv[i] << "\n" ; else cout << "pas d'arguments\n" ;}

Nom programme : D:\C++MODERNE\Projets\ArgLigneCommande\bin\Debug\ArgLigneCommande.exepas d'arguments

Nom programme : D:\C++MODERNE\Projets\ArgLigneCommande\bin\Debug\ArgLigneCommande.exeargument numero 1 : parametre

Nom programme : D:\C++MODERNE\Projets\ArgLigneCommande\bin\Debug\ArgLigneCommande.exeargument numero 1 : donnees.datargument numero 2 : sortie.txtargument numero 3 : 25argument numero 4 : septembreargument numero 5 : 2018

Exemple de récupération des arguments de la ligne de commande

4.5 Généralités sur les fonctions traitant des chaînes de style CC++ a hérité du C de nombreuses fonctions de manipulation de chaînes de style C. Commenous l’avons vu le type string offre les mêmes possibilités, sous une forme beaucoup plus fia-ble, et il devra donc être privilégié dans l’écriture de nouveaux codes. Pour vous permettrecependant d’exploiter d’anciens codes, les fonctions manipulant des chaînes de style C sontprésentées au paragraphe 3 de l’Annexe G fournie sur le site. Ici, nous vous fournissons quel-ques considérations générales concernant ces fonctions.

4.5.1 Ces fonctions travaillent toujours sur des adressesLa chaîne de style C ne constitue pas un type à part entière, mais simplement une conventionde représentation. On ne peut donc jamais transmettre la valeur d’une chaîne, mais seulementson adresse, ou plus précisément un pointeur sur son premier caractère. Ainsi, pour comparerdeux chaînes, on transmettra à la fonction concernée (par exemple, strcmp) deux pointeurs detype char*.Mieux, pour recopier une chaîne d’un emplacement à un autre, on fournira à la fonction vou-lue (par exemple, strcpy) l’adresse de la chaîne à copier et l’adresse de l’emplacement oùdevra se faire la copie. Encore faudra-t-il avoir prévu de disposer de suffisamment de place àcet endroit ! En effet, rien ne permet à la fonction de reconnaître qu’elle a écrit au-delà de ce

LIVRE.book Page 239 Jeudi, 12. septembre 2019 6:19 18

Page 122: Programmer en C++ moderne - fnac-static.com

Les vecteurs, les tableaux natifs et les chaînes CCHAPITRE 11240

que vous vouliez. En fait, vous disposerez cependant d’une façon de vous prémunir contre detels risques ; en effet, toutes les fonctions qui placent ainsi une information (susceptibled’être d’une longueur quelconque) à un emplacement d’adresse donnée possèdent deuxvariantes : l’une travaillant sans contrôle, l’autre possédant un argument supplémentaire per-mettant de limiter le nombre de caractères effectivement copiés à l’adresse concernée.

4.5.2 La fonction strlenLa fonction strlen fournit en résultat la longueur d’une chaîne dont on lui a transmis l’adresseen argument. Cette longueur correspond tout naturellement au nombre de caractères trouvésdepuis l’adresse indiquée jusqu’au premier caractère de code nul, ce caractère n’étant paspris en compte dans la longueur. Par exemple, l’expression :

strlen ("bonjour")

vaudra 7 ; de même, avec :char * adr = "salut" ;

l’expression :strlen (adr)

vaudra 5.

4.5.3 Le cas des fonctions de concaténationIl existe des fonctions dites de concaténation, c’est-à-dire de mise bout à bout de deux chaî-nes. A priori, de telles fonctions créent une nouvelle chaîne à partir de deux autres. Ellesdevraient donc recevoir en argument trois adresses ! En fait, ces fonctions se limitent à deuxadresses en convenant arbitrairement que la chaîne résultante serait obtenue en ajoutant laseconde à la fin de la première, laquelle se trouve donc détruite en tant que chaîne (en fait,seul son \0 de fin a disparu...). Là encore, on trouvera deux variantes dont l’une permet delimiter la longueur de la chaîne résultante.

4.6 Quelques précautions à prendre avec les chaînes de style CVoici ici quelques compléments d’information concernant des situations moins usitées, maisdont la méconnaissance peut nuire à l’adaptation d’anciens codes.

4.6.1 Une chaîne de style C possède une vraie fin, mais pas de vrai débutComme nous l’avons vu, il existe effectivement une convention de représentation de la find’une chaîne ; en revanche, rien de comparable n’est prévu pour son début. En fait, touteadresse de type char * peut toujours faire office d’adresse de début de chaîne.Par exemple, avec cette déclaration :

char * adr = "bonjour" ;

une expression telle que :strlen (adr+2)

LIVRE.book Page 240 Jeudi, 12. septembre 2019 6:19 18

Page 123: Programmer en C++ moderne - fnac-static.com

4 - Les chaînes de style C 241

serait acceptée : elle aurait pour valeur 5 (longueur de la chaîne commençant en adr+2). Demême, avec :

char * adr = "bonjour" ;

l’instruction suivante sera acceptée :cout << adr+10 ; // affiche des caractères à partir de l’adresse adr+10 // tant qu’on n’a pas trouvé de zero de fin !

Mais on affichera des caractères assez peu prévisibles et le zéro de fin pourra éventuellementse situer très loin !

4.6.2 Les risques de modification des chaînes constantesNous avons vu que, dans une instruction telle que :

char * adr = "bonjour" ;

le compilateur remplace la notation "bonjour" par l’adresse d’un emplacement dans lequel ila rangé la succession de caractères voulus. Dans ces conditions, on peut se demander ce quiva se produire si l’on tente de modifier l’un de ces caractères par une banale affectation telleque :

*adr = 'x' ; /* bonjour va-t-il se transformer en xonjour ? */* (adr+2) = 'x' ; /* bonjour va-t-il se transformer en boxjour ? */

A priori, la norme interdit la modification de quelque chose de constant. En pratique, beau-coup de compilateurs l’acceptent, de sorte que l’on aboutit à la modification de notre cons-tante bonjour en xonjour ou boxjour !Signalons qu’une constante chaîne apparaît également dans une instruction telle que :

cout << "bonjour" ;

Ici, on pourrait penser que sa modification n’est guère possible puisque nous n’avons pasaccès à son adresse. Cependant, lorsque cette même constante (bonjour) apparaît en plusieursemplacements d’un programme, certains compilateurs peuvent ne la créer qu’une fois ; dansces conditions, la chaîne transmise au flot cout peut très bien se trouver modifiée par le pro-cessus décrit précédemment...

RemarqueDans une déclaration telle que : char ch[20] = "bonjour" ;

il n’apparaît pas de chaîne littérale, et ceci malgré la notation employée ("...") laquelle,ici, n’est qu’une facilité d’écriture remplaçant l’initialisation des premiers caractères dutableau ch. En particulier, toute modification de l’un des éléments de ch, par une ins-truction telle que : *(ch + 3) = 'x' ;

est parfaitement licite (nous n’avons aucune raison de vouloir que le contenu du tableauch reste constant).

LIVRE.book Page 241 Jeudi, 12. septembre 2019 6:19 18