Mis à jour le 30/10/2013
  • Facile
Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Introduction du cours

Salut à tous amis zéros.
Dans ce tuto je vais tenter de vous expliquer l'utilisation des unions en C, les unions sont très proches des structures dans leur utilisation mais en sont bien différentes.
C'est parti ! :pirate:

Déclaration d'une union et utilisation

Déclaration

Une union se déclare de la même façon qu'une structure, comme ceci :

union MonUnion
{
    int entier;
    double reel;
};

Et maintenant voici comment déclarer une variable de type union dans une fonction :

int main(void)
{
    union MonUnion variable;
    return 0;
}

Mais c'est la même chose qu'une structure ton union ?! :diable:

Non ! Maintenant que nous sommes prêts à utiliser notre union, vous allez voir toute la différence.

En fait, quand vous voulez utiliser une union, celle-ci ne peut utiliser que l'un des types que vous lui avez donné à la fois.

Je ne te suis pas là !? :o

Ce n'est pas grave, vous allez voir c'est très simple.

Utilisation

Par exemple (en gardant mon union de tout à l'heure) :

int main(void)
{
    union MonUnion variable;

    variable.entier = 200;
    printf("Valeur entière = %d\n",variable.entier);

    variable.reel = 1200.05;
    printf("Valeur flottante = %lf\n",variable.reel);
    printf("Valeur entière = %d\n",variable.entier);

    return 0;
}

Vous obtenez ceci :

Valeur entière = 200

Valeur flottante = 1200.050000

Valeur entière = 858993459

Comme vous le voyez, après avoir utilisé la partie réelle de mon union, la valeur de la partie entière a été modifiée.

Comment ça se fait ? o_O

C'est très simple, en fait tout ce qui se trouve dans votre union partage la même zone de mémoire. Voici comment vérifier ceci :

int main(void)
{
    union MonUnion variable;

    printf("Adresse de l'union = %p\n",&variable);
    printf("Adresse de la partie entière = %p\n",&variable.entier);
    printf("Adresse de la partie réelle = %p\n",&variable.reel);

        return 0;
}
Adresse de l'union = 0023FF70

Adresse de la partie entière = 0023FF70

Adresse de la partie réelle = 0023FF70

Donc pour vous expliquer clairement, tout à l'heure quand nous avions utilisé la partie entière, en partant du principe que la taille en octets d'un int est de 4, ces emplacements de mémoire ont été utilisés :

0023FF70
0023FF71
0023FF72
0023FF73

Mais en utilisant la partie entière, en partant du principe que la taille en octets d'un double est de 8, nous avons utilisé ces emplacements :

0023FF70
0023FF71
0023FF72
0023FF73
0023FF74
0023FF75
0023FF76
0023FF77

Nous avons donc écrasé ce qui se trouvait à l'intérieur de notre partie entière.

Mais non. :p
Ça peut être très utile, tout d'abord vous devez savoir que contrairement à une structure, une union ne prend en mémoire que la place utilisée par son type le plus grand, ainsi notre union de tout à l'heure ne prend que la taille d'un double en mémoire.

Démonstration

union MonUnion
{
    int entier;
    double reel;
};

struct MaStruct
{
    int entier;
    double reel;
};

int main(void)
{
    union MonUnion variable;
    struct MaStruct variable2;

    printf("Taille de l'union = %d\n",sizeof(variable));
    printf("Taille de la structure = %d\n",sizeof(variable2));

    return 0;
}
Taille de l'union = 8

Taille de la structure = 16

Les unions peuvent s'avérer très utile dans certains cas, mais il faut faire très attention à leur utilisation.

Par la suite nous verrons une utilisation un peu plus avancée d'une union combinée avec une structure.

Utilisation d'union avec les arguments variables

Dans ce chapitre nous allons utiliser une union pour parser une chaîne tout comme le ferait la fonction printf par exemple mais pour une toute autre utilisation, nous allons créer une fonction qui additionne tous les arguments.

Création de l'union

Nous allons donc devoir créer une union pouvant supporter différents types voulus, comme ceci :

union MyNum
{
    char c;
    short i;
    long l;
    float f;
    double d;
    char* s;
};

Notre fonction prendra donc une chaîne de caractères en argument, et renverra un double (car le résultat peut être très grand).
Le prototype de notre fonction sera donc comme ceci :

double my_num_somme(const char* format,...);

Voici donc la fonction :

double my_num_somme(const char* format,...)
{
    double total = 0.0;
    union MyNum num;
    va_list ap;

    va_start (ap,format);
    while(*format != '\0')
    {
        switch(*format++)
        {
            /*l'argument est un char*/
            case 'c':
            num.c = (char)va_arg(ap,int);
            total += num.c;
            break;
            /*l'argument est un short*/
            case 'i':
            case 'I':
            num.i = (short)va_arg(ap,int);
            total += num.i;
            break;
            /*l'argument est un long*/
            case 'l':
            case 'L':
            num.l = va_arg(ap,long);
            total += num.l;
            break;
            /*l'argument est un float*/
            case 'f':
            num.f = (float)va_arg(ap,double);
            total += num.f;
            break;
            /*l'argument est un double*/
            case 'F':
            num.d = va_arg(ap,double);
            total += num.d;
            break;
            /*conversion d'une chaîne représentant un entier*/
            case 's':
            num.s = va_arg(ap,char*);
            total += atol(num.s);
            break;
            /*conversion d'une chaîne représentant un flottant*/
            case 'S':
            num.s = va_arg(ap,char*);
            total += atof(num.s);
            break;
            default:
            break;
        }
    }
    va_end(ap);

    return total;
}

Mais pourquoi tu passes un int à va_arg pour le type char et short et un double pour les float ? o_O

C'est tout simplement parce que le compiler, afin de se simplifier la vie, passe les entiers de type plus petits que des int en tant que int et les flottants de type float en tant que double.

L'avantage ici est donc que même si l'utilisateur envoie un nombre plus grand qu'un int par exemple et qu'il donne à son format le caractère c qui représente donc un char sera tronqué, vous contrôlez donc bien le type selon le format.

Et dans tout ça, elle a servi à quoi l'union ?

À gagner de la place en mémoire et surtout de la clarté dans le code, car sans cette union vous auriez dû déclarer tous les types de variables différents vous-même.
Évidemment ici il s'agit d'un petit exemple, mais imaginez dans une fonction qui fait une centaine de lignes. ^^

Voici un petit exemple de ce que donne notre fonction :

int main(void)
{
    printf("Total : %lf\n",my_num_somme("ifsS",100,5.8945,"10000","102.501"));
    return 0;
}
Total : 10208.395500



Press ENTER to continue.

Des variables intelligentes

Dans cette partie nous allons créer des variables intelligentes qui peuvent prendre plusieurs types différents et dont le type peut être connu.

Création de la variable

Nous allons donc tout d'abord créer une union qui pourra contenir les différents types voulus :

typedef union mon_union mon_union;
union mon_union
{
    int i;
    double d;
    char str[100];
};

Nous aurons donc la possibilité d'associer à notre variable un entier (i), un flottant (d) et une chaîne (str).

Maintenant nous avons besoin de pouvoir connaître le type de la variable en cours d'utilisation, nous devons donc ajouter une valeur, un int par exemple, mais nous ne pouvons pas l'ajouter à notre union car celle-ci serait écrasée à chaque modification de la valeur dans notre union.

Comment faire ?

La réponse est simple, nous allons créer une structure qui contiendra à la fois notre union et son type.

Voici ce que ça donne :

typedef struct Var Var;
struct Var
{
    mon_union val;
    int type;
};

Nous pouvons aussi simplifier ce code en déclarant directement l'union à l'intérieure de la structure (ceci est facultatif).

typedef struct Var Var;
struct Var
{
    union
    {
        int i;
        double d;
        char str[100];
    }val;
    int type;
};

Maintenant pour simplifier le code et éviter les erreurs, nous allons utiliser une énumération pour associer les différents types de notre variable à un nombre :

enum
{
    TYPE_INT,TYPE_DOUBLE,TYPE_STRING
};

Voilà, nous en avons fini avec les déclarations, voici donc un petit aperçu de ce que doit contenir notre fichier union.h :

#ifndef DEF_VAR_H
#define DEF_VAR_H


typedef struct Var Var;
struct Var
{
    union
    {
        int i;
        double d;
        char str[100];
    }val;
    int type;
};

enum
{
    TYPE_INT,TYPE_DOUBLE,TYPE_STRING
};

#endif

Création de fonctions pour notre structure

Nous devons maintenant créer des fonctions pour associer à notre structure différents types de données, nous passerons notre structure à l'aide des pointeurs.

Association d'un entier

void var_assoc_int(Var* variable, int i)
{
    variable->val.i = i;
    variable->type = TYPE_INT;
}

À la première ligne nous accédons donc à la valeur entière dans l'énumération de notre structure, d'où le variable->val.i.
Ensuite nous donnons le type TYPE_INT au champs type (variable->type) de notre structure.

Association d'un flottant

Le code est presque similaire excepté que au lieu d'envoyer un int nous envoyons donc un double à notre fonction, comme ceci :

void var_assoc_double(Var* variable, double d)
{
    variable->val.d = d;
    variable->type = TYPE_DOUBLE;
}

Association d'une chaîne

Maintenant nous allons associer une chaîne à notre variable, nous allons donc utiliser les fonctions de manipulation des chaînes.

void var_assoc_string(Var* variable, const char* chaine)
{
    memset(variable->val.str,'\0',100);
    strncpy(variable->val.str, chaine, 99);
    variable->type = TYPE_STRING;
}

À la première ligne nous utilisons la fonction memset.
Son utilisation est memset(void* pointeur, int valeur, size_t taille).
Celle-ci se charge de mettre tous les éléments de notre chaîne à '\0', ceci est pour être sûr que notre chaîne est bien vide avant d'y copier autre chose et pour qu'elle soit véritablement terminée par le caractère final des chaînes qui est le '\0'.

Ensuite nous utilisons strncpy qui fait pratiquement la même chose que strcpy à la différence qu'il prend un paramètre en plus qui détermine le nombre maximum de caractères à copier dans notre chaîne, car ne l'oubliez pas, nous avons utilisé un tableau (char str[100]) dans notre union, donc notre chaîne peut contenir 99 caractères et le 100ème doit être réservé pour le caractère final.
L'utilisation de strncpy est strncpy(char* chaîne, const char* copie, size_t maximum)
Et pour finir comme dans les autres fonctions, nous donnons le type voulu à notre variable, variable->type = TYPE_STRING;.

Et enfin nous allons créer une fonction qui imprimera à l'écran la valeur de notre variable en fonction de son type, pour ce faire, il faut procéder comme suit :

void var_print(Var* variable)
{
    switch(variable->type)
    {
        case TYPE_INT:
        /*valeur entière*/
        printf("%d",variable->val.i);
        break;
        case TYPE_DOUBLE:
        /*valeur flottante*/
        printf("%lf",variable->val.d);
        break;
        case TYPE_STRING:
        /*chaîne*/
        printf("%s",variable->val.str);
        break;
        default:
        /*erreur*/
        printf("Erreur : le type de la variable est inconnu!\n");
        break;
    }
}

Maintenant, testons un peu ce que nous donne ce bout de code. :D

Utilisation

Nous allons maintenant tester toutes nos fonctions.

#include <stdio.h>
#include <stdlib.h>
#include "var.h"/*obligatoire pour pouvoir connaître notre structure*/

int main(void)
{
    Var ma_variable;/*déclaration d'une variable de type Var*/

    /*on associe un int*/
    var_assoc_int(&ma_variable, 2006);
    /*on affiche*/
    var_print(&ma_variable);
    /*on associe un double*/
    var_assoc_double(&ma_variable, 0.123456);
    /*on affiche*/
    var_print(&ma_variable);
    /*on associe une chaîne*/
    var_assoc_string(&ma_variable, "Vive les ZeRos !");
    /*on affiche*/
    var_print(&ma_variable);

        return 0;
}

Résultat :

2006

0.123456

Vive les Zér0s !



Press ENTER to continue.

Voilà, vous avez maintenant les connaissances requises pour utiliser les unions, je vous laisse imaginer tout ce que vous pourrez en faire car les unions sont souvent bien pratiques. ;)

