Создание динамического массива, который принимает любой тип в C
Я пытаюсь найти способ сделать структуру для хранения динамического массива, который может работать с любым типом данных (включая определенные пользователем типы данных), до сих пор это то, что я придумал.
#define Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; }
typedef Vector(int) int_Vector;
int main(int argc, char* argv[]){
int_Vector vec;
return 0;
}
Пока это работает, мне было интересно, это хорошая практика? Должен ли я делать что-то подобное или есть лучший метод? Также есть способ реализовать это без части typedef Vector(int) int_vector
. В основном способ, который позволит мне использовать массив так же, как С++ использует шаблоны, где он будет выглядеть примерно так:
#define Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; }
int main(int argc, char* argv[]){
Vector(int) vec;
return 0;
}
В основном, чтобы избежать так много typedefs и иметь все под одним именем.
Ответы
Ответ 1
Ну нет, C не имеет системы шаблонов, поэтому вы не можете ее использовать.
Вы можете имитировать эффекты с помощью макросов, как и вы (довольно умное решение), но это, конечно, немного нестандартное и требует, чтобы пользователи вашего кода изучали макрос и его ограничения.
Обычно код C не пытается, так как это так неудобно.
Самый "общий" типичный вектор - это что-то вроде glib GArray
, но это не претендует на то, чтобы знать тип каждого элемента, Вместо этого пользователю остается только заботиться о доступе, и массив просто моделирует каждый элемент как n
байты.
Там _Generic()
в C11, который может немного помочь, я честно не очень разбираюсь в этом.
Ответ 2
Второй пример не будет работать, потому что две переменные определяются как разные типы, даже если их члены одинаковы. Почему это так, описано в моем существующем ответе.
Однако синтаксис можно сохранить одинаковым с использованием немного другого подхода:
#include <stdlib.h>
#define vector(type) struct vector_##type
struct vector_int
{
int* array;
size_t count;
} ;
int main(void)
{
vector(int) one = { 0 };
vector(int) two = { 0 };
one = two;
( void )one ;
return 0;
}
Использование, удивительно похожее на С++ vector<int>
, и полный пример можно увидеть здесь:
#include <stdlib.h>
#define vector_var(type) struct vector_##type
struct vector_int
{
int* array;
size_t count;
};
void vector_int_Push( struct vector_int* object , int value )
{
//implement it here
}
int vector_int_Pop( struct vector_int* object )
{
//implement it here
return 0;
}
struct vector_int_table
{
void( *Push )( struct vector_int* , int );
int( *Pop )( struct vector_int* );
} vector_int_table = {
.Push = vector_int_Push ,
.Pop = vector_int_Pop
};
#define vector(type) vector_##type##_table
int main(void)
{
vector_var(int) one = { 0 };
vector_var(int) two = { 0 };
one = two;
vector(int).Push( &one , 1 );
int value = vector(int).Pop( &one );
( void )value;
return 0;
}
Ответ 3
Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; }
также не работает для указателей на функции.
void*
является достаточным и корректным для указателя на любой объект, но не для указателя на функцию.
C позволяет указывать указатель на функцию одного типа как указатель на функцию другого типа. Используя union
двух ниже, код имеет достаточно места для сохранения указателя на любой тип. Управление типом и используемым элементом остается открытым.
union u_ptr {
void *object;
void (*function)();
}
Ответ 4
Неплохо. И я не вижу недостатка.
Просто для объяснения другого метода, чаще всего используемого в этом случае, используйте union:
typedef union { int i; long l; float f; double d; /*(and so on)*/} vdata;
typedef enum {INT_T,LONG_T,FLOAT_T, /*(and so on)*/} vtype;
typedef struct
{
vtype t;
vdata data
} vtoken;
typedef struct
{
vtoken *tk;
size_t sz;
size_t n;
} Vector;
Итак, это возможно. Перечисление типа данных можно избежать с помощью typedefs, но если вы используете смешанный (ex: sum long, double, to float и т.д.), Вы должны использовать их, поскольку int + double не равно double + int; Это также является причиной, потому что легче видеть, как профсоюзы выполняют эту работу. Вы оставляете арифметические правила нетронутыми.
Ответ 5
Развернув этот ответ в отношении решения полиморфизма, мы можем также включить его в типы указателей или пользовательские типы. Основное преимущество этого метода состоит в том, чтобы избавиться от перечисления "тип данных" и с ним все операторы переключения времени выполнения.
variant.h
#ifndef VARIANT_H
#define VARIANT_H
#include <stdio.h>
#include <stdint.h>
typedef void print_data_t (const void* data);
typedef void print_type_t (void);
typedef struct
{
void* data;
print_data_t* print_data;
print_type_t* print_type;
} variant_t;
void print_data_char (const void* data);
void print_data_short (const void* data);
void print_data_int (const void* data);
void print_data_ptr (const void* data);
void print_data_nothing (const void* data);
void print_type_char (void);
void print_type_short (void);
void print_type_int (void);
void print_type_int_p (void);
void print_type_void_p (void);
void print_type_void_f_void (void);
void print_data (const variant_t* var);
void print_type (const variant_t* var);
#define variant_init(var) { \
.data = &var, \
\
.print_data = _Generic((var), \
char: print_data_char, \
short: print_data_short, \
int: print_data_int, \
int*: print_data_ptr, \
void*: print_data_ptr, \
void(*)(void): print_data_nothing), \
\
.print_type = _Generic((var), \
char: print_type_char, \
short: print_type_short, \
int: print_type_int, \
int*: print_type_int_p, \
void*: print_type_void_p, \
void(*)(void): print_type_void_f_void) \
}
#endif /* VARIANT_H */
variant.c
#include "variant.h"
void print_data_char (const void* data) { printf("%c", *(const char*) data); }
void print_data_short (const void* data) { printf("%hd", *(const short*) data); }
void print_data_int (const void* data) { printf("%d", *(const int*) data); }
void print_data_ptr (const void* data) { printf("%p", data); }
void print_data_nothing (const void* data) {}
void print_type_char (void) { printf("char"); }
void print_type_short (void) { printf("short"); }
void print_type_int (void) { printf("int"); }
void print_type_int_p (void) { printf("int*"); }
void print_type_void_p (void) { printf("void*"); }
void print_type_void_f_void (void) { printf("void(*)(void)"); }
void print_data (const variant_t* var)
{
var->print_data(var->data);
}
void print_type (const variant_t* var)
{
var->print_type();
}
main.c
#include <stdio.h>
#include "variant.h"
int main (void)
{
char c = 'A';
short s = 3;
int i = 5;
int* iptr = &i;
void* vptr= NULL;
void (*fptr)(void) = NULL;
variant_t var[] =
{
variant_init(c),
variant_init(s),
variant_init(i),
variant_init(iptr),
variant_init(vptr),
variant_init(fptr)
};
for(size_t i=0; i<sizeof var / sizeof *var; i++)
{
printf("Type: ");
print_type(&var[i]);
printf("\tData: ");
print_data(&var[i]);
printf("\n");
}
return 0;
}
Вывод:
Type: char Data: A
Type: short Data: 3
Type: int Data: 5
Type: int* Data: 000000000022FD98
Type: void* Data: 000000000022FDA0
Type: void(*)(void) Data:
Недостатки с _Generic
для этой цели заключаются в том, что он блокирует использование частной инкапсуляции, поскольку она должна использоваться как макрос, чтобы передавать информацию о типе.
С другой стороны, "вариант" в этом случае должен поддерживаться для всех новых типов, с которыми вы сталкиваетесь, поэтому это не все, что практически или вообще.
Тем не менее эти трюки хороши для разных целей.