Vous êtes sur la page 1sur 148

C++ pour débutants

Guide étape par étape à la programmation C++


De basique à avancé
© Copyright 2017 - All rights reserved

Si vous souhaitez partager ce livre avec une autre personne, veuillez en acheter
une copie supplémentaire pour chaque destinataire. Merci de respecter le travail
acharné de cet auteur. Dans le cas contraire, la transmission, la duplication ou la
reproduction de l'un ou l'autre des travaux suivants, y compris des
renseignements spécifiques, seront considérés comme un acte illégal, qu'ils
soient effectués par voie électronique ou écrit. Cela s'applique à la création
d'une copie secondaire ou tertiaire de l'œuvre ou d'une copie enregistrée qui
n'est autorisée qu'avec le consentement écrit exprès de l'éditeur. Tout droit
supplémentaire réservé.
TABLE DES MATIÈRES

CHAPITRE 1 : INTRODUCTION ET INSTALLATION


L’histoire de C++
Lancer C++ sous Windows
Lancer C++ sous Mac et Linux
Lancer C++ en ligne
Pré-requis
CHAPITRE 2 : BASIQUES
Types et variables
Boolean
Void
Conditionnelles
EXERCICE
Itération
Fonctions
QUIZ DE FIN DE CHAPITRE
CHAPITRE 3 : STRUCTURE DE CLASSES ET POO
Héritage
EXERCICE
Encapsulation
Interfaces
QUIZ DE FIN DE CHAPITRE
CHAPITRE 4 : TECHNIQUES AMÉLIORÉES
Structures
Enums
Unions
EXERCICE
QUIZ DE FIN DE CHAPITRE
CHAPITRE 5 : OPTIONS AVANCÉES
Fichier I/O
Récursion
Exercice
Mémoire dynamique et pointeurs
QUIZ DE FIN DE CHAPITRE
CHAPITRE 1 : INTRODUCTION ET
INSTALLATION
L’histoire de C++
C++ est un langage de programmation à usage général. Son but étant d’offrir
une plateforme permettant de créer des applications efficaces et rudimentaires.
C++ sort en 1983 et est lancé en 1979 par Bjarne Stroustrup sous le nom de « C
with classes », renommé plus tard C++.

Le langage est maintenu à jour régulièrement à travers les années, et reste un


des langages de programmation les plus connus au monde.
Lancer C++ sous Windows
Un des meilleurs moyens de lancer C++ sous Windows est l’utilisation du
Studio Visuel, pouvant être téléchargé dans sa version la plus récente
directement depuis Microsoft.

Téléchargez-le puis lancez l’installateur, à travers lequel vous verrez ce menu :


assurez-vous que C++ est sélectionné.

Complétez l’installation et lancez Studio Visuel.

Afin de créer un projet, vous aurez besoin de sélectionner Fichier / Nouveau


Projet, comme ceci :
Vous verrez ensuite apparaître cette fenêtre :

Suivez les instructions à l’écran et vous serez prêts à lancer le code.


Lancer C++ sous Mac et Linux
Lancer C++ sous des environnements UNIX peut fait en utilisant le clang ou
« gcc », qui peut être utilisé comme suit dans le terminal OS :

gcc main.cpp -o main.out

Le fichier peut maintenant être lancé :

./main.out
Vous pouvez créez un fichier .cpp dans le bloc note de votre choix.
Lancer C++ en ligne
La manière la plus simple d’utiliser C++ en ligne est d’utiliser
« TutorialPoints » C++ Online, comme ci dessous :

https://www.tutorialspoint.com/compile_cpp11_online.php
Cela devrait ressembler à ça :
Pré-requis
Un certain nombre de choses doivent être éclaircies avant de commencer avec
les basiques.

La première étant la conception du programme de base :

#include "stdafx.h"

using namespace std;

int main()
{
return 0;
}
Le programme ci dessus est le modèle de base proposé, le code s’insère entre
les parenthèses () de la section principale ; chaque aspect sera décrit dans les
sections suivantes.

Il arrivera également que certaines lignes commencent par « // », qui


correspondent aux commentaires. Leur seul but étant d’ajouter des descriptions
afin de catégoriser et expliquer le code, ils sont définis par leur couleur verte :
//Comment

Ensuite, sorti d’un programme, vous pourrez voir :

cout << "Print" << endl;

Ceci imprime simplement des données sur l’écran ; nous y reviendrons.


CHAPITRE 2 : BASIQUES
Types et variables
Les bases d’un programme sont les données, sous la forme de nombres et de
caractères. Le stockage, la manipulation et la sortie de ces données donne la
fonctionnalité. Les variables retiennent ces données, pensez-y comme des boîtes
utilisées pour stocker individuellement des objets.

C++ est un langage lourdement caractérisé, dans lequel chaque variable doit
avoir un type défini. Un type est un identifiant sous la forme d’un mot-clé qui
définis ce que la variable peut contenir. Le premier type auquel nous allons
nous confronter est le nombre entier (ou integer), pouvant contenir seulement
des nombres réels (sans décimales), sa définition étant :

int value = 6;

- int est le mot-clé défini (nous en apprendrons les différentes possibilités


plus tard)
- value est l’identifiant de la variable, cela peut-être tout ce que vous
voulez, et il permet à cette dernière d’avoir un nom significatif. Cela
pourrait être « int ananas = 3; », mais il est plus sage de le rendre pertinent.
Toutefois, il existe quelques exceptions à cela, puisqu’une variable ne peut
pas être un simple chiffre (comme « 4 ») et ne peut contenir de caractères
spéciaux (! »£$%^&*).
- « =6; » désigne la section d’affectation, dans laquelle la valeur 3 est
placée dans la boîte integer pour un usage ultérieur. Ce passage termine
également avec un point virgule, signifiant la fin d’une ligne.

Cette variable peut désormais être utilisée dans les zones valides du
programme, comme ceci :
int anotherValue = value;

La valeur de « value », définie précédemment, sera désormais placée dans la


variable anotherValue et elles auront toutes les deux la valeur de 3. La valeur
de value ne change pas, puisqu’elle est juste copiée et placée dans
anotherValue.

Chaîne (String)
La chaîne est un autre type de variable crucial, utilisé afin de stocker une série
de caractères, comme par exemple « batman » ; le mot est composé de
caractères stockés dans la variable Chaîne. Il s’agit d’un tableau de caractères
(nous reviendrons plus tard sur les tableaux), mais dans les faits ce sont les
caractères seuls du mot stockés les uns à côté des autres dans la mémoire. Le
« \n » est un caractère spécial définissant une nouvelle ligne.

#include "stdafx.h"
#include <string>

using namespace std;

int main()
{
//String class
string wordStr = "";

//Character array
char wordCharArray[] = "";
return 0;
}
Comme vous pouvez le voir ci dessus il existe deux façons de sauvegarder une
chaîne/string. La première étant d’utiliser un « wrapper », qui vise à cacher la
fonctionnalité et ajouter des extras, permettant une navigation plus facile autour
d’un string (nous reviendrons sur la navigation autour des variables). La
seconde manière étant d’utiliser un tableau de caractères, string étant le moyen
de stockage des variables string recommandé.

Notez l’utilisation de :

#include <string>

Ceci informe le programme que vous souhaitez utiliser le « string » wrapper.


Boolean
Les Boolean sont utilisés comme des expressions, leurs valeurs ne peuvent être
que vrai ou faux. Ils sont utilisés pour signifier certains états ou marquer
certains évènements, et peuvent aussi être le résultat d’une expression
conditionnelle (sur lesquelles nous reviendrons). Tout ceci prendra plus de sens
lorsque nous parlerons des postulats conditionnels.

bool True = true;


bool False = false;
Flottant/Double
Les variables à point flottant sont des valeurs décimales créées avec un des
mathématiques de précision et les éléments techniques permettant de
comprendre comment elles fonctionnent sont hors du spectre de connaissances
de ce tutoriel, mais peuvent être rapidement expliqués à travers des ressources
numériques, en cherchant simplement « floating point precision » sur internet.
Le flottant permet une plus grande précision de la valeur, puisqu’elle est
décimale. Vous pouvez spécifier un flottant en ajoutant un « f » à la fin d’une
valeur.

float decimalValue = 3.0f;


Void
Ce type de données est assez particulier et utilisé pour spécifier qu’aucune
valeur n’est disponible. Cela sonne peut-être étrange, mais nous verrons plus
tard l’étendue de son utilité.

Mots-clés notables/Termes
const - Ce mot-clé rends la variable lisible seulement, c’est à dire que sa valeur
ne pourra être changée après initialisation. Le mot-clé est utilisé comme cela :

const float pi = 3.14f;

Variable globale - Il s’agit là d’un terme décrivant une définition variable hors
de la fonction principale. Par exemple, le int friendCount est une variable
globale et le int currentMonth ne l’est pas. Observez leurs positions :

//Global
int friendCount = 0;

int main()
{
//Not Global
int currentMonth = 5;

return 0;
}
Cela veut dire que la variable globale peut être utilisée n’importe où dans le
programme, et peut devenir dangereuse si elle n’est pas utilisée correctement.
Une façon correcte est de l’utiliser en conjonction avec le mot-clé const
mentionné ci-dessus, ce qui signifie que ses fonctions peuvent uniquement
référencer la valeur et non la modifier.

Récapitulatif
- int → Contient des nombres réels et entiers
- flottant/double → Contient des nombres décimaux
- void → Spécifie qu’il n’y a pas de type
- boolean → Contient vrai ou faux
- string → Tableau de caractères formant un mot ou une phrase
- global → Variable pouvant être utilisée n’importe où
- const → Signifie qu’une variable initialisée ne pourra pas voir sa valeur
modifiée
Conditionnelles

Déclaration « Si »
Il existe des situations dans lesquelles vous voulez qu’une action se déclenche
uniquement sous certaines conditions. C’est le rôle de la déclaration « Si » et le
moment de parler des déclarations conditionnelles.

