Un tableau multidimensionnel est un tableau dont les éléments sont eux-mêmes des tableaux. int grid[3][4]; n'est pas un type grille spécial - ce sont trois éléments, chacun étant un tableau de quatre entiers, stockés bout à bout. Une fois cela compris, le reste du sujet en découle : la disposition, l'arithmétique d'indexation, et la règle sinon déroutante sur leur passage aux fonctions.
Deux dimensions couvrent presque tous les usages pratiques - grilles, tables, matrices, plateaux de jeu, images - c'est donc avec cela que travaille cette page.
Déclarer et initialiser
int grid[3][4]; // 3 lignes, 4 colonnes - 12 int
Le premier nombre est le nombre de lignes, le second celui des colonnes. Les initialiseurs peuvent s'écrire à plat ou avec des accolades internes ; les accolades valent la peine car elles montrent la forme.
La forme int e[][3] compte : vous pouvez laisser le nombre de lignes vide et laisser l'initialiseur décider, mais le nombre de colonnes n'est jamais facultatif. La section suivante explique pourquoi.
La disposition par lignes
Le C stocke un tableau 2D en ordre par lignes : toute la ligne 0, puis toute la ligne 1, et ainsi de suite, dans un bloc de mémoire ininterrompu. Il n'y a pas de tableau de pointeurs de lignes en coulisses.
int grid[3][4] = {{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9,10,11,12}};
comment vous l'imaginez comment il est vraiment en memoire
+----+----+----+----+
| 1 | 2 | 3 | 4 | ligne 0 +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+ | 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|
| 5 | 6 | 7 | 8 | ligne 1 +--+--+--+--+--+--+--+--+--+--+--+--+
+----+----+----+----+ \_ ligne 0 _/\_ ligne 1 _/\_ ligne 2 _/
| 9 | 10 | 11 | 12 | ligne 2
+----+----+----+----+ grid[i][j] est a l'indice i*4 + j
Cette formule, i * colonnes + j, est tout le mécanisme - et c'est pourquoi le compilateur doit connaître le nombre de colonnes pour indexer quoi que ce soit. Le nombre de lignes n'entre jamais dans le calcul.
Vous pouvez voir la disposition directement en affichant les adresses :
Les adresses montent de sizeof(int) sans aucun trou, y compris là où une ligne se termine et où la suivante commence. La boucle aplatie le prouve - flat[k] parcourt les douze éléments comme une seule suite.
Cette disposition a aussi une conséquence de performance à connaître : boucler lignes-puis-colonnes touche la mémoire dans l'ordre, ce que le cache du processeur apprécie. Inverser l'imbrication pour que la boucle interne descende une colonne saute d'une ligne entière à chaque fois et peut tourner plusieurs fois plus lentement sur un grand tableau.
Les boucles imbriquées
Deux dimensions veulent deux boucles for : l'externe choisit la ligne, l'interne balaie les colonnes de cette ligne.
Nommez les compteurs d'après ce qu'ils signifient (i/row pour les lignes, j/col pour les colonnes) et gardez l'ordre cohérent - grid[row][col] partout. La moitié des bugs de tableaux 2D est une paire d'indices transposée.
Les tailles en #define ne sont pas décoratives non plus : les bornes de boucle et la déclaration ne peuvent plus diverger quand vous changez la forme.
Passer un tableau 2D à une fonction
Voici la règle qui fait trébucher tout le monde : le paramètre de la fonction doit déclarer le nombre de colonnes.
La raison est la dégradation. Passer grid le convertit en pointeur sur son premier élément - et ses éléments sont des lignes, donc le type est int (*)[4] : pointeur sur un tableau de 4 int. Pour que grid[i][j] signifie quelque chose, le compilateur doit savoir quelle distance couvre une ligne, et c'est ce 4. Le nombre de lignes est réellement absent du type, c'est pourquoi il voyage en argument séparé.
Notez que int (*grid)[COLS] et int grid[][COLS] sont le même paramètre écrit de deux façons - les parenthèses sont obligatoires, puisque int *grid[COLS] serait plutôt un tableau de pointeurs. Cette distinction est couverte dans pointeurs et tableaux.
Si le nombre de colonnes n'est connu qu'à l'exécution, les paramètres à modification variable de C99 vous permettent de le passer en premier :
void print_any(int rows, int cols, int grid[rows][cols]);
rows et cols doivent être déclarés avant le paramètre tableau qui les utilise. Là où ce n'est pas disponible, l'alternative courante est un tableau 1D plat plus une arithmétique d'indices manuelle :
data[i * cols + j] est exactement ce que le compilateur écrit pour vous dans le cas à taille fixe. Le faire à la main coûte une ligne et fonctionne pour n'importe quelle forme décidée à l'exécution.
Un exemple matriciel
La multiplication de matrices réunit toute la page - trois boucles imbriquées sur un stockage par lignes.
Deux détails à copier. La boucle interne k apparie a[i][k] avec b[k][j] - un indice parcourt une ligne, l'autre une colonne. Et la transposition démarre sa boucle interne à j = i + 1 : partir de 0 échangerait chaque paire deux fois et laisserait la matrice inchangée.
Trois dimensions et au-delà
Le motif s'étend, et la règle sur les paramètres de fonction aussi - toutes les dimensions sauf la première doivent être déclarées.
En pratique, les trois dimensions sont l'endroit où les tableaux de taille fixe commencent à devenir encombrants, et la plupart du code bascule vers un bloc plat avec indices calculés ou un tableau de structures qui nomme ce que chaque axe signifie.
Erreurs courantes
- Écrire
grid[i, j]. L'opérateur virgule évaluei, le jette, et indexe avecj. Cela compile. C'est faux. Utilisezgrid[i][j]. - Transposer les indices.
grid[col][row]lit un vrai élément au mauvais endroit, il n'y a donc aucune erreur pour l'attraper. Gardez l'ordre[row][col]partout. - Omettre la taille des colonnes dans un paramètre.
void f(int grid[][])ne compile pas, et c'est le compilateur qui vous sauve. - Sortir des bornes. Comme pour tout tableau, il n'y a pas de vérification de bornes.
grid[0][5]sur une grille[3][4]lit silencieusementgrid[1][1], car la disposition est contiguë et l'arithmétique s'en moque.
Questions fréquentes
Comment déclare-t-on un tableau 2D en C ?
Donnez deux tailles entre crochets : int grid[3][4]; déclare 3 lignes de 4 colonnes - 12 entiers au total. Lisez-le comme « un tableau de 3 choses, chacune étant un tableau de 4 int », ce qui est littéralement la façon dont le C le stocke.
Comment un tableau 2D est-il stocké en mémoire en C ?
Dans l'ordre par lignes (row-major) : tous les éléments de la ligne 0, puis tous ceux de la ligne 1, et ainsi de suite, dans un bloc contigu. grid[i][j] se trouve au décalage i * colonnes + j éléments depuis le début, c'est pourquoi le nombre de colonnes est le nombre dont le compilateur a besoin.
Comment passe-t-on un tableau 2D à une fonction en C ?
Le paramètre doit déclarer le nombre de colonnes : void print(int grid[][4], int rows) ou, de façon équivalente, void print(int (*grid)[4], int rows). Le nombre de lignes peut être omis car le tableau se dégrade en pointeur sur une ligne - mais sans la taille des colonnes, le compilateur ne peut pas calculer où commence une ligne.
Peut-on initialiser un tableau 2D entièrement à zéro ?
Oui : int grid[3][4] = {0}; met chaque élément à zéro, car tout élément non listé est initialisé à zéro. int grid[3][4] = {{1, 2}}; fixe les deux premières entrées de la ligne 0 et laisse les dix autres à zéro.