Vous êtes sur la page 1sur 83

Algorithmique et programmation

Langage C++

Deuxième année
2022 - 2023

1 2 3
V. Judalet  H. Le Guen-Glet  C. Boutammine

1. vincent.judalet@estaca.fr
2. helene.leguen-glet@estaca.fr (Toute erreur ou coquille doit être signalée à cette adresse)
3. cherif.boutammine@estaca.fr

1
2
Table des matières
1 Introduction 7
1.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.1 Objectif du cours . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.2 Contenu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.3 Plan du module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1.4 Références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2 La diérence entre la partie matérielle et logicielle . . . . . . . . . . . . . . . . . 8
1.3 Historique de l'informatique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4 Comment aboutir d'un besoin à un programme ? . . . . . . . . . . . . . . . . . . 9
1.5 Les langages de programmation . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5.1 L'algorithmique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.5.2 Popularité des langages informatiques . . . . . . . . . . . . . . . . . . . . 11
1.5.3 Langage C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5.4 Processus de compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5.4.1 Le pré-processeur . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5.4.2 Le compilateur . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5.4.3 Éditeur de lien . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2 Éléments de base du langage C++ 15


2.1 Règles de présentation d'un code source . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 La fonction principale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 L'espace de nom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.4 Les types de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.5 Les chaînes de caractères . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.6 Les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6.1 Les mots clés réservés . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6.2 La portée des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.6.3 Les constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.6.4 Les pointeurs et les références . . . . . . . . . . . . . . . . . . . . . . . . 21
2.6.4.1 Rappels sur les pointeurs . . . . . . . . . . . . . . . . . . . . . 21
2.6.4.2 La référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.6.5 L'allocation dynamique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.6.6 Les structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.6.7 Les énumérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.7 Les ux d'entrée/sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.8 Les opérateurs en C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.9 Les structures de contrôle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.9.1 Structure conditionnelle if . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.9.2 Structure itérative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.9.3 L'instruction de saut break . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3
2.9.4 Structure conditionnelle switch case . . . . . . . . . . . . . . . . . . . . . . 28
2.10 Le tableau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.10.1 Les tableaux statiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
2.10.2 Les tableaux dynamiques . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

3 Les fonctions 31
3.1 Passage des paramètres par valeur et par référence . . . . . . . . . . . . . . . . . 32
3.1.1 Passage des paramètres par valeur . . . . . . . . . . . . . . . . . . . . . . 32
3.1.2 Passage des paramètres par adresse . . . . . . . . . . . . . . . . . . . . . 33
3.1.3 Passage des paramètres par référence . . . . . . . . . . . . . . . . . . . . 33
3.2 Paramètre par défaut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3.3 Surcharge de fonction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4 La récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.5 Application à la récursivité : le tri fusion . . . . . . . . . . . . . . . . . . . . . . 37
3.6 La programmation modulaire et l'architecture logicielle . . . . . . . . . . . . . . 38

4 Les classes 41
4.1 La programmation orientée objet . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.1.1 Pourquoi un nouveau concept ? . . . . . . . . . . . . . . . . . . . . . . . 41
4.1.2 Présentation générale de la POO . . . . . . . . . . . . . . . . . . . . . . 42
4.2 Les classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.2.2 Des données et des méthodes . . . . . . . . . . . . . . . . . . . . . . . . 44
4.2.3 Structuration du code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.3 Les spécicateurs d'accès . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.4 Les constructeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.4.1 L'utilisation d'une liste d'initialisation . . . . . . . . . . . . . . . . . . . 50
4.4.2 L'appel du constructeur lors d'une allocation dynamique . . . . . . . . . 50
4.5 Le constructeur de copie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.6 Encapsulation et accesseurs/mutateurs . . . . . . . . . . . . . . . . . . . . . . . 51
4.7 L'amitié . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.8 La surcharge d'opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

5 Héritage 57
5.1 Notion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.2 Accessibilité des éléments aux classes enfants . . . . . . . . . . . . . . . . . . . . 58
5.3 Spécicateurs d'accès de l'héritage . . . . . . . . . . . . . . . . . . . . . . . . . . 60
5.4 Et les constructeurs dans l'héritage ? . . . . . . . . . . . . . . . . . . . . . . . . 61
5.5 Aectation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.6 Le masquage des méthodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.7 Le polymorphisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

6 Compléments 67
6.1 Aperçu de la STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.1.1 La classe string . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.1.2 Les conteneurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.1.2.1 Les itérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.1.2.2 La classe vector . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.1.2.3 La classe map . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.1.3 Algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

4
6.2 Gestion des exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

A Développer sous microsoft visual studio 73


A.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
A.2 Le projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
A.3 La compilation et exécution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
A.4 Le débogueur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
A.4.1 Les points d'arrêts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
A.4.2 Parcourir le code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
A.4.3 Voir les valeurs des variables . . . . . . . . . . . . . . . . . . . . . . . . . 78
A.5 L'assistant de création de classes . . . . . . . . . . . . . . . . . . . . . . . . . . 78

B Solutions des exercices 81

5
6
Chapitre 1
Introduction
1.1 Généralités
1.1.1 Objectif du cours

 Aider les étudiants de l'ESTACA à se sentir conant dans la lecture et/ou l'écriture de
programmes.
 Les préparer aux métiers de la vie réelle où ils se retrouveront en face de codeurs /
développeurs.
 Les aider à tirer prot des méthodes informatiques dans le domaine de leur choix.
 Apprendre à modéliser les données d'un problème dans une formulation algorithmique.
 Apprendre un nouveau langage basé sur la programmation objet.

1.1.2 Contenu

 La programmation procédurale en C++


 Instructions, mots-clés, aectations
 Types de données, opérateurs et variables
 Instructions de contrôle
 Récursivité
 Introduction à la programmation orienté objet
 Classes, objets, attributs, encapsulation
 Héritage simple, amitié, polymorphisme, STL
 Des exemples et des exercices d'applications

1.1.3 Plan du module

Le module est découpé de la manière suivante :

 Chap 1 : des généralités


 Chap 2 : les éléments constitutifs du langage, développer dans un style procédural avec
les mots du C++
 Chap 3 : les nouvelles possibilités concernant les sous-programmes en C++
 Chap 4 : la notion de classe
 Chap 5 : l'héritage ou comment tirer avantage de la programmation objet
 Chap 6 : compléments-STL

7
1.1.4 Références

Quelques liens vers des sites intéressants : certains sont des cours et vous guident dans des
apprentissages, d'autres plutôt des références lorsque vous cherchez à comprendre un concept
ou connaître l'utilisation d'une fonction précise.
https ://zestedesavoir.com/tutoriels/531/les-bases-de-la-programmation/
https ://zestedesavoir.com/tutoriels/822/la-programmation-en-c-moderne/
http ://www.cplusplus.com/
http ://www.sorting-algorithms.com/
https ://en.cppreference.com
http ://guillaume.belz.free.fr/doku.php ?id=programmez_avec_le_langage_c
Bjarne Stroustrup, Programming : Principles and Practice using C++, Addison-Wesley,
2009 (ISBN 978-0321543721).
P. Deitel, C++ How to program, 20 Hall, 2011, 8e éd., 1104 p. (ISBN 978-0-132-66236-9).
Le site openclassrooms propose diérents cours accessibles aux débutants. Pour le C++ :
https ://openclassrooms.com/fr/courses/1894236-programmez-avec-le-langage-c

1.2 La diérence entre la partie matérielle et logicielle


Les deux concepts Hardware et Software sont deux domaines diérents. En eet, le hard-
ware désigne le matériel physique constituant les ordinateurs de bureau, le PC, les matériels
externes tandis que le software (ou encore logiciel, application ou programme) est un programme
informatique permettant de résoudre un service à l'utilisateur.

Figure 1.1  Software VS Hardware

1.3 Historique de l'informatique


La notion d'algorithme remonte à l'antiquité. Cela s'est précisé dans le domaine des mathé-
matiques par l'emploi de variables. L'algorithme au sens informatique apparait avec l'invention
des premières machines dotés d'automatismes.
Le mot algorithme vient du nom du mathématicien perse du 9
ième siècle Abu Abdullah

Muhammad ibn Musa al-Khwarizmi. Le mot algorisme se reférait à l'origine uniquement aux

8
règles d'arithmétique utilisant les chires indo-arabes numériques mais cela a évolué par la
traduction en latin européen du nom Al-Khwarizmi's en algorithme au 18
ième siècle. L'utilisation

du mot a évolué pour inclure toutes les procédures dénies pour résoudre un problème ou
accomplir une tâche.

Parmi les algorithmes célèbres que vous connaissez peut être déjà on peut trouver :

 L'algorithme d'Euclide (300 av. JC.). Cet algorithme qui sert à calculer le plus grand
diviseur commun est le suivant :
 diviser a par b, on obtient le reste r
 remplacer a par b
 remplacer b par r
 continuer tant que c'est possible, le pgcd est le dernier reste non nul
 L'algorithme d'Archimède (200 av. JC) donne une approximation du nombre Pi en en-
cadrant la surface du cercle par les surfaces des polygones inscrits et circonscrits
 Le crible d'Eratosthènes (200 av. JC) a déni un algorithme pour retrouver les nombres
premiers.

Au 12ième siècle Adelard de Bath introduit le mot algorismus dérivé de Al Kwarizmi.

George Boole (1847) invente l'algèbre binaire, la base des ordinateurs. En fait il a unié la
logique et les calculs dans un symbolisme commun.

Ada Lovelace ainsi que Babbage écrivaient des programmes pour le projet de machine à
diérences puis la machine analytique de Babbage.

En 1945, l'allemand K Zuse, inventeur de l'ordinateur Z3, aurait déni un langage évolué
pour cette machine (avec arrays et records). On possède peu de documents sur ce langage.

Le concept a été formalisé en 1936 avec les machines d'Alan Turing et le calcul lambda
d'Alonzo Church, ce qui a alors créé les fondations de l'informatique.

1.4 Comment aboutir d'un besoin à un programme ?


La réalisation d'un programme informatique passe par plusieurs étapes.

Exigences de haut niveau : objectifs du programme

Algorithme : les étapes

Le langage de programma-
tion : code qui est accessible à
la compréhension d'un humain

Langage assembleur : représentation


symbolique d'un code machine

Code machine : code binaire

9
1.5 Les langages de programmation
Un langage de programmation a pour fonction de traduire un algorithme dans un langage
interprétable par la machine.

1.5.1 L'algorithmique

L'algorithme-recette de la crêpe

Figure 1.2  Des crèpes !

4 ÷ufs, 3 tasses de lait et 2 tasses de farine


Algorithme :

1. Placer tous les ingrédients dans un récipient

2. Mélanger jusqu'à avoir un mélange homogène sans grumeaux de farine

3. Chauer une poêle non adhésive avec un peu de matière grasse (huile ou beurre)

4. Verser une louche de la pâte dans la poêle

5. Etaler la pâte jusqu'à avoir une couche ne régulière dans la poêle

6. Attendre une à deux minutes avant de retourner la crêpe et de laisser chauer à peu près
30 secondes

7. Il ne reste plus qu'à la manger

Un algorithme est diérent d'un langage de programmation :


 Algorithme : recette/ étapes générales
 Langage : une construction syntaxiquement valide selon des règles spéciques
  un algorithme est implémenté en utilisant un langage de programmation spécique 

Niveau Exemple

Langage naturel ajouter 2 et 3

Algorithme result <- 2 + 3

Langage de programmation #include<iostream>


int main()
{
int result ;
result=2+3 ;
return 0 ;
}
code machine 1011100000000000101011110000001010000

10
Un langage a une syntaxe spécique et sa propre sémantique.
 La syntaxe fait référence à la représentation externe (ou structure grammaticale) : consi-
dérant un texte, est-ce un code bien formé selon les règles du langage ?
 La sémantique concerne le sens. Que signie un code bien formé ?

 Les chiens mordent les hommes  VS  Les hommes mordent les chiens .

Ces deux phrases sont correctes de manière syntaxique, mais seule la première est sémanti-
quement correcte.

1.5.2 Popularité des langages informatiques

On peut de manière légitime se poser la question de quel langage apprendre aujourd'hui.


En eet, le nombre de langages existants est important.

Figure 1.3  Les langages informatiques

1
L'index TIOBE suggère un début de réponse en publiant tous les mois la liste des langages
les plus populaires. Cet indice est basé sur les recherches réalisées dans les diérents moteurs
de recherche. Il ne veut en aucun cas dire qu'un langage est mieux qu'un autre.

Figure 1.4  Popularité des langages informatiques

1. https ://www.tiobe.com/tiobe-index/

11
Sur la gure 1.4 de l'index TIOBE du mois d'août 2021, on observe qu'en couplant le C et
le C++ on arrive à un taux de 20 %. La connaissance de ces deux langages est donc un bon
point de départ pour aborder les programmes dans la vie réelle.
Si maintenant nous restreignons l'étude au domaine des systèmes embarqués la présence du
2
C et du C++ prédomine clairement les autres langages . Une étude publiée en 2016 est illustrée
par la gure 1.5.

Figure 1.5  Les langages informatiques dans les systèmes embarqués

1.5.3 Langage C++

Ci-dessous quelques éléments clé dans la construction du langage C++ :


 Bjarne Stroustrup crée  C with classes  en 1979 comme un ensemble d'extension au
langage C.
 En 1983, le nom du langage change pour C++
 En 1985, la première édition du  The C++ Programming Language 
 En 1998, C++ est standardisé et publié : ISO/IEC 14882 :1998
Le C++ ore une compatibilité importante avec le C. Vous pouvez donc sans risque utilisez
des mots-clés en C dans votre code C++, même si il faut privilégier bien sûr les nouvelles
notions.

1.5.4 Processus de compilation

La compilation a pour objectif de transformer des chiers source en programme exécutable.


La compilation se déroule en plusieurs étapes qu'il est utile de comprendre pour pouvoir corriger
ecacement les erreurs. La gure 1.6 résume ces diérentes étapes.

2. https ://spectrum.ieee.org/static/interactive-the-top-programming-languages-2016

12
Figure 1.6  Étapes de compilation

1.5.4.1 Le pré-processeur
L'objectif du pré-processeur est de faire un pré-traitement sur les chiers sources avant de
lancer la compilation. Le pré-processeur exécute les directives précédées d'un #. Par exemple :
#include " le .h"
#include <libray>
#dene VARIABLE_NAME VALUE
#ifdef VARIABLE_NAME (or #ifndef)
...
#endif
Le pré-processeur fait des copier/coller dans les chiers sources. Le contenu des chiers
headers ".h" est notamment recopié dans les chiers cpp et les ".h" ne sont plus utilisés dans
le reste du processus de génération de l'exécutable. Dans cette phase chaque chier source est
manipulé de manière séparée.

1.5.4.2 Le compilateur
Chaque chier est toujours manipulé indépendamment des autres. Lors de cette étape beau-
coup de vérications ont lieu.
Trois cas peuvent se présenter :
 Le compilateur ne "comprend" pas le code écrit (des erreurs de syntaxe par exemple) ->
il indique des erreurs, il ne peut poursuivre le processus de génération.
 Le compilateur détecte des problèmes qui ne l'empêche cependant pas de poursuivre ->
il indique des warnings 3 .
 Le compilateur ne détecte aucun problème ( 0 erreur 0 warning).
A l'issue de la phase de compilation (si celle-ci se passe sans erreur), il y a un chier "binaire"
par chier source, pour arriver à cela plusieurs étapes sont nécessaires :
 L'analyse lexicale
 L'analyse syntaxique
 Les optimisations (diérentes selon le mode de compilation : release ou debug)
 Génération d'un code assembleur
 Transformation du code assembleur en code binaire
Un code généré dière d'un système d'exploitation (Windows, Linux ou Mac OS) à un autre.

3. Il faut corriger ceux-ci avant d'exécuter le programme car en général ils sont le signe d'une possible erreur
d'exécution

13
1.5.4.3 Éditeur de lien
linker
L'objectif de l'éditeur de lien ( en anglais) va prendre les diérents chiers binaires issus
de la compilation pour en faire un chier exécutable (.exe) ou encore une librairie dynamique
(.dll). Il va notamment vérier que les fonctions déclarées correspondent bien à leur dénition.

14
Chapitre 2
Éléments de base du langage C++
2.1 Règles de présentation d'un code source
Avant de démarrer les éléments du langage C++, il est important de rappeler quelques règles
d'écriture d'un code source. Ces règles permettent de faciliter la lecture, de diminuer le risque
d'erreurs et de repérer rapidement les erreurs éventuelles :

 Nommer les identicateurs (noms de variables, noms de fonctions, ...) de manière claire
et cohérente
 Ajouter des commentaires au début de chaque fonction, et à l'intérieur des fonctions
quand cela est nécessaire, an de faciliter la lecture du code. L'utilisation des com-
mentaires est identique au langage C // pour une ligne, et /*code*/ pour un code sur
plusieurs lignes.
 Indenter le code : cela consiste à ajouter des tabulations quand on dénit un nouveau
