• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

Ce cours existe en livre papier.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 17/10/2022

Manipulez des tableaux dynamiques

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. Elle contient la valeur 8.
Effet d'un push_back sur un vector

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 à :

  1. Ajouter #include <vector> au début du fichier.

  2. Ajouter std::  devant les vector  , comme pour les string   , 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 :

Le schéma représente un tableau de 5 colonnes. À l'intérieur de chaque colonne, il y a un petit tableau de 4 éléments dont on ne connaît pas la valeur, désignés par un point d'interrogation.
Un tableau bidimensionnel dans la mémoire de l'ordinateur

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.

Exemple de certificat de réussite
Exemple de certificat de réussite