Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Langage C
  3. Les unions
Extrait - Langage C Maîtriser la programmation procédurale (avec exercices pratiques) (2e édition)
Extraits du livre
Langage C Maîtriser la programmation procédurale (avec exercices pratiques) (2e édition)
2 avis
Revenir à la page d'achat du livre

Les unions

Principe de l’union

Une union est une variable unique, mais avec un choix de types. L’espace mémoire de la variable correspond à celui nécessaire pour le type le plus grand. Par exemple :

union utest{  
    char c;  
    int i;  
    float f;  
    double d;  
}; 

Pour disposer d’une union, il suffit d’une déclaration :

union utest u ; 

Comme les structures, un typedef permet de faire disparaître l’utilisation du mot-clé, le mot union :

typedef union {  
    char c;  
    int i;  
    float f;  
    double d;  
}utest; 

Afin d’obtenir des déclarations sous la forme :

utest u ; 

En C++ le typedef est implicite et la définition :

union utest{  
    char c;  
    int i;  
    float f;  
    double d;  
    float f;  
    double d;  
}; 

permet des déclarations sans le mot-clé union :

utest u ; 

Cette union utest définit le type d’une variable qui peut être soit un char, soit une int, soit un float, soit un double. Le choix du type pour la variable s’effectue en utilisant l’opérateur...

Unions discriminées

Une structure peut contenir une union ainsi qu’un identificateur qui permet de connaître le type choisi pour l’instance de la structure. C’est ce que l’on appelle une union discriminée. En voici un exemple inspiré d’un programme trouvé dans la documentation de Microsoft.

De façon schématique, il s’agit de retracer les données de température et de vent relatives au temps qu’il fait.

Nous avons deux types de structures : l’un pour des données de température, l’autre pour les données du vent :

typedef struct  
{  
    int idStation;  
    int min, max, actuelle;  
}Temperature;  
   
typedef struct   
{  
    int idStation;  
    int vitesse;  
    int direction;  
}Vent; 

Ces deux types de structures sont identifiés chacun par une constante dans une énumération à part :

typedef enum  { TEMPERATURE, VENT, MAXTYPE }TypeData; 

Une troisième structure, nommée Data, contient une union anonyme (sans nom) composée des deux structures pour la température et le vent. La structure Data contient également une variable de type TypeData qui permettra d’identifier le type de structure qui sera effectivement instancié dans l’union :

typedef struct  
{  
    TypeData type;  
    union  
    {  
           Temperature temp;  
           Vent vent;  
    };  
}Data; 

Pour chacune des structures composant l’union, nous écrivons deux fonctions : l’une pour initialiser la structure, l’autre pour en afficher les valeurs. Toutes les initialisations se font avec des valeurs aléatoires.

Les directions du vent sont répertoriées dans une énumération :

typedef enum  { NORD, EST, SUD, OUEST, MAXDIR }DirVent; 

Voici les fonctions d’initialisation et d’affichage d’une structure Vent  :

Vent init_vent()  
{  
    Vent v;  
   ...

Union de structures et simulation d’héritage (concept programmation objet)

Une union, dans le fond, ressemble à un emboîtement de poupées russes. L’espace mémoire est celui de la plus grande et il peut en contenir de plus petites. Avec des structures, en jouant sur ce principe d’emboîtement, nous pouvons constituer une entité qui peut prendre des types différents et ainsi obtenir des tableaux d’objets différents en C.

Pour ce faire, il convient de doter chaque structure constitutive de l’union d’un ou de plusieurs champs communs et, afin de les identifier dans le programme, de les équiper toutes d’un champ type. Cet ensemble commun de variables constitue en quelque sorte une classe de base (pour reprendre la terminologie de la programmation objet), et les champs supplémentaires, propres à chaque type de structure de l’union, équivalent aux données d’une classe dérivée.

Soit par exemple un jeu dans lequel nous avons des ennemis qui peuvent avoir quatre types différents : Dragon, Troll, Nain, Orc.

Tous les quatre possèdent des caractéristiques communes équivalant à une classe de base :

  • une variable pour identifier la nature de l’ennemi (1 dragon, 2 troll, 3 nain, 4 orc), nous la nommons type ;

  • une position x et y ;

  • un déplacement dx, dy.

En plus de ce tronc commun, chaque type d’ennemi ajoute à cette classe de base des caractéristiques bien à lui comme le ferait une classe dérivée :

  • deux variables flamme et cuirasse pour le type Dragon.

  • muscle et masse pour le type Troll.

  • hache et barde pour le Nain.

  • poignard et griffes pour l’Orc.

Nous pouvons bien sûr définir normalement une structure pour chaque type et répéter pour chacune le tronc commun. Dans la plupart des cas, c’est suffisant.

Mais nous pouvons aussi, plutôt que de répéter dans chaque structure les variables communes, extraire et instaurer un tronc commun à l’aide d’un define défini ainsi :

#define TRONC_COMMUN      int type; \  
                          int x;\  
                          int...

Tableau d’unions

À partir de notre union Ennemi défini ci-dessus, qui regroupe nos quatre structures Dragon, Troll, Nain et Orc, nous allons créer un tableau d’Ennemis qui contient des types différents de structures et réunit tous les ennemis. Ce tableau donne la possibilité de gérer simultanément tous les ennemis, quels que soient leurs types. Pour la démonstration, nous nous occupons de l’initialisation, de l’affichage et de l’avance des ennemis.

Chaque type d’ennemi dispose d’une fonction d’initialisation spécifique, en quelque sorte un constructeur. Toutes sont construites sur le même modèle. Voici par exemple celle pour le type Dragon :

Dragon CreateDragon()  
{  
       Dragon d;  
       d.type = DRAGON;  
       d.x = rand() % 100;  
       d.y = rand() % 100;  
       d.dx = rand() % 100;  
       d.dy = rand() % 100;  
       d.flamme = rand() % 1000;  
       d.cuirasse = rand() % 1000;  
       return d; 
} 

Ensuite, nous écrivons une fonction d’initialisation pour un tableau d’Ennemis. À l’issue de l’appel, le tableau passé en paramètre contient différents types d’ennemis obtenus aléatoirement :

void InitEnnemis(Ennemi e[], int nb)  
{  
    int i;  
    for (i = 0; i < 10; i++) {  
           switch (rand() % 4) {  
           case 0: e[i].dragon = CreateDragon();    break;  
           case 1: e[i].troll = CreateTroll();      break;  
           case 2: e[i].nain = CreateNain();        break;  
           case 3: e[i].orc = CreateOrc();          break;  
           }  
    }  
} 

La fonction d’affichage prend en compte le type de chaque ennemi afin de pouvoir restituer les valeurs de ses propriétés...