2. Structures

2.1. Généralités

Une structure permet de créer des types complexes. Imaginons que l'on veuille créer un programme permettant de gérer les scores d'une partie de belote. On veut manipuler des joueurs dont on connaît le nom et qui dispose d'un capital de points. On va créer une entité "logique" belote qui contient ces renseignements.

struct belote { 
char nom[80]; /* nom du joueur    */
int points;   /* points du joueur */ 
};

ATTENTION : le point virgule est OBLIGATOIRE.

Cette structure s'utilisera comme suit :

struct belote joueur1;   /* déclaration d'une variable de type
                            "struct belote"                     */
joueur1.nom    = "loic"; /* accès au membre nom de la variable*/
joueur1.points = 0;      /* accès au membre points de la variable */

Avec l'opérateur point, on accède au membre voulu de la structure.

Cette structure définit un nouveau type et bien évidemment, on peut manipuler des variables de ce type.

struct belote * pointeur; /* définition du pointeur sur la structure */
 
pointeur = &joueur1;      /* affectation du pointeur sur le joueur
                             précédemment créé                       */
pointeur->points = 100;   /* modification du membre "points"
                             de la variable                          */

Pour accéder au membre d'une variable en utilisant un pointeur, on se sert de l'opérateur flèche. En fait, on remplace (*pointeur).membre par pointeur->membre.

Avoir à rappeler le mot-clé "struct" en permanence devient vite contraignant. Pour éviter cela, on va utiliser le "typedef" qui permet de renommer un type. La syntaxe est la suivante :

typedef ancien_nom nouveau_nom;

Ainsi la déclaration de la structure devient :

typedef struct belote { 
char nom[80]; /* nom du joueur    */
int points;   /* points du joueur */ 
} belote_t;

On a renommé "struct belote" en "belote". Si l'on a pas l'intention d'utiliser le premier nom, on peut même omettre belote et alors la structure ne sera accessible qu'avec le typedef.

Pour déclarer des objets de la structure belote, on peut utiliser indifféremment :

struct belote joueur1;  /* ou */
belote_t      joueur2;

2.2. Les structures autoréférentielles

Derrière ce nom barbare, se cache le mécanisme permettant de définir des objets complexes tels que les listes chaînées, les arbres. Dans la structure, on a au moins un pointeur sur une structure de même type (d'où le "autoréférentiel").

typedef struct belote { 
char nom[80]; /* nom du joueur    */
int points;   /* points du joueur */ 
struct belote * suivant;
} belote_t;

On va créer notre premier joueur de la partie de belote. Chaque joueur constitue un bloc ou cellule de la liste chaînée définie par une tête de liste (dite fictive car elle ne contient aucun élément signifiant : on se sert de son pointeur suivant pour stocker le premier élément de la liste)

belote_t   tete;
belote_t * pointeur;
char       str [80];
 
tete.suivant = NULL; /* on initialise la liste avec aucun élément */
 
/* allocation d'une cellule*/
pointeur = (belote_t *) malloc(sizeof(belote_t));
printf("Quel est le nom du joueur ? ");
scanf("%s", str); /* noter l'absence de & : str est déjà
                     une adresse */
pointeur->nom = (char *) malloc((strlen(str)+1)*sizeof(char));
strcpy(pointeur->nom, str);
pointeur->points = 0;
pointeur->suivant = NULL;
 
/* affectation du premier élément de la liste */
tete.suivant = pointeur;

Imaginons que l'on ait créé une liste complète de joueurs que l'on veut détruire après la partie :

belote_t * courant, * temp; 
 
for (courant=tete.suivant; courant; courant=temp) {
temp=courant->suivant;
free(courant->nom);
free(courant);
} 
tete.suivant = NULL;

Oublier de libérer la mémoire utilisée pour stocker le nom serait une erreur grave (fuite mémoire : mémoire allouée mais perdue pour l'utilisateur et le système). On vide toute la liste, il n'est donc pas utile de mettre à jour au fur et à mesure tete->suivant.

Valid XHTML 1.0!