Informações

Prazo de entrega Sem prazo
Limite de submissão No limitation

Etiquetas

Entrar

[9.1] Rappel des concepts [Obligatoire]

Les structures

Une structure est une structure de données qui permet de stocker plusieurs valeurs de type différents dans une seule variable. Les composants qui stockent ces différentes valeurs sont appelés champs et en utilisant l'opérateur . (point) et leur nom, il est possible de spécifier quelle valeur on souhaite utiliser.

Voici un exemple de définition et d'utilisation d'une structure en C :

Comme vous pouvez le voir, la structure etudiant contient quatre champs : prenom, nom, section et resultat. Chacun de ces champs doit être initialisé avant d'être utilisé sinon cela peut provoquer des comportements indéfinis. Il est possible d'accéder à chacun de ces champs en utilisant l'opérateur . (point) suivi du nom du champ.

Dans le cas ci-dessus, l'initialisation des champs est un peu complexe car elle contient des chaines de caractères et il faut donc d'abord déclarer la variable avant d'initialiser chaque champs avec les appels à malloc et strcpy. Cependant, pour les structures plus simples, il est possible d'initialiser les champs directement, comme dans l'exemple suivant :

#include <stdio.h>

struct point {
    int x;
    int y;
};

int main(void) {
    // Déclaration et initialisation de la structure
    struct point point = {.x = 10, .y = 20};

    // Utilisation de la structure
    printf("Point coordinates: (%d, %d)\n", point.x, point.y);

    return 0;
}

Ici, la structure point est déclarée avec deux champs x et y, et elle est initialisée directement lors de sa déclaration avec la syntaxe {.x = 10, .y = 20} qui assigne les valeurs 10 et 20 aux champs x et y respectivement. Notez qu'il est important de bien mettre des . (points) avant les noms des champs lors de l'initialisation.

Il est aussi possible de créer un tableau de structures en déclarant un tableau de la structure souhaitée, comme dans l'exemple suivant :

#include <stdio.h>

struct point {
    int x;
    int y;
};

int main(void) {
    // Déclaration et initialisation
    struct point points[3] = {
      {.x = 10, .y = 20},
      {.x = 30, .y = 40},
      {.x = 50, .y = 60}
    };

    // Utilisation
    for (int i = 0; i < 3; i++) {
        printf("Point coordinates: (%d, %d)\n", points[i].x, points[i].y);
    }

    return 0;
}

Ici, un tableau de trois structures point est déclaré et initialisé avec des valeurs pour chaque champ de chaque structure.

Les structures allouées dynamiquement

Comme pour les tableaux, on a parfois besoin de créer dynamiquement une nouvelle structure. Pour cela, on utilise aussi les fonctions malloc et free :

Comme vous pouvez le voir, la structure point est allouée dynamiquement avec un appel à malloc et un pointeur vers la structure est retourné. Pour accéder aux champs de la structure pointée, on peut utiliser soit l'opérateur * suivi de l'opérateur . (point) ou l'opérateur -> qui combine les deux opérations en une seule.

Il est aussi possible de créer un tableau de structures allouées dynamiquement en multipliant la taille de la structure par le nombre d'éléments souhaités lors de l'appel à malloc :

Les énumérations

Les énumérations sont un autre type de structure de données qui permet de définir un ensemble de valeurs symboliques. Elles sont souvent utilisées pour représenter des états ou des options dans un programme. Cela permet d'éviter de traiter certaines valeurs numériques arbitraires avec une sémantique particulière et donc de rendre le code plus lisible et plus maintenable.

Voici un exemple d'énumération en C :

#include <stdio.h>

enum jour {
    LUNDI,
    MARDI,
    MERCREDI,
    JEUDI,
    VENDREDI,
    SAMEDI,
    DIMANCHE
};

int main(void) {
    enum jour aujourd_hui = MARDI;

    if (aujourd_hui == MARDI) {
        printf("Aujourd'hui, c'est mardi !\n");
    }

    return 0;
}

Il est aussi possible d'assigner des valeurs spécifiques aux éléments de l'énumération, comme dans l'exemple suivant :