if (Condition)
{
//Code will run here if Condition is true
}
//Code will jump here if Condition is false
En action :

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
int numberOfBooks = 10;
if (numberOfBooks > 0)
{
cout << "You have a book!" << endl;
}
}

Output:
> You have a book!
Ceci permet à un programmeur de contrôler le flux du programme et de choisir
les situations à venir lorsqu’une possibilité est vraie et vérifiée. Nous en verrons
d’autres exemples plus tard.

Déclaration « Autre »
« Autre » est optionnel mais peut être ajouté à la fin d’une déclaration « Si »
afin de lancer le code uniquement si la condition de la déclaration est fausse.

if (Condition)

//Code will run here if Condition is True

else
{

//Code will run here if Condition is False

Les déclarations Autre peuvent aussi devenir des Autre/Si quand une nouvelle
déclaration Si est ajoutée, comme cela :

if (Condition1)

//Code will run here if Condition1 is True

}
if else(Condition2)

//Code will run here if Condition1 is False and Condition2 is True


}

//If neither are true no code will run

Vous pouvez également lier deux déclarations Autre ensemble, créant alors une
chaîne infinie. Si une condition de la chaîne est vraie, alors celles suivantes ne
seront pas vérifiées :

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
if (false)
{
cout << "Contition1!" << endl;
}
else if (true) //This condition is true!
{
cout << "Contition2!" << endl;
}
else if (true) //Ignored, due to previous else statement being true
{
cout << "Contition3!" << endl;
}
else if (true) //Also ignored due to condition 2 being true
{
cout << "Contition4!" << endl;
}

return 0;
}

Output

> Condition 2
Après que le corps de la condition 2 ai été atteint et que le cout ai été exécuté,
le programme ne continuera pas à vérifier les autres déclarations.
EXERCICE
Adaptez un programme pour vérifier si une valeur est égale à une autre, et
imprimez « EQUAL » ; si elles ne le sont pas inscrivez « NOT EQUAL ». Le
code de base est le suivant :

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
int value1 = 10;
int value2 = 10;

//CODE GOES HERE


}

Solution
#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
int value1 = 10;
int value2 = 10;
if (value1 == value2)
{
cout << "EQUAL";
}
else
{
cout << "NOT EQUAL";
}

return 0;
}
Changez les valeurs de value1 et value2 et voyez comment le programme
change.

Utiliser plusieurs conditions


Vous pouvez utiliser plus d’une condition dans une seule déclaration, il existe
deux méthodes pour se faire, AND étant inscrit comme && et OR come ||
(double barre verticale).

AND vérifie si les deux conditions sont vraies avant de déclencher le corps de
la déclaration.

if (Condition1 && Condition2)

//Code will run here if Condition1 AND Condition2 are True

}
OR vérifie si une des conditions est vraie avant de déclencher le corps de la
déclaration.

if (Condition1 || Condition2)

//Code will run here if Condition1 OR Condition2 are True (Works if both
are true)

}
RÉCAPITULATIF
- Déclaration SI → Traite des déclarations conditionnelles, le code de son
corps se lance lorsque la condition est vraie
- Déclaration Autre → Utilisé en tant qu’extension à une déclaration Si,
vérifiée seulement si la déclaration est fausse
- && → Utilisé pour lier deux conditions, ne redeviendra vrai que si les
deux conditions sont vraies
- ⎢⎢ (Double barre) → Utilisé pour lier deux conditions, ne redeviendra
vrai que si une des conditions sur les deux est vraie

Switch-Case
Les switch case sont utilisées pour tester une variable sur son égalité avec une
expression constante sans avoir besoin de plusieurs déclarations
conditionnelles. Une des utilisations possibles pour cette structure est de vérifier
les d’entrées des utilisateurs. Trouvez ci-dessous la structure de base de la
switch-case :

//Switch-Case
switch (expression)
{
//Case statement
case constant-expression:
break; //Break isn't needed

//Any number of case statements


// |
// |
//\ /
// .

default:
break;
}
Le switch commence avec une « expression », il s’agit de la variable qui va être
comparée à l’expression constante. Ces constantes sont les valeurs littérales de
la variable, comme « 1 » ou « Z ». Les breaks sont optionnels, mais sans eux le
code risque de découler dans d’autres déclarations. Ci dessous se trouve un
exemple d’utilisation d’une déclaration avec switch-case. L’utilisateur inscrit un
caractère si ce dernier est N ou Y, « No » ou « Yes » sont produits
respectivement mais il y a une case par défaut qui s’applique à toutes les
situations, ceci doit se trouver à la fin.

char character;

//Reads in user input (Explained in more detail later)


scanf("%s", &character);

//Switch-Case
switch (character)
{
case 'N':
printf("NO\n");
break;
case 'Y':
printf("YES\n");
break;
default:
printf("Do not understand!\n");
break;
}
S’il n’y a aucune déclaration break, une descente va se produire. Voici un
exemple similaire à celui ci-dessus, sans les déclarations break :

char character;

//Reads in user input (Explained in more detail later)


char character;
cin >> character;

//Switch-Case
switch (character)
{
case 'N':
printf("NO\n");
case 'Y':
printf("YES\n");
default:
printf("Do not understand!\n");
}
Si « N » est l’ajout de l’utilisateur, le résultat sera :

>NO

>YES

>Do not understand!


Cela se produit lorsqu’une case déclarative est déclenchée et continue en
descendant jusqu’à trouver un break afin de s’arrêter.
Itération
L’itération signifie une boucle, et répéter rapidement donne aux programmes
l’habilité de réaliser de nombreuses opérations similaires en peu de temps. Il
existe deux types d’itération : « pour les boucles » et « pendant les boucles/faire
pendant les boucles ».

Pour les boucles


On attribue à cette fonction une valeur finale et la boucle continue jusqu’à cette
même valeur, tout en notant le nombre de boucles en cours ; voici un exemple :

for (int x = 0; x > 10; x++)


{
cout << "Loop!" << endl;
}

for(int x = 0; x > 10; x++)

Chaque partie a un rôle défini.

Rouge
Il s’agit de la section déclaration, elle permet de définir la variable du compteur
de boucles, le point de départ du compteur.
Vert
Cette section est appelée la Conditionnelle et contient une déclaration
conditionnelle verifiée à la fin de la boucle afin de voir si la déclaration Si doit
continuer en boucle. Dans ce cas précis, la boucle devrait continuer si x > 10, si
cette condition devient fausse alors la boucle s’arrêtera.

Bleu
La partie bleue est la section de l’Incrément, dans laquelle le compteur de
boucles est incrémenté (augmenté en valeur), le x++ est un raccourci pour x = x
+ 1. Cela peut aussi être x - -, si jamais un cas nécessite de diminuer le
compteur.

- La section déclaration définit le compteur de boucles


- La section conditionnelle continue la boucle si vrai
- La section d’incrément s’avère être où le compteur de boucles est augmenté

Boucles conditionnelles
Les boucles conditionnelles fonctionnent comme les boucles mais n’ont pas de
compteur de boucles, elle ne se mettront en marche que si une condition est
vraie. Cela veut dire que vous pouvez créez une boucle infinie, comme ceci :

while (true)
{
cout << "This will never stop looping!" << endl;
}
Note : Une boucle infinie est normalement construite en utilisant un « pour la
boucle » vierge :
for (;;)
{

Cette boucle ne s’arrêtera jamais et votre programme sera coincé dans la


boucle.

L’exemple ci-dessous montre que si la condition est fausse, le programme


n’atteindra jamais le code entre crochets.

while (false)
{
cout << "This will never loop!" << endl;
}
Pour utiliser efficacement cette boucle, vous pouvez vous servir d’une
déclaration conditionnelle (comme la déclaration Si) ou l’utiliser avec une
variable bool, voir les exemples ci-dessous.
int count = 0;
while (count > 10)
{

//Remember this, it’s the same as “count = count + 1;”


count++;
}
Comme dit plus haut, vous pouvez également utiliser un « pendant la boucle »
directement avec une variable Boolean :

int count = 0;
bool keepLooping = true; //Bool is true (1) is true
while (keepLooping)
{
//Remember this, it’s the same as “count = count + 1;”
count++;

if (count == 3)
{
keepLooping = 0; //keepLooping is now false, and the loop will stop
}
}
Note : La section entre les crochets du « pendant la boucle » vérifie si la
condition est vraie, vous pouvez également l’écrire comme suit :

while (!stopLooping) {}
En d’autres termes cela revient à dire : continuer la boucle tant que stopLooping
est faux (ou plutôt non-vrai), le « ! » est un symbole signifiant « non ».

Faire pendant les boucles


Ce type de boucle est très similaire à « Pendant la boucle » mais avec une petite
différence puisque elle vérifie si la condition est vraie après l’exécution du code
dans le corps de la boucle. Une « Pendant la boucle » vérifie si la condition est
vraie en amont du lancement du code. Le fragment de code ci-dessous montre
la différence :

while (false)
{
cout << "While Loop" << endl;
}

do
{
cout << "Do Loop" << endl;
} while (false)

Output:

> Do Loop

Car même si le Boolean est faux, la boucle « faire » s’exécute une seule fois
parce que la vérification a été faite à la fin du corps.

Utiliser une boucle « faire »


Un exemple plus concret de ce type de boucle serait de vérifier des entrées de
données d’utilisateurs, il les imprime et interroge ces données. Si tout va bien, il
n’y a pas besoin de lancer la boucle, dans le cas contraire elle se mettra en
marche. Ci-dessous un exemple cherchant si l’utilisateur entre un « a » :

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
//Will loop if this is false
bool correct = true;
do
{
cout << "Please enter the letter 'a':";

//Takes in user input


char a;
cin >> a;

//Checks if answer is correct


if (a != 'a')
{
//Incorrect
correct = false;

cout << "Incorrect" << endl;


}
else
{
//Correct
correct = true;
}
} while (!correct);

//If the user has completed the task


cout << "Correct" << endl;
}

