Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zér0 > Les tutoriels > Lecture du tutoriel

Les tableaux dynamiques

Avatar
Auteur : Nanoc
Créé : le 07/04/2008 22:12:07
Modifié : le 12/06/2008 20:25:01
Noter et commenter ce tutoriel
Imprimer ce tutoriel
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Avant de se plonger plus en avant dans les notions de programmation orientée objet, il est nécessaire de faire un petit détour par les tableaux.

Si vous vous rapellez bien, vous avez appris à réaliser des tableaux en C et par conséquent en C++. Mais ces tableaux ne sont pas pratiques et ne sont pas redimensionnables. La bibliothèque standard du C++ fourni une classe spéciale pour gérer les tableaux. Il s'agit des std::vector.

L'utilisation de la STL (la bibliothèque standard du C++) fait parfois appel à des notions avancées, telles que les itérateurs ou les templates. Cependant, la plupart du temps, il n'est pas nécessaire de faire appel à ces éléments. C'est pourquoi cette partie n'ee parlera pas et se limitera aux fonctions de base des vector. Ces notions dont le nom ne vous dit certainement rien seront présentées plus tard lorsqu'on en aura réelement besoin.

Je suis prêt à parier que ce sera comme pour les string; une fois que vous aurez essayé les vector, vous n'aurez plus jamais envie de faire comme vous le faites jusqu'à présent. :p

Sommaire du chapitre :
Sommaire

Introduction

Petit souvenir du C



En C++ et de manière générale en programmation, il arrive très souvent qu'on ait besoin de tableaux pour stocker plusieurs informations d'un même type.
En C et par conséquent en C++, il est possible de créer des tableaux statiques (c'est-à-dire dont la taille ne varie pas) en utilisant les []. On peut de cette manière créer un tableau ne n'importe quel type de base ou de n'importe quelle classe. La syntaxe est la suivante :

Code : C++ - Tableau statique de type C
1
2
3
4
5
int monTableau[10];  //Crée un tableau de 10 entiers
 
std::string monTableau2[8]; //Crée un tableau de 8 strings
 
maClasse monTableau3[2];  // Crée un tableau de 2 objets de type "maClasse".


Il est alors possible d'accéder aux éléments du tableau en utilisant à nouveau les []. Il faut pour cela se souvenir que le premier élément d'un tableau est le 0 et pas le 1 comme on pourrait le penser au premier abord. Soit par exemple :

Code : C++ - Accès aux éléments d'un tableau statique
1
2
3
4
int monTableau[10];  //Crée un tableau de 10 entiers
 
monTableau[0] = 1; //Met "1" dans la première case du tableau
monTableau[4] = 5;  //Met "5" dans la 5 ème case du tableau


Si l'on s'arrête là, il n'y a aucun problème; tout marche pour le mieux. Cependant, si l'on veut créer des tableaux dont la taille varie au cours du programme ou si la taille d'un tableau n'est pas connue lors de la compilation, des problèmes apparaissent. Pour créer des tableaux dont la taille varie, il faut allouer sois-même la mémoire via l'opérateur new et redimensionner "à la main" le tableau à chaque fois que l'on ajoute un élément ou qu'on en supprime. C'est donc très peu pratique et l'on fait souvent des erreurs.

Je ne vous expliquerai pas comment faire car c'est la mauvaise manière de faire cela en C++.

De plus, le C++ est un langage orienté objet; il conviendrait donc de placer toute la mécanique interne d'allocation et destruction de mémoire dans une grosse boîte de manière à ce que lorsque l'utilisateur a besoin d'un tableau, il n'ai pas besoin de réfléchir à la manière dont est implémenté le tableau. Et ça tombe bien, parce que ce que je vais vous apprendre aujourd'hui est justement basé sur ce concept. Une grosse boîte qui gère tout le travail pénible pour nous.

La bonne manière passe toujours (ou presque) par l'utilisation des std::vector.

Il existe d'autres conteneurs standards dans la STL qui sont adaptés à des utilisations bien précises. Nous les verrons plus tard. La plus part du temps, un std::vector est cependant largement suffisant.


Le template vector



Les std::vector sont des conteneurs génériques standards implémentés sous forme de séquence et réalisés de telle sorte que toutes les opérations soient effectuées en un temps optimal.

:-°

Dit comme cela, ça peut faire peur car il y a certainement plusieurs mots dans cette définiton dont vous ignorez le sens exact. Décortiquons ensemble cette phrase.

Les std::vector sont des conteneurs ...


Cela signifie que c'est un objet pouvant en contenir d'autres, tout comme votre boîte de crayon contient des crayons de couleurs différentes.

... génériques ...


Cela signifie que vous pouvez réaliser un vector qui peut contenir n'importe quel type ou de n'importe quelle classe. Vous pouvez créer une boîte de crayon aussi bien qu'une boîte de stylos, le principe de fonctionnement est le même. En pratique, la généricité est implémentée sous forme de templates.

... standards ...


Cela signifie qu'ils font partie de la bibliothèque standard du C++ et que par conséquent ils existent quelque soit le compilateur que vous utilisez et quelque soit l'OS sur lequel vous désirez compiler. Quelque soit la personne chez qui vous allez, vous trouverez une boîte de crayon qui fonctionne de manière identique; elle contient des crayons :) .