#include <stdio.h>

enum HttpStatus {
    HTTP_OK = 200,
    HTTP_BAD_REQUEST = 400,
    HTTP_UNAUTHORIZED = 401,
    HTTP_FORBIDDEN = 403,
    HTTP_NOT_FOUND = 404,
    HTTP_INTERNAL_SERVER_ERROR = 500
};

int main(void) {
    enum HttpStatus status = HTTP_NOT_FOUND;

    if (status == HTTP_NOT_FOUND) {
        printf("Erreur 404 : Page non trouvée.\n");
    }

    return 0;
}

Comportement des structures avec les fonctions

Contrairement aux tableaux, les structures ont un comportement relativement intuitif lorsqu'elles sont utilisées avec des fonctions.

Passage par valeur par défaut

Par défaut, une copie complète de la structure est créée lorsqu'elle est passée en argument à une fonction. Cela signifie que les modifications apportées à la structure dans la fonction n'affectentent pas la structure originale.

Voici un exemple illustrant ce comportement :

Cependant, il est possible de passer une structure par référence en utilisant un pointeur vers la structure, comme dans l'exemple suivant :

Retour d'une structure depuis une fonction

Contrairement aux tableaux, il est possible de retourner une structure depuis une fonction. Cela crée une copie complète de la structure retournée, comme dans l'exemple suivant :

Bonnes pratiques

Allocation dynamique que lorsque nécéssaire

Comme pour les tableaux, il est recommandé de n'utiliser l'allocation dynamique des structures que lorsque c'est nécéssaire.

Passage par référence pour les grosses structures

Si une structure est grosse (plus de quelques dizaines d'octets), il est généralement recommandé de la passer par référence aux fonctions en utilisant un pointeur pour éviter les copies coûteuses en performance du passage par valeur.

Utilisation de typedef pour simplifier le code

Dans les exemples précédents, les structures sont déclarées avec le mot-clé struct à chaque fois qu'elles sont utilisées. Cela peut rendre le code verbeux et difficile à lire. Pour simplifier cela, il est possible d'utiliser le mot-clé typedef pour créer un alias pour la structure, comme dans l'exemple suivant :

#include <stdio.h>

struct point {
    int x;
    int y;
};

typedef struct point point_t;

int main(void) {
    // Déclaration et initialisation de la structure
    point_t point = {.x = 10, .y = 20};

    // Utilisation de la structure
    printf("Point coordinates: (%d, %d)\n", point.x, point.y);

    return 0;
}

Cela permet d'utiliser point_t comme type de données au lieu de struct point, ce qui rend le code plus concis et lisible.

Puisque cela est une pratique courante en C, il est possible de faire cela directement lors de la déclaration de la structure en combinant les deux opérations, comme dans l'exemple suivant :

#include <stdio.h>

typedef struct point {
    int x;
    int y;
} point_t;

int main(void) {
    // Déclaration et initialisation de la structure
    point_t point = {.x = 10, .y = 20};

    // Utilisation de la structure
    printf("Point coordinates: (%d, %d)\n", point.x, point.y);

    return 0;
}

Parfois, on n'a pas envie de nommer la structure en elle-même car on a juste besoin de définir un nouveau type. Cela est possible en omettant le nom de la structure lors de la déclaration et en définissant seulement un nouveau type, comme dans l'exemple suivant :

#include <stdio.h>

typedef struct {
    int x;
    int y;
} point_t;

int main(void) {
    // Déclaration et initialisation de la structure
    point_t point = {.x = 10, .y = 20};

    // Utilisation de la structure
    printf("Point coordinates: (%d, %d)\n", point.x, point.y);

    return 0;
}

Cela crée un type point_t sans nommer la structure sous-jacente.

Nom des structures et des typedef en minuscules

Il est recommandé de nommer les structures avec des noms explicites en minuscules et de nommer les typedef avec les mêmes noms mais avec un suffixe _t pour indiquer qu'il s'agit d'un type défini par l'utilisateur.


Questão 1: Définir une structure
#include <stdio.h>
#include <math.h>

1️⃣ coordonnees {
    double x;
    double y;
};

2️⃣ 1️⃣ coordonnees coordonnees_t;

coordonnees_t new_coordonnees(double x, double y) {
    coordonnees_t c;
    c.x = x;
    c.y = y;
    return c;
}

double distance(coordonnees_t c1, coordonnees_t c2) {
    return sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y));
}