Mots-clés de contrôle des boucles


Il arrive dans certaines situations que vous deviez prématurément arrêter une
boucle toute entière ou une simple itération (boucle), c’est là qu’interviennent
les mots-clés de contrôle de boucles.

Il en existe deux : break ou continue.

Break arrêtera la boucle toute entière, cela peut être utile si une réponse a été
trouvée et que le reste des itérations prévues s’avèrent inutiles.

for (int x = 0; x < 5; x++)


{
if (x == 3)
{
break;
}

//The technicalities of this statement will be explained later


cout << "Loop value: " << x << endl;
}

Output:

> Loop value: 0


> Loop value: 1

> Loop value: 2


Maintenant si l’on change le code pour ne pas inclure le break :

for (int x = 0; x < 5; x++)


{
//The technicals of this statement will be explained later
cout << "Loop value: " << x << endl;

Output:

> Loop value: 0


> Loop value: 1

> Loop value: 2

> Loop value: 3

> Loop value: 4


Continue ; si nous utilisons le code ci-dessus mais remplaçons par continue
voilà à quoi cela ressemblera :

for (int x = 0; x < 5; x++)


{
if (x == 3)
{
continue;
}

//The technicals of this statement will be explained later


cout << "Loop value: " << x << endl;
}

Le résultat sera celui-ci :

Output:

> Loop value: 0


> Loop value: 1

> Loop value: 2

> Loop value: 4

Cela montre que quand x = 3, le continue est exécuté et la boucle est passée,
tout comme la déclaration cout ; il n’y a alors pas de « Loop value: 3 ».

Boucles imbriquées
Vous pouvez également placer des boucles dans des boucles pour leur attribuer
des actions spécifiques, dans l’exemple nous utilisons des « pour les boucles »
mais cela peut également être fait avec les autres types de boucles que nous
avons vus.

Cet exemple imprime une grille 2D, la boucle imbriquée lui confère une autre
dimension :

//Prints a 5x5 grid


for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
//Prints an element of the row
cout << "X ";
}

//Moves down a row


cout << endl;
}

Output

>XXXXX

>XXXXX

>XXXXX

>XXXXX

>XXXXX
Fonctions
Les fonctions agissent comme les fondations d’un programme, elles permettent
la réutilisation d’un code, la possibilité de le garder lisible et d’arrêter le
programmeur de répéter le code. Répéter un code est fortement déconseillé
puisque les bogues seront réitérés de multiples fois et les changements de code
auront également besoin d’être répétés. Les fonctions créent une aire de
contrôle centralisée qui gère les différents rôles du programme.

Une méthode a deux éléments, paramètres (les éléments passés dans la


fonction) et le type retour (la variable étant revenue) ; ces deux éléments sont
optionnels et il est possible d’avoir une fonction qui ne contient ni l’un ni
l’autre.

Une fonction doit être définie au-delà de son appel, comme suit :

//Function call
void PrintSmile()
{
cout << ":)" << endl;
}

int main()
{
//Method call
PrintSmile();

return 0;
}
Paramètres des fonctions
Il peut parfois s’avérer utile de transférer ou envoyer des données dans un
programme, il existe deux types de paramètres d’envoi : par référence et par
valeur. Passer par référence, comme cela l’indique, veut dire que l’ont envoie
une référence directe à une variable, pas une copie, donc chaque changement à
l’effet de cette variable envoyée seront répercutés dans la fonction d’appel.
Passer par valeur corresponds à l’envoi d’une copie de cette variable, donc tous
les changements de cette variable n’affecteront pas la variable passée.

- Passer par référence veut dire que les changements de paramètres affectent
la variable envoyée
- Passer par valeur veut dire que les changements de paramètres n’affectent
pas la variable envoyée

Passer par valeur

void Add(int num1, int num2)


{
//Adds values together
int newValue = num1 + num2;

//Prints result
printf("The result is: %d", newValue);
}

int main()
{
//Method call
Add(10, 4);
return 0;
}

Output:

> The result is: 14

Ci-dessous un autre exemple, mais cette fois un String est utilisé comme
paramètre :

void PrintStr(char word[])


{
cout << word << endl;
}
La méthode d’appel ressemble à ceci :

PrintStr("Flying Squirrel");

Il s’agit d’une fonctionnalité extrêmement utile, permettant de créer des codes à


usage général et de changer le résultat de leur fonction selon ce qui est inscrit en
tant que paramètre.

Passer par référence


Passer par référence se fait en utilisant des pointeurs (sur lesquels nous
reviendrons), mais ces derniers sont des addresses mémoire, donc tous les
changements affecteront la variable passé en dedans. Un paramètre référence
est désigné par « & » et s’effectue comme ceci :
void Change(int& ParaValue)
{
ParaValue = 20;
}

int main()
{
int value = 10;

Change(value);

cout << value << endl;

return 0;
}

Output

>20
Comme vous pouvez le voir, le changement de méthode altère la valeur de la
valeur integer et affecte la valeur dans la méthode principale.

Renvoi de valeurs
Le renvoi nous permet de renvoyer des données depuis une méthode, il nous
laisse faire du calcul au sein d’une fonction et fait en sorte que cette dernière
renvoie automatiquement le résultat. Prenons un des exemples précédents et
adaptons-le pour qu’il renvoie le résultat au lieu de l’imprimer :

int Add(int num1, int num2)


{
//Adds values together
int newValue = num1 + num2;

//Returned keyword
return newValue;
}

int main()
{
//Method call
int storeResult = Add(10, 4);
return 0;
}
Les zones ayant changé ont été surlignées en vert. Quand une valeur doit être
renvoyée, le mot-clé « return » est utilisé, après que cette ligne ai été lancée il
retourne à la ligne d’appel de la méthode afin que chaque code en dessous le
renvoi ne se lance pas.
Le résultat renvoyé est ensuite conservé dans « storeResult », pour une
utilisation ultérieure. Le renvoi peut se faire avec n’importe quel type de
variable. Voyons un exemple ci-dessous qui vérifie si le nombre est une valeur
paire (en utilisant l’opérateur de module dont nous avons parlé précédemment
qui trouve le reste d’une division) :

bool EvenNumber(int value)


{
if (value % 2 == 0)
{
//Return true
return 1;
}
return 0;
}

int main()
{
if (EvenNumber(2))
{
cout << "Even number!" << endl;
}

if (EvenNumber(5))
{
cout << "Odd number!" << endl;
}

return 0;
}

Output:

>Even number!
Ce programme utilise la variable renvoyée depuis la méthode EvenNumber()
comme une conditionnelle pour la déclaration Si ; et si elle est vraie, il
imprimera « Even number! ». Comme vous pouvez le voir au niveau du
résultat, la première imprime tandis que la seconde non.

Tableaux
Les tableaux ont été mentionnés précédemment ; il s’agit d’une structure de
données contenant un nombre fixe de variables les unes à côté des autres dans
la mémoire. Le tableau se verra attribuer un type, par exemple « int ». Les
tableaux sont utilisés pour définir rapidement de nombreuses variables, et
garder pertinentes les variables ensemble. Un tableau est défini ci-dessous :

Une taille statique, avec la taille entre crochets :

int lotsOfNumber[20];

Vous pouvez également définir la valeur à la définition, Note : une taille n’a pas
besoin d’être définie car elle est automatiquement déterminée par le nombre de
valeurs spécifiées :

int lotsOfNumbers[] = {1,3,4};


- Les tableaux démarrent à 0, alors le premier index dispose d’une valeur
identifiée de 0, la deuxième est 1 et ainsi de suite. Cela veut dire qu’en
accédant aux valeurs, vous devez vous rappeler qu’il s’agit toujours d’un de
moins que le nombre de valeurs contenues.

Vous pouvez accéder à l’index comme ceci :

int var = lotsOfNumbers[0];

Cela prendra le premier index du tableau et le placera en « var ».

Les tableaux sont très utiles pour accéder rapidement aux données étroitement
liées, vous pouvez utiliser un « pour la boucle » pour boucler à travers les index
et les utiliser selon. Un exemple ci-dessous :

int lotsOfNumbers[] = { 1, 3, 4, 10};


for (int x = 0; x < 4; x++)
{
cout << x << endl;
}

Output:

>1

>3

>4

> 10
Dans C++ vous ne pouvez pas obtenir la longueur directement, vous aurez
besoin d’y travailler. Cela peut être fait simplement en utilisant le mot-clé
sizeof(), qui renvoie la taille des éléments entre crochets. Donc par exemple sur
une machine 64bit, un int sera représenté en utilisant 4bytes, alors sizeof
renverra 4. La longueur d’un tableau peut être travaillée comme ceci :

int arrayLength = sizeof(lotsOfNumbers) / sizeof(lotsOfNumbers[0]);

Cela prends la taille complète du tableau, la divise par le premier élément et


cette division donne combien d’indices ou d’index sont contenus dans ce même
tableau.

Tableau multi-dimensionnels
Vous pouvez également définir une deuxième dimension dans un tableau, voire
même une troisième, cela accordera plus de flexibilité lors des travaux avec
tableaux. Par exemple, un tableau 2D peut être utilisé pour stocker les
coordonnées ou les positions d’une grille. Il est définit comme cela :

int arrayOfNumbers[][2] = { {1,1}, {1,1}};


Vous accédez à l’élément en insérant les valeurs dans les crochets carrés pour
les coordonnées x et y.

int element = arrayOfNumbers[X][Y]


La différence évidente est que la deuxième dimension a besoin d’une valeur,
cela ne peut être résolu automatiquement en créant un tableau et que
l’initialisation requiers des crochets imbriqués. Ci-dessous l’exemple 3D :

int arrayOfNumbers[][][2] = {{{1,1},{1,1}},{{1,1},{1,1}}};


