Programmation CPP
Programmation CPP
Programmation CPP
automate
programme
tâche
Bien qu’il s’agisse d’une classification différente de celle des machines, les
générations de langages sont, du moins pour les premières, liées sur le plan
chronologique aux générations de machines, et aux performances de leurs
composants
1. Notamment en ce qui concerne l’amorçage du système, et la connexion/prise en charge du système de lecture de cartes.
L 01001100 Assembleur
Codage
O 01001111
A 01000001 0101 ⇔ LOAD
«LOAD 6 5» D 01000100 0110 ⇔ 6
_ 10100000
6 00110110 0101 ⇔ 5
_ 10100000
5 00110101
Format symbolique Format binaire
Programme Programme
(langage assembleur) (langage machine)
commentaires
segment
de code 00 010101100101
LOAD 6 5 ; ... 01 000101100000
CMP 6 0 ; ... 02 010000110000
JUMP +3 03 001101100000
DECR 6 04 010011010000
JUMP -3 05 111100000000
END 06 000000000000
Assembleur
données
mnémoniques arguments
instructions opérandes segment
opérateurs
adresses de données
☞Un langage d’assemblage tel que celui décrit précédemment est appelé langage
d’assemblage pur, c’est à dire qu’il y a biunivocité entre les instructions machine et les
mnémoniques (le code source en langage d’assemblage comportent le même nombre
d’élements (d’instructions) que le code machine résultant).
Les tout premiers assembleurs étaient purs, mais cette «propriété» disparut rapidement,
avec l’apparition des langages offrant des macros-commandes (pour définir une seule fois
des portions de codes fréquentes) ainsi que des pseudo-instructions (réservation &
initialisation de mémoire, chargement de modules séparés,...).
La traduction effectuée par les assembleurs correspondants a permis de rendre
le code portable (i.e. réutilisable, moyennant son assemblage, sur une autre machine),
en masquant la couche matérielle (abstraction de la machine).
étiquettes
Parallèlement aux langages évolués, des langages encore plus spécialisés, voir
des méta-langages, ont fait leur apparition.
On peut par exemple citer les langages d’intelligence artificielle (Lisp, Prolog),
les langages objets (Smalltalk, Eiffel), certains langages de gestion (L4G), ...
☞ A l’inverse, les langages compilés sont à utiliser de préférence pour les réalisations
opérationnelles, ou les programmes de grande envergure:
• Les programmes obtenus sont plus efficaces:
→ d’une part, le compilateur peut effectuer des optimisations
plus facilement que l’interpréteur, puisqu’il possède une
visibilité globale sur le programme,
→ et d’autre part, l’effort de traduction n’est fait
qu’une seule fois, qui plus est en prétraitement.
✏
permet la diffusion du programme sous sa forme opérationnelle,
sans imposer pour autant sa diffusion sous forme conceptuelle
(lisible et compréhensible par un humain)
Développé dans les laboratoires d’AT&T Bell au début des années 1980 par
Bjarne Stroustrup, le langage C++ est un langage:
➱ à typage fort,
➱ compilé (impératif),
➱ et orienté objet (POO 1 ).
Schématiquement:
C++ = C + typage fort + objets (classes)
Avantages Défauts
• Effets indésirables et comportement peu intuitif,
• Programmes exempts de bogues «syntaxiques», et
conséquence de la production automatique de code
[un peu] plus robuste, grâce au typage fort
• Syntaxe parfois lourde et peu naturelle
• Applications efficaces grâce à la compilation
• Le langage ne définit pas de techniques de
• Compilateur disponible sur pratiquement toutes les
récupération automatique de mémoire (garbage
plate-formes et documentation abondante, grâce à sa
collector), de multiprogrammation ou de
large diffusion.
programmation distribuée.
1. Acronyme de Programmation Orientée Objet. Remarquons toutefois que C++ n’est pas purement objet (comme le sont par exemple Eiffel ou
Smalltak) mais est un langage hybride: on peut très bien programmer en C++ sans pour autant programmer par objets, c’est d’ailleurs ce qui sera
fait pendant tout le premier semestre de ce cours.
☞ Pour pouvoir être compilé en une séquence binaire exécutable, le code source
doit fournir au compilateur un «point d’entrée».
Par convention, ce point d’entrée est en C++
une fonction intitulée main:
Compilateur C++
void main ()
{
}
données
traitements
x 2 + bx + c = 0
– b ± ∆
------------------------ si ∆ > 0
2
–b
------ si ∆ = 0
2
∅ sinon
avec ∆ = b2 - 4c
#include <cmath>
#include <iostream> données
traitements
void main() structures de contrôle
{
float b(0.0);
float c(0.0);
float delta(0.0);
cin >> b >> c;
delta = b*b - 4*c;
if (delta < 0.0){
cout << "Pas de solutions réelles !" << endl;
}
else if (delta == 0.0) {
cout << "Une solution unique: " << -b/2.0 << endl;
}
else {
cout << "Deux solutions: [" << (-b-sqrt(delta))/2.0
<< ", " << (-b+sqrt(delta))/2.0 << ’]’ << endl;
}
}
#include <cmath>
#include <iostream>
void main()
{
float b(0.0);
float c(0.0);
float delta(0.0);
cin >> b >> c;
delta = b*b - 4*c;
if (delta < 0.0){
cout << "Pas de solutions réelles !" << endl;
}
else if (delta == 0.0) {
cout << "Une solution unique: " << -b/2.0 << endl;
}
else {
cout << "Deux solutions: [" << (-b-sqrt(delta))/2.0
<< ", " << (-b+sqrt(delta))/2.0 << ’]’ << endl;
}
}
Pour pouvoir être utilisée dans un programme C++, une donnée doit être associée à une
variable, c’est-à-dire un élément informatique qui sera manipulé par le programme. Une
variable est décrite à l’aide de 3 caractéristiques:
➱ Sa valeur littérale:
qui permet de définir sa valeur. Par exemple, si la donnée est un nombre, sa
valeur littérale pourra être (selon les conventions de représentation):
123, -18, 3.1415, 2e-13, , 0x3A5, ...
➱ Son identificateur:
qui est le nom par lequel la donnée est désignée dans le programme.
➱ Son type:
qui correspond à une classification de la données, par exemple en fonction des
opérations qui peuvent lui être appliquées; cette caractéristique sera développée
plus en détails dans la suite du cours, et généralisée en la notion de classe.
Dans les langages fortement typés comme C++, la création d’une variable se fait à
l’aide d’une déclaration, et l’association effective d’une valeur à la variable créée
se fait à l’aide d’une initialisation.
☞ Les déclarations doivent obligatoirement être faites
avant toute utilisation de la variable;
Exemple: int i;
float exemple_de_variable
2. Le compilateur n’interdit pas l’utilisation d’une variable non initialisée, mais il exclut l’utilisation de variables non déclarées.
Exemple: x
b3
TailleMax
nb_etudiants
3. Remarquons toutefois qu’il existe certaines séquences «interdites», correspondant à des mots réservés du langage.
Remarque:
la valeur littérale 0 est une valeur d’initialisation qui peut être affectée à une
variable de n’importe quel type élémentaire.
En résumé, en C++ une donnée est donc un élément informatique caractérisé par:
➱ son type
➱ son identificateur } définis lors de la déclaration
➱ sa valeur définie lors de l’initialisation
#include <cmath>
#include <iostream>
void main()
{
float b(0.0);
float c(0.0);
float delta(0.0);
cin >> b >> c;
delta = b*b - 4*c;
if (delta < 0.0){
cout << "Pas de solutions réelles !" << endl;
}
else if (delta == 0.0) {
cout << "Une solution unique: " << -b/2.0 << endl;
}
else {
cout << "Deux solutions: [" << (-b-sqrt(delta))/2.0
<< ", " << (-b+sqrt(delta))/2.0 << ’]’ << endl;
}
}
Exemple:
i = 3;
Exemple
int a(1);
cout << ’[’ << -a << ", "
[-1, 1]
<< a << ’]’ << end;
int i(2);
float x(3.14);
l’instruction:
cout << "=> " << 2*i+5 << ", " << x << endl;
permet à l’utilisateur de saisir au clavier 7 une liste de valeurs val1, val2, ..., valn
qui seront stockées dans les variables <vari>.
Exemple
Avec l’initialisation:
int i;
double x;
l’instruction:
cin >> i >> x;
Exemple de programme:
#include <iostream>
void main()
{
int i;
double x;
cout << "Valeurs pour i et x: " << flush;
cin >> i >> x;
cout << "=> " << i << ", " << x << endl;
}
#include <cmath>
#include <iostream>
void main()
{
float b(0.0);
float c(0.0);
float delta(0.0);
cin >> b >> c;
delta = b*b - 4*c;
if (delta < 0.0){
cout << "Pas de solutions réelles !" << endl;
}
else if (delta == 0.0) {
cout << "Une solution unique: " << -b/2.0 << endl;
}
else {
cout << "Deux solutions: [" << (-b-sqrt(delta))/2.0
<< ", " << (-b+sqrt(delta))/2.0 << ’]’ << endl;
}
}
8. En fait, et on le verra par la suite, il est également possible de définir les opérateurs arithmétiques pour des types non-élémentaires, comme par
exemple les nombres complexes.
* multiplication
/ division
% modulo Le modulo est le reste
de la division entière.
+ addition
- soustraction Ces opérateurs sont tous
associatifs à gauche
}
6
3
évaluations
3
8
• Z = (x+3) % y;
• Z = (3*x+y)/10;
• Z = (x = 3*y)+2;
Remarque:
9. Ou x++, la différence entre les deux notations sera expliquée dans les mini-références.
Exemple: 5/2 = 2
A
10 Au sens strict, une expression logique (ou condition) est une expression booléenne
(i.e. de type booléen), c’est-à-dire une expression qui peut avoit comme résultat :
soit la valeur «vraie» (true) – dans ce cas on dit que la condition est vérifiée –
soit la valeur «fausse» (false) – et dans ce cas, la condition n’est pas vérifiée.
E n fa i t , l a d é fi n i t i o n d u l a n ga g e C + + p e r m e t d e c o n s i d é r e r c o m m e
ex p r e s s i o n l o g i q u e u n e ex p r e s s i o n d e n ’ i m p o r t e q u e l t y p e , ave c l a
convention suivante:
si l’évaluation de l’expression est une valeur nulle, elle sera considérée
comme une condition fausse, sinon elle sera considérée comme une
condition vraie.
Opérateur Opération
< strictement inférieur
<= inférieur ou égal
> strictement supérieur
>= supérieur ou égal
== égalité
!= différence (non-égalité)
(x >= y)
(x+y == 4)
((x > y) == (y > z))
X Y non X x ET y x OU y
v v f v v
v f f f v
f v v f v
f f v f f
Plus précisément:
(x <= 0 || log(x) == 4)
Dans ce cas également, une évaluation complète conduirait à une
erreur lors du calcul de log(x) pour tout x inférieur ou égal à 0.
11. Notez que dans ce cas particulier, on pourrait contourner l’obstacle en testant l’expression équivalente:
((x>0) && (4 > 3*x)) || ((x<0) && (4 < 3*x))
#include <cmath>
#include <iostream>
void main()
{
float b(0.0);
float c(0.0);
float delta(0.0);
cin >> b >> c;
delta = b*b - 4*c;
if (delta < 0.0){
cout << "Pas de solutions réelles !" << endl;
}
else if (delta == 0.0) {
cout << "Une solution unique: " << -b/2.0 << endl;
}
else {
cout << "Deux solutions: [" << (-b-sqrt(delta))/2.0
<< ", " << (-b+sqrt(delta))/2.0 << ’]’ << endl;
}
}
• Le branchement conditionnel: if
• La boucle: while
• L’itération: for
Pour pouvoir être utilisée, les instructions d’un programmes C++ doivent être
regroupées en entités 1 appelées séquences d’instructions ou blocs.
Ces séquences sont explicitement délimitée par les accolades «{» et «}»:
{
int tmp(a); // échange du contenu
a = b; // de deux variables
b = tmp;
}
Exemple
if (x != 0) {cout << 1/x << endl;}
else {cout << "Erreur! (Infinity)" << endl;}
if (<condition 1>)
{ • L’expression <condition 1> est tout d’abord
<instructions 1: si condition 1 vérifiée> évaluée; si elle est vérifiée, le bloc
} <instructions 1:...> est exécuté, et les
else if (<condition 2>) alternatives ne sont pas examinée.
{
<instructions 2: si condition 2 vérifiée> • Sinon (1 ère condition non vérifiée), c’est
} l’expression <condition 2> qui est évaluée, et
... ainsi de suite.
else if (<condition N>)
{
<instructions N: si condition N vérifiée>
• Si aucune des N conditions n’est vérifiée,
et que la partie optionnelle else est
}
présente, c’est le bloc <instructions par défaut>
[else associé qui sera finalement exécuté.
{
<instructions par défaut>
}]
L’expression <expression> est évaluée, puis comparée successivement à chacune des valeurs
3
<Constantei> introduites par le mot réservé case. En cas d’égalité, le bloc (ou l’instruction)
associée est exécutée. Dans le cas où il n’y a aucune <Constantei> égale à la valeur de
l’expression, l’éventuel bloc introduit par le mot réservé default est exécuté.
3. Pour spécifier ces valeurs, on ne peut uniquement utiliser des expressions constituées de valeurs littérales ou de constantes
(l’expression doit être évaluable lors de la compilation)
Exemple de sélection
switch (a+b)
{
case 0: a = b; // exécuté uniquement lorsque
break; // (a+b) vaut 0
case 2:
case 3: b = a; // lorsque (a+b) vaut 2 ou 3
case 4:
case 5: a = 0; // lorsque (a+b) vaut 2,3,4 ou 5
break;
default: a = b = 0; // dans tous les autres cas.
}
Lorsque l’exécution des instructions d’un case est terminée, le contrôle ne passe pas à la
fin du switch, mais continue l’exécution des instructions suivantes (même si leur valeur
associée n’est pas égale à l’expression de sélection). Pour provoquer la terminaison de
l’instruction switch, il est nécessaire de placer explicitement une instruction break.
Exemple
do
{
cout << "valeur de i (>=0): ";
cin >> i;
cout << "=> " << i << endl;
}
while (i<0);
Exemple
while (x>0)
{
cout << x;
x /= 2;
}
Lorsque plusieurs instructions d’initialisation ou de mise à jour sont nécessaires, elles sont
séparées par des virgules (et sont évaluées de la gauche vers la droite)
0 0
for (int i(0),s(0); i<6; s+=i,++i) 1 0
{ 2 1
cout << i << s << endl; 3 3
} 4 6
5 10
L’instruction break:
Elle ne peut apparaître qu’au sein d’une boucle ou d’une clause de sélection.
Elle permet d’interrompre le déroulement de la boucle (quelque soit l’état de la
condition de continuation) ou de l’instruction de sélection, en provoquant un
saut vers l’instruction suivant la structure de contrôle.
L’instruction continue:
Elle ne peut apparaître qu’au sein d’une boucle.
Elle interrompt l’exécution des instructions du bloc, et provoque la
ré-évaluation de la condition de continuation, afin de déterminer si l’exécution
de la boucle doit être poursuivie (avec une nouvelle itération).
while (condition)
{
...
/* actions de la boucle */
...
break
...
continue
...
}
/* actions suivant la
structure de contrôle
*/
4. La visibilité effective d’une variable sera conditionnée par des règles de masquage qui seront vues plus loin dans le cours.
portée
// déclarations globales globale // début du programme
int z(0); int z(0);
... ...
void main() void main()
{ {
// déclaration locales portée locale // bloc niveau 1
int x,y; fonction main int x,y;
... ...
{ {
// déclaration locales // niveau 2
int y; portée locale
int y;
... x ... bloc interne ... x ...
... y ... ... y ...
... z ... ... z ...
} }
... y ... ... y ...
... z ... ... z ...
} }
{
int i(0);
Et de même
dans l’itération for (int i(0); i<10; ++i) while (i<10)
{ {
int j(0); ≡ int j(0);
cout << i << j << endl; cout << i << j << endl;
} ++i;
}
}
Exemple: le programme
const int MAX(5);
void main()
{
int i(120);
for (int i(1); i<MAX; ++i)
{
cout << i << endl;
}
cout << i << endl;
}
donne en sortie:
1
2
3
4
120
A ce point du cours,
un programme n’est rien de plus qu’une
séquence bien formée d’instructions simples ou
composées (i.e. comprenant des structures de contrôle).
2. Cette augmentation de taille est injustifiée car il existe une alternative à la duplication manuelle du code.
arguments
formels
x1
{
val = f(0.1,3,"ab"); x2
x3 Portion réutilisable
de programme
• un corps, qui correspond à un bloc (la portion de code réutilisable qui justifie
la création de la fonction) et qui induit également une portée –locale– pour les
éléments (variables, constantes) définis au sein de la fonction.
Comme dans le cas des variables, les fonctions ont une portée (et une visibilité).
Ceci implique que toute fonction doit être prototypée avant d’être utilisée.
Les prototypes des fonctions seront ainsi toujours placés
avant le corps de la fonction main.
Exemple:
float puissance(const float base, float exposant);
void main()
{
...
x = puissance(8.0,3.);
...
}
Exemple:
#include <iostream>
// Prototypages ..........................
// Définitions ...............................
void main()
{
cout << moyenne(8.0,3.0) << endl;
}
Cependant, si pour les autres types de données il est recommandé de toujours initialiser
lors de la déclaration, il est préférable, pour les fonctions, de réaliser le prototypage et la
définition en 2 opérations distinctes. 4
4. L’élément prédominant est de rendre le code le plus lisible possible. A cet fin, il est souvent préférable de regrouper les différentes déclarations en
un endroit unique (typiquement en début de programme), et disposer ainsi d’une sorte d’index dans lequel il est aisé de retrouver le prototype d’une
fonction donnée.
Pour une fonction f définie par: int f(t1 x1, t2 x2, ..., tn xn) { ... }
l’évaluation de l’appel fonctionnel f(arg1, arg2, ..., argn)
s’effectue de la façon suivante:
(1) les arguments arg1, arg2,..., argn sont évalués (de la gauche vers la droite),
et les valeurs produites sont liées avec les arguments formels x1, x2,..., xn de la fonction f
(cela revient concrètement à réaliser les affectations: x1=val1, x2=val2, ... , xn=valn),
où val1, val2,..., valn sont le résultat de l’évaluation des arguments ( vali ⇐ ( argi));
(3) l’expression (entière dans notre cas) définie par le mot clef return est évaluée,
et est retournée comme résultat de l’évaluation de l’appel fonctionnel.
void main();
int f(const int x);
Exemple:
Exemple:
int saisieEntiers();
void main()
{
int val = saisieEntiers();
cout << val << endl;
}
int saisieEntiers()
{
int i;
cout << "Entrez un entier: ";
cin >> i;
return i;
}
Les notions de portée locale v.s. globale définies pour les blocs
sont également valides dans le cadre des fonctions 5 .
Comme dans le cas des blocs,
la portée permet de résoudre les problèmes de conflits
de référençage entre entités externes (i.e. définies à l’extérieur du corps
de la fonction) et entités internes (i.e. définies dans le corps-même de la fonction).
Exemple:
int i(2);
void f(int a);
void main {
f(i);
}
void f(int a) {
int i(10);
cout << a*i << endl; Quelle variable est référencée par le i
} de l’instruction cout << a*i << endl ?
5. Cela est bien naturel, puisque le corps d’une fonction n’est autre qu’un bloc.
☞ En cas de conflit de référence entre une entité interne et une entité externe,
l’entité interne est systématiquement choisie; on dit que les références
internes sont de portée locale, i.e. limitées aux entités internes de la fonction.
On dit également que les entités internes masquent les entités externes.
6. Une fonction modifiant des variables externes sera dite «à effet de bord», ce qui est fortement déconseillé.
void main();
int f(const int x);
int z;
int f(const int x)
{
void main()
int y;
{
...
int x,y;
...
...
... ... x ...
z=f(y); ... y ...
... ... z ...
} }
Exemple
void f(int x)
{
x = x + 2;
cout << "’x’ vaut: " << x << endl;
}
void main()
{
int val(0);
f(val);
cout << "’val’ vaut: " << val << endl; ’x’ vaut: 2
} ’val’ vaut: 0
Le passage par référence doit être explicitement indiqué. Pour ce faire, on ajoute le symbole
«&» au type des arguments formels concernés (par exemple: int&, bool&, ...).
7. Ce type de «variable» est appelé référence, d’où le nom de passage par référence.
Exemple
void f(int& x) {
x = x + 2;
cout << "’x’ vaut: " << x << endl;
}
void main() {
int val(0);
f(val);
cout << "’val’ vaut: " << val << endl; ’x’ vaut: 2
} ’val’ vaut: 2
Remarquons que dans le cas d’arguments passés par référence, les valeurs littérales ne
peuvent être utilisés comme arguments lors de l’appel à la fonction que pour des arguments
formels déclarés constants.
Exemple:
char c(’a’);
const int i(2);
...
char& c_alias(c); // un simple synonyme de «c»
const int& i_alias(i); // un synonyme (obligatoirement) constant de la constante «i»
const char& c_alias2(c_alias); // un synonyme constant de la variable «c»
Une référence ne peut pas être ré-assignée. Une fois déclarée, elle restera donc toujours
associée au même élément.
8. Il est possible de déclarer des références constantes à des variables, mais il n’est pas permis de déclarer des références simples à des constantes.
Lors de sa définition,
une fonction peut être munie d’arguments avec valeur par défaut,
pour lesquels il n’est alors pas obligatoire de fournir de valeur
lors de l’appel de la fonction.
Exemple
void afficheLigne(const char c, const int n = 5)
{
for (int i(0); i<n; cout << c, ++i);
cout << endl;
}
void main()
{
afficheLigne(’*’); *****
afficheLigne(’+’,8); ++++++++
}
Exemple:
void f(int x, int y=2, int z=3); // prototype
void f(int x, int y, int z) { ... } // définition
...
f(1) <=> f(1,2,3)
f(0,1) <=> f(0,1,3)
9. La signature d’une fonction est donc similaire à la notion d’attribut identifiant dans le domaine des bases de données.
Exemple:
void main() {
affiche(1.0);
affiche(1);
affiche(5,2);
} Les arguments avec valeur
par défaut ne font pas
partie de la signature.
tri 1 3 5 2 6 4
tri insertion
insertion: insertion (au bon endroit) du
ne élément dans le tableau trié
de n-1 éléments
1 2 3 4 5 6
Attention
AlgoRécursif
entrées: entree
...
Appliqué à l’exemple de la
somme des n premiers entiers positifs,
on obtient l’algorithme correct suivant:
6 3
tri_recursif
entrée: tableau de n éléments
sortie: tableau triée
insertion
du dernier élément à la bonne
place dans le sous-tableau trié
10. Sur le plan syntaxique, une telle fonction n’est pas différente des autres. La seule différence réside dans le fait que la définition de la fonction induit
un cycle, par un appel à elle-même. Remarquons que l’on appelle également récursives des fonctions couplées (A appelle B qui appelle A).
// prototypage
int somme(const int n);
// définition
int somme(const int n)
{
if (n >= 1) // condition d’arrêt
{
return n;
}
else
{
return (n + somme(n-1)); // appel récursif
}
}
{1,2,3}
triRecursif({3,2,1},0,2) deplace(2,0,{2,3,1})
{2,3,1}
triRecursif({3,2,1},0,1) deplace(1,0,{3,2,1})
triRecursif({3,2,1},0,0)
Exemples:
age = 18; poids = 62.5; taille = 1.78; ...
hauteur
Exemple:
age
age[0] 20
Ainsi, le tableau age vu précédemment
peut être déclaré par: age[1] 35
age[2] 26
int age[5]; age[3] 38
age[4] 22
Exemple:
int age[5] = {20,35,26,38,22};
1. Il est également possible de n’initialiser que les premiers élements du tableau, comme dans: «int age[5] = {20,35}».
Exemple:
age[0] = 3;
age[4] = age[1+2]; // soit age[4] = age[3]
Pour pallier ce défaut, on pourra utiliser, comme nous le verrons plus loin
dans le cours, un type issu de l’approche objet de C++, le type string
4. Cette instruction est effectivement équivalente à celle vue précedemment (incluant le caractère ’\0’)
Exemple:
matrice[2][1] cube[0][1][2]
cube
matrice (transposée)
5. En fait, les tableaux informatiques correspondent aux vecteurs mathématiques, et les tableaux de tableaux à des matrices.
Exemple:
matrice[][j]
1 2
3 4 5 il faut spécifier autant d’indices
qu’il y a de dimensions dans le tableau.
6 70 8
9 10 11
Exemple:
#include <vector>
...
vector<int> age;
Il s’agit d’une déclaration de variable (« age») tout à fait traditionnelle, dans laquelle
la séquence « vector<int>» correspond à l’indication du type de la variable,
en l’occurence un tableau dynamique (vecteur) d’entiers.
On voit dans ce cas clairement ressortir la nature composite du type.
Le fait que l’on s’intéresse ici à des collections d’un nombre potentiellement
variable d’éléments explique que la déclaration puisse ne comporter aucune
indication sur la taille initiale du tableau. Une variable ainsi déclarée
correspond alors tout simplement à un tableau vide.
Cependant, une taille initiale peut, si nécessaire, être indiquée;
la syntaxe de la déclaration est alors:
vector<«type»> «identificateur»(«taille»);
Un tableau nommé identificateur comportant taille éléments de type type
sera créé, chacun des éléments ayant comme valeur la valeur par défaut de type
généralement une valeur dérivée de l’expression (0).
Exemple: age
age[0] 0
vector<int> age(5); age[1] 0
age[2] 0
Correspond à la déclaration d’un tableau d’entiers, initialement
composé de 5 éléments valant 0. age[3] 0
age[4] 0
La déclaration d’un vecteur peut être associée à une initialisation explicite des éléments
initiaux; cependant, cette initialisation ne pourra consister qu’en (a) une duplication
d’un même élément, ou (b) en une duplication d’un vecteur pré-existant: 10
(a) vector<«type»> «identificateur»(«taille»,«valeur»);
où valeur est une expression de type type, dont le résultat sera pris comme
valeur intiale des taille éléments du tableau identificateur.
Exemple:
vector<int> vect1(5,8);
déclare le vecteur d’entiers vect1 avec un contenu initial
de 5 entiers valant « 8»
10. Contrairement au cas des tableaux de taille fixe, il n’existe pas de moyen simple pour exprimer la valeur littérale d’un vecteur dont les éléments
n’ont pas tous la même valeur.
Exemple:
const vector<int> age;
Correspond à la déclaration d’un vecteur constant vide (ne contenant
aucun élément) et auquel aucun élément ne pourra être ajouté 11
const vector<int> vect2(vect1);
Correspond à la déclaration d’une copie figée (snapshot) du vecteur vect1.
5 3 0 32 41 26
17 7 22 1 4
D’un point de vue sémantique, 8 21 43 0 0
les vecteurs de vecteurs ne correspondent pas 35 20 0 42
(nécessairement) à des matrices, mais simplement à 18 0 5 41
des ensembles d’ensembles d’éléments. 43 55 2 12
7
8
vector<vector<int> >
12. Cette contrainte est en fait une convention adoptée pour distinguer ce typage de l’opérateur «>>»
Toute variable 13 de type vector peut être modifiée (globalement) par affectation:
«identificateur» = «valeur»;14
Exemple:
// 1) Déclarations-initialisations v1 v2 c3
vector<int> v1(4,1); 1 2 ’a’
vector<int> v2(3,2); 1 2 ’a’
vector<char> c3(2,’a’); 1 2 v1 v2 c3
// 2) Affectations ......... 1 8 1 ’a’
v2 = v1; 8 1 ’a’
v1 = vector<int>(3,8); 8 1
c3 = vector<int>(4,7); // ILLEGAL 1
13. Sauf [naturellement] les constantes.
14. Parmis les opérateurs définis pour les vecteurs, on trouve en effet celui d’affectation, i.e. « = ».
L’indice 15 , placé entre crochets «[]», indique le rang de l’élément dans le tableau.
Opérateur Opération
< strictement inférieur
<= inférieur ou égal comparaison lexicographique
> strictement supérieur des éléments.
>= supérieur ou égal
== égalité
!= différence (non-égalité)
Exemple:
individus.puch_back(jean);
Comme les fonction, les méthodes peuvent éventuellement retourner une valeur.
16. Les méthodes sont des éléments informatiques (séquences d’instructions) issus de l’extension «objet» de C++. Pour le moment, il vous suffit de les
considérer comme des fonctions ayant une syntaxe d’appel un peu particulière.
Une manière usuelle pour parcourir les éléments d’un vecteur est donc l’itération for suivante:
Modificateurs:
Accès:
do { vect.push_back(saisirEntier)
} while (vect.back());
Dans ces deux exemples, diametre, rayon et nbCercles sont tous de même type ( int).
Mais, si l’on utilise longueur pour exprimer toutes les longeurs (programme de gauche), et
que pour une raison quelconque on est amené à changer la représentations des longueurs
(p.ex. par des réels), il suffira d’opérer ce changement dans la définition de l’alias
longueur, plutôt qu’à chaque occurence de int représentant une longueur.
Pour pouvoir utiliser des string dans un programme, il faut importer les prototypes et
définitions contenus dans la librairie au moyen de la directive d’inclusion:
#include <string>
Exemple:
#include <string>
...
string chaine;
où valeur est:
➱ soit une chaîne de caractères de la forme "..."
➱ soit une variable, constante ou référence de type string
Exemple:
string str("une chaîne");
string chaine(str);
Exemple:
string chaine; // chaine vaut «»
const string chaine2("test"); // chaine2 vaut «test»
chaine = ’c’; // chaine vaut «c»
chaine = string("str-temporaire"); // chaine vaut «str-temporaire»
chaine = "built-in str"; // chaine vaut «built-in str»
chaine = chaine2; // chaine vaut «test»
Opérateur Opération
< strictement inférieur
<= La relation d’ordre utilisée pour ces
inférieur ou égal
> opérateurs est l’ordre alphabétique.
strictement supérieur
(plus exactement, ordre lexicographique)
>= supérieur ou égal
== égalité
!= différence (non-égalité)
• string + string,
• string + char,
• string + "...",
• char + string,
• "..." + string,
Une manière usuelle pour parcourir un à un les caractères d’une chaîne est
donc l’itération for suivante:
Insère, à partir de la position indiçée par pos, la chaîne s, et renvoie une référence
à la chaîne modifiée.
Insère, à partir de la position indiçée par pos, une sous-chaîne de s débutant à la position
start (ou 0 dans le cas du premier prototype), et de longueur len. Une référence à la chaîne
modifiée est retournée.
21. Une constante particulière, batisée «string::npos» permet en effet de représenter la notion «jusqu’à la fin de la chaîne».
22. Remarquons que grâce à la convertion automatique (casting) des chaînes de forme «"..."» vers le type string, la commande peut également s’écri-
re: «string("1234").insert(2,"#$%-!",3,1)».
Substitue s au long caractères de la chaîne, à partir de la position indiçée par pos. Renvoie une
référence à la chaîne modifiée.
• string& replace (int pos, int long, const char[] s, int len)
string& replace (int pos, int long const string& s, int start, int len)23
Ainsi, « str. find(s, pos)» consiste à faire une recherche de s dans str,
privée de ses pos premiers éléments:
s
str
0 pos
Ainsi, « str. rfind(s, pos)» consiste à faire une recherche «arrière» de s dans
la sous-chaîne préfixe de str et de longueur pos + 1:
s
str
0 pos
Acronyme Signification
CFF Chemin de Fer Fédéraux
c-à-d c’est-à-dire
... ...
#include <string>
#include <vector>
// Fonction de remplacement:
// utilise les variables externes définissant les acronymes,
// mais uniquement en lecture (pas d’effet de bord).
void expliciteAcronymes(string& str)
{
int debutAbbrev;
for (int i(0); i<acronymes.size(); ++i)
while ((debutAbbrev=str.find(acronymes[i])) != string::npos)
str.replace(debutAbbrev,acronymes[i].size(),significations[i]);
}
...
2) dans le cas des programmes interactifs, permettre à l’utilisateur d’interrompre son travail
(mettre momentanément fin à l’interaction avec le programme) et pouvoir le reprendre plus
tard. Exemple: l’éditeur XEmacs qui vous permet de sauvegarder votre travail, et le
poursuivre (reprendre) la semaine suivante 27 .
Pour assurer une telle persistance des données informatiques, on utilise la plupart du
temps le système de fichiers – mis à disposition par le système d’exploitation sous-jacent.
26. Dans ce cas, le format de «sauvegarde» des données (i.e. conventions de représentation, organisation) revêt une importance particulière: il doit soit
être humainement compréhensible, soit correctement documenté ou respecter un standard, de manière à pouvoir être interprété par un autre logiciel.
27. Dans ce cas le format de représentation des données importe peu, et il n’est en particulier pas nécessaire de l’expliciter (on parle de format interne).
Programme
out-stream output.txt
sortie
entree
in-stream
input.txt
istr2 in-stream
Système
d’exploitation
in/out-stream
iostr titi.log
#include <fstream>
28. Les streams de la librairie standard ne véhiculent que des données en mode texte. Pour lire/écrire des fichiers binaires, il faut soit utiliser des
fonctions ad hoc spécifiques, soit étendre «manuellement» la bibliothèques de streams.
Le mécanisme général pour la mise en œuvre d’entrée-sorties via les fichiers est:
(1) Création d’un stream (d’entrée ou de sortie),
par la déclaration d’une variable de type ifstream ou ofstream.
Exemple: ifstream entree;
ofstream sortie;
Exemple:
ifstream entree("input.txt");
On peut considérer que l’initialisation fait directement appel à la fonction open.
La fonction open admet comme argument une chaîne de caractères de type "..."
(en fait un char[]): il n’est donc pas possible d’utiliser directement une chaîne de
type string29 . Il faut dans ce cas demander la conversion en type char[] du
string, en faisant appel à la fonction-méthode c_str:
Exemple:
string str("output.txt");
ifstream entree2;
ofstream sortie(str.c_str());
entree2.open(str.c_str());
29. En effet, s’il y a conversion implicite des chaînes de la forme "..." vers les strings (promotion), l’inverse n’est pas vrai.
Remarque:
Une fonction utile pour tester si le lecture d’un fichier est terminée
est la fonction-méthode eof.
if (entree.eof()) {
cout << "Fin du fichier" << endl;
}
void main()
{
string motSaisit;
string nomFichier("phrase.txt");
ofstream sortie(nomFichier.c_str());
cout << "Entrez une phrase terminée par « .»" << endl;
do
{
cin >> motSaisit;
sortie << endl << motSaisit;
} while (motSaisit != ".");
sortie.close();
}
#include <iostream>
#include <fstream>
#include <string>
void main()
{
string motSaisit;
ifstream entree("phrase.txt");
while (!entree.eof())
{
entree >> motSaisit;
cout << motSaisit << ’ ’;
}
cout << endl;
entree.close();
}
Plus précisément, un certain nombre de prédicats sont associés aux streams, et permettent
d’en connaître l’état: 30
bool good() le stream est dans un état correct, la prochaine opération (lecture/écriture) sera un succès
bool eof() la fin du stream à été atteinte
bool fail() la prochaine opération (lecture/écriture) échouera
bool bad() le stream est corrompu: des données ont été perdues (et la prochaine opération échouera)
30. L’«évaluation» d’un stream revient en partivulier à tester le prédicat fail.
31. La version de gcc utilisée dans le cadre du cours utilise une ancienne représentation de ces entités, pas tout à fait compatible avec la nouvelle norme.
Toutefois, une adaptation a été écrite pour le besoin du cours, et placée dans le répertoire de la librairie standard.
string str ()
Exemple:
#include <sstream>
...
string composeMessage(const int errno, const string& description)
{
ostringstream ost;
ost << "Erreur (n˚" << errno << "): " << description;
return ost.str();
}
#include <sstream>
...
// Extrait les mots d’un string, et les affiches à
// raison de un par ligne
void wordByWord(const string& str){
istringstream ist(str);
string s;
while (ist >> s) {cout << s << endl;} Erreur
} (n˚5):
// en utilisant la fonction précédante: Fichier
wordByWord(composeMessage(5, "Fichier non trouvé")); non
} trouvé
Manipulateurs:
Options de configuration:
Quelques options:
cout.setf(ios::showbase);
hexa:0x40
cout << "hexa:" << hex << 64 << endl;
octal:0100
cout << "octal:" << oct << 64 << endl;
decim:64
cout << "decim:" << dec << 64 << endl;