如何在c中使用void *制作generics函数?

我有一个incr函数将值递增1我想让它通用,因为我不想为相同的function创建不同的函数。

假设我想将intfloatchar增加1

 void incr(void *vp) { (*vp)++; } 

但我知道的问题是Dereferencing a void pointer is undefined behaviour 。 有时可能会出错: Invalid use of void expression

我的main是:

 int main() { int i=5; float f=5.6f; char c='a'; incr(&i); incr(&f); incr(&c); return 0; } 

问题是如何解决这个问题? 有没有办法只在C解决它

要么

我必须为每种数据类型定义incr()吗? 如果是,那么什么是使用void *

swap()sort()有同样的问题。我想用相同的函数交换和排序各种数据类型。

您可以将第一个实现为宏:

 #define incr(x) (++(x)) 

当然,如果你不小心,这会产生令人不快的副作用。 这是C提供的唯一方法,可以将相同的操作应用于各种类型。 特别是,由于宏是使用文本替换实现的,所以在编译器看到它时,你只需要文字代码++whatever; ,它可以适用于您提供的项目类型。 使用指向void的指针,您对实际类型知之甚少(如果有的话),因此您无法对该数据进行太多直接操作)。

void *通常在有问题的函数不需要知道所涉及数据的确切类型时使用。 在某些情况下(例如, qsort ),它使用回调函数来避免必须知道数据的任何细节。

既然它同时进行排序和交换,那么让我们更详细地看一下qsort。 它的签名是:

 void qsort(void *base, size_t nmemb, size_t size, int(*cmp)(void const *, void const *)); 

所以,第一个是你问的void * – 指向要排序的数据的指针。 第二个告诉qsort数组中元素的数量。 第三,数组中每个元素的大小。 最后一个是指向可以比较单个项的函数的指针,因此qsort不需要知道如何执行此操作。 例如,qsort内部的某些代码类似于:

 // if (base[j] < base[i]) ... if (cmp((char *)base+i, (char *)base+j) == -1) 

同样,要交换两个项目,它通常会有一个本地数组用于临时存储。 然后它将字节从array[i]复制到temp,然后从array[j]复制到array[i] ,最后从temp复制到array[j]

 char temp[size]; memcpy(temp, (char *)base+i, size); // temp = base[i] memcpy((char *)base+i, (char *)base+j, size); // base[i] = base[j] memcpy((char *)base+j, temp, size); // base[j] = temp 

使用void *不会给你多态行为,这是我认为你正在寻找的。 void *只允许您绕过堆变量的类型检查。 要实现实际的多态行为,您必须将类型信息作为另一个变量传入并在incr函数中检查它,然后将指针转换为所需类型,或者将数据上的任何操作作为函数指针传递(其他人有提到qsort作为例子)。 C没有内置于该语言的自动多态性,因此您可以模拟它。 在幕后,构建多态的语言在幕后做了类似的事情。

详细说明, void *是指向通用内存块的指针,它可以是任何东西:int,float,string等。内存块的长度甚至不存储在指针中,更不用说类型了数据。 请记住,在内部,所有数据都是位和字节,而类型实际上只是逻辑数据物理编码的标记,因为本质上,位和字节是无类型的。 在C中,此信息不与变量一起存储,因此您必须自己将其提供给编译器,以便它知道是否应用操作将位序列视为2的补码整数,IEEE 754双精度浮点,ASCII字符数据,function等; 这些都是不同类型数据的格式和操作的特定标准。 当您将void *转换为指向特定类型的指针时, 作为程序员断言实际指向的数据是您要将其转换为的类型。 否则,你可能会出现奇怪的行为。

那么什么是void *有益的? 无论何种类型,都可以处理数据块。 这对于内存分配,复制,文件操作和传递指针到函数等问题是必要的。 但在几乎所有情况下,C程序员通过使用具有内置操作的类型构造数据,尽可能地从这种低级表示中抽象出来; 或者使用结构,对程序员定义的这些结构的操作作为函数。

您可以查看维基百科的解释以获取更多信息。

