Langage C/C++

Pointeur de pointeur en c – Guide Détaillé

iIntroduction aux Pointeurs en C :

Les pointeurs sont des variables qui stockent l’adresse mémoire d’une autre variable. Ils permettent un accès indirect à la mémoire, offrant ainsi une grande flexibilité dans la manipulation des données en C. Un pointeur de pointeur, comme son nom l’indique, est un pointeur qui pointe vers un autre pointeur.

Déclaration de Pointeurs de Pointeurs :

En C, la déclaration d’un pointeur de pointeur nécessite deux niveaux d’indirection. Voici comment vous pouvez déclarer un pointeur de pointeur :

int **ptr_ptr;

Ici, ptr_ptr est un pointeur de pointeur vers un entier.

Initialisation de Pointeurs de Pointeurs :

Pour initialiser un pointeur de pointeur, vous devez d’abord allouer de la mémoire pour le pointeur interne, puis assigner son adresse au pointeur de pointeur. Voici un exemple :

int *ptr;
int **ptr_ptr;
int num = 10;

ptr = #
ptr_ptr = &ptr;

Maintenant, ptr_ptr pointe vers ptr, qui lui-même pointe vers num.

Accéder à la Valeur Pointée par un Pointeur de Pointeur :

Pour accéder à la valeur pointée par un pointeur de pointeur, vous devez déréférencer deux fois. Voici comment vous pouvez accéder à la valeur pointée par ptr_ptr :

printf("Valeur de num : %d\n", **ptr_ptr);
Utilisation des Pointeurs de Pointeurs :

Les pointeurs de pointeurs sont souvent utilisés dans des situations où une fonction doit modifier un pointeur passé en argument. Par exemple :

void allocateMemory(int **ptr) {
    *ptr = (int*)malloc(sizeof(int));
}

int main() {
    int *ptr;
    allocateMemory(&ptr);
    *ptr = 20;
    printf("Valeur de la mémoire allouée : %d\n", *ptr);
    free(ptr);
    return 0;
}

Dans ce cas, la fonction allocateMemory alloue de la mémoire pour un entier et affecte l’adresse de cette mémoire à ptr dans la fonction appelante.

Libération de la Mémoire :

N’oubliez pas de libérer la mémoire allouée par les pointeurs de pointeurs pour éviter les fuites de mémoire. Dans l’exemple précédent, nous utilisons free(ptr) pour libérer la mémoire allouée.

Voici quelques ⭐ cas particuliers ⭐ où les pointeurs de pointeurs en C sont utiles, accompagnés de leur code correspondant :

1. Tableaux à deux dimensions :

Les pointeurs de pointeurs sont souvent utilisés pour représenter des tableaux à deux dimensions de manière dynamique. Voici un exemple :

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 3;
    int **matrix;
    matrix = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }
    // Accéder à un élément du tableau à deux dimensions
    printf("Element à la position [1][1] : %d\n", matrix[1][1]);
    // Libération de la mémoire
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
    return 0;
}

Ici, matrix est un pointeur de pointeur qui pointe vers un tableau de pointeurs, chaque pointeur individuel pointant vers un tableau d’entiers.

2. Listes chaînées :

Les pointeurs de pointeurs sont également utilisés dans les listes chaînées pour mettre à jour les liens entre les nœuds. Voici un exemple simple :

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

void insertAtBeginning(struct Node **head_ref, int new_data) {
    struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
    new_node->data = new_data;
    new_node->next = *head_ref;
    *head_ref = new_node;
}

void printList(struct Node *node) {
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->next;
    }
}

int main() {
    struct Node *head = NULL;
    insertAtBeginning(&head, 3);
    insertAtBeginning(&head, 2);
    insertAtBeginning(&head, 1);
    printf("Liste : ");
    printList(head);
    return 0;
}

Dans cet exemple, head est un pointeur de pointeur qui pointe vers le premier nœud de la liste chaînée. Lorsque nous ajoutons un nouvel élément, nous devons mettre à jour head en utilisant un pointeur de pointeur.

Synthèse 😉

Les pointeurs de pointeurs en C offrent une grande flexibilité dans la manipulation des données, ce qui les rend utiles dans de nombreux cas, tels que la gestion de tableaux à deux dimensions, les listes chaînées et d’autres structures de données dynamiques. En comprenant leur utilisation, vous pouvez écrire des programmes C plus efficaces et plus expressifs.

Voici quelques ⭐ exemples d’applications ⭐courantes où les pointeurs de pointeurs en C peuvent être utilisés, accompagnés de leur code correspondant :

1. Allocation dynamique de mémoire pour une matrice :

Les pointeurs de pointeurs sont souvent utilisés pour allouer dynamiquement de la mémoire pour une matrice à deux dimensions. Voici un exemple :

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 3;
    int **matrix;

    // Allocation de mémoire pour les pointeurs de lignes
    matrix = (int **)malloc(rows * sizeof(int *));

    // Allocation de mémoire pour chaque ligne
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
    }

    // Utilisation de la matrice
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }

    // Libération de la mémoire
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

Ici, matrix est un pointeur de pointeur qui pointe vers une matrice à deux dimensions. Nous utilisons deux niveaux d’indirection pour allouer dynamiquement de la mémoire pour chaque ligne de la matrice.