bloc de code
 Éviter la création de fonctions trop longues (>50 lignes, le critère peut être de permettre
de voir toute la fonction d'un seul coup d'÷il dans l'éditeur), et de chiers trop longs
(même si ça dépend du contexte il faut rester dans le raisonnable).

2.2 La fonction principale


Tout programme C++ doit contenir une fonction main() et une seule. Cette fonction est
le point d'entrée du programme. D'un point de vue extérieur, seule cette fonction est appe-
lée. On voit très rapidement qu'il n'est pas possible de mettre l'ensemble du code dans une
seule fonction. C'est pour cela que l'on structure ensuite le code, en fonction de la taille et de
la complexité attendue, en plusieurs sous-programmes ou plusieurs modules qui sont appelés
directement ou indirectement à partir d'une ligne de code se trouvant dans cette fonction main.
Un petit programme simple pourrait s'écrire en C++ sous la forme suivante :

1 #i n c l u d e <iostream >
2 i n t main ( )
3 {
4 s t d : : cout << " Hello , world ! " ;
5 return 0;
6 }

15
2.3 L'espace de nom
Comme on peut l'imaginer un programme peut devenir très important en nombre de lignes
de code, il peut, alors, être dicile de s'assurer que le nom d'une variable ou d'une fonction ne

namespace
soit pas déjà utilisé. Pour cela, C++ fournit un mécanisme permettant de regrouper sous un
espace de nom (en anglais ) un ensemble de données ou des fonctions. Par exemple
la bibliothèque standard est dénie dans un espace de noms nommé std. C'est pourquoi lorsque
l'on veut utiliser le mot clé cout, qui permet d'écrire sur la console, on le précède de std :: , car
cout fait partie de la librairie standard de C++.
Si dans notre programme on sait que les fonctions ou les données de la librairie standard,
tel que cout, n'entraîneront pas de conit avec d'autre fonctions ou données du programme, on
spécie en début de programme que l'on utilise cet espace de nom au moyen de using namespace
std. Ensuite il ne sera plus nécessaire de préciser le préxe std :: devant les noms de fonctions
appartenant à la librairie standard.
Le code source écrit dans l'exemple précédent peut ainsi s'écrire :

1 #i n c l u d e <iostream >
2 u s i n g namespace s t d ;
3 i n t main ( )
4 {
5 cout << " Hello , world ! " ;
6 return 0;
7 }

Dans les exemples de ce cours, par souci de simplication, l'espace de noms ne sera pas
toujours précisé. Pour pouvoir faire fonctionner ces exemples, vous devrez le rajouter.

2.4 Les types de données


Les types pouvant de base du C++ sont donnés au tableau 2.1.
La taille en nombre d'octets d'une variable dépend du type de la variable. Ces tailles ne
sont pas standardisées, pour les connaître vous pouvez utiliser la fonction sizeof(vt), vt repré-
sentant ici soit un type soit une variable. Le résultat est donné en nombre d'octets. Par exemple
sizeof(char) vaut 1.
La déclaration de variables est nécessaire lorsque l'on souhaite stocker ou manipuler des
données. Dans la plupart des langages de programmation, on doit préciser le type de chaque
variable pour que l'ordinateur connaisse la nature des données que l'on souhaite sauvegarder
et qu'il puisse réserver un espace mémoire susant. Cela lui permet également de savoir quelles
opérations peuvent être appliquées sur telle ou telle variable.
En C++, il est possible de dénir une variable de type bool. Cette variable prendra la valeur
true ou false qui sont des mots-clés réservés du langage. Vous avez donc le droit d'écrire :

1 #i n c l u d e <iostream >
2 u s i n g namespace s t d ;
3
4 i n t main ( )
5 {
6 b o o l nonTrouve = t r u e ; // v a r i a b l e b o o l é enne dé c l a r é e e t i n i t i a l i s é e
7 int i = 0;
8 i n t tab [ ] = { 0 , 0 , 1 , 1 , 0 } ;
9 w h i l e ( nonTrouve ) // t a n t que nonTrouve e s t v r a i
10 {

16
Nom Description Intervalle
char caractère ou petit entier signed : -128 to 127 ou unsigned : 0
to 255

short int entier court signed : -32768 to 32767 ou unsi-


gned : 0 to 65535

int entier signed : -2147483648 to 2147483647


ou unsigned : 0 to 4294967295

long int entier long signed : -2147483648 to 2147483647


ou unsigned : 0 to 4294967295

bool valeur booléenne true ou false

oat nombre réel +/- 3.4e +/- 38 ( 7 digits)

double nombre réel à précision +/- 1.7e +/- 308 ( 15 digits)


double

long double double long +/- 1.7e +/- 308 ( 15 digits)

wchar_t caractère stocké sur 2 octets 0 to 65535

Table 2.1  Les types natifs du langage C++

11 i f ( tab [ i ] == t r u e ) //Comme en C : 0 e s t c o n s i d é r é comme f a l s e


12 nonTrouve = f a l s e ; // nonTrouve prend l a v a l e u r f a l s e
13 else
14 i ++;
15 }
16 cout << " l ' element a e t e t r o u v e a l ' i n d i c e " << i ;
17 return 0;
18 }

Sur la console le message suivant sera aché : l'element a ete trouve a l'indice 2.
D'un point de vue syntaxique, void est un type fondamental. Il n'existe cependant aucun
objet de type void. Ce type est employé pour spécier qu'une fonction ne renvoie aucune valeur
ou comme type de base pour les pointeurs d'objets de type inconnu.
Il est également possible en C++ de dénir des nouveaux types au moyen de typedef comme
en C.

2.5 Les chaînes de caractères


Les chaînes de caractères correspondent à un ensemble de caractères. La valeur d'une chaîne
de caractères est entourée de doubles côtes (par exemple : "Hello, word") au contraire d'un seul
caractère qui est entouré par des simples côtes (par exemple : 'A').
Pour rappel en C pour utiliser une chaîne de caractère on utilise un tableau de char. Chaque
case du tableau contient donc un caractère.
En C++, il est possible d'utiliser la classe string pour une chaîne de caractère. Ce type est
déni dans la librairie standard. Pour utiliser ce type nous avons besoin d'inclure un chier
d'entête additionnel dans notre code source #include <string> et avoir accès à l'espace de nom
std.

17
En C++ privilégiez toujours l'utilisation de la classe string plutôt que le tableau de char utilisé
en C.
De nombreuses fonctionnalités pour manipuler des variables de type string sont disponibles,
elles permettent par exemple de connaître la taille de la chaîne de caractères (avec la méthode
size()), d'aecter directement une chaîne de caractères à une autre ou encore de concaténer
deux chaînes de caractères. Des informations complémentaires sont disponibles à la n de ce
poly.

1 #i n c l u d e <iostream >
2 #i n c l u d e <s t r i n g >
3
4 u s i n g namespace s t d ;
5
6 i n t main ( )
7 {
8 s t r i n g m y F i r s t S t r i n g = " H e l l o everybody " ;
9 cout << m y F i r s t S t r i n g <<e n d l ;
10 return 0;
11 }

2.6 Les variables


Pour utiliser une variable en C++, il faut toujours la déclarer d'abord en spéciant son
type de donnée. La syntaxe pour déclarer une nouvelle variable est d'écrire le type de la donnée
(int, bool, oat, ..) suivi d'un identicateur valide de variable. Un identicateur valide est
une séquence de une ou plusieurs lettres, de chires ou encore du caractère underscore (_). Il
commence nécessairement par une lettre. Le nom d'une variable ne doit contenir ni espace ni
symbole autre que le symbole "_".
Attention, le langage est sensible à la casse, il fait la diérence entre les majuscules et les
minuscule. En C++ "James" et 'james" sont deux identicateurs diérents.

Exercice 1 Déterminer si les noms de variables suivantes sont valides ou non.

Id Valid/Invalid ?
z1
one-banana
2h
$money
a msg
number
Me_gusta
dot.com
RiDdlE

Table 2.2  Identicateurs de variables avec des noms valides

18
Contrairement au langage C, où il est d'usage de déclarer les variables en début de sous-
programme, en C++, la déclaration d'une variable se fait au plus près de sa première utilisation.
Ce qui permet également de faire son initialisation dans la foulée. En l'absence d'initialisation
de celle-ci il faut imaginer que la valeur d'une variable peut être n'importe quoi. Si vous utilisez
ensuite cette variable (pour l'aecter à une autre variable par exemple) votre compilateur
produit un warning indiquant que l'on cherche à utiliser une variable non initialisée.
Il y a en C++ deux manières d'initialiser la valeur d'une variable lors de sa déclaration.
La première manière est une aectation, en mettant le symbole = suivi de la valeur que l'on
veut aecter à cette variable,

1 s t r i n g nom="Dupond" ;
2 i n t compteur =0;

La deuxième est une initialisation de type fonction, en mettant entre parenthèses, directe-
ment derrière le nom de la variable, la valeur d'initialisation. Cette deuxième façon de faire sera
expliquée lorsque la notion de constructeur de classe sera abordée (cf. 4.4).

1 s t r i n g prenom ( " Jean " ) ;


2 int indice (4) ;

2.6.1 Les mots clés réservés

Les mots-clés ci-dessous sont des mots du langage C++, ils ne peuvent pas être utilisés
comme nom de variable par exemple.
asm, auto, bool, break, case, catch, char, class, const, const_cast, continue, default, delete,
do, double, dynamic_cast, else, enum, explicit, export, extern, false, oat, for, friend, goto, if,
inline, int, long, mutable, namespace, new, operator, private, protected, public, register, reinter-
pret_cast, return, short, signed, sizeof, static, static_cast, struct, switch, template, this, throw,
true, try, typedef, typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t,
while.

2.6.2 La portée des variables

Une variable globale est déclarée à l'extérieur de toute fonction, dans le chier source.
Cette variable est alors accessible partout dans le chier.
Une variable locale est déclarée à l'intérieur du corps d'une fonction ou d'un bloc. Elle
est accessible uniquement dans cette fonction ou ce bloc, en dehors de cet endroit de dénition
elle est inconnue.

1 #i n c l u d e <iostream >
2 i n t z ; // z e s t une v a r i a b l e g l o b a l e , e l l e e s t dé f i n i e à l ' ext é r i e u r d ' une
fonction
3 i n t main ( )
4 {
5 i n t x , y ; // x e t y s o n t des v a r i a b l e s l o c a l e s à l a f o n c t i o n main
6 x=5;
7 y=2
8 f o r ( i n t i =0; i <=10; i ++) // i n ' e s t connue qu ' à l ' i n t é r i e u r de l a b o u c l e
9 {
10 x=x+1;
11 }

19
12 z=x=y ; // z e s t connu i c i
13 s t d : : cout<<z ; // a f f i c h e l a v a l e u r de z s u r l a s o r t i e c o n s o l e
14 return 0;
15 }

Une variable déclarée dans un sous-programme n'est connue que dans ce sous-programme.
On ne peut pas y accéder de l'extérieur.

Prenez l'habitude de déclarer les variables locales au plus près de l'endroit où vous en avez
besoin et n'utilisez pas de variables globales.

2.6.3 Les constantes

Pour dénir une constante, une première solution se fait en utilisant la directive du prépro-
cesseur #dene. Le format est le suivant :
#dene identier value
Attention dans ce cas, on ne crée pas une nouvelle variable. Il s'agit simplement d'une direc-
tive pour le préprocesseur. Quand celui-ci est lancé, il remplace chaque instance de l' identier
par la valeur indiquée. Un exemple est illustré ci-après :

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 #d e f i n e PI 3 . 1 4 1 5 9
6 #d e f i n e NEWLINE `\ n '
7
8 i n t main ( )
9 {
10 double r =5.0;
11 double a r e a _ o f _ c i r c l e ;
12 a r e a _ o f _ c i r c l e = PI * r * r ;
13 cout << " l ' a i r e du c e r c l e de rayon 5 cm : "<<a r e a _ o f _ c i r c l e <<" cm"<<NEWLINE;
14 }

Cela peut être utilisé pour autre chose que des constantes, simplement dans le cas où l'on
souhaite remplacer une expression littérale par une autre expression.
#dene expressionASubstituer nouvelleExpression
Dans ce cas il n'y a pas d'espace mémoire reservé pour stocker la valeur.
L'autre solution pour dénir une constante consiste à insérer le mot-clé const entre le type
1
et nom de la variable . Si ensuite dans le code, une ligne du programme tente de modier le
contenu de la variable, une erreur est générée.

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 i n t main ( )
6 {

1. le mot const peut aussi être indiqué avant le nom du type

20
7 i n t c o n s t PI = 3 . 1 4 1 5 9 ;
8 double r =5.0;
9 double a r e a _ o f _ c i r c l e ;
10 a r e a _ o f _ c i r c l e = PI * r * r ;
11 cout << " l ' a i r e du c e r c l e de rayon 5 cm : "<<a r e a _ o f _ c i r c l e <<" cm"<<e n d l ;
12 }

2.6.4 Les pointeurs et les références

En C++ comme en langage C, il est dicile d'ignorer comment les variables sont gérées en
mémoire. Même si en C++ on prend un peu de hauteur, tout ce qui a été vu en langage C
reste valable.

2.6.4.1 Rappels sur les pointeurs


Quand vous déclarez une variable int compteur=5; une case mémoire est réservée dans laquelle
la valeur 5 est stockée. L'identicateur "compteur" fait référence au contenu de la case mémoire
et non à son adresse. Si l'on souhaite désigner l'adresse de la case mémoire on doit précéder le
nom de la variable par le symbole esperluette "&".
Une variable de type pointeur (exemple int *p;) est une variable qui contient l'adresse d'une
case mémoire et non une valeur. Cette adresse mémoire peut être l'adresse d'une autre variable
présente dans le code : le pointeur pointe sur la variable. Par exemple :

1 i n t compteur = 8 ;
2 i n t * p=&compteur ;

Dans l'exemple, le programme commence par réserver en mémoire une zone de taille néces-
saire pour stocker un entier, il note dans cette zone la valeur 8. Cette zone créée se trouve à
un endroit particulier que le programme choisi, cet endroit (que l'on nomme adresse) peut être
connue à l'aide de &compteur. Qui se lit "adresse de compteur".
A la ligne suivante ( int *p) le programme crée une nouvelle zone mémoire pour la variable
p qui va cette fois-ci lui permettre de stocker l'adresse d'un entier. Le contenu de cette variable
est initialisé avec l'adresse de la variable compteur.
La variable p contient une adresse. Pour accéder au contenu de cette adresse (le contenu de
la case stockant la valeur de compteur dans notre cas) il faut précéder le nom de cette variable
par une *. En d'autres termes, *p signie le contenu de la case pointée par p.

2.6.4.2 La référence
En C++ est introduit une nouvelle notion la référence. L'intérêt de la référence sera abordé
dans la partie traitant des paramètres d'une fonction.
Une référence est un alias pour une autre variable. La référence doit être initialisée vers
une certaine variable mais ne pourra par la suite plus se référer à une autre variable.

1 i n t compteur =0;
2 i n t &refCompteur=compteur ;
3 compteur++;
4 refCompteur++;
5 cout<<compteur<<" "<<refCompteur ;

21
Dans l'exemple ci-dessus refCompteur est une référence sur la variable compteur. Quand on
incrémente refCompteur, c'est compteur qui est incrémenté. Sur la console, il est aché "2 2.
Ci-dessous le même exemple avec la notion de pointeur :

1 i n t compteur =0;
2 i n t * ptCompteur=&compteur ;
3
4 compteur++;
5 ( * ptCompteur )++;
6 cout<<compteur<<" "<<*ptCompteur ;

Sur la console, il est de nouveau aché "2 2.


La diérence entre un pointeur et une référence est qu'un pointeur peut être déplacé, c'est-
à-dire qu'on peut lui changer l'adresse vers laquelle il pointe. Alors que la référence ne peut pas
se référer à une autre variable si elle est déjà référencée sur une certaine variable.

L'utilisation des références est plus simple pour le passage de paramètres. C'est pourquoi il est
conseillé d'utiliser les pointeurs que lorsqu'ils sont vraiment nécessaires.

2.6.5 L'allocation dynamique

L'allocation dynamique consiste à allouer de la mémoire à une variable de type pointeur à


partir du moment ou l'on connaît l'espace mémoire nécessaire. En langage C, cette opération
est réalisée à l'aide de la fonction malloc. En C++, l'allocation dynamique se fait au moyen du
mot clé new.

1 i n t * var = new i n t ; // Éq u i v a l e n t à ( i n t * ) malloc ( s i z e o f ( i n t ) ) .

La ligne d'instruction précédente permet de déclarer une variable var de type "pointeur vers
un entier" et de lui aecter l'adresse d'une case mémoire (allouée grâce à new , qui retourne
l'adresse d'un emplacement mémoire fraichement alloué) de taille susante pour stocker un
entier. Il faut impérativement libérer l'espace mémoire alloué, une fois que celui-ci n'est plus
utile. Cela peut se faire grâce à l'instruction delete :

1 d e l e t e var ; // Éq u i v a l e n t à f r e e ( var ) ;

Les pointeurs sont aussi utilisés pour la manipulation de tableaux. On utilise pour cela les
opérateurs new[] et delete [] .

1 i n t * tab = new i n t [ 1 0 ] ;
2 ...
3 d e l e t e [ ] tab ;

2.6.6 Les structures

Les structures se déclarent comme en C à la diérence que le typedef est automatique.

22
1 s t r u c t Personne
2 {
3 s t r i n g nom ;
4 s t r i n g prenom ;
5 i n t age ;
6 };
7
8 Personne p1 ; //On peut d i r e c t e m e n t f a i r e r é f é r e n c e à Personne san s a j o u t e r
s t r u c t ou u t i l i s e r un t y p e d e f .

2.6.7 Les énumérations

De prime abord, un enum en C++ est un type, nommé ou non, qui regroupe des constantes
entières connues dès la compilation. La valeur de la première constante vaudra 0, la deuxième
vaudra 1, la troisième vaudra 2, etc. À moins que le programme n'impose une valeur explicite-
ment à une constante énumérée, celle-ci vaudra un de plus que celle qui la précède dans l'ordre
de leurs déclarations. Ainsi dans le programme suivant :

1 enum c l a s s j o u r {LUNDI, MARDI, MERCREDI, JEUDI , VENDREDI, SAMEDI, DIMANCHE} ;

LUNDI vaut 0, MARDI vaut 1, Mercredi vaut 2, JEUDI vaut 3, VENDREDI vaut 4, SA-
MEDI vaut 5 et DIMANCHE vaut 6.

Tandis que dans ce prochain exemple, A vaut 0, B vaut 1, C vaut -2, D vaut -1 et E vaut 0.

1 enum c l a s s l e t t r e {A, B, C = = 2, D, E } ;

Il est ensuite possible de déclarer une variable de type jour, et d'utiliser les valeurs de cette
variable de manière très simple.

1 j o u r monJour ; //monJour e s t une v a r i a b l e e n t i è r e prenant s e s v a l e u r s dans [ 0 ; 6 ]


2 ...
3 s w i t c h ( monJour )
4 {
5 c a s e j o u r : : LUNDI : //On t e s t e s i monJour ==0
6 cout<<" c ' e s t l e l u n d i " ;
7 ...
8 }
9
10 i f ( monJour==j o u r : : MARDI) // Permet de t e s t e r l a v a l e u r de monJour par r a p p o r t à 1
11 {
12 ...
13 }

L'énumération permet de faire du code lisible sans avoir besoin de passer par des chaînes
de caractères. L'énumération est à utiliser dans les cas où l'on souhaite un code entier pour les
valeurs possibles d'une variable.

Il n'est pas nécessaire ici non plus de faire une redénition de type avec typedef.

23
2.7 Les ux d'entrée/sortie
La bibliothèque standard fournit les ux istream pour les entrées et les ux ostream pour
les sorties. En d'autres mots, c'est ce qui va nous permettre de lire (cin) et d'écrire (cout) sur
la sortie console lorsque l'on fait des programme en mode console.
Pour accéder à la fois en lecture et en écriture, il faut inclure la bibliothèque <iostream>.
Ces nouvelles fonctionnalités vont remplacer les fonctions printf et scanf utilisé en langage C,
mais sans avoir à s'occuper cette fois-ci du type de la donnée qui est écrit ou lu (rappelez-vous
les %d ou %f ) , ni si on s'intéresse à l'adresse ou la valeur de la variable (plus d'erreur si vous
oubliez l'& devant le nom de votre variable pour le scanf !).
Si vous avez une suite de variables et de chaînes à écrire, chaque "tronçon" sera séparé par
des chevrons ( ou ).

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 i n t main ( )
6 {
7 s t r i n g prenom ;
8 cout << " v e u i l l e z s a i s i r v o t r e prenom\n" ;
9 c i n >> prenom ; // l i t une cha î ne de c a r a c t è r e e t l a p l a c e dans prenom
10 cout << " bonjour " << prenom ;
11 return 0;
12 }

L'utilisation du mot clé endl pour "end line" permet un retour à la ligne.

2.8 Les opérateurs en C++


Les opérateurs en C++ sont similaires à ceux du langage C.
 Opérateurs arithmétiques : +, -, *, /, % (reste de la division entière)
 Incrémentation et décrémentation : ++, ==
 Opérateurs de comparaison : ==, !=, >, <, >=, <=
 Opérateurs logiques : ! , &&, ||
 Opérateur conditionnel : ?

1 a=3; b=5;
2 c = ( a>b ) ? a : b ; // s i a e s t sup é r i e u r e à b a l o r s c=a s i n o n c=b

 Opérateurs bit à bit : &, | , ^, , <<, >>


5|6=7 : 0101 |0110 =0111
6<<1=12 : 110<<1=1100
 Opérateur de conversion de type explicite : (type)nomVariable, aussi appelé cast.

1 i n t main ( )
2 {
3 double x =3.1;
4 int i ;
5
6 cout << "x="<< x << e n d l ;
7 i =( i n t ) x ; // a s s i g n to i the i n t e g e r p a r t o f x

24
8 cout << " i="<< i << e n d l ;
9
10 system ( " pause " ) ;
11 return 0;
12 }

 Opérateurs d'assignation : +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=

Exercice 2 Compléter le tableau suivant :

Opérateur Exemple Expression équivalente


+= i += 3 i= i + 3
==
*=
/=
%=
<<= x <<= 1 ; x = x << 1 ;
>>=
&=
^=
|=

2.9 Les structures de contrôle


L'exécution d'un programme ne suit pas nécessairement une séquence linéaire d'instructions.
Les structures de contrôle permettent d'orienter l'exécution vers tel ou tel bloc d'instructions
(en fonction du contexte). Un bloc est un groupe d'instructions, lorsque l'on rentre dans un
bloc toutes les lignes de ce bloc sont exécutées. Un bloc est en général matérialisé entre une
accolade ouvrante et accolade fermante. Une indentation de même niveau permet de voir les
diérentes lignes d'un bloc.

2.9.1 Structure conditionnelle if

Cela permet d'exécuter une portion de code en fonction d'une condition. La syntaxe est la
suivante :
if (condition) bloc1 else bloc2
La condition peut être écrite sous la forme d'un test dont la valeur est soit vraie soit faux.
Elle peut également être une variable de type booléen.

1 i f ( a==10) // i c i on t e s t e l ' é g a l i t é i l f a u t mettre 2 f o i s l e symbole =


2 { // on r e n t r e dans un b l o c s i a e s t é g a l à 10
3 cout <<"a i s 10 " ;
4 a++;
5 } // on q u i t t e l e b l o c
6 e l s e // on r e n t r e dans l e b l o c c i =d e s s o u s s i a n ' e s t pas é g a l à 1 0 .

25
7 {
8 cout <<"a i s not 10 " ;
9 }

Il n'y a pas de " ;" après la parenthèse fermante du if. On peut ne pas mettre d'accolade si
il y a une seule instruction. Il n'y a pas de parenthèse avec une condition derrière else , ce bloc
est exécuté lorsque la condition derrière le if est faux

2.9.2 Structure itérative

Il s'agit ici d'exécuter plusieurs fois un même bloc. On utilise pour cela les mot-clés while ou
for .
La syntaxe pour les boucles for et while est la même que pour le langage C. De plus vous
pouvez maintenant directement déclarer votre variable de boucle dans le for, comme ceci :
for(int i=0;i<10;i++).
Les deux codes ci-dessous sont équivalents d'un point de vue fonctionnel.
Exemple avec un while. Seul le code utilisant un while est écrit, mais vous pouvez toujours
utilisez le do while.

1 i n t main ( )
2 {
3 i n t val , count =0;
4 cout <<" Entrer l a v a l e u r du compteur \n" ;
5 c i n >> v a l ;
6 w h i l e ( count<=v a l ) // t a n t que count e s t i n f é r i e u r ou é g a l à v a l
7 {
8 cout <<count<<"\n" ;
9 count=count + 1 ;
10 }
11 cout << "\n STOP \n" ;
12 return 0;
13 }

Exemple utilisant une boucle for . Pour rappel le for se décrit en trois partie séparées par des
points virgules : l'initialisation (code exécuté avant de démarrer la boucle), la condition d'arrêt
et l'incrémentation (code exécuté entre chaque boucle).

1 i n t main ( )
2 {
3 i n t val , count ;
4 cout <<" Entrer l a v a l e u r du compteur >" ;
5 c i n >> v a l ;
6 f o r ( count =0; count<=v a l ; count++) // pour count a l l a n t de 0 à v a l
7 {
8 cout <<count<<"\n" ;
9 }
10 cout << "\n STOP \n" ;
11 return 0;
12 }

Dans une boucle vous n'êtes pas tenu d'incrémenter votre variable de 1 à chaque itération,
vous pouvez l'augmenter, la décrémenter du nombre que vous souhaitez. Autre exemple :

26
1 i n t main ( )
2 {
3 i n t i n i t , count =0;
4 cout <<" Entrer l a v a l e u r du compteur >" ;
5 c i n >> i n i t ;
6 w h i l e ( count >0)
7 {
8 cout <<count<<"\n" ;
9 count=count = 1 ;
10 }
11 cout << "\n STOP \n" ;
12 return 0;
13 }

Cet exemple donne la sortie console suivante :

Figure 2.1  Sortie console

2.9.3 L'instruction de saut break

 L'instruction break stoppe une boucle même si la condition de n n'est pas remplie
 Cela peut être utilisé pour nir une boucle innie, ou pour forcer la n avant sa n
naturelle

1 #i n c l u d e <iostream >
2 u s i n g namespace s t d ;
3
4 i n t main ( )
5 {
6 i n t val , count ;
7 cout <<" Entrer l a v a l e u r du compteur >" ;
8 c i n >> v a l ;
9 f o r ( count =0; count<=v a l ; count++)
10 {
11 cout <<count<<"\n" ;
12 i f ( count==10) break ;
13 }
14 cout << "\n STOP \n" ;
15 return 0;
16 }

27
Figure 2.2  Sortie console

Le break est présenté comme une possibilité du langage mais ne doit jamais être privilégié pour
une sortie de boucle Il faut plutôt dénir des conditions d'arrêt de sortie de boucle clairs et
l'énoncé avec un while.

2.9.4 Structure conditionnelle switch case

Le switch case permet de faire un aiguillage dans le code à partir de la valeur entière d'une
variable. Cette construction peut être, par exemple, utilisée pour la création d'un menu. Un
exemple de menu est donné par le code suivant :

1 i n t main ( )
2 {
3 int choix ;
4 do
5 {
6 cout << "Que s o u h a i t e z vous f a i r e ? " << e n d l ;
7 cout << "0 = Q u i t t e r l a p a r t i e " << e n d l ;
8 cout << "1 = Demander une c a r t e à un a u t r e j o u e u r " << e n d l ;
9 cout << "2 = Poser une c a r t e s u r l a t a b l e " << e n d l ;
10 cout << "3 = Prendre une c a r t e dans l a p i o c h e " << e n d l ;
11 c i n >> c h o i x ;
12 switch ( choix )
13 {
14 case 0:
15 cout << "au r e v o i r " << e n d l ;
16 break ;
17 case 1:
18 askcard ( ) ;
19 break ;
20 case 2:
21 setCard ( ) ;
22 break ;
23 case 3:
24 takeCard ( ) ;
25 break ;
26 default :
27 cout << " Choix i n v a l i d e : s a i s i r une n o u v e l l e v a l e u r " << e n d l ;
28 }

28
29 } w h i l e ( c h o i x != 0) ;
30 system ( " pause " ) ;
31 return 0;
32 }
33

L'utilisation du break est ici nécessaire si on souhaite une exécution d'un seul des cas. Si on
ne met pas break, on rentrera au premier cas vrai et on exécutera les cas qui suivent.

2.10 Le tableau
Les tableaux sont utiles pour stocker un ensemble de données de même type. La création
d'un tableau permet de gérer plusieurs données dans une seule variable plutôt que d'avoir un
grand nombre de variables. La manipulation se fait ensuite dans des boucles.

2.10.1 Les tableaux statiques

Un tableau statique signie que lors de la déclaration du tableau, on alloue en mémoire


l'ensemble des cases du tableau. La déclaration d'un tableau statique signie donc que l'on
connaît à l'avance le nombre de cases du tableau. On pourra ensuite aller lire et écrire dans les
cases de ce tableau. Nous utiliserons comme tableau statique en C++ celui du langage C.
Un tableau est déclaré par type identicateur[TAILLE] ;
Où :
 type est le type de tous les éléments du tableau
 identicateur est le nom du tableau
 TAILLE représente le nombre d'éléments du tableau
L'indice pour un tableau en C/C++ commence toujours à 0.

1 //dé c l a r a t i o n d ' un t a b l e a u de 7 double dont l ' i n d i c e v a r i e de 0 à 6


2 double n o t e s [ 7 ] ;
3
4 f o r ( i n t i =0; i <7; i ++)
5 {
6 n o t e s [ i ] = 0 ; // i n i t i a l i s a t i o n de chaque element du t a b l e a u à 0
7 }
8
9 notes [5]=17; // a f f e c t a t i o n du 6eme é l é ment à 17
10
11 cout <<n o t e s [3]<< e n d l ; // a f f i c h a g e du 4eme é l é ment du t a b l e a u

Il est également possible de déclarer des tableaux de type "élaboré" comme des types struc-
turés ou encore des objets (la notion d'objet sera dénie dans le chapitre 4).

2.10.2 Les tableaux dynamiques

En langage C, le tableau dynamique se gère avec des pointeurs. En C++ nous allons utiliser
la classe vector.
Un tableau se déclare de la façon suivante : vector<type > identicateur(TAILLE) ; La
manipulation des données du tableau se fait ensuite de la même manière qu'un tableau statique
en accédant à l'élément d'indice i en utilisant les crochets [i]. Le premier indice de ce tableau
est 0.

29
En quoi ce tableau est-il dynamique ? et bien si vous n'avez pas alloué assez de place pour
tout stocker, vous pouvez ajouter un nouvel élément à la n au moyen d'une méthode push_back
(NouvelleValeur). Vous pouvez aussi supprimer des cases de ce tableau à l'aide de la méthode
pop_back() ou erase() (chose qui n'est pas possible avec un tableau statique). En d'autres termes
si vous ne savez pas combien de places il faut réserver dans votre tableau, vous pouvez en créer
un sans taille puis ajouter progressivement les éléments.
Exemple :

1 v e c t o r <i n t > t a b l e a u ; //dé c l a r a t i o n d ' un t a b l e a u v i d e


2 t a b l e a u . push_back ( 3 ) ; // on a j o u t e 3 à l a f i n du t a b l e a u
3 t a b l e a u . push_back ( 5 ) ; // on a j o u t e 5
4 f o r ( i n t i =0; i <t a b l e a u . s i z e ( ) ; i ++) // t a b l e a u . s i z e ( ) permet de r é cup é r e r l e
nombre d ' é l é ments du t a b l e a u
5 {
6 cout <<t a b l e a u [ i ]<<" " ; //Acc è s à l a v a l e u r avec l e s c r o c h e t s .
7 }

Le passage d'un vecteur en paramètre de fonction se passe par défaut par valeur, contraire-
ment au tableau statique présenté dans le paragraphe précédent.

30
Chapitre 3
Les fonctions
1
Une fonction est un ensemble d'instructions qui peut être exécutée quand elle est appelée
de n'importe quel endroit du programme.

Une fonction doit être relativement courte et doit répondre à un seul objectif. L'identicateur
de la fonction fait référence à cet objectif. Vous prendrez un soin particulier à isoler dans des
fonctions dédiées les fonctions ayant des intéractions avec l'utilisateur. Les identicateurs de
celles-ci pourront commencer par saisir ou acher.
Une fonction se présente sous le format suivant :
type nom (type1 parametre1, type2 parametre2, ...)
{
instructions
}
On peut l'appeler ensuite quand on en a besoin. Par exemple :

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 // f o n c t i o n qui prend deux e n t i e r s en param è t r e e t r e n v o i e l e u r somme
6 i n t add ( i n t x , i n t y )
7 {
8 int z ;
9 z=x+y ;
10 r e t u r n z ; // z d o i t ê t r e de type e n t i e r comme l a f o n c t i o n
11 }
12
13 i n t main ( )
14 {
15 i n t somme ;
16 // app el de l a f o n c t i o n add e t a f f e c t a t i o n de l a v a l e u r de r e t o u r à somme
17 somme = add ( 3 , 1 ) ; //somme d o i t ê t r e du type e n t i e r comme l a f o n c t i o n
18 cout << "3 p l u s 1 e g a l e " << somme <<"\n" ;
19 return 0;
20 }

1. Par abus de langage, on utilise souvent le terme fonction en C/C++ pour désigner à la fois la procédure
et la fonction

31
La déclaration d'une fonction consiste à dire qu'une fonction existe, elle s'écrit en utilisant
le prototype (ou signature) de la fonction suivi directement d'un point virgulee.
exemple : int add(intx, int y);
La dénition d'une fonction consiste à écrire le code correspondant à une fonction à l'inté-
rieur des accolades.
La fonction main est de type int, cela signie qu'elle retourne un entier. le return 0 signie
que le programme s'est exécuté sans erreur.
Lorsque l'on crée une fonction, il faut penser boîte noire :
 pour appeler une fonction, il est juste nécessaire de savoir ce que la fonction fait et non
pas comment elle le fait
 quand on écrit une fonction, on a besoin de savoir comment
Pour rappel, si le type de retour est void la fonction de renvoie rien : il n'y aura donc pas
de return. Attention une fonction ne peut pas avoir comme paramètre de retour un tableau
statique.
Quand on appelle une fonction, on ne précise plus le type des paramètres d'entrée. Il faut
simplement passer une variable (ou une valeur) de type correspondant. Ne pas mettre add (int
5, int 3) mais add (5, 3)

Exercice 3 Ajouter une autre fonction au programme vu précédemment qui soustrait deux
nombres. Puis l'appeler depuis le programme principal en soustrayant 2 du résultat de l'addition.

3.1 Passage des paramètres par valeur et par référence


Les paramètres sont les entrées du sous-programme.
Lorsque l'on dénit une fonction nous devons nous poser cette question : voulons-nous que
la fonction puisse modier les variables passées en paramètres ? En fonction du besoin on écrira
diéremment la déclaration des paramètres.

3.1.1 Passage des paramètres par valeur

Par défaut, tous les paramètres d'une fonction sont passés par valeur. Cela signie que lors
de l'appel de la fonction, la valeur est passée à la fonction qui elle crée une nouvelle variable
pour stocker cette valeur. En d'autres termes, la fonction travaille sur une copie de la donnée
qui a été transmise et si des modications sont faites sur sa valeur, cette modication n'a lieu
qu'au niveau de la fonction et non pas à l'extérieur.

1 void permut ( i n t x , i n t y ) //La f o n c t i o n attend 2 v a l e u r s d ' e n t i e r qu ' e l l e


s t o c k e dans deux n o u v e l l e s v a r i a b l e s qu ' e l l e a p p e l l e x e t y
2 {
3 i n t temp ;
4 temp = x ; //temp prend l a v a l e u r de x
5 x=y ; //x prend l a v a l e u r de y
6 y=temp ; //y prend l a v a l e u r s t o c k é e dans temp .
7 //La permutation a é t é r é a l i s é e s u r l e s v a r i a b l e s x e t y mais c e l a n ' a
aucune i n c i d e n c e s u r l e s v a r i a b l e s a e t b du programme p r i n c i p a l
8 }
9
10 i n t main ( )
11 {
12 int a = 2 , b = 5;
13 permut ( a , b ) ; // on p a s s e l e s v a l e u r s c on t en u es dans l e s v a r i a b l e s a e t b
14 cout << a << " "<<b ; // i c i a vaut t o u j o u r s 2 e t b vaut 5

32
15 return 0;
16 }

D'autre part, faire une copie des paramètres peut s'avérer couteux en terme d'espace mé-
moire et peut entraîner un ralentissement l'exécution.

3.1.2 Passage des paramètres par adresse

L'idée est ici de passer, en paramètre, l'adresse d'une variable plutôt que sa valeur. Cela
veut dire que si on communique à la fonction l'adresse d'une variable (dénie dans le main()
par exemple), la fonction pourra modier le contenu de cette variable.
De cette manière, la variable paramètre contiendra l'adresse d'une variable créée ailleurs
dans le code, et pourra donc aller modier le contenu de cette case.

1 void permut ( i n t * x , i n t * y ) //La f o n c t i o n attend 2 a d r e s s e s d ' e n t i e r s


2 {
3 i n t temp ;
4 temp = * x ; //temp prend l a v a l e u r s e trouvant à l ' a d r e s s e x
5 * x=*y ; // dans l a c a s e à l ' a d r e s s e x on met l e contenu s e trouvant à l '
adresse y
6 * y=temp ; // l e contenu de l ' a d r e s s e y d e v i e n t l a v a l e u r s t o c k é dans temp .
7 }
8
9 i n t main ( )
10 {
11 int a = 2 , b = 5;
12 permut(&a ,&b ) ; // on p a s s e l e s a d r e s s e s
13 cout << a << " "<<b ; // i c i a vaut 5 e t b vaut 2
14 return 0;
15 }

Pour rappel un tableau statique est toujours passé par adresse. Car le nom de la variable
d'un tableau statique correspond à l'adresse du premier élément.

3.1.3 Passage des paramètres par référence

La notion de référence a été introduite dans le chapitre précédent (cf. 2.6.4). L'intérêt est
le même que pour le passage par adresse (la fonction pourra modier la valeur des paramètres)
sauf que l'écriture est simpliée

1 void permut ( i n t &x , i n t &y ) // Lors de l ' appel de l a f o n c t i o n 2 r é f é r e n c e s s o n t


c r é é e s s u r l e s v a r i a b l e s pass é e s en param è t r e
2 {
3 i n t temp ;
4 temp = x ;
5 x=y ;
6 y=temp ;
7 }
8
9 i n t main ( )
10 {
11 int a = 2 , b = 5;
12 permut ( a , b ) ; // on p a s s e l e s v a r i a b l e s
13 cout << a << " "<<b ; // i c i a vaut 5 e t b vaut 2

33
14 return 0;
15 }

2
Le simple fait de mettre des esperluettes devant les variables dans la signature de la fonction
sut à faire le passage par référence. Cela simplie beaucoup l'écriture proposée au paragraphe
3.1.2.

Si vous ne souhaitez pas que votre fonction modie la variable passée lors de l'appel, deux
solutions sont envisageables :

 passage par valeur : une nouvelle variable est créée le temps de l'exécution du sous-
programme dans laquelle est stockée la valeur
 passage par référence (ou adresse) en ajoutant le mot clé const : permet de ne pas recréer
une nouvelle variable qui peut parfois être conséquente en mémoire tout en protégeant
la variable passée en paramètre de toute modication éventuelle. Ex : void acher (const
vector<Etudiant>& promo).

3.2 Paramètre par défaut


En C++, il est possible de préciser des valeurs par défaut aux paramètres d'une fonction.
Cela veut dire, que si, lors de l'appel de la fonction, un paramètre n'est pas précisé, celui-ci sera
considéré égal à sa valeur par défaut. Les valeurs par défaut sont précisées lors de la déclaration,
mais pas lors de la dénition. Si vous utilisez des chiers d'entête (".h"), les valeurs par défaut
ne seront indiqués que dans le ".h".

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 void a d d i t i o n ( i n t a=0, i n t b=0) ;
6
7 void a d d i t i o n ( i n t a , i n t b )
8 {
9 cout<< a << "+" <<b<<"="<<a+b<<e n d l ;
10 }
11
12 i n t main ( )
13 {
14 a d d i t i o n ( 1 , 2) ; // appel avec l e s deux param è t r e s
15 a d d i t i o n ( 3 ) ; // appel avec l e premier param è t r e
16 a d d i t i o n ( ) ; // appel sans param è t r e
17 system ( " pause " ) ;
18 return 0;
19 }

La sortie de la console est donnée ci-dessous. C'est-à-dire qu'en l'absence de paramètre il


va prendre la valeur par défaut. Par contre, on ne peut pas omettre les premiers paramètres et
mettre seulement les derniers. Si par exemple on ne voulait mettre que le deuxième paramètre
on ne pourrait pas. C'est toujours les paramètres à partir de la n qui peuvent être supprimés.

2. La signature est simplement la première ligne de la dénition d'une fonction : ce qui nous permet de savoir
les entrées et les sorties de la fonction

34
3.3 Surcharge de fonction
En C il n'était pas possible d'avoir deux fonctions avec le même identicateur. En C++,
cette règle est assouplie en il n'est pas possible d'avoir deux fonctions avec la même signature.
La signature correspond au nom de la fonction + les types de diérents paramètres. En d'autres
mots il ne faut simplement pas qu'il y ait une ambiguïté sur la fonction à utiliser.

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 void a d d i t i o n ( i n t a , i n t b )
6 {
7 cout<<" a d d i t i o n de 2 e n t i e r s "<<e n d l ;
8 cout<< a <<"+"<<b<<"="<<a+b<<e n d l ;
9 }
10
11 void a d d i t i o n ( double a , double b )
12 {
13 cout<<" a d d i t i o n de 2 d o u b l e s "<<e n d l ;
14 cout<< a <<"+" <<b<<"="<<a+b<<e n d l ;
15 }
16
17 i n t main ( )
18 {
19 a d d i t i o n ( 1 , 2) ;
20 addition (3.4 ,8.521) ;
21 system ( " pause " ) ;
22 return 0;
23 }

Dans l'exemple précédent, nous avons déni deux fonctions ayant le même nom : addition,
mais avec deux signatures (ou prototypes) diérentes. Lors de l'appel de la fonction addition(a
,b); , le programme se basera sur le type des paramètres a et b, pour savoir laquelle des deux
fonctions sera exécutée, comme cela est illustré par la sortie console ci-dessous.

3.4 La récursivité
Une fonction est dite récursive si elle s'appelle elle-même. C'est-à-dire si dans le corps de la
dénition de la fonction il y a un appel à la fonction elle-même. Alors que la fonction est en
cours d'exécution, un appel à cette fonction recommencera lors de l'exécution de la ligne où est
de nouveau fait référence cette fonction.

35
Lors de l'écriture d'une telle fonction, il faut s'assurer qu'il y a bien un critère de n, pour
ne pas avoir de boucle innie.

36
Exemple :

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 void d i s p l a y ( i n t n )
6 {
7 i f ( n >= 1) // permet de s ' a s s u r e r que l ' on a pas une b o u c l e i n f i n i e
8 {
9 n==;
10 cout << n << " " ;
11 d i s p l a y ( n ) ; // appel à d i s p l a y
12 }
13 }
14
15 i n t main ( )
16 {
17 display (11) ;
18 cout << " done ! \ n" ;
19 system ( " pause " ) ;
20 return 0;
21 }

Dans ce programme la fonction display s'appelle elle-même après avoir aché son paramètre
moins 1. L'appel à la fonction display est gardé par la condition de la ligne 7. Quand cette
condition est fausse on arrête l'appel, et les diérentes ns de fonctions sont exécutées les unes
après les autres.

Exercice 4 Créer un programme avec une fonction récursive qui calcule n !

3.5 Application à la récursivité : le tri fusion


L'an passé plusieurs algorithmes de tri ont été abordés : le tri par sélection, le tri par
insertion et le tri à bulle. Avec l'aide de la récursivité on peut aborder un autre algorithme de
tri (plus performant) le tri fusion.
Le code suivant va nous permettre d'illustrer les diérents appels à la fonction à travers un
exemple :

1 i n t main ( )
2 {
3 v e c t o r <i n t > vec ={3 ,5 , = 8 ,41 ,2 , = 18 ,13}; // l i s t e que l ' on s o u h a i t e t r i é e
4 vec= t r i F u s i o n ( vec ) ; // app el à l a f o n c t i o n t r i f u s i o n
5 a f f i c h e r ( vec ) ;
6 }

La fonction triFusion est de la forme suivante :

1 v e c t o r <i n t > t r i F u s i o n ( v e c t o r <i n t > l i s t )


2 {
3 i f ( l i s t . s i z e ( ) >1}
4 {
5 // s é p a r e r en 2 p a r t i e s
6 // t r i e r c e s deux p a r t i e s en f a i s a n t app el à l a f o n c t i o n t r i F u s i o n

37
7 // f u s i o n n e r
8 }
9 }

Le principe consiste lors d'une itération à séparer le tableau en deux moitiés, de passer
chaque moitié en paramètre de triFusion qui va les retourner trié puis de fusionner ces deux
parties triées. On ne décompose pas le tableau si le nombre d'éléments présents dans celui-ci
est égal à 1.
La logique du programme va être la suivante : comme le tableau passé en paramètre contient
plus d'un élément, le programme va créer deux tableaux, puis demander de trier chacun d'eux.
Chacun d'eux sera de nouveau séparé en deux, jusqu'à ce que chaque tableau ne contienne plus
qu'un seul élément. Ensuite commence la phase de fusion où l'on va fusionner deux tableaux
triés. Cela se fait simplement en parcourant les deux tableaux en parallèle et en choisissant
l'élément le plus petit entre les deux tableaux. La gure 3.1 détaille les diérentes étapes.

Figure 3.1  Etapes du tri fusion

3.6 La programmation modulaire et l'architecture logicielle


Quand la taille d'un logiciel augmente, des modules sont nécessaires. L'architecture logiciel
ou la conception consiste à dénir des modules et les règles de dépendances entre eux. Si aucun
module n'est créé, un programme ressemble à un plat de spaghetti.

ou

38
La création modulaire peut passer par la création dans un projet de plusieurs chiers : des
chiers d'en-tête (.h) et des chiers sources (.cpp). Chaque chier est ensuite compilé de manière
séparé puis relié par l'éditeur de lien. Un programme ne contiendra qu'un seul main tout chier
confondu.
Ce qu'il faut faire pour que cela fonctionne :
 Dénir des modules en fonction du sens des fonctions que l'on souhaite mettre dedans.
Il faut également avoir en tête que chaque module aura ainsi le moyen d'être réutilisé
dans une autre application
 Pour chaque module créé un chier source ".cpp" et un chier d'entête ".h". Dans le
".h" on met les déclarations des fonctions et dans le chier ".cpp" on met les dénitions.
Dans le ".cpp" il faut ajouter le #include correspondant au chier ".h".
 A chaque fois que l'on souhaite utiliser une fonction dénie dans ce module il sura
d'inclure le chier .h
Ci-dessous un exemple :
dans le chier operation.h

1 i n t a d d i t i o n ( i n t a , i n t b ) ; //dé c l a r a t i o n des f o n c t i o n s du module

dans le chier operation.cpp

1 #i n c l u d e " o p e r a t i o n . h" // i n c l u r e l e f i c h i e r où l a dé c l a r a t i o n e s t f a i t e
2 i n t a d d i t i o n ( i n t a , i n t b ) //dé f i n i t i o n de l a f o n c t i o n
3 {
4 r e t u r n a+b ;
5 }

dans le chier main.cpp

1 #i n c l u d e " o p e r a t i o n . h" // i n c l u r e l e f i c h i e r où l a dé c l a r a t i o n e s t f a i t e
2 i n t main ( )
3 {
4 i n t somme = a d d i t i o n ( 2 , 8 ) ;
5 return 0;
6 }

Exercice 5 Reprendre l'exemple de l'exercice pour en faire un projet sur plusieurs chiers/-
modules : un pour gérer la console, un pour gérer les calculs, un pour la gestion des erreurs et
un pour le main.

39
40
Chapitre 4
Les classes
4.1 La programmation orientée objet
Dans une démarche de recherche de nouvelles solutions pour faciliter la programmation et
la rendre plus accessible, de nouveaux concepts émergent. La POO (Programmation Orientée
Objet) est une nouvelle approche pour résoudre des problèmes. En plus des types et des struc-
tures de données classiques, un nouveau concept appelé objet est utilisé. Dans ce cours nous
découvrirons les aspects principaux de la POO avec des exemples en C++.

Figure 4.1  Comment ne pas faire d'erreurs ? Cinq relecteurs ?

4.1.1 Pourquoi un nouveau concept ?

Une voiture est assemblée à partir de diérents éléments tel que un châssis, des portes, un
moteur, des roues, un système de freins, etc. Les composants sont réutilisables, par exemple
une roue peut être utilisée pour diérentes voitures (selon certains critères bien entendu).
Qu'en est-il des logiciels ? Peut-on assembler un logiciel en piquant une fonction par ici, une
autre par là et s'attendre à ce que le programme fonctionne ?
Les langages procédurals traditionnels (tel que le C) sourent de quelques faiblesses notables
pour créer des composants logiciels réutilisables.

41
Figure 4.2  Structure d'un programme en langage C

Les programmes sont construits à partir de fonctions. Les fonctions sont souvent non réutili-
sables. Il est très dicile de copier une fonction d'un programme et de la réutiliser dans un autre
programme, car la fonction est dépendante de références aux headers, aux variables globales et
aux autres fonctions. En d'autre mots, les fonctions ne sont pas bien encapsulées pour en faire
une unité réutilisable.
La POO ore un plus haut niveau d'abstraction quant à la modélisation et à la résolution
des problèmes de la vie réelle. Par exemple, les programmes C utilisent des entités tels que
if-else, des boucles for, des tableaux, des fonctions, des pointeurs, tous ces éléments sont bas
niveaux et rendent dicile la compréhension de la modélisation et de la solution mise en place,
surtout face à des problèmes complexes tels qu'un système de CRM (Customer Relationship
Management) ou bien encore un jeu vidéo de football.

4.1.2 Présentation générale de la POO

Dans un langage objet les programmateurs dénissent non seulement les données relatives
à une entité mais aussi les opérations qui peuvent être appliquées sur ou par cette entité. C'est-
à-dire qu'un objet inclus à la fois des données et des fonctions (qu'on appellera méthodes).
Les programmateurs peuvent créer des relations entre un objet et un autre.

Figure 4.3  Programmation Orienté Objet

Comme cela est illustré à la gure 4.3 : chaque objet est constitué de données ainsi que de
comportements particuliers qui leur permettent d'interagir avec les autres.

42
Exemple 1 : Concrètement, si on souhaite programmer un jeu de football les diérents objets
de notre programme seront les diérentes entités que l'on peut trouver : le joueur, le ballon, le
terrain, l'arbitre, le public, la météo, le tableau des scores (cf. gure 4.4).

Figure 4.4  Les objets dans un jeu de football

Exemple 2 : Si maintenant nous souhaitons implémenter la gestion d'une école d'ingénieur,


Diérents objets prennent part à la vie d'une école d'ingénieurs. Nous pouvons dénir les objets
de type "Etudiant". Un "Etudiant" a un nom, une date de naissance, un ensemble de notes
relatives à ses résultats. Si l'on considère maintenant les opérations que l'on peut faire sur un
étudiant : récupérer son age, récupérer sa moyenne, passer en année supérieure...

Exercice 6 Nous voulons maintenant dénir un objet de type "cannette de soda". Quelles
données et fonctions peut-on identier pour cet objet ?

4.2 Les classes


4.2.1 Introduction

Une classe est un modèle d'objet, c'est ce qui va nous permettre de dénir un nouveau
type de variable. Un objet est une instanciation d'une classe, c'est la variable qui est du type
décrit par la classe.
Prenons l'exemple d'un étudiant, on va d'abord créer un type Etudiant au moyen d'une
classe, puis lorsque l'on voudra manipuler un étudiant, on créera une variable de type Etudiant.
Intéressons tout d'abord à la création d'une classe. Les classes sont déclarées en utilisant le
mot-clé class, suivant le format suivant :

1 c l a s s NomdeLaClasse
2 {
3 / * i n f o r m a t i o n s s u r ce que c o n t i e n t l a c l a s s e * /
4 };

Si nous reprenons l'exemple de l'Etudiant, un premier programme simple peut être le suivant.
Pour comprendre cette nouvelle notion, ce programme est entièrement codé dans un chier
source ".cpp", cela changera par la suite.

1 #i n c l u d e <s t r i n g >
2 #i n c l u d e <iostream >
3

43
4 u s i n g namespace s t d ;
5
6 c l a s s Etudiant //dé c l a r a t i o n de l a c l a s s e Etudiant = c r é a t i o n d ' un nouveau type
de donn é e
7 {
8 s t r i n g nom ;
9 s t r i n g prenom ;
10 i n t anneeNaissance ;
11 };
12
13 i n t main ( ) // programme p r i n c i p a l
14 {
15 Etudiant Benjamin ; //Cré a t i o n d ' un o b j e t i n s t a n c i é de l a c l a s s e = dé
c l a r a t i o n d ' une n o u v e l l e v a r i a b l e bas é s u r ce type
16 return 0;
17 }

La déclaration de la classe Etudiant permet de créer un nouveau type de donnée, comme


on peut le faire avec une structure. Ensuite dans le code, on peut créer des variables selon ce
nouveau type.
Nous avons déjà utilisé des classes : string et vector. Et oui c'est deux nouveaux types que
l'on a utilisé sont en fait des classes. Elles sont dénies dans la bibliothèque standard.

4.2.2 Des données et des méthodes

Ce qui est présenté dans le paragraphe précédent était très simplié. La puissance de la
programmation objet c'est aussi que dans le nouveau type que l'on crée il n'y a pas seulement
des données mais aussi des services, qui s'expriment à l'aide de méthodes. Reprenons un exemple
un peu plus détaillé :

1 #i n c l u d e <s t r i n g >
2 #i n c l u d e <iostream >
3
4 u s i n g namespace s t d ;
5
6 c l a s s Etudiant
7 {
8 private :
9 s t r i n g nom ; // Les a t t r i b u t s
10 s t r i n g prenom ;
11 i n t anneeNaissance ;
12 public :
13 Etudiant ( i n t a ) { anneeNaissance = a ; } ; // Les mé thodes
14 i n t getAge ( ) { r e t u r n 2019 = anneeNaissance ; } ;
15 };
16
17 i n t main ( )
18 {
19 Etudiant Benjamin ( 2 0 0 0 ) ;
20 cout << " age " << Benjamin . getAge ( ) ; // appel d ' une mé thode de l ' o b j e t
21 return 0;
22 }

La classe Etudiant a ici trois attributs (appelés aussi données membres) et 2 méthodes.
Vous remarquerez que pour utiliser des éléments de l'objet, on utilise le nom de l'objet (le nom

44
de la variable) suivi du point, puis du nom de la méthode souhaitée (un peu comme un type
structuré).
Quand la variable "Benjamin" est créée, une variable de type Etudiant est créé, c'est-à-
dire de quoi stoker les informations relatives à un étudiant : son nom, son prénom et son
année de naissance. Il faut interpréter la variable "Benjamin" comme une variable qui contient
à l'interieur trois données. Les attributs d'une classe sont accessibles directement depuis les
méthodes de cette classe (nul besoin de les faire passer en paramètres ou de les redéclarer
au sein de la méthode). C'est-à-dire que lors de la dénition de getAge vous pouvez utiliser
l'attribut anneeNaissance dans le code de la méthode.

4.2.3 Structuration du code

Dans l'exemple précédent tout a été implémenté dans un seul chier. Cela fonctionne mais
ce n'est pas la méthode conseillée pour aborder la programmation objet.
Par convention vous utiliserez 2 chiers par classe : un chier d'entête (.h, ou parfois .hpp) et
un chier source (.cpp). Ces chiers porteront le nom de la classe. Pour créer la classe Etudiant
vous aurez Etudiant.cpp et Etudiant.h.
Le chier d'entête (le header) ne contient que la déclaration de la classe, c'est-à-dire les
attributs et les signatures des méthodes, le corps des méthodes est dénie dans le chier source.
Dans le chier Etudiant.h, on met :

1 #i n c l u d e <s t r i n g >
2
3 u s i n g namespace s t d ;
4
5 c l a s s Etudiant //dé c l a r a t i o n de l a c l a s s e Etudiant
6 {
7 private :
8 s t r i n g nom ;
9 s t r i n g prenom ;
10 i n t anneeNaissance ;
11 v e c t o r <f l o a t > n o t e s ;
12 public :
13 Etudiant ( i n t a ) ;
14 i n t getAge ( ) ;
15 f l o a t getMoyenne ( ) ;
16 };

Dans le chier Etudiant.cpp, on trouve :

1 #i n c l u d e " Etudiant . h" // on i n c l u e l e f i c h i e r de l a dé c l a r a t i o n de l a c l a s s e


2
3 Etudiant : : Etudiant ( i n t a )
4 {
5 anneeNaissance = a ;
6 }
7
8 i n t Etudiant : : getAge ( ) //dé f i n i t i o n de l a mé thode de l a c l a s s e
9 {
10 r e t u r n 2022 = anneNaissance ; // acc è s à une donn é e membre de l a c l a s s e
11 }
12
13 f l o a t Etudiant : : getMoyenne ( )
14 {
15 f l o a t somme=0;

45
16 f o r ( i n t i =0; i <n o t e s . s i z e ( ) ; i ++)
17 {
18 somme +=n o t e s [ i ] ;
19 }
20 r e t u r n somme/ n o t e s . s i z e ( ) ;
21 }

Vous remarquerez que pour pouvoir identier dans le chier source qu'il s'agit bien de
méthodes de la classe Etudiant, il est nécessaire d'ajouter devant le nom de la méthode Etu-
diant : :. Si ce préxe n'est pas inséré devant l'identicateur de la méthode, le compilateur va
interpréter cette méthode comme une fonction indépendante (et non pas comme une méthode
d'une classe). Pour résumé, lorsque vous dénissez le corps d'une méthode dans le chier .cpp
il faut toujours précéder la signature de cette méthode par "NomClasse : :".
Si nous nous intéressons maintenant à l'utilisation de cette classe dans le chier main.cpp :

1 #i n c l u d e <iostream >
2 #i n c l u d e " Etudiant . h" // I n c l u r e l e f i c h i e r de dé c l a r a t i o n de l a c l a s s e
3
4 u s i n g namespace s t d ;
5
6 i n t main ( )
7 {
8 Etudiant Benjamin ( 2 0 0 0 ) ; //Cré a t i o n d ' un o b j e t i n s t a n c i é de l a c l a s s e
9 cout << " age " << Benjamin . getAge ( ) ; // u t i l i s a t i o n d ' une mé thode de l a
classe
10 return 0;
11 }

Le fait de dénir une classe dans un module (NomClasse.h + NomClasse.cpp) permet de


rendre le code modulaire, ainsi il sera possible d'utiliser la classe Etudiant dans diérents
programmes simplement en ajoutant un #include ``Etudiant.h'' . D'autre part, cela permet de
faire abstraction de la manière dont les méthodes sont implémentées, dans la fonction main, on
a juste besoin de connaître le nom des méthodes et leurs paramètres pour pouvoir les utiliser.

On remarquera que la programmation objet peut se voir à deux niveaux :


 Le développeur qui crée la bibliothèque de classe qui a pour objectif de rendre l'utilisation
de la classe la plus simple possible en cachant les dicultés éventuelles
 Le développeur qui utilise la librairie de classe qui a simplement à créer ses objets et à
l'aide d'un point accède à pleins de services disponibles sur ces objets.

4.3 Les spécicateurs d'accès


Les spécicateurs d'accès permettent de restreindre et de contrôler l'accès aux membres
d'une classe (attributs ou méthodes), ce qui permet "l'encapsulation". En POO L'encapsulation
est un mécanisme consistant à rassembler les données et les méthodes au sein d'une structure
en cachant l'implémentation de l'objet, c'est-à-dire en empêchant l'accès aux données par un
autre moyen que les services proposés par le concepteur de la classe. L'encapsulation permet
donc de garantir l'intégrité des données contenues dans l'objet.
Un spécicateur d'accès peut être :

46
 private : accessible seulement pour les autres membres de la classe ou pour leur amis
 protected : mêmes règles que private + accessible pour les membres des classes dérivées
 public : accessible partout où l'objet est visible.
Par défaut, si le spécicateur d'accès n'est pas précisé, tous les membres sont considérés
d'accès private.
Par exemple dans le chier Soda.h, la classe Soda est déclarée avec 3 attributs privés et 3
méthodes publiques :

1 #i n c l u d e <s t r i n g >
2 u s i n g namespace s t d ;
3
4 c l a s s Soda
5 {
6 private :
7 s t r i n g name ;
8 s t r i n g type ;
9 i n t volume ;
10 public :
11 Soda ( s t r i n g n , s t r i n g T, i n t v ) ;
12 i n t getVolume ( ) ;
13 ~Soda ( ) ;
14 };

Les trois méthodes sont dénies dans le chier Soda.cpp :

1 #i n c l u d e "Soda . h"
2 Soda : : Soda ( s t r i n g n , s t r i n g t , i n t v )
3 {
4 name = n ;
5 type = t ;
6 volume = v ;
7 }
8 Soda : : ~ Soda ( )
9 {}
10 i n t Soda : : getVolume ( )
11 {
12 r e t u r n volume ;
13 }

Lorsque l'on se trouve dans une méthode membre d'une classe (le nom de la méthode est
précédée par NomClasse : :), on peut accéder à toutes les données et les méthodes membres de
la classe. Ainsi dans la méthode Soda::Soda(string n, string t , int v) ci-dessus, il est fait référence
à name, il s'agit de l'attribut name de l'objet, qui a été déclaré en accès privé.
Dans le chier main.cpp :

1 #i n c l u d e "Soda . h"
2
3 i n t main ( )
4 {
5 Soda coke ( "Coca" , " can " , 33) ;
6 cout << " volume i s " << coke . getVolume ( ) << "\n" ;
7 return 0;
8 }

47
Dans la fonction main, un objet de type Soda est déclaré. C'est ici que l'on fera la diérence
entre ce qui est déclaré en private et ce qui est déclaré en public. L'objet s'appelle ici coke, pour
accéder à un de ses membres, il sut de mettre un point puis le nom d'un de ces membres
publics. Nous n'aurons pas accès ici à ce qui est privé, en d'autres termes il n'est pas autorisé
d'écrire coke.name, car name est un membre privé de la classe.

Exercice 7 Ajouter une fonction takeASip qui prend en paramètre le volume bu. Dans la
fonction main appeler cette fonction et après appeler une nouvelle fois getVolume.

4.4 Les constructeurs


Le constructeur est une méthode particulière qui doit avoir le même nom que la classe et
qui ne doit pas avoir de type de retour (ni int, void, oat, ...). Un constructeur est une méthode
qui est appelée implicitement lors de la création de l'objet, elle ne peut être appelée en dehors
de ce moment-là.
Lorsque vous déclarez un objet (par exemple Soda coke;, un constructeur sans paramètre va
être appelé, on appelle ce constructeur le constructeur par défaut. Par contre, si on indique
Soda coke("Coca","can", 33); comme cela est fait dans l'exemple ci-dessus, un constructeur avec 3
paramètres dont les deux premiers paramètres sont des chaînes de caractères et le troisième un
entier, va être recherché et exécuté.
De manière traditionnelle, le constructeur est utilisé pour initialiser les attributs mais il s'agit
simplement d'une méthode appelée au moment où l'objet est créé et vous pouvez y mettre le
code que vous souhaitez.
Le constructeur, comme toute autre fonction, peut être surchargé, vous pouvez donc dénir
plusieurs constructeurs avec un nombre de paramètres diérents pour répondre à diérents
besoins. Lors de la déclaration de l'objet il sera cherché le constructeur adapté aux paramètres
précisés.
Il est aussi tout-à-fait possible d'avoir des valeurs par défaut aux paramètres si ceux-ci sont
omis.
Le destructeur est appelé quant à lui lors de la destruction de l'objet. Il est utile pour
désallouer la mémoire si celle-ci a été allouée de manière dynamique.
Dans l'exemple ci-après, vous pouvez voir ce que donne ces mécanismes d'appel au construc-
teur/destructeur.
Dans le chier Note.h :

1 c l a s s Note
2 {
3 private :
4 std : : s t r i n g course ;
5 f l o a t value ;
6 public :
7 Note ( ) ;
8 Note ( s t d : : s t r i n g course , f l o a t v a l u e =0) ; // dans ce cas = l à on pourra p a s s e r
un ou deux param è t r e s .
9 ~Note ( ) ;
10 void d i s p l a y ( ) ;
11 f l o a t getValue ( ) ;
12 }

Dans l'exemple vous remarquerez que la possibilité d'avoir des valeurs par défaut pour le
constructeur a été utilisée. Dans le cas présenté ici il est possible d'appeler ce constructeur

48
avec un ou deux paramètres. Notez que les valeurs par défaut se mettent dans le chier header
et pas dans le chier source.
Dans l'hypothèse où tous les paramètres auraient des valeurs par défaut, ce constructeur
serait aussi un constructeur par défaut.
Le code correspondant dans le chier Note.cpp est le suivant :

1 #i n c l u d e <iostream >
2 #i n c l u d e " Note . h"
3
4 u s i n g namespace s t d ;
5
6 Note : : Note ( )
7 {
8 cout << " appel du c o n s t r u c t e u r par d e f a u t " << e n d l ;
9 c o u r s e = "" ;
10 value = 0;
11 }
12
13 Note : : Note ( s t d : : s t r i n g course , f l o a t v a l u e )
14 {
15 cout << " appel du c o n s t r u c t e u r avec deux parametres " << e n d l ;
16 t h i s =>c o u r s e = c o u r s e ;
17 t h i s =>v a l u e = v a l u e ;
18 }
19
20 Note : : ~ Note ( )
21 {
22 cout << " appel du d e s t r u c t e u r " << e n d l ;
23 }
24
25 void Note : : d i s p l a y ( )
26 {
27 cout << c o u r s e << " : " << v a l u e << e n d l ;
28 }
29
30 f l o a t Note : : getValue ( )
31 {
32 return value ;
33 }

Si une variable locale ou un paramètre porte le même nom qu'un attribut de la classe et que
l'on souhaite désigner l'attribut sans qu'il y ait confusion, il est possible d'utiliser la syntaxe
suivante : "this->NomAttribut". Le mot clé "this", désigne un pointeur sur l'objet sur lequel
la méthode a été appelée, c'est pour cette raison que nous utilisons "->" pour accéder aux
données membres. Quand il n'y a pas de confusion, il n'est pas nécessaire de le mettre mais il
est dans tous les cas sous-entendu.
Le code écrit dans le chier main.cpp est le suivant :

1 #i n c l u d e <iostream >
2 #i n c l u d e " Note . h"
3
4 u s i n g namespace s t d ;
5
6 i n t main ( )
7 {
8 Note note1 ( "Math" ) ; // c r é a t i o n de l a v a r i a b l e note1
9 note1 . d i s p l a y ( ) ;

49
10 i f ( note1 . getValue ( ) == 0)
11 {
12 Note note2 ; // c r é a t i o n de l a v a r i a b l e note2
13 note2 . d i s p l a y ( ) ;
14 } // s u p p r e s s i o n de l a v a r i a b l e note 2
15 system ( " pause " ) ;
16 return 0;
17 }

Sur la sortie console nous pouvons voir, grace aux traces insérées dans le code source,
les appels aux constructeurs et destructeurs. L'appel au destructeur pour la variable note1
n'apparaît pas car le destructeur est appelé à la fermeture du programme.

Le destructeur sera très peu utilisé cette année. Il est obligatoire de l'utiliser si un attribut
de la classe est un pointeur, que le constructeur fait une allocation dynamique ; dans ce cas la
desallocation mémoire est faite dans le destructeur.

4.4.1 L'utilisation d'une liste d'initialisation

Il y a une autre méthode pour initialiser les attributs avec un constructeur c'est d'utiliser
la liste d'initialisation. Dans le chier source plutôt que d'écrire les initialisations dans le corps
du constructeur, il est possible sur la ligne de l'entête de fonction d'ajouter deux points, puis
de mettre les attributs avec entre parenthèses les valeurs que ces données vont prendre. Pour le
constructeur à deux paramètres nous aurions pu simplement mettre :

1 Note : : Note ( s t r i n g c , f l o a t v ) : c o u r s e ( c ) , v a l u e ( v ) {}

4.4.2 L'appel du constructeur lors d'une allocation dynamique

Comme en C il est tout à fait possible de dénir des variables de type pointeur, c'est-à-
dire une variable contenant l'adresse d'une donnée. L'allocation dynamique en C++ se fait
en utilisant le mot-clé new. Pour rappel un pointeur est une variable qui contient une adresse.
L'allocation dynamique consiste à réserver une zone mémoire de la taille nécessaire à la création
d'une donnée, et renvoie l'adresse de la zone créée.

1 #i n c l u d e <iostream >
2 #i n c l u d e " Note . h"
3
4 u s i n g namespace s t d ;
5
6 i n t main ( )
7 {
8 Note * pnote ;
9 pnote = new Note ( "Thermo" , 1 3 ) ; // a l l o c a t i o n dynamique e t appel d ' un
constructeur
10 pnote =>d i s p l a y ( ) ; // u t i l i s a t i o n de l a f l è che pour acc é der aux mé thodes
publiques

50
11 d e l e t e pnote ; // s u p p r e s s i o n de l a zone mé moire
12 return 0;
13 }

4.5 Le constructeur de copie


Le constructeur de copie permet de créer un objet comme une copie d'un objet existant.
Deux méthodes peuvent être utilisées pour appeler ce constructeur : avec le symbole = ou
en appelant de manière explicite le constructeur de copie.

1 c l a s s Vo it ur e
2 {
3 public :
4 Vo it ur e ( ) ;
5 Vo it ur e ( s t d : : s t r i n g marque ) ;
6 ~Vo it ur e ( ) ;
7 Vo it ur e ( c o n s t Vo it ure& v o i t u r e ) ;
8 i n t getSpeed ( ) ;
9 i n t brake ( i n t p e r c e n t a g e ) ;
10 private :
11 i n t nbPortes ;
12 s t d : : s t r i n g marque ;
13 };
14
15 Vo it ur e : : Voi tu re ( c o n s t V oi tu re& v o i t u r e )
16 {
17 cout << " appel du c o n s t r u c t e u r de c o p i e \n" ;
18 t h i s =>marque=v o i t u r e . marque ;
19 t h i s =>nbPortes = v o i t u r e . nbPortes ;
20 }
21
22 i n t main ( )
23 {
24 Vo it ur e maPetiteVoiture ( " Renault " ) ;
25 Vo it ur e maGrandVoiture=maPetiteVoiture ; // app el du c o n s t r u c t e u r de c o p i e
26 Vo it ur e maMoyenneVoiture ( maPetiteVoiture ) ; // appel du c o n s t r u c t e u r de c o p i e
27 system ( " pause " ) ;
28 }

En l'absence d'un constructeur de copie explicite, le compilateur en créera un par défaut.


Son comportement aura pour objectif de copier chaque attribut. Dans l'hypothèse où c'est le
comportement attendu il n'est pas nécessaire de l'écrire.

4.6 Encapsulation et accesseurs/mutateurs


Par convention, les attributs sont toujours déclarés en privés, les méthodes sont générale-
ment déclarées en publiques, c'est ce que l'on appelle le principe d'encapsulation. C'est-à-dire
qu'un objet est manipulé de l'extérieur par certaines méthodes choisies et implémentées par
son concepteur. Ces méthodes sont implémentées de manière à protéger les données internes
auxquelles on ne peut pas accéder de manière directe. C'est ce qui va concourir à sécuriser ses
données mais aussi à simplier le code en créant un niveau d'abstraction.

51
Si on considère par exemple les chaînes de caractères et des tableaux (les classes vector et
string) que vous avez manipulés jusque là, ces types sont en réalité des classes. Elles ont bien
sûr des attributs mais on ne les connaît pas. Et on remarque que les tableaux et les chaînes de
caractères se manipulent simplement sans avoir besoin de savoir concrètement la manière dont
les données privées sont stockées et comment elles sont manipulées dans les méthodes. Ce qui
nous simplie quand même pas mal l'existence !

Il est cependant souvent nécessaire d'accéder en lecture (accesseur) et en écriture (mutateur)


aux attributs de la classe. On nomme ces méthodes les accesseurs/mutateurs (ou en anglais
getter/setter). Ces fonctions ont pour objectif soit d'écrire, soit de retourner la valeur.

Par convention, on précède une méthode permettant de lire une donnée par get, et une
méthode permettant d'écrire une donnée par set.

Pour une méthode get où on veut juste lire la valeur, on peut rajouter le mot clé const à la
n de la ligne pour indiquer que cette méthode ne procède pas à des modications sur l'objet
qui l'appelle. Le mot clé const peut être utilisé de cette manière sur toutes les méthodes qui
n'apporte pas de modication sur l'objet.

1 c l a s s Vo it ur e
2 {
3 public :
4 Vo it ur e ( ) ;
5 Vo it ur e ( s t d : : s t r i n g marque ) ;
6 ~Vo it ur e ( ) ;
7 Vo it ur e ( c o n s t Vo it ure& v o i t u r e ) ;
8 s t d : : s t r i n g getMarque ( ) c o n s t ; // a c c e s s e u r permettant de r e t o u r n e r l a
marque de l a v o i t u r e
9 void setNbPortes ( i n t nbP) ; // mutateur permettant de p o s i t i o n n e r l e nombre de
portes
10 private :
11 i n t nbPortes ;
12 s t d : : s t r i n g marque ;

Pourquoi ne pourrait-on pas mettre tous les attributs en accès public plutôt que d'ajouter des
accesseurs / mutateurs ? Tout d'abord vous ne mettrez des méthodes pour accéder aux données
privés que lorsque cela est nécessaire. Ensuite votre méthode vous permettra de contrôler ce
qui est fait et de ne pas écrire n'importe quoi sur votre attribut. Par exemple l'utilisation d'un
setNbPortes(int nb) va vous permettre d'ajouter un test vériant que nb est compris entre 1 et
6 avant de procéder à une aectation.

52
En toute logique, un objet ne doit pas avoir des mutateurs et des accesseurs sur chaque
attribut. Cela ne respecte pas la philosophie de la programmation objet. Car l'idée est d'avoir
des objets avec des services simples qui traitent en interne les éléments. Il n'y a donc pas
d'intérêt à sortir les informations à l'extérieur de la classe. Par exemple si vous souhaitez
acher les informations relatives à une voiture, il faut créer une méthode qui ache plutôt que
récupérer les données et acher ensuite par le code les données.

4.7 L'amitié
En C++ il possible de déclarer une classe comme amie d'une autre classe. Ce qui permet
aux méthodes de la première d'accéder directement aux membres privés (ou protégés) de la
deuxième. Pour cela il su d'utiliser le mot-clé friend .

1 c l a s s Note
2 {
3 s t r i n g matiere ;
4 string valeur ;
5 f r i e n d c l a s s Etudiant ; //Dé c l a r a t i o n d ' une c l a s s e amie
6 };
7
8 c l a s s Etudiant
9 {
10 s t r i n g nom , prenom ;
11 v e c t o r <Note> n o t e s ;
12 f l o a t moyenne ;
13 public :
14 f l o a t calculerMoyenne ( ) :
15 f r i e n d b o o l VerifAdmis ( Etudiant e ) ; //Dé c l a r a t i o n d ' une f o n c t i o n amie
16 }
17
18 f l o a t Etudiant : : calculerMoyenne ( )
19 {
20 f l o a t i =0, somme=0;
21 w h i l e ( i <n o t e s . s i z e ( ) )
22 somme +=n o t e s [ i ] . v a l e u r ; // on acc è de i c i à l a p a r t i e p r i v é e de notes , c '
e s t a u t o r i s é gr â ce à l ' a m i t i é .
23 moyenne = somme/ n o t e s . s i z e ( ) ;
24 r e t u r n moyenne ;
25 }
26
27 b o o l VerifAdmis ( Etudiant e )
28 {
29 i f ( e . moyenne >10) // acc è s à l ' a t t r i b u t p r i v é moyenne
30 cout <<" admis " ;
31 }

Il est également possible de déclarer une fonction amie d'une classe, c'est-à-dire qu'à l'inté-
rieur de cette fonction, il sera possible d'accéder directement à l'ensemble des membres privés
(ou protégés). Ce qui est le cas, par exemple, pour la fonction verifAdmis qui accède directement
à l'attribut "moyenne" de la classe "Etudiant".
Deux règles à savoir :
 l'amitié ne va que dans un sens : si une classe A est amie avec une classe B, cela n'induit
pas que B est amie avec A.
 Si A est amie de B et B est amie de C cela n'induit pas que A est amie de C

53
Exercice 8 Ajouter au projet Ecole, une nouvelle classe Association. Une association a un nom
et un ensemble de membres. Vous développerez une fonction externe à toute classe prenant en
paramètre une association et un étudiant, et qui déterminera si l'étudiant appartient à cette
association ou non. Implémenter le code nécessaire pour tester cette fonction.

4.8 La surcharge d'opérateurs


Les opérateurs sont faciles à utiliser et peuvent avoir un sens dans l'utilisation de variable
de type classe. Comme les fonctions peuvent être surchargées, les opérateurs peuvent également
l'être. Cela revient à dénir une nouvelle possibilité d'utilisation lorsque l'opérateur est manipulé
par exemple avec des objet de type classe.
Nous souhaitons implémenter le code suivant :

1 i n t main ( )
2 {
3 Vo it ur e maPetiteVoiture ( " Renault " ) ;
4 Vo it ur e maGrandVoiture=maPetiteVoiture ;
5 i f ( maPetiteVoiture == maGrandeVoiture )
6 {
7 cout << maPetiteVoiture ;
8 }
9 system ( " pause " ) ;
10 return 0;
11 }

Nous remarquons qu'à la ligne 5 il y a un test qui compare deux voitures. Le programme
ne sait pas ce que veut dire l'égalité entre deux voitures. De même à la ligne 7 on voudrait
pouvoir acher une voiture de la même manière que l'on ache une variable de type simple.
Les questions auxquelles on cherche à répondre ici sont :
 Comment faire en sorte que le programme sache si deux voitures sont égales ?
 Comment faire en sorte qu'il comprenne le code cout << maPetiteVoiture ?
La surcharge d'opérateur permet de dénir des nouveaux comportements pour les opérateurs
de base. Ceci permet au programme de savoir quoi faire lorsqu'on lui demande, par exemple,
de comparer deux objets de type "Voiture" avec l'opérateur "==". Cette solution (la surcharge
d'opérateurs) est mise en place dans les codes suivants :

1 #i n c l u d e <iostream >
2
3 c l a s s Vo it ur e
4 {
5 public :
6 Vo it ur e ( ) ;
7 Vo it ur e ( s t d : : s t r i n g marque , i n t nbPortes =0) ;
8 ~Vo it ur e ( ) ;
9 Vo it ur e ( c o n s t Vo it ure& v o i t u r e ) ;
10 i n t getSpeed ( ) ;
11 i n t brake ( i n t p e r c e n t a g e ) ;
12 b o o l i s E q u a l ( c o n s t Vo it ure &v o i t u r e ) c o n s t ;
13 void a f f i c h e r ( c o n s t s t d : ostream &f l u x ) c o n s t ;
14 private :
15 i n t nbPortes ;
16 s t d : : s t r i n g marque ;
17 };
18

54
19 b o o l o p e r a t o r == ( c o n s t Vo itu re &voitA , c o n s t V oit ur e &voitB ) ;
20 s t d : : ostream &o p e r a t o r <<(s t d : : ostream &f l u x , Voi tu re c o n s t& v o i t u r e ) ;

Par exemple à la ligne 19, la surcharge de l'opérateur == est déclarée. Nous remarquons que
la déclaration de la surcharge se trouve à l'extérieur de la classe. C'est-à-dire après l'accolade
fermante de la déclaration de la classe. En eet vous n'allez pas appeler votre opérateur en
faisant "maPetiteVoiture.==". Il ne s'agit donc pas d'une méthode membre de la classe. Sa
déclaration prend tout de même du sens à être réalisée dans les chiers de la classe, car il s'agit
d'une facilité qui est oerte lorsque l'on souhaite utiliser la classe. La surchage d'opérateur
prend deux paramètres car il s'agit ici d'un opérateur prenant un élément à gauche et un autre
à droite du signe "==". Le premier paramètre fait référence à l'élément de gauche.
Les paramètres peuvent être passés en constante pour indiquer que les objets utilisés par le
test d'égalité ne seront pas modiés. Il n'est fait que préciser ce que l'on considère logique pour
ce type d'opération.
Notre surcharge d'opérateur renvoie un booléen.
Ces méthodes sont ensuite dénies dans le chier source :

1 void V oi tur e : : a f f i c h e r ( s t d : : ostream & f l u x ) c o n s t


2 {
3 f l u x << " v o i t u r e de marque : " << marque << " avec " << nbPortes << " p o r t e s . \ n
";
4 }
5
6 b o o l V oi tu re : : i s E q u a l ( c o n s t V oi tu re &v o i t u r e ) c o n s t
7 {
8 i f ( ( t h i s =>marque == v o i t u r e . marque ) && ( t h i s =>nbPortes == v o i t u r e . nbPortes )
)
9 return true ;
10 else
11 return f a l s e ;
12 }
13
14 b o o l o p e r a t o r == ( c o n s t Vo itu re& voitA , c o n s t V oit ur e &voitB )
15 {
16 r e t u r n voitA . i s E q u a l ( voitB ) ;
17 }
18
19 ostream &o p e r a t o r <<(ostream &f l u x , Vo itu re c o n s t & v o i t u r e )
20 {
21 voiture . afficher ( flux ) ;
22 return flux ;
23 }

Comme il ne s'agit pas de méthodes de la classe (vous noterez également l'absence du


Voiture : : devant la dénition des opérateurs), il n'est pas possible d'accéder directement aux
attributs qui sont déclarés privés. Dans l'exemple ci-dessus, il a été choisi de passer par une
méthode publique et d'utiliser son retour pour contourner ce problème. Il est également possible
de déclarer ses opérateurs comme amis de la classe. Il sera possible ainsi d'accéder directement
aux attributs et les méthodes aher et isEqual ne seront plus nécessaires.
Dans cet exemple nous voyons que la classe possède d'une certaine façon plusieurs services
qui permettent de faire la même chose. Deux façons diérentes de s'acher : une en utilisant le
cout, l'autre au moyen d'une méthode. Cela n'est pas un problème. L'idée générale lorsque l'on
implémente une classe c'est de fournir à un autre développeur (peut être même plusieurs déve-

55
lopeurs) les services qui lui conviendront, ce qui nécessite de penser à plusieurs cas d'utilisation
diérents.

56
Chapitre 5
Héritage
5.1 Notion
L'héritage permet de créer des classes qui sont dérivées d'autres classes. De cette manière
elles peuvent bénécier automatiquement des éléments dénis dans la classe parent.
Pour indiquer qu'une classe hérite d'une autre classe, on utilise la syntaxe suivante :
class NomClasseDérivée : specicateur_acces NomClasseBase

Figure 5.1  Héritage de classes

La classe polygone peut avoir plusieurs classes lles : par exemple Triangle ou bien en-
core Carré. Pour indiquer que la classe Carré hérite de Polygone, la déclaration de la classe
commencera par class Carre : public Polygon.
La notion d'héritage est possible dans la mesure où l'on peut dire que la classe enfant est un
objet de la classe parent. Par exemple ici un Triangle est un polygone. Par contre un polygone
n'est pas nécessairement un triangle. Dans la notion d'héritage on suppose que la classe dérivée
contient plus de données que la classe parente. On pourrait imaginer compléter l'exemple, en
disant qu'un polygone hérite d'une classe Figure, ou bien encore qu'une classe TriangleRectangle
hérite de Triangle.
L'idée de l'héritage est qu'une classe dérivée va pouvoir hériter, de manière automatique, de
tout ce qui a déjà été déni dans la classe parente. On aura donc une hiérarchie de classes et
on pourra regrouper toutes les données et les méthodes communes au niveau de la classe mère.
Dans notre exemple de polygone on pourrait considérer comme attribut le nombre de côtés
de la gure ainsi que sa couleur. Chaque classe héritée de Polygone aura comme attributs (sans
avoir à les créer de nouveau) couleur et nombre de côtés.

57
La notion d'héritage est diérente de l'amitié. Par exemple Etudiant peut être ami avec Note,
mais on ne peut pas dire que Etudiant est une note!. Par contre on pourrait considérer que
Etudiant dérive d'une classe Personne, car un étudiant est une personne.
Pour représenter sous forme de schéma la hiérarchie d'une famille de classes, il est possible
d'utiliser UML (Unied Modelling Language) une méthodologie permettant de concevoir des
logiciels. Un des diagrammes concerne le modèle des classes qui seront implémentées dans
l'application. La notion d'héritage est représentée par un triangle comme illustré à la gure 5.2.

Figure 5.2  Diagramme de classes UML

5.2 Accessibilité des éléments aux classes enfants


Quand un attribut est déclaré avec le spécicateur d'accès "private", cet attribut n'est pas
connu pour les classes enfants. Pour qu'elles puissent hériter de cet attribut, il faut le déclarer
en "protected" au niveau de la classe mère.
L'implémentation des classes de la famille Polygone donne le code suivant :
Dans les chiers .h : Polygone.h, Carre.h et Triangle.h.

1 c l a s s Polygone
2 {
3 private :
4 ecouleur couleur ;
5 protected :
6 i n t nbSommets ;
7 public :
8 Polygone ( ) ;
9 void i n i t i a l i s a t i o n ( e c o u l e u r c o u l e u r , i n t nbSommets ) ;
10 ~Polygone ( ) ;
11 };

58
1 c l a s s Carre : p u b l i c Polygone
2 {
3 private :
4 i n t longueurCote ;
5 public :
6 Carre ( i n t c o t e ) ;
7 ~Carre ( ) ;
8 float calculerAire () ;
9 };

1 c l a s s T r i a n g l e : p u b l i c Polygone
2 {
3 private :
4 int longueurCotes [ 3 ] ;
5 public :
6 Triangle ( int a , int b , int c ) ;
7 ~Triangle () ;
8 float calculerAire () ;
9 };

Dans la classe Polygone, l'attribut "couleur" a été déclaré en private : cela signie que cette
donnée ne sera pas accessible au niveau de la classe dérivée. En d'autres termes, probablement
que tout ce qui aura trait à la couleur sera gérée au niveau de la classe parente, sans avoir
besoin d'y faire référence au niveau des classes enfants. Cet attribut est de type ecouleur qui est
un énuméré prenant ses valeurs dans l'ensemble {BLEU, ROUGE, VERT, JAUNE, BLANC,
NOIR, VIOLET}.
Par contre l'attribut nbSommets a été déclarée en protected : on va pouvoir y accéder depuis
les classes dérivées.
Et dans les chiers .cpp correspondants :

1 void Polygone : : i n i t i a l i s a t i o n ( e c o u l e u r c o u l e u r , i n t nbSommets ) {


2 t h i s =>c o u l e u r = c o u l e u r ;
3 t h i s =>nbSommets = nbSommets ;
4 }

Nous remarquons de nouveau ici l'utilisation du pointeur this pour lever l'ambiguïté entre
l'attribut et le paramètre.

1 Carre : : Carre ( i n t c o t e ) {
2 // cout << appel du c o n s t r u c t e u r c a r r e
3 longueurCote = c o t e ;
4 nbSommets=4; //nbSommets a é t é hé r i t é c a r e l l e e s t sous p r o t e c t e d
5 }
6
7 Carre : : ~ Carre ( ) {}
8
9 f l o a t Carre : : c a l c u l e r A i r e ( )
10 {
11 r e t u r n longueurCote * longueurCote ;
12 }

59
1 Triangle : : Triangle ( int a , int b , int c )
2 {
3 // cout << appel du c o n s t r u c t e u r t r i a n g l e
4 longueurCotes [ 0 ] = a ;
5 longueurCotes [ 1 ] = b ;
6 longueurCotes [ 2 ] = c ;
7 nbSommets=3; //nbSommets a é t é hé r i t é c a r e l l e e s t sous p r o t e c t e d
8 }

Nous remarquons que dans le code ci-dessus, il est possible d'utiliser l'attribut nbSommets
dans les méthodes des classes Carre et Triangle car cette donnée fait partie de ces classes, elles
en ont hérité. On est bien dans cette logique : quand une classe hérite d'une classe, elle récupère
les éléments public et protected de la classe parente.
On peut utiliser les classes de la manière suivante :

1 void main ( )
2 {
3 Carre monCarre ( 4 ) ;
4 T r i a n g l e monTriangle ( 2 , 5 , 4 ) ;
5
6 // on acc è de à l a f o n c t i o n i n i t i a l i s a t i o n de l a c l a s s e p a r e n t e
7 monCarre . i n i t i a l i s a t i o n (BLEU, 4) ; // on hé r i t e de ce qui e s t p u b l i c
8 monTriangle . i n i t i a l i s a t i o n (ROUGE, 3) ;
9
10 // on acc è de aux mé thodes de l a chaque c l a s s e
11 cout << " a i r e c a r r e " << monCarre . c a l c u l e r A i r e ( )<<e n d l ;
12 cout << " a i r e t r i a n g l e " << monTriangle . c a l c u l e r A i r e ( ) << e n d l ;
13 system ( " pause " ) ;
14 }

Avec les éditeurs de code, vous vous rendrez compte rapidement que dès que vous mettez
le point, l'éditeur vous propose l'ensemble des données ou méthodes que vous pouvez utiliser.
Cela vous sera d'une grande aide pour gérer l'accessibilité des diérentes méthodes.

Exercice 9 Ajouter une classe dérivée Rectangle et une méthode dans cette classe qui calcule
l'aire d'un rectangle. Finalement créer un objet Rectangle en l'initialisant avec une hauteur de
5 et une largeur de 3. Acher son aire.

5.3 Spécicateurs d'accès de l'héritage


Dans l'exemple ci-dessus, les attributs hérités par Triangle et Carré ont les mêmes permis-
sions d'accès que dans la classe parente Polygone. Cela parce que la relation d'héritage a été
déclarée en utilisant le mot-clé public pour chaque classe dérivée.

1 c l a s s Carre : p u b l i c Polygone

Si le motprotected est utilisé, tous les membres publics de la classe de base vont être consi-
dérées comme protected dans la classe dérivée.
Si le niveau d'accès le plus élevé est utilisé ( private ), tous les membres de la classe sont
hérités comme privés.

60
5.4 Et les constructeurs dans l'héritage ?
Par défaut lorsque que l'on déclare une instance d'une classe dérivée, il y a un appel en
cascade des constructeurs en commençant par celui de plus haut niveau.
Vous pouvez faire un essai en ajoutant une trace (qui va vous écrire sur la console le construc-
teur appelé) dans les constructeurs de vos classes et en réexécutant le code du paragraphe
précédent.

Dans l'exemple précédent, nous avons dénit une méthode qui permet d'initialiser les don-
nées d'un objet de type Polygone (la couleur ainsi que le nombre de sommets). Il aurait été
plus judicieux de dénir un constructeur qui permet de renseigner directement les attributs
au moment de la création de l'objet. En eet, il est possible de surcharger le constructeur par
défaut de sorte à ce qu'il prenne des paramètres en entrée qui serviront à initialiser les attributs
de la classe parente. Cela peut se faire, soit par une liste d'initialisation, soit par des aectations
classiques.
Dans l'exemple ci-dessous, nous pouvons voir que le constructeur d'une classe lle peut faire
appel à l'un de constructeurs de la classe parente, ceci ne peut cependant se faire que par une
liste d'initialisation.
Dans notre programme, en plus de la modication liée au constructeur, nous avons ajouté
la possibilité d'acher les caractéristiques des classes Carré et Triangle.
Les modications de la classe Polygone sont résumées ci-dessous :
 la fonction initialisation a été supprimée car on souhaite dorénavant passer par les
constructeurs
 ajout d'une fonction getCouleur qui permet de retourner la couleur en toutes lettres
 modication du constructeur pour qu'il permette l'initialisation des données membres

1 Polygone : : Polygone ( e c o u l e u r c , i n t nbS ) : c o u l e u r ( c ) , nbSommets ( nbS )


2 {}
3
4 s t r i n g Polygone : : getCouleur ( )
5 {
6 switch ( couleur )
7 {
8 c a s e NOIR :
9 return " noir " ;
10 c a s e JAUNE:
11 r e t u r n " jaune " ;
12 c a s e BLEU:
13 return " bleu " ;
14 c a s e ROUGE:
15 r e t u r n " rouge " ;
16 }
17 }

Les modications qui ont été réalisées dans la classe Triangle :


61
 appel du constructeur de la classe parente dans la liste d'initialisation
 création d'une méthode acher qui permet d'acher toutes les informations du triangle

1 Triangle : : Triangle ( int x , i n t y , i n t z , e c o u l e u r c , i n t nbS ) : Polygone ( c , nbS )


2 {
3 longueurCotes [ 0 ] = x;
4 longueurCotes [ 1 ] = y;
5 longueurCotes [ 2 ] = z;
6 }
7
8 void T r i a n g l e : : a f f i c h e r ( )
9 {
10 cout << " t r i a n g l e " << getCouleur ( ) << " avec " << nbSommets << " sommets ,
de mesures " ;
11 cout <<" ( " <<l o n g u e u r C o t e s [ 0 ] <<" , " <<l o n g u e u r C o t e s [ 1 ] <<" , " <<
l o n g u e u r C o t e s [2]<<" ) , " ;
12 cout << " a i r e : " << c a l c u l e r A i r e ( )<<e n d l ;
13 }

Les modications dans la classe Carre sont du même ordre que pour Triangle.

1 Carre : : Carre ( i n t cote , e c o u l e u r c , i n t nbS ) : Polygone ( c , nbS ) , longueurCote ( c o t e )


2 {}
3
4 void Carre : : a f f i c h e r ( )
5 {
6 cout << " c a r r e " << getCouleur ( ) << " avec " << nbSommets << " sommets , de
mesure " << longueurCote ;
7 cout << " a i r e : " << c a l c u l e r A i r e ( ) << e n d l ;
8 }

et dans le programme principal :

1 void main ( )
2 {
3 Carre monCarre ( 5 ,NOIR, 4 ) ;
4 T r i a n g l e monTriangle ( 2 , 5 , 4 ,JAUNE, 3 ) ;
5
6 // on acc è de aux mé thodes de chaque c l a s s e
7 monCarre . a f f i c h e r ( ) ;
8 monTriangle . a f f i c h e r ( ) ;
9 system ( " pause " ) ;
10 }

Les constructeurs des classes Triangle et Carre appelent dorénavant de manière explicite le
constructeur de la classe Polygone. Si nous revenons à la dénition du constructeur Triangle.
Celui-ci a 5 paramètres : les trois longueurs des côtés qui sont ensuite utilisés dans le corps de
la méthode pour initialiser l'attribut "longueurCotes", et 2 autres paramètres qui sont ensuite
repassés en paramètres au constructeur de Polygone. En utilisant cette façon de faire, il n'est
pas toujours nécessaire de mettre les attributs en protected.
Une autre façon de faire aurait été de mettre tous les attributs de la classe parente en
protected et de les initialiser dans le corps des constructeurs des enfants de manière directe.
Même si les deux méthodes sont possibles, privilégiez plutôt le fait que chaque objet garde ses
spécicités et s'occupe au plus près de ses traitements. Donc la solution de faire un constructeur

62
par objet et d'appeler le constructeur parent depuis la classe lle est plus élégante. D'autre part,
l'autre cas aurait généré deux codes identiques : l'initialisation des attributs à la fois dans la
classe parente et dans les classes lles. Les doublons de code ne sont jamais conseillés.

On remarquera dans cet exemple que le programme principal devient minimaliste. On sou-
haite simplement acher les propriétés dans polygone que l'on a créé, tout se fait dans les
classes. On voit bien ici le concept d'encapsulation : de l'extérieur on a très peu besoin de
savoir comment les classes sont construites, leur utilisation doit en être très simple.

5.5 Aectation
Il est possible d'aecter un objet "enfant" à un objet "parent". L'inverse n'est pas vrai.

1 Polygon p (NOIR, 3) ;
2 Triangle t (4 ,3 ,5) ;
3 p=t ; // a u t o r i s é
4 t=p ; // non a u t o r i s é

p=t signie que la partie relative à Polygon inclue dans t est copié dans p. Dans p est
dorénavant perdu tout ce qui est dans un triangle mais pas dans un polynome.

5.6 Le masquage des méthodes


Le terme masquage est employé lorsque l'on redénit une méthode de même signature dans
une classe enfant. Celle-ci sera appelée à la place de la méthode de la classe parente. Si l'on veut
de manière spécique appeler la méthode de la classe parente et non celle de la classe courante,
on devra préxer l'appel de la méthode par le nom de la classe suivi de  : :.

Par exemple on suppose qu'il y a une méthode acher dans la classe polygone, qui permet
d'acher les informations dont elle dispose :

1 void Polygon : : a f f i c h e r ( ) {
2 cout << nbSommets << " sommets " << endl ;
3 }

Et si maintenant, on souhaite appeler cette méthode dans une méthode de la classe lle :

1 void Carre : : a f f i c h e r ( )
2 {
3 cout << " c a r r e " << getCouleur ( ) <<
4 Polygon : : a f f i c h e r ( ) ;
5 cout << " c o t é de mesure " << longueurCote ;
6 cout << " a i r e : " << c a l c u l e r A i r e ( ) << e n d l ;
7 }

Si on n'avait pas préciser Polygon : : devant la méthode acher à la ligne 4, le compilateur


aurait considéré qu'il s'agissait d'un appel à la méthode de la classe courante. On serait dans
ce cas-là dans un appel récursif...

63
5.7 Le polymorphisme
Le polymorphisme permet de manipuler diérentes formes.
Le polymorphisme peut être mise en ÷uvre lorsqu'il y a une hiérarchie de classe reliées par
une relation d'héritage. En C++ cela signie que l'on peut appeler une fonction membre sur un
objet "générique" et que cette fonction va s'exécuter diéremment en fonction de l'objet réel
sur lequel est appliquée.
Dans l'exemple ci-dessous, nous avons plusieurs objets hérités d'une classe polygone. On
souhaite pouvoir appeler une fonction qui ache les propriétés d'un polygone qui sache prendre
en compte le type réel du polygone.
Il faut pour cela dénir une fonction Acher dans la classe Polygone, mais pour informer
qu'elle sera obligatoirement dénie dans les classes lles, on met le mot clé virtual devant le
nom de la fonction dans le chier header. Si une fonction est dénie comme virtuelle dans une
classe, elle sera forcément virtuelle dans les autres classes dérivées.

1 c l a s s Polygone
2 {
3 private :
4 ecouleur couleur ;
5 protected :
6 i n t nbSommets ;
7 public :
8 Polygone ( e c o u l e u r , i n t ) ;
9 ~Polygone ( ) ;
10 s t d : : s t r i n g getCouleur ( ) ;
11 v i r t u a l void a f f i c h e r ( ) {}
12 };

Comme on avait déjà dénie cette fonction dans Carre et dans Triangle, il n'y a rien à
changer dans ces classes.
La question que l'on se pose est maintenant : à quoi cela va bien pouvoir nous servir ? l'intérêt
est bien de pouvoir manipuler des objets d'une famille (qui ont tous une même fonction) sans
savoir précisément de quel type d'enfant il s'agit.
Par exemple, imaginez que vous ayez une application (type géogebra) et que vous avez créé
beaucoup de formes. Au moment de la création vous savez quelle forme vous créez mais vous
avez ensuite envie de les manipuler dans un seul conteneur pour pouvoir faire des fonctions
génériques comme acher un rapport des objets dessinés par exemple. Et vous ne souhaitez
pas dans votre code avoir plusieurs conteneurs par type d'objet créé pour plusieurs raisons :
 le nombre de formes dans ce type d'application peut être important
 certains pourraient même être vides si par exemple vous n'avez créé que des carrés
 chaque fois qu'il y a une variable diérente il faudrait remettre un code assez similaire
pour demander à chaque élément de cette liste d'acher les propriétés
Pour ces raisons on va créer un conteneur pour gérer des adresses d'objet de type Polygone.
Attention si ici si vous ne passez pas par des pointeurs ou des références, vous allez écraser les
propriétés spéciques de triangle ou de carré en recopiant dans une nouvelle variable que ce qui
réfère à Polygone et non plus à l'objet précédemment déni.

1 i n t main ( )
2 {
3 Carre monCarre ( 5 ,NOIR, 4 ) ;
4 T r i a n g l e monTriangle ( 2 , 5 , 4 ,JAUNE, 3 ) ;
5 v e c t o r <Polygone *> mesPolygones ;
6

64
7 mesPolygones . push_back(&monCarre ) ;
8 mesPolygones . push_back(&monTriangle ) ;
9
10 f o r ( i n t i = 0 ; i < mesPolygones . s i z e ( ) ; i ++)
11 {
12 mesPolygones [ i ]=> a f f i c h e r ( ) ;
13 }
14 system ( " pause " ) ;
15 return 0;
16 }

La sortie obtenue tient bien en compte l'appel de la méthode de la bonne classe :

65
66
Chapitre 6
Compléments
6.1 Aperçu de la STL
6.1.1 La classe string

Pour l'utiliser, il faut rajouter #include <string>.


Il s'agit d'une classe standard qui permet de représenter une chaîne de caractères. Cette
classe encapsule des données pour pouvoir eectuer toutes les opérations de base sur les chaînes.
Ces opérations sont assez complexes notamment la gestion de la mémoire : l'encapsulation
permet de masquer à l'utilisateur de la classe toutes les dicultés techniques.
Quelques méthodes utiles :
size renvoie la longueur du string
clear réinitialise la chaîne de caractère
empty teste si la chaîne est vide
operator+ concaténation de deux chaînes
L'exemple suivant montre un exemple d'utilisation de cette classe.

1 s t r i n g c h a i n e = " coucou " ;


2 int t a i l l e = chaine . s i z e () ;
3 cout <<" l a c h a i n e "<<chaine << " c o n t i e n t "<< t a i l l e << " caracteres ";

6.1.2 Les conteneurs

vector<T> un vecteur de taille variable


list<T> une liste doublement chaînée
queue<T> une le
stack<T> une pile
deque une le a double sens
priority_queue<T> une le triée par valeur
set<T> un ensemble
multiset<T> un ensemble dans lequel une valeur peut apparaître plusieurs fois
map<key, val> un tableau associatif
multimap<key,val> une map dans laquelle une clé peut apparaître plusieurs fois

6.1.2.1 Les itérateurs


Les itérateurs sont des objets qui ressemblent aux pointeurs et qui vont nous permettre de
parcourir les conteneurs. L'intérêt est que ces itérateurs nous permettent de parcourir avec un

67
même objet diérents conteneurs.

Quand on déclare un conteneur, on va déclarer en même temps un itérateur.

Par exemple pour un vector :

1 v e c t o r <i n t > tab ;


2 v e c t o r <i n t > : : i t e r a t o r i t ; // on d i t que i t e s t un i t é r a t e u r s u r un t a b l e a u d '
entiers

La variable it pointe sur une case du conteneur. Pour que l'on pointe sur la première case,
on utilise la méthode begin() :

1 i t = tab . begin ( ) ;

Pour ensuite aller vers l'élément suivant on incrémente simplement de la manière suivante :
it ++. Pour récupérer le contenu de la case il sut d'ajouter une étoile devant l'itérateur : * it .
Ce qui nous permet d'écrire le code suivant si par exemple on recherche le max d'un tableau
d'entiers.

1 v e c t o r <i n t > tab ( 7 ) = { 2 , 5 , 3 , 9 , 4 , 2 , 1 } ;


2 v e c t o r <i n t > : : i t e r a t o r i t ;
3 i n t max=tab [ 0 ] ;
4 f o r ( i t=tab . begi n ( ) +1; i t != tab . end ( ) ; i t ++)
5 {
6 i f ( * i t <max)
7 max=* i t ;
8 }

La méthode end() appliqué à un conteneur nous renvoie l'adresse de la case après la dernière
case occupé. Dans la boucle ci-dessous il est mis it !=tab.end() cela signie "tant que l'adresse
contenu dans it est diérent de l'adresse de la case d'après". Attention il n'y aurait pas de sens
de mettre le symbole "<" ici car il n'y a pas de relation d'ordre dénie sur les adresses.

Ce code pourrait être fait simplement avec un entier i pour un conteneur de type vector,
mais l'utilisation des itérateurs est indispensable pour utiliser certaines méthodes de la classe
vector, ou encore pour manipuler d'autres conteneurs que vector.

6.1.2.2 La classe vector


Les vecteurs sont des tableaux génériques et dynamiques à une dimension. Pour l'utiliser, il
faut inclure la bibliothèque <vector> : #include <vector>

Le constructeur permet de dénir la taille du nombre d'éléments. Par exemple, vector<int>


tab(10) : permet d'allouer un tableau avec 10 éléments. Une fois que les éléments sont créés vous
pouvez y accéder comme pour un tableau classique avec des crochets : tab[4]=6; Les indices vont
de 0 à taille -1. La méthode push_back permet d'ajouter un élément à la n. Donc si vous avez
déclarer un tableau de 10 entiers, et que vous fait tab.push_back(30), on obtiendra tab[10]=30,
et on aura 11 éléments dans le tableau.

Quelques fonctions membres :

68
at permet de renvoyer à la valeur en fonction de l'indice
insert insère un élément avant l'indice indiqué
erase supprime l'élément pointé par un itérateur
clear vide le contenu du vecteur
push_back insère un élément à la n
pop_back supprime l'élément de la n
size renvoie le nombre de cases du tableau
empty renvoie vrai si le vecteur est vide
swap interverti deux valeurs du tableau
operator[] permet d'accéder à l'élément spécié

6.1.2.3 La classe map


Ce conteneur permet de ranger des paires de données sous la forme (clé, valeur).
Quelques méthodes :
at permet de renvoyer à la valeur en fonction de l'indice
insert insère un élément avant l'indice indiqué
erase supprime l'élément pointé par un itérateur
clear vide le contenu de la map
count retourne le nombre d'éléments correspondant à la clé spéciée
nd trouve l'élément en fonction de la clé spéciée
size renvoie le nombre de cases du conteneur
empty renvoie vrai si la map est vide
swap interverti deux valeurs du conteneur
L'exemple suivant a pour objectif de stocker les notes, la clé représentera la matière et la
valeur le résultat obtenu dans cette matière.

1 #i n c l u d e <s t r i n g >
2 #i n c l u d e <iostream >
3 #i n c l u d e <map>
4
5 u s i n g namespace s t d ;
6
7 c l a s s Etudiant //dé c l a r a t i o n de l a c l a s s e Etudiant
8 {
9 private :
10 s t r i n g nom ;
11 s t r i n g prenom ;
12 i n t anneeNaissance ;
13 map<s t r i n g , f l o a t > n o t e s ; // d e c l a r a t i o n de l a map
14 public :
15 Etudiant ( i n t a ) { anneeNaissance = a ; } ;
16 i n t getAge ( ) { r e t u r n 2019 = anneeNaissance ; } ;
17 void a j o u t e r N o t e ( s t r i n g matiere , f l o a t v a l u e ) ;
18 void a f f i c h e r R e s u l t a t ( ) ;
19 };
20
21 void Etudiant : : a j o u t e r N o t e ( s t r i n g matiere , f l o a t v a l u e )
22 {
23 n o t e s . i n s e r t ( make_pair ( matiere , v a l u e ) ) ; // a j o u t d ' une p a i r e de v a l e u r
24 }
25
26 void Etudiant : : a f f i c h e r R e s u l t a t ( )
27 {
28 map<s t r i n g , f l o a t > : : i t e r a t o r i t ;

69
29 // a f f i c h a g e du contenu de l a map
30 f o r ( i t = n o t e s . begin ( ) ; i t != n o t e s . end ( ) ; i t ++)
31 {
32 cout << i t => f i r s t << " : " << i t =>second << e n d l ;
33 }
34 }
35
36 i n t main ( )
37 {
38 Etudiant Benjamin ( 2 0 0 0 ) ;
39 Benjamin . a j o u t e r N o t e ( " Mathematiques " , 1 3 . 2 5 ) ;
40 Benjamin . a j o u t e r N o t e ( "RTOS" , 7 . 5 ) ;
41 Benjamin . a j o u t e r N o t e ( " Excel2 " , 18) ;
42 Benjamin . a j o u t e r N o t e ( " Mecanique des f l u i d e s " , 1 1 . 5 ) ;
43 Benjamin . a f f i c h e r R e s u l t a t ( ) ;
44 return 0;
45 }

6.1.3 Algorithmes

La bibliothèque algorithmique dénit les fonctions pour des opérations sur des plages d'élé-
ments (par exemple, recherche, tri, comptage, manipulation). Notez qu'une plage est dénie
comme (premier, dernier). Pour pouvoir l'utiliser il est nécessaire d'ajouter #include <algorithm
>.
Quelques fonctions intéressantes :
count Permet de compter combien d'éléments sont présents entre 2 bornes
nd recherche un élément dans un conteneur
sort permet de trier les éléments à l'intérieur d'un conteneur

6.2 Gestion des exceptions


Lorsqu'un programme est conçu avec des modules séparés la gestion des erreurs doit être
adapté à cette structure. En eet quand la taille du programme est importante on peut tout à
fait détecter une erreur sans toutefois savoir qu'en faire. L'idée à ce moment là est de dénir un
mécanisme qui va informer le code appelant du problème en espérant que celui-ci sera à même
de traiter le problème.
La notion d'exception permet de simplier le signalement de l'erreur.
Trois mots-clés sont utilisés pour faire cela : try, throw et catch.
L'exemple ci-dessous montre le principe :

1 i n t d i v i s i o n ( i n t a , i n t b ) // C a l c u l e a d i v i s é par b .
2 {
3 i f ( b==0)
4 throw s t r i n g ( "ERREUR : D i v i s i o n par z é ro ! " ) ; //On l è ve une e x c e p t i o n
5 else
6 r e t u r n a/b ;
7 }
8
9 i n t main ( )
10 {
11 int a ,b;
12 cout << " Valeur pour a : " ;
13 c i n >> a ;

70
14 cout << " Valeur pour b : " ;
15 c i n >> b ;
16
17 //On e s s a y e de f a i r e une d i v i s i o n
18 try
19 {
20 cout << a << " / " << b << " = " << d i v i s i o n ( a , b ) << e n d l ;
21 }
22 // Attrape l ' e x c e p t i o n s i c e l l e = c i a é t é l e v é
23 catch ( s t r i n g c o n s t& c h a i n e )
24 {
25 c e r r << c h a i n e << e n d l ;
26 }
27 return 0;
28 }

71
72
Annexe A
Développer sous microsoft visual studio
Microsoft visual studio (VS) est un IDE (integrated Development Editor) c'est-à-dire que
ce logiciel intègre plusieurs outils permettant de faire un logiciel : un éditeur de code, un
compilateur et un débuggueur. Microsoft Visual Studio est un outil très complet. Seule la base
est présentée ici.

A.1 Installation
Pour télécharger la version Visual Studio Community (version gratuite), coonectez-vous sur :
https ://visualstudio.microsoft.com/fr/, vous sélectionnerez ensuite

Lancer l'installateur. Puis sélectionner dans les composants à installer Développement Desk-
top en C++. A la n de l'installation vous devrez vous connecter avec votre compte ou vous
en créer un si besoin.

A.2 Le projet
VS est basé sur la notion de projet (également nommé solution). Un projet contient l'en-
semble des informations nécessaire à la création du programme que vous souhaitez réaliser :

 les chiers sources


 les chemins vers des librairies
 des options de compilations
 ...

La première chose à faire est de créer un nouveau projet (cf. gure A.2). Je vous conseille
de démarrer en utilisant Projet vide. Vous pourrez mettre ainsi uniquement ce que vous
voulez dans votre application. Si vous utilisez application console, VS va créer une application
spécique pour windows avec des dépendances vers des chiers qui risquent de vous gêner par
la suite.

Une fois le projet créé, nous allons ajouter un premier chier source. Pour cela, allez dans
l'explorateur de solution, et ouvrer le menu contextuel (menu disponible à l'aide du clic droit
sur la souris) en ayant au préalable sélectionné l'élément chiers sources' (cf. A.2)'.

