Вы не можете иметь массив разных типов, точно. Но вы можете добиться аналогичного эффекта (по крайней мере, для некоторых целей) несколькими различными способами.
Если вы хотите, чтобы несколько значений разных типов были упакованы вместе, но количество и типы значений не меняются, вам просто нужно 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);