[Plan du site]
Vous êtes ici ---
> Le Site du Zér0
> Les tutoriels
> Lecture du tutoriel
Les tableaux dynamiques
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.
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 C1
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 statique1
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.
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
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 nom1
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 vector1
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ée1
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éments1
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.
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 tableau1
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++ - Vidange1
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 ?
La dernière partie est consacrée à quelques 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 tableau1
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 optimale1
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 tableau1
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-dimensionnel1
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 2D1
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 2D1
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++ - Exercice1
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.
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[].
Bon, il est maintenant temps de refermer la parenthèse pour repartir vers les contrées sauvages de la POO.