Vous allez sélectionner nouvel élément, puis chier C++ (.cpp) comme indiqué à la gure
A.3.

Il ne vous reste plus qu'à écrire un premier programme. Un exemple est illustré à la gure
A.4.

73
Figure A.1  Création d'un nouveau projet

Figure A.2  Ajout d'un chier source dans le projet

74
Figure A.3  Ajout d'un chier source dans le projet

Figure A.4  Ecriture d'un programme dans l'éditeur

75
A.3 La compilation et exécution
Pour pouvoir être exécuté, un programme en C++ doit d'abord être compilé. Pour rappel
la compilation consiste à créer un chier exécutable à partir de chiers sources. Il y a deux
modes de compilation : debug et release (cf. gure A.5) cela permet de créer des exécutables
avec des caractéristiques diérentes. Une compilation en mode debug permet ensuite d'utiliser
le débogueur. Il y a des traces insérées dans le chier exécutable qui vont nous permettre de
connaître par exemple les valeurs des variables lors de l'exécution de chaque ligne de code. Si la
compilation est réalisée en release vous ne pourrez pas utiliser le débogueur. Le code exécutable
est, cette fois-ci, optimisé pour être le plus léger et le plus rapide possible.

Figure A.5  Compilation

Durant la phase de compilation, des erreurs et des warnings sont détectées (cf. gure A.6).
La présence d'erreur signie que l'exécutable n'a pas pu être généré. Dans cette situation, il est
inutile d'essayer de lancer l'exécutable. La priorité devient donc la suppression de l'ensemble
des erreurs.

