Académique Documents
Professionnel Documents
Culture Documents
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
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
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
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
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.
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.
Le langage de programma-
tion : code qui est accessible à
la compréhension d'un humain
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
3. Chauer une poêle non adhésive avec un peu de matière grasse (huile ou beurre)
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
Niveau Exemple
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
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.
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.
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).
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.
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
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.
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 }
Id Valid/Invalid ?
z1
one-banana
2h
$money
a msg
number
Me_gusta
dot.com
RiDdlE
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).
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.
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.
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 {
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 }
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.
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 ;
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.
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 ;
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 .
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 :
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.
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.
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
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 : +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=
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.
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
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 }
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.
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.
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).
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 :
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.
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.
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.
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.
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.
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
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).
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 }
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.
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 }
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.
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 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 }
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++.
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.
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.
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).
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 ?
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 }
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.
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 };
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 }
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 };
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.
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.
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 ) {}
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 }
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 }
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 !
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.
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 :
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
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.
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 :
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.
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
Les modications dans la classe Carre sont du même ordre que pour Triangle.
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.
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 }
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 }
65
66
Chapitre 6
Compléments
6.1 Aperçu de la STL
6.1.1 La classe string
67
même objet diérents conteneurs.
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.
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.
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é
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
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 :
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
74
Figure A.3 Ajout d'un chier source dans le projet
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.
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.
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.
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.
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
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.
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.
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.
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
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