Les tableaux

Pointeurs & Tableaux

Introduction

Jusqu'ici, chaque variable stocke une seule valeur. Mais que faire si tu veux stocker 10 notes, 100 mesures, ou les lettres d'un texte ? Déclarer int note1, note2, note3, ... serait ingérable.

Un tableau c'est une suite de variables du même type, stockées côte à côte en mémoire, accessibles par un numéro (l'index). Au lieu de 10 variables séparées, tu déclares un seul tableau de 10 cases.

Déclarer un tableau

C
1int tab[5];

Ça crée 5 int côte à côte en mémoire. Chaque case est accessible par son index.

En mémoire

tab :  ┌──────┬──────┬──────┬──────┬──────┐
       │  ?   │  ?   │  ?   │  ?   │  ?   │
       └──────┴──────┴──────┴──────┴──────┘
index:   [0]    [1]    [2]    [3]    [4]

Les 5 cases sont consécutives — pas de trou entre elles. Chaque case fait sizeof(int) = 4 octets. Le tableau entier occupe 5 × 4 = 20 octets.

La taille doit être constante

La taille du tableau doit être connue au moment de la compilation — pas une variable :

C
1int tab[5]; // OK — 5 est une constante
2int tab[size]; // INTERDIT à 42 (VLA)

Pour des tailles dynamiques, il faudra malloc (cours sur malloc et free).

Sans initialisation

C
1int tab[5];
2// Le contenu de tab[0], tab[1], etc. est du GARBAGE

Comme pour les variables simples, un tableau non initialisé contient des valeurs aléatoires.

Accéder aux éléments

Lire et écrire

C
1int tab[5];
2
3tab[0] = 10; // Écrire dans la première case
4tab[1] = 20;
5tab[4] = 50; // La dernière case (index 4, pas 5)
6
7int val;
8
9val = tab[0]; // Lire la première case → val = 10

Parcourir avec une boucle

C'est le pattern le plus courant en C :

C
1int tab[5];
2int i;
3
4i = 0;
5while (i < 5)
6{
7 tab[i] = i * 10; // tab = {0, 10, 20, 30, 40}
8 i++;
9}

Pour lire :

C
1i = 0;
2while (i < 5)
3{
4 // faire quelque chose avec tab[i]
5 i++;
6}

Tableaux et pointeurs — La relation

Le nom du tableau est une adresse

En C, le nom d'un tableau est automatiquement converti en un pointeur vers son premier élément :

C
1int tab[5];
2int *p;
3
4p = tab; // p pointe vers tab[0]

tab[i] est un raccourci

L'expression tab[i] est en réalité *(tab + i) — "prends l'adresse de tab, avance de i éléments, et lis la valeur". Le compilateur fait ce calcul automatiquement.

C'est pour ça que les index commencent à 0 : tab[0] = *(tab + 0) = la valeur à l'adresse du début du tableau, sans avancer.

Passer un tableau à une fonction

Quand tu passes un tableau à une fonction, tu passes un pointeur. La fonction peut modifier le tableau original :

C
1void ft_remplir(int *tab, int taille, int valeur)
2{
3 int i;
4
5 i = 0;
6 while (i < taille)
7 {
8 tab[i] = valeur;
9 i++;
10 }
11}
12
13int main(void)
14{
15 int notes[5];
16
17 ft_remplir(notes, 5, 0);
18 // notes = {0, 0, 0, 0, 0}
19 return (0);
20}

Deux points importants :

Les opérations courantes sur les tableaux

Trouver le maximum

Le principe : prendre le premier élément comme maximum provisoire, puis comparer chaque élément suivant. Si un élément est plus grand, il devient le nouveau maximum.

Inverser un tableau

Le principe : échanger les éléments symétriques — le premier avec le dernier, le deuxième avec l'avant-dernier, etc. On s'arrête au milieu.

Avant :  [10, 20, 30, 40, 50]
         swap(10, 50) → [50, 20, 30, 40, 10]
         swap(20, 40) → [50, 40, 30, 20, 10]
         milieu atteint → fin
Après :  [50, 40, 30, 20, 10]

Trier un tableau

Le tri le plus simple c'est le tri par sélection : pour chaque position, chercher le plus petit élément dans le reste du tableau et l'échanger avec la position actuelle. Deux boucles imbriquées.

[50, 30, 10, 40, 20]
Position 0 : min dans tout le tableau = 10 → swap avec 50 → [10, 30, 50, 40, 20]
Position 1 : min dans le reste = 20 → swap avec 30 → [10, 20, 50, 40, 30]
Position 2 : min dans le reste = 30 → swap avec 50 → [10, 20, 30, 40, 50]
Position 3 : min dans le reste = 40 → pas de swap → [10, 20, 30, 40, 50]

Les limites des tableaux

Pas de vérification des bornes

Le C ne vérifie jamais que ton index est dans les limites :

C
1int tab[5];
2
3tab[5] = 99; // HORS LIMITES — le 6ème élément n'existe pas
4tab[100] = 99; // HORS LIMITES — très loin
5tab[-1] = 99; // HORS LIMITES — avant le début

Le programme ne crashe pas forcément — c'est pire. Il peut :

  • Écraser d'autres variables silencieusement
  • Crasher plus tard à un endroit inattendu
  • Sembler fonctionner (le bug le plus dangereux)

C'est un comportement indéfini (le programme peut faire n'importe quoi). C'est ta responsabilité de rester dans les bornes.

Pas de copie par affectation

C
1int a[5];
2int b[5];
3
4b = a; // ERREUR de compilation

Pour copier un tableau, il faut copier élément par élément avec une boucle.

Pas de retour de tableau local

C
1int *mauvais(void)
2{
3 int tab[5];
4
5 return (tab); // DANGER — tab est détruit quand la fonction se termine
6}

Le tableau local vit dans la mémoire de la fonction. Quand elle se termine, cette mémoire est libérée. Le pointeur retourné pointe vers du vide. Pour retourner un tableau, il faudra malloc (cours sur malloc et free).

Questionnaire

Vérifie tes acquis — sélectionne la bonne réponse pour chaque question

1Quel est l'index du dernier élément d'un tableau int tab[10] ?
2Que vaut tab (le nom du tableau) quand il est utilisé dans une expression ?
3Pourquoi faut-il toujours passer la taille du tableau en paramètre ?
4Que se passe-t-il si tu accèdes à tab[10] dans un tableau int tab[5] ?
5int *tab et int tab[] dans les paramètres d'une fonction — quelle différence ?
6Pour inverser un tableau, pourquoi s'arrêter au milieu ?
7Pourquoi int b[5]; b = a; ne compile pas ?
8Un tableau int tab[5] occupe combien d'octets en mémoire ?
9Peut-on utiliser une variable comme taille de tableau à 42 ?
10Pourquoi ne pas retourner un tableau local depuis une fonction ?