Figure A.6  Liste des erreurs

La présence de warning indique quelque chose à laquelle il faut être vigilant, mais cela
n'empêche pas l'ide de créer un exécutable. Cependant, dans une part importante de situations,
un warning signie une erreur potentielle d'exécution, il est donc conseillé de tous les supprimer
avant de vérier si le comportement du programme correspond à celui qui est attendu.
Une fois ce chier exécutable créé, on peut l'exécuter. Dans visual vous utiliserez la èche
verte qui a pour objectif de lancer l'exécution du programme. Si des chiers ont été modiés,
la compilation est lancée d'abord. Si des points d'arrêt ont été insérés dans le code et que

76
vous lancez l'application au moyen de ce bouton vous utiliserez le débogueur. Si vous souhaitez
exécuter sans le débogueur mais laisser les points d'arrêt il y a un bouton dans le menu déboguer.

Figure A.7  Compilation

A.4 Le débogueur
Le débogueur est un outil permettant de voir ce qui se passe à l'intérieur du code lors de
l'exécution. C'est un outil indispensable pour comprendre, notamment pourquoi l'exécution ne
se déroule pas comme prévue.
Le débogueur doit être utilisé lorsque le programme compile sans erreur. L'idée est d'aller
voir ligne par ligne le comportement du programme pour identier le problème.

