制作一个接受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部分来实现它。 基本上这种方式可以让我使用数组,就像c ++使用模板一样,它看起来像这样:

 #define Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; } int main(int argc, char* argv[]){ Vector(int) vec; return 0; } 

主要是为了避免这么多的typedef并将它们全部放在一个名字下面。

不,C没有模板系统,所以你不能使用它。

您可以像使用宏一样模拟宏的效果(非常聪明的解决方案),但这当然有点不标准,需要您的代码用户学习宏及其局限性。

通常C代码不会尝试,因为它太尴尬了。

最“通用”的典型向量类似于glib的GArray ,但它并不假装知道每个元素的类型。 相反,在访问时留给用户关心,并且数组仅将每个元素建模为n个字节。

C11中的_Generic()可能会有所帮助,老实说,我并不是很有经验。

第二个例子不起作用,因为即使它们的成员相同,这两个变量也被定义为不同的类型。 为什么会如此,我现有的答案都涵盖了这一点。

但是,使用稍微不同的方法可以保持语法相同:

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

这个用法与C ++的vector非常类似,可以在这里看到一个完整的例子:

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

Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; } 也失败了指向函数的指针。

对于指向任何对象的指针, void*足够且定义良好,但对于指向函数的指针则不然。

C允许将指向一种类型的函数的指针保存为指向另一种类型的函数的指针。 通过使用下面两者的union ,代码有足够的空间来保存指向任何类型的指针。 管理什么类型和使用的成员仍然是开放的。

 union u_ptr { void *object; void (*function)(); } 

不错。 我认为没有任何不利之处。 只是为了解释另一种方法,在这种情况下最常用的是使用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; 

所以这是可能的方式。 数据类型的枚举,你可以避免使用typedef,但如果你使用mixed(ex:sum long,to double,to float等)你必须使用它们,因为int + double不等于double + int; 这也是一个原因,因为更容易看到工会做这项工作。 你保持算术规则不受影响。

扩展关于多态解决方案的答案 ,我们也可以使它包括指针类型或用户定义的类型。 这种方法的主要优点是摆脱了“数据类型”枚举以及所有运行时检查switch语句。

variant.h

 #ifndef VARIANT_H #define VARIANT_H #include  #include  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  #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 

输出:

 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用于此目的的缺点是它阻止我们使用私有封装,因为它必须用作宏才能传递类型信息。

另一方面,这种情况下的“变体”必须为所提出的所有新类型进行维护,因此并非所有这些都是实用的或通用的。

对于各种类似的目的,这些技巧仍然很有用。