...implémentés sous forme de séquence ...


Cela veut dire que les éléments contenus dans le tableau dynamique se trouvent l'un à coté de l'autre dans la mémoire, comme lorsqu'on utilise les tableaux statiques avec []. Tous les crayons se trouvent cote-à-cote dans la boîte et pas éparpillés partout sur votre bureau.

... et réalisés de telle sorte que toutes les opérations soient effectuées en un temps optimal.


Cela signifie que ce sont les meilleurs programmeurs qui ont écrits le code de ces tableaux et qu'ils ont fait cela de manière optimale. Autrement dit, vous ne ferez que du code moins efficace si vous essayez d'implémenter vous-même une classe de ce type.

En termes plus techniques, l'accès aux éléments se fait en O(1) alors que l'insertion en fin de tableau a une complexité en O(n) amorti.


L'analogie avec les crayons n'est peut-être pas la meilleure mais il faut bien garder ces idées à l'esprit.

J'ai parlé de template un petit peu plus haut. C'est un aspect avancé du langage C++, cependant il n'est pas nécessaire de savoir écrire des fonctions ou classes templates pour savoir utiliser un vector. Vous pourrez toujours revenir plus tard quand vous maitriserez bien cette notion pour voir ce qu'il y a de template dans cette classe. Je le répète encore une fois, les vector sont la bonne manière de réaliser des tableaux dynamiques en C++.


Cette structure de donnée s'appelle "vector". Le nom n'est pas forcément bien choisis car il fait penser à la notion mathématique de vecteur. Les std::vector ne sont cependant pas le meilleur moyen de représenter des vecteurs mathématiques. La STL propose pour cela les std::valarray optimisés pour ce genre d'opérations.


Une question qui se pose souvent est de savoir pourquoi préférer les std::vector aux tableaux dynamiques de style C. En effet, il est tout à fait possible de créer du code efficace et sécurisé de cette manière.
Premièrement, il y a les raisons d'efficacité citées plus haut. Il y a aussi le fait que les std::vector sont des objets et que par conséquent ils sont détruits à la fin d'une portée même si une exception est levée dans cette même portée (Les exceptions seront abordées plus loin). Ce qui ne serait pas le cas si vous utilisez les tableaux statiques car il faudrait gérer les delete[] nécessaires à la destruction correcte du tableau. En utilisant les std::vector vous profitez donc pleinement de l'idiome RAII. Vous n'aurez en aucun cas une fuite de mémoire; et ça c'est un vrai plus.

Créer un std::vector

Bibliothèque et espace de nom



Pour utiliser un vector, il faut inclure le fichier d'en-tête qui les définit. Il s'agit, comme par hasard, du fichier vector. Vous devez l'inclure à chaque utilisation des tableaux dynamiques.

Code : C++ - Header à inclure
1
#include <vector>


J'ai également dit plus haut, que c'était un conteneur standard, il se trouve par conséquent dans l'espace de nom std. Il y a alors deux possibilités, ajouter std:: devant chaque fonction ou utiliser la directive using namespace std;. J'utiliserai la deuxième méthode dans ce tutoriel afin de rendre le code le plus clair possible.

