Pregunta ¿Cómo se puede almacenar un tipo de datos mixtos (int, float, char, etc.) en una matriz?


Quiero almacenar tipos de datos mixtos en una matriz. ¿Cómo podría uno hacer eso?


139
2017-09-02 16:26


origen


Respuestas:


Puedes hacer que los elementos de la matriz sean una unión discriminada, también conocida como unión etiquetada.

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

los type miembro se utiliza para mantener la elección de qué miembro del union debe ser utilizado para cada elemento de la matriz. Entonces, si quieres almacenar una int en el primer elemento, harías:

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

Cuando desee acceder a un elemento de la matriz, primero debe verificar el tipo, luego usar el miembro correspondiente de la unión. UN switch declaración es útil:

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
}

Depende del programador asegurarse de que type miembro siempre corresponde al último valor almacenado en el union.


235
2017-09-02 16:31



Use una unión:

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

Sin embargo, tendrá que hacer un seguimiento del tipo de cada elemento.


33
2017-09-02 16:28



Los elementos de la matriz necesitan tener el mismo tamaño, por eso no es posible. Podría solucionarlo creando un tipo de variante:

#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;
}

El tamaño del elemento de la unión es el tamaño del elemento más grande, 4.


20
2017-09-02 16:47



Hay un estilo diferente de definir el tag-union (con el nombre que sea) que IMO lo hace mucho más agradable utilizar, al eliminar la unión interna. Este es el estilo utilizado en el Sistema X Window para cosas como Eventos.

El ejemplo en la respuesta de Barmar da el nombre val a la unión interna. El ejemplo en la respuesta de Sp. Utiliza una unión anónima para evitar tener que especificar el .val. cada vez que accede al registro de variante. Desafortunadamente, las estructuras y uniones internas "anónimas" no están disponibles en C89 o C99. Es una extensión de compilación, y por lo tanto inherentemente no portátil.

Una mejor manera de IMO es invertir toda la definición. Haga que cada tipo de datos sea su propia estructura y coloque la etiqueta (especificador de tipo) en cada estructura.

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

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

Luego envuelve estos en una unión de nivel superior.

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

enum types { INVALID, INT, REAL };

Ahora puede parecer que nos estamos repitiendo, y nosotros son. Pero considere que esta definición es probable que esté aislada en un solo archivo. Pero hemos eliminado el ruido de especificar el intermediario .val. antes de llegar a los datos.

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

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

En cambio, va al final, donde es menos desagradable. :RE

Otra cosa que esto permite es una forma de herencia. Editar: esta parte no es estándar C, pero usa una extensión 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;

Up-casting y down-casting.


Editar: Uno debe saber si está construyendo uno de estos con inicializadores designados C99. Todos los inicializadores de miembro deben ser a través del mismo miembro de unión.

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

problem.tag; // may not be initialized

los .tag el inicializador puede ser ignorado por un compilador de optimización, porque el .int_ inicializador que sigue alias la misma área de datos Aunque nosotros conocer el diseño (!), y debería estar bien No, no lo es. Utilice la etiqueta "interna" en su lugar (se superpone a la etiqueta externa, tal como queremos, pero no confunde el compilador).

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

not_a_problem.tag; // == INT

9
2017-09-03 06:24



Puedes hacer un void * array, con una matriz separada de size_t.Pero pierdes el tipo de información.
Si necesita mantener el tipo de información de alguna manera, mantenga una tercera matriz de int (donde int es un valor enumerado) Luego, codifique la función que arroja dependiendo de la enum valor.


6
2017-09-02 16:28



La unión es el camino estándar a seguir. Pero tienes otras soluciones también. Uno de esos es puntero etiquetado, que implica almacenar más información en "gratis" bits de un puntero.