A.4.1 Les points d'arrêts

Un point d'arrêt s'ajoute en cliquant dans la marge grise de gauche

Figure A.8  Ajout d'un point d'arrêt

Lorsque durant l'exécution du programme, le programme se trouve à exécuter une ligne où


est indiqué un point d'arrêt, le programme s'arrête et c'est ensuite le développeur qui suit le
déroulement du programme.

A.4.2 Parcourir le code

La gure A.9 montre dans son ensemble l'environnement de débogage. La ligne où se trouve
le programme est indiqué par une èche jaune. Lorsqu'un point d'arrêt a été inséré ; la èche
jaune se trouve sur ce point d'arrêt.

77
Figure A.9  Environnement de débogage

Il s'agit ensuite de poursuivre l'exécution en indiquant au programme la suite à donner. Si


vous souhaitez simplement aller sur la ligne suivante et rester au même niveau d'exécution vous
choisissez la èche du milieu indiqué sur la gure A.10. Des raccourcis clavier sont également
disponible pour faire ces actions. Par exemple, la touche F10 permet de passer à la ligne suivante.

Figure A.10  Navigation du débogage

A la gure A.9, nous pouvons voir l'exécution d'un programme. Le programme s'est arrêté
à la ligne 9 par le point d'arrêt, puis l'utilisateur demande la réalisation de chaque ligne de
code une par une. Ce qui a permis d'acher quel est votre nom, l'utilisateur l'a renseigné, le
programme a lu au niveau de la ligne 10 le nom indiqué par l'utilisateur.