Mais à ce stade nous commençons à perdre en lisibilité et il est préférable de
s’entrainer à l’exposer ainsi :

int arrayOfNumbers[][][2] =
{
{{1,1},{1,1}},
{{1,1},{1,1}}
};

Passer des tableaux dans des fonctions


Comme nous l’avons vu lorsque nous avons parlé des fonctions, passer des
variables en tant que paramètres peut être très pratique, tout autant que passer
de nombreuses variables stockés en tant que tableau. Toutefois, nous avons
appris comment déterminer la taille d’un tableau, mais cela ne fonctionne pas
avec un tableau passé en tant que paramètre. Nous devons alors passer une
variable qui représente le nombre d’éléments contenus dans le tableau. Il existe
un type de variable très particulier permettant de retenir le compte des variables,
il s’agit de size_t et c’est une valeur entière (integer) non signée (ce qui veut
dire qu’elle ne peut pas devenir négative) utilisée pour contenir des valeurs pour
un compte.

Un exemple d’un tableau passé en tant que paramètre ci-dessous :

void Print_SingleDimenArray(size_t length, int ageArray[])

{
//Length is used to dynamically determine the for loop length
for (int i = 0; i < length; i++)
{
printf("%d\n",ageArray[i]);
}
}

int main()
{
//An array of ages
int ages[] = { 32, 11, 12, 1, 8, 5, 10 };

//Size is determined as shown previously


size_t ageLen = sizeof(ages) / sizeof(ages[0]);

//Method call
Print_SingleDimenArray(ageLen, ages);
}

Output

>32
>11

>12

>1
>8

>5

>10
Le tableau a été passé et utilisé pour imprimer des valeurs. La longueur du
tableau est cruciale pour le programme, puisqu’elle permet au « pour la
boucle » de travailler pour des tableaux de longueurs différentes.

- Chaque changement apporté à un tableau, avec n’importe quelle méthode,


changera la variable du tableau dans la méthode d’appel. Il s’agit de ce que
nous avons mentionné plus tôt en tant que passer par référence, tout le
tableau n’est pas copié puis il est passé par référence avec une petite tricherie.

User I/O
Il existe des situations et des problèmes de programmations qui impliquent
l’utilisation de l’interaction de l’utilisateur ; c’est ici qu’interviennent les
entrées et sorties de données par l’utilisateur, la bibliothèque qui gère ceci est
appelée « iostream » et est inclue dans le projet de cette manière :

#include <iostream>
Impression/Sortie de données
Parfois, l’utilisateur a besoin d’une réplique, ou l’information a besoin d’être
affichée ; c’est là que la console d’impression nous sera utile. Le mot-clé cout
est utilisé pour imprimer des données en dehors de l’écran, c’est ensuite utilisé
avec stream insertion operator désigné par « << » pour indiquer ce qu’il faut
imprimer. Voici un exemple :

cout << "Hello!" << endl;

La sortie de données serait juste « Hello! » à l’écran. Endl est simplement


utilisé pour imprimer un retour de transport (« \n »).

Vous pouvez également créer une sortie impliquant une variable ou une autre
chaîne (string), plusieurs opérateurs d’insertion de flux sont utilisés :

int x = 10;
cout << "The value is: " << x << endl;

Output

> The value is: 10


Lire / Entrée de données
Prendre les entrées d’utilisateurs peut aussi s’avérer pratique, l’opérateur est
stream extraction operator « << » et il est utilisé conjointement à cin, ils
fonctionnent de la façon suivante :

#include "stdafx.h"
#include <iostream>
using namespace std;

int main()
{
//Reads in name
char name[10];
cin >> name;

//Prints name
cout << name;
}
QUIZ DE FIN DE CHAPITRE

Cette section est destinée à vous maintenir à jour par rapport au contenu vu
précédemment ; vous y trouverez 10 questions auxquelles vous pouvez tenter de
répondre pour tester vos connaissances. Vous trouverez les réponses ci-dessous
dans la section avoisinante.

1. Nommez le type de données utilisé pour contenir une série de caractères?


2. Pour quoi le signe « % » est-il utilisé?
3. À quoi corresponds « = = »?
4. Si un « && » est entre deux déclarations conditionnelles dans un Si
singulier, qu’est ce que cela signifie?
5. Que fait dire « ++ » à la fin d’une variable?
6. Quel est l’opérateur désigné par « >> »?
7. Est-ce qu’un tableau dispose d’une propriété inhérente lui permettant de
trouver sa taille?
8. Quel est le nom de ce type de boucle et que fait-elle?
for(;;)

}
9. Quelles sont les deux valeurs contenues par un boolean?
10. Dans quels cas est utilisé le type « void »?

Réponses
1. Chaînes/String
2. Pour obtenir le modulaire de deux nombres.
3. Si les deux valeurs de chaque côté sont égales.
4. La déclaration sera seulement vraie si les deux conditionnelles sont vraies.
5. Cela incrémente/augmente la valeur de cette même variable.
6. Stream extraction operator.
7. Non, la seule manière de trouver sa taille totale et celle de ses éléments est
d’utiliser l’opérateur sizeof() puis de diviser les deux valeurs.
8. Il s’agit d’un vierge pour boucle, qui bouclera à l’infini.
9. Vrai ou Faux.
10. Il est utilisé pour signifier qu’une fonction ne renvoie pas de valeur.
CHAPITRE 3 : STRUCTURE DE CLASSES ET
POO

Les classes constituent les fondements d’un programme en C++, elles


permettent de manière facile et sans cookies de créer des endroits où stocker et
représenter des données. Une classe en C++ dispose de deux fichiers : le fichier
d’en-tête (header file : .h) et le fichier d’implémentation (.cpp).

- Le fichier d’en-tête devrait simplement contenir les définitions des classes


et des variables.
- Le fichier d’implémentation, comme le suggère son nom, contient les
fonctionnalités des fonctions et des variables.

Un fichier d’en-tête vide ressemble à ceci :

class Example
{
public:
Example();
~Example();
};
Il y a quelques morceaux clés à ce propos, la section publique définit comment
se fait l’accès aux méthodes ; mais ne vous en inquiétez pas pour le moment,
nous y reviendrons dans la section d’Encapsulation. Aussi, les fonctions
Example() et ~Example() sont respectivement le constructeur et le destructeur ;
le constructeur se lance quand la classe est créée, et le destructeur, lui, quand la
classe est détruite. Ceci peut être fait manuellement, en appelant la méthode ou
alors le compilateur le fera automatiquement.
Le fichier .cpp ressemble alors à ceci :

#include "Example.h"

Example::Example()
{
}

Example::~Example()
{
}
Tout cela est en fait constitué de références aux classes de l’Exemple. Vous
pouvez implémenter des fonctions ou des variables qui ne sont pas définies par
le fichier d’en-tête, cela n’est cependant pas conseillé.
Donc par exemple, utilisons une version étoffée d’une classe pour montrer
comment elle est utilisée :

class Shape
{
public:
Shape(double len, double wid);
~Shape();

double getArea();

private:
double length;
double width;
double area;
};

#include "Shape.h"
#include <iostream>
using namespace std;

Shape::Shape(double len, double wid)


{
length = len;
width = wid;

area = length * width;

cout << "Shape made!" << endl;


}

Shape::~Shape()
{
}

double Shape::getArea()
{
return area;
}
Et une instance de forme peut être faite comme ceci :

Shape s = Shape(10, 10);


Note : le fichier d’en-tête doit comporter ceci en haut :

#include "Shape.h"

Cela permet au programmeur de créer de nombreux contenants de données


utiles de façon très simple.
Héritage
L’héritage est un des concepts les plus importants lors de la programmation
d’objet ; il s’agit de l’idée de prendre une classe basique et de donner aux autres
classes ses caractéristiques comme les fonctions et variables. Cela nous donne
l’opportunité parfaite de réutiliser le code.

Avec notre exemple de forme ci-dessus, nous pouvons désormais créer une
autre classe appelée « Square », mais nous héritons des caractéristiques de la
classe de base « Shape ». Pour des besoins d’affichage, nous mettrons tout le
code dans un seul et même fichier .cpp à partir de maintenant. Voici la classe
Square :

#include "stdafx.h"
#include <iostream>

using namespace std;

class Shape
{
public:
double length;
double width;
double area;

Shape(double l, double w)
{
length = l;
width = w;
area = l * w;
}
~Shape()
{

}
double getArea()
{
return area;
}
};

class Square : public Shape


{
public:
Square(double l, double w) : Shape(l, w)
{
}
};