Code : C++ - Espace de nom
1
2
#include <vector>
using namespace std;


Construction d'un vector



Cela ne vous a sûrement pas échappé, j'ai plusieurs fois parlé de classe depuis le début. Et en effet, il s'agit d'une classe (template). Pour créer un vector, il faut donc passer par son constructeur ou plutôt par un de ses constructeurs. Je vais vous présenter les 3 plus importants.

Code : C++ - Mon premier vector
1
2
3
4
5
6
7
8
9
#include <vector>
#include <string>
using namespace std;
 
vector<int> monTableau;  //Crée un vector d'entier vide
 
vector<string> monTableau2;  //Crée un vector de strings vide
 
vector<Personnage> monTableau3; //Crée un vector pouvant contenir des Personnages


Vous êtes obligé d'indiquer un type entre les < >. Il n'est pas autorisé de laisser la zone vide.


J'ai utilisé ici le constructeur par défaut. Il crée un tableau dont la taille est 0.
Mais ... Quel intérêt y-a-t'il à créer un tableau sans cases ?

N'oubliez pas que c'est un tableau dynamique. Sa taille peut varier pendant l'exécution du programme !

Vous remarquerez que l'indication du type contenu se fait entre deux chevrons < et >. C'est la marque de fabrique des templates. Il faut donc vous souvenir de cette syntaxe un peu particulière.


Bon c'est bien joli tout ça, mais il serait bien plus pratique de pouvoir créer un tableau de taille non-nulle. Tout à fait, et les concepteurs de la STL ont pensé à vous; ils ont créé un constructeur qui fait ça. Quand on utilise ce constructeur, les éléments qui seront créés dans les cases du tableau seront initialisées via leur constructeur par défaut. Pour les types de base, ils seront remplis avec la valeur 0. Donc si vous écrivez une classe et que vous voulez créer un std::vector contenant des instances de votre classe, il faudra écrire un constructeur par défaut si vous voulez utiliser cette construction du std::vector.

Code : C++ - Construction d'un tableau de taille donnée
1
2
3
4
5
6
7
8
#include <vector>
using namespace std;
 
vector<double> monTableau(10); //Crée un tableau de 10 double
 
vector<int> monTableau2(5);    //Crée un tableau de 5 entiers

vector<Personnage> Armee(10);  //Création d'une armée de 10 Personnages en utilisant le constructeur par défaut de Personnage


Et puisque vous êtes gentils, je vous montre même le constructeur permettant de remplir directement le tableau avec une valeur donnée. De cette manière, tous les éléments du tableau ont la même valeur.

Code : C++ - Constructeur bonus !
1
2
3
4
5
6
7
8
#include <vector>
using namespace std;
 
vector<double> monTableau(10,3.14); //Crée un tableau de 10 double dont la valeur est 3.14
 
vector<int> monTableau2(5,4);    //Crée un tableau de 5 entiers dont la valeur est 4

vector<Personnage> Armee(10,Personnage("Epee",20)); //Création d'une armée de 10 Personnages en utilisant un des constructeurs surchargés


Il n'existe pas de moyen simple de construire un vector dont les éléments n'ont pas tous la même valeur.


Accès aux éléments du tableau



Pour accéder aux éléments du tableau, c'est très simple. Il suffit d'utiliser les [], comme dans le cas des tableaux statiques. Et comme pour les tableaux statiques, le premier élément possède l'indice 0. Ce qui donne par exemple :

Code : C++ - Accès aux éléments
1
2
3
4
5
6
7
8
9
#include <vector>
#include <iostream>
using namespace std;
 
vector<double> monTableau(10,3.14); //Crée un tableau de 10 double dont la valeur est 3.14
 
monTableau[5] = 2.718; //On met 2.718 dans la 6ème case du tableau
 
cout << monTableau[0] << endl; //Affiche la valeur contenue dans la première case du tableau


Et si j'accède à une case qui n'existe pas ?

Mmmh... bonne question. Si vous avez de la chance, votre programme va crasher à cause d'une "segmentation fault". Il peut aussi très bien ne pas crasher et continuer comme si il ne s'était rien passé et planter un autre jour à cause de cette erreur que vous avez oublié de traiter. C'est donc exactement comme avec les tableaux statiques.

