Comment un type de données mixte (int, float, char, etc.) peut-il être stocké dans un tableau?

je veux stocker des types de données dans un tableau. Comment pourrait-on le faire?

141
demandé sur chanzerre 2013-09-02 20:26:58

6 réponses

vous pouvez faire les éléments de tableau un syndicat discriminé, alias syndicat étiqueté .

struct {
    enum { is_int, is_float, is_char } type;
    union {
        int ival;
        float fval;
        char cval;
    } val;
} my_array[10];

l'élément type est utilisé pour choisir l'élément du union qui doit être utilisé pour chaque élément du tableau. Donc, si vous voulez stocker un int dans le premier élément, vous feriez:

my_array[0].type = is_int;
my_array[0].val.ival = 3;

Lorsque vous souhaitez accéder à un élément du tableau, vous devez d'abord vérifier le type, puis utilisez le membre correspondant de l'union. Une déclaration switch est utile:

switch (my_array[n].type) {
case is_int:
    // Do stuff for integer, using my_array[n].ival
    break;
case is_float:
    // Do stuff for float, using my_array[n].fval
    break;
case is_char:
    // Do stuff for char, using my_array[n].cvar
    break;
default:
    // Report an error, this shouldn't happen
}

il appartient au programmeur de s'assurer que le membre type correspond toujours à la dernière valeur stockée dans le union .

236
répondu Barmar 2016-01-08 22:54:05

Use syndicat:

union {
    int ival;
    float fval;
    void *pval;
} array[10];

Vous devez garder une trace du type de chaque élément, bien.

33
répondu 2013-09-02 16:28:50
Les éléments du tableau

doivent avoir la même taille, c'est pourquoi ce n'est pas possible. Vous pourriez travailler autour de lui en créant un variante type :

#include <stdio.h>
#define SIZE 3

typedef enum __VarType {
  V_INT,
  V_CHAR,
  V_FLOAT,
} VarType;

typedef struct __Var {
  VarType type;
  union {
    int i;
    char c;
    float f;
  };
} Var;

void var_init_int(Var *v, int i) {
  v->type = V_INT;
  v->i = i;
}

void var_init_char(Var *v, char c) {
  v->type = V_CHAR;
  v->c = c;
}

void var_init_float(Var *v, float f) {
  v->type = V_FLOAT;
  v->f = f;
}

int main(int argc, char **argv) {

  Var v[SIZE];
  int i;

  var_init_int(&v[0], 10);
  var_init_char(&v[1], 'C');
  var_init_float(&v[2], 3.14);

  for( i = 0 ; i < SIZE ; i++ ) {
    switch( v[i].type ) {
      case V_INT  : printf("INT   %d\n", v[i].i); break;
      case V_CHAR : printf("CHAR  %c\n", v[i].c); break;
      case V_FLOAT: printf("FLOAT %f\n", v[i].f); break;
    }
  }

  return 0;
}

La taille de l'élément de l'union est la taille du plus grand élément, 4.

20
répondu 2013-09-02 16:57:06

il y a un style différent de définir l'union-étiquette (par n'importe quel nom) que IMO rendre beaucoup plus agréable à utiliser , en enlevant l'union interne. C'est le style utilisé dans le système X Window pour des choses comme des événements.

L'exemple dans la réponse de Barmar donne le nom val à l'union interne. L'exemple de la Sp.'s Réponse utilise une union anonyme pour éviter d'avoir à spécifier le .val. chaque fois que vous accédez à la variante dossier. Malheureusement, les structures internes et les syndicats" anonymes " ne sont pas disponibles en C89 ou C99. C'est une extension du compilateur, et donc intrinsèquement non portable.

une meilleure façon pour IMO est d'Inverser toute la définition. Faites que chaque donnée tape sa propre structure, et mettez la balise (type specifier) dans chaque structure.

typedef struct {
    int tag;
    int val;
} integer;

typedef struct {
    int tag;
    float val;
} real;

ensuite, vous enveloppez ceux-ci dans une union de haut niveau.

typedef union {
    int tag;
    integer int_;
    real real_;
} record;

enum types { INVALID, INT, REAL };

maintenant, il peut sembler que nous répétons nous - mêmes, et nous sommes . Mais considérer que cette définition est susceptible d'être isolé dans un seul fichier. Mais nous avons éliminé le bruit de spécifier l'intermédiaire .val. avant d'obtenir les données.

record i;
i.tag = INT;
i.int_.val = 12;

record r;
r.tag = REAL;
r.real_.val = 57.0;

à la place, il va à la fin, où il est moins odieux. : D

une Autre chose que cela permet une forme d'héritage. Edit: cette partie n'est pas standard C, mais utilise une extension GNU.

if (r.tag == INT) {
    integer x = r;
    x.val = 36;
} else if (r.tag == REAL) {
    real x = r;
    x.val = 25.0;
}

integer g = { INT, 100 };
record rg = g;

coulée et vers le bas de la coulée.


Edit: un gotcha d'être au courant de est si vous construisez un de ceux-ci avec C99 initialisateurs désignés. Tous les initialisateurs membres devraient passer par le même membre du syndicat.

record problem = { .tag = INT, .int_.val = 3 };

problem.tag; // may not be initialized

le .tag initialiseur peut être ignoré par un compilateur d'optimisation, parce que le .int_ initialiseur qui suit alias la même zone de données. Même si nous connaître la disposition (!), et il devrait être ok. Non, pas du tout. Utilisez la balise" interne " à la place (elle superpose la balise externe, comme nous le voulons, mais ne confond pas le compilateur).

record not_a_problem = { .int_.tag = INT, .int_.val = 3 };

not_a_problem.tag; // == INT
9
répondu luser droog 2015-08-19 04:00:42

