Allocation dynamique de tableaux multidimensionnels
L'utilisation de doubles pointeurs (pointeurs de pointeurs) est indispensable pour créer des structures de données dont les dimensions ne sont connues qu'à l'exécution. Un double pointeur peut représenter un tableau de pointeurs, où chaque élément pointe vers un segment de mémoire distinct représentant une ligne.
int **creer_matrice(int nb_lignes, int nb_colonnes) {
int **matrice = malloc(nb_lignes * sizeof(int *));
for (int i = 0; i < nb_lignes; i++) {
matrice[i] = malloc(nb_colonnes * sizeof(int));
}
return matrice;
}
void liberer_matrice(int **matrice, int nb_lignes) {
for (int i = 0; i < nb_lignes; i++) {
free(matrice[i]);
}
free(matrice);
}
Modification d'un pointeur au sein d'une fonction
En C, les arguments sont passés par valeur. Pour modifier l'adresse contenue dans un pointeur à l'intérieur d'une fonction (et que ce changement soit répercuté à l'appelant), il est nécessaire de passer l'adresse du pointeur, ce qui implique l'usage d'un double pointeur.
void assigner_nouvelle_adresse(int **ptr_cible, int *nouvelle_zone) {
*ptr_cible = nouvelle_zone;
}
int main() {
int val_a = 100, val_b = 200;
int *selecteur = &val_a;
// Passage de l'adresse du pointeur
assigner_nouvelle_adresse(&selecteur, &val_b);
// Affiche maintenant 200
printf("Valeur pointée : %d\n", *selecteur);
return 0;
}
Manipulation de listes chaînées sans retour de fonction
L'insertion d'un nœud en tête de liste modifie la racine de la structure. L'utilisation d'un double pointeur permet de mettre à jour le pointeur de tête directement sans avoir à retourner la nouvelle adresse.
typedef struct Element {
int valeur;
struct Element *suivant;
} Element;
void inserer_debut(Element **racine, int donnee) {
Element *nouveau = malloc(sizeof(Element));
if (nouveau) {
nouveau->valeur = donnee;
nouveau->suivant = *racine;
*racine = nouveau;
}
}
int main() {
Element *ma_liste = NULL;
inserer_debut(&ma_liste, 50);
inserer_debut(&ma_liste, 100);
Element *iterateur = ma_liste;
while (iterateur != NULL) {
printf("[%d] -> ", iterateur->valeur);
iterateur = iterateur->suivant;
}
printf("NULL\n");
return 0;
}
Tableaux de pointeurs vs Pointeurs de tableaux
Il est crucial de distinguer un tableau de pointeurs d'un pointeur pointant sur un tableau. Un tableau de pointeurs stocke plusieurs adresses indépendantes, tandis qu'un pointeur de tableau pointe sur un bloc mémoire contigu de taille fixe.
void demonstration_types() {
// Tableau de 3 pointeurs vers des entiers
int *tab_ptr[3];
int x = 1, y = 2, z = 3;
tab_ptr[0] = &x;
tab_ptr[1] = &y;
tab_ptr[2] = &z;
// Pointeur vers un tableau de 3 entiers
int bloc[3] = {10, 20, 30};
int (*ptr_sur_tableau)[3] = &bloc;
printf("Premier élément via pointeur de tableau : %d\n", (*ptr_sur_tableau)[0]);
}
Transmission de tableaux 2D dynamiques à des fonctions
Pour passer une matrice entièrement dynamique à une fonction, on utilise le double pointeur ainsi que les dimensions en paramètres pour permettre l'itération correcte sur les éléments.
void afficher_grille_dynamique(int h, int l, int **grille) {
for (int i = 0; i < h; i++) {
for (int j = 0; j < l; j++) {
printf("%02d ", grille[i][j]);
}
printf("\n");
}
}
int main() {
int lignes = 2, cols = 5;
int **data = malloc(lignes * sizeof(int *));
for (int i = 0; i < lignes; i++) {
data[i] = malloc(cols * sizeof(int));
for (int j = 0; j < cols; j++) {
data[i][j] = i + j;
}
}
afficher_grille_dynamique(lignes, cols, data);
// Nettoyage
for (int i = 0; i < lignes; i++) free(data[i]);
free(data);
return 0;
}