Si vous n'êtes pas sûr que la case à laquelle vous souhaitez accéder est valide, il faut utiliser la fonction membre at(). Cette dernière lève une exception standard si vous accédez à une case interdite. Le mieux est encore de prendre un exemple :

Code : C++ - Accès sécurisé aux éléments
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <vector>
#include <iostream>
using namespace std;
 
vector<double> monTableau(10,3.14); //Crée un tableau de 10 double dont la valeur est 3.14
 
monTableau.at(5) = 2.718; //On met 2.718 dans la 6ème case du tableau
 
cout << monTableau.at(0) << endl; //Affiche la valeur contenue dans la première case du tableau
 
//Jusque là le résultat est identique au code précédent
 
monTableau.at(10) = 3;   //Ceci n'est pas autorisé. La case 10 ne fait pas partie du tableau.
// Il faudra donc récupérer l'exception et la traiter pour continuer le programme


Dans la plupart des cas l'utilisation de at() est inutile puisqu'on peut connaître facilement la taille du tableau (voir plus loin). On utilise donc quasiment tout le temps les [], puisqu'ils ont l'avantage d'être plus rapide.


Il existe également deux fonctions membres permettant d'accéder à des cases particulières du tableau. La première, front(), permet d'accéder au premier élément du tableau. La deuxième, back(), permet d'accéder au dernier élément sans pour cela avoir besoin de connaître la taille du tableau.

Code : C++ - front() et back()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <vector>
#include <iostream>
using namespace std;
 
vector<double> monTableau(10,3.14); //Crée un tableau de 10 double dont la valeur est 3.14
 
monTableau[0] = 2.718; //On met 2.718 dans la 1ère case du tableau
 
cout << monTableau.front() << endl; //Affiche la valeur contenue dans la première case du tableau
//Affiche 2.718
 
monTableau.back() = 1.0;  //Met 1.0 dans la dernière case du tableau.
 
cout << monTableau[9] << endl; //Affiche la dernière case du tableau
//Affiche 1.0


Bon, ok, mais jusque là, on a rien appris de nouveau par rapport aux tableaux statiques. Tout à fait mais restez quand même, la suite est bien plus intéressante.



Quelques fonctions de base

Connaître la taille d'un tableau



Aha ! Voilà quelquechose qu'il était impossible de faire proprement avec les tableaux statiques. Ici, c'est on ne peut plus simple, il suffit d'utiliser la fonction membre size().

Code : C++ - Taille d'un tableau
1
2
3
4
5
6
7
#include <vector>
#include <iostream>
using namespace std;
 
vector<double> monTableau(10,3.14); //Crée un tableau de 10 double dont la valeur est 3.14
 
cout << monTableau.size() << endl; //Affiche la taille du tableau, dans notre cas 10


C'est simple non ? Je vous l'avais promis, les vector c'est hyper-facile à utiliser. Connaître la taille est très pratique. Cela permet par exemple de parcourir un tableau via une boucle for sans avoir besoin de connaître en avance sa taille.

Code : C++ - Parcourir un tableau
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <vector>
#include <iostream>
using namespace std;
 
vector<double> monTableau(10,3.14); //Crée un tableau de 10 double dont la valeur est 3.14
 
for(unsigned int i(0);i<monTableau.size();++i) //On parcourt le tableau
{
    cout << monTableau[i] << " ";   //Et on affiche chacun des éléments séparés par un espace
}


Il existe également une fonction empty() renvoyant true si le tableau est vide et false dans le cas contraire.

Code : C++ - Vide ou non-vide ?
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <vector>
#include <iostream>
using namespace std;
 
vector<int> monTableau;  //Crée un tableau vide pouvant contenir des entiers
 
if(monTableau.empty())  //Si le tableau est vide
{
    cout << "Le tableau est vide" << endl;
}


Modifier la taille d'un tableau



Entrons donc dans le vif du sujet, changer la taille d'un tableau. Il existe plusieurs fonctions permettant de faire cela. Commençons par la plus simple. Il s'agit de push_back(). Cette fonction ajoute une case à la fin du tableau la remplit avec la valeur passée en argument.

Code : C++ - Ajout de cases
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <vector>
using namespace std;
 
