Découvrez les tableaux dynamiques
Je vous avais dit que nous allions parler de deux sortes de tableaux : ceux dont la taille est fixée et ceux dont la taille peut varier, les tableaux dynamiques. Certaines choses sont identiques, ce qui va nous éviter de tout répéter.
Déclarez un tableau dynamique
La première différence se situe au tout début de votre programme. Il faut ajouter la ligne #include <vector>
pour utiliser ces tableaux.
La deuxième grosse différence se situe dans la manière de déclarer un tableau :
vector<TYPE> NOM(TAILLE);
Par exemple, pour un tableau de 5 entiers, on écrit :
#include <iostream>
#include <vector> //Ne pas oublier !
using namespace std;
int main()
{
vector<int> tableau(5);
return 0;
}
Cela veut dire que les choses ne ressemblent pas vraiment aux tableaux statiques. Cependant, vous allez voir que, pour parcourir le tableau, le principe est similaire. Mais avant cela, il y a deux astuces bien pratiques à savoir.
On peut directement remplir toutes les cases du tableau en ajoutant un deuxième argument entre les parenthèses :
vector<int> tableau(5, 3); //Crée un tableau de 5 entiers valant tous 3
vector<string> listeNoms(12, "Sans nom");
//Crée un tableau de 12 strings valant toutes « Sans nom »
Et on peut déclarer un tableau sans case, en ne mettant pas de parenthèses du tout :
vector<double> tableau; //Crée un tableau de 0 nombre à virgule
Euh… À quoi sert un tableau vide ?
Rappelez-vous que ce sont des tableaux dont la taille peut varier. On peut donc ajouter des cases par la suite. Attendez un peu et vous saurez tout.
Accédez aux éléments d'un tableau dynamique
La déclaration était très différente des tableaux statiques. Par contre, l'accès est exactement identique. On utilise à nouveau les crochets, et la première case possède aussi le numéro 0.
On peut donc réécrire l'exemple de la section précédente avec un vector
:
int const nombreMeilleursScores(5); //La taille du tableau
vector<int> meilleursScores(nombreMeilleursScores); //Déclaration du tableau
meilleursScores[0] = 118218; //Remplissage de la première case
meilleursScores[1] = 100432; //Remplissage de la deuxième case
meilleursScores[2] = 87347; //Remplissage de la troisième case
meilleursScores[3] = 64523; //Remplissage de la quatrième case
meilleursScores[4] = 31415; //Remplissage de la cinquième case
Changez la taille d'un tableau dynamique
Entrons maintenant dans le vif du sujet : faire varier la taille d'un tableau. Commençons par ajouter des cases à la fin d'un tableau. 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 :
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
Voyons de plus près ce qui se passe dans la mémoire :
Une case supplémentaire a été ajoutée au bout du tableau, de manière automatique. C'est fou ce que cela peut être intelligent un ordinateur, parfois.
Et bien sûr on peut ajouter beaucoup de cases à la suite les unes des autres :
vector<int> tableau(3,2); //Un tableau de 3 entiers valant tous 2
tableau.push_back(8); //On ajoute une 4ème case qui contient la valeur 8
tableau.push_back(7); //On ajoute une 5ème case qui contient la valeur 7
tableau.push_back(14); //Et encore une avec le nombre 14 cette fois
//Le tableau contient maintenant les nombres : 2 2 2 8 7 14
Et ils ne peuvent que grandir, les vectors ?
Non ! Bien sûr que non. Les créateurs du C++ ont pensé à tout.
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 :
vector<int> tableau(3,2); //Un tableau de 3 entiers valant tous 2
tableau.pop_back(); //Et hop ! Plus que 2 cases
tableau.pop_back(); //Et hop ! Plus que 1 case
Comme la taille peut changer, on ne sait pas de manière certaine combien d'éléments contient un tableau, si ?
Heureusement, il y a une fonction pour cela : size()
.
Avec tableau.size()
, on récupère un entier correspondant au nombre d'éléments de tableau
:
vector<int> tableau(5,4); //Un tableau de 5 entiers valant tous 4
int const taille(tableau.size());
//Une variable qui contient la taille du tableau
//La taille peut varier mais la valeur de cette variable ne changera pas
//On utilise donc une constante
//À partir d'ici, la constante 'taille' vaut donc 5
Calculez une moyenne avec un vector
Revenons à notre exercice du calcul des moyennes, nous allons le réécrire "à la sauce" vector
.
Je vous laisse essayer. Si vous n'y arrivez pas, voici ma solution :
#include <iostream>
#include <vector> //Ne pas oublier !
using namespace std;
int main()
{
vector<double> notes; //Un tableau vide
notes.push_back(12.5); //On ajoute des cases avec les notes
notes.push_back(19.5);
notes.push_back(6);
notes.push_back(12);
notes.push_back(14.5);
notes.push_back(15);
double moyenne(0);
for(int i(0); i<notes.size(); ++i)
//On utilise notes.size() pour la limite de notre boucle
{
moyenne += notes[i]; //On additionne toutes les notes
}
moyenne /= notes.size();
//On utilise à nouveau notes.size() pour obtenir le nombre de notes
cout << "Votre moyenne est : " << moyenne << endl;
return 0;
}
On a écrit deux programmes qui font exactement la même chose de deux manières différentes. C'est très courant, il y a presque toujours plusieurs manières de faire les choses. Chacun choisit celle qu'il préfère.
Les vector
et les fonctions
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.
Grâce à la fonction size()
, il n'y a même pas besoin d'ajouter un deuxième argument pour la taille du tableau ! Cela donne tout simplement :
//Une fonction recevant un tableau d'entiers en argument
void fonction(vector<int> a)
{
//…
}
Simple, non ? Mais on peut quand même faire mieux.
Souvenez-vous du passage par référence constante pour optimiser la copie. En effet, si le tableau contient beaucoup d'éléments, le copier prendra du temps. Il vaut donc mieux utiliser cette astuce.
Ce qui donne :
//Une fonction recevant un tableau d'entiers en argument
void fonction(vector<int> const& a)
{
//…
}
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 :
vector<int> tableau(3,2); //On crée un tableau de 3 entiers valant 2
fonction(tableau); //On passe le tableau à la fonction déclarée au-dessus
Si, comme moi, vous aimez découper votre programme en plusieurs fichiers en séparant les fichiers d'en-tête et les fichiers source, il faudra penser à :
Ajouter
#include <vector>
au début du fichier.Ajouter
std::
devant lesvector
, comme pour lesstring
, en fait :
#ifndef TABLEAU_H_INCLUDED
#define TABLEAU_H_INCLUDED
#include <vector>
void fonctionAvecTableau(std::vector<int>& tableau);
#endif // TABLEAU_H_INCLUDED
Il est évidemment possible d'écrire une fonction renvoyant un vector
. Je suis sûr que vous avez déjà deviné comment déclarer une telle fonction. Par exemple pour une fonction qui renvoie un tableau multi-dimensionnel de nombres à virgule, on écrira :
vector<double> encoreUneFonction(int a)
{
//...
}
Manipulez des tableaux multidimensionnels
Je vous ai dit en début de chapitre que l'on pouvait créer des tableaux de n'importe quoi. Des tableaux d'entiers, des tableaux de strings, et ainsi de suite. On peut donc créer des tableaux… de tableaux !
Je vous vois d'ici froncer les sourcils et vous demander à quoi cela peut bien servir. Une fois n'est pas coutume, je vous propose de commencer par visualiser la mémoire :
La grosse case jaune représente, comme à chaque fois, une variable. Cette fois, c'est un tableau de 5 éléments dont j'ai représenté les cases en utilisant des colonnes. À l'intérieur de chacune des colonnes, on trouve un petit tableau de 4 éléments dont on ne connaît pas la valeur.
Mais si vous regardez attentivement les points d'interrogation, vous pouvez voir une grille régulière ! Un tableau de tableau est donc une grille de variables. Et là, je pense que vous trouvez cela tout de suite moins bizarre.
Déclarez un tableau multidimensionnel
Pour déclarer un tableau multidimensionnel, il faut indiquer les dimensions les unes après les autres entre crochets :
type nomTableau[tailleX][tailleY]
Pour reproduire le tableau du schéma, on doit déclarer le tableau suivant :
int tableau[5][4];
Ou encore mieux, en déclarant des constantes :
int const tailleX(5);
int const tailleY(4);
int tableau[tailleX][tailleY];
Et c'est tout ! C'est bien le C++, non ?
Accédez aux éléments de la grille
Je suis sûr que je n'ai pas besoin de vous expliquer la suite. Vous avez sûrement deviné tout seul. Pour accéder à une case de notre grille, il faut indiquer la position en X et en Y de la case voulue.
Par exemple tableau[0][0]
accède à la case en bas à gauche de la grille.
tableau[0][1]
correspond à celle qui se trouve juste au-dessus, alors que tableau[1][0]
se situe directement à sa droite.
Comment accéder à la case située en haut à droite ?
Il s'agit de la dernière case dans la direction horizontale. Entre les premiers crochets, il faut donc mettre tailleX-1, c'est-à-dire 4. C'est également la dernière case selon l'axe vertical : par conséquent, entre les seconds crochets, il faut spécifier tailleY-1. Ainsi, cela donne tableau[4][3]
.
Allez plus loin
On peut bien sûr aller encore plus loin et créer des grilles tridimensionnelles, voire plus. On peut tout à fait déclarer une variable comme ceci :
double grilleExtreme[5][4][6][2][7];
Mais là, il ne faudra pas me demander un dessin !
Notez qu'il est aussi possible de créer des tableaux multidimensionnels de taille variable, en utilisant des vector
. Pour une grille 2D d'entiers, on devra écrire :
vector<vector<int> > grille;
Le problème est que ce n'est pas réellement un tableau 2D, mais plutôt un tableau de lignes. Il faudra donc commencer par ajouter des lignes à notre tableau :
grille.push_back(vector<int>(5)); //On ajoute une ligne de 5 cases à notre grille
grille.push_back(vector<int>(3,4)); //On ajoute une ligne de 3 cases contenant chacune le nombre 4 à notre grille
Chaque ligne peut donc avoir une longueur différente. On peut accéder à une ligne en utilisant les crochets :
grille[0].push_back(8); //Ajoute une case contenant 8 à la première ligne du tableau
Finalement, on peut accéder aux valeurs dans les cases de la grille en utilisant deux paires de crochets, comme pour les tableaux statiques. Il faut par contre s'assurer que cette ligne et cette colonne existent réellement.
grille[2][3] = 9; //Change la valeur de la cellule (2,3) de la grille
Voyez un string
comme un tableau
Avant de terminer ce chapitre, il faut quand même que je vous fasse une petite révélation : les chaînes de caractères sont en fait des tableaux !
On ne le voit pas lors de la déclaration, c'est bien caché. Mais il s'agit en fait d'un tableau de lettres. Il y a même beaucoup de points communs avec les vector
.
Accédez aux lettres
L'intérêt de voir une chaîne de caractères comme un tableau de lettres, c'est qu'on peut accéder à ces lettres et les modifier. Et je ne vais pas vous surprendre, on utilise aussi les crochets :
#include <iostream>
#include <string>
using namespace std;
int main()
{
string nomUtilisateur("Julien");
cout << "Vous etes " << nomUtilisateur << "." <<endl;
nomUtilisateur[0] = 'L'; //On modifie la première lettre
nomUtilisateur[2] = 'c'; //On modifie la troisième lettre
cout << "Ah non, vous etes " << nomUtilisateur << "!" << endl;
return 0;
}
Testons pour voir :
Vous etes Julien. Ah non, vous etes Lucien!
C'est fort ! Mais on peut faire encore mieux…
Utilisez des fonctions
On peut également utiliser size()
pour connaître le nombre de lettres et push_back()
pour ajouter des lettres à la fin. La encore, c'est comme avec vector
:
string texte("Portez ce whisky au vieux juge blond qui fume."); //46 caractères
cout << "Cette phrase contient " << texte.size() << " lettres." << endl;
Mais contrairement aux tableaux, on peut ajouter plusieurs lettres d'un coup, en utilisant le +=
:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string prenom("Albert");
string nom("Einstein");
string total; //Une chaîne vide
total += prenom; //On ajoute le prénom à la chaîne vide
total += " "; //Puis un espace
total += nom; //Et finalement le nom de famille
cout << "Vous vous appelez " << total << "." << endl;
return 0;
}
Cela donne bien sûr :
Vous vous appelez Albert Einstein.
C'est fou ce que c'est bien le C++, parfois !
En résumé
Si la taille du tableau est susceptible de varier, créez un tableau dynamique de type
vector
:vector<int> tableau(5);
.On peut créer des tableaux multidimensionnels. Par exemple,
int tableau[5][4];
revient à créer un tableau de 5 colonnes et 4 lignes.Les chaînes de caractères
string
peuvent être considérées comme des tableaux. Chacune des cases correspond à un caractère.
Grâce aux tableaux, vous pouvez maintenant simplifier le stockage de valeurs et les manipuler facilement à l’aide de boucles. Voyons ensuite comment avec un programme lire ou écrire dans des fichiers.