vous pouvez faire un tableau void * , avec un tableau séparé de size_t. mais vous perdez le type d'information.

Si vous avez besoin de conserver le type d'information d'une certaine façon, conservez un troisième tableau de int (où int est une valeur énumérée), puis codez la fonction qui émet en fonction de la valeur enum .

6
répondu dzada 2013-09-03 05:29:08

Union est la voie standard à suivre. Mais vous avez d'autres solutions. L'un d'eux est pointeur étiqueté , qui implique de stocker plus d'informations dans le "libre" bits d'un pointeur.

selon les architectures, vous pouvez utiliser les bits bas ou haut, mais le moyen le plus sûr et le plus portable est d'utiliser le bits bas inutilisés en profitant de la mémoire alignée. Par exemple, dans les systèmes 32 bits et 64 bits, les pointeurs vers int doivent être des multiples de 4 et les 2 bits les moins significatifs doivent être 0, donc vous pouvez les utiliser pour stocker le type de vos valeurs. Bien sûr, vous devez effacer les bits d'étiquette avant de déréférencier le pointeur. Par exemple, si votre type de données est limité à 4 types différents, alors vous pouvez l'utiliser comme ci-dessous

void* tp; // tagged pointer
enum { is_int, is_double, is_char_p, is_char } type;
// ...
intptr_t addr = (intptr_t)tp & ~0x03; // clear the 2 low bits in the pointer
switch ((intptr_t)tp & 0x03)          // check the tag (2 low bits) for the type
{
case is_int:    // data is int
    printf("%d\n", *((int*)addr));
    break;
case is_double: // data is double
    printf("%f\n", *((double*)addr));
    break;
case is_char_p: // data is char*
    printf("%s\n", (char*)addr);
    break;
case is_char:   // data is char
    printf("%c\n", *((char*)addr));
    break;
}

si vous pouvez vous assurer que les données sont alignées de 8 octets (comme pour les pointeurs dans les systèmes 64 bits, ou long long et uint64_t ...), vous aurez un peu plus de la balise.

ceci a un inconvénient que vous aurez besoin de plus de mémoire si les données n'ont pas été stockées dans une variable ailleurs. Par conséquent, si le type et la portée de vos données sont limités, vous pouvez stocker les valeurs directement dans le pointeur. Cette technique a été utilisée dans la version 32 bits de moteur V8 de Chrome , où il vérifie le plus petit bit de l'adresse pour voir si c'est un pointeur pointeur vers un autre objet (comme double, grands entiers, chaîne ou un autre objet) ou un valeur signée 31 bits (appelé smi -petit entier ). Si C'est un int , Chrome fait simplement un déplacement arithmétique à droite 1 bit pour obtenir la valeur, sinon le pointeur est déréférencé.


sur la plupart des systèmes 64 bits actuels l'espace d'adresse virtuelle est encore beaucoup moins plus de 64 bits, donc le haut les bits les plus significatifs peuvent également être utilisés comme des étiquettes . Selon l'architecture, vous avez différentes façons d'utiliser ces balises. ARM , 68k et beaucoup d'autres vous permettent d'ignorer les bits supérieurs, de sorte que vous pouvez les utiliser librement sans vous soucier de segfault ou quoi que ce soit. De l'article wikipedia lié ci-dessus:

un exemple significatif de l'utilisation de pointeurs est L'objectif-C runtime sur iOS 7 sur ARM64, notamment utilisé sur l'iPhone 5S. Dans iOS 7, les adresses virtuelles sont de 33 bits (Byte-aligned), donc les adresses alignées sur les mots n'utilisent que 30 bits (les 3 bits les moins significatifs sont 0), laissant 34 bits pour les tags. Les pointeurs de classe Objective-C sont alignés sur word, et les champs de balise sont utilisés à de nombreuses fins, comme stocker un nombre de référence et si l'objet a un destructeur.[2] [3]

premières versions de MacOS utilisées appelé Poignées pour stocker des références à des objets de données. Les bits élevés de l'adresse indiquaient si l'objet de données était verrouillé, purgeable et/ou provenait d'un fichier de ressources, respectivement. Cela a causé des problèmes de compatibilité lorsque MacOS adressing avancé de 24 bits à 32 bits dans le système 7.

x86_64 vous pouvez toujours utiliser les bits élevés que les balises avec soin . Bien sûr, vous n'avez pas besoin d'utiliser tous les 16 bits et peut laisser des bits pour l'avenir

dans les versions antérieures de Mozilla Firefox ils utilisent également petites optimisations entières comme V8, avec le 3 bits Bas utilisés pour stocker le type (int, chaîne, objet... etc.). Mais depuis JägerMonkey ils ont pris un autre chemin ( nouvelle représentation de la valeur JavaScript de Mozilla , lien de sauvegarde ). La valeur est maintenant toujours stockée dans une double précision 64 bits variable. Lorsque le double est un normalisé , il peut être utilisé directement dans les calculs. Cependant, si les 16 bits les plus élevés sont tous des bits 1s, ce qui dénote un NaN , le bas 32 bits stockera l'adresse (dans un ordinateur 32 bits) à la valeur ou à la valeur directement, les 16 bits restants seront utilisés pour stocker le type. Cette technique est appelée Nan-boxe ou boxe religieuse. Il est également utilisé dans le JavaScriptCore de WebKit 64 bits et le SpiderMonkey de Mozilla avec le pointeur étant stocké dans le bas 48 bits. Si votre type de données principal est à virgule flottante, c'est la meilleure solution et offre de très bonnes performances.

plus d'informations sur les techniques ci-dessus: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations

2
répondu phuclv 2018-07-09 03:47:40