A.4.3 Voir les valeurs des variables

Ce qui va dans un premier temps nous intéresser c'est de connaître la valeur des variables
à chaque étape d'exécution du code. Vous pouvez pour cela ajouter la fenêtre des variables
locales (accessible à partir du menu Deboguer/Fenêtres). Cette fenêtre vous indique à chaque
étape les valeurs que prennent les variables locales. Par exemple à la gure A.9, nous pouvons
observer la valeur de la variable nom avant l'exécution de la ligne 11. Cette variable a été lu
à la ligne 10. Elle est donc égale à Toto. Si ce n'avait pas été le cas, cela aurait signié que la
lecture était mal codée.

A.5 L'assistant de création de classes


L'idée de l'assistant de classes c'est de vous permettre de créer les classes de manière auto-
matique. Pour lancer l'assistant de classes, vous pouvez lancer le menu contextuel à partir de

78
l'explorateur de solution comme cela est illustré à la gure A.11. Je vous conseille tout de même
vivement de savoir faire les diérentes actions sans l'assistant car il est souvent plus facile de
reprendre à la main plutôt que de passer par l'assistant.

Figure A.11  Lancer l'assistant de classe

La première étape consiste à ajouter une classe à un projet. Après avoir sélectionné Ajouter
une classe, vous indiquez un nom de classe. L'assistant crée alors automatiquement les chiers
.h et .cpp correspondant à la classe. A ce niveau vous pouvez aussi indiquer si la classe hérite
d'une autre classe.
Dans la fenêtre de l'assistant de classes il y a un plusieurs onglets qui vous permettent de
congurer les propriétés de la classe. Cette fenêtre peut être ouverte à tout moment pour faire
des modications. Cependant il est souvent plus facile de faire directement les modications
dans les chiers.

