Массив указателей на несколько типов, C - PullRequest
5 голосов
/ 17 октября 2011

Можно ли иметь массив нескольких типов, используя malloc?

EDIT:

В настоящее время у меня есть:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define int(x) *((int *) x)


int main() {
        void *a[10];

        a[0] = malloc(sizeof(int));
        int(a[0]) = 4;

        char *b = "yola.";

        a[1] = malloc(strlen(b)*sizeof(char));
        a[1] = b;

        printf("%d\n", int(a[0]));
        printf("%s\n", a[1]);
}

Но это грязно. Другие способы?

РЕДАКТИРОВАТЬ: немного почистил.

Ответы [ 6 ]

15 голосов
/ 18 октября 2011

Вы не можете иметь массив разных типов, точно. Но вы можете добиться аналогичного эффекта (по крайней мере, для некоторых целей) несколькими различными способами.

Если вы хотите, чтобы несколько значений разных типов были упакованы вместе, но количество и типы значений не меняются, вам просто нужно struct и вы можете получить к ним доступ по имени:

struct s_item {
  int     number;
  char    str[100];
} item;
item.number = 5;
strcpy(item.str,"String less than 100 chars");

Если вы знаете, какие типы вы можете использовать, вы можете создать объединение или структуру, содержащую объединение, чтобы пометить его типом. Затем вы можете создать массив из них. Член type позволяет вам проверить, что вы сохранили в каждом элементе массива позже.

enum ElementType { et_str, et_int, et_dbl };
struct Element {
  ElementType type;
  union {
    char      *str;
    int       i;
    double    d;
  }
};

struct Element *arr = malloc(sizeof(struct Element) * 3);
arr[0].type = et_str;
arr[0].str = strdup("String value"); /* remember to free arr[0].str */
arr[1].type = et_int;
arr[1].i = 5;
arr[2].type = et_dbl;
arr[2].d = 27.3;

/* access the values.. */
for (int i = 0; i < 3; i++) {
  switch(arr[i].type) {
    case et_str: printf("String: %s\n",arr[i].str); break;
    case et_int: printf("Integer: %d\n",arr[i].i); break;
    case et_dbl: printf("Double: %f\n",arr[i].d); break;
  }
}

/* The strings are dynamically allocated, so free the strings */
for (int i = 0; i < 3; i++)
  if (arr[0].type == et_str) free(arr[0].str);
/* free the malloc'ed array */
free(arr);
/* etc., etc. */

Этот подход может тратить пространство, потому что:

  • Каждый элемент имеет дополнительное значение для отслеживания типа данных, которые он содержит
  • Структура может иметь дополнительные отступы между ее членами
  • Типы в объединении могут быть разных размеров, в этом случае объединение будет таким же большим, как самый большой тип

Если у вас есть другой способ узнать, какой тип вы сохранили в каждом элементе, вы можете использовать только простое объединение без структуры, охватывающей его. Это немного более компактно, но каждый элемент будет по крайней мере таким же большим, как самый большой тип в объединении.


Вы также можете создать массив значений void *. Если вы сделаете это, вам придется как-то распределить элементы и назначить их адреса элементам массива. Затем вам нужно привести их к соответствующему типу указателя для доступа к элементам. C не предоставляет никакой информации о типе времени выполнения, поэтому нет способа узнать, на какой тип данных указывает каждый элемент, от самого указателя - вы должны следить за этим самостоятельно. Этот подход намного более компактен, чем другие, когда типы, которые вы храните, большие и их размеры сильно различаются, поскольку каждый выделяется отдельно от массива, и ему может быть предоставлено только пространство, необходимое для этого типа. Для простых типов вы ничего не выиграете от использования объединения.

void **arr = malloc(3 * sizeof(void *));
arr[0] = strdup("Some string"); /* is a pointer already */
arr[1] = malloc(sizeof(int));
*((int *)(arr[1])) = 5;
arr[2] = malloc(sizeof(double));
*((double *)(arr[2])) = 27.3;

/* access the values.. */
printf( "String: %s\n", (char *)(arr[0]) );
printf( "Integer: %d\n", *((int *)(arr[1])) );
printf( "Double: %f\n", *((double *)(arr[2])) );

/* ALL values were dynamically allocated, so we free every one */
for (int i = 0; i < 3; i++)
  free(arr[i]);
/* free the malloc'ed array */
free(arr);

Если вам нужно отслеживать тип в массиве, вы также можете использовать структуру для хранения типа вместе с указателем, аналогично предыдущему примеру с объединением. Это, опять-таки, действительно полезно, только когда хранимые типы велики и имеют большой размер.

enum ElementType { et_str, et_int, et_dbl };
struct Element {
  ElementType type;
  void        *data;
};

struct Element *arr = malloc(sizeof(struct Element) * 3);
arr[0].type = et_str;
arr[0].data = strdup("String value");
arr[1].type = et_int;
arr[1].data = malloc(sizeof(int));
*((int *)(arr[1].data)) = 5;
arr[2].type = et_dbl;
arr[2].data = malloc(sizeof(double));
*((double *)(arr[2].data)) = 27.3;

