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:
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 :
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.
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;
}
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.
À 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 :
À 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.