Larithmétique des pointeurs rend leur utilisation si pratique quon sen sert souvent dans des situations où, dans dautres langages, on aurait utilisé des tableaux.
Cest déjà le cas avec les chaînes de caractères utilisées sous le type char*
. Mais la remarque sapplique à tous les tableaux, et plus particulièrement à ceux qui sont placés en mémoire dynamique (voir dans le chapitre 5, sur ce sujet), ou dont la taille nest pas connue à lavance, comme par exemple dans les appels de fonctions. Ainsi, la procédure affiche_tableau
, vue au paragraphe sur les tableaux, pourrait être réécrite ainsi :
void affiche_tableau(unsigned n, int *tab) { for (int *fin = tab +n; tab < fin; tab++) cout << *tab << '\t'; }
On notera que lincrémentation de tab nest pas fautive et ne provoque pas deffet de bord (voir chapitre 5). On remarquera aussi que le pointeur initialisé dans la boucle for
nest pas celui qui est incrémenté : mais cela est parfaitement permis.
Comme les tableaux sont en fait des pointeurs sur leur premier élément, on peut encore écrire :
int tableau[20]; affiche_tableau(20, tableau);
tout comme avec lancienne version.
La question qui vient naturellement à lesprit est la suivante : y a-t-il gain ou perte par rapport à lancienne version (qui utilisait un entier dindex i
et affichait les tableau[i]
successifs) ? Bien qua priori on puisse penser que ces deux implémentations sont équivalentes, il nen est rien en réalité, il y a un gain.
En effet, lorsquon écrit :
cout << tableau[i];
la machine fait deux opérations : connaissant ladresse de tableau et i , elle multiplie le second par la taille des entiers et ajoute le résultat à tableau (pointeur sur lélément 0
), obtenant ainsi une adresse qui est celle de lélément dindice i. Lécriture précédente est donc équivalente à :
cout << *(int*)((char*)tableau + i * sizeof(int));
ce qui est évidemment plus complexe (donc plus lent) que :
cout << *tab;
De telles considérations justifient souvent lemploi de pointeurs à la place des tableaux, en particulier pour les opérations que lon souhaite rapides, comme celles sur les chaînes de caractères par exemple.
Reste à savoir comment lon définit des tableaux multidimensionnels de cette façon. Cest déjà moins pratique ; il existe deux possibilités. Soit on considère quun tableau multidimensionnel est un tableau unidimensionnel dont on numérote spécialement les éléments, auquel cas on utilise un pointeur simple. Par exemple les instructions suivantes, qui écrivent entièrement une matrice de 3*5 entiers :
int tableau[3][5] = ... // initialiser... int i = 0, j = 0; while (i < 3) { cout << tableau[i][j] << (j < 4 ? '\t' : '\n'); if (++j == 5) { i++; j = 0; } }
deviennent :
int *tab = ... // initialiser... int *fin = tab + 3*5; for (int *t = tab; t < fin; t++) cout << *t << ( (t -tab+1)%5 ? '\t' : '\n');
On notera que certaines opérations comme ici le choix entre tabulation et fin de ligne sont assez difficiles. En outre laccès à un élément particulier du tableau est peu pratique, puisquil faut écrire tab +i*5 +j
là où tableau[i][j]
suffisait précédemment. En particulier, il est nécessaire de connaître la largeur du tableau (5).
La deuxième approche consiste à dire que le tableau multidimensionnel est un tableau de tableaux. On peut donc le remplacer par un pointeur sur un pointeur :
int **tbp = ... // initialiser... int **fin = tbp +3; for (int **tp = tbp; tp < tbp; tp++) { int *stop = *tp +4; for (int *p = *tp; p <= stop; p++) cout << *p << (p < stop ? '\t' : '\n'); }
Cette solution a des avantages et des inconvénients. En général, linitialisation (omise ici) est plus lourde, car il faut attribuer la place au pointeur de pointeurs, puis à chaque pointeur séparément. En contrepartie, laccès à des éléments fixes peut être plus facile. Cette structure permet en outre une utilisation partielle seulement, cest-à-dire que certains pointeurs peuvent rester à zéro ; cela fait gagner de la mémoire. Un usage pratique, lorsquil est secondé par la POO.
Précédent | Sommaire | Suivant |