Voici les différents fichiers du projet :

var.h

#ifndef DEF_VAR_H
#define DEF_VAR_H

#include <stdio.h>/*pour printf*/
#include <string.h>/*pour strncpy*/

/*un typedef pour éviter d'avoir à preciser le mot struct à chaque
utilisation de nos 'Var'*/
typedef struct Var Var;

/*la structure*/
struct Var
{
    union
    {
        /*valeur entière*/
        int i;
        /*valeur flottante*/
        double d;
        /*chaîne d'un maximum de 99 caractères + le caractère final*/
        char str[100];
    }val;
    /*le type en cours d'utilisation*/
    int type;
};

/*énumération des différents types*/
enum
{
    TYPE_INT,TYPE_DOUBLE,TYPE_STRING
};

/*les prototypes des fonctions*/
void var_assoc_int(Var* variable, int i);
void var_assoc_double(Var* variable, double d);
void var_assoc_string(Var* variable, const char* str);
void var_print(Var* variable);

#endif

var.c

#include "var.h"

void var_assoc_int(Var* variable, int i)
{
    variable->val.i = i;
    variable->type = TYPE_INT;
}

void var_assoc_double(Var* variable, double d)
{
    variable->val.d = d;
    variable->type = TYPE_DOUBLE;
}

void var_assoc_string(Var* variable, const char* chaine)
{
    memset(variable->val.str,0,100);
    strncpy(variable->val.str, chaine, 99);
    variable->type = TYPE_STRING;
}

