0% ont trouvé ce document utile (0 vote)
36 vues89 pages

Riadh HARIZI

Le module de C++ comprend 12 cours et TPs sur des sujets tels que la syntaxe, les structures de données, et la programmation orientée objet. Pour valider le module, les étudiants doivent réussir un TP noté et un examen final. La bibliographie inclut des ouvrages de référence sur le C++ et son histoire, ainsi que des ressources en ligne.

Transféré par

harizi
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
36 vues89 pages

Riadh HARIZI

Le module de C++ comprend 12 cours et TPs sur des sujets tels que la syntaxe, les structures de données, et la programmation orientée objet. Pour valider le module, les étudiants doivent réussir un TP noté et un examen final. La bibliographie inclut des ouvrages de référence sur le C++ et son histoire, ainsi que des ressources en ligne.

Transféré par

harizi
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

C++ 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

Il s’agit simplement d’une protection, évitant lors de programme plus complexe,


d’inclure deux fois un fichier header. Le compilateur terminerait alors en erreur car il
ne veut pas de multiples définitions (surtout lorsqu’on définira des class).
#ifndef SOMME_HH
#define SOMME_HH En gros, si la constante SOMME_HH n’est pas définie, alors inclure le code ci
après.
Dans le code en question, on commence par définir une tel constante, et on déclare
notre fonction.
Enfin, on ferme la condition #ifndef par #endif
DÉCLARATIONS

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';

Donne la valeur 97 à la variable c.


Lorsqu’on affiche c. Ce n’est pas 97 qui apparait, mais ‘a’
car le compilateur sait qu’on veut afficher un caractère
et non sa valeur, grâce à son type.

c = 'a' + 1; Mais il s’agit bien d’un nombre, comme


l’indique cet exemple, valide en C++,
qui affiche le caractère suivant ‘a’ dans
la table.
Soit ‘b’ !
/ C++
Master IM / C++
OPÉRATEURS
Il y a des nombreux opérateurs en C++
Classiques :
Arithmétiques, relationnels, logiques
Moins classiques :
Manipulation de bits
Et des opérateurs originaux, d’affectation, d’incrémentation

/ C++
OPÉRATEURS ARITHMÉTIQUES

C++ dispose d’opérateurs classiques binaires (deux opérandes)


Addition +, Soustraction -, multiplication * et division /
Il y a aussi un opérateur unaire : - pour les nombres négatifs. ( -x + y)

/ 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;

Aura comme sorti :


$ ./[Link]
4: 1
8: 3.14
8: 4.14

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++.

En termes de priorité, elles sont les mêmes que dans l’algèbre


traditionnel. Et en cas de priorité égale, les calculs s’effectuent de
gauche à droite.

On peut également se servir de parenthèses pour lever les ambiguïtés,


et rendre son code plus lisible !!

/ 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

else // l’instruction else est facultative

instruction_2

expression est une expression quelconque avec la convention


Différente de 0  vrai
Egale à 0  faux
Instruction_1 et instruction_2 sont des instructions quelconques i.e. :
- Simple (terminée par un point virgule)
- bloc
- Instruction structurée

/ 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]
}

permet dans certain cas d’éviter une abondance d’instruction if imbriquées.


expression est une expression quelconque comme dans le cas de if, dont la valeur va être testé contre les constantes.
constante : expression constante de type entier (char est accepté car converti en int)
Instruction : suite d’instruction quelconque
Petite subtilité : Une fois un cas positif trouvé, les instructions suivantes sont exécutées. Même si elles appartiennent à un
autre cas. Ce peut être pratique, mais pas toujours. Pour éviter cela, on utilisera l’instruction break qui stoppe le flot
d’exécution.

/ C++
STRUCTURES DE CONTRÔLES
L’INSTRUCTION DO … WHILE
do
instruction
while (expression);

permet de répéter une ou plusieurs instructions tant que la condition


