Langage C/C++

Pointeurs en C – Exercices Corrigés avec Cas Avancés

Ce guide propose des exercices corrigés sur les pointeurs en C, allant des bases aux cas avancés. Il vise à renforcer la compréhension de concepts tels que la manipulation de mémoire, les pointeurs de fonctions, les chaînes de caractères dynamiques et les structures.


Exercice 1 : Déréférencement Basique

Énoncé :

Créez un programme qui utilise un pointeur pour afficher et modifier la valeur d’une variable.

Solution :

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;

    printf("Valeur initiale de a : %d\n", *p);  // Afficher la valeur via le pointeur

    *p = 20;  // Modifier la valeur via le pointeur
    printf("Nouvelle valeur de a : %d\n", a);

    return 0;
}

Points Clés :

  • Un pointeur permet de lire et modifier directement la valeur de la variable à laquelle il est lié.

Exercice 2 : Échanger Deux Variables

Énoncé :

Écrivez une fonction qui échange les valeurs de deux variables en utilisant des pointeurs.

Solution :

#include <stdio.h>

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 5, b = 10;

    printf("Avant échange : a = %d, b = %d\n", a, b);
    swap(&a, &b);  // Passer les adresses
    printf("Après échange : a = %d, b = %d\n", a, b);

    return 0;
}

Points Clés :

  • La fonction utilise les adresses des variables pour modifier leurs valeurs directement.

Exercice 3 : Tableaux et Pointeurs

Énoncé :

Implémentez un programme qui affiche les éléments d’un tableau en utilisant des pointeurs.

Solution :

#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *p = arr;

    for (int i = 0; i < 5; i++) {
        printf("Élément %d : %d\n", i, *(p + i));  // Accès via le pointeur
    }

    return 0;
}

Points Clés :

  • *(p + i) est équivalent à arr[i].

Exercice 4 : Chaînes de Caractères Dynamiques

Énoncé :

Écrivez un programme qui copie une chaîne de caractères dans une mémoire allouée dynamiquement.

Solution :

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

int main() {
    char source[] = "Pointeurs en C";
    char *dest = (char *)malloc((strlen(source) + 1) * sizeof(char));

    if (dest == NULL) {
        printf("Erreur d'allocation mémoire\n");
        return 1;
    }

    strcpy(dest, source);
    printf("Chaîne copiée : %s\n", dest);

    free(dest);  // Libérer la mémoire
    return 0;
}

Points Clés :

  • malloc est utilisé pour allouer suffisamment de mémoire.
  • N’oubliez jamais de libérer la mémoire allouée avec free.

Exercice 5 : Tableaux 2D Dynamiques

Énoncé :

Allouez dynamiquement un tableau 2D, assignez des valeurs, puis affichez-les.

Solution :

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

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

    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
    }

    // Remplir le tableau
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }

    // Afficher le tableau
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

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

    return 0;
}

Points Clés :

  • Les tableaux 2D nécessitent une double allocation dynamique.
  • Chaque ligne doit être libérée individuellement avant de libérer le tableau principal.

Exercice 6 : Pointeurs de Fonction

Énoncé :

Implémentez un programme qui utilise un pointeur de fonction pour appeler dynamiquement des fonctions.

Solution :

#include <stdio.h>

void add(int a, int b) {
    printf("Addition : %d\n", a + b);
}

void multiply(int a, int b) {
    printf("Multiplication : %d\n", a * b);
}

int main() {
    void (*operation)(int, int);

    operation = add;
    operation(5, 3);  // Appeler add via le pointeur

    operation = multiply;
    operation(5, 3);  // Appeler multiply via le pointeur

    return 0;
}

Points Clés :

  • Un pointeur de fonction peut stocker l’adresse de différentes fonctions ayant la même signature.

Exercice 7 : Pointeurs Dangling

Énoncé :

Montrez comment éviter un pointeur dangling lors de l’utilisation de free.

Solution :

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

int main() {
    int *p = (int *)malloc(sizeof(int));
    *p = 42;

    printf("Valeur : %d\n", *p);

    free(p);
    p = NULL;  // Réinitialiser le pointeur pour éviter un pointeur dangling

    return 0;
}

Points Clés :

  • Après avoir libéré une mémoire, réinitialisez toujours le pointeur à NULL.

Exercice 8 : Accès Concurrent avec Mutex

Énoncé :

Protégez un tableau partagé entre plusieurs threads avec un mutex.

Solution :

#include <stdio.h>
#include <pthread.h>

#define SIZE 5

int arr[SIZE] = {0};
pthread_mutex_t lock;

void *increment(void *arg) {
    pthread_mutex_lock(&lock);
    for (int i = 0; i < SIZE; i++) {
        arr[i]++;
    }
    pthread_mutex_unlock(&lock);
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_mutex_init(&lock, NULL);

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&lock);

    for (int i = 0; i < SIZE; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

Points Clés :

  • Les mutex assurent un accès sécurisé aux ressources partagées entre threads.

Suite : Exercices Avancés et Complexes avec les Pointeurs en C

Cette suite d’exercices couvre des concepts encore plus avancés avec les pointeurs en C. Les exercices impliquent la gestion de structures imbriquées, les pointeurs void, les allocations complexes, et les erreurs intentionnelles pour apprendre à déboguer.


Exercice 9 : Manipuler des Structures avec Pointeurs

Énoncé :

Créez une structure représentant un étudiant (nom, âge, note moyenne) et utilisez un pointeur pour accéder et modifier ses champs.

Solution :

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
    float average;
} Student;