coordonnees_t decaler(coordonnees_t c, double decalage_x, double decalage_y) {
    c.x += decalage_x;
    c.y += decalage_y;
    return c;
}

void print(coordonnees_t c){
    printf("(%lf, %lf)\n", c.x, c.y);
}

int main(void) {
  3️⃣ c = new_coordonnees(0, 0);
  print(c);

  3️⃣ j = decaler(c, 1, 1);
  print(j);
  print(c);

  // Notez que `c` n'a pas été modifié. Il a été passé par valeur et pas
  // par référence à la fonction `décaler`.
  double dist = distance(c, j);
  printf("%lf\n", dist);

  return 0;
}

Observez le programme ci-dessus mais ne l'exécutez pas. Quel est le mot-clé à mettre à la place des 1️⃣ pour définir la structure coordonnees ?

Questão 2: Définir un nouveau type

Dans le programme de l'exercice précédent, quel est le mot-clé à mettre à la place de 2️⃣ pour définir le nouveau type coordonnees_t ?

Questão 3: Déclarer une variable d'un nouveau type

Dans le programme de l'exercice précédent, j'aimerais déclarer des variables avec le nouveau type que j'ai défini précédemment, que dois-je écrire à la place des 3️⃣ ?

Questão 4: Déclarer une variable de type structure

Dans le programme de l'exercice précédent, si j'avais plutôt voulu déclarer les variables avec le type de la structure directement, qu'aurais-je dû écrire à la place des 3️⃣ ?

Questão 5: Composants d'une structure

Comment s'appellent les composants d'une structure ?

Questão 6: Accès aux composants

Quel est l'opérateur qui permet d'accéder à un composant d'une structure ?

Questão 7: Code (ligne 26-30)
 1 #define N 10
 2 
 3 struct temperatures {
 4     double temp[N];
 5     int i;
 6     int nb;
 7     double min;
 8     double max;
 9 };
10 typedef struct temperatures temperatures_t;
11 
12 temperatures_t ajouter(temperatures_t t, double valeur) {
13     if (val > t.max) {
14         t.max = val;
15     }
16     if (val < t.min) {
17         t.min = val;
18     }
19     t.nb++;
20     t.temp[t.i] = val;
21     t.i = (t.i + 1) % N;
22     return t;
23 }
24 
25 int main(void) {
26     temperatures_t t;
27     t.min = 100;
28     t.max = -274;
29     t.nb = 0;
30     t.i = 0;
31 
32     t = ajouter(t, 24);
33     t = ajouter(t, -3);
34 
35     return 0;
36 }

Observez le programme ci-dessus mais ne l'exécutez pas. Après l'exécution des lignes 26 à 30, quelle est la valeur de t.min ?

Questão 8: Code (encore ligne 26-30)

Dans le programme de l'exercice précédent, après l'exécution des lignes 26 à 30, quelle est la valeur de t.i ?

Questão 9: Code (ligne 32-33)

Dans le programme de l'exercice précédent, après l'exécution des lignes 32 à 33, quelle est la valeur de t.max ?

Questão 10: Code (encore ligne 32-33)

Dans le programme de l'exercice précédent, après l'exécution des lignes 32 à 33, quelle est la valeur de t.min ?

Questão 11: Code (toujours ligne 32-33)

Dans le programme de l'exercice précédent, après l'exécution des lignes 32 à 33, quelle est la valeur de t.i ?

Questão 12: Code (enfin ligne 32-33)

Dans le programme de l'exercice précédent, après l'exécution des lignes 32 à 33, quelle est la valeur du premier élément de t.temp ?

Questão 13: Énumérations

Quelles affirmations sont correctes concernant les énumérations ?