void var_print(Var* variable)
{
    switch(variable->type)
    {
        case TYPE_INT:
        /*valeur entière*/
        printf("%d\n",variable->val.i);
        break;
        case TYPE_DOUBLE:
        /*valeur flottante*/
        printf("%lf\n",variable->val.d);
        break;
        case TYPE_STRING:
        /*chaîne*/
        printf("%s\n",variable->val.str);
        break;
        default:
        /*erreur*/
        printf("Erreur : le type de la variable est inconnu!\n");
        break;
    }
}

main.c

#include <stdio.h>
#include <stdlib.h>
#include "var.h"/*obligatoire pour pouvoir connaître notre structure*/

int main(void)
{
    Var ma_variable;/*déclaration d'une variable de type Var*/

    /*on associe un int*/
    var_assoc_int(&ma_variable, 2006);
    /*on affiche*/
    var_print(&ma_variable);
    /*on associe un double*/
    var_assoc_double(&ma_variable, 0.123456);
    /*on affiche*/
    var_print(&ma_variable);
    /*on associe une chaîne*/
    var_assoc_string(&ma_variable, "Vive les ZeRos !");
    /*on affiche*/
    var_print(&ma_variable);

        return 0;
}

Après avoir lu le dernier chapitre, vous pouvez améliorer quelques points vous-même au projet :

  • Gérer les erreurs, par exemple un pointeur à NULL est envoyé.

  • Créer d'autres fonctions qui permettent de convertir la valeur de l'union en un autre type voulu.

  • Allouer dynamiquement la mémoire pour la chaîne de l'union afin de pouvoir y mettre autant de caractères que vous le voulez.

Voilà, bonne lecture. :)

Exemple de certificat de réussite
Exemple de certificat de réussite