expression est vrai.
A noter que :
• La série d’instruction est exécutée au moins une fois.
• Il faut s’assurer que expression peut devenir fausse (sinon on ne sort jamais
de la boucle !!)

/ 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

Instructions de branchement inconditionnel :


• break et continue s’utilisent principalement dans des boucles afin de contrôler
plus finement le flux d’exécution.
• break permet de sortir de la boucle à n’importe quel moment (souvent une
condition validée dans la boucle par un if)
• continue va stopper prématurément le tour de boucle actuel et passer
directement au suivant.
• goto est une instruction déconseillée, elle s’utilise conjointement à des étiquettes
dans le code et permet d’y aller directement. Même si cela semble intéressant en
première approche, son usage sera interdit lors de ce cours.

/ 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

Avant de pouvoir utiliser une fonction, c’est-à-dire de l’appeler, il est nécessaire


que le compilateur « connaisse » la fonction. Il pourra ainsi réaliser les contrôles
nécessaires qui pourront donner lieu à des erreurs de compilation le cas
échéant.
Ainsi, on prendra soin d’écrire le « prototype » de la fonction :
Pour my_pow, double my_pow(double, unsigned int);
• Il n’est pas nécessaire de préciser le nom des paramètres dans ce cas.
• La déclaration se termine par un point virgule

/ 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 &);

int main() Pourquoi ?


{
int a = 2, b = 3;
std::cout << "a : " << a << " b : " << b << std::endl; La notation ‘int &’ signifie qu’on ne passe plus
my_swap(a, b);

}
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

La portée d’une variable peut varier.


On dit qu’une variable est globale lorsque la portée de celle-ci s’étend sur une portion
de code ou de programme groupant plusieurs fonctions. On les utilise en générale
pour définir des constantes qui seront utilisées dans l’ensemble du programme, par
exemple si nous devions définir dans une bibliothèque de maths la valeur PI. Elles sont
définies hors de toute fonction, ou dans un fichier header, et sont connues par le
compilateur dans le code qui suit cette déclaration.
Leur utilisation est cependant déconseillée tant elle peuvent rendre un code
compliqué à comprendre et à maintenir.
Nous ne nous attarderons pas sur elles pour l’instant, il faut juste savoir que cela
existe.

/ 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

Aussi appelé overloading ou surdéfinition.


Un même symbole possède plusieurs définitions. On choisit l’une ou l’autre de
ces définitions en fonction du contexte.
On a en fait déjà rencontré des operateurs qui étaient surchargés.
Par exemple + peut être une addition d’entier ou de flottants en fonction du
type de ses opérandes.
Pour choisir quelle fonction utiliser, le C++ se base sur le type des arguments.

/ 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
{

std::cout << "Hello ! i m an integer ! : " << a << std::endl;


retour ne change pas.
}
Le type du paramètre change.
void print_me(double a)