Dependiendo de las arquitecturas puede usar los bits bajos o altos, pero la forma más segura y más portátil es usar el bits bajos no utilizados aprovechando la ventaja de la memoria alineada. Por ejemplo, en sistemas de 32 y 64 bits, punteros a int debe ser múltiplos de 4 y los 2 bits menos significativos deben ser 0, por lo tanto, puede usarlos para almacenar el tipo de sus valores. Por supuesto, debe borrar los bits de etiqueta antes de desreferenciar el puntero. Por ejemplo, si su tipo de datos está limitado a 4 tipos diferentes, puede usarlo como se muestra a continuación.

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 puede asegurarse de que los datos estén alineados a 8 bytes (como para los punteros en sistemas de 64 bits, o long long y uint64_t...), tendrá un bit más para la etiqueta.

Esto tiene la desventaja de que necesitará más memoria si los datos no se han almacenado en una variable en otro lugar. Por lo tanto, en caso de que el tipo y rango de sus datos sea limitado, puede almacenar los valores directamente en el puntero. Esta técnica se ha utilizado en la versión de 32 bits de Motor V8 de Chrome, donde verifica el bit menos significativo de la dirección para ver si eso es puntero a otro objeto (como números enteros dobles, grandes, cadenas o algún objeto) o un Valor con signo de 31 bits (llamado smi - pequeño entero) Si es un intChrome simplemente hace un desplazamiento aritmético a la derecha de 1 bit para obtener el valor, de lo contrario, el puntero se desreferencia.


En la mayoría de los sistemas de 64 bits actuales, el espacio de direcciones virtuales es aún mucho menor que 64 bits, de ahí alto los bits más significativos también se pueden utilizar como etiquetas. Dependiendo de la arquitectura, tienes diferentes formas de usarlos como etiquetas. BRAZO, 68k y muchos otros le permiten ignorar los bits superiores, para que pueda usarlos libremente sin preocuparse por segfault ni nada. Del artículo vinculado de wikipedia arriba:

Un ejemplo significativo del uso de punteros etiquetados es el tiempo de ejecución de Objective-C en iOS 7 en ARM64, notablemente utilizado en el iPhone 5S. En iOS 7, las direcciones virtuales son 33 bits (alineadas por byte), por lo que las direcciones alineadas con palabras solo usan 30 bits (3 bits menos significativos son 0), dejando 34 bits para las etiquetas. Los punteros de la clase Objective-C están alineados con las palabras, y los campos de las etiquetas se utilizan para muchos fines, como el almacenamiento de un recuento de referencias y si el objeto tiene un destructor. [2] [3]

Las primeras versiones de MacOS usaban direcciones etiquetadas llamadas Handles para almacenar referencias a objetos de datos. Los bits altos de la dirección indicaban si el objeto de datos estaba bloqueado, purgable y / o se originó a partir de un archivo de recursos, respectivamente. Esto causó problemas de compatibilidad cuando el direccionamiento de MacOS avanzó de 24 bits a 32 bits en el Sistema 7.

En x86_64 aún puedes usar los bits altos como etiquetas con cuidado. Por supuesto, no necesita usar todos esos 16 bits y puede dejar algunos bits para futuras pruebas

En versiones anteriores de Mozilla Firefox también usan pequeñas optimizaciones enteras como V8, con el 3 bits bajos utilizados para almacenar el tipo (int, cadena, objeto ... etc.). Pero desde JägerMonkey tomaron otro camino (Nueva representación del valor de JavaScript de Mozilla, enlace de respaldo) El valor ahora se almacena siempre en una variable de precisión doble de 64 bits. Cuando el double es un normalizado uno, se puede usar directamente en los cálculos. Sin embargo, si los 16 bits altos de ella son todos 1s, que denotan un Yaya, los 32 bits bajos almacenarán la dirección (en una computadora de 32 bits) en el valor o el valor directamente, los 16 bits restantes se usarán para almacenar el tipo. Esta técnica se llama NaN-boxeo o monja-boxeo. También se usa en JavaScriptCore WebKit de 64 bits y SpiderMonkey de Mozilla con el puntero almacenado en los 48 bits más bajos. Si su tipo de datos principal es de punto flotante, esta es la mejor solución y ofrece muy buen rendimiento.

Lea más sobre las técnicas anteriores: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations


2
2017-07-19 18:15



Preguntas populares