vector<int> monTableau;  //Crée un tableau vide pouvant contenir des entiers
 
monTableau.push_back(1); //On ajoute une case à la fin du tableau et on met "1" dans cette case
//A cet endroit du code monTableau.size() vaut 1
 
monTableau.push_back(5); //On ajoute une case à la fin du tableau et on met "5" dans cette case
//A cet endroit du code monTableau.size() vaut 2


Vous devez remplir la case avec un type compatible. Vous ne pouvez pas faire push_back("Salut") sur un vector<int> !


Cette fonction permet donc d'ajouter des éléments en fin de tableau. Si l'on veut supprimer des éléments, il faut faire appel à la fonction "opposée" pop_back(), qui, elle, supprime la dernière case du tableau.

Code : C++ - Pop pop pop...
1
2
3
4
5
6
7
8
9
#include <vector>
using namespace std;
 
vector<int> monTableau(2,2);  //Crée un tableau de deux entiers dont la valeur est 2
 
monTableau.pop_back();
monTableau.pop_back();
 
//A cet endroit du code, monTableau.size() vaut 0


Super non ? Il n'y a pas besoin de se pré-occuper de réallocation de mémoire, de copie et de plein d'autres problèmes qui pourraient survenir. La classe vector s'occupe de tout. C'est la magie de la POO !

Et si je veux modifier plus que une case à la fois ?


Pfff, vous êtes jamais satisfaits avec ce qu'on vous donne. Bon puisque c'est vous, je vous explique.
Il y a plusieurs manières de faire cela selon ce que vous voulez faire. La fonction clear(), vide entièrement un tableau. Ce qui donne par exemple :


Code : C++ - Vidange
1
2
3
4
5
6
7
8
#include <vector>
using namespace std;
 
vector<int> monTableau(2,2);  //Crée un tableau de deux entiers dont la valeur est 2
 
monTableau.clear(); //Vide entièrement le tableau
 
//A cet endroit du code, monTableau.size() vaut 0


Il existe également la fonction assign() qui change le contenu du tableau avec ce que vous lui passez en argument.

Code : C++ - La fonction assign()
1
2
3
4
5
6
#include <vector>
using namespace std;
 
vector<int> monTableau(2,2);  //Crée un tableau de deux entiers dont la valeur est 2
 
monTableau.assign(5,3); //Vide entièrement le tableau et remplace son contenu par 5 entiers dont la valeur est 3


Vous remarquerez que cette fonction change la taille et le contenu du vector


Si vous voulez agrandir le tableau tout en conservant ce qu'il y a dedans, vous pouvez utilisez la fonction resize()

Code : C++ - Redimensionnement
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <vector>
using namespace std;
 
vector<int> monTableau(2,2);  //Crée un tableau de deux entiers dont la valeur est 2
 
monTableau.resize(5,3); //Ajoute assez de cases au tableau pour que la taille soit 5
                        //Et remplit les cases ajoutées avec la valeur 3
 
//   A ce moment là, le tableau est donc:
//   |2|2|3|3|3|


Si par contre vous appelez la fonction resize() avec comme premier argument un nombre plus petit que la taille actuelle de votre tableau, le tableau sera réduit jusqu'à la taille voulue. Le deuxième argument n'est pas utilisé dans ce cas. Il est d'ailleurs inutile

Code : C++ - Redimensionnement
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <vector>
using namespace std;
 
vector<int> monTableau(5,2);  //Crée un tableau de 5 entiers dont la valeur est 2
 
monTableau.resize(4,3); //Redimensionne le tableau pour qu'il fasse 4 cases
 
//   A ce moment là, le tableau est donc:
//   |2|2|2|2|

monTableau.resize(2); //Redimensionne le tableau pour qu'il fasse 2 cases

//   A ce moment là, le tableau est donc:
//   |2|2|


Voilà, vous savez utiliser un std::vector. Je vous ai pas menti, c'était facile, non ? :p

La dernière partie est consacrée à quelques notions plus avancées.

Notions plus avancées

Passage d'un tableau à une fonction



Les std::vector étant des objets comme les autres, il est possible de les passer en argument à une fonction. Le prototype d'une telle fonction serait par exemple :

Code : C++ - Recevoir un tableau
1
2
3
#include <vector>
 