2. Passage de tableau à une fonction :

Les pointeurs de pointeurs sont utiles pour passer un tableau à une fonction en tant qu’argument, car ils permettent à la fonction de modifier le tableau d’origine. Voici un exemple :

#include <stdio.h>

void modifyArray(int **arr, int size) {
    for (int i = 0; i < size; i++) {
        (*arr)[i] *= 2;
    }
}

int main() {
    int size = 5;
    int *arr = (int *)malloc(size * sizeof(int));

    // Initialisation du tableau
    for (int i = 0; i < size; i++) {
        arr[i] = i;
    }

    // Appel de la fonction pour modifier le tableau
    modifyArray(&arr, size);

    // Affichage du tableau modifié
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }

    free(arr);
    return 0;
}

Dans cet exemple, la fonction modifyArray prend un pointeur de pointeur vers un tableau et modifie les éléments du tableau en les multipliant par 2.

3. Manipulation de listes chaînées :

Les pointeurs de pointeurs sont également utilisés dans les listes chaînées pour modifier les liens entre les nœuds. Voici un exemple :

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

void insertAtBeginning(struct Node **head_ref, int new_data) {
    struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
    new_node->data = new_data;
    new_node->next = *head_ref;
    *head_ref = new_node;
}

void printList(struct Node *node) {
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->next;
    }
}

int main() {
    struct Node *head = NULL;

    // Insertion d'éléments au début de la liste
    insertAtBeginning(&head, 3);
    insertAtBeginning(&head, 2);
    insertAtBeginning(&head, 1);

    // Affichage de la liste
    printf("Liste : ");
    printList(head);

    return 0;
}

Dans cet exemple, head est un pointeur de pointeur qui pointe vers le premier nœud de la liste chaînée. Nous utilisons un pointeur de pointeur pour que la fonction insertAtBeginning puisse modifier la tête de la liste chaînée.

Voici deux ⭐ cas avancés ⭐ où les pointeurs de pointeurs en C peuvent être utilisés de manière avancée :

1. Tableaux dynamiques à dimensions multiples :

Les pointeurs de pointeurs peuvent être utilisés pour créer et manipuler des tableaux à dimensions multiples de manière dynamique. Par exemple, un tableau tridimensionnel peut être représenté à l’aide de pointeurs de pointeurs comme suit :

#include <stdio.h>
#include <stdlib.h>

int main() {
    int ***array;
    int i, j, k;
    int dim1 = 2, dim2 = 3, dim3 = 4;

    // Allocation de mémoire pour la première dimension
    array = (int ***)malloc(dim1 * sizeof(int **));
    for (i = 0; i < dim1; i++) {
        // Allocation de mémoire pour la deuxième dimension
        array[i] = (int **)malloc(dim2 * sizeof(int *));
        for (j = 0; j < dim2; j++) {
            // Allocation de mémoire pour la troisième dimension
            array[i][j] = (int *)malloc(dim3 * sizeof(int));
        }
    }

    // Utilisation du tableau
    for (i = 0; i < dim1; i++) {
        for (j = 0; j < dim2; j++) {
            for (k = 0; k < dim3; k++) {
                array[i][j][k] = i * dim2 * dim3 + j * dim3 + k;
            }
        }
    }

    // Libération de la mémoire
    for (i = 0; i < dim1; i++) {
        for (j = 0; j < dim2; j++) {
            free(array[i][j]);
        }
        free(array[i]);
    }
    free(array);

    return 0;
}

Dans cet exemple, array est un pointeur de pointeur de pointeur qui représente un tableau tridimensionnel. Nous utilisons des boucles pour allouer dynamiquement de la mémoire pour chaque dimension du tableau, puis nous utilisons ces pointeurs de pointeurs pour accéder et manipuler les éléments du tableau.

2. Tableaux de fonctions :

Les pointeurs de pointeurs peuvent également être utilisés pour créer des tableaux de pointeurs de fonction, ce qui permet une sélection dynamique des fonctions à appeler. Voici un exemple :

#include <stdio.h>

void func1() {
    printf("Fonction 1\n");
}

void func2() {
    printf("Fonction 2\n");
}

int main() {
    void (*func_ptr[2])(); // Tableau de pointeurs de fonction

    // Initialisation du tableau de pointeurs de fonction
    func_ptr[0] = &func1;
    func_ptr[1] = &func2;

    // Appel des fonctions à l'aide des pointeurs de pointeurs de fonction
    (*func_ptr[0])(); // Appel de func1
    (*func_ptr[1])(); // Appel de func2

    return 0;
}

Dans ce cas, func_ptr est un tableau de pointeurs de fonction qui peut contenir des adresses de fonctions. Nous utilisons des pointeurs de pointeurs de fonction pour stocker et appeler dynamiquement des fonctions à partir du tableau.

Autres articles

Guide : Comment créer un QCM en...
Le QCM en langage C peut être simulé dans un...
Read more
Tableaux en Langage C : Exercices Corrigés
Voici une série d'exercices corrigés sur les tableaux en langage...
Read more
Comment fonctionne la récursion terminale en C...
La récursion terminale en CLa récursion terminale est une forme...
Read more

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *