C
C
C
Matthieu DURUT
2
Note préliminaire
1
Ces notes de cours ne constituent qu’une version préliminaire d’un
cours toujours en écriture. Elles sont amenées à évoluer et à être
modifiées, plus particulièrement en fonction des retours des élèves et
relecteurs. Tout commentaire, agréable ou non, est le bienvenu.
3
4 CHAPITRE 1. NOTE PRÉLIMINAIRE
Remerciements
5
6 CHAPITRE 1. NOTE PRÉLIMINAIRE
Table des matières
1 Note préliminaire 3
I Éléments de syntaxe 25
4 Compilateurs, interpréteurs 27
4.1 Déclaration/Définition . . . . . . . . . . . . . . . . . 28
4.2 Les phases de la compilation . . . . . . . . . . . . . . 30
4.3 Pré-compilation . . . . . . . . . . . . . . . . . . . . . 30
4.3.1 Les #include . . . . . . . . . . . . . . . . . . 31
4.3.2 Les #define . . . . . . . . . . . . . . . . . . . 31
4.3.3 Les #ifndef, #endif . . . . . . . . . . . . . . . 33
4.3.4 Les macros . . . . . . . . . . . . . . . . . . . 35
4.3.5 Templates . . . . . . . . . . . . . . . . . . . . 37
4.4 La compilation . . . . . . . . . . . . . . . . . . . . . 37
4.5 L’édition des liens . . . . . . . . . . . . . . . . . . . . 37
4.6 Un exemple éléméntaire . . . . . . . . . . . . . . . . 38
4.7 Librairies standard . . . . . . . . . . . . . . . . . . . 40
4.7.1 ... du C++ standard . . . . . . . . . . . . . . 40
4.7.2 ... du C++11 . . . . . . . . . . . . . . . . . . 43
4.8 Survivre à des messages d’erreurs incompréhensibles . 44
4.8.1 Erreurs à la génération du projet . . . . . . . 44
7
8 TABLE DES MATIÈRES
5 Variables et Typage 47
5.1 Représentation décimale . . . . . . . . . . . . . . . . 47
5.2 Représentation en base 2 . . . . . . . . . . . . . . . . 48
5.3 les nombres négatifs . . . . . . . . . . . . . . . . . . . 48
5.4 les entiers . . . . . . . . . . . . . . . . . . . . . . . . 48
5.5 Les réels . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.6 Déclaration des variables . . . . . . . . . . . . . . . . 51
5.7 Les booléens . . . . . . . . . . . . . . . . . . . . . . . 53
5.8 Les caractères . . . . . . . . . . . . . . . . . . . . . . 54
5.9 Types définis par l’utilisateur . . . . . . . . . . . . . 54
5.10 Constantes et énumérations . . . . . . . . . . . . . . 54
5.10.1 Constantes . . . . . . . . . . . . . . . . . . . . 54
5.10.2 Enumérations . . . . . . . . . . . . . . . . . . 55
5.11 Tableaux statiques . . . . . . . . . . . . . . . . . . . 57
5.12 Opérations de conversion / casting . . . . . . . . . . 58
5.12.1 Conversions implicites . . . . . . . . . . . . . 58
5.12.2 Conversions explicites . . . . . . . . . . . . . 59
5.12.3 Indécisions sur les cast . . . . . . . . . . . . . 60
5.13 Le typage du C++ . . . . . . . . . . . . . . . . . . . 62
6 Les pointeurs 63
6.1 Définition des pointeurs . . . . . . . . . . . . . . . . 63
6.2 Déréférenciation . . . . . . . . . . . . . . . . . . . . . 65
6.3 Opérateurs & et * . . . . . . . . . . . . . . . . . . . . 65
6.3.1 Formalisation des pointeurs . . . . . . . . . . 66
6.4 Usage des pointeurs . . . . . . . . . . . . . . . . . . . 66
7 Fonctions et Scope 69
7.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . 69
7.1.1 Prototypes des fonctions . . . . . . . . . . . . 70
7.1.2 La fonction main . . . . . . . . . . . . . . . . 71
7.2 Déclaration et définition de fonctions . . . . . . . . . 72
7.3 Premier modèle mémoire, Stack et Scope des variables 75
7.4 Passage d’arguments . . . . . . . . . . . . . . . . . . 79
7.4.1 Passage d’argument par valeur . . . . . . . . . 81
7.4.2 Passage d’argument par pointeur . . . . . . . 82
7.4.3 Passage d’argument par référence . . . . . . . 83
7.5 Effets de bord . . . . . . . . . . . . . . . . . . . . . . 84
TABLE DES MATIÈRES 9
10 Opérateurs 125
10.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . 125
10.2 Quelques exemples pertinents de surcharge d’opérateurs130
10.3 Un autre exemple . . . . . . . . . . . . . . . . . . . . 130
10.3.1 Digression autour du "Named Constructor Idiom"133
10.4 Surcharge de l’opérateur d’affectation . . . . . . . . . 136
10.5 Opérateurs internes, opérateurs externes . . . . . . . 138
13 Polymorphisme 173
13.1 Polymorphisme dynamique . . . . . . . . . . . . . . . 173
13.1.1 Factorisation de code . . . . . . . . . . . . . . 173
13.1.2 Redéfinition de méthodes dans les classes filles
et slicing . . . . . . . . . . . . . . . . . . . . . 175
13.1.3 Passage par pointeur . . . . . . . . . . . . . . 177
13.1.4 Virtualité . . . . . . . . . . . . . . . . . . . . 178
13.1.5 Virtualité Pure, classes abstraites et interfaces 180
13.1.6 Coût de la virtualité . . . . . . . . . . . . . . 182
13.1.7 Virtualité et Destructeurs . . . . . . . . . . . 184
13.2 Polymorphisme statique . . . . . . . . . . . . . . . . 185
13.2.1 Position du polymorphisme statique . . . . . . 185
13.2.2 Implémentation . . . . . . . . . . . . . . . . . 185
V Annexes 223
12 TABLE DES MATIÈRES
— It looked like a good thing to do.
Dennis Ritchie, à propos de l’invention du langage
C
2
langage C++
13
14 CHAPITRE 2. NOTES SUR LE C++
17
18 CHAPITRE 3. QUELQUES NOTIONS HARDWARE
culatrices géantes) dans les années 40, avec l’équipe Von Neumann.
Pour les besoins en calculs du projet Manhattan à Los Alamos, Von
Neumann élabore avec Mauchly et Eckert en s’inspirant des travaux
de Turing 1 un premier calculateur, dont le design est depuis appelé
(plutôt injustement) architecture de Von Neumann. C’est cette ar-
chitecture qui reste présente dans la quasi-totalité des architectures
actuelles 2 . Dans ces premiers ordinateurs, la machine avait très prin-
cipalement vocation au calcul. Chacun des composants était fabriqué
spécifiquement pour cette machine, par le même constructeur.
3.1 Le CPU
Le CPU (Central Processing Unit) est le composant principal de
calcul. Les CPU qui nous intéressent sont construits sur un seul circuit
imprimé (depuis les années 1970) : on les appelle micro-processeurs.
Un CPU moderne est constitué de millions de transistors, qui per-
mettent de réaliser de très nombreuses opérations mathématiques
par seconde.
1. Alan Turing est avec Von Neumann l’un des pères de l’informatique. Il s’est suicidé en
croquant une pomme empoisonnée au cyanure, probablement en référence à Blanche-Neige ; le
logo d’Apple est un hommage à Turing.
2. même si l’architecture interne des micro-processeurs depuis 10 ans s’en écarte continuel-
lement
3.1. LE CPU 19
3.2 La mémoire
La mémoire d’un ordinateur est répartie sur de multiples com-
posants. Tous les composants d’un ordinateur stockent des données,
au moins celles nécessaires à leur fonctionnement instantané. Cer-
tains composants (mémoire RAM, mémoire Cache, disque-dur) sont
cependant entièrement dédiés à stocker les données et à les rendre
disponibles pour les autres éléments. Ces composants de stockage se
distinguent suivant trois critères : la persistence ou non des données
lorsque la machine est éteinte/rallumée, la vitesse d’accès aux don-
nées stockées, et la capacité de stockage.
5. La techno est en train d’être remplacée par des SSD mais devrait être encore dominante
pour quelques années.
6. c’est souvent un des composants les plus fragiles : lorsque vous faites tomber votre laptop,
les têtes de lecture viennent souvent perforer les disques, détruisant ainsi le disque dur.
3.2. LA MÉMOIRE 21
3.2.5 Prefetching
Les sections précédentes ont démontré les hiérarchies de mémoire,
de la plus rapide à la plus lente : Cache L1, Cache L2, Cache L3,
RAM. Comment un programme utilise-t-il ces différents espaces de
stockage ? Toutes les données nécessaires à l’exécution d’un pro-
gramme se trouvent dans la mémoire RAM. La mémoire Cache va
servir de copie de la mémoire RAM et devenir une mémoire-tampon ;
afin de limiter le coût d’accès à la mémoire RAM, la mémoire Cache
va copier une partie des données nécessaires à l’exécution du pro-
gramme. Lorsque la donnée est répliquée en cache, le CPU requêtera
la mémoire Cache plutôt que la mémoire RAM, afin d’obtenir la
donnée plus rapidement. Lorsque le CPU requête une donnée et que
celle-ci se trouve dans le Cache, on parle de Cache Hit ; dans le cas
contraire, on parle de Cache Miss.
3.2. LA MÉMOIRE 23
tion de localité des données (nous renvoyons le lecteur intéressé à [8]).
8. sauf lorsqu’il y a des instructions conditionnelles comme IF, auquel cas le préfetcheur va
faire du branching...
24 CHAPITRE 3. QUELQUES NOTIONS HARDWARE
3.3 Le GPU
La carte graphique, aussi connue sous le terme GPU (Graphic
Processor Unit), est une sorte de processeur dédié. C’est en fait un
composant matériel qui héberge en son sein plusieurs centaines de
micro-CPU élémentaires et bon-marchés. Ces micro-CPU possèdent
beaucoup moins d’instructions que les CPU classiques et ont une
cadence plus faible. Cependant, leur nombre fait des GPU des unités
de calcul extrêmement puissantes dans certains cas.
Éléments de syntaxe
25
— There are only two things wrong with C++ : The initial concept and the implementation.
Bertrand Meyer
Compilateurs, interpréteurs
4
Note préliminaire : le terme compilation désigne originellement
une étape du processus de transformation de votre code source en
un programme exécutable. Par synecdoque, il est également utilisé
pour décrire l’intégralité de ce processus. Dans la suite de ce chapitre,
nous utiliserons le terme dans ses deux entendements.
27
28 CHAPITRE 4. COMPILATEURS, INTERPRÉTEURS
4.1 Déclaration/Définition
En C++, il vous faut à la fois déclarer et définir vos variables,
vos fonctions et vos classes.
2. La loi de Moore, qui n’avait pas été énoncée en ces termes par Moore, stipule que la
puissance des ordinateurs, c’est à dire leur fréquence (le rapport entre fréquence et puissance
des processeurs est détaillée dans la section 3 ) double tous les 18 mois, et cette loi s’est
remarquablement vérifiée jusque 2007/2008.
3. En réalité, les choses sont plus complexes, puisqu’il existe de nombreux langages semi-
interprétés, que certains langages ne sont partiellement compilés qu’à l’exécution (c’est le cas
pour C# et Java avec la compilation Just In Time (JIT), que sont en développement des
langages où la compilation se ferait en continu pendant l’exécution du programme (Compilation
Continue), etc...
4. CINT, UnderC, ...
4.1. DÉCLARATION/DÉFINITION 29
Dans le cas des variables, nous pouvons effectuer ces deux opéra-
tions en une seule ligne 5 de la manière suivante :
1 int x = 2;
1 int x;
2 x = 2;
Nous déclarons ainsi une fonction Pow, qui prend deux arguments,
l’un réel (type : double) et l’autre entier (type : int), et qui retourne
un double. Nous pouvons maintenant définir cette fonction, c’est à
dire instruire le compilateur de ce qu’elle fait exactement :
5. en une seule ligne, mais pas de manière atomique. Ceci dépasse cependant le cadre de
notre cours...
30 CHAPITRE 4. COMPILATEURS, INTERPRÉTEURS
4.3 Pré-compilation
La précompilation (ou pré-processing) désigne l’ensemble des
instructions réalisées par l’IDE au niveau du texte représentant le
code source. Avant la précompilation, votre projet est un ensemble de
fichiers textes, après la pré-compilation également. Les instructions de
pré-processing commencent par le symbole #. Parmi les instructions
du pré-processeur, nous parlerons des #include, des #define, des
#ifndef, des #endif et nous parlerons des macros uniquement pour
vous les interdire.
6. Sauf dans le cas des fonctions inlinées ou templatées, mais nous y reviendrons.
4.3. PRÉ-COMPILATION 31
1 #include "A.h"
1 #include <iostream>
A.h B.h
#include A.h
Déclarations contenues
dans A.h Déclarations contenues
dans B.h
A.h B.h
#include A.h
Déclarations contenues
dans A.h Déclarations contenues
dans B.h
A.h B.h
Déclarations contenues
dans A.h
Déclarations contenues
dans A.h
Déclarations contenues
dans B.h
commande :
1 #define PI 3.141592653589793
1 #ifndef PI
2 #define PI 3.141592653589793
3 #endif
A.h B.h
4 /∗ Content of A.h ∗/
5
6 #endif
4 /∗ Content of B.h ∗/
5
6 #endif
4.3. PRÉ-COMPILATION 35
1 double y = MULTIPLICATE(a+1,b+1);
8. Il est possible de remplacer cette syntaxe par un #pragma once, mais ceci n’est pas défini
par la norme C++ : cette instruction est reconnue par Visual Studio mais pas par d’autres
IDE, c’est pourquoi nous vous la déconseillons et n’en parlons pas plus avant.
9. On leur préfère maintenant des fonctions templatées inlinées, qui ont les mêmes avantages,
mais pas leurs inconvénients.
36 CHAPITRE 4. COMPILATEURS, INTERPRÉTEURS
1 y = a + 1*b + 1 = a + b + 1
1 6 / MULTIPLICATE(2,3)
1 6 / 2 * 3 = 3 * 3 = 9
1 int x = 1;
2 int y = SQUARE(x++);
4.3.5 Templates
C’est pendant l’étape de pré-processing que les classes et fonctions
templatées sont créées. Selon l’usage des templates que vous aurez,
cette phase pourra être réduite à sa plus simple expression ou au
contraire se révéler très longue 10 . Nous reviendrons sur ce point dans
le chapitre consacré aux templates.
4.4 La compilation
A la suite du pré-processing, vient la deuxième étape de la compila-
tion dans laquelle le compilateur compile chaque fichier source (.cpp),
c’est-à-dire qu’il transforme chacun de ces fichiers sources en fichiers
binaires (.o ou .obj) contenant du code directement exécutable par
la machine. Cette phase constitue la compilation proprement dite.
Bonnes habitudes 5 (Compilation régulière) Compilez votre code
dès que possible. Quoi qu’il arrive, compilez au moins une fois par
heure. Lorsque vous débutez, compilez toutes les 5 minutes. Si la
compilation échoue, réglez les erreurs de compilation avant de conti-
nuer à développer. Si vous travaillez dans un IDE correct, il vous
montrera les problèmes de code avant même que vous ne compiliez ;
réglez les dès que vous les voyez.
1 ///////////////////////////main.cpp//////////////////////
2 #include "A.h"
3 #include "B.h"
4
5 void main()
6 {
7 functionInA();
8 functionInB();
9 }
1 ///////////////////////////A.h////////////////////////////
2
3 #ifndef A_H
4 #define A_H
5
6 #include "B.h" //not useful here, just put to illustrate the compilation process
7
8 void functionInA(void);
9
10 #endif
1 //////////////////////////B.h//////////////////////////////
2 #ifndef B_H
3 #define B_H
4
5 void functionInB(void);
6
7 #endif
4.6. UN EXEMPLE ÉLÉMÉNTAIRE 39
1 ////////////////////////////////////////////A.cpp
///////////////////////////////////
2 #include "A.h"
3 #include "B.h"
4
5 void functionInA(void)
6 {
7 functionInB();
8 }
1 ////////////////////////////////////////////B.cpp
///////////////////////////////////
2 #include "B.h"
3
4 void functionInB(void)
5 {
6 //Do Nothing
7 }
Le préprocesseur copie :
– les déclarations de B.h dans A.h,
– les déclarations de A.h et B.h dans main.cpp,
– les déclarations de A.h et B.h dans A.cpp,
– les déclarations de B.h dans B.cpp
Le fait d’utiliser les #ifndef #endif nous permet d’éviter d’inclure
deux fois les déclarations de B.h dans main.cpp et A.cpp, ce qui
amènerait à des erreurs de compilation.
<iostream>
12. Le C++11, non traité dans ce cours, fournit maintenant certaines de ces fonctionnalités
dans les librairies standard.
4.7. LIBRAIRIES STANDARD 41
1 #include <iostream>
2 using namespace std;
3
4 void main()
5 {
6 cout << "Hello World";
7 }
1 #include <iostream>
2 using namespace std;
3
4 void main()
5 {
6 int a = 2;
7 cout << a;
8 }
1 #include <iostream>
2 using namespace std;
3
4 void main()
5 {
6 int a = 2;
7 cout << "The value of a is" << a << "\n";
8 }
<vector>
1 #include <vector>
2 using namespace std;
3
4 void main()
5 {
6 int n = 10*10;
7 vector v(n);
8 for (int i = 0 ; i < n ; i++)
9 {
10 v[i] = i*2;
11 }
12 }
<string>
1 #include <string>
2 using namespace std;
3
4 void main()
5 {
6 string myMessage = "Hello World";
7 }
<math.h>
<fstream>
1 #include <fstream>
2 using namespace std;
3
4 int main () {
5 ofstream myfile;
6 myfile.open ("SomeExample.txt");
7 myfile << "Writing something to this file.\n";
8 myfile.close();
9 return 0;
10 }
<exception>
<random>
<memory>
<unordered_map>
<tuple>
There are 10 types of people in the world : Those who understand binary, and
those who don’t.
Variables et Typage
5
Vous êtes familiers avec les mots clefs int, short, long, double et
float, et avec la base 2 ? sautez ce chapitre en première lecture.
47
48 CHAPITRE 5. VARIABLES ET TYPAGE
3. et depuis récemment sous les machines Apple, le lecteur souhaitant développer sous des
machines Mac un peu vieilles et utiliser des lectures disque est très vivement invité à s’enquérir
du principe de Little et Big Endian.
5.4. LES ENTIERS 49
4. Les bornes pour les int ne sont valables que pour les architectures 32 bits.
50 CHAPITRE 5. VARIABLES ET TYPAGE
2, 76 ∗ 101
0, 276 ∗ 102
276 ∗ 10−1
entiers selon les plages de valeurs que nous anticipons, nous avons
également plusieurs types différents pour stocker un réel.
1 TypeDeLaVariable NomDeLaVariable;
1 double threshold;
2 long y;
3 int length;
4 short minValue;
5 float mean;
6 char firstLetter;
7 bool isLocked;
Il est important de noter que les noms des variables sont sensibles
à la casse (case sensitive) 11 . En d’autres termes, cela signifie que
Variable1 et variable1 désignent deux variables différentes. Nous
attirons votre attention sur cette propriété, qui sera source
10. Il y a des contre-exemples bien sûr, mais celà dépasse le cadre de ce cours
11. C’est-à-dire à la distinction entre majuscules et minuscules.
5.7. LES BOOLÉENS 53
1 Widget widget;
12. Nous nous acharnerons sur vous en TD si vous ne les respectez pas.
54 CHAPITRE 5. VARIABLES ET TYPAGE
1 #include <iostream.h>
2
3 int main()
4 {
5 double pi = 3.1415926538;
6
1 #include <iostream.h>
2
3 int main()
4 {
5 const double pi = 3.1415926538;
6
5.10.2 Enumérations
Nous pouvons également avoir besoin d’une liste de constantes
mais liées entre elles. Considérons le code du listing 5.3.
1 #include <iostream.h>
2
13
14 int main()
15 {
16 f(small);
17 }
Listing 5.3 – Une série de constantes
56 CHAPITRE 5. VARIABLES ET TYPAGE
1 enum NomEnumeration
2 {
3 premierElement,
4 deuxiemeElement,
5 troisiemeElement
6 ...
7 };
1 enum Size
2 {
3 Small,
4 Medium,
5 Big
6 }
1 #include <iostream.h>
2
3 enum Size
4 {
5 SMALL,
6 MEDIUM,
7 BIG;
8 }
9
16
17 int main()
18 {
19 f(Size.SMALL);
20 }
Listing 5.4 – Emploi d’une énumération
1 Type arrayName[size];
Listing 5.5 – Déclaration d’un tableau
Par exemple,
1 double someArray[ 50 ];
58 CHAPITRE 5. VARIABLES ET TYPAGE
1 double someArray[ 10 ];
2
4 4 for i=1 to 10
5 for i in range(1, 11): 5 tableau(i)=i*i
6 tableau.append(i * i) 6 next i
Pour créer un tableau dont la taille n’est pas une constante écrite
nommément dans le code, c’est beaucoup plus difficile ; nous revien-
drons sur ce point dans le chapitre ??.
1 float pi = 3.14;
2 double pii = pi;
Listing 5.7 – "Conversion implicite"
float=>double, double => int, etc.
1 class A {};
2 class B { public: B (A a) {} };
3
4 A a;
5 B b=a;
Listing 5.8 – "Conversion implicite par le constructeur"
1 short s = 200;
2 int b = (int) s;
Listing 5.9 – "Conversion explicite d’un short en int"
1 int a = 16;
2 int b = 17;
3 int q1 = a/b; //q1 = 0
4
5 double c = 16;
6 double d = 17;
7 double q2 = c/d // q2 = 0.94117647058
Listing 5.10 – "Divisions réelles et euclidiennes"
1 int a = 16;
2 int b = 17;
3 double q1 =((double) a)/b; //q = 0.94117647058
Listing 5.11 – "Cast d’un entier en double pour obtenir une division réelle"
1 #include <math.h>
2 double Power(double x, double y)
3 {
4 return exp(y*log(x));
5 }
Listing 5.12 – "Une première fonction Power"
5.12. OPÉRATIONS DE CONVERSION / CASTING 61
2 void main()
3 {
4 int a = 3;
5 int b = 2;
6 double c = Power(a,b);
7 }
Listing 5.13 – "Un autre cast implicite"
1 #include <math.h>
2 float Power(float x, float y)
3 {
4 return exp(y*log(x));
5 }
Listing 5.14 – "Une deuxième fonction Power"
Il nous faut donc ici réaliser un cast explicite pour lever l’ambi-
guïté.
62 CHAPITRE 5. VARIABLES ET TYPAGE
2 void main()
3 {
4 int a = 3;
5 int b = 2;
6 double c = Power( (double) a, (double) b);
7 }
Listing 5.15 – "Le cast explicite est ici indispensable"
15. En C#, on peut par exemple récupérer le type d’un objet à l’exécution, parcourir
l’ensemble des types chargés en mémoire, sélectionner les types qui héritent de telle classe et
qui possèdent un constructeur vide, les instancier, etc.
6
Les pointeurs
Chaque variable d’un programme est stockée dans une des mé-
moires de la machine qui exécute le code (mémoire Cache, mémoire
RAM, disque dur, etc.). Nous pouvons pour le moment considérer la
mémoire de notre machine comme un ensemble de cases mémoires
contigües, chacune d’un octet. Chaque case est numérotée, afin de
pouvoir rapidement accéder à une case précise. Lorsque nous écri-
vons :
63
64 CHAPITRE 6. LES POINTEURS
Case mémoire 0 1 2 3 4 5 6 7 8 9
Nom n n n n
Valeur 110..000 00... 000... 000..
1 double b = 3.2;
2 double* pb = &b;
6.2 Déréférenciation
Lorsque nous disposons d’un pointeur, nous pouvons souhaiter
récupérer la valeur pointée, c’est à dire le déréférencer. Pour celà,
nous utilisons l’opérateur * –dit opérateur de déréférencement– pour
accéder à la valeur stockée à l’adresse du pointeur :
1 double b = 3.2;
2 double* pb = &b;
3 double c = *pb;
Nous venons de voir deux utilisations distinctes du symbole * :
1. lors de la déclaration d’un pointeur, il spécifie que la variable
pb par exemple est un pointeur sur double.
2. lors de la déréférenciation, c’est l’opérateur * qui associe à un
pointeur la variable vers laquelle le pointeur renvoie.
Il s’agit donc d’une homonymie de l’opérateur *, à laquelle il faut
prendre garde lorsque l’on débute.
1 double a = 3.2;
2 double b = *(&a);
66 CHAPITRE 6. LES POINTEURS
1 double a = 3.2;
2 double* pa = &a;
3 double* pb = &(*pa);
1 int a = 3;
2 double* pa = &a;
Fonctions et Scope
7
7.1 Fonctions
Intéressons nous d’abord à la fonction qui calcule le carré d’un
entier.
1 int Square(int x)
2 {
3 return x*x;
4 }
Listing 7.1 – Fonction qui calcule le carré d’un nombre.
69
70 CHAPITRE 7. FONCTIONS ET SCOPE
Comme pour les noms de variables, les noms des fonctions tiennent
compte de la casse (c’est à dire des majuscules et des minuscules).
1 int monNomDeVariableTropLong;
et
1 int mon_nom_de_variable_trop_long;
1 void main()
2 {
3 //code
4 }
2 //Main.cpp
3
4 int Square(int x)
5 {
6 return x*x;
7 }
8
9 void main()
10 {
11 int a = 3;
12 int b = Square(a);
13 }
2 //Main.cpp
3
4 void main()
5 {
6 int a = 3;
7 int b = Square(a);
8 }
9
10 int Square(int x)
11 {
12 return x*x;
13 }
2 //Main.cpp
3
6 void main()
7 {
8 int a = 3;
9 int b = Square(a);
10 }
11
2 //Main.h
3
4 int Square(int);
et dans main.cpp :
2 //Main.cpp
3 #include "main.h"
4
5 void main()
6 {
7 int a = 3;
8 int b = Square(a);
9 }
10
11 int Square(int x)
12 {
13 return x*x;
14 }
x1
main
y1
z
7 }
8
9 void main()
10 {
11 double x1 = 2.3;
12 double y1 = 3.2;
13 double z = SquareError(x1, y1);
14 }
SquareError
x
y
result
return point
x1
main
y1
z
7 }
8
9 void main()
10 {
11 double x = 2.3;
12 double y = 3.2;
13 double z = SquareError(x, y);
14 }
2 #include <math.h>
3
17 void main()
18 {
19 double a = 2.0;
20 double b = 1;
21 double c = 3.0;
22 double d = SquareError(a,b);
23 double e = ManhattanError(a,b);
24 }
Dans cet exemple, les valeurs a,b,c,d,e ont pour scope la fonc-
tion main. Une variable s est déclarée dans SquareError, et elle est
détruite à la fin de la fonction. L’identifiant s est "recyclé" dans la
fonction ManhattanError, et la variable s est également détruite à
la fin de cette fonction. Le double usage de l’identifiant s ne pose
pas de problème au compilateur, qui comprend que la variable est
"muette" dans les deux cas.
a
b
main
c
d
e
s
SquareError
x
y
result
return point
a
b
main
c
d
e
ManhattanError
x
y
result
return point
a
b
main
c
d
e
x
??
abs
??
return point
s
ManhattanError
x
y
result
return point
a
b
main
c
d
e
6 void main()
7 {
8 unsigned int i = 2;
9 Increment1(i);
10 unsigned int b = i;
11 }
Case mémoire 0 1 2 3 4 5 6 7 8 9 10 11 12
Nom i i i i
Valeur 0100... 000... 00... 000...
Case mémoire 0 1 2 3 4 5 6 7 8 9 10
Nom i i i i a a a a
Valeur 0100... 000... 00... 000.. 0100... 000... 00... 000..
Juste avant de sortir de la fonction Increment1 :
Case mémoire 0 1 2 3 4 5 6 7 8 9 10
Nom i i i i a a a a
Valeur 0100... 000... 00... 000.. 1100... 000... 00... 000..
De retour dans le main après appel à Increment1 :
Case mémoire 0 1 2 3 4 5 6 7 8 9 10
Nom i i i i
Valeur 0100... 000... 00... 000..
Il nous faut donc spécifier en C++ au compilateur que nous ne
voulons pas qu’il travaille avec une copie de la variable i, mais bien
avec la variable i elle-même.
1 void Increment2(int* p)
2 {
3 (*p)++;
4 }
5
6 void main()
7 {
8 int i = 2;
9 int* pi = &n;
10 Increment2(pi);
11 int b = i;
12 }
1 void Increment3(int& a)
2 {
3 a++;
4 }
5 void main()
6 {
7 int a = 2;
8 Increment3(a);
9 int b = a;
10 }
main.
Wrong. The common subset of C and C++ is easier to learn than C. There
will be less type errors to catch manually (the C++ type system is stricter
and more expressive), fewer tricks to learn (C++ allows you to express more
things without circumlocution), and better libraries available. The best initial
subset of C++ to learn is not "all of C". Bjarne Stroustrup, FAQ sur sa page
personnelle
Comme dans les autres langages, nous voulons pouvoir faire des
tests sur les valeurs des variables. La syntaxe d’un test ainsi qu’un
exemple peuvent être lus ci-dessous :
1 1
2 if (condition) 2 if (0 == x)
3 { 3 {
4 /∗Code∗/ 4 x = x + 1;
5 } 5 }
6 else if(condition) 6 else if (1 == x)
7 { 7 {
8 /∗Code∗/ 8 x = x * 2;
9 } 9 }
10 else 10 else
11 { 11 {
12 ... 12 x = x + 3;
13 } 13 }
Listing 8.1 – Syntaxe d’un test Listing 8.2 – Exemple de test
85
86 CHAPITRE 8. AUTRES ÉLÉMENTS DE SYNTAXE
de l’opérateur !=.
1 if (x = 0)
2 {
3 x = x + 1;
4 }
Listing 8.3 – Une erreur classique
1 if (0 == x)
2 {
3 x = x + 1;
4 }
En lieu et place de :
1 if (x == 0)
2 {
3 x = x + 1;
4 }
Tests en séries
1 #include <iostream.h>
2
4 int main()
5 {
6 char c;
7
11 if (c == ’A’)
12 cout << "Lettre A";
13 else if (c == ’B’)
14 cout << "Lettre B";
15 else if (c == ’C’)
16 cout << "Lettre C";
17 else
18 cout << "Autre lettre;
19
20 return 0;
21 }
Listing 8.4 – Tests en série
1
1 switch(c)
2 switch (variable)
2 {
3 {
3 case ’A’:
4 case valeur1 :
4 cout << ‘‘lettre A’’;
5 /∗code∗/
5 break;
6 break;
6 case ’B’:
7 case valeur2 :
7 cout << ‘‘lettre B’’;
8 /∗code∗/
8 break;
9 break;
9 default:
10 /∗autres cas∗/
10 cout <<‘‘Autre lettre’’;
11 default:
11 break;
12 /∗code∗/
12 }
13 break;
14 } Listing 8.6 – Exemple de tests
multiples
Listing 8.5 – Syntaxe d’un switch
– Le mot clé switch indique que nous allons opérer une séparation
des cas.
– Chaque cas est ensuite traité à l’aide du mot clé case.
– Les cas non prévus explicitement sont traités par le cas default.
– Le mot clé break sert à indiquer la fin du cas. Si ce mot clé est
absent, le cas en dessous sera également exécuté.
1 #include <iostream.h>
2
3 int main()
4 {
5 char c;
6
10 switch (c)
11 {
12 case ’A’:
13 cout << "Lettre A";
14 break;
15
16 case ’B’:
17 cout << "Lettre B";
18 break;
19
20 case ’C’:
21 cout << "Lettre C";
22 break;
23
24 default:
25 cout << "Autre lettre";
26 break;
27 }
28 return 0;
29 }
Listing 8.7 – Tests en série
8.1.2 Boucles
La boucle for
La boucle while
L’autre type de boucle dont nous pouvons avoir besoin est une
boucle signifiant tant que, que nous exprimons à l’aide du mot clé
while. La condition est testée avant le bloc de code.
1 while (condition)
2 {
3 /∗code∗/
4 }
1 do
2 {
3 /∗code∗/
4 }
5 while(condition);
3 do
4 {
5 i++;
6 cout << i << endl;
7 }
8 while ( i < 100 )
break et continue
1 #include <iostream.h>
2
5 int main()
6 {
7 for(int i = 0; i < 10 ; i++)
8 {
9 cout << i << endl;
10 if (i>3)
11 break;
12 }
13 return 0;
14 }
Listing 8.10 – examplebreak.cpp
1 #include <iostream.h>
2
4 int main()
5 {
6 for (int i = 0; i<10; i++)
7 {
8 if (i % 2 == 1)
9 {
10 continue;
11 }
12 cout << i << endl;
13 }
14 return 0;
15 }
Listing 8.11 – examplecontinue.cpp
Deuxième partie
Programmation orientée
objet
93
- - Ah, voilà enfin le roi de la classe ! L’homme trop bien sapé,
Abitbol ! Alors comme ça tu as été élu l’homme le plus classe
du monde ! Laisse-moi rire ! Style le grand playboy des fonds
marins, [...]
- [...] Mais c’est pas ça qu’on appelle la classe. Je te dis ça en
qualité d’homme le plus classe du monde.
9
- Excuse-moi de te dire ça mon pauvre José, mais tu confonds
un peu tout. Tu fais un amalgame entre la coquetterie et la
classe. Tu es fou. Tu dépenses tout ton argent dans les habits
et accessoires de mode mais tu es ridicule. Enfin si ça te plait...
C’est toi qui les portes. Mais moi, si tu veux mon opinion, ça
fait un peu... has been.
95
96 CHAPITRE 9. INTRODUCTION À L’OBJET : LES CLASSES
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 #include <string.h>
6
7 class Accumulator
8 {
9 public:
10 int n;
11 double xSum;
12 double xSquareSum;
13 }; //ne pas oublier le ; ici !
14
15 #endif
Une fois notre classe déclarée, nous pouvons alors déclarer des ins-
tances de ce type ; Nous déclarons/définissons donc un Accumulator
dans notre main de la manière suivante :
9.1. DÉCLARATION DES CLASSES 97
1 //main.cpp
2 #include "Accumulator.h"
3
4 void main()
5 {
6 Accumulator acc;
7 }
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 public:
8 int n;
9 double xSum;
10 double xSquareSum;
11
12 void Add(double);
13 };
14
15 #endif
1 //Accumulator.cpp
2
3 #include "Accumulator.h"
4
5 void Accumulator::Add(double x)
6 {
7 n++;
8 xSum += x;
9 xSquareSum += x*x;
10 }
98 CHAPITRE 9. INTRODUCTION À L’OBJET : LES CLASSES
1 instance.champ = ...
2 instance.Method()
1 void main()
2 {
3 //construction of object acc
4 Accumulator acc;
5
– elle est très verbeuse, puisqu’il faut une ligne de code par champ
renseigné.
– il est possible (facile même) d’oublier un champ, et d’avoir alors
un champ non initialisé, ce qui posera très vraissemblablement
problème par la suite.
– nous n’avons pas d’initialisation par défaut de certains champs.
9.2. INITIALISATION ET CONSTRUCTEURS 99
1 //User.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 public:
8 int n;
9 double xSum;
10 double xSquareSum;
11
12 void Add(double);
13 void Initialize(int, double, double);
14 };
15
16 #endif
1 void main()
2 {
3 //construction of object acc
4 Accumulator acc;
5
9.2.2 Constructeurs
1 Accumulator acc(0,0,0);
2 /∗ Initialize serait alors appelée automatiquement∗/
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 public:
8 int n;
9 double xSum;
10 double xSquareSum;
11
1 void main()
2 {
3 Accumulator acc(0,0,0);
4
1 void main()
2 {
3 Accumulator acc; //Does not work anymore since the constructor expects 3
arguments.
4 acc.Initialize(0,0,0);
5
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 public:
8 int n;
9 double xSum;
10 double xSquareSum;
11
8 Accumulator::Accumulator()
9 {
10 n = 0;
11 xSum = 0;
12 xSquareSum = 0;
13 }
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 public:
8 int n;
9 double xSum;
10 double xSquareSum;
11
12 Accumulator();
13 void Add(double);
14 void Initialize(int, double, double);
15 };
16 #endif
1 Accumulator::Accumulator()
2 {
3 }
9.2.5 Destructeur
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 public:
8 int n;
9 double xSum;
10 double xSquareSum;
11
19 #endif
1 Accumulator::~Accumulator(void)
2 {
3 }
9.3 Encapsulation
1 //main.cpp
2 #include "Accumulator.h"
3
4 void main()
5 {
6 Accumulator acc(0,0,0);
7
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int n;
9 double xSum;
10 double xSquareSum;
11
12 public:
13 Accumulator(int, double, double);
14 Accumulator();
15 ~Accumulator(void);
16 void Add(double);
17 void Initialize(int, double, double);
18 };
19
20 #endif
1 //main.cpp
2 #include "Accumulator.h"
3
4 void main()
5 {
6 Accumulator acc;
7 acc.Add(1);
8
9 double mean = acc.xSum / acc.n; //ERROR: private fields xSum and n cannot
be accessed outside the class Accumulator
10 }
Listing 9.1 – "Ce code refusera de compiler"
1 double Accumulator::GetMean(void)
2 {
3 return xSum / n;
4 }
1 //main.cpp
2 #include "Accumulator.h"
3
4 void main()
5 {
6 Accumulator acc;
7 acc.Add(1);
8
Par convention, les champs privés d’une classe sont préfixés par
"_". Nos fichiers header et source devenant alors :
9.3. ENCAPSULATION 109
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int _n;
9 double _xSum;
10 double _xSquareSum;
11
12 public:
13 Accumulator(int, double, double);
14 Accumulator();
15 ~Accumulator(void);
16 void Add(double);
17
18 double GetMean(void);
19 };
20
21 #endif
110 CHAPITRE 9. INTRODUCTION À L’OBJET : LES CLASSES
1 #include "Accumulator.h"
2
10 Accumulator::Accumulator()
11 {
12 _n = 0;
13 _xSum = 0;
14 _xSquareSum = 0;
15 }
16
17 Accumulator::~Accumulator(void)
18 {
19 }
20
21 void Accumulator::Add(double x)
22 {
23 _n++;
24 _xSum += x;
25 _xSquareSum += x*x;
26 }
27
28 double Accumulator::GetMean(void)
29 {
30 return _xSum / _n;
31 }
9.3.2 Accesseurs
1 //in Accumulator.cpp :
2 int Accumulator::GetN(void)
3 {
4 return _n;
5 }
Pourquoi donc utiliser des accesseurs alors qu’il serait plus rapide
de déclarer le champ public et de pouvoir le lire et le modifier plus
facilement ? C’est le principe même de l’encapsulation, ébauché ci-
dessus. Les champs d’une classe servent en règle générale à décrire un
état interne de la classe, qui ne doit pas être accessible de l’extérieur.
1 void main()
2 {
3 Accumulator acc1;
4 acc1.Add(1);
5 acc1.Add(7);
6
7 Accumulator acc2;
8 acc2.Add(4);
9
10 int n1 = acc1.GetN();
11 int n2 = acc2.GetN();
12
13 int n = n1+n2;
14 }
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int _n;
9 double _xSum;
10 double _xSquareSum;
11 static int nTotal;
12
13 public:
14 Accumulator(int, double, double);
15 Accumulator();
16 ~Accumulator(void);
17 void Add(double);
18
19 double GetMean(void);
20 int GetN(void);
21 };
22
23 #endif
1 void Accumulator::Add(double x)
2 {
3 _n++;
4 _xSum += x;
5 _xSquareSum += x*x;
6 nTotal++;
7 }
1 int Accumulator::nTotal=0;
L’initialisation des variables statiques peut poser problème. En
effet, le compilateur agit "comme si" les variables étaient toutes
déclarées, puis elles sont ensuite définies dans l’ordre dans lequel
le compilateur lit les définitions dans le fichier source. Nous pou-
vons alors avoir des comportements étranges, comme en témoigne
l’exemple suivant :
1 //A.h
2 #ifndef A_H
3 #define A_H
4
5 class A
6 {
7 static int a;
8 static int b;
9 };
10
11 #endif
1 //A.cpp
2
3 #include "A.h"
4
1 //A.cpp
2
3 #include "A.h"
4
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int _n;
9 double _xSum;
10 double _xSquareSum;
11 static int nTotal;
12 static int accCreated;
13
14 public:
15 Accumulator(int, double, double);
16 Accumulator();
17 ~Accumulator(void);
18 void Add(double);
19
20 double GetMean(void);
21 int GetN(void);
22 };
23
24 #endif
9.4. MÉTHODES ET VARIABLES STATIQUES 117
1 #include "Accumulator.h"
2
11 Accumulator::Accumulator()
12 {
13 _n = 0;
14 _xSum = 0;
15 _xSquareSum = 0;
16 accCreated++;
17 }
18
19 Accumulator::~Accumulator(void)
20 {
21 }
22
23 void Accumulator::Add(double x)
24 {
25 _n++;
26 _xSum += x;
27 _xSquareSum += x*x;
28 nTotal++;
29 }
30
31 double Accumulator::GetMean(void)
32 {
33 return _xSum / _n;
34 }
35
36 int Accumulator::GetN(void)
37 {
38 return _n;
39 }
40
41 int Accumulator::nTotal=0;
42 int Accumulator::accCreated=0;
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int _n;
9 double _xSum;
10 double _xSquareSum;
11 static int nTotal;
12 static int accCreated;
13
14 public:
15 Accumulator(int, double, double);
16 Accumulator();
17 ~Accumulator(void);
18 void Add(double);
19 static int GetAccCreatedCount(void); //new static method just created
20
21 double GetMean(void);
22 int GetN(void);
23 };
24
25 #endif
1 //In Accumulator.cpp :
2
3 int Accumulator::GetAccCreatedCount(void)
4 {
5 return accCreated;
6 }
tiques publics, ne sont pas appelés via une instance mais directement
via le nom de la classe suivi de : :. Ainsi, nous pouvons tester dans
notre main par exemple s’il existe déjà une instance ou non :
1 void main()
2 {
3 if (Accumulator::GetAccCreatedCount() >= 1)
4 {
5 // .....
6 }
7 }
9.5 Constructeur-copie
Dans la section 7.4, nous avons expliqué que si nous ne spécifions
pas qu’un argument doit être passé par référence, il est par défaut
passé par valeur. Ainsi, dans le listing 9.2, l’instance a de type A est
passée par valeur à la fonction f alors qu’elle est passée par référence
à la fonction g. Nous nous trouvons donc à l’entrée de la fonction f,
120 CHAPITRE 9. INTRODUCTION À L’OBJET : LES CLASSES
2 class A
3 {
4 public:
5 A(int c, int d);
6 ~A();
7
8 private:
9 int _c;
10 int _d;
11 }
12
13 A::A(int c, int d)
14 {
15 _c = c;
16 _d = d;
17 }
18
19 A::~A()
20 {
21 }
22
26 }
27
28 void g(A& a)
29 {
30
31 }
32
33 void main()
34 {
35 A a;
36 f(a);
37 g(a);
38 }
Listing 9.2 – passage par valeur et par référence d’un argument de type non-
primitif
A copy
f
return
main
A a
A), qui prend en argument une instance source de type A qu’il cherche
à copier, et en crée une copie, également de type A.
2 class A
3 {
4 public:
5 A(int c, int d);
6 A(const A& source);
7 ~A();
8
9 private:
10 int _c;
11 int _d;
12 }
13
14 A::A(int c, int d)
15 {
16 _c = c;
17 _d = d;
18 }
19
26 A::~A()
27 {
28 }
Listing 9.3 – Constructeur-copie de la classe A explicité
Notez bien que le constructeur-copie prend son argument par réfé-
rence (sinon il faudrait appeler le constructeur-copie avant de rentrer
dans le constructeur-copie, et il faudrait appeler le constructeur-
copie avant de rentrer dans le constructeur-copie pour rentrer dans
le constructeur-copie, et ainsi de suite.)
2 class A
3 {
4 public:
5 A(int value){_value = value;}
6 ~A();
7 void FooBar() const
8 {
9 //some code here
10 }
11
12 private:
13 int _value;
14 };
Listing 9.4 – Une fonction membre const
2 class A
3 {
4 public:
5 A(int value){_value = value;}
6 ~A();
7 void FooBar() const
8 {
9 _value = 2; // Compile−time error here
10 }
11
12 private:
13 int _value;
14 };
Listing 9.5 – Une fonction membre const qui refuse de compiler
ces champs ne seront pas modifiés, tout comme dans le cas d’une
fonction membre statique.
10 Opérateurs
10.1 Introduction
Les opérateurs sont une construction d’un langage qui se comporte
sémantiquement comme les fonctions, mais qui disposent de libertés
syntaxiques pour leur utilisation, et qui agissent sur un nombre fixe et
limité d’arguments (en général un ou deux). Les opérateurs peuvent
précéder leur argument (par exemple ++ dans i++), ils peuvent suc-
céder à leur argument (par exemple *p où p est un pointeur), ou
se retrouver entre leur deux arguments (par exemple + dans a+b).
Parmi les opérateurs les plus connus du C++, nous pouvons citer :
+, -, +=, -=, *, *=, /, /=, ->, (), ++, –, %, <, >, <=, >=, ==, !=, «, », !,
&&, ||, ?, ,, ˆ, ˜, etc.
125
126 CHAPITRE 10. OPÉRATEURS
aux deux premières instances. Dans ce but, nous pouvons écrire une
fonction MergeWith :
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int _n;
9 double _xSum;
10 double _xSquareSum;
11 static int nTotal;
12 static int accumulatorInstancesCreated;
13
14 public:
15 Accumulator(int, double, double);
16 Accumulator();
17 ~Accumulator(void);
18 void Add(double);
19 static int GetInstancesCreatedCount(void); //new static method just created
20 Accumulator MergeWith(const Accumulator&);
21
22 double GetMean(void);
23 int GetN(void);
24 };
25
26 #endif
Listing 10.1 – Accumulator.h
1 //In Accumulator.cpp :
2
1 //In main.cpp :
2
3 #include "Accumulator.h"
4
5 void main()
6 {
7 Accumulator acc1;
8 Accumulator acc2;
9 acc1.Add(2);
10 acc1.Add(3);
11
12 acc2.Add(2.3);
13
1 //In main.cpp :
2
3 #include "Accumulator.h"
4
5 void main()
6 {
7 Accumulator acc1;
8 Accumulator acc2;
9 acc1.Add(2);
10 acc1.Add(3);
11
12 acc2.Add(2.3);
13
nous écrivons :
1 //Accumulator.h
2 #ifndef ACCUMULATOR_H
3 #define ACCUMULATOR_H
4
5 class Accumulator
6 {
7 private:
8 int _n;
9 double _xSum;
10 double _xSquareSum;
11 static int nTotal;
12 static int accumulatorInstancesCreated;
13
14 public:
15 Accumulator(int, double, double);
16 Accumulator();
17 ~Accumulator(void);
18 void Add(double);
19 static int GetInstancesCreatedCount(void);
20 Accumulator operator+(const Accumulator&);
21
22 double GetMean(void);
23 int GetN(void);
24 };
25
26 #endif
Listing 10.7 – Accumulator.h
10.1. INTRODUCTION 129
1 //In Accumulator.cpp :
2
Nous donnons pour illustrer notre propos quelques cas dans les-
quels l’usage d’opérateurs est justifié. La plupart de ces classes
n’ayant pas encore été introduites, vous pourrez y revenir en temps
voulu.
matique, en l’occurence l’implémentation (partielle) d’une classe
Complex.
Il est fondamental en informatique de ne pas réinventer la roue.
Le problème auquel vous devez faire face a probablement déjà été
rencontré et résolu par de nombreuses personnes avant vous. Outre
le fait qu’elles sont probablement plus compétentes que vous, le code
qu’elles ont produit a été éprouvé par le temps et l’usage, et il sera a
priori moins sujet aux bugs que vous ne manquerez pas de rencontrer
si vous optez pour une solution que vous développerez vous-même.
En conséquence, considérez toujours d’abord de réutiliser du
code 1 plutôt que d’en écrire. Dans notre cas, il est évident qu’il
ne faut jamais réimplémenter une classe Complex, nous en donnons
juste une implémentation partielle à titre pédagogique.
Notez que certains opérateurs sont implémentés comme des fonc-
tions membres, alors que d’autres non. Celà respecte les suggestions
fournies dans le paragraphe 10.5.
1. Ce qui ne vous dédouane pas de le tester bien sûr, par exemple unitairement.
10.3. UN AUTRE EXEMPLE 131
1 #ifndef COMPLEX_H
2 #define COMPLEX_H
3
4 class Complex
5 {
6 public:
7 Complex(double,double);
8 ~Complex(void);
9 void operator += (const Complex&);
10 void operator -= (const Complex&);
11
12 private:
13 double _real;
14 double _im;
15 };
16
17 //Non−Member functions
18 Complex operator + (const double&, const Complex&);
19 Complex operator + (const Complex&, const double&);
20 Complex operator + (const Complex&, const Complex&);
21
26 #endif
Listing 10.9 – Complex.h
132 CHAPITRE 10. OPÉRATEURS
1 #include "Complex.h"
2
9 Complex::~Complex(void)
10 {
11 }
12
1 #include "Complex.h"
2 void main()
3 {
4 Complex c(1,Pi/2);
5 }
Listing 10.11 – main.cpp
1 #ifndef COMPLEX_H
2 #define COMPLEX_H
3
4 class Complex
5 {
6 public:
7 static Complex FromCartesian(double, double);
8 static Complex FromPolar(double,double);
9 ~Complex(void);
10 void operator += (const Complex&);
11 void operator += (const double&);
12 void operator -= (const Complex&);
13 void operator -= (const double&);
14
15 private:
16 double _real;
17 double _im;
18 Complex(double,double);
19 };
20
21 //Non−Member functions
22 Complex operator + (const double&, const Complex&);
23 Complex operator + (const Complex&, const double&);
24 Complex operator + (const Complex&, const Complex&);
25
30 #endif
Listing 10.12 – Complex.h
10.3. UN AUTRE EXEMPLE 135
1 #include "Complex.h"
2 #include <cmath>
3
20 Complex::~Complex(void)
21 {
22 }
23
int b
main
int a
1 #include "Complex.h"
2 void main()
3 {
4 Complex c1 = Complex::FromPolar(1,Pi/2);
5 Complex c2 = Complex::FromCartesian(0,1);
6 }
Listing 10.14 – main.cpp
1 void main()
2 {
3 int a = 3;
4 int b = 2;
5 b=a;
6 }
Listing 10.15 – affectation d’un entier par un autre
10.4. SURCHARGE DE L’OPÉRATEUR D’AFFECTATION 137
Complex c2
main
Complex c1
Lorsque nous voulons écrire la même chose pour des Complex, que
se passe-t-il ?
1 #include "Complex.h"
2 void main()
3 {
4 Complex c1 = Complex::FromCartesian(1,0);
5 Complex c2 = Complex::FromCartesian(0,1);
6 c1=c2;
7 }
Listing 10.16 – affectation d’un Complex par un autre
11 Les templates
141
142 CHAPITRE 11. LES TEMPLATES
1 template<typename T>
2 T Max(T array[], int length)
3 {
4 T vmax = array[0];
5 for (int i = 1; i < length; i++)
6 if (array[i] > vmax)
7 vmax = array[i];
8 return vmax;
9 }
1. Une règle importante du développement pourrait être : "ne faites pas de copier/coller
de code au sein d’un projet, sauf éventuellement si celà vous évite de devoir inclure des
dépendances supplémentaires."
11.1. TEMPLATING PAR UN TYPE, L’EXEMPLE DES FONCTIONS 143
Lorsque dans notre code, nous voulons utiliser notre fonction Max,
nous pouvons le faire de la sorte :
2. En réalité, les deux mots clefs ne sont parfaitement substituables l’un à l’autre que dans
les cas simples qui nous intéressent. Pour en savoir plus, nous renvoyons le lecteur par exemple
à [17].
144 CHAPITRE 11. LES TEMPLATES
1 template<typename T>
2 const T & Max( const T & a, const T & b )
3 {
4 return a > b ? a : b;
5 }
1 template<typename T>
2 inline const T & Max( const T & a, const T & b )
3 {
4 return a > b ? a : b;
5 }
1 class SomeClass
2 {
3 public SomeClass();
4 public ~SomeClass();
5
6 template<typename T>
7 static void Display( const T & t )
8 {
9 cout << t;
10 }
11 }
12
13 int main()
14 {
15 SomeClass.Display<int>(2);
16 SomeClass.Display<string>("Hello World");
17 SomeClass.Display<double>(3.14);
18 }
1 int main()
2 {
3 SomeClass.Display(2);
4 SomeClass.Display("Hello World");
5 SomeClass.Display(3.14);
6 }
Il est cependant des cas où une telle inférence n’est pas possible,
notamment dans le cas d’ambiguïté que le compilateur ne peut pas
lever lui-même. Ainsi, la fonction suivante doit être spécifiée explici-
tement :
146 CHAPITRE 11. LES TEMPLATES
7 int main()
8 {
9 int s2 = 1;
10 double s1 = 3.2;
11
11.1.4 Multi-templating
Il est possible de paramétrer une fonction par plusieurs arguments ;
en voici un exemple :
1 // exemple.h
2
3 #ifndef EXEMPLE_H
4 #define EXEMPLE_H
5
16 // exemple.tpp
17
2 template<typename T>
3 double TwoPow(T y)
4 {
5 return exp(y*ln(2);
6 }
13 return r;
14 }
7 template<typename T>
8 public double Pow<T,int>(T x, int y)
9 {
10 double q= (y >= 0) ? x : ((double)1)/x; //do not forget the explicit cast
into double !
11 int yAbs = abs(y);
12 double r=1;
13
17 return r;
18 }
You rarely read fairy tales about the heroes who are smart enough to just avoid the whole situation
entirely. You never hear a hero say, "Wait a minute, if I leave to make my fortunes on the high seas lea-
ving Buttercup behind I could die and then she’d have to marry some ugly prince named Humperdink.
Humperdink ! I think I’ll stay here and start a Farm Boy for Rent business." If he did that there’d be
12
no fire swamp, dying, reanimation, sword fights, giants, or any kind of story really. Because of this,
the forest in these stories seems to exist like a black hole that drags the hero in no matter what they do.
http ://learnpythonthehardway.org/book/ex44.html
12.1.1 Motivation
Par les vicissitudes d’une faille spatio-temporelle, vous vous re-
trouvez téléporté dans la peau d’un développeur pour une société de
location de vélos et de voitures. Votre premier jet met en place les
classes Bike et Car décrites dans les listings 12.1 et 12.2.
151
152 CHAPITRE 12. HÉRITAGE ET COMPOSITION
1 #ifndef CAR_H
2 #define CAR_H
3
4 class Car
5 {
6 public :
7 Car(int state, int fuel, int color, int price);
8 ~Car();
9
19 private:
20 int _state; //is it second hand or new?
21 int _fuel; // fuel remaining
22 int _color;
23 int _price;
24 };
25
26 #endif
Listing 12.1 – Car1.h
12.1. HÉRITAGE SIMPLE 153
1 #ifndef BIKE_H
2 #define BIKE_H
3
4 class Bike
5 {
6 public :
7 Bike(int state, int color, int price);
8 ~Bike();
9
17 private:
18 int _state; //is it second hand or new?
19 int _color;
20 int _price;
21 };
22
23 #endif
Listing 12.2 – Bike1.h
1 #ifndef VEHICULE_H
2 #define VEHICULE_H
3
4 class Vehicule
5 {
6 public :
7 Vehicule(int state = 1,
8 int color = 0,
9 int price = 10000);
10 ~Vehicule();
11
19 private:
20 int _state; //new?
21 int _color;
22 int _price;
23
24 };
25
26 #endif
Listing 12.3 – vehicule2.h
1 #ifndef CAR_H
2 #define CAR_H
3 #include "Vehicule2.h"
4
14 private:
15 int _fuel;
16 };
17
18 #endif
Listing 12.4 – voiture2.h
12.1. HÉRITAGE SIMPLE 155
1 #ifndef BIKE_H
2 #define BIKE_H
3 #include "vehicule2.h"
4
11 bool GetIsPadlocked();
12 void SetIsPadlocked(bool isPadlocked);
13
14 private:
15 bool _isPadlocked;
16 };
17 #endif
Listing 12.5 – velo2.h
1 /∗premiere possibilite ∗/
2 class Fille : public class Mere
3 {
4 /∗nouveaux membres∗/
5 };
6 /∗seconde possibilite ∗/
7 class Fille : protected class Mere
8 {
9 /∗nouveaux membres∗/
10 };
11 /∗troisieme possibilite ∗/
12 class Fille : private class Mere
13 {
14 /∗nouveaux membres∗/
15 };
1 Bike b;
2
1 void Car::DisplayCost(void)
2 {
3 cout << "Cost of this car is : " << _price << " euros.\n"
4 }
Pour que nos classes dérivées aient accès au membre _price, nous
158 CHAPITRE 12. HÉRITAGE ET COMPOSITION
1 #ifndef VEHICULE_H
2 #define VEHICULE_H
3
4 class Vehicule
5 {
6 public :
7 Vehicule(int state = 1,
8 int color = 0,
9 int price = 10000);
10 ~Vehicule();
11
19 protected:
20 int _state; //new?
21 int _color;
22 int _price;
23
24 };
25
26 #endif
Listing 12.6 – vehicule3.h
1 #ifndef MOTHER_H
2 #define MOTHER_H
3
4 class Mother
5 {
6 public :
7 Mother();
8 Mother(int);
9 ~Mother();
10
11 private:
12 int _id;
13 }
14
15 #endif
Listing 12.7 – Mother.h
1 #include "Mother.h"
2 #include <iostream>
3 using namespace std;
4
5 Mother::Mother()
6 {
7 cout << "Default mother constructor called \n";
8 }
9
10 Mother::Mother(int id)
11 {
12 _id = id;
13 cout << "1-argument mother constructor called \n";
14 }
15
16 Mother::~Mother()
17 {
18 cout << "Mother destructor called \n";
19 }
Listing 12.8 – Mother.cpp
1 #ifndef CHILD_H
2 #define CHILD_H
3
11 #endif
Listing 12.9 – Child.h
1 #include "Child.h"
2 #include <iostream>
3 using namespace std;
4
5 Child::Child()
6 {
7 cout << "Child empty constructor called \n";
8 }
9
10 Child::~Child()
11 {
12 cout << "Child destructor called \n";
13 }
Listing 12.10 – Child.cpp
1 #include "Child.h"
2
3 void f()
4 {
5 Child c;
6 }
7
8 void main()
9 {
10 f();
11 }
1 #include "Child.h"
2 #include <iostream>
3 using namespace std;
4
10 Child::~Child()
11 {
12 cout << "Child destructor called \n";
13 }
Listing 12.11 – Child2.cpp
Si vous ne spécifiez pas explicitement le constructeur mère à ap-
peler lorsque vous construisez une fille, nous avons vu que c’était
le constructeur mère par défaut qui était appelé. Dans un tel cas,
et si le constructeur mère par défaut n’existe pas (par exemple si
vous avez déclaré un seul constructeur dans Mother.h qui prend en
argument des paramètres), alors votre IDE échouera à compiler, vous
spécifiant un message de la sorte :
162 CHAPITRE 12. HÉRITAGE ET COMPOSITION
1 class Mother
2 {
3 public:
4 int x;
5 protected:
6 int y;
7 private:
8 int z;
9 };
10
Principe
L’idée est assez naturelle : nous allons faire hériter notre classe
dérivée de deux classes mères. D’un point de vue syntaxique, on
écrira :
1 class Child :
2 public|protected|private class Mother1,
3 public|protected|private class Mother2,
4 . . .
5 public|protected|private class MotherN
6 {
7 }
11 private:
12 int _maxAltitude;
13 }
Listing 12.12 – Véhicule volant
Vehicle
Car FlyingVehicle
FlyingCar
1 void VehiculeVolant::DisplayColor()
2 {
3 cout << "The color of our new brand flying car is " << \_color << ".\n"
;
4 }
Héritage virtuel
1 #include "vehicule4.h"
2 class FlyingVehicle : public virtual Vehicle
3 {
4 public:
5 FlyingVehicle();
6 ~FlyingVehicle();
7
11 private:
12 int _maxAltitude;
13 }
Listing 12.14 – vehiculeVolant4.h
1 #include "vehicule5.h"
2
13 private:
14 int _fuel;
15 };
Listing 12.15 – voiture5.h
12.3. COMPOSITION ET AGGRÉGATION 167
1 class A
2 {
3 public:
4 A();
5 ~A();
6 };
7
8 class B
9 {
10 public:
11 B(A a);
12 ~B();
13
14 private:
15 A _a;
16 };
17
18 B::B(A a)
19 {
20 _a = a;
21 }
lorsque l’objet possédant (B) est détruit, les objets possédés (_a) sont
détruits simultanément.
12.3.2 Aggrégation
Un cas un peu plus subtil apparaît quand nous ne voulons pas de
la notion de possession décrite dans le cas de la composition. Nous
pouvons alors dans notre classe B posséder un pointeur sur A, plutôt
qu’une instance de la classe A.
1 class A
2 {
3 public:
4 A();
5 ~A();
6 };
7
8 class B
9 {
10 public:
11 B(A* pa);
12 ~B();
13
14 private:
15 A* _pa;
16 };
17
18 B::B(A* pa)
19 {
20 _pa = pa;
21 }
1 class A
2 {
3 public:
4 A(A* pa);
5 ~A();
6
7 private:
8 A* _inner;
9 };
10
11 A::A(A* pa)
12 {
13 _inner = pa;
14 }
1 class FbUser
2 {
3 public:
4 FbUser(string name, int id, string status, FbUser* pa);
5 ~FbUser();
6
7 private:
8 FbUser* _mate;
9 string _name;
10 string _status;
11 int _id;
12 };
1 class Node
2 {
3 public:
4 Node(Node* left, Node* right);
5 ~Node();
6
7 private:
8 Node* _left;
9 Node* _right;
10 };
1 class B;
2
3 class A
4 {
5 B* PtrB;
6 };
Listing 12.16 – A.h
1 #include "A.h"
2 #include "B.h"
3
4 // ...
Listing 12.17 – A.cpp
1 #include "A.h"
2
3 class B
4 {
5 A a;
6 };
Listing 12.18 – B.h
1 #include "B.h"
2
3 // ...
Listing 12.19 – B.cpp
172 CHAPITRE 12. HÉRITAGE ET COMPOSITION
"When I see a bird that walks like a duck and swims like a duck and quacks
like a duck, I call that bird a duck." James Whitcomb Riley.
"One issue with duck typing is that it forces the programmer to have a much
wider understanding of the code he or she is working with at any given time.
In a strongly and statically typed language that uses type hierarchies and
parameter type checking, it’s much harder to supply an unexpected object type
to a class. For instance, in Python, you could easily create a class called Wine,
which expects a class implementing the "press" attribute as an ingredient.
However, a class called Trousers might also implement the press() method.
13
With Duck Typing, in order to prevent strange, hard-to-detect errors, the
developer needs to be aware of each potential use of the method "press", even
when it’s conceptually unrelated to what he or she is working on. In essence,
the problem is that, "if it walks like a duck and quacks like a duck", it could
be a dragon doing a duck impersonation. You may not always want to let
dragons into a pond, even if they can impersonate a duck."
http ://en.wikipedia.org/wiki/Duck_typing
Polymorphisme
173
174 CHAPITRE 13. POLYMORPHISME
1 void main(void)
2 {
3 A a;
4 B b;
5 C c;
6 f(a);
7 f(b);
8 f(c);
9 }
10
Nous devons donc établir un procédé pour que ce code puisse être
appliqué à toute classe possédant la méthode Display, mais que ce
code ne soit écrit qu’une seule fois, c’est à dire que nous voulons
factoriser une partie du code, pour isoler la partie de la logique
commune à chaque classe.
il est donc possible d’écrire une classe Base possédant une méthode
Display. Nous pouvons alors faire hériter publiquement les classes
A, B, et C de la classe Base. Ce faisant, chaque instance de type A,
B ou C possédera une méthode Display (héritée de la classe Base),
alors que le code n’a été écrit qu’une seule fois : nous venons de
factoriser du code.
1 void main(void)
2 {
3 A a;
4 B b;
5 C c;
6 f(a);
7 f(b);
8 f(c);
9 }
10
1 void Base::Display()
2 {
3 cout << "I’m a Base instance. \n";
4 }
5
6 void A::Display()
7 {
8 cout << "I’m a A instance. \n";
9 }
10
11 void B::Display()
12 {
13 cout << "I’m a B instance. \n";
14 }
13.1.4 Virtualité
1 class Base
2 {
3 public Base();
4 public ~Base();
5 public virtual void Display(void);
6 };
7
Nous obtenons alors par l’appel successif proposé dans les exemples
précédents :
1 I’m a A instance.
2 I’m a B instance.
3 I’m a Base instance.
1 class Wookie
2 {
3 public Wookie();
4 public void Display(void);
5 };
6
7 class MilleniumFalcon
8 {
9 public MilleniumFalcon();
10 public void Display(void);
11 };
1 void f(A a)
2 {
3 }
Indirection
Inlining
1. Dans le cas d’un pricer, on peut souvent lire dans des projets
débutants la création d’une classe BaseOption possédant une
méthode Payoff virtuelle pure, et une dizaine de classes héritant
de BaseOption (EuropeanCall, AsiaticPut, LookBack, Barrier,
...) et implémentant chacune le payoff correspondant. Par l’usage
de la virtualité, vous vous privez de la possibilité d’inliner
ces méthodes payoff. Le gain à passer par du polymorphisme
statique et de l’inlining est ici très conséquent, mais ne doit
être mis en place dans un véritable projet que si contrainte de
performance il y a.
2. Dans le cas d’algorithmes de datamining, comme un programme
de recherche de proches voisins (KNN) dans un jeu de données
dans RD , nous pouvons être tentés d’utiliser de la virtualité
pour manipuler différentes métriques : une classe BaseMetric,
abstraite, et différentes classes en héritant et implémentant des
métriques L∞ , L1 ,L2 ... Là encore, dans beaucoup d’algorithmes
ces calculs de métriques vont être utilisés de très nombreuses
fois, pour représenter une charge importante des calculs. La
virtualité est ici à proscrire. 3
7 class B : public A
8 {
9 public B();
10 public ~B();
11 };
12
13 void main()
14 {
15 B* pB = new B();
16 delete pB; //Le destructeur de A est appelé, en lieu et place du destructeur
de B, car le destructeur de A n’a pas été marqué comme virtuel
17 }
13.2.2 Implémentation
8 class A
9 {
10 public void Display(){};
11 };
12
13 class B
14 {
15 public void Display(){};
16 };
17
18 class C
19 {
20 public void AnotherMethod(){};
21 };
22
23 int main()
24 {
25 A a;
26 B b;
27 C c;
28
Gestion de la mémoire
187
—C makes it easy to shoot yourself in the foot ; C++ makes it harder, but when you do it
blows your whole leg off.
Bjarne Stroustrup
14
Allocation dynamique
14.1 Motivation
Comme nous l’avons vu dans la section 7.3, lorsque l’exécution
de notre code entre dans une nouvelle fonction, une zone mémoire
dédiée, appellée "Stack Frame", est ajoutée à la Stack lorsque nous
entrons dans la fonction. Symétriquement, cette zone mémoire est
automatiquement détruite lorsque nous terminons la fonction.
189
190 CHAPITRE 14. ALLOCATION DYNAMIQUE
1 class A
2 {
3 public:
4 A();
5 ~A();
6 };
7
8 void f()
9 {
10 A* pa = new A();
11 }
12
13 void main()
14 {
15 int b = 3;
16 f();
17 }
Listing 14.1 – Exemple d’ appel à new
A* pa
f
return A
main
int b
1 class A
2 {
3 public:
4 A(int i);
5 ~A();
6 void Display();
7 };
8
9 void f()
10 {
11 int n = 8;
12 A* pa = new A(n);
13 }
14
15 void main()
16 {
17 int b = 3;
18 f();
19 }
Listing 14.2 – Exemple d’ appel à new en donnant un argument au constructeur-
copie
192 CHAPITRE 14. ALLOCATION DYNAMIQUE
1 void f()
2 {
3 int n = 8;
4 A* pa = new A(n);
5 delete pa;
6 }
Listing 14.3 – Exemple d’ appel à delete
Cet exemple est bien entendu idiot, puisque nous désallouons ici la
mémoire dans la même fonction que nous l’allouons, ce qui supprime
tout l’intérêt d’allouer dans la Heap plutôt que dans la Stack.
1 void f()
2 {
3 int n = 8;
4 A* pa = new A(n);
5 delete pa;
6 delete pa;
7 }
Listing 14.4 – Exemple d’ appel à new
194 CHAPITRE 14. ALLOCATION DYNAMIQUE
Figure 14.2 – Screenshot d’une fenêtre d’erreur générée par une Segmentation
Fault.
1 class A
2 {
3 public:
4 A(void);
5 ~A(void);
6 };
7
8 class B
9 {
10 public:
11 B(void);
12 ~B(void);
13
14 private:
15 A* _pa;
16 };
17
18 B::B(void)
19 {
20 _pa = new A();
21 }
22
23
24 B::~B(void)
25 {
26 delete _pa;
27 }
Listing 14.5 – Un cas classique de SegFault
B copy
f
A* _pa A
main
B b
A* _pa
5 }
6
7 void main()
8 {
9 B b;
10 f(b);
11 }
Listing 14.6 – Un cas classique de SegFault
1 void f()
2 {
3 int n = 8;
4 A* pa = new A(n);
5 delete pa;
6
7 pa->Display();
8 }
Listing 14.7 – Exemple d’ appel à new
9 ~SmartPointer(){}
10
11 T& operator* ()
12 {
13 return *_pInner;
14 }
15
16 T* operator-> ()
17 {
18 return _pInner;
19 }
20
21 private:
22 T* _pInner; // Generic pointer to be stored
23 };
Listing 14.8 – Première implémentation d’un SmartPointer
9 ~SmartPointer()
10 {
11 delete _pInner;
12 }
13
14 T& operator* ()
15 {
16 return *_pInner;
17 }
18
19 T* operator-> ()
20 {
21 return _pInner;
22 }
23
24 private:
25 T* _pInner; // Generic pointer to be stored
26 };
Listing 14.9 – Seconde implémentation d’un SmartPointer
6 void Decrement()
7 {
8 _count--;
9 }
10
11 void Increment()
12 {
13 _count++;
14 }
15
16 int GetValue()
17 {
18 return _count;
19 }
20
21 Counter()
22 {
23 _count = 1;
24 }
25
26 private :
27 int _count;
28 }
Listing 14.10 – Implémentation d’un SmartPointer avec un Reference Counting
(Partie1)
14.4. SMART POINTERS 201
11 ~SmartPointer()
12 {
13 _pCount->Decrement();
14 if ( 0 == _pCount->GetValue())
15 {
16 delete _pInner;
17 delete _pCount;
18 }
19 }
20
49 T& operator* ()
50 {
51 return *_pInner;
52 }
53
54 T* operator-> ()
55 {
56 return _pInner;
57 }
58
59 private:
60 T* _pInner;
61 Count* _pCount;
62 };
Listing 14.11 – Implémentation d’un SmartPointer avec un Reference Counting
202 CHAPITRE 14. ALLOCATION DYNAMIQUE
1 a=a;
1 #include <iostream.h>
2
3 int main()
4 {
5 int n;
6
12 return 0;
13 }
Listing 14.12 – Tableau de taille variable
De la même manière que nous avons utilisé le mot clef new pour
allouer dynamiquement une instance sur la Heap, nous pouvons uti-
liser ce même mot clef suivi d’un type et d’un nombre entre crochets
pour allouer à l’exécution sur la Heap un tableau d’un type donné.
Dans le cas de la déclaration dynamique d’un tableau, l’instruc-
tion new renvoie un pointeur vers le début du tableau, c’est à dire
vers son premier élement. La syntaxe (dans notre cas) est la suivante :
1 #include <iostream.h>
2
4 int main()
5 {
6 int n, i;
7 int* someArray = NULL;
8
1 someArray[i]=30;
1 *(someArray + i)=30;
Case mémoire 0 1 2 3 4 5 6 7 8 9
Nom someArray mémoire réservée pour le tableau
Valeur case mémoire 4
Case mémoire 0 1 2 3 4 5 6 7 8 9
Nom someArray mémoire réservée pour tableau
Valeur Case mémoire 4
Case mémoire 0 1 2 3 4 5 6 7 8 9
Nom someArray mémoire réservée pour tableau
Valeur Case mémoire 4 37
Case mémoire 0 1 2 3 4 5 6 7 8 9
Nom someArray mémoire réservée pour tableau
Valeur Case mémoire 4 42
C’est une des raisons pour lesquelles les tableaux en C++ com-
mencent à l’indice 0 et non 1 4 .
Une subtilité s’est cependant glissée ici, qui a été passée sous
silence pour des raisons de simplicité. En effet, suivant son type, une
variable n’occupe pas le même nombre de cases mémoires : un int
va généralement 5 occuper 4 cases, un char 1 case, et un double 8
cases :
Case mémoire 0 1 2 3 4 5 6 7 8 9 10 11 12
Nom char c int n double d
Valeur
1 *(someArray + 2) = 42;
Comme dans la section 14.4, nous présentons ici les enjeux stra-
tégiques et d’implémentation d’une classe standard de gestion de
mémoire, la classe vector de la STL. Cette classe permet de mani-
puler des tableaux dont la taille n’est pas connue à la compilation.
Plutôt que d’en donner le code exact, nous en construisons une
version simplifiée.
1 class Vector
2 {
3 public :
4 Vector(unsigned int n); //constructor
5 ~Vector() //destructor
6
10 private:
11 // properties
12 unsigned int _length;
13 double* _data;
14 }
Listing 14.15 – Vecteur.h
208 CHAPITRE 14. ALLOCATION DYNAMIQUE
1 \#include "Vector.h"
2
9 void Vector::~Vector()
10 {
11 _length=0;
12 delete[] _data; //Don’t forget to use delete [] and not delete for arrays
13 }
14
1 u’._data = u._data
2 u’._length = u._length
La norme C++ spécifie que si un constructeur est redéfini par
l’utilisateur (plutôt que d’utiliser la version par défaut), plus aucun
constructeur (copie ou non) n’est généré par défaut. Visual Studio
s’est affranchi de cette spécification, afin de simplifier la vie des
développeurs débutants, et le constructeur copie est généré automa-
tiquement, même si le constructeur de notre classe Vecteur a été
redéfini.
et au fichier source :
1 Vector::Vector(const Vector& v)
2 {
3 _length = v._length;
4
1 v1 = v2= v3;
Listing 14.20 – double affectation
C’est pour cette raison que nous donnons comme type de retour
un vecteur. Cette valeur de retour est passée en référence, pour
limiter le nombre de copies créées.
11 return (*this);
12 }
Listing 14.21 – Vector.cpp
1 int main()
2 {
3 Vecteur v;
4
5 v = v;
6 return 0;
7 }
1 _data[ i ] = v._data[ i ];
14.6. LA CLASSE VECTOR 211
7 _length = v._length;
8 if (_data != NULL)
9 delete[] _data;
10
15 return (*this);
16 }
Listing 14.22 – Vector.cpp
1 class Vector
2 {
3 public:
4 Vector(unsigned int n);
5 ~Vector();
6
7 /∗assignment operator∗/
8 Vector& operator=(const Vector &v);
9 /∗Copy−Constructor∗/
10 Vector(const Vector &v);
11
12 /∗operateur [] ∗/
13 double & operator[](unsigned int i);
14
17 private:
18 unsigned int _length;
19 double* _data;
20 };
Listing 14.23 – vecteur4.h
14.6. LA CLASSE VECTOR 213
1 #include "vector4.h"
2
8 Vector::~Vector()
9 {
10 delete[] _data;
11 }
12
18
24 _length = v._length;
25 if (_data != 0)
26 delete[] _data;
27
32 return (*this);
33 }
34
39 if (_data != 0)
40 delete[] _data;
41
1 #include "Vector.h"
2 #include <iostream>
3 using namespace std;
4
15 return total;
16 }
17
18
19 int main()
20 {
21 Vector v1(5), v2(6);
22
31 return 0;
32 }
Listing 14.25 – Affection et passage par valeur
7. Ceci est de plus en plus discutable, cf les derniers benchmarks http ://shoo-
tout.alioth.debian.org/ ; il faut de tout manière être très prudent lorsque l’on parle de perfor-
mances
216 CHAPITRE 14. ALLOCATION DYNAMIQUE
Quatrième partie
D’autres considérations
217
— I think [making computer languages easier for
average people] would be misguided. The idea of
programming as a semiskilled task, practiced by
people with a few months’ training, is dangerous.
We wouldn’t tolerate plumbers or accountants that
poorly educated. We don’t have as an aim that ar-
chitecture (of buildings) and engineering (of bridges
and trains) should become more accessible to people
with progressively less training. Indeed, one serious
problem is that currently, too many software deve-
15
lopers are undereducated and undertrained.
Bjarne Stroustrup
219
220 CHAPITRE 15. POUR ALLER PLUS LOIN
Annexes
223
Le point de vue de Linus Torvald sur le
C++
225
226
dels around it, and you cannot fix it without rewriting your app.
So I’m sorry, but for something like git, where efficiency was a
primary objective, the "advantages" of C++ is just a huge mistake.
The fact that we also piss off people who cannot see that is just a
big additional advantage.
Linus
Listings
227
228 LISTINGS
231
232 BIBLIOGRAPHIE