你不能完全按照你的要求去做 – 像增量这样的运算符需要使用特定的类型。 所以,你可以这样做:

 enum type { TYPE_CHAR, TYPE_INT, TYPE_FLOAT }; void incr(enum type t, void *vp) { switch (t) { case TYPE_CHAR: (*(char *)vp)++; break; case TYPE_INT: (*(int *)vp)++; break; case TYPE_FLOAT: (*(float *)vp)++; break; } } 

然后你会这样称呼它:

 int i=5; float f=5.6f; char c='a'; incr(TYPE_INT, &i); incr(TYPE_FLOAT, &f); incr(TYPE_CHAR, &c); 

当然,除了定义单独的incr_int()incr_float()incr_char()函数之外,这并没有真正给你任何东西 – 这不是void *的目的。

当你编写的算法不关心对象的真实类型时,就会实现void *的目的。 一个很好的例子是标准排序函数qsort() ,它被声明为:

 void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *)); 

这可用于对任何类型对象的数组进行排序 – 调用者只需提供可比较两个对象的比较函数。

swap()sort()函数都属于这一类。 swap()更容易 – 算法不需要知道除交换它们的对象大小之外的任何东西:

 void swap(void *a, void *b, size_t size) { unsigned char *ap = a; unsigned char *bp = b; size_t i; for (i = 0; i < size; i++) { unsigned char tmp = ap[i]; ap[i] = bp[i]; bp[i] = tmp; } } 

现在给定任何数组,您可以交换该数组中的两个项目:

 int ai[]; double ad[]; swap(&ai[x], &ai[y], sizeof(int)); swap(&di[x], &di[y], sizeof(double)); 

使用“通用”交换的示例。

此代码交换两块内存。

 void memswap_arr(void* p1, void* p2, size_t size) { size_t i; char* pc1= (char*)p1; char* pc2= (char*)p2; char ch; for (i= 0; i 

你这样称呼它:

 int main() { int i1,i2; double d1,d2; i1= 10; i2= 20; d1= 1.12; d2= 2.23; memswap_arr(&i1,&i2,sizeof(int)); //I use memswap_arr to swap two integers printf("i1==%d i2==%d \n",i1,i2); //I use the SAME function to swap two doubles memswap_arr(&d1,&d2,sizeof(double)); printf("d1==%f d2==%f \n",d1,d2); return 0; } 

我认为这应该让您了解如何将一个函数用于不同的数据类型。

在解除引用之前,您应该将指针强制转换为具体类型。 所以你还应该添加代码来传递指针变量的类型。

很抱歉,如果这可能不是对广泛问题“ 如何在c中使用void *制作generics函数? ”的答案,而是你似乎遇到的问题(递增任意类型的变量,并交换2个变量)对于宏来说,使用宏可以比使用函数和指向void的函数更容易完成。

增量很简单:

 #define increment(x) ((x)++) 

对于交换,我会做这样的事情:

 #define swap(x, y) \ ({ \ typeof(x) tmp = (x); \ (x) = (y); \ (y) = tmp; \ }) 

…基于我的测试,它适用于int,double和char指针(字符串)。

虽然递增宏应该非常安全,但交换宏依赖于typeof()运算符,它是一个GCC / clang扩展,不是标准C的一部分(如果你真的只用gcc或clang编译,那么这不应该是一个太大的问题)。

我知道那种躲过了原来的问题; 但希望它仍能解决你原来的问题。

您可以使用类型通用设施(C11标准)。 如果您打算使用更高级的数学函数(比++运算符更高级),可以转到 ,它是函数的类型generics定义

您还可以使用_Generic关键字将类型generics函数定义为宏。 下面是一个例子:

 #include  #define add1(x) _Generic((x), int: ++(x), float: ++(x), char: ++(x), default: ++(x)) int main(){ int i = 0; float f = 0; char c = 0; add1(i); add1(f); add1(c); printf("i = %d\tf = %g\tc = %d", i, f, c); } 

您可以在Rob的编程博客中找到有关语言标准的更多信息以及这篇文章中更多有关soffisticated的例子。

至于* void ,swap和sort问题,最好参考Jerry Coffin的回答。