Langage C:Structures et unions

Un article de WikiTuto.

Jump to: navigation, search

Sommaire

Définition

Dans un tableau, tous les constituants doivent obligatoirement être du même type. Ce n'est pas le cas des structures, qui sont des variables composées de plusieurs variables (ou CHAMPS) de types différents. Chaque champIl es n'est plus désigné par un numéro comme dans un tableau, mais par un identificateur.

Déclaration

struct nom_type {déclaration des champs} liste_variables ;

exemple

  struct identite { char nom[30];
		    char prenom[20];
		    short int age;
		  }jean,jacques,groupe[20];

Toute variable de type identité (jean, jacques, groupe[i]) comporte trois champs : nom, prenom et age. sizeof(jacques) retournera 52. Les champs peuvent être de n'importe quel type valable (scalaires, tableaux, pointeurs...), y compris une structure (à condition d'être déclaré plus haut). nom_type et liste_variables sont optionnels mais au moins l'un des deux doit être présent. Les noms de champs ont une portée limitée à la structure (c'est à dire qu'un autre objet peut avoir le même nom, s'il n'est pas cité dans cette structure). Nom_type (ici identite) est le nom d'un nouveau type, il peut être utilisé plus loin pour déclarer d'autres variables, voire d'autres types:

struct identite lui;
struct prisonnier {long numero; struct identite id;} moi;

Il est également possible d'utiliser typedef, et (pas sur tous les compilateurs) d'initialiser une structure :

typedef struct {int jour;int mois;int année;}date;
date aujourdhui={24,12,1992}; /*évite de répéter struct*/ 

Utilisation

On accède à une composante par NOM_VARIABLE . NOM_CHAMP , par l'opérateur unaire "."

gets(jean.nom);
printf("initiales : %c %c\n",lui.nom[0],lui.prenom[0]);
printf("nom %s \n",groupe[10].nom);
scanf("%d",&moi.id.age);

Une composante d'enregistrement s'utilise comme une variable du même type (avec les mêmes possibilités mais aussi les mêmes limitations). Depuis la norme ANSI, on peut utiliser l'affectation pour des structures (recopie de tous les champs), ainsi que le passage des structures en arguments de fonction passés par valeur. Sur les compilateurs non ANSI, il faut utiliser des pointeurs.

On utilise des pointeurs de structures comme des pointeurs sur n'importe quel autre type. L'opérateur -> permet une simplification d'écriture (il signifie champ pointé) :

date *ptr;
ptr=(struct date *)malloc(sizeof(date));
*ptr.jour=14;
ptr->mois=7;

Les structures sont tres importantes en C, et encore plus dans les langages plus récents, les objets en découlent. Les champs d'une structure peuvent être relativement complexes (tableaux, autres structures), et on peut créer des tableaux de structures.

Exemple

déclarez le types et variables suivants : une date est composée de 3 entiers j, m et a. Une opération est une référence (entier long), une quantité et un prix unitaire. Une facture est une date, un numéro de client, un nombre d'opérations et au maximum 20 oprérations. Un client est un numéro, un nom et une adresse. Un produit est une référence, une désignation, un prix unitaire, une quantité en stock. Une compta est un tableau de 1000 factures. Un stock est un tableau (dynamique) de produits. Et l'on peut continuer ainsi longtemps.

Champs de bits

En ne définissant que des champs entiers (signés ou non), on peut définir la taille (en bits) de chaque champ. Il suffit pour cela de préciser, derrière chaque champ, sa taille après un ":". struct état{unsigned ReadOnly:1;int Crc:6;}