{
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

Les indices peuvent prendre la forme de n’importe quelle expression


arithmétique d’un type entier.
Par exemple, si n, p k et j sont de type int, il est valide d’écrire :
t[n-3], t[3*p-2*k+j%1]
Il n’existe pas de mécanisme de contrôles des indices ! Il revient au
programmeur de ne pas écrire dans des zones mémoires qu’il n’a pas alloué.
Source de nombreux bugs ….

/ C++
TABLEAUX & POINTEURS
QUELQUES REGLES

En C ANSI et en iso C++, la dimension d’un tableau (son nombre d’éléments) ne


peut être qu’une constante, ou une expression constante. Certains
compilateurs l’acceptent néanmoins en tant qu’extension du langage.
const int N = 50;
int t[N]; // Est valide quelque soit la norme et le
compilateur
int n = 50;
int t[n]; // n’est pas valide systématiquement et
doit être utilisé avec précaution.

/ 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

C++ permet, comme C, un contrôle fin de la mémoire. En effet, il permet, grâce


aux pointeurs, d’accéder directement à des zones de la mémoire, en travaillant
sur les adresses.
mémoire

adresses 0x0 … 0x42 0x43 …

valeurs ’a’ ‘b’

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

En C++, l’identificateur d’un tableau (sans indice à sa suite) est considéré


comme un pointeur.
Par exemple, lorsqu’on déclare le tableau de 10 entiers
int t[10]
La notation t est équivalente à &t[0], c’est-à-dire à l’adresse de son
premier élément.

/ 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

On sait maintenant qu’un tableau peut être considéré comme un pointeur.


Plus précisément, il s’agit d’un pointeur constant. Pour accéder aux éléments
d’un tableau, on a donc deux possibilités :
- La notation indicielle : t[5]
- La notation pointeur : *(t+5)
Attention:
• La priorité des operateurs est importante : *(t+5) ≠ *t + 5
• Un nom de tableau est un pointeur constant ! On ne peut pas écrire tab
+= 1 ou tab = tab + 1 ou encore tab++ pour parcourir les
éléments d’un tableau.

/ 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

main variables de la fonction main Ceci est une représentation schématisée de


la mémoire occupée par un processus au
PILE (stack) fct_1 variables et arguments de la fonction fct_1 appelée dans main
cours de son exécution.
On connait déjà la pile (ou stack en anglais)
fct_2 variables et arguments de la fonction fct_2 appelée dans fct_1
qui contient les variables et les tableaux
que l’on a déclaré jusqu’à présent.
MÉMOIRE

La pile peut grandir en occupant la mémoire libre


Le tas (ou heap) est une zone de la mémoire
mémoire libre qui peut grandir au fil de l’ exécution et
dont le contenu est géré par le
Le tas peut grandir en occupant la mémoire libre programmeur. Mais,
« Un grand pouvoir implique de grandes
Le tas offre un espace de mémoire dite d'allocation dynamique. C'est un responsabilités ! »
espace mémoire qui est géré par le programmeur, en faisant appel aux
TAS (heap)
opérateurs d'allocation new pour allouer un espace et delete pour liberer cet
espace.

/ C++
TABLEAUX & POINTEURS
LES OPERATEURS NEW ET DELETE

new est un opérateur unaire prenant comme argument un type.


new type où type représente un type quelconque.
Il renvoie un pointeur de type type* dont la valeur est l’adresse de la zone
mémoire allouée pour notre donnée de type type.
int *ptr = new int;
On peut maintenant utiliser notre pointeur pour accéder à un entier que l’on a alloué
en mémoire.
Une autre syntaxe permet d’allouer un espace mémoire contiguë pour plusieurs
données à la fois. Le pointeur renvoyé, toujours de type type* pointe vers la
première valeur allouée.
int* ptr2 = new int[10];
L’allocation peut elle échouer ? Si oui que se passe-t-il ?

/ C++
TABLEAUX & POINTEURS
LES OPERATEURS NEW ET DELETE

• On ne peut évidemment pas allouer indéfiniment de la mémoire, celle-ci


étant finie. Un programme trop gourmand risque de mettre le système
entier en danger et bien souvent celui-ci préfèrera le terminer de manière
brutale.
• delete est l’opérateur permettant de faire le ménage dans la mémoire en
libérant l’espace qui ne sert plus.
• Lorsque qu’un pointeur ptr a été alloué par new, on écrira alors
delete ptr pour le libérer.

/ 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

Lorsqu’un exécutable est chargé en mémoire, ses fonctions le sont évidemment


aussi. Par voie de conséquence, elles ont donc une adresse, que C++ permet de
pointer.
Si nous avons une fonction, dont le prototype est le suivant :
int fct(double, double);
Un pointeur sur cette fonction sera déclaré de la façon suivante :
int (* fct_ptr)(double, double); // et le pointeur s’appellera fct_ptr
On notera l’utilisation des parenthèses. En effet, écrire
int *fct(double, double) ne signifie pas du tout la même chose.

/ 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++

Vous aimerez peut-être aussi