/* access the values.. */
for (int i = 0; i < 3; i++) {
  switch(arr[i].type) {
    case et_str: printf( "String: %s\n", (char *)(arr[0].data) ); break;
    case et_int: printf( "Integer: %d\n", *((int *)(arr[1].data)) ); break;
    case et_dbl: printf( "Double: %f\n", *((double *)(arr[2].data)) ); break;
  }
}

/* again, ALL data was dynamically allocated, so free each item's data */
for (int i = 0; i < 3; i++)
  free(arr[i].data);
/* then free the malloc'ed array */
free(arr);
3 голосов
/ 17 октября 2011

Нет, все элементы должны быть одного типа.Вы могли бы сойти с рук с массивом структур.

struct mixed {
    enum {
        INTEGER,
        STRING,
    } type;
    union {
        int num;
        char *str;
    } value;
};


struct mixed v[10];
v[0].type = INTEGER;
v[0].value.num = 10;

Я сам никогда бы такого не сделал (кажется грязным).Но ваш подход типа «массив пустот» аналогичен: вы должны где-то хранить информацию о типе.

2 голосов
/ 17 октября 2011

Вы можете легко иметь массив указателей, которые указывают на различные типы. Конечно, чтобы это было очень полезно, вам нужно было бы каким-то образом записать или определить, на какой тип в настоящее время ссылается каждый элемент.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

    // This implicitly allocates memory to store 10 pointers
    void *a[10];

    // The first element will be a pointer to an int
    // Allocate the memory it points to, then assign it a value.
    a[0] = malloc(sizeof(int));
    *( (int *)a[0] ) = 4;

    // The second element will be a pointer to char; for simplicity,
    // I'm hardcoding the length of the string + 1 for the null byte.
    a[1] = malloc( 6*sizeof(char) );
    strncpy( a[1], "hello", 5 );

    printf( "%d\n", *( (int *)a[0] ) );
    printf( "%s\n", a[1] );

}
1 голос
/ 17 октября 2011

Я не уверен, чего вы хотите достичь, но есть две возможности:

1 - Вы на самом деле не хотите массив, а структуру:

struct {
    int number;
    char *string;
} a;

В этом случае вы можете получить доступ к номеру как a.number и строке как a.string.

2 - Вы хотите массив вариантного типа. В C вы можете использовать объединения (желательно с тегами) для типов вариантов:

struct Variant {
    int type;
    union {
        int number;
        char *string;
    }
}

Затем вы можете закодировать ваш тип с 0 для числа и 1 для строки. Конечно, лучше использовать enum вместо integer для типа.

0 голосов
/ 28 декабря 2018

Самая большая проблема - заставить компилятор C по-разному обрабатывать каждый элемент массива.

Могу ли я предложить гибридный подход.

Отложите несколько указателей, каждый со своими соответствующими определениями структуры.

Когда вы решите, какой тип элемента вы хотите использовать, используйте этот указатель для malloc и setup, а затем используйте позже.

Затем скопируйте значение этого указателя в массив указателей.

Позже, когда вы захотите использовать этот элемент, скопируйте элемент массива в его указатель aproprate, чтобы сделать компилятор счастливым.

Пожалуйста, имейте в виду, что это только пример, у него есть некоторые короткие переходы, такие как сложность сортировки или вставка узла в середине, но ...

Например:

struct      this_type {
    char        mod_kind[20];
    int         this_int;
};
struct      that_type {
    char        mod_kind[20];
    char        that_string[20];
};

void  *list_o_pointers[10];
struct  this_type       *p_this;
struct  that_type       *p_that;

p_this = malloc(sizeof(struct this_type));
list_o_pointers[0] = p_this;
strcpy(p_this->mod_kind, "this kind");  // or whatever you want to use to differentate different types

p_that = malloc(sizeof(struct that_type));
list_o_pointers[0] = p_that;
strcpy(p_that->mod_kind, "that kind");

// later
p_this = list_o_pointers[0];
p_that = list_o_pointers[0];
if (strstr(p_this->mod_kind, "this kind")) { /* do this stuff */ }
if (strstr(p_that->mod_kind, "that kind")) { /* do that stuff */}

это решает грубость таких вещей, как необходимость разыгрывать * ((double *) (arr [2] .data)) =, а также помогает с удобочитаемостью.

Это может сломаться, если у вас много разных структур узлов.

Это немного грубая сила, но (ИМХО) это немного легче для мозга. Массив является простым массивом, и каждый узел прост. Узлам не нужен указатель «следующий», как в связанном списке.

Mark.

0 голосов
/ 17 октября 2011

Это потому, что вы пытаетесь сохранить значение в слоте, который ожидает указатель.Попробуйте следующее (проверка ошибок для краткости опущена)

int* pIntTemp = malloc(sizeof(int));
*pIntTemp = 4;
a[0] = pIntTemp;
...