Les champs sont créés à partir des bits de poids faible. Le nom du champ est optionnel (dans le cas de champs réservés, non utilisés par le programme). Les champs n'ont alors pas d'adresse (impossible d'utiliser & sur un champ). On utilise ces structures comme les autres.

Unions

Déclaration

union nom_type {déclaration des champs} liste_variables ;

Les différents champs commenceront tous à la même adresse (permet d'utiliser des variables pouvant avoir des types différents au cours du temps, mais un seul à un instant donné). Les champs peuvent être de tout type, y compris structures. On les utilise comme les structures, avec les opérateurs "." et "->".

Exercice (tel)

A l'aide d'un tableau de personnes (nom, prénom, numéro dans la rue, rue, code postal, ville, numéro de téléphone), faire un programme de recherche automatique de toutes les informations sur les personnes répondant à une valeur d'une rubrique donnée (tous les PATRICK , tous ceux d'Obernai, travaillant à l'IPST, etc...). On suppose que le tableau est déjà initialisé. Cliquez ici pour une solution. 

Structures chaînées

Le principal problème des données stockées sous forme de tableaux est que celles-ci doivent être ordonnées : le "suivant" doit toujours être stocké physiquement derrière. Imaginons gérer une association. Un tableau correspond à une gestion dans un cahier : un adhérent par page. Supposons désirer stocker les adhérents par ordre alphabétique. Si un nouvel adhérent se présente, il va falloir trouver où l'insérer, gommer toutes les pages suivantes pour les réécrire une page plus loin, puis insérer le nouvel adhérent. Une solution un peu plus simple serait de numéroter les pages, entrer les adhérents dans n'importe quel ordre et disposer d'un index : un feuille où sont indiqués les noms, dans l'ordre, associés à leur "adresse" : le numéro de page. Toute insertion ne nécessitera de décalages que dans l'index. Cette méthode permet l'utilisation de plusieurs index (par exemple un second par date de naissance). La troisième solution est la liste chaînée : les pages sont numérotées, sur chaque page est indiquée la page de l'adhérent suivant, sur le revers de couverture on indique l'adresse du premier. L'utilisation d'une telle liste nécessite un véritable "jeu de piste", mais l'insertion d'un nouvel adhérent se fera avec le minimum d'opérations.

Appliquons cela , de manière informatique, à une liste d'entiers, avec pour chaque valeur l'adresse (numéro de mémoire) du suivant :

Image:Langage-C-Tpc01.gif

Si l'on veut insérer une valeur dans la liste, les modifications à apporter sont minimes :

Image:Langage-C-Tpc02.gif

En C on définira un type structure regroupant une valeur entière et un pointeur :

struct page {int val; struct page *suivant; };

Un pointeur (souvent global) nous indiquera toujours le début de la liste:

struct page *premier;

Au fur et à mesure des besoins on se crée une nouvelle page :

nouveau=(struct page *)malloc(sizeof(struct page));

En n'oubliant pas de préciser le lien avec le précédent :

precedent->suivant=nouveau;

le dernier élément ne doit pas pointer sur n'importe quoi, on choisit généralement soit le pointeur NULL, soit le premier (la liste est dite bouclée).

exemple

#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <alloc.h> /*ou stdlib.h*/
struct page {int val; struct page *suivant; };
struct page *premier;


int encore(void) /* demande si on en veut encore*/
 {
  printf("encore (O/N) ? ");
  return(toupper(getche())= ='O');
 }
void lecture(void)
 {
  struct page *precedent,*nouveau;
  premier=(struct page *)malloc(sizeof(struct page));
  puts("entrez votre premier entier");
  scanf("%d",&premier->val);
  precedent=premier;
  while (encore())
   {
    nouveau=(struct page *)malloc(sizeof(struct page));
    precedent->suivant=nouveau;
    precedent=nouveau;
    puts("\nentrez votre entier");
    scanf("%d",&nouveau->val);
   }
  precedent->suivant=NULL;
 }
void affiche(struct page *debut)
 {
  printf("\nliste : ");
  while(debut!=NULL)
   {
    printf("%d ",debut->val);
    debut=debut->suivant;
   }
  printf("\n");
 }
int main(void)
 {
  lecture();
  affiche(premier);
 }

Exercice (liste)

modifier la fonction lecture ci-dessus pour que la liste soit stockée dans l'ordre inverse de son introduction (chaque nouvel élément est placé devant la liste déjà existante). Cliquez ici pour une solution de cet exercice (et du suivant).

Les modifications sont aisées, une fois que l'on a repéré l'endroit de la modification. Exemple : suppression d'un élément :

void suppression(void)
 {
  struct page *actu,*prec;
  actu=premier;
  while (actu!=NULL)
   {
    printf("\nvaleur : %d - supprimer celui_ci (O/N) ? ",
	    actu->val);
    if (toupper(getche())= ='O')
     {
      if(actu= =premier)premier=actu->suivant;
      else prec->suivant=actu->suivant;
      free(actu);
      break;
     }
    else
     {
      prec=actu;
      actu=prec->suivant;
     }
   }
 }

Exercice (insertion)

ajouter au programme précédent une procédure d'insertion d'une valeur dans la liste.

La solution de l'exercice précédent contient également cette insertion

Ce type de données (structure pointant sur un même type) est utilisé dans d'autres cas. Par exemple, pour représenter un arbre, il suffit pour chaque élément de connaître l'adresse de chaque fils :

Image:Langage-C-Tpc03.gif

Remarque : si le nombre de fils n'est pas constant, on a intérêt à stocker uniquement le fils aîné, ainsi que le frère suivant(voir partie algorithmique et structures de données).

Voir aussi


Auteur :Patrick TRAU. Copyright : utilisation de ces documents libre pour tout usage personnel. Utilisation autorisée pour tout usage public non commercial, à condition de citer son auteur (Patrick TRAU, IPST, Université Louis Pasteur Strasbourg, email : Patrick.Trau (à) ipst-ulp.u-strasbg.fr ) et de me signaler tout usage intensif. Utilisation commerciale interdite sans accord écrit de ma part.

Boîte à outils
Annuaire gratuitCe site est listé dans la catégorie Informatique : Aide et astuces en informatique Annuaire