Cours Langage C/C++ - Première partie
Thierry Vaira
BTS IRIS Avignon
tvaira@[Link] « v0.2
Sommaire
1 Introduction 7 Types agrégés : les tableaux
2 Manipuler des variables 8 Types structurés : struct,
union et champs de bits
3 Les instructions conditionnelles
et itératives 9 Conteneur en C++
4 Les opérateurs 10 Les fonctions
5 lvalue et rvalue 11 Classe d’allocation
6 La conversion de types 12 La mémoire
13 Allocation dynamique
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 2 / 153
Introduction
Historique (1/3)
En 1970, Ken Thompson, créa un nouveau langage : Le B,
descendant du BCPL (Basic Combined Programming Language, créé
en 1967 par Martin Richards). Son but était de créer un langage
simple, malheureusement, son langage fût trop simple et trop
dépendant de l’architecture utilisée.
En 1971, Dennis Ritchie commence à mettre au point le successeur
du B, le C. Le résultat est convaincant : Le C est totalement
portable (il peut fonctionner sur tous les types de machines et de
systèmes), il est de bas niveau (il peut créer du code aussi rapide que
de l’assembleur) et il permet de traiter des problèmes de haut
niveau. Le C permet de quasiment tout faire, du driver au jeu. Le C
devient très vite populaire.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 3 / 153
Introduction
Historique (2/3)
En 1989, l’ANSI (American National Standards Institute) normalisa
le C sous les dénominations ANSI C ou C89. Un programme écrit
dans ce standard est compatible avec tous les compilateurs.
En 1983, Bjarne Stroustrup des laboratoires Bell crée le C++. Il
construit donc le C++ sur la base du C. Il garde une forte
compatibilité avec le C.
En 1999, l’ISO (International Organization for Standardization)
proposa une nouvelle version de la norme, qui reprenait quelques
bonnes idées du langage C++. Il ajouta aussi le type long long
d’une taille minimale de 64 bits, les types complexes, l’initialisation
des structures avec des champs nommés, parmi les modifications les
plus visibles. Le nouveau document est celui ayant autorité
aujourd’hui, est connu sous le sigle C99.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 4 / 153
Introduction
Historique (3/3)
Certaines autres extensions du C ont elles aussi été standardisées,
voire normalisées. Ainsi, par exemple, des fonctions spécifiques aux
systèmes UNIX, sur lesquels ce langage est toujours très populaire, et
qui n’ont pas été intégrées dans la norme du langage C, ont servi à
définir une partie de la norme POSIX.
Le langage C++ est normalisé par l’ISO. Sa première normalisation
date de 1998 (C++98), puis une seconde en 2003. Le standard actuel
a été ratifié et publié par ISO en septembre 2011 (C++11). Mais
certains compilateurs ne la supportent pas encore complétement.
Les langages C et C++ sont les langages les plus utilisés dans
le monde de la programmation.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 5 / 153
Introduction
Apports du C++ par rapport au C
Le C++ a apporté par rapport au langage C les notions suivantes :
les concepts orientés objet (encapsulation, héritage),
les références, la vérification stricte des types,
les valeurs par défaut des paramètres de fonctions,
la surcharge de fonctions (plusieurs fonctions portant le même nom se
distinguent par le nombre et/ou le type de leurs paramètres),
la surcharge des opérateurs (pour utiliser les opérateurs avec les
objets), les constantes typées,
la possiblité de déclaration de variables entre deux instructions d’un
même bloc
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 6 / 153
Introduction
C vs C++
Ce cours traitera des aspects de la programmation C et C++ car :
On peut passer progressivement de C à C++.
Il suffit en premier lieu de changer de compilateur (par exemple
gcc → − g++), sans nécessairement utiliser les nombreuses possibilités
supplémentaires qu’offre C++.
Le principal intérêt du passage de C à C++ est de profiter pleinement
de la puissance de la programmation orientée objets (POO).
L’option -std des compilateurs gcc/g++ permet de choisir la norme à
appliquer au moment de la compilation. Par exemple : -std=c89 ou c99
pour le C et -std=c++98 ou c++0x pour le C++ (compilateur version
4.4.3)
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 7 / 153
Introduction
Exemple 0a : premier programme en C
#include <stdio.h>
int main (int argc, char **argv)
{
int n;
int i;
printf("Donnez un entier : ");
scanf("%d", &n);
for(i = 0; i < n; i++)
printf("Hello world !\n");
return 0;
}
L’extension par défaut des fichiers écrits en langage C est .c
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 8 / 153
Introduction
Exemple 0b : premier programme en C++
#include <iostream>
int main (int argc, char **argv)
{
int n;
std::cout << "Donnez un entier : " << std::endl;
std::cin >> n;
for(int i = 0; i < n; i++)
std::cout << "Hello world !" << std::endl;
return 0;
}
L’extension par défaut des fichiers écrits en langage C++ est .cpp ou .cc
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 9 / 153
Introduction
Par défaut, gcc et g++ fabriquent un exécutable de nom [Link] (ou
[Link]). Sinon, on peut lui indiquer le nom du fichier exécutable en
utilisant l’option -o.
Exemples 0a et 0b : fabrication de l’exécutable (sous Linux)
$ gcc <fichier.c> -o <executable>
$ g++ <[Link]> -o <executable>
L’exécution de ces 2 programmes donne le même résultat :
$ ./[Link]
Donnez un entier : 2
Hello world !
Hello world !
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 10 / 153
Introduction
Étapes de fabrication
1 Le préprocesseur (pré-compilation)
Traitement de toutes les directives #xxxxx
Inclusion de fichiers (.h)
Substitutions lexicales : les "macros"
2 La compilation
Vérification de la syntaxe
Traduction dans le langage d’assemblage de la machine cible
3 L’assemblage
Traduction finale en code machine
Production d’un fichier objet (.o ou .obj)
4 L’édition de liens
Unification des symboles internes
Étude et vérification des symboles externes (bibliothèques .so ou .DLL)
Production du programme exécutable
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 11 / 153
Introduction
Étapes de fabrication avec gcc (1/2)
Pré-compilation
$ gcc -E <fichier.c> -o <fichier_precompile.c>
Compilation
$ gcc -S <fichier_precompile.c> -o <fichier.s>
// ou
$ gcc -c <fichier.c> -o <fichier.o>
Assemblage
$ as <fichier.s> -o <fichier.o>
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 12 / 153
Introduction
Étapes de fabrication avec gcc (1/2)
Édition des liens
$ gcc <fichier.o> -o <executable>
// ou
$ gcc <fichier1.o> <fichier2.o> <fichiern.o> -o <executable>
// voir aussi : $ ld -dynamic-linker <fichier.o> -o <executable>
Fabrication
// Combiner en une seule ligne de commande toutes les étapes (préprocesseur,
compilation, assemblage et édition des liens)
$ gcc <fichier.c> -o <executable>
Décomposition des étapes
$ gcc -v -save-temps <fichier.c> -o <executable>
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 13 / 153
Manipuler des variables
Définition
Une variable est un espace de stockage pour un résultat.
Une variable est un symbole (habituellement un nom qui sert
d’identifiant) qui renvoie à une position de mémoire (adresse) dont
le contenu peut prendre successivement différentes valeurs pendant
l’exécution d’un programme.
Règle n°5 : Les données prévalent sur le code. Si vous avez conçu la
structure des données appropriée et bien organisé le tout, les algorithmes
viendront d’eux-mêmes. La structure des données est le coeur de la
programmation, et non pas les algorithmes. (Rob Pike)
Cette règle n°5 est souvent résumée par « Écrivez du code stupide qui
utilise des données futées.» ...
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 14 / 153
Manipuler des variables
Caractéristiques
De manière générale, on distinguera les caractéristiques suivantes :
son nom : c’est-à-dire sous quel nom est déclaré la variable ;
son type : c’est la convention d’interprétation de la séquence de bits qui constitue la
variable. Le type de la variable spécifie aussi sa taille (la longueur de cette
séquence) soit habituellement 8 bits, 32 bits, 64 bits, ... ;
sa valeur : c’est la séquence de bit elle même. Cette séquence peut être codée de
différrentes façons suivant son type. Certaines variables sont déclarées constantes
(ou en lecture seule) et sont donc protégées contre l’écriture. Le mot clé const
permet de déclarer des variables constantes ; son adresse : c’est l’endroit dans la
mémoire ou elle est stockée ;
sa durée de vie : c’est la portion de code dans laquelle la variable existe.
Généralement, on parle de variable locale (à un bloc {}) ou globale (accessible de
partout dans le code du programme) ;
sa visibilité : c’est un ensemble de règles qui fixe qui peut utiliser la variable. En
C++, les variables (membres) private ne sont visibles qu’à l’intérieur de la classe
par opposition aux variables (membres) public qui elles sont visibles aussi à
l’extérieur de la classe. En C, il est possible de "cacher" certaines variables ;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 15 / 153
Manipuler des variables
Créer une variable
Pour créer une variable en C/C++, il faut bien évidemment lui
choisir son nom (parlant et précis) mais aussi obligatoirement lui
choisir son type :
pour les variables booléenes : bool (seulement en C++)
pour les nombres entiers : char, short, int, long et long long
pour les nombres à virgule flottante : float, double et long double
Le type char permet aussi de stocker le code ASCII d’une lettre
(caractère).
Il est recommandé d’initialiser la variable (attention à ne pas
l’oublier !) avec une valeur du type correspondant.
Remarque : pour les variables de type entier, il est possible de préciser
à la déclaration si celle-ci aura un signe (signed) ou non
(unsigned).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 16 / 153
Manipuler des variables
Exemple 1a : des variables de différents types en C
#include <stdio.h> /* fichier en-tête pour accéder à la déclaration de la fonction printf */
int main() /* la fonction principale appelée automatiquement au lancement de l’exécutable */
{
int nombreDOeufs = 3; // 3 est une valeur entière
unsigned long int jeSuisUnLong = 12345678UL; // U pour unsigned et L pour long
float quantiteDeFarine = 350.0; // ".0" rend la valeur réelle
char lettre = ’g’; // ne pas oublier les quotes : ’ ’
printf("La variable nombreDOeufs a pour valeur %d\n", nombreDOeufs);
printf("La variable nombreDOeufs occupe %d octet(s)\n", sizeof(int)); // l’opérateur sizeof retourne la
taille en octets de la variable
printf("La variable jeSuisUnLong a pour valeur %lu\n", jeSuisUnLong);
printf("La variable jeSuisUnLong occupe %d octet(s)\n", sizeof(unsigned long int));
printf("La variable quantiteDeFarine a pour valeur %f\n", quantiteDeFarine);
printf("La variable quantiteDeFarine occupe %d octet(s)\n", sizeof(float));
printf("La variable lettre a pour valeur %c (%d ou 0x%02X)\n", lettre, lettre, lettre);
printf("La variable lettre occupe %d octet(s)\n", sizeof(char));
printf("Recette : il faut %d oeufs et %.1f %c de farine\n", nombreDOeufs, quantiteDeFarine, lettre);
return 0; /* fin normale du programme */
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 17 / 153
Manipuler des variables
Exemple 1a : fabrication de l’exécutable (sous Linux)
$ gcc <fichier.c>
Attention : Le binaire, le décimal et l’hexadécimal ne sont qu’une
représentation numérique d’une même valeur (cf. la variable lettre).
Exemple 1a : exécution
$ ./[Link]
La variable nombreDOeufs a pour valeur 3
La variable nombreDOeufs occupe 4 octet(s)
La variable jeSuisUnLong a pour valeur 12345678
La variable jeSuisUnLong occupe 4 octet(s)
La variable quantiteDeFarine a pour valeur 350.000000
La variable quantiteDeFarine occupe 4 octet(s)
La variable lettre a pour valeur g (103 ou 0x67)
La variable lettre occupe 1 octet(s)
Recette : il faut 3 oeufs et 350.0 g de farine
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 18 / 153
Manipuler des variables
Exemple 1b : des variables de différents types en C++
#include <iostream> /* pour cout */
using namespace std;
int main() /* la fonction principale appelée automatiquement au lancement de l’exécutable */
{
bool reussie = true; // true est une valeur booléenne
int nombreDOeufs = 3; // 3 est une valeur entière
unsigned long int jeSuisUnLong = 12345678UL; // U pour unsigned et L pour long
float quantiteDeFarine = 350.0f; // ".0" rend la valeur réelle et f pour float
char unite = ’g’; // ne pas oublier les quotes : ’ ’
cout << "La variable nombreDOeufs a pour valeur " << nombreDOeufs << endl;
cout << "La variable jeSuisUnLong a pour valeur " << jeSuisUnLong << endl;
cout << "La variable quantiteDeFarine a pour valeur " << quantiteDeFarine << endl;
cout << "La variable unite a pour valeur " << unite << endl;
cout << "Recette " << reussie << " : il faut " << nombreDOeufs << " oeufs et " << quantiteDeFarine << "
" << unite << " de farine" << endl;
return 0; /* fin normale du programme */
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 19 / 153
Manipuler des variables
Exemple 1b : fabrication de l’exécutable (sous Linux)
$ g++ <[Link]>
Exemple 1b : exécution
La variable nombreDOeufs a pour valeur 3
La variable jeSuisUnLong a pour valeur 12345678
La variable quantiteDeFarine a pour valeur 350
La variable unite a pour valeur g
Recette 1 : il faut 3 oeufs et 350 g de farine
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 20 / 153
Manipuler des variables
Remarque : std représente un espace de nom. cin et cout existent dans cet
espace de nom mais pourraient exister dans d’autres espaces de noms. Le nom
complet pour y accéder est donc std::cin et std::cout. L’opérateur :: permet la
résolution de portée en C++ (un peu comme le / dans un chemin !).
Pour éviter de donner systématiquement le nom complet, on peut écrire le code
ci-dessous. Comme on utilise quasiment tout le temps des fonctions de la
bibliothèque standard, on utilise presque tout le temps " using namespace std;
" pour se simplifier la vie !
Exemple 1c : utilisation de l’espace de nom std
#include <iostream>
using namespace std; // si C++ ne connaît pas un symbole (cout, endl ici), il le cherchera dans std
int main()
{
cout << "Hello world !" << endl;
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 21 / 153
Manipuler des variables
Affecter une variable
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 22 / 153
Manipuler des variables
Les types entiers
bool : false ou true −
→ booléen (seulement en C++)
unsigned char : 0 à 255 (28 − 1) −
→ entier très court (1 octet ou 8 bits)
[signed] char : -128 (−27 ) à 127 (27 − 1) −
→ idem mais en entier relatif
unsigned short [int] : 0 à 65535 (216 − 1) −
→ entier court (2 octets ou 16 bits)
[signed] short [int] : -32768 (−215 ) à +32767 (215 − 1) −
→ idem mais en entier relatif
unsigned int : 0 à 4.295e9 (232 − 1) −
→ entier sur 4 octets ; taille "normale" actuelle
[signed] int : -2.147e9 (−231 ) à +2.147e9 (231 − 1) −
→ idem mais en entier relatif
unsigned long [int] : 0 à 4.295e9 −
→ entier sur 4 octets ou plus ; sur PC identique à
"int" (hélas...)
[signed] long [int] : -2.147e9 à -2.147e9 −
→ idem mais en entier relatif
unsigned long long [int] : 0 à 18.4e18 (264 − 1) −
→ entier (très gros !) sur 8 octets sur
PC
[signed] long long [int] : -9.2e18 (−263 ) à -9.2e18 (263 − 1) −
→ idem mais en entier
relatif
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 23 / 153
Manipuler des variables
Les types à virgule flottante
float : Environ 6 chiffres de précision et un exposant qui va jusqu’à ±10±38 −
→ Codage
IEEE754 sur 4 octets
double : Environ 10 chiffres de précision et un exposant qui va jusqu’à ±10±308 −
→
Codage IEEE754 sur 8 octets
long double −
→ Codé sur 10 octets
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 24 / 153
Manipuler des variables
Le standard IEEE 754
Le standard IEEE 754 définit les formats de représentation des
nombres à virgule flottante de type float/double (signe,
mantisse, exposant, nombres dénormalisés) et valeurs spéciales (infinis
et NaN), un ensemble d’opérations sur les nombres flottants et quatre
modes d’arrondi et cinq exceptions (cf. [Link]/wiki/IEEE_754).
La version 1985 de la norme IEEE 754 définit 4 formats pour
représenter des nombres à virgule flottante :
simple précision (32 bits : 1 bit de signe, 8 bits d’exposant (-126 à
127), 23 bits de mantisse, avec bit 1 implicite),
simple précision étendue (>= 43 bits, obsolète, implémenté en pratique
par la double précision),
double précision (64 bits : 1 bit de signe, 11 bits d’exposant (-1022 à
1023), 52 bits de mantisse, avec bit 1 implicite),
double précision étendue (>= 79 bits, souvent implémenté avec 80
bits : 1 bit de signe, 15 bits d’exposant (-16382 à 16383), 64 bits de
mantisse, sans bit 1 implicite).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 25 / 153
Manipuler des variables
Mantisse et partie significative
Le compilateur gcc (pour les architectures compatible Intel 32 bits)
utilise le format simple précision pour les variables de type float,
double précision pour les variables de type double, et la double
précision ou la double précision étendue (suivant le système
d’exploitation) pour les variables de type long double.
La mantisse est la partie décimale de la partie significative,
comprise entre 0 et 1.
Donc 1+mantisse représente la partie significative.
Elle se code (attention on est à droite de la virgule) :
pour le premier bit avec le poids 2−1 soit 0,5,
puis 2−2 donc 0,25,
2−3 donnera 0,125, ainsi de suite ...
Par exemple : ,010 donnera ,25.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 26 / 153
Manipuler des variables
Les limites des nombres
Le fichier limits.h contient, sous forme de constantes ou de macros,
les limites concernant le codage des entiers et le fichier float.h
contient celles pour les "floattants".
Exemple : les limites des nombres en C
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main() {
printf("*** Limites pour les entiers ***\n");
printf("Nombre de bits dans un char : %d bits\n", CHAR_BIT);
printf("%d <= char <= %d\n", CHAR_MIN, CHAR_MAX);
printf("0 <= unsigned char <= %u\n", UCHAR_MAX);
printf("%d <= int <= %d\n", INT_MIN, INT_MAX);
printf("0 <= unsigned int <= %u\n", UINT_MAX);
printf("%ld <= long <= %ld\n", LONG_MIN, LONG_MAX);
printf("0 <= unsigned long <= %lu\n", ULONG_MAX);
printf("\n*** Limites pour les réels ***\n");
printf("%e <= float <= %e\n", FLT_MIN, FLT_MAX);
printf("%e <= double <= %e\n", DBL_MIN, DBL_MAX);
printf("%Le <= long double <= %Le\n", LDBL_MIN, LDBL_MAX);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 27 / 153
Manipuler des variables
Exemple : exécution
*** Limites pour les entiers ***
Nombre de bits dans un char : 8 bits
-128 <= char <= 127
0 <= unsigned char <= 255
-2147483648 <= int <= 2147483647
0 <= unsigned int <= 4294967295
-2147483648 <= long <= 2147483647
0 <= unsigned long <= 4294967295
*** Limites pour les réels ***
1.175494e-38 <= float <= 3.402823e+38
2.225074e-308 <= double <= 1.797693e+308
3.362103e-4932 <= long double <= 1.189731e+4932
Première loi : Le type d’une variable spécifie sa taille (de sa
représentation en mémoire) et ses limites. Attention, Elle peut donc
déborder (overflow ).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 28 / 153
Manipuler des variables
Les constantes
Celles définies pour le préprocesseur : c’est simplement une
substitution syntaxique pure (une sorte de copier/coller). Il n’y a
aucun typage de la constante.
#define PI 3.1415 /* en C traditionnel */
L’utilisation de #define améliore surtout la lisibilité du code source.
La convention usuelle est d’utiliser des MAJUSCULES (pour les
distinguer des variables).
Celles définies pour le compilateur : c’est une valeur typée, ce qui
permet des contrôles lors de la compilation.
const double pi = 3.1415; // en C++ et en C ISO
A utiliser pour des valeurs constantes.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 29 / 153
Manipuler des variables
Les pointeurs
Les pointeurs sont des variables spéciales permettant de stocker une
adresse (pour la manipuler ensuite).
L’adresse représente généralement l’emplacement mémoire d’une
variable (ou d’une autre adresse).
Comme la variable a un type, le pointeur qui stockera son adresse
doit être du même type pour la manipuler convenablement.
Le type void* représentera un type générique de pointeur : en fait
cela permet d’indiquer sagement que l’on ne sait pas encore sur quel
type il pointe.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 30 / 153
Manipuler des variables
Utilisation des pointeurs
On utilise l’étoile (*) pour déclarer un pointeur.
déclaration d’un pointeur (*) sur un entier (int) : int *ptrInt;
On utilise le & devant une variable pour initialiser ou affecter un
pointeur avec une adresse.
déclaration d’un entier i qui a pour valeur 2 : int i = 2;
affectation avec l’adresse de la variable i (&i) : ptrInt = &i;
On utilise l’étoile devant le pointeur (*) pour accéder à l’adresse
stockée.
indirection ("pointe sur le contenu de i") : *ptrInt = 3;
Maintenant la variable i contient 3
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 31 / 153
Manipuler des variables
Particularités des pointeurs
Avec printf, il est possible d’afficher une adresse mémoire en
utilisant le formateur %p.
Pour connaître la taille qu’occupe une variable de type pointeur, on
utilisera sizeof(). Sur une architecture donnée, tous les pointeurs
ont la même taille (probablement 32 bits). Sur 32 bits, un pointeur
pourra contenir une adresse dans un espace de 4 GO (232 ).
Les pointeurs peuvent être incrémentés, décrémentés, additionnés ou
soustraits. Dans ce cas, leur nouvelle valeur dépend du type sur lequel
ils pointent.
Exemple : incrémenter un pointeur de char ajoute 1 à sa valeur (un
caractère est représenté sur 1 octet). Incrémenter un pointeur sur un
int ajoute 4 à sa valeur (cela dépend de l’architecture, un entier
pouvant être représenté sur 4 octets).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 32 / 153
Manipuler des variables
Exemple : manipulons un pointeur
#include <stdio.h>
int main()
{
int i = 2; // déclaration d’un entier i qui a pour valeur 2
int *ptrInt; // déclaration d’un pointeur (*) sur un entier (int)
printf("La variable i a pour valeur %d et pour adresse %p\n", i, &i);
ptrInt = &i; // affectation du pointeur avec l’adresse de la variable i
printf("La variable ptrInt contient l’adresse %p et pointe donc sur i qui contient %d\n", ptrInt, *
ptrInt);
*ptrInt = 3; // j’accède à l’adresse contenue dans le pointeur et je pointe dessus (*ptrInt) et je
modifie son contenu
printf("La variable i a maintenant pour valeur %d\n", i);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 33 / 153
Manipuler des variables
Exemple 2 : exécution
La variable i a pour valeur 2 et pour adresse 0xbfa56938
La variable ptrInt contient l’adresse 0xbfa56938 et pointe donc sur i qui
contient 2
La variable i a maintenant pour valeur 3
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 34 / 153
Manipuler des variables
Les références en C++
En C++, il est possible de déclarer une référence j sur une variable
i : cela permet de créer un nouveau nom j qui devient synonyme
de i (un alias).
On pourra donc modifier le contenu de la variable en utilisant une
référence.
La déclaration d’une référence se fait en précisant le type de l’objet
référencé, puis le symbole &, et le nom de la variable référence qu’on
crée.
Une référence ne peut être initialisée qu’une seule fois : à la
déclaration.
Une référence ne peut donc référencer qu’une seule variable tout au
long de sa durée de vie.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 35 / 153
Manipuler des variables
Exemple : les références
#include <iostream>
int main (int argc, char **argv)
{
int i = 10; // i est un entier valant 10
int &j = i; // j est une référence sur un entier, cet entier est i.
//int &k = 44; // ligne7 : illégal
std::cout << "i = " << i << std::endl;
std::cout << "j = " << j << std::endl;
// A partir d’ici j est synonyme de i, ainsi :
j = j + 1; // est équivalent à i = i + 1 !
std::cout << "i = " << i << std::endl;
std::cout << "j = " << j << std::endl;
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 36 / 153
Manipuler des variables
Exemple : exécution
i = 10
j = 10
i = 11
j = 11
Si on dé-commente la ligne 7, on obtient cette erreur à la compilation :
ligne 7: erreur: invalid initialization of non-const référence of type ’int&’
from a temporary of type ’int’
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 37 / 153
Manipuler des variables
Intérêt des références
Attention : le = dans la déclaration de la référence n’est pas réellement une
affectation puisqu’on ne copie pas la valeur de i. En fait, on affirme plutôt
le lien entre i et j. En conséquence, la ligne 7 est donc parfaitement
illégal ce que signale le compilateur.
Comme une référence établit un lien entre deux noms, leur utilisation
est efficace dans le cas de variable de grosse taille car cela évitera
toute copie.
Les références sont (systématiquement) utilisées dans le passage des
paramètres d’une fonction (ou d’une méthode) dès que le coût d’une
recopie par valeur est trop important ("gros" objet).
Exemple :
void truc(const grosObjet& rgo);
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 38 / 153
Manipuler des variables
Variables globale et locale
une variable globale est une variable déclarée à l’extérieur du
corps de toute fonction ou classe, et pouvant donc être utilisée
n’importe où dans le programme. On parle également de
variable de portée globale.
une variable locale est une variable qui ne peut être utilisée que
dans la fonction ou le bloc où elle est définie. On parle également
de variable de portée locale.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 39 / 153
Manipuler des variables
Durée de vie
Exemple : Variable globale et locale
#include <stdio.h>
int g = 1; // je suis une variable globale
int main()
{
int x = 5; // je suis une variable locale, ma durée de vie est celle du main
printf("La variable globale g a pour valeur %d\n", g);
printf("La variable locale x a pour valeur %d\n", x);
return 0;
}
Exemple : exécution
La variable globale g a pour valeur 1
La variable locale x a pour valeur 5
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 40 / 153
Manipuler des variables
Visibilité
Exemple : "Attention, une variable peut en cacher une autre"
#include <stdio.h>
int i = 1; // je suis une variable globale de nom i
int main()
{
int i = 2; // je suis une variable locale de nom i ! aie !
printf("La variable i a pour valeur %d\n", i);
return 0;
}
Conclusion : La visibilité s’applique à la plus "proche" déclaration.
Exemple : exécution
La variable i a pour valeur 2
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 41 / 153
Manipuler des variables
typdedef
Le mot réservé typedef (signifie littéralement « définition de type »)
permet simplement la définition de synonyme de type qui peut
ensuite être utilisé à la place d’un nom de type :
Exemple d’utilisation de typedef
typedef int entier;
typedef float reel;
entier a; // a de type entier donc de type int
reel x; // x de type réel donc de type float
Conclusion : Le mot-clé typedef permet donc au programmeur de créer de
nouveaux noms de types.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 42 / 153
Manipuler des variables
Intérêt de typdedef
typedef peut être utilisé à la fois pour :
donner plus de clarté au code source
rendre plus facile les modifications de ses propres types de données
permettre la portabilité du code source (sur une autre ou future
plateforme)
Notez que beaucoup de langages fournissent l’alias de types ou la possibilité de
déclarer plusieurs noms pour le même type.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 43 / 153
Manipuler des variables
enum
enum permet de déclarer un type énuméré constitué d’un ensemble
de constantes appelées énumérateurs.
Une variable de type énuméré peut recevoir n’importe quel
énumérateur (lié à ce type énuméré) comme valeur.
Le premier énumérateur vaut zéro (par défaut), tandis que tous les
suivants correspondent à leur précédent incrémenté de un.
Exemple d’utilisation de enum
enum couleur_carte
{
TREFLE = 1, /* un énumérateur */
CARREAU, /* 1+1 donc CARREAU = 2 */
COEUR = 4, /* en C, les énumérateurs sont équivalents à des entiers (int) */
PIQUE = 8 /* il est possible de choisir explicitement les valeurs (ou de
certaines d’entre elles). */
};
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 44 / 153
Manipuler des variables
Exemple : utilisation des typedef et enum précédents
typedef int entier;
typedef float reel;
typedef enum{FALSE,TRUE} booleen;
void main() {
entier e = 1, reel r = 2.5, booleen fini = FALSE;
enum couleur_carte carte = CARREAU;
printf("Le nouveau type entier possède une taille de %d octets (ou %d bits)\n", sizeof(entier), sizeof(
entier)*8);
printf("La variable e a pour valeur %d et occupe %d octets\n", e, sizeof(e));
printf("La variable r a pour valeur %.1f et occupe %d octets\n", r, sizeof(r));
printf("La variable fini a pour valeur %d et occupe %d octets\n", fini, sizeof(fini));
printf("La variable carte a pour valeur %d et occupe %d octets\n", carte, sizeof(carte));
}
Exemple : exécution
Le nouveau type entier possède une taille de 4 octets (ou 32 bits)
La variable e a pour valeur 1 et occupe 4 octets
La variable r a pour valeur 2.5 et occupe 4 octets
La variable fini a pour valeur 0 et occupe 4 octets
La variable carte a pour valeur 2 et occupe 4 octets
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 45 / 153
Les instructions conditionnelles et itératives
Instruction conditionnelle (1/2)
Il en existe deux formes possibles :
if (expression) instruction;
if (expression) instruction_1; /* SI ... ALORS ... */
else instruction_2; /* SINON ... */
expression doit être de type numérique ou de type pointeur.
Cela donne le comportement suivant :
si l’expression est de type numérique et que sa valeur est différente de
0 (faux), alors l’instruction_1 est exécutée, sinon c’est l’instruction_2.
si l’expression est de type pointeur, cela revient à comparer le pointeur
avec la valeur null. Une valeur non null du pointeur provoque
l’exécution de l’instruction_1 et une valeur null, celle de
l’instruction_2.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 46 / 153
Les instructions conditionnelles et itératives
Instruction conditionnelle (2/2)
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 47 / 153
Les instructions conditionnelles et itératives
Instruction de sélection (1/2)
Une instruction switch sélectionne une des instructions la composant, en
fonction de la valeur d’une expression, qui doit être de type entier. Elle a la
forme suivante :
switch (expression)
{
case constante_scalaire_1 : liste_d_instructions_1;
case constante_scalaire_2 : liste_d_instructions_2;
// etc ...
default : liste_d_instructions;
}
Important : expression doit rendre un résultat de type entier. La
valeur exprimée derrière un case est une constante et en aucun cas
une instruction.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 48 / 153
Les instructions conditionnelles et itératives
Instruction de sélection (2/2)
Important : expression est comparée aux constantes derrière les
case. A la première correspondance des deux valeurs, la liste
d’instructions correspondante est exécutée.
Si aucune constante n’est égale à expression :
s’il existe une partie default, elle est exécutée
sinon, l’instruction switch ne fait rien.
Attention : Une fois terminée l’exécution d’une liste d’instructions
d’un case, on continue en séquence dans le case suivant. Si on ne
veut pas que ce genre de phénomène se produise, il faut utiliser
l’instruction break (qui fait sortir du switch).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 49 / 153
Les instructions conditionnelles et itératives
Instruction de contrôle d’itération : while
L’instruction while contrôle l’exécution répétitive d’une autre
instruction TANT QUE expression est vraie. Elle a la forme suivante :
expression doit être soit de type entier, soit de type pointeur.
Au début de chaque itération, expression est évaluée. Si celle-ci a
une valeur différente de zéro (ou de null), l’itération se poursuit,
sinon la boucle est terminée. Cette boucle sera effectuée zéro ou
plusieurs fois.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 50 / 153
Les instructions conditionnelles et itératives
Instruction de contrôle d’itération : do
La suite d’instructions se trouvant entre les symboles do et while est
exécutée de manière répétitive, TANT QUE expression est vraie
(donc produise une valeur différente de zéro (ou de null). Elle a la forme
suivante :
Comme précédemment, expression doit être soit de type entier, soit
de type pointeur. Cette suite d’instructions sera exécutée au moins
une fois.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 51 / 153
Les instructions conditionnelles et itératives
Instruction de contrôle d’itération : for (1/2)
L’instruction for est une forme singulière et pratique de l’instruction
while. Cette instruction n’a été rajoutée dans le langage que pour des
raisons de clarté de programme. Elle a la forme suivante :
for (expression_1; expression_2; expression_3) instruction;
//ou :
for (expression_1; expression_2; expression_3)
{
instruction_1;
instruction_2;
// etc ...
}
expression_1 : correspond à la partie initialisation de l’itération
expression_2 : correspond à la condition d’arrêt (de type TANT
QUE)
expression_3 : correspond au pas de l’incrémentation ou à une
réinitialisation
instruction : correspond au corps de l’itération
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 52 / 153
Les instructions conditionnelles et itératives
Instruction de contrôle d’itération : for (2/2)
L’instruction for est ainsi équivalente à l’instruction while suivante :
expression_1;
while (expression_2)
{
instruction;
expression_3;
}
Remarque : N’importe laquelle des 3 expressions peut donc être
omise. Si expression_2 est omise, on a alors affaire à une
instruction while(1), c’est à dire une boucle infinie. Exemple : for
(;;) instruction; /* je suis une boucle infinie ! */
C’est dans les parties initialisation et incrémentation que l’opérateur
virgule ’,’ est le plus utile.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 53 / 153
Les instructions conditionnelles et itératives
Instructions de rupture de séquence (1/2)
Il en existe plusieurs :
Instruction break : Cette instruction fournit un mécanisme pour
arrêter une boucle prématurément. Elle permet aussi de sortir d’un
case de l’instruction switch, ce qui est nécessaire pour éviter
d’exécuter en séquence le reste des instructions composant le switch.
Elle cause ainsi l’arrêt de la première instruction while, do, for ou
switch englobante.
Instruction continue : Cette instruction est très proche de l’instruction
break mais ne s’applique pas à l’instruction switch. Elle ne s’applique
en effet qu’aux instructions permettant le contrôle d’itération. Elle
provoque l’arrêt de l’itération courante et le passage au début de
l’itération suivante dans une boucle contrôlée par une instruction
while, do ou for. Dans une boucle for, la partie réinitialisation de la
boucle (expression_3) est exécutée.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 54 / 153
Les instructions conditionnelles et itératives
Instructions de rupture de séquence (2/2)
Il existe aussi :
Instruction return : Cette instruction provoque le retour chez
l’appelant de la fonction courante. Les deux formes de l’instruction
return sont : return; ou return expression;
Instruction goto (et étiquettes) : La forme d’une instruction goto est
goto etiquette;. Elle a pour effet de provoquer le passage à
l’exécution de l’instruction étiquetée par etiquette. Elle a pour
limitation de n’autoriser le branchement qu’à l’intérieur du bloc régi par
une fonction. Bien que cette instruction soit bannie de la
programmation structurée, elle est parfois utilisée car elle permet un
traitement plus facile de certaines situations.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 55 / 153
Les opérateurs
Les opérateurs arithmétiques et logiques
Les opérateurs arithmétiques (+, -, *, / et %) et les opérateurs
relationnels (<, <=, >, >=, == et !=) ne sont définis que pour des
opérandes d’un même type parmi : int, long int (et leurs
variantes non signées), float, double et long double.
Mais on peut constituer des expressions mixtes (opérandes de types
différents) ou contenant des opérandes d’autres types (bool, char et
short), grâce aux conversions implicites et explicites.
Les opérateurs logiques && (et), || (ou) et ! (non) acceptent
n’importe quel opérande numérique (entier ou flottant) ou pointeur,
en considérant que tout opérande de valeur non nulle correspond à
"faux". Les deux opérateurs && et || sont "à court-circuit" : le
second opérande n’est évalué que si la connaissance de sa valeur est
indispensable.
Remarque : l’opérateur modulo (%) permet d’obtenir le reste d’une
division.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 56 / 153
Les opérateurs
Opérateurs logiques à court-circuit
Attention : le second opérande n’est évalué que si la connaissance de sa
valeur est indispensable.
int a = 0;
// Danger : si le premier opérande suffit à déterminer l’évaluation
du résultat logique
if( 0 < 1 || a++ != 5 ) // Attention : a++ != 5 n’est pas évalué donc
(a n’est pas incrémenté) car 0 < 1 et donc toujous VRAI dans un
OU
printf("VRAI !\n"); // Affiche toujours : VRAI !
else printf("FAUX !\n");
if( 1 < 0 && a++ != 5 ) // Attention : a++ != 5 n’est pas évalué donc
(a n’est pas incrémenté) car 1 < 0 et et donc toujous FAUX dans
un ET
printf("VRAI !\n");
else printf("FAUX !\n"); // Affiche toujours : FAUX !
printf("a = %d\n", a); // Affichera toujours : a = 0 !!!
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 57 / 153
Les opérateurs
Opérateurs logique et bit à bit (1/2)
Ne pas confondre les opérateurs logiques avec les opérateurs bit à bit
unsigned char a = 1; unsigned char b = 0;
unsigned char aa = 20; /* non nul donc VRAI en logique */
unsigned char bb = 0xAA;
// Ne pas confondre !
/* ! : inverseur logique */
/* ~ : inverseur bit à bit */
printf("a = %u - !a = %u - ~a = %u (0x%hhX)\n", a, !a, ~a, ~a);
printf("b = %u - !b = %u - ~b = %u (0x%hhX)\n", b, !b, ~b, ~b);
printf("aa = %u (0x%hhX) - !aa = %u - ~aa = %u (0x%hhX)\n", aa, aa, !
aa, ~aa, ~aa);
printf("bb = %u (0x%hhX) - !bb = %u - ~bb = %u (0x%hhX)\n", bb, bb, !
bb, ~bb, ~bb);
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 58 / 153
Les opérateurs
Opérateurs logique et bit à bit (2/2)
Pour les opérateurs bit à bit, il est conseillé d’utiliser la représentation
en hexadécimale :
Exemple
a = 1 - !a = 0 - ~a = 4294967294 (0xFE)
b = 0 - !b = 1 - ~b = 4294967295 (0xFF)
aa = 20 (0x14) - !aa = 0 - ~aa = 4294967275 (0xEB)
bb = 170 (0xAA) - !bb = 0 - ~bb = 4294967125 (0x55)
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 59 / 153
Les opérateurs
Les opérateurs d’affectation
Les opérateurs d’affectation (=, +=, -=, *=, /=, %=, &=, ^=,
|=, «= et »=) nécessitent une lvalue pour l’opérande de gauche.
L’affectation à la déclaration d’une variable est appelée "déclaration
avec initialisation", par exemple : int a = 5; déclare a et
l’initialise avec la valeur entière 5.
Affectation avec opérateur : Il existe toute une gamme
d’opérateurs permettant d’effectuer une opération avec le contenu
d’une variable et de mettre le résultat dans cette même variable.
Une opération du type : a operateur= expression; équivaut à : a
= a operateur (expression);
Par exemple : L’opérateur "+=" signifie "affecter en additionnant à" :
a += 2; est équivalent a = a + 2;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 60 / 153
Les opérateurs
Opération sur les pointeurs
Les opérations arithmétiques sur les pointeurs sont bien évidemment
réalisées sur les adresses contenues dans les variables pointeurs.
Le type du pointeur a une influence importante sur l’opération.
Supposons un tableau t de 10 entiers (int) initialisés avec des
valeurs croissantes de 0 à 9. Si on crée un pointeur ptr sur un entier
(int) et qu’on l’initialise avec l’adresse d’une case de ce tableau, on
pourra alors se déplacer avec ce pointeur sur les cases de ce tableau.
Comme ptr pointe sur des entiers (c’est son type), son adresse
s’ajustera d’un décalage du nombre d’octets représentant la taille d’un
entier (int). Par exemple, une incrémentation de l’adresse du
pointeur correspondra à une opération +4 (octets) si la taille d’un
int est de 4 octets !
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 61 / 153
Les opérateurs
Opération de déplacement d’un pointeur
int t[10] = { 0,1,2,3,4,5,6,7,8,9 };
int *ptr; // un pointeur pour manipuler des int
ptr = &t[5]; // l’adresse d’une case du tableau
printf("Je pointe sur la case : %d (%p)\n", *ptr, ptr); // Je pointe
sur la case : 5 (0xbf8d87b8)
ptr++; // l’adresse est incrémentée de 4 octets pour pointer sur l’
int suivant
printf("Maintenant, je pointe sur la case : %d (%p)\n", *ptr, ptr);
// Maintenant, je pointe sur la case : 6 (0xbf8d87bc)
ptr -= 4; // en fait je recule de 4 int soit 4*4 octets pour la
valeur de l’adresse
printf("Maintenant, je pointe sur la case : %d (%p)\n", *ptr, ptr);
// Maintenant, je pointe sur la case : 2 (0xbf8d87ac)
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 62 / 153
Les opérateurs
Opérateurs d’incrémentation et de décrémentation
Les opérateurs unaires (à une seule opérande) d’incrémentation
(++) et de décrémentation (––) agissent sur la valeur de leur unique
opérande (qui doit être une lvalue) et fournissent la valeur après
modification lorsqu’ils sont placés à gauche (comme dans ++n) ou
avant modification lorsqu’ils sont placés à droite (comme dans n––).
i++; est (à première vue) équivalent à i = i + 1;
Mais i++ est une right-value donc ...
int i = 10;
int j = i++; // équivalent à int j=i; i=i+1;
printf("i = %d\n", i); // Affiche : i = 11
printf("j = %d\n", j); // Affiche : j = 10
Attention c’est une post-incrémentation : on augmente i après avoir affecté
j.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 63 / 153
Les opérateurs
Opérateur ternaire : ?
L’opérateur ternaire ? ressemble au if(...) {...} else {...}
mais joue un rôle de right-value et pas de simple instruction.
La syntaxe est la suivante : (A?B:C) prend la valeur de l’expression B
si l’expression A est vraie, sinon la valeur de l’expression C.
int age = 1; int parite; /* un booléen */
printf("J’ai %d an%c\n", age, age > 1 ? ’s’ : ’’); // J’ai 1 an
printf("Je suis %s\n", age >= 18 ? "majeur" : "mineur"); // Je suis
mineur
parite = (age%2 == 0 ? 1 : 0 );
printf("Parité = %d\n\n", parite); // Parité = 0
age = 20;
printf("J’ai %d an%c\n", age, (age > 1) ? ’s’ : ’’); // J’ai 20 ans
printf("Je suis %s\n", (age >= 18) ? "majeur" : "mineur"); // Je suis
majeur
parite = (age%2 == 0 ? 1 : 0 );
printf("Parité = %d\n", parite); // Parité = 1
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 64 / 153
Les opérateurs
Priorité des opérateurs (1/2)
Liste des opérateurs du plus prioritaire au moins prioritaire :
:: (opérateur de résolution de portée en C++)
. -> [] (référence et sélection) () (appel de fonction) () (parenthèses) sizeof()
++ ~ (inverseur bit à bit) ! (inverseur logique) - (unaire) & (prise d’adresse) *
(indirection) new delete delete[] (opérateurs de gestion mémoire en C++)
() (conversion de type)
* / % (multiplication, division, modulo)
+ - (addition et soustraction)
« » (décalages et envoi sur flots)
< <= > >= (comparaisons)
== != (comparaisons)
& (ET bit à bit)
^ (OU-Exclusif bit à bit)
| (OU-Inclusif bit à bit)
&& (ET logique)
|| (OU logique)
(? : ) (expression conditionnelle ou opérateur ternaire)
= *= /= %= += = «= »= &= |= ~=
, (mise en séquence d’expressions)
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 65 / 153
Les opérateurs
Priorité des opérateurs (2/2)
Quelques exemples :
1 y = (x+5) est équivalent à : y = x+5 car l’opérateur + est prioritaire sur l’opérateur
d’affectation =.
2 (i++) * (n+p) est équivalent à : i++ * (n+p) car l’opérateur ++ est prioritaire sur *. En
revanche, * est prioritaire sur +, de sorte qu’on ne peut éliminer les dernières parenthèses.
3 moyenne = 5 + 10 + 15 / 3 donnera 20 (/ est plus prioritaire que le +) alors que
mathématiquement le résultat est 10 ! Il faut alors imposer l’ordre en l’indiquant avec des
parenthèses : moyenne = (5 + 10 + 15) / 3
4 Important : Si deux opérateurs possèdent la même priorité, C exécutera les opérations de
la gauche vers la droite (sauf pour les opérateurs suivants où l’ordre est de la droite vers
la gauche : ++ ~ (inverseur bit à bit) ! (inverseur logique) - (unaire) & (prise d’adresse) *
(indirection) new delete delete[] (? :) et = *= /= %= += = «= »= &= |= ~=).
5 L’ordre des opérateurs n’est donc pas innocent. En effet : 3/6*6 donnera 0 alors que
3*6/6 donnera 3 !
Conclusion : comme il est difficile de se rappeler de l’ensemble des priorités
des opérateurs, le programmeur préfère coder explicitement en utilisant des
parenthèses afin de s’éviter des surprises. Cela améliore aussi la lisibilité.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 66 / 153
lvalue et rvalue
lvalue
Une Left-value (valeur gauche) est un élément de syntaxe C/C++
pouvant être écrit à gauche d’un opérateur d’affectation (=).
Exemple : une variable, une case de tableau, ...
Modèle d’une affectation :
lvalue = rvalue; soit left-value ←
− right-value
Une left-value doit donc être un emplacement de stockage en
mémoire possédant un type précis, c’est-à-dire la référence à
quelque chose de modifiable.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 67 / 153
lvalue et rvalue
Affectation d’une lvalue
#include <stdio.h>
int main()
{
int x;
x = 5; // affectation de la valeur entière 5 à la variable x (x est
une lvalue)
2 = x + 1; // problème car 2 n’est pas une lvalue (2 n’est pas "
modifiable") !
return 0;
}
Le compilateur détecte l’erreur et le message est très clair : "error:
lvalue required as left operand of assignment"
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 68 / 153
lvalue et rvalue
rvalue
Une Right-value (valeur droite) est un élément de syntaxe C/C++
pouvant être écrit à droite d’un opérateur d’affectation (=).
Exemple : une valeur, une constante, une variable, une expression, ...
Modèle d’une affectation :
lvalue = rvalue; soit left-value ←
− right-value
Une right-value doit être une valeur d’un type précis mais n’a pas
forcement de zone de stockage en mémoire.
Une affectation est encore une right-value ... ce qui donne le droit
d’écrire : i = j = 10;
C’est équivalent à i=(j=10); c’est-à-dire : j=10; i=j;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 69 / 153
lvalue et rvalue
Utilisation d’une affectation comme rvalue
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
i = j = 10;
printf("i = %d\n", i); // Pas de surprise : i = 10
printf("j = %d\n", j); // Pas de surprise : j = 10
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 70 / 153
La conversion de types
Conversion "forcée" par la lvalue
Les opérateurs d’affectation (=, -=, += ...), appliqués à des valeurs
de type numérique, provoquent la conversion de leur opérande de
droite dans le type de leur opérande de gauche. Cette conversion
"forcée" peut être "dégradante" (avec perte).
#include <stdio.h>
int main() {
int x = 5; float y = 1.5;
int res;
res = (x + y); // cela revient à faire (int)(x + y) car res est de
type int
printf("res = %d\n", res); // Affiche : res = 6
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 71 / 153
La conversion de types
Conversion automatique
Soit l’opération suivante : short int a = 2; a + 2;
L’opération a + 2 revient à faire l’addition entre un short int (a)
et un int (2). Cela est impossible car on ne peut réaliser que des
opérations entre type identique.
Une conversion implicite (automatique) sera faite (pouvant donner
lieu à un warning de la part du compilateur).
Les conversions d’ajustement de type automatique réalisées suivant la
hiérarchie ci-dessous sont réalisées sans perte :
1 char →
− short int →
− int →
− long →
− float →
− double →
− long
double
2 unsigned int →
− unsigned long →
− float →
− double →
− long
double
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 72 / 153
La conversion de types
Conversion forcée ou cast
Une conversion de type (ou de promotion de type) peut être implicite
(automatique) ou explicite (c’est-à-dire forcée par le programmeur).
Lorsqu’elle est explicite, on utilise l’opérateur de cast : (float)a
permet de forcer le short int a en float.
Les conversions forcées peuvent être des conversions dégradantes
(avec perte). Par exemple : int b = 2.5;
En effet, le cast (int)b donnera 2 : perte de la partie décimale. Cela
peut être dangereux (source d’erreur).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 73 / 153
Types agrégés
Présentation
Dans de très nombreuses situations, les types de base s’avèrent
insuffisants pour permettre de traiter un problème : il peut être
nécessaire, par exemple, de stocker un certain nombre de valeurs en
mémoire afin que des traitements similaires leurs soient appliqués.
Dans ce cas, il est impensable d’avoir recours à de simples variables
car tout traitement itératif est inapplicable.
D’autre part, il s’avère intéressant de pouvoir regrouper ensemble
plusieurs variables afin de les manipuler comme un tout.
Pour répondre à tous ces besoins, le langage C comporte la notion de
type agrégé en utilisant : les tableaux, les structures, les unions et
les énumérations.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 74 / 153
Types agrégés
Les tableaux
Un tableau est un ensemble d’éléments de même type désignés
par un identificateur unique (un nom).
Chaque élément est repéré par une valeur entière appelée indice (ou
index) indiquant sa position dans l’ensemble.
Les tableaux sont toujours à bornes statiques et leur indiçage
démarre toujours à partir de 0.
La forme générique de déclaration d’un tableau est la suivante :
[classe de mémorisation] type identificateur[dimension1 ]...
[dimensionn ]
Contrairement à beaucoup d’autres langages, il n’existe pas en C de véritable
notion de tableaux multidimentionnels. De tels tableaux se définissent par
composition de tableaux, c’est à dire que les éléments sont eux-mêmes des
tableaux.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 75 / 153
Types agrégés
Exemple : les tableaux
#define MAX 20 // définit l’étiquette MAX egale à 20
int t[10]; // tableau de 10 éléments entiers (int)
// tableau à 2 dimensions de 2 lignes et 5 colonnes :
int m[2][5] = { 2, 6, -4, 8, 11, // initialise avec des valeurs
3, -1, 0, 9, 2 };
int x[5][12][7]; // tableau a 3 dimensions, rarement au-delà de cette dimension
float f[MAX]; // tableau de MAX éléments de type float
t[2] = 6; // accès en écriture au 3eme élément du tableau t
printf("%d", m[1][3]); // affiche la valeur 9
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 76 / 153
Types agrégés
Particularités des tableaux
L’identificateur du tableau désigne non pas le tableau dans son
ensemble, mais plus précisément l’adresse en mémoire du début du
tableau.
Ceci implique qu’il est impossible d’affecter un tableau à un autre :
int a[10], b[10]; a = b; // cette affectation est interdite
L’identificateur d’un tableau sera donc "vu" comme un pointeur
constant.
Danger : le plus grand danger dans la manipulation des tableaux est d’accéder en
écriture en dehors du tableau. Cela provoque un accès mémoire interdit qui n’est pas
contrôlé au moment de la compilation. Par contre, lors de l’exécution, cela provoquera
une exception de violation mémoire (segmentation fault) qui se traduit généralement
par une sortie prématurée du programme avec un message "Erreur de segmentation".
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 77 / 153
Types agrégés
La dimension d’un tableau peut être omise dans 2 cas :
Exemple : les tableaux
1 le compilateur peut en définir la valeur
int t[]={2, 7, 4}; // tableau de 3 éléments
char msg[]="Bonjour"; // chaîne de caractères
2 l’emplacement mémoire correspondant a été réservé
// la fonction fct admet en parametre
void fct(int t_i[]) // un tableau d’entiers qui existe déjà
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 78 / 153
Types agrégés
Exemple : tableaux et pointeurs
int t[5] = {0, 2, 3, 6, 8}; // un tableau de 5 entiers
int *p1 = NULL; // le pointeur est initialisé à NULL (précaution obligatoire)
int *p2; // pointeur non initialisé : il pointe donc sur n’importe quoi (gros
danger)
p1 = t; // p1 pointe sur t c’est-a-dire la première case du tableau
// identique a : p1 = &t[0];
p2 = &t[1]; // p2 pointe sur le 2eme élément du tableau
*p1 = 4; // la première case du tableau est modifiée
printf("%d ou %d\n", *p1, t[0]); // affiche 4 ou 4
printf("%d ou %d\n", *p2, t[1]); // affiche 2 ou 2
p2 += 2; // p2 pointe sur le 4eme élément du tableau (indice 3)
printf("%d ou %d\n", *p2, t[3]); // affiche 6 ou 6
// on peut utiliser les [] sur un pointeur :
p1[1] = 8; // identique à : *(p1+1) = 8; ou a : t1[1] = 8;
printf("%d\n", t[1]); // affiche 8
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 79 / 153
Types agrégés
Les chaînes de caractères
Une chaîne de caractères est un tableau de caractères dont le dernier
caractère est le caractère nul (valeur 0) qui marque ainsi la fin de
la chaîne. En C, un caractère est un code ASCII sur 8 bits (cf. man
ascii).
De nombreuses fonctions de la librairie standard (les fonctions
commençant par str, comme strlen, strcpy, strcat, ...) reçoivent
en paramètre des chaînes de caractères (et doivent donc posséder le
fin de chaîne final).
Danger : omettre le fin de chaîne provoquera généralement une "Erreur de
segmentation" car le traitement itératif des caractères se poursuivra en dehors du
tableau. Il est donc recommandé de ne pas oublier de déclarer son tableau d’une taille
suffisante pour y stocker la chaîne de caractères agrandie d’un caractère pour y placer le
fin de chaîne.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 80 / 153
Types agrégés
Exemple : les chaînes de caractères
#include <stdio.h>
#include <string.h> /* pour les fonctions str... */
int main()
{
char tab[4] = { ’a’, ’b’, ’c’, ’d’ }; // un tableau de caractères (il n’y a
pas de fin de chaînes)
char msg[] = "Bonjour"; // chaîne de caractères (le fin de chaîne est ajouté
automatiquement ici)
printf("msg : %s contient %d caractères\n", msg, strlen(msg));
printf("tab : %s contient %d caractères\n", tab, strlen(tab)); // risque !
tab[3] = 0; // maintenant, on place le caractère nul de fin de chaîne
printf("tab : %s contient %d caractères\n", tab, strlen(tab));
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 81 / 153
Types agrégés
Traité tab comme une chaîne engendrera un bug à l’exécution :
$ ./[Link]
msg : Bonjour contient 7 caractères
tab : abcdp\? contient 8 caractères <---- ici !
tab : abc contient 3 caractères
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 82 / 153
Types agrégés
string en C++ (1/2)
Attention : Ce n’est pas un type de base (c’est une classe) qui permet de stocker
(et de manipuler) une suite de lettres (une chaîne de caractères).
Utilisation de string en C++ :
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
int main (int argc, char **argv) {
string le0 = "Chuck";
string le1 = "Norris";
cout << "Il n’y a que deux éléments en informatique : le " << le0 << " et le
" << le1 << ’.’ << endl;
if(le0 != le1) cout << "Remarque: Les deux chaînes sont différentes" << endl;
printf("Une chaîne en C : Si windows existe encore c’est parce que %s %s ne s
’est jamais intéressé à l’informatique.\n", le0.c_str(), le1.c_str());
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 83 / 153
Types agrégés
string en C++ (2/2)
La définition de variable de type string est très simple :
string s1; // s1 contient 0 caractère
string s2 = "New York"; // s2 contient 8 caractères
string s3(60,’*’); // s3 contient 60 étoiles (*)
string s4 = s3; // copie de s3 dans la nouvelle s4 (operator=)
string s5(s3); // idem mais avec le constructeur de copie
string s6(s2, 4, 2); // s6 contient "Yo"
Lecture et écriture très simple également :
string s;
cin >> s; // La taille de s s’adapte toute seule à la saisie.
cout << s;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 84 / 153
Types agrégés
Compromis entre string et iostream
Le compromis entre un string et un iostream (istream ou ostream) est
stringstream (istringstream ou ostringstream). Cela permet : de
lire/écrire dedans avec » et « et d’obtenir un string comme résultat (en utilisant
la méthode str()).
#include <iostream>
#include <sstream> // stringstream !
#include <string>
using namespace std;
int main() {
string s("pi"); int n=2; float pi=3.14;
ostringstream oss;
oss << n << s << ’=’ <<2*pi; // on écrit dans oss, sans affichage
cout << "resultat : " << [Link]() << endl; // affiche : resultat : 2pi=6.28
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 85 / 153
Types structurés
struct
Une structure est un objet agrégé comprenant un ou plusieurs
champs (membres) d’éventuellement différents types que l’on
regroupe sous un seul nom afin d’en faciliter la manipulation et le
traitement.
Chacun des champs peut avoir n’importe quel type, y compris une
structure, à l’exception de celle à laquelle il appartient.
Déclaration d’une structure
[classe de memorisation] struct [etiquette]
{
type champ_1;
...
type champ_n;
} [identificateur];
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 86 / 153
Types structurés
Déclaration d’une structure
Le mot clé struct indique la déclaration d’une structure qui
correspond à une liste de déclarations entre accolades.
Il est possible de faire suivre le mot struct d’un nom baptisé
etiquette de la structure.
Cette etiquette désigne cette sorte de structure et, par la suite, peut
servir pour éviter d’écrire entièrement toute la déclaration.
Déclaration d’une structure date :
struct date
{
int jour,
mois,
annee;
};
struct date date_naissance;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 87 / 153
Types structurés
Définition de variable structurée
Il suffit pour cela de faire suivre la liste entre accolade
d’identificateurs de variables.
Définition de variables de type struct :
struct
{
int jour,
mois,
annee;
} naissancePierre, naissanceMarie;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 88 / 153
Types structurés
Déclaration et Définition de variable structurée
Il est aussi possible de combiner les deux possibilités : date est le
nom de la structure (le modèle), tandis que naissancePierre,
naissanceMarie et mortColuche sont des variables.
Définition de variables de type struct date :
struct date
{
int jour,
mois,
année;
) naissancePierre,
naissanceMarie = {7, 11, 1867}; // initialisation de la structure
naissanceMarie
struct date mortColuche;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 89 / 153
Types structurés
Particularités des structures
La taille d’une structure est la somme des tailles de tous les objets qui
la compose (cf. sizeof()). Dans notre exemple, la structure aura
une taille de 3*4 (int) soit 12 octets.
Pour accéder aux champs dune structure, il faut distinguer 2 cas :
1 la structure est délivrée par une variable : cet accès se fait à l’aide de
l’opérateur . (point)
Exemple : [Link] désigne le champ mois de la
variable naissanceMarie (soit 11 dans notre exemple).
2 la structure est délivrée par un pointeur p : cet accès se fait à l’aide de
l’opérateur -> (indirection)
Exemple : p->jour désigne le champ jour de la variable p
Ou : (*p).jour désigne le champ jour de la variable p
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 90 / 153
Types structurés
Définition de synonyme de type
Utilisation courante : le programmeur a l’habitude de définir un
nouveau type lorsqu’il déclare une nouvelle structure. La convention
habituelle est de mettre ce nom en majuscules.
Utilisation de typedef :
// directement :
typedef struct
{
int jour,
mois,
annee;
} DATE; // le type DATE
// ou apres :
typedef struct date DATE_NAISSANCE; // le type DATE_NAISSANCE
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 91 / 153
Types structurés
Utilisation des structures :
int main()
{
struct date naissanceMarie = {7, 11, 1867}; // initialisation de la structure naissanceMarie
struct date *p_naissanceMarie = &naissanceMarie;
DATE_NAISSANCE naissancePierre = {15, 5, 1859}; // initialisation de la structure naissancePierre
DATE mortColuche = {19, 6, 1986};
printf("Marie Curie est née le %02d/%02d/%4d\n", [Link], [Link],
[Link]);
printf("Marie Curie est née le %02d/%02d/%4d\n", p_naissanceMarie->jour, p_naissanceMarie->mois,
p_naissanceMarie->annee);
printf("Pierre Curie est né le %02d/%02d/%4d\n", [Link], [Link],
[Link]);
printf("Coluche est mort le %02d/%02d/%4d\n\n", [Link], [Link], [Link]);
printf("La structure struct date occupe une taille de %d octets\n", sizeof(struct date));
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 92 / 153
Types structurés
L’exécution suivante illustre différentes utilisations des structures :
Marie Curie est nee le 07/11/1867
Marie Curie est nee le 07/11/1867
Pierre Curie est ne le 15/05/1859
Coluche est mort le 19/06/1986
La structure struct date occupe une taille de 12 octets
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 93 / 153
Types structurés
union
Une union est conceptuellement identique à une structure mais peut,
à tout moment, contenir n’importe lequel des différents champs.
Rappel : Une structure définit une suite de champs, portant chacun
un unique nom et tous présents simultanément. Ils sont rangés dans
des emplacements mémoire consécutifs.
Une union, d’un autre côté, définit plusieurs manières de regarder
le même emplacement mémoire. A l’exception de ceci, la façon
dont sont déclarés et référencées les structures et les unions est
identique.
Déclaration d’une union
[classe de memorisation] union [etiquette] {
type champ_1;
...
type champ_n;
} [identificateur];
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 94 / 153
Types structurés
Définition d’une union
On va déclarer une union pour conserver une valeur d’une mesure
issue d’un capteur générique, qui peut par exemple fournir une mesure
sous forme d’un char (-128 à +127), d’un int (-2147483648 à
2147483647) ou d’un float.
Une telle définition définit une variable de nom valeurCapteur et de
type union qui peut, à tout moment, contenir SOIT un entier, SOIT
un réel, SOIT un caractère.
La taille mémoire de la variable valeurCapteur est égale à la taille
mémoire du plus grand type qu’elle contient (ici c’est float).
Définition d’une union :
union mesureCapteur {
int iVal;
float fVal;
char cVal;
} valeurCapteur;
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 95 / 153
Types structurés
Utilisation d’une union :
#include <stdio.h>
typedef union mesureCapteur {
int iVal;
float fVal;
char cVal;
} CAPTEUR;
int main() {
CAPTEUR vitesseVent, temperatureMoteur, pressionAtmospherique;
[Link] = 1013; /* un int */
[Link] = 50.5; /* un float */
[Link] = 2; /* un char */
printf("La pression atmosphérique est de %d hPa\n", [Link]);
printf("La température du moteur est de %.1f °C\n", [Link]);
printf("La vitesse du vent est de %d km/h\n", [Link]);
printf("Le type CAPTEUR occupe une taille de %d octets\n", sizeof(CAPTEUR));
return 0;
}
L’exécution du programme d’essai permet de vérifier cela :
La pression atmosphérique est de 1013 hPa
La température du moteur est de 50.5 °C
La vitesse du vent est de 2 km/h
Le type CAPTEUR occupe une taille de 4 octets
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 96 / 153
Types structurés
Champs de bits
Les champs de bits ("Drapeaux" ou "Flags"), qui ont leur principale
application en informatique industrielle, sont des structures qui ont
la possibilité de regrouper (au plus juste) plusieurs valeurs.
La taille d’un champ de bits ne doit pas excéder celle d’un entier.
Pour aller au-delà, on créera un deuxième champ de bits.
On utilisera le mot clé struct et on donnera le type des groupes de
bits, leurs noms, et enfin leurs tailles :
Déclaration d’un champ de bits
[classe de memorisation] struct [etiquette] {
{
type champ_1 : nombre_de_bits;
type champ_2 : nombre_de_bits;
[...]
type champ_n : nombre_de_bits;
} [identificateur];
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 97 / 153
Types structurés
Champ de bits : exemple
Si on reprend le type structuré date, on peut maintenant décomposer
ce type en trois groupes de bits (jour, mois et annee) avec le
nombre de bits suffisants pour coder chaque champ.
Les différents groupes de bits seront tous accessibles comme des
variables classiques d’une structure ou d’une union.
La taille mémoire d’une variable de ce type sera égale à 2 octets (5 +
4 + 7 = 16 bits).
Le champ de bits date :
struct date
{
unsigned short jour : 5; // 2^5 = 0-32 soit de 1 à 31
unsigned short mois : 4; // 2^4 = 0-16 soit de 1 à 12
unsigned short annee : 7; // 2^7 = 0-128 soit de 0 à 99 (sans les siècles)
};
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 98 / 153
Types structurés
Utilisation d’un champ de bits :
typedef struct date DATE_NAISSANCE;
int main (int argc, char **argv) {
struct date naissanceRitchie = {9, 9, 41};
struct date *p_naissanceRitchie = &naissanceRitchie;
DATE_NAISSANCE naissanceThompson = {4, 2, 43};
struct date mortRitchie = {12, 10, 11};
printf("Dennis Ritchie est né le %02d/%02d/%2d\n", [Link], [Link],
[Link]);
printf("Dennis Ritchie est né le %02d/%02d/%2d\n", p_naissanceRitchie->jour, p_naissanceRitchie->mois,
p_naissanceRitchie->annee);
printf("Dennis Ritchie est mort le %02d/%02d/%2d\n\n", [Link], [Link], mortRitchie.
annee);
printf("Ken Thompson est né le %02d/%02d/%2d\n", [Link], [Link],
[Link]);
printf("La structure champs de bits date occupe une taille de %d octets\n", sizeof(struct date));
return 0;
}
L’exécution du programme d’essai permet de vérifier cela :
Dennis Ritchie est né le 09/09/41
Dennis Ritchie est né le 09/09/41
Dennis Ritchie est mort le 12/10/11
Ken Thompson est né le 04/02/43
La structure champs de bits date occupe une taille de 2 octets
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 99 / 153
Conteneur en C++
Les conteneurs en C++ (1/2)
Le C++ possède une bibliothèque standard (SL pour Standard
Library ) qui est composée, entre autre, d’une bibliothèque de flux, de
la bibliothèque standard du C, de la gestion des exceptions, ..., et de
la STL (Standard Template Library : bibliothèque de modèles
standard). En fait, STL est une appellation historique communément
acceptée et comprise. Dans la norme, on ne parle que de SL.
Un conteneur (container ) est un objet qui contient d’autres
objets.
Il fournit un moyen de gérer les objets contenus (au minimum ajout,
suppression, parfois insertion, tri, recherche, ...) ainsi qu’un accès à
ces objets qui dans le cas de la STL consiste très souvent en un
itérateur.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 100 / 153
Conteneur en C++
Les conteneurs en C++ (2/2)
Les itérateurs permettent de parcourir une collection d’objets sans
avoir à se préoccuper de la manière dont ils sont stockés. Ceci permet
aussi d’avoir une interface de manipulation commune, et c’est ainsi
que la STL fournit des algorithmes génériques qui s’appliquent à la
majorité de ses conteneurs (tri, recherche, ...).
Parmi les conteneurs disponibles dans la STL on trouve les tableaux
(vector ), les listes (list), les ensembles (set), les piles (stack), et
beaucoup d’autres.
Nous allons nous intéresser à la notion de vector.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 101 / 153
Conteneur en C++
vector : les tableaux en C++
Un vector est un tableau dynamique où il est particulièrement aisé
d’accéder directement aux divers éléments par un index, et d’en
ajouter ou en retirer à la fin.
A la manière des tableaux de type C, l’espace mémoire alloué pour un
objet de type vector est toujours continu, ce qui permet des
algorithmes rapides d’accès aux divers éléments.
La forme générique de déclaration d’un tableau est la suivante :
vector<type> identificateur(taille);
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 102 / 153
Conteneur en C++
Exemple 1 : les vector
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v1; // un vecteur d’entier vide
v1.push_back( 10 ); // ajoute l’entier 10 à la fin
v1.push_back( 9 ); // ajoute l’entier 9 à la fin
v1.push_back( 8 ); // ajoute l’entier 8 à la fin
// enleve le dernier élément
v1.pop_back(); // supprime l’entier 8
// utilisation d’un indice pour parcourir le vecteur v1
cout << "Le vecteur v1 contient " << [Link]() << " entiers : \n";
for(int i=0;i<[Link]();i++)
cout << "v1[" << i << "] = " << v1[i] << ’\n’;
cout << ’\n’;
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 103 / 153
Conteneur en C++
Exemple 2 : les vector
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v2(4, 100); // un vecteur de 4 entiers initialisés avec la valeur
100
// utilisation d’un itérateur pour parcourir le vecteur v2
cout << "Le vecteur v2 contient " << [Link]() << " entiers : ";
for (vector<int>::iterator it = [Link](); it != [Link](); ++it)
cout << ’ ’ << *it;
cout << ’\n’;
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 104 / 153
Conteneur en C++
Ces deux exemples donnent :
Le vecteur v1 contient 2 entiers :
v1[0] = 10
v1[1] = 9
Le vecteur v2 contient 4 entiers : 100 100 100 100
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 105 / 153
Les fonctions
Programmation modulaire
Le découpage d’un programme en sous-programmes est appelée
programmation modulaire.
La programmation modulaire se justifie par de multiples raisons :
un programme écrit d’un seul tenant devient très difficile à comprendre
dès lors qu’il dépasse une page de texte
la programmation modulaire permet d’éviter des séquences
d’instructions répétitives
la programmation modulaire permet le partage d’outils communs qu’il
suffit d’avoir écris et mis au point une seule fois
des fonctions convenablement choisies permettent souvent de
dissimuler les détails d’un calcul contenu dans certaines parties du
programme qu’il n’est pas indispensable de connaître.
D’une manière générale, la programmation modulaire clarifie
l’ensemble d’un programme et facilite les modifications ultérieures.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 106 / 153
Les fonctions
Procédure et Fonction
Dans la plupart des langages évolués, on trouve deux sortes de
modules : les procédures et les fonctions.
Le langage C n’offre pour sa part qu’une seule sorte de module : la
fonction. Si une fonction ne rend aucun résultat et elle peut alors
être considérée comme une procédure.
Une fonction est déclarée de la manière suivante :
[classe de memorisation] [type] identificateur (parametres);
La classe de mémorisation et type sont optionnels.
Lorsque la classe de mémorisation est absente, la fonction est
considérée externe et est donc accessible par tous les modules avec
lesquels une édition de liens est faite.
Si le type est absent, le résultat de la fonction est par défaut un entier
int.
Lorsqu’il est présent, il définit le type du résultat que produit la
fonction.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 107 / 153
Les fonctions
Fonction
Une fonction est définie de la manière suivante :
[classe de memorisation] [type] identificateur (parametres)
{
...
return resultat;
}
La liste de paramètres n’est en fait qu’une liste de couples
type/identificateur séparés par des virgules.
Derrière cela, se trouve le bloc {} qui définit les traitements effectués
dans la fonction.
Pour retourner son résultat, la fonction doit utiliser l’instruction
return.
Dans tous les cas, une fonction peut être utilisée :
comme opérande dans une expression, à partir du moment où il y a
concordance de types
comme instruction.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 108 / 153
Les fonctions
Instruction return
L’instruction return provoque le retour chez l’appelant de la fonction
courante. Les deux formes d’utilisation sont : return ou return
expression.
Dans ce premier cas, il s’agit d’une fonction ne retournant aucun
résultat (void). Elle a été programmée en tant que procédure.
void foo() {
instruction;
return;
}
Dans ce second cas, il s’agit d’une fonction qui retourne la valeur
de l’expression a%2. Si nécessaire la valeur retournée est convertie,
comme pour une affectation, dans le type du résultat.
int estImpair(int a) {
return (a%2); // retourne 0 (pair) ou 1 (impair)
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 109 / 153
Les fonctions
Exemple : la fonction multiplier
#include <stdio.h>
/* La fonction de nom multiplier calcule la multiplication de deux
entiers (a et b) et retourne le résultat sous forme d’entier */
int multiplier(int a, int b) {
return a * b;
}
int main() {
int x = 2; int y = 3; int res1, res2;
// Appel de fonction en lui passant les valeurs de x et y
res1 = multiplier(x, y); // on récupère la valeur retournée dans res1
printf("La multiplication de %d par %d donne comme résultat : %d\n",
x, y , res1);
res2 = multiplier(x, y) + 2; // on peut utiliser un fonction dans une
instruction
printf("Et si on ajoute 2, on obtient : %d\n", res2);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 110 / 153
Les fonctions
Exemple : la fonction multiplier
Exemple
La multiplication de 2 par 3 donne comme résultat : 6
Et si on ajoute 2, on obtient : 8
Remarques : En aucun cas l’imbrication de fonctions n’est possible. Enfin,
l’appel de fonctions en C peut se faire de manière récursive (une fonction
peut s’appeler elle-même).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 111 / 153
Les fonctions
Pour conclure, il faut distinguer :
la déclaration d’une fonction qui est une instruction fournissant au
compilateur un certain nombre d’informations concernant une fonction.
Il existe une forme recommandée dite prototype :
int plus(int, int) ; ←
− fichier en-tête (.h)
sa définition qui revient à écrire le corps de la fonction (qui définit
donc les traitements effectués dans le bloc {} de la fonction)
int plus(int a, int b) { return a + b ; } ← − fichier source (.c ou .cpp)
l’appel qui est son utilisation. Elle doit correspondre à la déclaration
faite au compilateur qui vérifie.
int res = plus(2, 2) ; ←− fichier source (.c ou .cpp)
Remarque : La définition d’une fonction tient lieu de déclaration. Mais elle doit être
"connue" avant son utilisation (un appel). Sinon, le compilateur génère un message
d’avertissement (warning) qui indiquera qu’il a lui-même fait une déclaration implicite de
cette fonction : "attention : implicit declaration of function ’multiplier’"
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 112 / 153
Les fonctions
Pointeur vers une fonction
Le nom d’une fonction est une constante de type pointeur :
int f(int x, int y)
{
return x+y;
}
int (*pf)(int, int); // pointeur vers fonction admettant 2 entiers en
paramètres et retournant un entier
pf = f; // pointeur vers la fonction f
printf("%d\n", (*pf)(3, 5)); // affiche 8
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 113 / 153
Les fonctions
Passage de paramètres
Lors d’un appel de fonction, les paramètres passés à la fonction
doivent correspondre aux paramètres déclarés par leur nombre et par
la compatibilité de leurs types.
En C, il n’existe qu’un seul mode de passage des paramètres : c’est le
passage par valeur. On dit aussi par passage par recopie (de
valeurs). Il faut donc considérer un paramètre comme une variable
locale ayant été initialisée avant l’appel de la fonction.
int multiplier(int a, int b);
int x = 2; int y = 3; int res;
// Appel de fonction en lui passant les valeurs de x et y
// donc la valeur de x est recopié dans a et la valeur de y est recopié
dans b
res = multiplier(x, y);
Remarque : les noms peuvent être identiques cela ne change rien au
mécanisme de recopie !
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 114 / 153
Les fonctions
Problème : tentative de permutation de deux variables
#include <stdio.h>
void permuter(int a, int b) {
int c;
c = a;
a = b;
b = c;
printf("Dans la fonction après permutation : a = %d et b = %d\n", a,
b);
}
int main() {
int a = 2; int b = 3;
printf("Dans le main : a = %d et b = %d\n", a, b);
permuter(a, b);
printf("Après l’appel de la fonction : a = %d et b = %d\n", a, b);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 115 / 153
Les fonctions
Mise en évidence du problème de modification de copies
Effectivement, cela ne marche pas car la fonction permuter à travailler sur
des copies de a et b :
La permutation ne fonctionne pas :
Dans le main : a = 2 et b = 3
Dans la fonction après permutation : a = 3 et b = 2
Après l’appel de la fonction : a = 2 et b = 3
Piste : L’exception à cette règle est le cas des tableaux. En effet, lorsque
le nom d’un tableau constitue l’argument d’une fonction, c’est l’adresse du
premier élément qui est transmise. Ses élément ne sont donc pas recopiés.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 116 / 153
Les fonctions
Permutation d’éléments d’un tableau (1/3)
Ceci reste en effet cohérent avec le fait qu’il n’existe pas de variable
désignant un tableau comme un tout. Quand on déclare int t[10], t ne
désigne pas l’ensemble du tableau. t est une constante de type pointeur
vers un int dont la valeur est &t[O] (adresse du premier élément du
tableau).
#include <stdio.h>
void permuter(int t[])
{
int c;
c = t[0];
t[0] = t[1];
t[1] = c;
printf("Dans la fonction après permutation : t[0] = %d et t[1] = %d\n
", t[0], t[1]);
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 117 / 153
Les fonctions
Permutation d’éléments d’un tableau (2/3)
Remarque : c’est une très bonne chose en fait car dans le cas d’un "gros
tableau", on évite ainsi de recopier toutes les cases. Le passage d’une
adresse (par exemple 32 bits) sera beaucoup plus efficace et rapide.
int main()
{
int t[2] = { 2, 3 };
printf("Dans le main : t[0] = %d et t[1] = %d\n", t[0], t[1]);
permuter(t);
printf("Après l’appel de la fonction : t[0] = %d et t[1] = %d\n", t
[0], t[1]);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 118 / 153
Les fonctions
Permutation d’éléments d’un tableau (3/3)
Effectivement, cela marche car la fonction permuter à travailler avec
l’adresse du tableau :
Exemple
Dans le main : t[0] = 2 et t[1] = 3
Dans la fonction après permutation : t[0] = 3 et t[1] = 2
Après l’appel de la fonction : t[0] = 3 et t[1] = 2
En conséquence, pour réaliser l’effet de passage de paramètre par
adresse, il suffit alors de passer en paramètre un pointeur vers la
variable.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 119 / 153
Les fonctions
Passage par adresse (1/2)
Pour qu’une fonction puisse modifier le contenu d’une variable passée
en paramètre, on doit utiliser en C un pointeur sur cette variable.
Exemple de déclaration : void permuter(int *pa, int *pb); //
pa et pb sont des pointeurs sur des int.
Le passage se fait toujours par valeur ou par recopie sauf que
maintenant, on passe l’adresse d’une variable.
void permuter(int *pa, int *pb);
int main() {
int a = 2; int b = 3;
printf("Dans le main : a = %d et b = %d\n", a, b);
permuter(&a, &b); // l’adresse (&) de a est recopiée dans le pointeur
pa, idem pour l’adresse de b dans pb
printf("Après l’appel de la fonction : a = %d et b = %d\n", a, b);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 120 / 153
Les fonctions
Passage par adresse (2/2)
void permuter(int *pa, int *pb) {
int c;
c = *pa;
*pa = *pb;
*pb = c;
printf("Dans la fonction après permutation : a = %d et b = %d\n", *pa
, *pb);
}
La permutation fonctionne maintenant :
Dans le main : a = 2 et b = 3
Dans la fonction après permutation : a = 3 et b = 2
Après l’appel de la fonction : a = 3 et b = 2
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 121 / 153
Les fonctions
Protection contre le passage par adresse (1/4)
Remarque n°1 : lorsqu’on passe une adresse par pointeur, il y a un risque
que la fonction modifie cette adresse. Dans ce cas, on peut se protéger en
déclarant l’adresse passée comme constante.
#include <stdio.h>
void foo(int * const pa) {
int un_autre_a;
pa = &un_autre_a; // tentative de modification d’adresse contenue
dans le pointeur
}
int main() { int a = 2; foo(&a); return 0; }
On obtient un contrôle d’accès à la compilation :
In function ’foo’:
erreur: assignment of read-only location ’pa’
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 122 / 153
Les fonctions
Protection contre le passage par adresse (2/4)
Remarque n°2 : On a vu que lorsque l’on doit passer en paramètre des
"grosses" tailles de variables, il serait plus efficace et plus rapide à
l’exécution de passer une adresse. Mais il y aurait un risque que la fonction
modifie cette variable. Dans le cas d’un accès limité en lecture, on peut se
protéger en déclarant le pointeur comme constant.
#include <stdio.h>
void foo(const long double *pa) {
printf("Je suis un pointeur de taille plus légère %d octets\n",
sizeof(pa));
printf("Je contiens l’adresse de a : pa = %p\n", pa);
printf("et j’ai un accès en lecture : a = %.1Lf\n", *pa);
/* et non, tu ne peux mas modifier le contenu de la variable pointée
! */
printf("mais pas en écriture !\n");
*pa = 3.5; // ligne 10 à enlever !
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 123 / 153
Les fonctions
Protection contre le passage par adresse (3/4)
On obtient un contrôle d’accès à la compilation :
In function ’foo’:
ligne 10: erreur: assignment of read-only location ’*pa’
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 124 / 153
Les fonctions
Protection contre le passage par adresse (4/4)
int main() { long double a = 2.0;
printf("Je suis une grosse variable de taille %d octets\n",sizeof(a));
printf("Je suis a et j’ai pour valeur : a = %.1Lf\n", a);
printf("et pour adresse : &a = %p\n\n", &a);
foo(&a);
return 0;
}
Une utilisation de pointeur constant :
Je suis une grosse variable de taille 12 octets
Je suis a et j’ai pour valeur : a = 2.0
et pour adresse : &a = 0xbf8d8240
Je suis un pointeur de taille plus légère 4 octets
Je contiens l’adresse de a : pa = 0xbf8d8240
et j’ai un accès en lecture : a = 2.0
mais pas en écriture !
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 125 / 153
Les fonctions
Passage par référence (1/2)
En C++, on a aussi la possibilité d’utiliser le passage par référence.
Intérêt : lorsqu’on passe des variables (ou des objets) en paramètre de
fonctions ou méthodes et que le coût d’une recopie par valeur est trop
important ("gros" objet), on choisira un passage par référence.
void permuter(int &a, int &b) {
int c;
c = a; a = b; b = c;
printf("Dans la fonction après permutation : a = %d et b = %d\n", a,b);
}
int main() {
int a = 2; int b = 3;
printf("Dans le main : a = %d et b = %d\n", a, b);
permuter(a, b);
printf("Après l’appel de la fonction : a = %d et b = %d\n", a, b);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 126 / 153
Les fonctions
Passage par référence (2/2)
La permutation fonctionne également en utilisant un passage par
référence :
Dans le main : a = 2 et b = 3
Dans la fonction après permutation : a = 3 et b = 2
Après l’appel de la fonction : a = 3 et b = 2
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 127 / 153
Les fonctions
Protection contre le passage par référence
Si le paramètre ne doit pas être modifié, on utilisera alors un passage par
référence sur une variable constante :
void foo(const long double &a) {
printf("J’ai un accès en lecture : a = %.1Lf\n", a);
printf("mais pas en écriture !\n");
//a = 3.5; // interdit car const ! (voir message)
}
int main() { long double a = 2.0;
printf("Je suis a et j’ai pour valeur : a = %.1Lf\n", a);
foo(a);
return 0;
}
Le compilateur nous préviendra de toute tentative de modification :
In function ’void foo(const long double&)’:
erreur: assignment of read-only reference ’a’
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 128 / 153
Les fonctions
Bilan
En résumé, voici les différentes déclarations en fonction du contrôle
d’accès désiré sur un paramètre reçu :
passage par valeur →
− accès en lecture seule à la variable passée en
paramètre : void foo(int a);
passage par adresse →
− accès en lecture seule à la variable passée en
paramètre : void foo(const int *a);
passage par adresse →
− accès en lecture et en écriture à la variable
passée en paramètre : void foo(int *a);
passage par adresse →
− accès en lecture et en écriture à la variable
passée en paramètre (sans modification de son adresse) : void
foo(int * const a);
passage par adresse →
− accès en lecture seule à la variable passée en
paramètre (sans modification de son adresse) : void foo(const int
* const a);
passage par référence →
− accès en lecture et en écriture à la variable
passée en paramètre : void foo(int &a);
passage par référence →
− accès en lecture seule à la variable passée en
paramètre : void foo(const int &a);
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 129 / 153
Les fonctions
Surcharge
Il est généralement conseillé d’attribuer des noms distincts à des
fonctions différentes.
Cependant, lorsque des fonctions effectuent la même tache sur des
objets de type différent, il peut être pratique de leur attribuer des
noms identiques.
Ce n’est pas possible de réaliser cela en C mais seulement en C++.
L’utilisation d’un même nom pour des fonctions (ou méthodes)
s’appliquant à des types différents est nommée surcharge.
Remarque : cette technique est déjà utilisée dans le langage C++
pour les opérateurs de base. L’addition, par exemple, ne possède
qu’une seul nom (+) alors qu’il est possible de l’appliquer à des
valeurs entières, virgule flottante, etc ...
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 130 / 153
Les fonctions
La surcharge de fonctions en action (1/2)
#include <iostream>
using namespace std;
// Un seul nom au lieu de print_int, print_float, ...
void print(int);
void print(float);
int main (int argc, char **argv)
{
int n = 2;
float x = 2.5;
print(n);
print(x);
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 131 / 153
Les fonctions
La surcharge de fonctions en action (1/2)
void print(int a)
{
cout << "je suis print et j’affiche un int : " << a << endl;
}
void print(float a)
{
cout << "je suis print et j’affiche un float : " << a << endl;
}
On obtient :
je suis print et j’affiche un int : 2
je suis print et j’affiche un float : 2.5
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 132 / 153
Les fonctions
Signature et Prototype
Remarque importante : la surcharge ne fonctionne que pour des
signatures différentes et le type de retour d’une fonction ne fait pas
partie de la signature.
On distingue :
La signature d’une fonction est le nombre et le type de chacun
de ses arguments.
Le prototype d’une fonction est le nombre et le type de ses
arguments (signature) et aussi de sa valeur de retour.
Aller plus loin : il est aussi possible de surcharger les opérateurs de
base avec des signatures différentes pour ses propres classes.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 133 / 153
Classe d’allocation
Statique ou automatique
La classe d’allocation détermine la façon dont l’emplacement mémoire
attribué à une variable est géré.
Elle peut être statique ou automatique.
Les variables de classe statique voient leur emplacement alloué une
fois pour toutes avant le début de l’exécution du programme. C’est le
cas des variables globales.
Les variables de classe automatique voient leur emplacement alloué au
moment de l’entrée dans un bloc ou une fonction. Il est supprimé lors
de la sortie de ce bloc ou de cette fonction. C’est le cas des variables
locales.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 134 / 153
Classe d’allocation
Déclaration
La syntaxe d’une déclaration est :
[classe_de_mémorisation] [qualifieurs] type identificateur
[=valeur];
Les crochets [] indiquent ce qui est optionnel donc non obligatoire
pour réaliser une déclaration.
classe_de_mémorisation peut prendre les valeurs suivantes : static,
extern, automatic, register.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 135 / 153
Classe d’allocation
Cas des variables globales
static : Ces variables sont globales au bloc ou au fichier de
compilation et ne seront connues que du module dans lequel elles sont
définies. Les variables statiques sont initialisées à 0 par défaut.
extern : extern permet de déclarer des variables qui sont définies
dans un autre fichier source. Une telle variable est accessible par
tous les modules avec lesquels une édition de liens est effectuée.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 136 / 153
Classe d’allocation
Cas des variables locales (1/2)
static : La visibilité de ces variables est limitée au bloc dans lequel
elles sont définies. Si un programme appelle à nouveau un bloc dans
lequel une variable statique est déclarée, celle-ci conserve sa
précédente valeur.
extern : Comme pour les variables globales, extern permet de
déclarer des variables qui sont définies ailleurs.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 137 / 153
Classe d’allocation
Cas des variables locales (2/2)
automatic : Les variables automatiques sont considérées locales au bloc dans lequel elles
sont déclarées. Un espace leur est alloué (dans la pile, stack en anglais) à chaque entrée
de bloc et est libéré a chaque sortie de bloc. Une variable automatique ne reçoit aucune
valeur initiale par défaut. L’absence de toute classe de mémorisation dans la déclaration
d’une variable locale amène le compilateur à le considérer automatic.
register : Elles obéissent aux mêmes règles que les variables automatiques. Cependant,
la désignation de register indique au compilateur que ces variables doivent être
conservées dans des ressources à accès rapide du CPU (généralement dans un registre du
microprocesseur). Il faut noter qu’une variable register ne possède pas d’adresse en
mémoire et que par conséquent, l’opérateur & ne peut lui être appliqué. D’autre part, ce
sont des variables limitées en nombre, ainsi qu’à certains types (entier, caractère ou
pointeur). Son utilisation sera vue plus tard (programmation multi-tâches par exemple)
car elle nécessite un bon niveau d’expertise.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 138 / 153
Classe d’allocation
Exemple : variable locale statique
#include <stdio.h>
int foo()
{
static int nb = 0; // je suis une variable locale statique
// je conserverais ma valeur à chaque appel
// mais attention je ne suis initialisée qu’une seule fois
nb++; // je compte le nombre d’appels à la fonction foo
printf("La fonction foo a été appelée %d fois\n", nb);
}
int main()
{
foo();
foo();
foo();
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 139 / 153
Classe d’allocation
Exemple : exécution
La fonction foo a été appelée 1 fois
La fonction foo a été appelée 2 fois
La fonction foo a été appelée 3 fois
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 140 / 153
Classe d’allocation
Exemple : valeur initiale des variables globales et locales
#include <stdio.h>
static int i; // je suis une variable globale limitee a ce fichier
int foo()
{
int j; // je suis une variable locale, ma durée de vie est celle de la
fonction foo
printf("La variable locale j a pour valeur %d\n", j); // la variable locale j
n’a pas été initialisée, elle contient donc n’importe quoi !
printf("La variable locale j a pour adresse %p\n", &j);
}
int main()
{
printf("La variable globale i a pour valeur %d\n", i); // la variable globale
i n’a pas été initialisée, elle contient donc 0 !
printf("La variable globale i a pour adresse %p\n", &i);
foo();
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 141 / 153
Classe d’allocation
Exemple : exécution
La variable globale i a pour valeur 0
La variable globale i a pour adresse 0x8049660
La variable locale j a pour valeur 134513960
La variable locale j a pour adresse 0xbfc2facc
Conclusion : ne pas initialiser ses variables est une source d’erreur
(bug).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 142 / 153
Classe d’allocation
Les variables globales
Avantages
grande facilité d’utilisation
gain de temps à l’exécution
facilite la communication entre des fonctions appelées à des niveaux
différents (cf. programmation multi-tâche)
pallie les limites de mémoire automatique sur certaines machines
Inconvénients
trop grande facilité d’utilisation !
plus de notion de paramètre (nécessite alors de nombreux
commentaires pour indiquer qui modifient ces variables globales)
risques d’effets de bord (le plus dangereux)
Conclusion : Les variables globales sont à bannir de la programmation structurée.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 143 / 153
La mémoire
La pile et le tas
La mémoire dans un ordinateur est une succession d’octets (soit 8
bits), organisés les uns à la suite des autres et directement
accessibles par une adresse.
En C/C++, la mémoire pour stocker des variables est organisée en
deux catégories :
1 la pile (stack)
2 le tas (heap)
Remarque : Dans la plupart des langages de programmation compilés,
la pile (stack) est l’endroit où sont stockés les paramètres d’appel et
les variables locales des fonctions.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 144 / 153
La mémoire
La pile (stack)
La pile (stack) est un espace mémoire réservé au stockage des
variables désallouées automatiquement.
Sa taille est limitée mais on peut la régler (appel POSIX setrlimit).
La pile est bâtie sur le modèle LIFO (Last In First Out) ce qui signifie
"Dernier Entré Premier Sorti". Il faut voir cet espace mémoire comme
une pile d’assiettes où on a le droit d’empiler/dépiler qu’une seule
assiette à la fois. Par contre on a le droit d’empiler des assiettes de
taille différente. Lorsque l’on ajoute des assiettes on les empile par le
haut, les unes au dessus des autres. Quand on les "dépile" on le fait
en commençant aussi par le haut, soit par la dernière posée.
Lorsqu’une valeur est dépilée elle est effacée de la mémoire.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 145 / 153
La mémoire
Le tas (heap)
Le tas (heap) est l’autre segment de mémoire utilisé lors de
l’allocation dynamique de mémoire durant l’exécution d’un
programme informatique.
Sa taille est souvent considére comme illimitée mais elle est en réalité
limitée.
Les fonctions malloc et free, ainsi que les opérateurs du langage
C++ new et delete permettent, respectivement, d’allouer et
désallouer la mémoire sur le tas.
La mémoire allouée dans le tas doit être désallouée explicitement.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 146 / 153
Allocation dynamique
malloc et free
L’allocation d’une nouvelle zone mémoire se fait dans un endroit
particulier de la mémoire appelée le tas (heap).
Elle se fait par la fonction malloc : void *malloc(size_t
taille);
L’argument transmis correspond à la taille en octets de la zone
mémoire désirée.
La valeur retournée est un pointeur void * sur la zone mémoire
allouée, ou NULL en cas d’échec de l’allocation.
Si vous devez redimensionner un espace mémoire qui a été alloué
dynamiquement, il faudra utiliser la fonction realloc().
La mémoire allouée doit, à un moment ou un autre, être libérée.
Cette libération mémoire se fait par la procédure free : void
free(void *pointeur);
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 147 / 153
Allocation dynamique
Exemple : allocation mémoire
int *p; // pointeur sur un entier
int *T; // pointeur sur un entier
// allocation dynamique d’un entier
p = (int *)malloc(sizeof(int)); // alloue 4 octets (= int) en mémoire
*p = 1; // ecrit 1 dans la zone mémoire allouée
// allocation dynamique d’un tableau de 10 int
T = (int *)malloc(sizeof(int) * 10); // alloue 4 * 10 octets en mémoire
// initialise le tableau avec des 0 (cf. la fonction memset)
for(int i=0;i<10;i++) {
*(T+i) = 0; // les 2 écritures sont possibles
T[i] = 0; // identique à la ligne précèdente
}
// ou plus directement
memset(T, 0, sizeof(int)*10); // il faudra alors inclure string.h
free(p);
free(T);
Une fois qu’une zone mémoire a été libérée, il ne faut sous aucun prétexte y accéder, de
même qu’il ne faut pas tenter de la libérer une seconde fois.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 148 / 153
Allocation dynamique
Règles de bonne conduite
Un certain nombre de règles, quand elles sont observées, permettent de se
mettre à l’abri de problèmes :
Toute déclaration de pointeur s’accompagne de son initialisation à
NULL
Avant tout appel de malloc(), on doit s’assurer que le pointeur à
allouer est bien NULL
Après tout appel de malloc(), on s’assure qu’aucune erreur ne s’est
produite
Avant de libérer une zone mémoire, on s’assure que son pointeur n’est
pas NULL
Dès qu’une zone mémoire vient d’être libérée par free(), on
réinitialise son pointeur à NULL
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 149 / 153
Allocation dynamique
new et delete
Pour allouer dynamiquement en C++, on utilisera l’opérateur new.
Celui-ci renvoyant une adresse où est crée la variable en question, il
nous faudra un pointeur pour la conserver.
Manipuler ce pointeur, reviendra à manipuler la variable allouée
dynamiquement.
Pour libérer de la mémoire allouée dynamiquement en C++, on
utilisera l’opérateur delete.
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 150 / 153
Allocation dynamique
Pour allouer dynamiquement en C++, on utilisera l’opérateur new.
Exemple : allocation dynamique
#include <iostream>
#include <iostream>
#include <new>
using namespace std;
int main ()
{
int * p1 = new int; // pointeur sur un entier
*p1 = 1; // ecrit 1 dans la zone mémoire allouée
cout << *p1 << endl; // lit et affiche le contenu de la zone mémoire allouée
delete p1; // libère la zone mémoire allouée
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 151 / 153
Allocation dynamique
Exemple : allocation dynamique d’un tableau
#include <iostream>
#include <new>
using namespace std;
int main ()
{
int * p2 = new int[5]; // alloue un tableau de 5 entiers en mémoire
// initialise le tableau avec des 0 (cf. la fonction memset)
for(int i=0;i<5;i++)
{
*(p2 + i) = 0; // les 2 écritures sont possibles
p2[i] = 0; // identique à la ligne précèdente
cout << "p2[" << i << "] = " << p2[i] << endl;
}
delete [] p2; // libère la mémoire allouée
return 0;
}
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 152 / 153
Allocation dynamique
Fuite de mémoire
L’allocation dynamique dans le tas ne permet pas la désallocation
automatique.
Chaque allocation avec "new" doit impérativement être libérée
(détruite) avec "delete" sous peine de créer une fuite de mémoire.
La fuite de mémoire est une zone mémoire qui a été allouée dans le
tas par un programme qui a omis de la désallouer avant de se
terminer. Cela rend la zone inaccessible à toute application (y compris
le système d’exploitation) jusqu’au redémarrage du système. Si ce
phénomène se produit trop fréquemment la mémoire se remplit de
fuites et le système finit par tomber faute de mémoire.
Ce problème est évité en Java en introduisant le mécanisme de
"ramasse-miettes" (Garbage Collector ).
tv (BTS IRIS Avignon) Cours C/C++ tvaira@[Link] « v0.2 153 / 153