Riadh HARIZI
Riadh HARIZI
PRÉSENTATION DU MODULE
12 semaines : 12 cours / 12 TPs
Plan
Validation du module
Objectifs
Bibliographie
C++
CONTACTS
•
PLAN DES COURS
• 12 cours sur le semestre
• Plan des cours prévisionnel
• Aspects impératifs du C++, éléments de syntaxe, structures de contrôles, fonctions,
pointeurs, tableaux et références.
• Structures de données et types utilisateurs
• Objets & classes, constructeurs, destructeurs
• Surdéfinition d’opérateurs
• Héritage simple, Héritage multiple, polymorphisme
• Template et métaprogrammation
• Entrées/sorties
• Standard Template Library (STL)
• Outils : makefile / débuggeurs …
C++
VALIDATION
Pour valider le module :
Contrôle des connaissances :
• Un TP noté : 1/3 de la note finale
• Un examen de fin de semestre : 2/3 de la note finale
/ C++
OBJECTIFS
Une introduction au langage C++ ainsi qu’au paradigme objet.
L’objectif est de faire découvrir le langage, d’être capable d’écrire et de
concevoir un programme C++ simple de bout en bout.
/ C++
BIBLIOGRAPHIE
• Apprendre le C++ de Claude Delannoy (sur lequel s’appuie en partie
ce cours)
• Pour les puristes : Le langage C++ de Bjarne Stroustrup
• Pour les curieux : LE LANGAGE C. Norme ANSI de Brian-W
Kernighan, Denis-M Ritchie
• Le site de référence : [Link]
/ C++
/ C++
PETITE HISTOIRE DU C/C++
Le C a été inventé au cours de l’année 1972
dans les laboratoires Bell par Dennis Ritchie
et Ken Thompson.
En 1978, Brian Kernighan, qui aida à
populariser le C, publia le livre « The C
programming Language », le K&R, qui décrit
le C « traditionnel » ou C ANSI.
Ken Thompson (à gauche) et Dennis Ritchie (à droite).
(source Wikipédia)
/ C++
PETITE HISTOIRE DU C/C++
Dans les années 80, Bjarne Stroustrup
développa le C++ afin d’améliorer le C, en lui
ajoutant des « classes ». Le premier nom de
ce langage fut d’ailleurs « C with classes ».
Ce fut en 1998 que le C++ fut normalisé pour
la première fois. Une autre norme corrigée fut
adoptée en 2003.
Une mise à jour importante fut C++11, suivie
de C++14, ajoutant de nombreuses
fonctionnalités au langage.
Toutes ces normes permettent une écriture
indépendante du compilateur. Le C++ est le
même partout, pourvu qu’on respecte ces
Bjarne Stroustrup (source Wikipédia)
normes.
/ C++
ASPECT IMPÉRATIF DU C++
Le C++ est une surcouche de C, avec quelques incompatibilités de
syntaxe
Un programme C est la plupart du temps un programme C++.
Donc on peut faire du « C+ » c’est-à-dire du C++ sans objet.
Impératifs : les instructions se suivent dans un ordre précis et transmis
au processeur de la machine dans cet ordre.
Impératif et objet ne se contredisent pas, C++ est un langage multi-
paradigmes. Il respecte à la fois le paradigme objet et impératif.
On va donc commencer par faire du C++ impératif.
/ C++
[Link]
Comme dans la plupart des cours de programmation, on commence par
un HelloWorld : Programme le plus simple qui affiche un message à
l’écran.
Dans un éditeur de texte quelconque. (notez ici la coloration syntaxique)
#include <iostream>
int main()
{
std::cout << "Hello World" << std::endl;
/ C++
COMPILATION ET EXÉCUTION DU
HELLOWORLD
roland@DESKTOP-M1EA3EP ~
$ g++ [Link] -o HelloWorld
roland@DESKTOP-M1EA3EP ~
$ ./[Link]
Hello World
/ C++
LE HELLOWORLD LIGNE À LIGNE
#include <iostream>
En C++ comme en C, les lignes commençant par # sont des
« directives préprocesseurs ». Elles s’adressent à un programme appelé
préprocesseur, cpp (pour « c preprocessor ») qui prépare le code source
en traitant ces directives.
Ici, en appelant #include, on dit au préprocesseur d’inclure le fichier
iostream, de la bibliothèque standard C++ et qui contient les définitions
pour afficher quelque chose à l’écran via des « flots ».
/ C++
LE HELLOWORLD LIGNE À LIGNE
int main()
Il s’agit de l’entête de la fonction main. En C++, une fonction se divise en deux
parties principales. L’entête et le corps de la fonction.
On peut voir ici trois éléments fondamentaux dans l’écriture d’une fonction.
main est le nom de la fonction. En C++, c’est aussi le point d’entrée du programme.
Nous verrons plus tard ce que cela signifie. Il faut juste retenir que main est la seule
fonction qui doit absolument apparaitre dans un programme. C’est une convention.
int est le type de retour de la fonction main. int pour integer, c’est-à-dire que la
fonction main, une fois terminée, doit retourner une valeur entière. Pour
information, cette valeur peut servir dans l’environnement appelant notre
programme une valeur de bonne exécution ou un code erreur.
() ici vide, il s’agit de la liste des arguments fournis lors de l’appel de notre fonction.
/ C++
LE HELLOWORLD LIGNE À LIGNE
{
std::cout << "Hello World" << std::endl;
}
Le corps de la fonction main.
Le corps d’une fonction se situe après l’entête de celle-ci et entre deux accolades. Elles définissent en fait le
bloc d’instruction de la fonction.
Ici, il n’y a qu’une seule instruction, se terminant par un ;
std::cout peut être vu comme l’écran, il s’agit du flot de sortie standard.
<< est un opérateur opérant sur un flot de sortie à sa gauche et une donnée à lui transmettre, à sa droite.
« Hello World » est une chaine de caractère, c’est-à-dire un emplacement mémoire contigüe contenant
un caractère par octet de mémoire, et se terminant conventionnellement par le caractère nul. Nous verrons cela
plus en détail lorsque nous reparlerons des types de données.
std::endl demande au flux de passer à la ligne suivante.
/ C++
COMPILATION ET EXÉCUTION DU
HELLOWORLD
roland@DESKTOP-M1EA3EP ~
$ g++ [Link] -o HelloWorld
roland@DESKTOP-M1EA3EP ~
$ ./[Link]
Hello World
Comme il s’agit d’un programme simplissime, la ligne de compilation est elle-même très simple. En la
décomposant élément par élément :
g++ est le nom du compilateur c++ de GNU, celui utilisé pour ce cours.
[Link] est le nom du fichier dans lequel on vient d’écrire notre code.
-o HelloWorld est une option transmise au compilateur lui demandant de créer un fichier exécutable
portant ce nom là. Il s’agit d’un argument optionnel. Notre programme se nommerait [Link] (sous Linux, [Link]
sous windows), sans cet argument.
./[Link] dans un shell, permet d’exécuter notre programme, qui, comme prévu, affiche Hello
World et se termine.
/ C++
ORGANISATION D’UN PROGRAMME EN C++
Le code source d’un programme est un ensemble de fichiers textes qui contiennent les déclarations et les
définitions des différents éléments qui seront ensuite transmises au compilateur.
Un fichier se décompose généralement en 2 ou 3 parties.
Les directives préprocesseur (souvenez vous, elles commencent par #) se situent généralement en début de
fichier.
Viennent ensuite les définitions et déclarations de variables ou de fonctions ou de type de données. Il ne s’agit
pas d’instructions à proprement parlé, mais plutôt d’informations qui permettront au compilateur de vérifier la
cohérence du code écrit ensuite. Cela peut être assez long, et, souvent le programmeur le déplace dans un fichier
header suffixé en .h ou .hh qui sera inclus via une directive préprocesseur.
Enfin viennent les définitions des fonctions, le code du programme à proprement parlé, dans un fichier suffixé
en .cpp pour le C++, .c pour le C.
/ C++
ORGANISATION D’UN PROGRAMME EN C++
#include <iostream> Voici l’exemple d’un programme un peu plus
/* complexe.
Déclaration de la fonction somme
*/ On commence par déclarer une fonction somme,
double somme(double a, double b);
sans la définir, c’est-à-dire sans écrire son code.
// point d'entrée de notre programme
int main()
On signifie seulement qu’il existe une telle
{
int a, b; fonction, prenant deux réels en argument et
std::cout << "Donnez deux entiers" << std::endl; renvoyant un réel.
std::cin >> a >> b;
std::cout << a << " + " << b << "=" << somme(a, b) << std::endl; On peut à présent l’utiliser dans le code qui suit la
}
return 0; déclaration. On pourrait aussi, et on doit même le
// somme retourne la somme de a et b deux réels fournis en paramétres. faire pour plus de propreté, écrire cette partie
double somme(double a , double b)
{ dans un fichier header.
return a+b;
}
/ C++
ORGANISATION D’UN PROGRAMME EN C++
#ifndef SOMME_HH Un tel fichier header ressemblerait à celui-ci .
#define SOMME_HH
On voit apparaitre 3 nouvelles directives préprocesseurs ainsi que la
/*
déclarations de la fonction somme.
Déclaration de la fonction somme On remarquera aussi que ce fichier NE CONTIENT PAS DE CODE,
*/ seulement des déclarations.
double somme(double a, double b);
#endif
En C++, avant d’utiliser une variable, une constante ou une fonction, on doit la déclarer, ainsi que
son type. Ainsi, le compilateur pourra faire les vérifications nécessaires lorsque celle-ci sera utilisée
dans les instructions.
Exemple de déclarations :
int i; // On déclare une variable de type entier.
float x; // On déclare une variable de type flottant (approximation
d’un nombre réel)
const int N = 5; // On déclare une constante N de type entier dont
// la valeur est 5.
/ C++
VARIABLES
Une variable, comme son nom l’indique, est un espace mémoire dont le contenu peut varier au cours de
l’exécution.
#include <iostream>
Représentation en mémoire de a = 0
using namespace std;
a
00000000 00000000 00000000 00000000
int main() … 101 102 103 104 105 106 107 108 …
{
int a;
a = 0;
cout << "a vaut : " << a << endl;
Puis on lui donne la valeur 5, son emplacement
a = 5; en mémoire n’a pas changé, mais le contenu si.
cout << "a vaut à present : " << a << endl; a
00000000 00000000 00000000 00000101
} … 101 102 103 104 105 106 107 108 …
Master IM / C++
/ C++
TYPES DE DONNÉES
Le C++ est un langage « fortement typé »
La compilation permet de détecter des erreurs de typage.
Chaque variable d’un programme a un type donné tout au long de son
existence.
Un type peut représenter une valeur numérique, sur 1, 2 ou 4 octets,
signé ou non, un nombre à virgule flottante dont l’encodage en mémoire
est assez complexe.
/ C++
TYPES DE DONNÉES NUMÉRIQUES
#include <iostream>
int main()
{
int a; // On déclare un entier a; On réserve donc 4 octets en mémoire que l’on nomme a
unsigned int b; // On déclare un entier non signé b, 4 octets sont aussi alloués
char c; // On déclare un « caractère » c, un octet est réservé
double reel1, reel2; //deux réels sont déclarés et la place correspondantes en mémoire est allouée
a = 0; //On attribue à a la valeur 0, jusqu’à maintenant, il n’y a pas de règle quant à sa valeur
b = -1; //On essaye de donner une valeur négative à b !
c = 'a'; //’a’ est la notation pour le caractère a.
reel1 = 1e4; //reel1 prend la valeur 10.000
reel2 = 0.0001;
std::cout << "a : " << a << " " << std::endl
<< "Interessant : "
<< "b : " << b << std::endl // HA !
<< "c ; " << c << " " << std::endl;
std::cout << reel1 << std::endl;
std::cout << reel2 << std::endl;
/ C++
TYPES DE DONNÉES ALPHABETIQUES
Un caractère (de type char) est un
élément de la table ASCII codé sur un
octet. Il s’agit en fait d’un nombre entre
0 et 127.
Le caractère ‘a’ est donc la valeur 97
‘A’ se code 65
Il s’agit d’un jeu de caractère particulier.
Il y en a beaucoup d’autre, Unicode par
exemple.
/ C++
TYPES DE DONNÉES ALPHABÉTIQUES
Ainsi, c = 'a';
/ C++
OPÉRATEURS ARITHMÉTIQUES
/ C++
OPÉRATEURS ARITHMÉTIQUES
Ces opérateurs binaires ne sont à priori définis que pour des opérandes
de même type parmi
Int, long int, float, double, et long double
Alors comment faire pour ajouter 1 (entier int ) et 2.5 (flottant simple
précision) ? ce qui semble assez naturel ? par le jeu des conversions
implicites de type. Le compilateur se chargera de convertir un opérande
ou les deux dans un type rendant l’opération possible.
/ C++
OPÉRATEURS ARITHMÉTIQUES
#include <iostream>
int main()
{
int a = 1;
double b = 3.14;
double c = a +b;
std::cout << sizeof(a) << ": " << a << std::endl;
std::cout << sizeof(b) << ": " << b << std::endl;
std::cout << sizeof(c) << ": " << c << std::endl;
Master IM / C++
OPÉRATEURS ARITHMÉTIQUES
OPÉRATEUR %
Reste de la division entière : n’est défini que sur les entiers en C++ (ce
n’est pas le cas en Java par exemple)
Pour les entiers négatifs : le résultat dépend de l’implémentation ! ne
pas utiliser !
Par ailleurs, il est à noter que la division / est différente suivant le type
des opérandes :
S’ils sont entiers alors la division est entière, sinon s’il s’agit de
flottants, la division sera réelle.
/ C++
OPÉRATEURS ARITHMÉTIQUES
Il n’y a pas d’opérateur pour la puissance : il faudra alors faire appel aux
fonctions de la bibliothèques standard du C++.
/ C++
OPÉRATEURS RELATIONNELS ET DE
COMPARAISONS
Il s’agit des opérateurs classiques, vous les connaissez déjà.
Ils ont deux opérandes et renvoient une valeur booléenne
<, > , <=, >= , == , !=
Les deux derniers sont l’égalité et la différence.
En effet = est déjà utilisé pour l’affectation !
/ C++
PETITE FACÉTIE DES OPÉRATEURS D’ÉGALITÉ
SUR LES DOUBLES.
#include <iostream>
int main()
{
double a = 3.6;
double b = 4.5;
double c = 8.1;
if(a + b == c) {
std::cout << "a+b=c" << std::endl;
}
else {
std::cout << "a+b != c" << std::endl;
}
if(a == c-b) {
std::cout << "a = c-b" << std::endl;
}
else {
std::cout << "a != c-b" << std::endl;
}
}
/ C++
OPÉRATEURS LOGIQUES
En C++ il y a trois opérateurs logiques : et (noté && ) ou noté (||) et
non (noté !)
Ces opérateurs travaillent sur des valeurs numériques de tout type avec
la simple convention
Nulle faux
Autre que nulle vrai
/ C++
COURT-CIRCUIT DANS L’ÉVALUATION DES
OPÉRATEURS LOGIQUES
La seconde opérande d’un opérateur n’est évalué que lorsque sa
connaissance est indispensable
Typiquement, si on sait déjà par son premier opérande qu’un ‘ou’ ou
un ‘et’ seront vrai ou faux, on n’évalue pas la deuxième partie.
On peut - mais ce n’est qu’un exemple - protéger une portion de code :
if (ptr != 0 && *ptr == 8)
Dans cet exemple, on vérifie d’abord que ptr n’est pas nul avant de le
déréférencer pour comparer sa valeur pointée en mémoire.
/ C++
OPÉRATEURS D’AFFECTATION ÉLARGIE
C++ permet d’alléger la syntaxe de certaines expressions en donnant la
possibilité d’utiliser de condenser des opérations classiques du type:
variable = variable opérateur expression
Ainsi, au lieu d’écrire a = a * b
On pourra écrire a *= b;
Liste des opérateurs d’affectation élargie
+= -= *= /= %=
|= ^= &= <<= >>=
/ C++
OPÉRATEUR CONDITIONNEL
Il s’agit d’un opérateur ternaire.
Il permet des affectations du type :
Si condition est vraie alors variable vaut valeur, sinon variable vaut
autre valeur.
On l’écrit de la manière suivante :
x = (cond) ? a : b;
Par exemple :
int x = (y > 0) ? 2 :3;
/ C++
AUTRES OPÉRATEURS
sizeof : Son usage ressemble à celui d’une fonction, il permet de
connaitre la taille en mémoire de l’objet passé en paramétre.
Opérateurs de manipulation de bit :
& ET bit à bit
| OU bit à bit
^ OU Exclusif bit à bit
<< Décalage à gauche
>> Décalage à droite
~ Complément à un (bit à bit)
/ C++
/ C++
STRUCTURES DE CONTRÔLES
Un programme est un flux d’instructions qui est exécuté dans l’ordre. Pour
casser cette linéarité et donner au programme une relative intelligence, les
langage de programmation permettent d’effectuer des choix et des boucles.
On va parler de blocs d’instructions :
Il s’agit d’un ensemble d’instructions entouré d’accolades ouvrantes et
fermantes.
{
a = 5;
…
}
/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION IF – SYNTAXE
if (expression)
instruction_1
instruction_2
/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION SWITCH – SYNTAXE
switch (expression)
{ case constante_1 : [instruction_1]
case constante_2 : [instruction_2]
...
case constante_n : [instruction_n]
[default : suite_instruction]
}
/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION DO … WHILE
do
instruction
while (expression);
/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION WHILE
while (expression)
instruction
permet de répéter une ou plusieurs instructions tant que la condition
expression est vrai.
A noté que :
• Il faut s’assurer que expression peut devenir fausse (sinon on ne sort jamais
de la boucle !!)
• L’expression est évaluée avant l’exécution des instructions. Celles-ci ne sont
donc pas forcément exécutées.
/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION FOR – « BOUCLE AVEC COMPTEUR »
for (expression_declaration; expression_2; expression_3)
instruction
permet de répéter une ou plusieurs instructions avec une syntaxe parfois plus
pratique que les boucles while.
expression_declaration va permettre d’initialiser le compteur de boucle.
expression_2 une condition sur le compteur pour arrêter la boucle.
expression_3 l’incrémentation du compteur.
/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION FOR – « BOUCLE AVEC COMPTEUR » - UN EXEMPLE SIMPLE
#include <iostream> Ce programme, une fois compilé et exécuté roland@DESKTOP-M1EA3EP ~
using namespace std; affichera simplement à l’écran les nombres $ g++ [Link]
int main(){ de 0 à 9.
for (int i = 0; i < 10; i++) On aurait pu évidemment ce résultat avec roland@DESKTOP-M1EA3EP ~
{
une boucle while.
$ ./[Link]
cout << "i = " << i << endl; i = 0
} i = 1
} i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9
/ C++
STRUCTURES DE CONTRÔLES
BREAK, CONTINUE ET GOTO
/ C++
/ C++
LES FONCTIONS
Pour structurer un programme, un des moyens les plus courants est de
diviser le code en briques appelées fonction.
Le terme n’est pas strictement équivalent au terme mathématique.
En effet, une fonction permet de renvoyer un résultat scalaire, mais pas
seulement :
Elle peut modifier les valeurs de ses arguments, ne rien renvoyer, agir
autrement qu’en renvoyant une valeur : affichage, ouverture et écriture
dans un fichier etc.
/ C++
LES FONCTIONS
UN EXEMPLE DE FONCTION : LA FONCTION PUISSANCE
#include <iostream> Voici un exemple de fonction.
double my_pow(double a, unsigned int exp)
{ La fonction my_pow prend en argument un flottant
double res = 1; et un entier non signé et retourne une valeur de type
flottant.
res est une variable locale à la fonction qui permet de
for (int i = 0; i < exp; i++) stocker les valeurs intermédiaires du calcul qui est
res *= a; effectué dans la boucle.
return res; Le résultat de la fonction, un double, est donné grâce
} au mot clé return.
A noté que return marque la fin de l’exécution de la
int main()
fonction : les instructions qui se trouvent après ne
{ sont jamais exécutées.
std::cout << "2^5 = " << my_pow(2.0,5) << std::endl; Une fonction peut contenir plusieurs return (dans
} des conditions par exemple) ou aucun, si la fonction
ne renvoie rien.
/ C++
LES FONCTIONS
DECLARATION DE FONCTIONS
/ C++
LES FONCTIONS
PASSAGE PAR VALEUR
#include <iostream>
Quand on exécute ce programme, on
/*
Cette fonction doit échanger la valeur des remarque qu’il ne fait pas ce qu’on veut.
deux entiers passés en paramètres */
void my_swap(int, int);
Les valeurs de a et de b sont les mêmes avant
int main()
{
et après l’appel à la fonction my_swap.
int a = 2, b = 3;
std::cout << "a : " << a << " b : " << b << std::endl;
my_swap(a, b);
Pourquoi ?
}
std::cout << "a : " << a << " b : " << b << std::endl;
Par défaut en c++, le passage des arguments à
void my_swap(int a, int b){ une fonction se fait « par valeur ».C’est à dire
int tmp = a;
a = b; que la valeur du paramètre est copiée en
}
b = tmp;
mémoire, et une modification sur la copie
n’entraîne évidement pas la modification de
l’original.
/ C++
LES FONCTIONS
PASSAGE PAR REFERENCE
#include <iostream>
Modifions la fonction my_swap.
/*
Cette fonction doit échanger la valeur des
deux entiers passés en paramètres */ Cette fois-ci le programme a bien l’effet désiré!
void my_swap(int &, int &);
}
std::cout << "a : " << a << " b : " << b << std::endl; un entier par valeur mais par référence. Il n’y a
void my_swap(int & a, int & b){
donc plus copie de la valeur. On passe
int tmp = a;
a = b;
directement la valeur elle-même.
b = tmp;
}
Une modification dans la fonction est donc
répercutée sur les paramètres transmis.
/ C++
LES FONCTIONS
PASSAGE PAR REFERENCE
#include <iostream>
/*
Quand on tente de compiler ce programme, le
Cette fonction doit echanger la valeur des
deux entiers passés en paramétres */ compilateur termine en erreur.
void my_swap(int &, int &);
int main()
{
Pourquoi ?
my_swap(2, 3);
}
A la lecture du message, on comprend qu’on ne
void my_swap(int &a, int &b){
int tmp = a;
a = b;
fournit pas à la fonction un paramètre du bon
}
b = tmp;
type.
$ g++ [Link]
[Link]: Dans la fonction ‘int main()’:
[Link]:15: erreur : invalid initialization of non-
En effet, on ne peut pas modifier la constante
const reference of type ‘int&’ from an rvalue of type ‘int’
my_swap(2, 3);
^
2 ou 3 ! Heureusement !
[Link]:6: note : initializing argument 1 of ‘void
my_swap(int&, int&)’
void my_swap(int &, int &);
/ C++
LES FONCTIONS
PASSAGE PAR REFERENCE
#include <iostream>
/*
La fonction my_swap modifie ses paramètres.
Cette fonction doit echanger la valeur des
deux entiers passés en paramétres */ On ne peut donc évidement pas l’appeler avec
void my_swap(int &, int &);
int main()
des arguments constants.
{
}
my_swap(2, 3);
Pour lever cette ambiguïté, on considère
void my_swap(int &a, int &b){
int tmp = a;
qu’une fonction qui ne modifie pas ses
}
a = b;
b = tmp; arguments doit le spécifier dans sa déclaration
$ g++ [Link] en ajoutant le mot clé const au type de ses
[Link]: Dans la fonction ‘int main()’:
[Link]:15: erreur : invalid initialization of non-
const reference of type ‘int&’ from an rvalue of type ‘int’
arguments. Sinon, on considère qu’ils sont
my_swap(2, 3);
^ modifiables.
[Link]:6: note : initializing argument 1 of ‘void
my_swap(int&, int&)’
void my_swap(int &, int &);
/ C++
LES FONCTIONS
VARIABLES GLOBALES
/ C++
LES FONCTIONS
VARIABLES LOCALES
Ce sont les variables les plus couramment utilisées dans un programme informatique
impératif. (de loin !)
Elles sont déclarées dans une fonction, et n’existent que dans celle-ci.
Elles disparaissent (leur espace mémoire est libéré) une fois que la fonction se
termine.
L’appel des fonctions et la création des variables locales repose sur un système LIFO
(Last In – First Out) ou de pile.
Lors de l’appel d’une fonction, les valeurs des variables, des paramètres etc. est
« empilée » en mémoire et « dépilée » lors de la sortie de la fonction.
Le système considère donc que cet espace mémoire est réutilisable pour d’autres
usages !!
/ C++
LES FONCTIONS
SURCHARGE
/ C++
LES FONCTIONS
SURCHAGE – UN EXEMPLE
#include <iostream>
La fonction print_me est définie deux
void print_me(int a) fois. Le nom ne change pas, la valeur de
{
{
Lorsque l’on appelle la fonction, le
std::cout << "Hello ! i m a double ! : " << a << std::endl; compilateur se base sur le type de
}
l’argument pour choisir quelle fonction il
main() va appeler.
{
print_me(2);
Dans certains cas, le compilateur
print_me(2.0); n’arrive pas à faire un choix. Il se
}
terminera alors en erreur.
/ C++
/ C++
TABLEAUX & POINTEURS
PREMIER EXEMPLE
#include <iostream>
using namespace std;
La déclaration int t[10] réserve en
main()
mémoire l’emplacement pour 10
{ éléments de type entier.
int t[10];
Dans la première boucle, on initialise
for (int i = 0; i < 10; i++)
chaque élément du tableau. Le premier
t[i] = i;
étant conventionnellement numéroté 0.
for (int i = 0; i < 10; i++)
Dans la deuxième boucle, on parcourt
cout << "t["<<i<<"]" << " : " << t[i] << endl;
chaque élément du tableau pour
}
l’afficher.
On notera que la notation [] s’emploie
aussi bien pour la déclaration que pour
l’accès à un élément du tableau.
/ C++
TABLEAUX & POINTEURS
QUELQUES REGLES
Il ne faut pas confondre les éléments d’un tableau avec le tableau lui-même.
Ainsi, t[2] = 3, tab[i]++ sont des écritures valides.
Mais t1 = t2, si t1 et t2 sont des tableaux, n’est pas possible.
Il n’existe pas en C++ de mécanisme d’affectation globale pour les tableaux.
/ C++
TABLEAUX & POINTEURS
QUELQUES REGLES
/ C++
TABLEAUX & POINTEURS
QUELQUES REGLES
/ C++
TABLEAUX & POINTEURS
QUELQUES REGLES – UNE PARENTHESE SUR LES OPTIONS DE COMPILATION
$ g++ -std=c++98 -Wall -pedantic -Werror test_tab.cpp
test_tab.cpp: Dans la fonction ‘int main()’:
test_tab.cpp:8:10: erreur : ISO C++ forbids variable length array ‘t’ [-Werror=vla]
int t[n];
^
cc1plus : les avertissements sont traités comme des erreurs
#include <iostream> C’est l’occasion d’ouvrir une parenthèse sur d’autres options du compilateur
using namespace std; g++. Si l’on compile le code ci-contre avec les options ci-dessus, ce code ne
compile pas. En effet, par défaut, un compilateur fera son possible pour
int main() compiler un programme, quitte à ne pas respecter scrupuleusement la norme
{ du langage.
int n = 50; On peut, en rajoutant ces options, forcer le compilateur à respecter la norme.
int t[n]; Ceci a comme but de garantir un maximum de compatibilité et de portabilité si
cout << sizeof(t) << endl; on change de compilateur ou de version de compilateur.
} Pour avoir plus d’information sur ces options, on pourra consulter le manuel
de g++. (man g++)
/ C++
TABLEAUX & POINTEURS
PLUSIEURS INDICES
On peut écrire :
int t[5][3];
Pour réserver un tableau de 15 éléments (5*3) de type entier.
On accède alors à un élément en jouant sur les deux indices du tableau.
Le nombre d’indice peut être quelconque en C++. On prendra néanmoins en
compte les limitations de la machine elle-même comme la quantité de mémoire
à disposition.
/ C++
TABLEAUX & POINTEURS
INITIALISATION
#include <iostream>
Nous avons déjà initialisé des tableaux
using namespace std;
grâce à des boucles.
main()
{ On peut en fait les initialiser « en dur »
int t[10] = {0,2,4,6,8,10,12,15,16,18}; lors de leur déclaration.
for (int i = 0; i < 10; i++)
cout << t[i] << ";";
On utilisera alors la notation {} comme
cout << endl;
dans l’exemple ci contre.
}
/ C++
TABLEAUX & POINTEURS
POINTEURS
Par exemple, ici, on voit que l’octet situé à l’adresse mémoire 42 (en
hexadécimal, noté 0x) contient la valeur ‘a’ (un char suivant la table ascii) .
Le suivant contient lui la valeur ‘b’.
/ C++
TABLEAUX & POINTEURS
POINTEURS – LES OPERATEURS * ET &
#include <iostream>
using namespace std;
On commence par déclarer une variable
main() ptr de type int * : un pointeur sur
{ entier.
int *ptr;
int i = 42; Puis une variable i de type entier.
ptr = &i;
On assigne à ptr l’adresse en mémoire de
cout << "ptr : " << ptr << endl; la variable i, grâce à l’opérateur &.
cout << "*ptr : " << *ptr << endl;
}
On affiche ensuite ce que contient ptr :
une adresse – une valeur qui sera affichée
en hexadécimal.
$ ./[Link]
ptr : 0xffffcbf4 Puis on affiche la valeur pointée par ptr
*ptr : 42 (la même que la valeur de i). On dit que
l’on a déréférencé le pointeur ptr.
C++
TABLEAUX & POINTEURS
POINTEURS – LES OPERATEURS * ET &
adresses valeurs variables Voici une représentation schématisée de
0x0
l’exemple précédent.
On voit bien que la valeur de la variable ptr
…
0x42
est l’adresse en mémoire de la variable i.
0xffffcbf4 ptr
m 0x43 Le type du pointeur est important : il permet
é
m
de connaître la taille en mémoire de la valeur
…
o pointée !
i 0xffffcbf4
r 0xffffcbf5
Pour un type entier, il s’agira des 4 octets
e 0xffffcbf6
42 i suivant l’adresse 0xffffcbf4.
0xffffcbf7 La taille du pointeur lui-même varie en
fonction du nombre de bits du système : 16,
32, ou pour les machines actuelles : 64 bits.
…
/ C++
TABLEAUX & POINTEURS
RELATION TABLEAUX ET POINTEURS
/ C++
TABLEAUX & POINTEURS
POINTEURS – ARITHMETIQUE DES POINTEURS
Une adresse est une valeur entière. Il parait donc raisonnable de pouvoir lui
additionner ou lui soustraire un entier. En suivant toutefois des règles particulières.
Que signifie ajouter 1 à un pointeur sur entier ? Est-ce la même chose que pour un
pointeur sur char par exemple ?
Non.
Ajouter 1 à un pointeur à pour effet de le décaler en mémoire du nombre d’octets
correspondant à la taille du type pointé.
En ajoutant (soustrayant) 1 à un pointeur sur int (float, double, char …), on le
décale en mémoire de la taille d’un int (resp. float, double, char …).
On appelle ce mécanisme l’arithmétique des pointeurs.
/ C++
TABLEAUX & POINTEURS
RELATION TABLEAUX ET POINTEURS
/ C++
TABLEAUX & POINTEURS
POINTEURS PARTICULIERS
• Le pointeur nul, dont la valeur vaut 0. Il est utile car il permet de désigner un
pointeur ne pointant sur rien. Evidemment déréférencer le pointeur nul conduit
irrémédiablement à une erreur de segmentation.
• Le pointeur générique void *.
Un pointeur est caractérisé par deux informations : la valeur de l’adresse pointée
et la taille du type pointé. void * ne contient que l’adresse. Il permet donc de
manipuler n’importe quelle valeur sans soucis de la taille du type. C’était un type
très utile en C, notamment pour écrire des fonctions génériques valables quelque
soit le type des données.
• Par exemple : voici l’entête de la fonction qsort de la bibliothèque standard de
C. Utilisable en C++, mais déconseillée, on a des outils bien plus puissants ! On
notera par ailleurs l’emploi d’un pointeur de fonction, que nous verrons plus tard.
void qsort (void *tableau , size_t nb_elem , size_t taille_elem , int (*compare) (void const *a, void const *b));
/ C++
TABLEAUX & POINTEURS
ALLOCATION STATIQUE ET DYNAMIQUE
/ C++
TABLEAUX & POINTEURS
LES OPERATEURS NEW ET DELETE
/ C++
TABLEAUX & POINTEURS
LES OPERATEURS NEW ET DELETE
/ C++
TABLEAUX & POINTEURS
LES OPERATEURS NEW ET DELETE
Remarques:
• Des précautions doivent être prises lors de l’utilisation de delete.
• delete ne doit pas être utilisé pour des pointeurs déjà détruits.
• delete ne doit pas être utilisé pour des pointeurs obtenus autrement que
par l’utilisation de new.
• Une fois un pointeur détruit, on doit évidement arrêter de l’utiliser.
/ C++
TABLEAUX & POINTEURS
EXEMPLE
#include <iostream>
using namespace std; Ce programme calcule la moyenne des valeurs que
double sum(double *val, int n)
{ l’utilisateur entre au clavier durant l’ exécution.
double res = 0;
for (int i = 0; i < n; i++)
Mais le nombre de ces valeurs varient aussi ! Si
res += val[i]; nous avions alloué un tableau statiquement, sur la
return res;
} pile, comme jusqu’à présent, nous aurions du
int main()
entrer une valeur constante pour sa taille qui
{ aurait pu être soit trop grande, soit trop petite.
int n;
double *val; On notera
cout << "nombres de valeurs : "; • l’utilisation de delete qui permet de libérer
cin >> n;
val = new double[n];
notre pointeur proprement à la fin de notre
for (int i = 0; i < n; i++) { programme.
cout << i << " : " ;
cin >> val[i]; • L’utilisation du pointeur val avec la notation
}
cout << "Moyenne de ces valeurs : " << sum(val,n) / n << endl;
indicielle [], au lieu d’utiliser l’arithmétique
delete val; des pointeurs.
}
/ C++
TABLEAUX & POINTEURS
POINTEURS SUR FONCTIONS
/ C++
TABLEAUX & POINTEURS
POINTEURS SUR FONCTIONS
#include <iostream>
using namespace std; On définit deux fonctions ayant même valeur de retour et même
double fct1(double x){
return x*x;
type d’argument, fct1 et fct2.
} La fonction aff_tab n’est là que pour aider, et affiche un
double fct2(double x){
return 2*x;
tableau de double donné en paramètre.
} La nouveauté se situe dans la fonction apply qui va appliquer
void apply(double *val, int n, double (*fct)(double)){
sur chaque éléments d’un tableau de n éléments la fonction
for (int i = 0; i < n; i++)
val[i] = (*fct)(val[i]); passée en paramètre, à l’aide d’un pointeur.
} On notera que pour appeler la fonction pointée, il faut la
void aff_tab(double *val, int n){
for (int i = 0; i < n; i++)
déréférencer, toujours à laide de l’opérateur *.
cout << i << " : " << val[i] << endl; Cela permet d’écrire des fonctions génériques puissantes et se
}
main()
passer de l’écriture de code redondants !
{ En effet, on aurait pu appliquer des fonctions de la librairie math
double t[10] = {1,2,3,4,5,6,7,8,9,10};
comme cos ou sqrt, sans réécrire pour chaque cas une boucle
aff_tab(t, 10);
apply(t, 10, fct1);
pour l’appliquer à chaque éléments de ce tableau.
aff_tab(t, 10);
}
TABLEAUX & POINTEURS
CHAINES DE CARACTERES EN C
#include <iostream>
Les chaines de caractères telles qu’elles
sont représentées en C sont toujours
using namespace std;
valables en C++.
main() Bien que celui-ci offre d’autres
{ mécanismes de plus haut niveau pour la
char *str = "Hello World"; manipulation de chaines, nous allons
char str2[10] = {'C','o','u','c','o','u','\0'}; étudier celles-ci.
int i = 0;
D’une part car c’est un bon exercice sur
cout << str << endl;
les pointeurs.
while(str2[i++]) cout << str2[i-1]; Pour comprendre ce qu’est une chaine
} de caractères.
Car elles sont encore utilisées en C++.
/ C++
TABLEAUX & POINTEURS
CHAINES DE CARACTERES EN C
#include <iostream>
En C, les chaines de caractères peuvent
using namespace std;
êtres vues comme des tableaux de char.
il y a néanmoins une convention
main() supplémentaire.
{
char *str = "Hello World";
Ils s’agit d’un ensemble d’octets contigüe se
char str2[10] = {'C','o','u','c','o','u','\0'}; terminant par le caractère nul noté \0. Ceci
int i = 0; afin de donner une fin à la chaine.
La notation "Hello World" définie donc un
cout << str << endl;
while(str2[i++]) cout << str2[i-1];
pointeur sur caractère vers une zone de la
}
mémoire où est définie la constante
« hello world ». On récupère cette
adresse dans le pointeur str.
/ C++