79
Figure A.12 

80
Annexe B
Solutions des exercices
Id Valide/Invalide ?
z1 Valide
one-banana
2h Valide
$money Invalide
Solution 1
a msg Invalide
number Valide
Me_gusta Valide
dot.com Invalide
RiDdlE Valide

Solution 3

1 #i n c l u d e <iostream >
2
3 u s i n g namespace s t d ;
4
5 i n t add ( i n t x , i n t y )
6 {
7 int z ;
8 z=x+y ;
9 return z ;
10 }
11
12 int substract ( int x , int y)
13 {
14 r e t u r n x=y ;
15 }
16
17 i n t main ( )
18 {
19 int result ;
20 r e s u l t=add ( 3 , 1 ) ;
21 result = substract ( result ,2) ;
22 return 0;
23 }
24

Solution 6 Données : son nom (string) qui peut être Coca-cola, pepsi, ..., son type qui peut
être une canette, une bouteille de verre, son volume qui peut être 33 cl, 50cl ... Opération :
connaître le volume de soda restant ! boire une quantité de volume.

81
Solution 7

1 i n t Soda : : takeASip ( i n t volumeBu )


2 {
3 i f ( volumeBu<volume )
4 volume==volumeBu ;
5 else
6 volume = 0 ;
7 }

Solution 9

1 i n t Rectangle : : a r e a ( )
2 {
3 r e t u r n width * h e i g h t ;
4 }

82
Index
amitié, 53, 58 scanf, 24
software, 8
bool, 16, 17
STL, 67
break, 27
string, 17, 67
structure, 22
char, 17
switch case, 28
compilateur, 13
syntaxe d'un langage, 11
compilation, 76
sémantique d'un langage, 11
const, 20, 52
constructeur, 48 typage, 16
typedef, 17, 22
delete, 22
destructeur, 48 UML, 58
déclaration d'une fonction, 32
dénition d'une fonction, 32 vector, 29, 68
void, 17
editeur de lien, 14
erreur de compilation, 13 warning, 13
while, 26
for, 26

hardware, 8

if, 25
indentation, 15, 25

langage C, 12

main, 15, 39
malloc, 22
map, 69
masquage, 63

new, 22

paramètre de fonction, 31
point d'arrêt, 77
pointeur, 21
Polymorphisme, 64
printf, 24
private, 47
protected, 47
préprocesseur, 13
public, 47

référence, 21

83

Vous aimerez peut-être aussi