Structures de données en C++
Structures de données en C++
Structure de données
Giraud, Nassinda T.
2
Chapitre I : Principes de base du langage C++
I.1 Introduction
Le langage C++ peut être considéré comme un perfectionnement du langage C qui offre les
possibilités de la POO.
Les notions de base de la programmation en C restent valables en C++, néanmoins C et C++
diffèrent sur quelques conventions (déclaration des variables et des fonctions, nouveaux mots
clés…)
Ce chapitre retrace ses différences, et traite les autres outils de la programmation structurée
ajouté à C++
La fonction main est la fonction appelée par le système d’exploitation lors de l'exécution du
programme
▪ { et } délimitent le corps de la fonction
▪ main retourne un entier au système: 0 (zéro) veut dire succès
▪ Chaque expression doit finir par; (point virgule)
I.2.2 Commentaires
En C et C++: Commentaires sur plusieurs lignes : délimités par /* (début) et */ (fin).
En C++ uniquement : Commentaires sur une seule ligne : délimités par // (début) et fin de ligne
(n’existe pas en C)
3
Un suffixe est utilisé pour déterminer le type de fichier
▪ .h, .H, .hpp, .hxx : pour les fichiers de description d’interface (header files ou include
files)
▪ .c, .cc, .cxx, .cpp, .c++ : pour les fichiers d’implémentation
4
Exemple:
#include <iostream>
#include <cstdio> // Les librairie C
int main()
{
std ::cout << "Hello !"<<std ::endl;
printf(" Hello bis !\n ") ; // Lesinstructions C return 0;
}
Syntaxe:
using namespace std;
Exemple d’utilisation:
#include <iostream> #include
<conio.h>
using namespace std; // Importation de l'espace de nom
void main()
{ double x, y;
// Le préfixe n'est plus requis :
cout << "X:";
cin >> x; cout
<< "Y:";
// Il est toujours possible d'utiliser le préfixe :
std::cin >> y;
cout << "x * y = " << x * y << endl;
_getch(); // Les fonctions de la bibliothèque C sont toujours utilisables
}
Il est possible aussi de définir un espace de nom alors les identificateurs de cet espace seront
préfixés.
Syntaxe :
namespace nom
{
// Placer ici les déclarations faisant partie de l'espace de nom
}
5
Exemple:
#include <iostream>
using namespace std; // Importation de l'espace de nom
namespace USTOMB
{
void afficher_adresse()
{ cout << "USTOMB" << endl;
cout << "El Mnaouar, BP 1505 , "<<endl<< " Bir El Djir 31000"<<endl ;
}
void afficher_coordonnees_completes()
{ afficher_adresse(); // Préfixe non requis, car dans le même espace de nom :
cout << "Tel : 041 61 71 46\n";
}
}
int main()
{ USTOMB::afficher_coordonnees_completes(); return 0;
}
Syntaxes :
cout << exp_1 << exp_2 << … … << exp_n ;
Tous les caractères de formatage comme '\t', '\n' peuvent être utilisés. Par ailleurs, l’expression
endl permet le retour à la ligne et le vidage du tampon.
6
Exemple:
#include <iostream> // Standard C++ I/O using
namespace std;
int main()
{ int val1, val2;
cout << "Entrer deux entiers: " << endl;
cin >> val1 >> val2;
cout << "Valeurs entrées: " << val1 << " et " << val2 <<
endl; cout << "valeur 1+valeur 2 =" << val1 + val2 << endl;
return 0;
}
Exemple:
#include <iostream>
#include <iomanip> // attention a bien inclure cette librairie using namespace std ;
int main()
{ int i=1234;
float p=12.3456;
cout << "|" << setw(8) << setfill('*‘) << hex << i << "|" <<endl
<< "|" << setw(6) << setprecision(4) << p <<"|"<< endl;
return 0;
}
7
Affichage de
l’exécution du code:
Exemple:
ageEtudiant, nom_etudiant, NOMBRE_Etudiants: Noms valides.
Ageétudiant, nom etudiant: Noms non valides.
8
bool Booléen Prend deux valeurs: true et false
Même taille que le type int, mais une conversion implicite
parfois 1 sur quelques (valant 0 ou 1) est faite par le
compilateurs compilateur lorsque l'on affecte
un
#include <iostream>
using namespace std; int
main()
{ string nomEtudiant("Moussa");
int ageEtudiant(20);
float moyGen(12.43);
double pi(3.14159);
bool estAdmis(true);
I.3.4 Constantes
Contrairement aux variables les constantes ne peuvent pas être initialisées après leur
déclaration et leur valeur ne peut pas être modifiée après initialisation. Elles doivent être
déclarées avec le mot clé const et obligatoirement initialisées dès sa définition.
Syntaxe:
const <type> <NomConstante> = <valeur>;
Exemple:
9
const char c ('A'); //exemple de constante caractère const
int i (2017); //exemple de constante entière const double PI
(3.14); //exemple de constante rèel
b. Références
En C++, on peut coller plusieurs étiquettes à la même case mémoire (ou variable). On obtient
alors un deuxième moyen d'y accéder. On parle parfois d'alias, mais le mot correct
en C++ est référence. Pour déclarer une référence sur une variable, on utilise une esperluette
(&). Exemple:
#include <iostream>
using namespace std; int
main()
{ int i(5); int & j(i); // la variable ‘j ‘ fait référence à ‘i '. On peut utiliser à partir
d'ici 'j ' ou 'i '
// indistinctement puisque ce sont deux étiquettes de la même case en mémoire
int k=j ;
cout<<" Valeur de i: "<<i<< " || "<<" Valeur de j: "<<j<<" || "<<" Valeur de k: "<<k<<
endl; j=j+2; k=i ;
cout<<" Valeur de i: "<<i<< " || "<<" Valeur de j: "<<j<<" || "<<" Valeur de k: "<<k<< endl;
return 0;
}
10
Affichage de l’exécution du
code:
▪ Remarque: Si la variable est définie dans un autre module il faut la déclarer avant de
l’utiliser en utilisant le mot clé « extern » Exemple: [1]
extern float maxCapacity; // declaration float
limit = maxCapacity * 0.90; // usage
c. Tableaux
Un tableau est une collection d’objets du même type, chacun de ces objets est accédé par sa
position. La dimension du tableau doit être connue en temps de compilation.
Exemple:
const int numPorts = 200; double
portTable[numPorts]; int
bufferSize;
char buffer[bufferSize]; // ERROR: the value of bufferSize is unknown
Le tableau peut être initialisé lors de la définition :
int groupTable[3] = {134, 85, 29}; int userTable[] = {10, 74, 43, 45, 89}; // 5 positions
11
I.3.7 Conversion de Type
Ce type d’opération consiste à modifier la façon d’interpréter la séquence de bits contenue
dans une variable
a. Conversion implicite
Faite automatiquement par le compilateur (non sans warnings) lorsque plusieurs types de
données sont impliqués dans une opération.
▪ Lors de l’affectation d’une valeur à un objet, le type de la valeur est modifié au type de
l’objet
int count = 5.890; // conversion à int 5
▪ Chaque paramètre passé à une fonction est modifié au type de l’argument espéré par la
fonction
extern int add(int first, int second); count =
add(6.41, 10); // conversion à int 6
▪ Dans les expressions arithmétiques, le type de taille supérieure détermine le type de
conversion
int count = 3;
count + 5.8978; // conversion à double 3.0 + 5.8978
Exemple :
int count = 10;
count = count * 2.3; // Conversion de 23.0 à int 23
▪ Tout pointeur à une valeur non constante de n’importe quel type, peut être affecté à un
pointeur de type void*
char* name = 0;
void* aPointer = name; // conversion implicite
b. Conversion explicite ou casting
Cette conversion est demandée par le programmeur Exemple :
int result = 34;
result = result + static_cast<int>(10.890);
En combinant des noms de variables, des opérateurs, des parenthèses et des appels de
fonctions on obtient des expressions.
12
I.4.1 Opérateurs Arithmétiques et logiques
a. Opérateurs multiplicatifs
Opération Symbole Exemple
b. Opérateurs additifs
Opérateur Signification Arité Associativité
+ Addition unaire unaire Non
- négation unaire unaire Non
+ Addition binaire Gauche à droite
- Soustraction binaire Gauche à droite
Il est clair que « l’addition » unaire ne fait rien pour les types simples (nombres entiers
et réels), mais grâce à la surcharge des opérateurs, on peut donner un sens à cet opérateur
sur des types (=classes) complexes
c. Opérateurs de décalage
Opérateur Signification Arité Associativité
<< Décalage à droite Binaire Gauche à droite
>> Décalage à gauche binaire Gauche à droite
Ces opérateurs sont assez peu employés dans leur contexte « C » (décalage de bits),
mais ils prennent une importance considérable en C++ dans la manipulation des flux
d’entrées/sorties.
Exemple :
int b=100 ; // b=1100100 en code binaire std
::cout<< " (b<<1) = " <<(b<<1) << std ::endl ;
std ::cout<< " (b<<2) = " <<(b<<2) << std
::endl ; std ::cout<< " (b>>1) = " <<(b>>1) <<
std ::endl ; std ::cout<< " (b>>2) = " <<(b>>2)
<< std ::endl ;
13
d. Opérateurs relationnels
Opérateur Signification Arité Associativité
Inférieur à binaire Gauche à droite
<
> Supérieur à binaire Gauche à droite
<= Inférieur ou égal à binaire Gauche à droite
e. Opérateurs d’égalité
Opérateur Signification Arité Associativité
== Egalité binaire Gauche à droite
!= Inégalité binaire Gauche à droite
Ces opérateurs travaillent sur les bits, comme les décalages. Attention de ne pas les
confondre avec les opérateurs logiques.
g. Opérateurs logiques
Opérateur Signification Arité Associativité
&& Et logique binaire Gauche à droite
|| Ou logique binaire Gauche à droite
e1 ?e2 :e3 Condition if - else ternaire De droite à gauche
Ces opérateurs travaillent sur le type bool et retournent un bool.
Le dernier opérateur signifie: « si e1 est vraie alors faire e2, sinon faire e3 »
h. Opérateurs d’affectation
Opérateur Signification Arité Associativité
= Affectation R=a Droite à gauche
14
*= Multiplication et affectation a = a*b Droite à gauche
/= Division et affectation a = a/b Droite à gauche
%= Modulo et affectation a = a%b Droite à gauche
+= Addition et affectation a = a+b Droite à gauche
-= Soustraction et affectation a = a-b Droite à gauche
<<= Décalage à gauche et affectation a = a<<b Droite à gauche
>>= Décalage à droite et affectation a = a>>b Droite à gauche
&= Et binaire et affectation a = a&b Droite à gauche
|= Ou binaire a = a|b Droite à gauche
^= Ou exclusif binaire et affectation a = a^b Droite à gauche
++ Incrémentation a = a+1 ≈ a++ Droite à gauche
-- Décrémentation a = a-1 ≈ a-- Droite à gauche
Exemple:
int a, b=3, c, d=3 ; a = ++b ; // équivalent à b++ ; puis
a=b ; => a=b=4 c = d++ ; // équivalent à c=d ; puis
d++ ; => c=3 et d=4
où condition est une expression dont la valeur est booléenne ou entière. Toute valeur non nulle
est considérée comme vraie. Si le test est vrai, instruction est exécutée.
Il est possible de définir plusieurs conditions à remplir avec les opérateurs ET et OU (&&
et ||). Par exemple, l'instruction ci-dessous exécutera les instructions si l'une ou l'autre
des deux conditions est vraie :
Exemple:
if ((condition1) || (condition2))
{ instruction ; }
15
b. if … else : ce qu'il faut faire si la condition n'est pas vérifiée
L'instruction if dans sa forme basique ne permet de tester qu'une condition, or la plupart du
temps on aimerait pouvoir choisir les instructions à exécuter en cas de non réalisation de la
condition. L'expression if ... else permet d'exécuter une autre série d'instructions en cas de non-
réalisation de la condition.
16
switch (choix)
{
case valeur1 :
instruction1;
break;
case valeur2 :
instruction2;
break;
case valeur3 :
instruction3;
break;
default:
instructionParDéfaut;
}
La variable choix est évaluée en premier. Son type doit être entier. Selon le résultat de
l’évaluation, l’exécution du programme se poursuit au cas de même valeur. Si aucun des cas ne
correspond et si default est présent, l’exécution se poursuit après default. Si en revanche default
n’est pas présent, on sort du switch. Pour forcer la sortie du switch, on doit utiliser le mot-clé
break.
Exemple: [4a]
#include <iostream> using
namespace std;
int main()
{ int a;
cout << "Tapez la valeur de a : "; cin >> a; // Ce
programme demande à l'utilisateur de taper une switch(a) //
valeur entière et la stocke dans la variable a. On teste
{ case 1 : // ensuite la valeur de a : en fonction de cette valeur on
cout << "a vaut 1" << endl; // affiche respectivement les messages "a vaut 1","a vaut 2
break; // ou 4","a vaut 3, 7 ou 8", ou "valeur autre". case 2 :
case 4 :
cout << "a vaut 2 ou 4" <<
endl; break; case 3 :
case 7 : case 8 :
cout << "a vaut 3, 7 ou 8" <<
endl; break; default :
cout << "valeur autre" << endl;
}
return 0;
}
17
Affichage de l’exécution du code:
18
Exemple :
#include <iostream>
using namespace std;
int main()
{ int compteur(0);
for (compteur = 1 ; compteur < 10 ; compteur++)
cout << compteur <<" || " ;
cout << endl ; return 0 ;
}
Affichage de
l’exécution du code:
int main()
{ int i = 1;
while (i < 10) //affiche les valeurs de 1 jusqu’à
9
{ cout <<" "<< i <<endl ;
i++ ;
}
return 0 ;
}
Affichage de l’exécution du
code:
19
I.5.2.3 Boucle do … while
La structure de contrôle do … while permet, tout comme le while, de réaliser des boucles en
attente d’une condition. Cependant, contrairement à celui-ci, le do … while effectue le test sur
la condition après l’exécution des instructions. Cela signifie que les instructions sont toujours
exécutées au moins une fois, que le test soit vérifié ou non.
Syntaxe:
do
{
instructions ; } while (condition) ;
Exemple:
#include <iostream>
using namespace std;
int main()
{ int i = 1;
do
{ cout << i << endl;
i++;
} while(i<10); //affiche les valeurs de 1 jusqu’à 9
return 0;
}
int main()
{ int i;
for (i = 0 ; i < 10 ; i++)
{ cout <<" i= " <<i<< endl;
if (i==3)
break;
}
cout<<" Valeur de i lors de la sortie de la boucle : " << i <<endl ; return 0;
}
20
Affichage de l’exécution du
code:
I.5.3.2 continue
L'instruction continue sert à "continuer" une boucle (for, while et do … while) avec la
prochaine itération.
Exemple:
#include <iostream>
using namespace std;
int main()
{ int i;
for (i = 0 ; i < 5 ; i++)
{ if (i==3)
continue;
cout <<" i= " <<i<< endl;
}
cout<<" Valeur de iaprès la sortie de la boucle : " << i <<endl ; return 0;
}
21
Chapitre II : Fonctions et structures de programmes
Exemple:
Liste des
Type de la Nom de la
paramètres
valeur de retour fonction
Corps de la fonction
}
Il peut se trouver des situations où une fonction doit être appelée dans une autre fonction
définie avant elle. Comme cette fonction n’est pas définie au moment de l’appel, elle doit être
déclarée.
Le rôle des déclarations est donc de signaler l’existence des fonctions aux compilateurs afin
de les utiliser, tout en reportant leur définition de ces fonctions plus loin ou dans un autre fichier.
Syntaxe :
type nomDeLafonction (paramètres) ;
Exemple [4b]:
double Moyenne(double x, double y); // Fonction avec paramètres et type retour char
LireCaractere(); // Fonction avec type de retour et sans paramètres
void AfficherValeurs(int nombre, double valeur); //Fonction avec paramètres et sans type de
retour
22
Il est possible de créer plusieurs fonctions ayant le même nom. Il faut alors que la liste des
arguments des deux fonctions soit différente. C'est ce qu'on appelle la surcharge d'une fonction.
Exemple:
#include <iostream>
using namespace std;
int main()
{ double x, y, resultat; cout << "Tapez
la valeur de x : "; cin >> x; cout << "Tapez
la valeur de y : "; cin >> y;
resultat = moyenne(x, y); //appel de notre fonction somme
cout <<" Moyenne entre "<< x <<" et " << y << " est : " << resultat << endl;
return 0;
}
// définition de la fonction moyenne :
double moyenne(double a, double b)
{ double moy;
moy = (a + b)/2;
return moy;
}
Dans ce programme, on a créé une fonction nommée moyenne qui reçoit deux nombres réels
a et b en paramètre et qui, une fois qu'elle a terminé, renvoie un autre nombre moy réel qui
représente la moyenne de a et b. l’appel de cette fonction se fait au niveau du programme
principale (main).
Dans cet exemple, le prototype est nécessaire, car la fonction est définie après la fonction
main() qui l'utilise. Si le prototype est omis, le compilateur signale une erreur.
Affichage de l’exécution:
23
II.1.3 Fonctions sans retour
C++ permet de créer des fonctions qui ne renvoient aucun résultat. Mais, quand on la déclare,
il faut quand même indiquer un type. On utilise le type void. Cela veut tout dire : il n'y a vraiment
rien qui soit renvoyé par la fonction.
Exemple:
#include <iostream> using namespace std;
void presenterPgm () ;
int main ()
{
presenterPgm(); return 0;
}
void presenterPgm ()
{
cout << "ce pgm …";
}
Une fonction produit toujours au maximum un résultat, c’est-à-dire qu’Il n'est pas possible de
renvoyer plus qu'une seule valeur.
24
Exemple:
#include <iostream>
using namespace std;
25
return 0;
}
26
Affichage de l’exécution:
int main()
{ afficheLigne(‘+’) ;
cout<<endl ;
afficheLigne(‘*’,8) ;
return 0 ;
}
Affichage de l’exécution:
Il y a quelques règles que vous devez retenir pour les valeurs par défaut:
27
Pour mieux comprendre, Prenons le programme suivant qui ajoute 2 à l'argument fourni en
paramètre.
void sp (int );
int main()
{int n = 3;
cout << "n=" << n << endl;
sp (n);
cout << "n=" << n;
return 0 ;
}
void sp (int nbre)
{ cout << "----------"<<endl ;
cout << "nbre=" << nbre << endl;
nbre = nbre + 2; cout
<< "nbre=" << nbre;
cout << "----------"<<endl ;
}
Affichage de l’exécution:
28
Exemple :
#include <iostream>
using namespace std;
int main()
{int n = 3;
cout << "n=" << n << endl;
sp (n); //appel de la fonction
cout << "n=" << n<<endl ;;
return 0 ;
}
void sp (int & nbre) //Ajout de la référence dans la fonction
{ cout << "----------"<<endl ;
cout << "nbre=" << nbre << endl;
nbre = nbre + 2;
cout << "nbre=" << nbre<<endl ;
cout << "----------"<<endl ;
}
Affichage de l’exécution:
29
Exemple :
#include <iostream>
using namespace std;
int main()
{int n = 3;
cout << "n=" << n << endl; sp
(&n); //appel de la fonction
cout << "n=" << n<<endl ;
return 0 ;
}
void sp (int * nbre) //Ajout de la référence dans la fonction
{ cout << "----------"<<endl ;
cout << "nbre=" << *nbre << endl;
*nbre = *nbre + 2;
cout << "nbre=" <<* nbre<<endl ;
cout << "----------"<<endl ;
}
Affichage de l’exécution:
Les fonctions "inline" permettent de gagner au niveau de temps d'exécution, mais augmente
la taille des programmes en mémoire.
Contrairement aux macros dans C, les fonctions "inline" évitent les effets de bord (dans une
macro.
30
Les fonctions "inline" doivent être définies dans des fichiers d'entête (.h) qui seront inclus
dans des fichiers source (.cpp), pour que le compilateur puisse faire l'expansion.
Syntaxe :
Exemple:
31
Le nombre de valeurs entre accolades ne doit pas être supérieur au nombre d'éléments du
tableau.
Les valeurs entre accolades doivent être des constantes, l'utilisation de variables provoquera
une erreur du compilateur.
Si le nombre de valeurs entre accolades est inférieur au nombre d'éléments du tableau, les
derniers éléments sont initialisés à 0.
Si le nombre de valeur entre accolades est nul, alors tous les éléments du tableau s’initialisent
à zéro. Exemple :
int meilleurScore [5] = {};
III.1.1.2 Manipulation des éléments
Un élément du tableau (repéré par le nom du tableau et son indice) peut être manipulé
exactement comme une variable, on peut donc effectuer des opérations avec (ou sur) des
éléments de tableau.
Le point fort des tableaux, c'est qu'on peut les parcourir en utilisant une boucle. On peut ainsi
effectuer une action sur chacune des cases d'un tableau, l'une après l'autre : par exemple afficher
le contenu des cases.
int main()
{
int const taille(10); //taille du tableau int
tableau[taille]; //declaration du tableau
Affichage de l’exécution:
32
void afficheTableau(int tableau[], int n)
{
for(int i = 0; i < n; i++)
cout << tableau[i] << endl;
}
Pour remédier ces trois problèmes, Le C++ a introduit la notion des tableaux dynamiques ces
derniers sont des tableaux dont le nombre de cases peut varier au cours de l'exécution du
programme. Ils permettent d'ajuster la taille du tableau au besoin du programmeur.
33
Exemple:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int const TailleTab(5); //Déclaration
du tableau
vector<int> Scores(TailleTab);
//Remplissage du tableau
Scores[0] = 100432; Scores[1] = 87347; Scores [2] = 64523; Scores[3] = 31415; Scores[4] =
118218;
On peut modifier la taille d’un tableau soit en ajoutant des cases à la fin d'un tableau ou en
supprimant la dernière case d'un tableau.
a. Fonction push_back()
Il faut utiliser la fonction push_back(). On écrit le nom du tableau, suivi d'un point et du mot
push_back avec, entre parenthèses, la valeur qui va remplir la nouvelle case.
Exemple :
vector<int> tableau(3,2); //Un tableau de 3 entiers valant tous 2
tableau.push_back(8); //On ajoute une 4ème case au tableau qui contient la valeur 8
b. Fonction pop_back()
On peut supprimer la dernière case d'un tableau en utilisant la fonction pop_back() de la même
manière que push_back(), sauf qu'il n'y a rien à mettre entre les parenthèses.
Exemple :
vector<int> tableau(3,2); //Un tableau de 3 entiers valant tous 2 tableau.pop_back(8); // Il y a
plus que 2 éléments dans le tableau
34
IV.1.2.3 Autres opérations de la classe "vector"
Opération Description
Accès aux éléments, itérateurs
retourne une référence sur le premier élément
front() ex : vf.front() += 11; // +11 au premier élément
int tab[] = { 10, 20, 30 }; // v2 remplacé par les éléments du tableau tab
v2.assign(tab, tab + 3);
// affichage des tailles des vecteurs
cout << int(v1.size()) << endl; cout << int(v2.size()) << endl;
35
ex: //supprime les éléments compris entre les itérateurs premier et dernier
vector<float>::iterator prem = v.begin()+1; vector<float>::iterator dern
= v.end()-1; v.erase(prem,dern); affiche_vector(v);
erase(p)
efface tous les éléments d'un conteneur. Équivalent à c.erase(c.begin(), c.end()) ex:
v.clear(); // efface tout le conteneur
clear()
Taille et capacité
retourne le nombre d'éléments du « vector »
size() ex : vector<int> v(5);
cout<<v.size()<<endl; // 5
redimensionne un « vector » avec nb éléments. Si le conteneur existe avec une
taille plus petite, les éléments conservés restent inchangés et les éléments
supprimés sont perdus. Avec une taille plus grande, les éléments ajoutés sont
initialisés avec une valeur par défaut ou avec une valeur spécifiée en val ex:
vector<int> v(5);
for(unsigned i=0; i<v.size(); i++)
resize(nb) v[i]=i;
resize(nb, val) affiche_vector(v); // 0,1,2,3,4
v.resize(7); affiche_vector(v); //
0,1,2,3,4,0,0 v.resize(10,99);
affiche_vector(v); // 0,1,2,3,4,0,0,99,99,99
v.resize(3); affiche_vector(v); // 0,1,2
Le parcours d'un « vector » peut aussi s'effectuer en utilisant des itérateurs (pointeurs) plutôt
que le système d'indice. De ce fait, la classe « vector » est équipée avec toutes les méthodes les
concernant :
Méthode Description
begin() retourne un itérateur « iterator » qui pointe sur le premier élément
end() retourne un itérateur « iterator » qui pointe sur l'élément suivant le dernier
rbegin() retourne un itérateur « reverse_iterator » qui pointe sur le premier élément de la séquence
inverse (le dernier) ;
rend() retourne un itérateur « reverse_iterator » qui pointe sur l'élément suivant le dernier dans
l'ordre inverse (balise de fin, par exemple NULL)
36
cbegin() retourne un itérateur « const_iterator » qui pointe sur le premier élément. L'élément
pointé n'est alors accessible qu'en lecture et non en écriture. Il ne peut pas être modifié
cend() retourne un itérateur « const_iterator » qui pointe sur ce qui suit le dernier élément (balise
de fin, par exemple NULL). L'élément pointé n'est alors accessible qu'en lecture et non
en écriture. Il ne peut pas être modifié
crbegin() retourne un itérateur « const_reverse_iterator » qui pointe sur le premier élément de la
séquence inverse (le dernier). L'élément pointé n'est accessible qu'en lecture et ne peut
pas être modifié
crend() retourne un itérateur « const_reverse_iterator » qui pointe sur la fin de la séquence
inverse, avant le premier élément (balise de fin sens inverse, par exemple NULL).
L'élément pointé n'est accessible qu'en lecture et ne peut pas être modifié
Exemple :
vector<int>vi(15, 7); // 15 cases entières initialisées avec 7 vector<int>::iterator
it;
for (it=vi.begin(); it!=vi.end();
it++) cout<<*it<<"-";
cout<<endl;
Passer un tableau dynamique en argument à une fonction est beaucoup plus simple que pour
les tableaux statiques. Comme pour n'importe quel autre type, il suffit de mettre vector<type>
en argument. Et c'est tout. Grâce à la fonction size(), il n'y a même pas besoin d'ajouter un
deuxième argument pour la taille du tableau :
Exemple :
void afficheTableau(vector<int> tab)
{ for(int i = 0; i < tab.size(); i++)
cout << tab[i] << endl;
}
Si le tableau contient beaucoup d'éléments, le copier prendra du temps. Il vaut donc mieux
utiliser un passage par référence constante const& pour optimiser la copie. Ce qui donne:
void afficheTableau(vector<int> const& tab)
{
// ...
}
37
Dans ce cas, le tableau dynamique ne peut pas être modifié. Pour changer le contenu du tableau,
il faut utiliser un passage par référence tout simple (sans le mot const donc). Ce qui
void
Pour appeler une fonction recevant un vector en argument, il suffit de mettre le nom du tableau
dynamique comme paramètre entre les parenthèses lors de l'appel. Ce qui donne :
38
III.3 Les strings sont des tableaux de caractères (lettres)
En C++, une chaîne de caractères n’est rien d’autre qu’un tableau de caractères, avec un
caractère nul ’\0’ marquant la fin de la chaîne.
On peut par exemple représenter la chaîne « Bonjour » de la manière suivante :
Les chaînes de caractère peuvent être saisies au clavier et affichées à l’écran grâce aux objets
habituels cin et cout.
III.3.1 Déclaration
Pour définir une chaîne de caractères en langage C, il suffit de définir un tableau de caractères.
Le nombre maximum de caractères que comportera la chaîne sera égal au nombre d'éléments
du tableau moins un (réservé au caractère de fin de chaîne).
Exemple :
char nom[20], prenom[20]; // 19 caractères utiles char adresse[3][40]; // trois
lignes de 39 caractères utiles char texte[10] = {‘B’,’o’, ‘n’, ‘j’,’o’, ‘u’,’r’, ‘\0’}; //
ou : char texte[10] = "Bonjour"; On peut utiliser un vector si l'on souhaite
changer la longueur du texte :
vector<char> texte;
39
III.3.3 Instanciation et initialisation de chaînes
Pour initialiser notre objet au moment de la déclaration (et donc lui donner une valeur !), il y
a plusieurs possibilités:
int main()
{ string maChaine("Bonjour !"); //Création d'un objet 'maChaine' de type string et initialisation
String s3 = "chaine 3";
return 0;
}
main (void
s1, s2, s3;
40
On peut utiliser une autre syntaxe de concaténation: s1.append (s2) qui est équivalente à s1
= s1+s2
III.3.6 Quelques méthodes utiles du type « string »
Index: permet d'indiquer à partir de quel caractère on doit couper (ce doit être un numéro
de caractère).
Num : permet d'indiquer le nombre de caractères que l'on prend. Par défaut, la valeur
est npos, ce qui revient à prendre tous les caractères qui restent. Si vous indiquez 2, la
méthode ne renverra que 2 caractères.
Exemple:
Affichage de
l’exécution
41
int main()
{ string chaine("Bonjour !"); cout << chaine.substr(3, 4) << endl;
return 0; }
On a demandé à prendre 4 caractères en partant du caractère n°3, ce qui fait qu'on a récupéré
« jour ».
BONJOUR
AU REVOIR
Affichage de l’exécution
Dans cet exemple, c1 est un tableau de 8 char contenant la chaîne "BONJOUR " sans oublier
le caractère de fin de chaîne '\0'.
Le pointeur c2 est un pointeur vers un tableau non modifiable de char.
Les variables s1 et s2 sont des string. On peut affecter directement s1=c1 : le tableau de char
sera transformé en string.
Dans c2, on peut récupérer une chaîne « de type C » identique à notre string en écrivant
c2=s2.c_str(). On peut transformer aisément un string en tableau de char et inversement.
IV.3.6.5 Méthode «istr() »
42
Pour transformer une chaîne en double ou en int, il faut transformer la chaîne en flot de
sortie caractères : il s'agit d'un istringstream. Ensuite, nous pourrons lire ce flot de
caractères en utilisant les opérateurs usuels >>.
La méthode eof() sur un istringstream permet de savoir si la fin de la chaîne a été atteinte.
Chaque lecture sur ce flot grâce à l'opérateur >> renvoie un booléen qui nous indique
d'éventuelles erreurs.
<iostream>
<sstream>
main (void
Affichage de l’exécution
Tapez une chaîne : 12345
VOUS AVEZ TAPE L'ENTIER 12345
Dans cet exemple, est une chaîne : on saisit une chaîne au clavier en utilisant getline(cin,s).
On crée ensuite un istringstream appelé istr et construit à partir de s.
On peut lire un entier i à partir de istr en utilisant : istr>>i . (istr>>i) renvoie true si un entier
valide a pu être lu et renvoie false sinon. De la même manière, on pourrait lire des données
d'autres types, double par exemple.
43
III.4 Pointeurs et allocation dynamique [9]
Dans un programme, pour garder un lien vers une donnée (une variable), on utilise des
références ou des pointeurs. En programmation, les pointeurs et références servent
essentiellement à trois choses:
1. Permettre à plusieurs portions de code de
partager des objets (données, fonctions,.. )
sans les dupliquer => Référence
Les « pointeurs intelligents » (smart pointers): gérés par le programmeur, mais avec des
gardes-fous. Il en existe 3: unique_ptr, shared_ptr, weak_ptr (avec #include <memory>)
Les « pointeurs « hérités de C » » (build-in pointers): les plus puissants (peuvent tout
faire) mais les plus « dangereux »
III.4.1.1 Référence
Une référence est un autre nom pour un objet existant, un synonyme, un alias.
La syntaxe de déclaration d’une référence est: <type>& nom_reference(identificateur) ; Après
une telle déclaration, nom_reference peut être utilisé partout où identificateur peut l’être [9].
int val(1) ;
int& x(val) ;
44
C’est exactement ce que l’on utilise lors d’un passage par référence:
rri(ri); // Non
45
On ne peut donc pas faire de tableau de références
Notes :
Une référence n’est pas un «vrai pointeur» car ce n’est pas une variable en tant que telle.
Une référence est «une autre étiquette».
Un pointeur «une variable contenant une adresse»
nullptr : mot clé C++, spécifie que le pointeur ne pointe sur rien
Pour le compilateur, une variable est un emplacement dans la mémoire, Cet
emplacement est identifié par une adresse
Chaque variable possède une et une seule adresse.
Un pointeur permet d’accéder à une variable par son adresse.
Les pointeurs sont des variables faites pour contenir des adresses.
& est l’opérateur qui retourne l’adresse mémoire de la valeur d’une variable. Si x est de
type type, &x est de type type* (pointeur sur type).
46
* est l’opérateur qui retourne la valeur pointée par une variable pointeur. Si px est de
type type*, (*px) est la valeur de type type pointée par px.
int*
ptr(NULL); int
i = 2; p = &i ;
cout<< " i = "<< i <<" id = "<< id <<endl ;
cout<< " Le pointeur p pointe sur la valeur "<< *p <<endl
;
Par convention, le nom d’une variable utilisé dans une partie droite d’expression donne le
contenu de cette variable dans le cas d’une variable simple. Mais un nom de tableau donne
l’adresse du tableau qui est l’adresse du premier élément du tableau.
Nous faisons les constatations suivantes :
un tableau est une constante d’adressage ;
un pointeur est une variable d’adressage.
47
Ceci nous amène à regarder l’utilisation de pointeurs pour manipuler des tableaux, en prenant
les variables: long i, tab[10], *pti ;
tab est l’adresse du tableau (adresse du premier élément du tableau &tab[0] ;
pti = tab ; initialise le pointeur pti avec l’adresse du début de tableau. Le & ne sert à rien
dans le cas d’un tableau. pti = &tab est inutile et d’ailleurs non reconnu ou ignoré par
certains compilateurs ;
&tab[1] est l’adresse du 2e élément du tableau.
pti = &tab[1] est équivalent à: pti = tab ; où pti pointe sur le 1er élément du tableau.
pti += 1 ; fait avancer, le pointeur d’une case ce qui fait qu’il contient l’adresse du 2ème
élément du tableau.
Le premier cas d’utilisation d’un tel tableau est celui où les éléments du tableau de pointeurs
contiennent les adresses des éléments du tableau de variables. La figure suivante illustre un
exemple d’utilisation de tableau de pointeurs à partir des définitions de variables suivantes :
48
Tab[0] /*pt[0]
pt pt[0]/*pt
*tab / **pt
Tab[1]/*pt[1]
pt[1]/*(pt+1)
*(tab+1)/**(pt+1)
Tab[2]/*pt[2]
pt[2]/*(pt+2) *(tab+2)/**(pt+2)
Tab[3]/*pt[1]
pt[3]/*(pt+3)
*(tab+3)/**(pt+3)
Tab[4]/*pt[4]
pt[4]/*(pt+4)
*(tab+4)/**(pt+4)
Tab[5]/*pt[5]
pt[5]/*(pt+5)
*(tab+5)/**(pt+5)
C++ possède deux opérateurs new et delete permettant d’allouer et de libérer dynamiquement
de la mémoire.
49
int* p; p =
new int ; *p
=2;
cout<< " p = "<< p <<" *p = "<< *p
<<endl ;
int* p; p = new
int (2);
cout<< " p = "<< p <<" *p = "<< *p
<<endl ;
C’est-à-dire que cette zone mémoire peut maintenant être utilisée pour autre chose. On ne peut
plus y accéder !
Bonnes pratiques
Faire suivre tous les delete de l’instruction « pointeur = NULL; ».
Toute zone mémoire allouée par un new doit impérativement être libérée par un delete
correspondant !
Exemple :
int*
px(NULL); px
= new int ; *px
= 20 ;
cout<< "*px = "<<*px
;
delete px ; px
= NULL ;
Notes :
Une variable allouée statiquement est désallouée automatiquement (à la fermeture du
bloc).
Une variable (zone mémoire) allouée dynamiquement doit être désallouée
explicitement par le programmeur.
int* px(NULL);
{px = new int(4) ; int n=6 ;}
cout<< "*px = "<<*px ;
delete px ; px = NULL ;
cout<< "*px = "<<*px
<<endl; cout<< "n = "<< n ;
50
Attention
Si on essaye d’utiliser la valeur pointée par un pointeur pour lequel aucune mémoire n’a été
réservée, une erreur de type
int* px;
*px = 20 ; // ! Erreur : px n’a pas été alloué
cout<< "*px = "<<*px<<endl;
Segmentation fault se produira à
l’exécution.
Compilation : Ok
Exécution: arrêt programme
(Segmentation fault)
Bonnes pratiques
int* px(NULL); if(px != NULL)
{ *px = 20 ;
cout<< "*px = "<<*px<<endl;
}
51
Syntaxe :
for (int i = 0; i < ligm; i++)
delete[lignes] data[i]; // Suppression des colonnes delete[] data; //
Suppression des lignes
Permet de libérer l'emplacement mémoire désigné par data alloué préalablement par new[]
Exemple 1:
#include <iostream>
#include <ctime>
Using namespace std;
cout<<"Colonnes : n = " ;
cin>>n; cout<< " Lignes : m = " ;
cin>>m;
52
data = new double*[m];
for (int j = 0; j < m; j++)
data[j] = new double[n];
display(data);
de_allocate(data); return
0;
}
Exemple 2:
#include <iostream>
Using namespace std;
int main(void)
{ Etudiant *ptrEtudiant ;
initialiserEtudiant(ptrEtudiant) ;
afficherEtudiant(ptrEtudiant) ;
delete (ptrEtudiant) ;
return 0;
}
void initialiserEtudiant(Etudiant *e)
{ cout<< " Saisir le nom : " ; cin>>(*e).nom ;
cout<< " Saisir la moyenne : " ; cin>>(*e).moyenne ;
cout<< " Saisir le sexe : " ; cin>>(*e).sexe ;
}
void afficherEtudiant(Etudiant *e)
{ if((*e).sexe== 'F') cout<< " Madame " ; else cout<< " Monsieur "
; cout<< (*e).nom <<" Votre moyenne est de "<<
(*e).moyenne<<endl ;
53
}
54