void f(std::vector<int> tableau);


Vous remarquerez, qu'on a pas besoin de passer la taille comme deuxième argument comme dans le cas des tableaux statiques, puisqu'on peut accéder à cette information via la fonction membre size().


Cette manière de faire est cependant une mauvaise idée. En effet, en procédant de la sorte, votre tableau est recopié entièrement lors du passage à la fonction. Il y a donc une perte de temps (qui peut être importante si le tableau est grand) et surtout une duplication de l'information inutile. La bonne solution consiste à utiliser une référence ou mieux encore, une référence constante si vous ne comptez pas modifier le tableau dans la fonction.

Code : C++ - Recevoir un tableau de façon optimale
1
2
3
4
5
#include <vector>
 
void f(std::vector<int>& tableau);   //Bien
 
void g(const std::vector<int>& tableau); //Encore mieux


Passer des arguemnts par référence constante est une bonne habitude à prendre en C++.


Vous remarquerez également qu'on est obligé de spécifier le type contenu dans le vector. Comme dans le cas des tableaux statiques en fait.

Renvoyer un tableau



Comme les tableaux dynamiques sont des objets comme les autres, il est tout à fait possible d'écrire une fonction renvoyant un vector, chose qui n'est pas faisable proprement avec un tableau statique.

Code : C++ - Renvoyer un tableau
1
2
3
#include <vector>
 
std::vector<double> f();


A nouveau, c'est très simple. Je ne vais donc pas m'éterniser là-dessus.

Les types contenus



J'ai dit plus haut, qu'un tableau dynamique pouvait contenir n'importe quel type d'objet. Vous pouvez donc tout-à-fait créer des vector contenant des choses plus complexes telles que :

Code : C++ - Types autorisés
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <vector>
 
std::vector<int*> vector1;   //Un tableau de pointeurs sur des entiers
 
std::vector<char**> vector2; //Un tableau de pointeurs sur des pointeurs sur char
 
std::vector<const double> vector3; //Un tableau de nombres réels constants
 
std::vector<int* const> vector4;   //Un tableau de pointeurs constants sur des entiers
 
std::vector<const int*> vector5;   //Un tableau de pointeurs sur des entiers constants

std::vector<Personnage*> Armee;   //Un tableau de pointeur sur des personnages


Il y a cependant une petite contrainte : Le type contenu doit être copiable (donc posséder un constructeur de copie), affectable (donc posséder un opérateur =) et comme on l'a vu avant posséder un constructeur par défaut si l'on veut initialiser le tableau en spécifiant uniquement la taille. Il est rare qu'un type ne remplisse pas ces contraintes, il ne faut donc pas réelement s'en préoccuper.

Une erreur classique est de vouloir faire un tableau de références sur un type. Ceci n'a pas de sens au vu de la remarque précédente et sera donc équivalent à un tableau sur le type qu'on veut référencer.


Créer un tableau multi-dimensionnel



Il arrive souvent qu'on ait besoin de tableaux multi-dimensionnels, par exemple pour représenter une carte dans un jeu ou pour stocker des informations comme dans une base de donnée. Réaliser cela à l'aide des vector est très simple. Je vous ai dit qu'on pouvait mettre n'importe quoi dans ces tableaux; on peut donc, en particulier, mettre ... un autre vector dedans ! La syntaxe est la suivante :

Code : C++ - Déclaration d'un tableau multi-dimensionnel
1
2
3
#include <vector>
 
std::vector< std::vector< int > > monTableau2D; //Déclaration d'un tableau 2D contenant des entiers


Vous remarquerez qu'il y a un espace entre les deux >. Il est important pour montrer au compilateur qu'il ne s'agit pas de l'opérateur >> !


En utilisant le même principe, vous pouvez créer des tableaux d'autant de dimensions que vous voulez. La première question qu'on se pose, est de savoir comment initialiser un tel tableau. La syntaxe est la suivante :

Code : C++ - Initialisation d'un tableau 2D
1
2
3
4
5
6
#include <vector>
 
std::vector< std::vector< int > > monTableau(5,std::vector<int> (5,1));
// On construit un tableau de tableau d'entiers dans lequel on met
// 5 tableaux d'entiers eux-même constitués de 5 entiers dont
// la valeur est 1