int main()
{
Square s = Square(10, 10);
cout << s.getArea() << endl;

Ouput

> 100
Square a désormais tout ce que Shape a défini en tant que public et protégé
(nous expliquerons pourquoi dans les prochaines sections) ; mais comme
mentionné il s’agit d’un bon moyen de répéter un code déjà créé.
EXERCICE
Ajoutez une classe appelée « Rectangle » et laissez la hériter de la classe Shape.
N’oubliez pas d’inclure un constructeur pour la classe Rectangle.

Solution

class Rectangle : public Shape


{
public:
Rectangle(double l, double w) : Shape(l, w)
{

}
};
Encapsulation
L’encapsulation correspond à la limitation d’accès à des variables sensibles ou
des méthodes contenues dans une classe, il existe trois différents modificateurs
d’accès :

Classe Classe Assemblée En dehors de


contenante dérivée contenante l’assemblée

Public

Protégé

Privé

Cette grille compare où vous pouvez et ne pouvez pas accéder à une variable ou
une méthode selon le modificateur choisi.

- La classe contenante est la classe dans laquelle a été défini l’objet


- La classe dérivée corresponds à n’importe quelle classe ayant hérité d’une
autre
- L’assemblée contenante désigne n’importe où dans le fichier .exe ou .dll
présent
- En dehors de l’assemblée est une autre zone existante

Ce qui veut dire que vous pouvez contrôler l’accès aux variables. Mais que se
passe-t-il si une variable est marquée privée, comment peut-on y accéder depuis
l’extérieur? Vous avez utilisé des « getters » et « setters », des méthodes
spécialement conçues pour récupérer et altérer des variables. Il s’agit d’une
bonne pratique pour garder un code protégé, modifions alors la forme afin de le
rendre encore plus sécuritaire.

Avant, nous pouvions faire ceci :

Shape s = Shape(10,10);
s.length = -1;
Toutefois ce n’est pas ce que nous voulons puisque cela invalide l’information,
vous ne pouvez pas avoir de longueur négative, c’est là que nous allons utiliser
un setter ; la fonction peut contenir du code pour valider (vérifier) la valeur
essayant d’être placée dans la variable de longueur :

void setLength(double value)


{
if (value > 0)
{
length = value;
}
else
{
cout << "Error: Value is negative!" << endl;
}
}
Cette fonction effectue les vérifications nécessaires sur la valeur pour prévenir
les informations invalides qui seraient dans les variables de la classe. Ci-
dessous nous avons une version améliorée de la classe :

class Shape
{
private:
double length;
double width;
double area;

public:
Shape(double l, double w)
{
length = l;
width = w;
area = l * w;
}
~Shape()
{

double getArea()
{
return area;
}
double getLength()
{
return length;
}
double getWidth()
{
return width;
}
}
Comme vous pouvez le voir, toutes les variables ont été marquées en privée et
les getters ont été ajoutés, c’est la façon dont les classes devraient être
structurées.

Polymorphisme
Le mot « polymorphisme » signifie avoir plusieurs formes, et dans le contexte
du POO cela veut dire qu’une méthode peut avoir plusieurs rôles.

Nous pouvons utiliser l’exemple de la forme de classe et surcharger une


méthode qui détermine la zone ; si par exemple nous voulions créer une classe
Triangle, il s’agit de la forme de classe mais avec une méthode virtuelle
getArea() :

class Shape
{
protected:
double length;
double width;
double area;

public:
Shape(double l, double w)
{
length = l;
width = w;
}
~Shape()
{

}
virtual double getArea()
{
return length * width;
}
};
Les méthodes virtuelles permettent d’être substituées et changées par une classe
qui en hérite, il s’agit également d’un changement facultatif.

class Triangle : public Shape


{
public:
Triangle(double l, double w) : Shape(l, w)
{

double getArea() override


{
return 0.5 * (length * width);
}
};
Ci-dessus se trouve l’implémentation de la classe Triangle, « override » est le
mot-clé surligné, utilisé pour changer la fonctionnalité de la fonction virtuelle.
int main()
{
Shape s = Shape(10, 10);
cout << "The shapes area is: " << s.getArea() << endl;

Triangle t = Triangle(10, 10);


cout << "The triangles area is: " << t.getArea() << endl;
}

Ouput

>The shapes area is: 100


>The shapes area is: 50

Comme vous pouvez le voir dans l’exemple ci-dessus, chaque fonction


getArea() est implémentée différemment et rends un résultat complètement
différent.

Ce modèle polymorphe donne la possibilité de créer de nombreux nouveaux


types de données sans nécessairement répéter le code, avec un niveau de
personnalisation supérieur.

EXERCICE
Ajoutez une nouvelle classe appelée Cercle, elle aura besoin d’un constructeur
différent qui prends seulement la hauteur, et la méthode getArea devra être
surchargée.

SOLUTION
class Circle : public Shape
{
public:
Circle(double h) : Shape(h, 0)
{

double getArea() override


{
return 3.14 * ((length / 2) * (length / 2));
}
};

Vous remarquerez qu’il y a constructeur qui inclus seulement la hauteur et que


le getArea() retourne une aire relativement imprécise pour le cercle.

Le polymorphisme, s’il est correctement utilisé, peut donner une base de code
extrêmement propre et lisible, avec des méthodes et des classes qui s’adaptent si
nécessaire.
Interfaces
Une interface décrit le comportement des capacités d’une classe C++ sans
s’engager à implémentation. Les interfaces sont implémentées en utilisant des
classes abstraites, et sont juste un plan pour la classe.

Les classes abstraites sont réalisées en ajoutant des fonctions purement


virtuelles à une classe ; et une fonction purement virtuelle est une fonction sans
corps, spécifiée en plaçant un « =0 » à côté de cette dernière, comme ceci :

virtual double getVar() = 0;

Cela veut dire que le rôle de cette fonction devra simplement être hérité et
adapté. Ne pas réussir à substituer une fonction virtuelle dans une classe héritée
fait apparaître une erreur, c’est donc un modèle stricte pour concevoir des
classes.

Voici l’erreur résultant de la non-implémentation d’une fonction virtuelle pure :

Ci-dessous un exemple impliquant des animaux :

#include "stdafx.h"
#include <iostream>

using namespace std;

class Animal
{
public:
//Pure virtual function
virtual void MakeSound() = 0;
};

class Dog : public Animal


{
public:
void MakeSound()
{
cout << "Bark!" << endl;
}
};

class Cat : public Animal


{
public:
void MakeSound()
{
cout << "Meow!" << endl;
}
};

class Lion : public Animal


{
public:
void MakeSound()
{
cout << "Raw!" << endl;
}
};

int main()
{
Dog d;
d.MakeSound();

Cat c;
c.MakeSound();

Lion l;
l.MakeSound();
}

Output

>Bark!

>Meow!

>Raw!

Comme vous pourrez en témoigner, c’est un exemple rudimentaire et inutile ;


toutefois cela montre comment une classe de base peut contenir de
l’information basique et la classe qui en hérite est supposée ajouter de
l’information par dessus. Dans cet exemple, le programme déclare que tout ce
qui hérite des animaux doit avoir un son, cela peut être adapté pour réaliser des
choses plus complexes pour les classes, il serait aisé de le remplir
d’informations plus pertinentes.
Un autre exemple ci-dessous utilise l’exemple de la forme classique, il aura une
classe de base appelée « Shape » qui contiendra une fonction abstraite
(« getArea() ») qui devra être implémentée pour chaque forme :

#include "stdafx.h"
#include <iostream>

using namespace std;

class Shape
{
public:
//Pure virtual function
virtual int Area() = 0;

Shape(double h, double w)
{
heigth = h;
width = w;
}
protected:
double heigth;
double width;
};

class Circle : public Shape


{
public:
Circle(double h, double w) : Shape(h, w) {}
int Area()
{
return 3.14 * ((heigth / 2) * (heigth / 2));
}
};

class Rectangle : public Shape


{
public:
Rectangle(double h, double w) : Shape(h, w) {}

int Area()
{
return heigth * width;
}
};

int main()
{
Rectangle r = Rectangle(10, 20);
cout << r.Area() << endl;

Circle c = Circle(5, 5);


cout << c.Area() << endl;

}
Un autre exemple très simple, mais prenez votre temps pour vous y habituer :
l’architecture abstraite de classe, permettant une addition facile d’une nouvelle
fonction et de classes, même si le modèle a été créé grâce au plan facile du
modèle.
QUIZ DE FIN DE CHAPITRE

1. Quels sont les deux fichiers utilisés pour créer une classe?
2. Quel symbole vient avant la définition du destructeur?
3. Quel rôle devrait avoir un fichier d’en-tête?
4. Que veut dire « protégé » pour une variable ayant été héritée?
5. Quel est le rôle du polymorphisme?
6. Quel mot-clé est utilisé pour changer une méthode virtuelle?
7. Comment signifier une fonction virtuelle pure?
8. Où peut-on accéder à une fonction ou une variable marquée en privée?
9. Quel est le rôle du constructeur?
10. Que devrait contenir le fichier .cpp?

RÉPONSES
1. Header (.h) et le fichier C++ (.cpp)
2. Le symbole « ~ » utilisé pour montrer un destructeur
3. Seulement définir les fonctions et les variables
4. Elle peut seulement être accessible en dehors de la définition classe par
classe qui en hérite
5. Il permet d’avoir une seule définition de base mais plusieurs
implémentations différentes
6. La substitution (override) est utilisé pour changer une méthode virtuelle
7. Avec une définition de fonction normale qui est égale à zéro
8. Seulement dans la classe ou elle a été définie
9. C’est une fonction spéciale sans retour type qui est appelée quand un
exemple de classe est créé
10. L’implication des fonctions et variables définies dans le fichier d’en-
tête
CHAPITRE 4 : TECHNIQUES AMÉLIORÉES
Structures
Les structures (structs) sont originaires de C, et viennent du monde de la
programmation antérieur aux classes. Toutefois, elles sont toujours utiles et
peuvent être utilisées lorsque les classes sont réputées trop puissantes.

Un bon exemple d’utilisation de structure serait pour la tenue de registres, de


dossiers ; si par exemple vous aviez besoin de garder une trace des joueurs de
football dans une équipe, et vous deviez stocker :

- le nom
- le numéro
- le salaire
- le pied fort
- etc.

S’il n’y a pas besoin d’ajouter de fonctionnalités dans cette information, une
structure est parfaite pour le cas présente. Une structure stockant ces
informations est définie comme ceci :

struct FootballPlayer
{
string Name;
int KitNumber;
double Wage;
string StrongestFoot;
};
Cette structure contient les valeurs pertinentes, elle va donc fournir un
contenant adéquat pour de l’information similaire.
Un nouvel exemple de cette structure serait celui-ci :

FootballPlayer player1;
player1.Name = "Messi";
player1.KitNumber = 10;
player1.Wage = 1000000;
player1.StrongestFoot = "Both";
Ce code va créer un FootballPlayer et assigner des valeurs à toutes ses
variables.

L’usage réel des structure est souvent très simple, la plupart du temps on
cherche à stocker des données relativement triviales comme celles ci-dessus ;
lorsque vous avez besoin de fonctionnalité et d’encapsulation utilisez une
classe. Ceci s’appelle une encapsulation POD (Plain Old Data).

Note : L’accès par défaut des variables d’une structure est public tandis que
pour les classes le réglage par défaut est privé.
Enums
Enums est le raccourci pour le type Énumérations, qui a le rôle d’un boolean
mais avec un nombre illimité de types définis par l’utilisateur ; cela peut-être
utilisé comme des drapeaux pour des situations comme « HOME_SCREEN »
ou « SETTING_SCREEN ».

Une enum est définie comme suit :

enum CurrentScreen
{
HOME,
SETTINGS,
CONTACTS,
CALCULATOR
};
Chaque mot entre crochets est un état que peut revêtir une enum. Un exemple
d’enum pourrait alors être :

int main()
{
CurrentScreen phone1;

phone1 = HOME;
}
Le code ci-dessus crée une nouvelle enum et l’assigne en tant que « HOME ».
Vous pouvez désormais utiliser cette enum pour vérifier quel est son état, par
exemple ci-dessous nous utiliserons une switch-case :

#include "stdafx.h"
#include <iostream>

using namespace std;

enum CurrentScreen
{
HOME,
SETTINGS,
CONTACTS,
CALCULATOR
};

int main()
{
CurrentScreen phone1;

//Change me
phone1 = HOME;

switch (phone1)
{
case HOME:
cout << "You're on the home page" << endl;

break;
case SETTINGS:
cout << "You're on the settings page" << endl;

break;
case CONTACTS:
cout << "You're on the contacts page" << endl;

break;
case CALCULATOR:
cout << "You're on the calculator page" << endl;

break;
default:
break;
}

Output

>You’re on the home page


Ce code vous montre que vous pouvez utiliser des structures conditionnelles
avec une enum afin de créer facilement une belle structure ramifiée. Amusez-
vous avec la définition de l’enum commentée « Change me » et voyez comment
le résultat change.
Unions
Les unions sont une structure de données conçues pour fournir des usages de
mémoire efficaces. Elles peuvent contenir autant de variables que désirées mais
seulement pour une variable à la fois. Donc la structure occupe seulement de
la mémoire pour la plus grande variable.

Les unions doivent être utilisées avec précaution puisque définir la valeur d’une
autre variable effacera les données stockées dans une autre, il est donc très
facile de perdre des données importantes.

Une union est définie comme ceci :

union Example
{
int i;
int x;
int y;
};
Et un exemple et une valeur sont créés comme ceci :

Example e;
e.i = 4;

Afin de comprendre comment fonctionne la taille d’une union, voir le


programme ci-dessous :

#include "stdafx.h"
#include <iostream>
using namespace std;

union Example
{
int i;
int x;
int y;
};

int main()
{
Example e;
e.i = 4;

cout << "Your int size is: " << sizeof(int) << " bytes" << endl;
cout << "The union has 3 variables that should be a total of: " <<
sizeof(int) * 3 << " bytes"<< endl;
cout << "However, the union is " << sizeof(e) << " bytes" << endl;
}
En fonction de l’architecture de votre CPU, le résultat sera :

Ouput

>Your int size is: 4 bytes

>The union has 3 variables that should be a total of: 12 bytes

>However, the union is 4 bytes


Comme vous pouvez le voir, l’union est seulement de la taille d’un integer, ces
structures interviennent lorsque l’on veut efficacement utiliser de la mémoire
dans les cas ou les variables sont inactives et aucune n’est nécessaire.

Ci-dessous un code qui montre ce qui arrive à toutes les valeurs lorsqu’une est
changée :

#include "stdafx.h"
#include <iostream>

using namespace std;

union Example
{
int i;
int x;
int y;
};

void Print_Union(Example e)
{
cout << "i: " << e.i << endl;
cout << "x: " << e.x << endl;
cout << "y: " << e.y << endl;
}

int main()
{
Example e;

cout << "1" << endl;


e.i = 4;
Print_Union(e);

cout << "2" << endl;


e.y = 3;
Print_Union(e);

cout << "3" << endl;


e.x = 1;
Print_Union(e);
}
Ouput

>1
> i: 4

> x: 4

> y: 4

>2

> i: 3

> x: 3

> y: 3

>3

> i: 1
> x: 1

> y: 1
Vous voyez qu’en changeant une valeur, elles sont toutes égales à la même
valeur ; c’est pourquoi toutes les valeurs partagent le même emplacement de
mémoire donc en en changeant une on les modifie toutes.

Listes d’arguments variables


Les listes d’arguments variables permettent la possibilité de paramètres sans fin.
Plus tôt, nous avions vu les fonctions avec un nombre défini et statique de
paramètres (variables pouvant devenir une méthode), comme :

int Add(int i, int y)


{
}
Ici nous avons deux paramètres, integer « j » et integer « y ».

On le connaît sous le nom de Fonction Variadique, ou fonction avec une arité


infinie (nombre de paramètre, donc « Ajouter » au dessus a une arité de 2).

Les variables sont stockées dans une variable va_list, qui fonctionne comme
n’importe quelle autre variable mais contient seulement la liste des variables
passées. Une fonction variadique est définie ainsi :

void Add(int numberOfVariables, ...)


{
}

Là où « numberOfVariable » est le nombre de paramètres passés en surplus,


notez que cela n’inclue pas la variable. C’est nécessaire car va_list ne connaît
pas le nombre total de variables et ne garde pas de trace d’où il se trouve dans la
liste.

Utiliser va_list requiers l’utilisation de plusieurs fonctions :


- va_start(va_list, numberOfVariables) → prends les variables passées et
leur assigne un nombre spécifié par le second paramètre au premier paramètre
- va_arg(va_list, type) → prends la variable suivante depuis la liste
spécifiée dans le premier paramètre et crée une variable spécifiée par « type »
- va_end(va_list) → range la liste spécifiée

Nous pouvons maintenant utiliser ces fonctions pour créer une fonction
variadique fonctionnelle :
#include "stdafx.h"
#include <cstdarg>
#include <iostream>

using namespace std;

void Add(int numberOfVariables, ...)


{
//The variable list
va_list arg;

//Grabs the list


va_start(arg, numberOfVariables);

int total = 0;
for (int i = 0; i < numberOfVariables; i++)
{
//Grabs the next variable
total += va_arg(arg, int);
}

//Clean up
va_end(arg);
cout << "Total is: " << total << endl;
}

int main()
{
//4 Arguments
Add(4,
1, 2, 3, 4);

//10 Arguments
Add(10,
6, 9, 5, 11, 22, 1, 3, 56, 90, 63);
}

Ouput

>Total is: 10
>Total is: 266

Chaque section du processus de la liste de variables est labellisé, cette fonction


ajoute tous les paramètres qu’elle reçoit. Cela donne une façon très dynamique
de créer une fonction qui puisse contenir une liste presque infinie de
paramètres.
EXERCICE
Créez une fonction qui multiplie tous les paramètres données et retourne une
valeur, puis l’imprime. Note : cet #include est requis :

#include <cstdarg>

SOLUTION

#include "stdafx.h"
#include <cstdarg>
#include <iostream>

using namespace std;

int Multi(int numberOfVariables, ...)


{
va_list arg;
va_start(arg, numberOfVariables);

int total = 1;
for (int i = 0; i < numberOfVariables; i++)
{
//Grabs the next variable
total *= va_arg(arg, int);
}
va_end(arg);
return total;
}
int main()
{
cout << "The total is: " << Multi(3, 56, 90, 63) << endl;
}
Espaces de nommage
Les espaces de nommage sont utilisés pour prévenir les variables et fonctions
du même nom de se mélanger. Par exemple, la fonction Run() peut être
appliquée dans de nombreuses situations, elle doit donc être distinguée de
toutes les autres fonctions Run(), c’est là qu’interviennent les espaces de
nommage.

Ils sont définis comme ceci, avec toutes les fonctions et les variables dans les
crochets en tant que partie inhérente de ces espaces de nommage :

namespace NamespaceName
{

}
L’exemple ci-dessous montre comment ils sont utilisés. Deux espaces de
nommage sont définis ainsi avec le même nom de fonction :

namespace Proccesing
{
void Run()
{
cout << "Processing!" << endl;
}
}
namespace Initialisation
{
void Run()
{
cout << "Initialisation started!" << endl;
}
}
Cela donne au programmeur l’opportunité de choisir quel Run() il peut exécuter
juste en spécifiant l’espace de nommage, comme ceci :

Proccesing::Run();

et

Initialisation::Run();

et en les exécutant en même temps :

int main()
{
Initialisation::Run();
Proccesing::Run();
}

Output

> Initialisation started!

> Processing!
Les espaces de nommage donnent toutefois un moyen de segmenter nettement
un code, et améliorent la lisibilité en donnant une façon de labellisé des
morceaux de code en quelques sortes.
QUIZ DE FIN DE CHAPITRE

1. Quelle est la situation la plus adaptée à l’utilisation d’une structure?


2. De quoi enum est-il le raccourci?
3. Comment est dictée la taille d’une union?
4. Quel est le facteur clé d’une union?
5. Qu’est-ce qu’une fonction variadique?
6. Que contient va_list?
7. Pourquoi avez-vous besoin de spécifier le nombre de paramètres dans une
fonction variable?
8. Pourquoi va_end() doit être lancé?
9. Quel est le rôle de base d’un espace de nommage?
10. Quelle est la syntaxe utilisée lors de l’appel d’une fonction dans un
espace de nommage, dans le cas où vous appelleriez la fonction Add() dans
l’espace de nommage Math?

RÉPONSES

1. Pour POD (Plain Old Data)


2. Énumération
3. Selon la taille de sa plus grande variable
4. Toutes les variables partagent le même emplacement de mémoire, une seule
variable peut donc contenir de la valeur à la fois
5. Une fonction avec la possibilité d’un nombre de paramètres infini
6. Une liste de variables passées à une fonction
7. Il n’y a pas de façon de déterminer la taille d’une va_list, vous devez
spécifier le nombre de paramètres passés
8. Pour nettoyer la liste des variables une fois utilisée
9. Pour segmenter et labéliser des variables et des fonctions de manière
pertinente
10. Math::Add()
CHAPITRE 5 : OPTIONS AVANCÉES
Fichier I/O
Vous rencontrerez éventuellement une situation dans laquelle vous aurez besoin
que les données existent quand le programme est lancé ou non, c’est là
qu’intervient la sauvegarde de fichier.

La sauvegarde de fichier est effectuée en utilisant les fonctions dans la librairie


fstream, inclue comme ceci :

#include <fstream>

Vous aurez aussi besoin de « iostream », que nous avons vu précédemment avec
« cout » et « cin ».

#include <iostream>

Il existe trois objets principaux utilisés pour la sauvegarde et la lecture de


fichier :

- ofstream → celui-ci traite avec le flux de sortie et est utilisé pour créer et
écrire des données dans un fichier
- ifstream → celui-là est l’opposé de ofstream et est utilisé pour lire des
données depuis les fichiers
- fstream → cette fonction a les particularités de ofstream et ifstream
simultanément

Ouvrir un fichier
Pour ouvrir un fichier vous devrez utiliser la fonction « open() », membre de
tous les objets principaux, fonctionnant comme suit :
fstream f;
f.open("file.txt", ios::in);
Où :

- « file.txt » est le nom de fichier


- « ios::in » est le drapeau pour la façon dont sera ouvert le fichier, consultez
le tableau ci-dessous pour un récapitulatif de toutes les options

Name Description
ios::app Ouvre le fichier dans le mode
ajouter, chaque entrée de donnée
sera ajoutée à la fin de fichier
ios::ate Ouvre un fichier à entrer dans un
fichier, et déplace le contrôle lire/
écrire à la fin du fichier
ios::in Ouvre un fichier pour la lecture
ios::out Ouvre un fichier pour l’écriture
Ios::trunc Si le fichier existe, les données
contenues sont effacées (tronquées)

Vous pouvez aussi avoir deux de ces modes en vous servant de OR


conjointement ; cela peut-être utile si vous souhaitez ouvrir le fichier pour y
écrire et voulez vous débarrasser des données déjà existantes :

f.open("file.txt", ios::out || ios::trunc);

Fermer un fichier
Fermer un fichier est aussi simple que vous le pensez. Pour se faire, tout ce dont
vous avez besoin c’est d’appeler la fonction « close() » :

f.close();

Cela nettoie le flux et relâche toute la mémoire allouée au fichier.

Écrire dans un fichier


Écrire dans un fichier demande l’utilisation de l’opérateur d’insertion (<<) ;
nous en avons déjà parlé dans le cas de « cout » et tout ce que vous devez faire
c’est de diffuser les données en fstream ou ofstream au lieu de cout.
Voici comment procéder :

f << "Writing data!" << endl;


Lire des données
Pour lire des données la démarche est similaire à l’écriture, mais inversée. Vous
devez utiliser l’opérateur d’extraction de flux (>>) ; comme nous avions fait
avec cin mais ici utilisez fstream ou ifstream à la place.

Comme ceci :

char data[100];
f >> data;
Note : Lire comme ceci vous amène à lire toutes les données jusqu’à un espace
ou une nouvelle ligne.

Exemple
Ci-dessus se trouve tout ce dont vous avez besoin pour lire ou écrire dans un
fichier, ci-dessous un exemple concret de code avec lecture et écriture :

#include "stdafx.h"
#include <iostream>
#include <fstream>

using namespace std;

int main()
{
//Opens a file for writing
fstream write;
write.open("file.txt", ios::out);

//Writes data to file


write << "Example Data" << endl;
write << "More example data" << endl;
write.close();

//Opens a file for reading


fstream read;
read.open("file.txt", ios::in);

//"!read.eof()" = Read while not end of file


//.eof() is a bool to flag if the end of the file has been reached
char data[100];
while (!read.eof())
{
//Reads in
read >> data;

//Prints
cout << data << endl;
}

read.close();
}
Output

>Example
>Data

>More

>example
>data
Comme vous pouvez le voir, chaque commande de lecture est arrêtée quand elle
atteint un espace ou une nouvelle ligne (comme dit précédemment), dans ce cas
là cela complique la section de lecture car nous avons désormais toutes les
sentences qui sont séparées. Il existe cependant une façon de régler ce problème
en utilisant « getline() ». Nous adapterons l’exemple ci-dessus pour ligne les
lignes entières (les changements ont été surlignées) :

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
//Opens a file for writing
fstream write;
write.open("file.txt", ios::out);
//Writes data to file
write << "Example Data" << endl;
write << "More example data" << endl;
write.close();

//Opens a file for reading


fstream read;
read.open("file.txt", ios::in);

//"!read.eof()" = Read while not end of file


//.eof() is a bool to flag if the end of the file has been reached
string data;
while (!read.eof())
{
//Reads in
getline(read, data);

//Prints
cout << data << endl;
}

read.close();
}

Output

>Example Data
>More example data

La librairie <string> a été inclue pour accéder à la méthode getline(), cette


dernière utilise le flux de « lecture » (read), les premier et deuxième paramètres
étant l’endroit de stockage des données. Comme vous pouvez le remarquer,
« data » a également été adapté à une chaîne, permettant une façon plus nette et
propre de lire les données puisqu’il n’est pas nécessaire de spécifier la longueur.
Récursion
Une fonction récursive est une fonction qui contient dans sa définition un appel
à elle-même, ce qui crée une boucle (et si vous n’êtes pas prudent, une boucle
infinie).

La récursion peut être très utile dans la mesure où elle crée une itération avec
une petite base de code, en voici un exemple :

void Recursive(int count)


{
if (!(count <= 0))
{
//Prints current value of count
cout << count << endl;

//Makes recursive call


Recursive(count - 1);
}
}
Toute ce que fait cette fonction c’est un décompte depuis la valeur initiale
appelée, donc :

Recursive(5);
Donnera :

>5

>4
>3
>2

>1
Le bit clé de cette fonction ci-dessus est la déclaration Si, permettant de
s’assurer que l’appel récursif s’arrête ; il s’agit de la condition d’arrêt qui stoppe
le programme de crasher à cause d’un trop-plein d’appels de la fonction.
Un exemple plus utile que vous rencontrerez souvent si vous recherchez la
récursion est l’exemple factoriel, qui est le produit de tous les integers positifs
allant de 1 jusqu’à un nombre donné, alors le factoriel de 4 (écrit 4!) est :

4! = 4 * 3 * 2 * 1 = 24

Et le code récursif ressemble à ceci :

int Factorial(int value)


{
if (value > 0)
{
return value * Factorial(value - 1);
}
else
{
return 1;
}
}
Comme vous pouvez le voir il y a un appel de fonction dans la déclaration
retournée ; vous utiliserez beaucoup ceci pour les récursions puisque cela
signifie que la fonction tombera dans un espèce de trou récursif ; arrivez à la fin
(dans ce cas précis « return 1 ») et utilisez cette information pour remplir les
trous au-dessus et déterminer une réponse au début. Cela donne une manière
différente et plus propre de traiter avec l’itération.
Exercice
Il s’agit ici d’un exercice difficile, visant à déclencher une meilleur
compréhension de la récursion. Prenez votre temps et faites quelques recherches
avant de commencer. Dans cet exercice, vous devrez créer une fonction
récursive qui calcule la suite de Fibonacci avec toutes les valeurs en dessous de
1000, et qui les imprime au fur et à mesure.

Commencez avec un appel comme ceci :

Fibonacci(0, 1);
Note : Vous aurez besoin de deux paramètres, retour/précédent et suivant.
Solution

#include "stdafx.h"
#include <iostream>
using namespace std;

int Fibonacci(int previous, int next)


{
//Print
cout << previous << endl;

if (next < 1000)


{
int newNext = previous + next;

return Fibonacci(next, newNext);


}
}

int main()
{
Fibonacci(0, 1);
}
Output

>0
>1

>1

>2
>3

>5

>8
>13

>21

>34
>55

>89

>144
>233

>377

>610

>987
Rappelez-vous qu’il y a plein de manières différentes de faire les choses, alors
si votre cheminement est différent mais que votre résultat de sortie est le même,
c’est correct !
Pré-processeurs
Les pré-processeurs sont des instructions pour le compilateur, ce dernier doit les
traiter avant que tout code soit compilé. Ils commencent tous avec (#) mais ne
finissent pas avec un point virgule (;) puisque ce ne sont pas des déclarations.

#define
Define est utilisé pour créer une définition d’un symbole ou d’un mot ;
l’exemple le plus commun étant d’utiliser #define pour spécifier si le
programme est en mode sans échec. Cela vous permet de lancer seulement le
code de débogue, une fois que le drapeau sans échec est défini. Vous pouvez
également l’utiliser pour définir des valeurs constantes ; en voici un exemple :

#include "stdafx.h"
#include <iostream>

#define NUMBER 10

using namespace std;

int main()
{
cout << NUMBER << endl;
}

Output

>10
Ceci est utilisé comme un système alias, aucune variable n’est définie ici mais
vous pouvez ajouter des valeurs importantes dans ces déclarations define, et
créer un code plus lisible.

Vous pouvez également utiliser les déclarations #define pour créer des macro-
fonctions, comme suit :

#define BIGGEST(a,b) ((a < b) ? b : a)

Cela revient à dire que si b est supérieur à (<) a, il faut retourner (?) b puisque
la valeur autre (:) retourne a.
Les macro-fonctions sont appelées comme toutes les autres fonctions :

cout << BIGGEST(10, 2) << endl;


Un des usages réels les plus pratiques de ceci est de créer une meilleure syntaxe
pour un Pour une boucle au sein de #define ; seulement à des fins de syntaxe :

#define For(index, max) for(index = 0; index < max; index++)


Cela définit un pour une boucle dans le corps de #define, vous mettez
simplement la liste et la valeur souhaitées dans l’index, pour ensuite avoir une
bonne syntaxe.

Voici à quoi cela ressemble :

int i;
For(i, 10)
{
cout << i << endl;
}
Bien meilleur que son équivalent :

for (int i = 0; i < 10; i)


{
cout << i << endl;
}
Veuillez noter que ces deux modèles font exactement la même chose, seule leur
syntaxe est différente.

Compilation conditionnelle
Ceci peut être utilisé conjointement à #define, il y a #ifdef (if defined, si défini)
et #ifudef (if undefined, si indéfini) qui vérifient tous les deux les déclarations
#define. Ils peuvent être utilisés ainsi :

#ifdef VERBOSE
cout << "Print!" << endl;
#endif
Cela entour le code et dit par exemple que s’il n’y a pas de #define avant, le
code sera ignoré.

#define VERBOSE;

#ifudef est l’opposé, il ignorera le code s’il y a un #define.

Les opérateurs # et ##
L’opérateur # replace un macro avec une chaîne donnée, en voici un exemple :

#define CREATE_STRING(x) #x;


Puis, lorsqu’appelé il remplacera l’appel avec « x » :

#include "stdafx.h"
#include <iostream>

using namespace std;

#define CREATE_STRING(x) #x

int main()
{
cout << CREATE_STRING(STRING EXAMPLE) << endl;
}

Output

>STRING EXAMPLE
Car :

CREATE_STRING(STRING EXAMPLE)
Appelle :

#define CREATE_STRING(x) #x

Là où :

#define CREATE_STRING(STRING EXAMPLE) #STRING EXAMPLE


Et :

#STRING EXAMPLE

Sont égaux à :

"STRING EXAMPLE"

Opérateur ##
L’opérateur double dièse concatène deux objets en un, cela est mieux expliqué
avec un exemple :

#include "stdafx.h"
#include <iostream>

using namespace std;

#define MAKE_ONE(a, b) a ## b

int main()
{
int numone = 100;

cout << MAKE_ONE(num, one) << endl;


}

Output
>100
Que s’est-il produit? Voyons de plus près.

Le double dièse prends deux objets et les concatène, alors :

MAKE_ONE(num, one)
Se résous :

#define MAKE_ONE(num, one) num ## one

Et :

num ## one
Est égal à :

numone

Donc :

cout << MAKE_ONE(num, one) << endl;

Est égal à :

cout << numone << endl;

La variable ci-dessus est appelée et le résultat est 100.


Mémoire dynamique et pointeurs
Pour comprendre les pointeurs, vous devez appréhender correctement la façon
dont les variables sont stockées. On accorde aux variables un espace de
mémoire, et la taille dépends de la taille de la variable. Un pointeur est une
variable contenant l’adresse de cet espace de mémoire, permettant alors un
accès direct à une variable.

Un pointeur est défini comme ceci :

int* p;

Vous devez ensuite faire en sorte que le pointeur pointe un espace de mémoire
réel, pour se faire il faut utiliser l’opérateur de référence (&). Le code ci-
dessous assigne une variable à pointer au pointeur :

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
int i = 10;
int* p = &i;
}
Pour définir un pointeur à un integer, imprimons la valeur juste après
l’initialisation et voyons à quoi cela ressemble, et ce en ajoutant le code ci-
dessous à la fin du corps de la méthode principale :
cout << p << endl;

Output

> 008FF768
Voici l’adresse contenant l’integer « I ».

Note : Cette valeur sera différente à chaque fois.

Cela vous permet de passer les valeurs par référence ; si vous passiez le
pointeur en un paramètre, chaque changement accordé au pointeur résulterait en
un changement à la variable de base, l’exemple ci-dessous le démontre :

#include "stdafx.h"
#include <iostream>

using namespace std;

void Change(int* pointer)


{
*pointer = 200;
}

int main()
{
int i = 10;
int* p = &i;

cout << "Before: " << i << endl;


Change(p);

cout << "After: " << i << endl;


}

Output

>Before: 10
>After: 200
Comme vous pouvez le voir, la valeur de « I » a changé, sans pour autant
apporter de changement directement à la variable.

Les pointeurs peuvent pointer n’importe quel type de donné, vous pouvez
également pointer des objets.

Opérateurs nouveau et supprimer


L’opérateur nouveau (new) permet d’allouer manuellement de la mémoire à un
type de données :

double* p = new double;

Vous remarquerez que le mot-clé nouveau (new) attribue de la mémoire et


retourne un pointeur à cette dernière, dans le cas de l’exemple ci-dessus il s’agit
d’un pointeur ) un double. C’est de la mémoire manuellement attribuée, et le
compilateur ne relâchera pas manuellement de données ; si cela n’est pas
rectifié manuellement il pourrait y avoir une fuite de données, là où la mémoire
est inutilement occupée.
C’est là qu’intervient l’opérateur supprimer (delete), il s’agit de l’opposé du
mot-clé nouveau, il retirera de la mémoire pour la variable.

Lançons les opérateurs :

#include "stdafx.h"
#include <iostream>

using namespace std;

int main()
{
//Creates pointer
double* p = NULL;

//This checks to see if memory has been allocated


//It just checks to make sure "p" is pointing to something
if (!(p = new double))
{
cout << "ERROR: Memory problems" << endl; //Will only print if there
is a problem
}

//Changes value etc


*p = 100;
cout << "P is: " << *p << endl;

//Deallocates memory
delete p;
}
Comme vous le voyez, il y a une vérification pour l’attribution de la mémoire
pour p ; il s’agit d’une pratique recommandée car cela permet de garder un oeil
sur les problèmes de mémoire :

double* p;
p = new double;
Si la deuxième ligne échoue, p pourrait pointer n’importe quel espace de
mémoire, ce qui provoquerait un crash si la valeur de p a été changée.

Mémoire dynamique pour les tableaux


Comme vous pouvez le faire avec les variables, vous le pouvez aussi pour les
tableaux avec :

- nouveau[] (ou new[]) pour allouer une portion de mémoire


- supprimer[] (ou delete[]) pour retirer une portion de mémoire

Donc par exemple, si nous voulions réaliser un tableau de pointeurs vers 5


objets :

#include "stdafx.h"
#include <iostream>

using namespace std;

class MyClass
{
public:
MyClass()
{
cout << "Made!" << endl;
}
~MyClass()
{
cout << "Deleted!" << endl;
}

};

int main()
{
//Create
MyClass* classes = new MyClass[5];

//Delete
delete[] classes;
}

Output

>Made!

>Made!

>Made!

>Made!

>Made!

>Deleted!

>Deleted!
>Deleted!

>Deleted!

>Deleted!

On remarque que la première ligne dans le main() crée 5 MyClasses, les


constructeurs tirent et impriment « Made! », le lien suivant va ensuite libérer la
mémoire puis les destructeurs tirent et impriment « Deleted! » ; cela peut être
utilisé pour créer un nombre important de pointeurs avec très peu de code.

Arithmétique d’un pointeur


Vous pouvez ajouter une valeur à un pointeur pour le déplacer vers le prochain
espace de mémoire ; disons que vous avez un pointeur dirigé vers un integer, et
sur un système 64bits un integer doit correspondre à 4bytes. Alors :

int* p = new int;


p = p + 1;
Le code ci-dessus se déplacera aux prochains 4 bytes, par « +1 » on lui indique
de se déplacer vers le prochain trou à la taille de la variable. Par exemple, si
nous avions un « long long » qui devrait être 8 bytes, en ajouter un déplacera 8
bytes tout du long. Vous pouvez également faire ceci dans la direction opposée
et en prendre une, ce qui aura le même résultat, mais dans l’autre sens.

En voici un exemple :

#include "stdafx.h"
#include <iostream>
using namespace std;

int main()
{
//A size check
cout << "int is: " << sizeof(int) << " bytes" << endl;

//Creates memory
int* next = new int[10];

for (int i = 0; i < 10; i++)


{
//Moves along 4 bytes
cout << next + i << endl;
}

//Deletes memory
delete[] next;
}

Output

>int is: 4 bytes


>00DDDB50

>00DDDB54

>00DDDB58
>00DDDB5C

>00DDDB60
>00DDDB64

>00DDDB68

>00DDDB6C

>00DDDB70

>00DDDB74
Comme vous le voyez, la mémoire est attribuée et le pointeur est déplacé au fur
et à mesure de 1 à chaque fois. Dans le résultat, les adresses hexadécimales sont
différentes de 4 entre elles puisque les adresses des espaces de mémoire sont
adjacents.
QUIZ DE FIN DE CHAPITRE

1. Que signifie le drapeau « ios::app »?


2. Pourquoi est-il important de fermer un fichier?
3. Quel opérateur est utilisé pour écrire des données dans un fichier?
4. Que signifie .eof()?
5. De quoi a besoin chaque fonction récursive?
6. Que veut dire #ifudef?
7. Qu’est-ce qu’effectue l’opérateur « ## »?
8. Quand devrait être utilisé « delete[] »?
9. Que retourne l’opérateur « nouveau » (« new »)?
10. Quelle est l’utilité d’ajouter 1 à un pointeur integer?

RÉPONSES

1. Le fichier s’ouvrira en mode ajouter


2. Car cela relâche des ressources attachées au fichier
3. L’opérateur d’insertion (<<)
4. Il s’agit d’une méthode qui retourne « vrai » lorsque la fin d’un fichier est
atteinte
5. D’une condition d’arrêt qui prévient et permet d’éviter une boucle infinie
6. Le code encapsulé sera inclus si l’alias est indéfini
7. Il concatène et remplace deux valeurs
8. Lorsque l’on souhaite supprimer un bloc de mémoire assignée par
l’utilisateur
9. Un pointeur dirigé vers un nouvel emplacement de mémoire
10. Si l’integer est de 4 bytes, il déplacera tout le long 4 bytes

Vous aimerez peut-être aussi