Les structures ont pour taille approximativement la somme des tailles de leurs composants. Leur taille peut donc devenir très grande. Or il arrive que certains champs ne soient pas utilisés lorsque dautres le sont, parce quils sont mutuellement incompatibles.
Pour résoudre partiellement ce problème, le langage fournit les unions. Il sagit de groupes de données, comme les structures, mais au lieu de se trouver placées les unes derrière les autres en mémoire, elles se trouvent toutes à la même adresse. Par exemple, lunion suivante :
union longgroupe { long l; unsigned mots[2]; unsigned char octets[4]; }
noccupe que quatre octets en mémoire (on suppose ici que cest la taille des entiers long
, mais cela peut être différent sur votre ordinateur). De la sorte, si lon écrit :
longgroupe lg;lg.l = 100000;
comme 100000 = 0x186A0
, on trouvera dans lg.mots
les valeurs { 0x86A0
, 0x1
} (soit 34464 et 1) (sur PC, le mots de poids faibles sont placés en premier) et dans lg.octets
{ 0xA0
, 0x86
, 0x1
, 0x0
} (soit 160, 134, 1 et 0). On a ainsi un moyen simple de décomposer un entier long, ou nimporte quoi dautre, en octets.
On peut initialiser une union en donnant entre accolades la valeur de son premier champ, comme ceci :
longgroupe lg = { 100000 };
Dune façon générale, une union peut contenir autant de champs de nimporte quel genre que souhaité, mais ils se trouvent tous à la même adresse, de sorte que la taille de la structure est celle du plus long de ces champs. En outre, lunion ne sait pas quel champ « est le bon » , en ce sens que nimporte lequel peut être modifié à tout moment, avec des répercussions sur tous les autres. Cest pourquoi il est peu recommandé de placer des informations différentes dans une union simplement pour récupérer de la place, si lon ne dispose pas dun moyen simple pour savoir quelle est le champ qui correspond à une information valable.
Un tel moyen consiste à utiliser les unions comme champs de structures ou mieux encore de classes. Prenons un exemple (un peu bête car il est difficile de ne pas en prendre un artificiel, vu quil existe souvent de meilleurs systèmes) : nous disposons dun fichier avec les noms complets de personnes. En Occident, le nom complet est généralement formé de deux mots, ici arbitrairement limités à 15 caractères. Dans certains pays, en Orient notamment, il est composé de trois mots plus petits (10 caractères), dont seul le premier représente le nom de famille, les deux autres formant le prénom. Voici un exemple de classe qui peut indifféremment stocker un nom oriental ou occidental. Un champ spécial indique si lon est en présence de lune ou lautre alternative :
class noms { char oriental; // 1 = oriental, 0 = occidental union { char nomorient[3][10]; char nomoccident[2][15]; }; public : noms(char *nom, char *prenom1, char *prenom2 = 0) { oriental = (prenom2 != 0); if (oriental) { strncpy(nomorient[0], nom, 10); strncpy(nomorient[1], prenom1, 10); strncpy(nomorient[2], prenom2, 10); } else { strncpy(nomoccident[0], nom, 15); strncpy(nomoccident[1], prenom1, 15); } } char *nom(void) { if (oriental) return nomorient[0]; else return nomoccident[0]; } char *prenom1(void) { if (oriental) return nomorient[1]; else return nomoccident[1]; } char *prenom2(void) { if (oriental) return nomorient[2]; else return ""; } char *prenomcomplet(void) { static char tampon[22]; if (oriental) { strcpy(tampon, nomorient[1]); strcat(tampon, "-"); strcat(tampon, nomorient[2]); return tampon; } else return nomoccident[1]; } };
Noter quil nest pas nécessaire de préciser un nom de champ pour lunion, les noms des champs internes suffisent. Par contre, quand une union contient un champ de type structure, classe ou union, il faut lui donner un nom.
Le constructeur suppose que lorsque aucun second prénom nest précisé, il sagit dun nom occidental. On peut donc écrire :
noms occ("Dupont", "Jean");noms ori("Fang", "Li", "Zi");
Nous laissons au lecteur le soin décrire une fonction renvoyant le nom complet, avec la convention que le prénom vient en premier en occident, tandis quil vient en dernier en orient.
Exercice 6.8 | Trouvez un moyen plus simple dimplanter une telle classe, sans utiliser dunion. |
Voir solution |
Cet exemple illustre le fait que les unions sont nettement moins dangereuses lorsquelles sont membres de classes contenant un indicateur qui les contrôle.
Les unions peuvent avoir des méthodes et des constructeurs, mais tous leurs membres sont obligatoirement publics ; en outre, Turbo C++ naccepte pas que les unions anonymes aient des méthodes.
Exercice 6.9 | Sans écrire les méthodes, donner une implantation dune classe pouvant contenir soit un nom et un prénom de 15 caractères chacun, soit un nom de 15 caractères, un prénom de 14 et une initiale intermédiaire de 1 (nom américain), soit un nom en trois parties à lorientale. |
Voir solution |
Sauf pour des décompositions comme longgroupe
lintérêt des unions est assez faible en C++, dautant que, contrairement à ce qui se passe par exemple en Pascal, les parties qui se recouvrent dans une union sont nécessairement réduites à un seul élément, et que lunion elle-même est tout entière en mode de recouvrement, ce qui oblige à utiliser des structures imbriquées compliquées pour faire recouvrir des données différentes.
Précédent | Sommaire | Suivant |