Pour accéder aux éléments d'un tel tableau, il faut à nouveau faire appel aux []. Il faut alors penser que les [] appliqués au tableau principal donnera accès au vector qu'il contient et pas à un élément. Il faut utiliser deux fois les [] pour accéder à un élément précis.

Code : C++ - Accès aux éléments d'un tableau 2D
1
2
3
4
5
6
#include <vector>
 
std::vector< std::vector< int > > monTableau(5,std::vector<int> (5,1));
 
monTableau[0] = ... ; //Modifie la première ligne du tableau
monTableau[0][0] = 2; //Modifie l'element d'indice (0,0) du tableau


Il y a cependant une chose à laquelle vous devez faire attention. C'est un tableau de tableau et pas un tableau 2D. Cela veut dire que la structure ainsi créée n'est pas forcément recangulaire. Chacun des vector contenu dans le vector principal peut avoir une taille différente ! Voyons cela avec un exemple.

Code : C++ - Tableaux 2D
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <vector>
#include <iostream>
using namespace std;
 
int main()
{
  vector<vector< int > > monTableau;  //On cree un vector 2D                    
 
  monTableau.resize(5,vector<int>(2,1));
  //On redimensionne le tableau pour qu'il ait 5 lignes                         
  //de 2 colonnes remplies de 1                                                 
 
  monTableau[1].resize(4,2);
  //On redimensionne la deuxieme ligne du tableau de sorte                      
  //qu'elle soit de longueur 4 et contienne des 2 dans les                      
  // nouvelles cases                                                            
 
  monTableau[2].resize(1);
  //On diminue la taille de la deuxieme ligne                                   
 
  monTableau[3].clear();
  //On vide la 4eme ligne du tableau                                            
 
  //On affiche le tableau                                                       
  for(unsigned int i(0);i<monTableau.size();++i){
    for(unsigned int j(0);j<monTableau[i].size();++j)
      {
        cout << monTableau[i][j] << " , ";
      }
    cout << endl;
  }
 
  return 0;
}


Ce code produit le résultat suivant :

Code : Console
1 , 1 , 
1 , 1 , 2 , 2 , 
1 , 
 
1 , 1 ,


On voit donc clairement que le tableau n'est pas rectangulaire !

On a vu comment accéder à une ligne. Mais si je veux accéder à une colonne, je fais comment ?


Ce n'est pas possible de manière simple. Les vector ne sont pas fait pour. Il vous faudra donc écrire une fonction qui le fait. C'est d'ailleurs un exercice intéressant. Essayez d'écrire le corps de la fonction suivante :

Code : C++ - Exercice
1
2
3
4
std::vector<int> getColonne(const std::vector<std::vector<int> >& Tableau , unsigned int n)
{
    //Renvoit la colonne n du Tableau si elle existe
}


Il est facile de créer un tableau multi-dimensionnel. Cependant, ce n'est pas le moyen le plus efficace de créer un type "Matrice". Pour cela, on utilisera plutôt des std::valarray ou d'autres structures venant de Boost par exemple.

Q.C.M.

Quelle déclaration crée un tableau de 10 entiers dont la valeur est 3 ?
Comment trouve-t'on la taille du tableau suivant ?


Code : C++
1
std::vector<double> monTableau(10);


Comment ajouter des éléments à la fin d'un vector ?
Comment faire pour vider un vector ?
Que contient le vector à la fin du code suivant ?


Code : C++
1
2
3
4
5
std::vector<int> tableau(10,2);
tableau.resize(2);
tableau.push_back(8);
tableau.resize(6,3);
tableau.pop_back()


A partir de maintenant, je ne veux plus voir personne créer des tableaux à partir d'un pointeur en allouant la mémoire avec malloc ou new[]. :pirate:

Bon, il est maintenant temps de refermer la parenthèse pour repartir vers les contrées sauvages de la POO.
Sommaire
Auteur : Nanoc
Noter et commenter ce tutoriel
Imprimer ce tutoriel

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | Fil RSS | XHTML 1.0 | CSS 2.0
Édité par Simple IT SARL : Nous contacter | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 279 Zéros connectés | Requêtes SQL 10 requêtes | Temps de génération de la page : Total (SQL) 0.1019s (0.089s)