• Aucun résultat trouvé

11. Types de données composés et types dérivés

11.4. Les unions

Dans les structures, une suite de membres occupait un espace contigu de mémoire. Dans les unions ce sont des membres différents qui occuperont successivement et selon vos besoins la même zone.

On pourra ainsi utiliser dans les mêmes zones de mémoire des variables de type différents, int,

double, struct,... un seul des membres pouvant être actif. La taille d'une union reste celle de son membre le plus grand.

Exemple 1 :

#include <stdio.h>

int main() {

union Exemple {

int i;

double d;

char ch;

} mu = {90}, alpha, *ptr= &mu;

printf("\n taille des objets mu: %d"

"\n\t\t mu.i: %d , mu.d: %d , mu.ch: %d\n", sizeof( mu), sizeof( mu.i),

sizeof( mu.d), sizeof( mu.ch));

printf("\n mu.i= %d\n mu.d= %.3lf\n mu.ch= %c\n", mu.i, mu.d, mu.ch);

mu.d = 89.012;

printf("\n mu.i= %d\n mu.d= %.3lf\n mu.ch= %c\n", mu.i, mu.d, mu.ch);

mu.ch = 'A';

printf("\n mu.i= %d\n mu.d= %.3lf\n mu.ch= %c\n", mu.i, mu.d, mu.ch);

ptr->i = 88;

printf("\n mu.i= %d\n mu.d= %.3lf\n mu.ch= %c\n", ptr->i, ptr->d, ptr->ch);

alpha = mu;

printf("\n alpha.i= %d\n alpha.d= %.3lf\n alpha.ch= %c\n", alpha.i, alpha.d, alpha.ch);

1) La première série d'affichages donne la taille des différents objets. Remplacez le mot union

par le mot struct et vous aurez le même résultat mais la taille de l'objet mu passe de 8 (la taille du double) à 13 octets (la somme des tailles de chaque type).

2) L'initialisation n'est possible avec la déclaration que pour le premier membre de la liste de définition de l'union: union Exemple { int i; double d; } mu = { 90}; donne mu.i= 90 Si on avait écrit 90.5 la valeur aurait été convertie en 90. Mais il vaut peut être mieux ne pas jouer avec ça! L'initialisation peut se faire par affectation, comme pour les structures: alpha= mu;

3) L'accès aux membres se fait aussi comme avec les structures: alpha.i ou ptr->i

4) Analysez les résultats affichés et vous constaterez que malgré certaines coïncidences, on ne peut se fier qu'à la valeur introduite en dernier lieu dans l'union, car il n'y a pas de manière simple permettant de tester le type de la variable vivante dans l'union. Dans un programme

complexe, on peut rapidement ne plus trop savoir où on en est!

Quand il faut impérativement utiliser une union, on peut maintenir une certaine sécurité en lui associant une numération qui précise l'état où se trouve l'union:

union { int i; double d; char ch; } mu;

enum etat_union { Vide, car, entier, reel= 8 } e_mu= Vide;

Les nombres entiers associés à l'énumération 0, 1, 2, 8 correspondent à la longueur des types des variables de l'union.

mu.i = 90;

e_mu = entier;

Il faut reconnaître que c'est assez lourd à gérer.

Exemple 2 :

#include <stdio.h>

#include <stdlib.h>

typedef union { int i; double d; } Exemple;

typedef enum { vide, entier = 2, reel = 8 } Etat;

typedef enum { Echec = -1, Succes, Vrai = 0, Faux } Logique;

void lireInt(int *);

void lireDouble(double *);

Logique lireExemple(Exemple * const, const Etat);

int main()

test = lireExemple(&ex, e_ex);

if (test == Succes)

printf(" ex. i= %d ", ex.i);

else

printf("\n Echec de l'allocation de mémoire!");

e_ex = reel;

test = lireExemple(&ex, e_ex);

if (test == Succes)

printf(" ex. d= %.3lf ", ex.d);

else

printf("\n Echec de l'allocation de mémoire!");

return 0;

}

Logique lireExemple(exemple *const e, const etat e_e) {

exemple *ptr = ( Exemple * ) malloc(sizeof(Exemple));

if (ptr == NULL) return Echec;

if (e_e == entier) {

printf(" \n Entrez une valeur entière: ");

lireInt(&ptr->i);

e->i = ptr->i;

while (scanf("%lf", &dble)!= 1) while (getchar() != '\n') ;

while (scanf("%lf", reel)!= 1) while (getchar() != '\n') ; while (getchar() != '\n') ; }

L'exemple précédent rassemble un petit paquet de difficultés. L'objectif est de réaliser une opération simple dans une fonction qui accepte indifféremment des variables entières ou réelles. L'opération sera ici la saisie d'une valeur. Pour corser un peu les choses, nous créerons dynamiquement dans la fonction une variable qui pourra alternativement avoir le type int ou double. Nous utiliserons ensembles des énumérations, des unions, et la directive typedef.

1) Nous définissons une union et deux numérations que nous connaissons déjà en utilisant la directive typedef.

2) La fonction lireExemple() est une fonction d'initialisation du type Exemple. Elle reçoit un pointeur de type union Exemple et une variable entière de type enum Etat. Elle renvoie une variable entière de type enum Logique.

3) Dans le programme principal on déclare l'union ex de type Exemple et l'énumération Etat qui lui est liée qu'on initialise aussitôt avec le mot vide qui correspond à son état actuel. On déclare également l'énumération test de type Logique qui sera dans tous les cas initialisée par la valeur de type Logique renvoyée par la fonction. On l'initialiserait tout de même, pour des raisons de sécurité, dans un programme d'application.

4) On donne la valeur entier à l'énumération et on appelle la fonction qui renverra une valeur affectée à la variable test. Le retour de la fonction et la variable sont bien sûr du même type Logique. L'adresse de la variable ex de type Exemple est &ex comme avec une structure. On passe également à la fonction l'énumération e_ex de type Etat dont la valeur actuelle est entier ou 2.

5) Pour ne pas travailler directement avec la variable qui lui a été passée, la fonction commence par créer dynamiquement une variable intermédiaire. Cette variable de type

Exemple contiendra les deux types int et double. Elle servira aux manipulations nécessaires dans la fonction et sera finalement affectée à e. Notez que le pointeur utilisé avec malloc() doit être du type de la variable manipule, qui est ici Exemple.

6) Une autre solution aurait consisté à créer dans la partie qui leur est propre une variable de type int et une de type double, la variable e_e ayant successivement les valeurs 2 et 8. int

*ptr = ( int * )malloc(e_e); et plus loin double *ptr = ( double * ) malloc(e_e); Notez que les pointeurs sont bien du type de la variable manipulée. La fonction est virtuellement dédoublée!

7) En cas d'échec de l'allocation, malloc() renvoie un pointeur NULL, la fonction est fermée et renvoie un entier de type Logique avec la valeur Echec. A la fin de la fonction la zone de mémoire allouée est libérée.

8) La saisie se fait de la même manière que pour les structures avec l'adresse passée &ptr->i . Finalement on affecte la valeur saisie dans ptr à la variable ex par pointeurs et la fonction renvoie Succes.