int main() {
    Student s1;
    Student *ptr = &s1;

    // Modifier via pointeur
    strcpy(ptr->name, "Alice");
    ptr->age = 20;
    ptr->average = 17.5;

    // Afficher les valeurs
    printf("Nom : %s\n", ptr->name);
    printf("Âge : %d\n", ptr->age);
    printf("Moyenne : %.2f\n", ptr->average);

    return 0;
}

Points Clés :

  • Utilisez l’opérateur -> pour accéder aux membres d’une structure via un pointeur.
  • Combinez les structures avec des pointeurs pour gérer des données dynamiques.

Exercice 10 : Allocation Dynamique de Structures

Énoncé :

Allouez dynamiquement un tableau de structures Student pour gérer plusieurs étudiants.

Solution :

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

typedef struct {
    char name[50];
    int age;
    float average;
} Student;

int main() {
    int n = 3;
    Student *students = (Student *)malloc(n * sizeof(Student));

    if (students == NULL) {
        printf("Erreur d'allocation mémoire\n");
        return 1;
    }

    // Initialiser les données
    for (int i = 0; i < n; i++) {
        snprintf(students[i].name, 50, "Etudiant %d", i + 1);
        students[i].age = 18 + i;
        students[i].average = 15.0 + i;
    }

    // Afficher les données
    for (int i = 0; i < n; i++) {
        printf("Nom : %s, Âge : %d, Moyenne : %.2f\n",
               students[i].name, students[i].age, students[i].average);
    }

    free(students);
    return 0;
}

Points Clés :

  • Chaque élément du tableau est une structure.
  • Assurez-vous de libérer la mémoire après utilisation.

Exercice 11 : Utilisation des Pointeurs void

Énoncé :

Utilisez un pointeur void pour créer une fonction générique qui affiche différentes valeurs.

Solution :

#include <stdio.h>

void printValue(void *ptr, char type) {
    switch (type) {
        case 'i': // Entier
            printf("Valeur entière : %d\n", *(int *)ptr);
            break;
        case 'f': // Float
            printf("Valeur flottante : %.2f\n", *(float *)ptr);
            break;
        case 'c': // Caractère
            printf("Caractère : %c\n", *(char *)ptr);
            break;
        default:
            printf("Type non reconnu\n");
    }
}

int main() {
    int a = 10;
    float b = 3.14;
    char c = 'A';

    printValue(&a, 'i');
    printValue(&b, 'f');
    printValue(&c, 'c');

    return 0;
}

Points Clés :

  • Les pointeurs void permettent une flexibilité maximale, mais nécessitent un transtypage explicite.

Exercice 12 : Création d’une Liste Chaînée

Énoncé :

Implémentez une liste chaînée pour stocker des entiers.

Solution :

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

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

// Ajouter un élément en tête
Node *addNode(Node *head, int value) {
    Node *newNode = (Node *)malloc(sizeof(Node));
    newNode->data = value;
    newNode->next = head;
    return newNode;
}

// Afficher la liste
void printList(Node *head) {
    Node *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

// Libérer la liste
void freeList(Node *head) {
    Node *current = head;
    while (current != NULL) {
        Node *next = current->next;
        free(current);
        current = next;
    }
}

int main() {
    Node *head = NULL;

    head = addNode(head, 10);
    head = addNode(head, 20);
    head = addNode(head, 30);

    printList(head);
    freeList(head);

    return 0;
}

Points Clés :

  • Utilisez des pointeurs pour relier les nœuds.
  • Gérez soigneusement la libération de mémoire pour éviter les fuites.

Exercice 13 : Fonction Générique avec Pointeurs

Énoncé :

Créez une fonction générique pour trier un tableau en fonction de la taille des éléments.

Solution :

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

void sort(void *arr, size_t n, size_t size, int (*cmp)(const void *, const void *)) {
    char *base = (char *)arr;
    for (size_t i = 0; i < n - 1; i++) {
        for (size_t j = i + 1; j < n; j++) {
            if (cmp(base + i * size, base + j * size) > 0) {
                char temp[size];
                memcpy(temp, base + i * size, size);
                memcpy(base + i * size, base + j * size, size);
                memcpy(base + j * size, temp, size);
            }
        }
    }
}

int compareInt(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

int main() {
    int arr[] = {5, 2, 9, 1, 6};
    size_t n = sizeof(arr) / sizeof(arr[0]);

    sort(arr, n, sizeof(int), compareInt);

    for (size_t i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

Points Clés :

  • Les fonctions génériques rendent le code réutilisable.
  • Combinez void * avec des fonctions de comparaison pour généraliser les algorithmes.

Autres articles

La Vérité sur les Tableaux et les...
Les tableaux et les pointeurs sont au cœur du langage...
Read more
Guide : Déclarer un Pointeur en C...
Cet article vous montre comment déclarer un pointeur en C...
Read more
Guide : L'Allocation Dynamique en C
L'allocation dynamique en C permet de gérer la mémoire de...
Read more

Laisser un commentaire

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