Tableaux (introduction)#
Résumé des épisodes précédents#
Pour le moment nous avons vu :
Expressions :
3 * (4+5)
1 < x and x < 5 or y == 3
Variables, types, affectation :
variable = expression
Instruction conditionnelle :
if
Instructions itératives :
while
,do ... while
,for
Fonctions, procédures
Pourquoi aller plus loin?#
Les tableaux#
Exemple : Mini annuaire#
On souhaite implanter un mini annuaire. Pour cela, on
va stocker, pour chaque personne, un nom et un numéro de
téléphone. Nous utiliserons pour chacun d’entre eux une
chaîne de caractères (string
). Noter que le numéro n’est
pas un nombre : il peut contenir des espaces, etc.
Commençons par les incantations magiques usuelles :
#include <iostream>
using namespace std;
Dans un premier temps, notre annuaire aura trois personnes (les noms et numéros sont factices!) :
string nom1 = "Jeanne";
string telephone1 = "04 23 23 54 56";
string nom2 = "Franck";
string telephone2 = "03 23 42 34 26";
string nom3 = "Marie";
string telephone3 = "06 52 95 56 06";
Écrivons un petit programme pour afficher le contenu de l’annuaire :
cout << nom1 << " " << telephone1 << endl;
cout << nom2 << " " << telephone2 << endl;
cout << nom3 << " " << telephone3 << endl;
Est-ce satisfaisant comme manière de procéder?
Non : ce code est très redondant. Imaginez s’il y avait 100 personnes dans l’annuaire. La ligne d’affichage serait répétée 100 fois.
Répéter 3 fois (quasiment) la même ligne cela s’appelle une boucle for
.
Essayons :
for ( int i=1; i <= 3; i++ ) {
cout << nom1 << " " << telephone1 << endl;
}
Ce n’est pas encore ce que l’on veut. En effet, pour \(i=1\), on
voudrait utiliser la variable nom1
, pour \(i=2\) la variable
nom2
, etc. Mais ce sont des variables distinctes!
La solution va être de regrouper les trois variables nom1
, nom2
, nom3
contenant chacune un nom en une seule variable contenant les trois noms.
Pour cela, on va utiliser un tableau. En C++, on utilisera
le type vector<string>
pour représenter un tableau de chaînes de caractères.
Il faut au préalable charger la bibliothèque vector
:
#include <vector>
using namespace std;
On construit un tableau pour les trois noms et un pour les trois numéros de téléphone :
vector<string> noms;
noms = vector<string>(3);
vector<string> telephones;
telephones = vector<string>(3);
noms
et telephones
contiennent chacun trois chaînes vide
(ne vous préocuppez pas du type affiché en deuxième ligne;
c’est essentiellement vector<string>
.
noms
telephones
Commençons à remplir le tableau. Notez que, pour accéder au i-ème nom, on
utilise la syntaxe noms[i]
.
noms[1] = "Jeanne";
telephones[1] = "04 23 23 54 56";
noms[2] = "Franck";
telephones[2] = "03 23 42 34 26";
Voyons ce que cela donne :
noms
Oups! Que s’est-il passé????
Explication : en C++, comme dans la plupart des langages, les tableaux
commencent à 0. Ainsi, le premier nom est noms[0]
, le deuxième noms[1]
,
etc.
Reprenons :
noms[0] = "Jeanne";
telephones[0] = "04 23 23 54 56";
noms[1] = "Franck";
telephones[1] = "03 23 42 34 26";
noms[2] = "Marie";
telephones[2] = "06 52 95 56 06";
Maintenant, nos tableaux sont remplis correctement :
noms
telephones
Nous pouvons enfin écrire notre boucle for
:
#include <iostream>
for ( int i=0; i < 3; i++ ) {
cout << noms[i] << " " << telephones[i] << endl;
}
Notez bien que nous avons fait varier i
de 0
à 2
!
Si vous êtes aventureux, regardez ce qui se passe si on fait varier
i
de 1
à 3
. Soyez prêt à redémarrer votre noyau!
Indication
Nommage : telephone
ou telephones
?
Il pourrait être tentant de nommer notre variable telephone
, pour
que telephone[i]
ressemble à telephonei
. Mais ce n’est pas la
bonne convention. Le nom d’une variable doit refléter ce qu’elle
contient. Ici c’est plusieurs numéros de téléphones. Donc
telephones
.
Un annuaire avec seulement trois personnes, c’est un peu triste. Ajoutons une quatrième personne :
noms[3] = "Joël";
telephones[3] = "07 23 63 92 38"
noms
Oups! Que s’est-il passé? On a essayé de rajouter un quatrième nom dans un tableau prévu pour trois noms, et ça a tout planté.
Redémarrez votre noyau.
Reprenons à zéro : incantations magiques, création de tableau de taille 4, remplissage :
#include<vector>
#include<iostream>
using namespace std;
vector<string> noms;
noms = vector<string>(4);
vector<string> telephones;
telephones = vector<string>(4);
noms[0] = "Jeanne";
telephones[0] = "04 23 23 54 56";
noms[1] = "Franck";
telephones[1] = "03 23 42 34 26";
noms[2] = "Marie";
telephones[2] = "06 52 95 56 06";
noms[3] = "Joël";
telephones[3] = "07 23 63 92 38";
Réutilisons notre programme pour afficher l’annuaire :
for ( int i=0; i < 3; i++ ) {
cout << noms[i] << " " << telephones[i] << endl;
}
Oups! Quel est le problème?
Forcément, on ne peut pas réutiliser exactement le même
programme. Il faut changer le 3
en 4
:
for ( int i=0; i < 4; i++ ) {
cout << noms[i] << " " << telephones[i] << endl;
}
Ce n’est pas très pratique de devoir à chaque fois ajuster notre programme. Imaginez le vendeur de téléphone s’il devait changer son programme chaque fois qu’il rajoute un client!
Pour éviter cela, nous allons exploiter le fait qu’un tableau connaît sa taille :
noms.size()
Nous pouvons maintenant écrire notre boucle de sorte qu’elle fonctionne pour un annuaire de taille quelconque :
for ( int i=0; i < noms.size(); i++ ) {
cout << noms[i] << " " << telephones[i] << endl;
}
Ce n’est pas encore satisfaisant de devoir reconstruire l’annuaire chaque fois que l’on veut rajouter une personne. Imaginez si vous deviez retaper tous vos contacts sur votre téléphone chaque fois que vous souhaitez en rajouter un.
Pour pallier cela, nous allons voir une dernière opération sur
les tableaux: push_back
; littéralement, rajouter à la fin :
noms.push_back("Zoé");
telephones.push_back("04 12 43 93 27");
noms
Observez ce qui se passe si vous exécutez à nouveau les deux cellules précédentes.
Tout ceci n’est pas encore parfait : on mélange encore beaucoup données et code. Imaginez si chaque propriétaire de téléphone portable devait réécrire le code pour afficher le contenu de son annuaire.
Un peu plus tard aujourd’hui, nous verrons comment écrire le code une bonne fois pour toutes dans une fonction réutilisable. Dans deux semaines, nous verrons comment regrouper toute l’information dans un tableau unique, où le nom et le numéro de téléphone sont proches l’un de l’autre. Plus tard dans le semestre, nous verrons comment utiliser les fichiers pour stocker les données complètement séparément.
Les tableaux#
Définition
Un
tableau (homogeneous contiguous array) est une
valeur composite
homogène
C’est-à-dire qu’elle est formée de plusieurs valeurs du même type
Une valeur (ou élément (element)) d’un tableau \(t\) est désignée par son indice (array index) \(i\) dans le tableau
on la note \(t[i]\)
En C++ : cet indice est un entier entre \(0\) et \(\ell-1\),
où \(\ell\) est le nombre d’éléments du tableau
Exemple :
Voici un tableau de six entiers : \(\begin{array}{|c|c|c|c|c|c|c|c|c|c|} \hline 1 & 4 & 1 & 5 & 9 & 2\\\hline \end{array}\)
Avec cet exemple,
t[0]
vaut 1,t[1]
vaut 4,t[2]
vaut 1, …Notez que l’ordre et les répétitions sont importants!
Les tableaux en C++#
Exemple :
Au préalable :
#include <vector>
using namespace std;
vector<int> t;
t = vector<int>(6);
t[0] = 1;
t[1] = 4;
t[2] = 1;
t[3] = 5;
t[4] = 9;
t[5] = 2;
t[1] + 2 * t[3]
Construction des tableaux#
Déclaration d’un tableau d’entiers :
vector<int> t;
Pour un tableau de nombres réels :
vector<double>
, etc.♣
vector
est un template
Allocation d’un tableau de six entiers :
(on réserve de l’espace en mémoire pour ces six entiers)
t = vector<int>(6);
Initialisation du tableau :
t[0] = 1;
t[1] = 4;
t[2] = 1;
Les trois étapes de la construction d’un tableau#
À retenir
Une variable de type tableau se construit en trois étapes:
Déclaration
Allocation
Sans elle : faute de segmentation (au mieux!)Initialisation
Sans elle : même problème qu’avec les variables usuelles
Astuce
Raccourci
Déclaration, allocation et initialisation en un coup :
vector<int> t = { 1, 4, 1, 5, 9, 2 };
Introduit par la norme C++ de 2011
Utilisation des tableaux#
Syntaxe et sémantique#
t[i]
s’utilise comme une variable usuelle :// Exemple d'accès en lecture x = t[2] + 3 * t[5]; y = sin( t[3]*3.14 ); // Exemple d'accès en écriture t[4] = 2 + 3*x;
En C++ les indices ne sont pas vérifiés!
Le comportement de
t[i]
n’est pas spécifié en cas de débordement
Source numéro 1 des trous de sécurité!!!
Accès avec vérifications:
t.at(i)
au lieu det[i]
Quelques autres opérations sur les tableaux
t.size(); // Taille du tableau `t`
t.push_back(3); // Ajout d'un élément à la fin de `t`
Indication
Remarque
La syntaxe de ces opérations peut vous paraître suprenante :
que vient le .
faire là? Pourquoi n’écrit-on pas plutôt
size(t)
ou push_back(t,3)
?
En fait, nous sommes en train d’utiliser de la programmation objet sans le dire. Vous n’avez pas besoin de connaître les détails pour le moment; juste de mémoriser la syntaxe un peu particulière. Les détails vous seront donnés dans le cours de programmation modulaire.
Fonctions et tableaux#
Indication
Remarque Nous avons dit qu’un tableau était une valeur. Il est donc tout à fait possible d’écrire une fonction qui prend un ou des tableaux en paramètres et/ou qui renvoie des tableaux.
Voici par exemple une fonction qui affiche le contenu d’un annuaire :
#include <iostream>
#include <vector>
using namespace std;
void affiche_annuaire(vector<string> noms, vector<string> telephones) {
for ( int i = 0; i < noms.size(); i++ ) {
cout << noms[i] << " " << telephones[i] << endl;
}
}
Appliquons la à notre annuaire :
vector<string> noms = {"Jeanne", "Franck", "Marie", "Joël"};
vector<string> telephones = {"04 23 23 54 56", "03 23 42 34 26", "06 52 95 56 06", "07 23 63 92 38"}
affiche_annuaire(noms, telephones)
En voici une autre qui calcule la somme des éléments d’un tableau :
#include<vector>
using namespace std;
int somme(vector<int> v) {
int s = 0;
for ( int i = 0; i < v.size(); i++) {
s = s + v[i];
}
return s;
}
vector<int> v = { 1, 2, 3 };
somme(v)
Voir la fiche d’exercices sur les tableaux et fonctions.
Résumé : les tableaux#
Motivation : manipulation de collections de données
Exemple : un annuaire
Un tableau est une valeur composite formée de plusieurs valeurs du même type
Un tableau se construit en trois étapes :
Déclaration :
vector<int> t;
Allocation :
t = vector<int>(3);
Initialisation :
t[0] = 3; t[1] = 0; ...
Utilisation :
t[i] = t[i]+1
,t.size()
,t.push_back(3)
Un tableau est une valeur comme les autres
Il peut être passé en paramètre à, ou renvoyé par, une fonction