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

Omk*_*ant 16 c void-pointers

我有一个incr函数来增加值1 我希望使它成为通用的,因为我不想为相同的功能创建不同的函数.

假设我要增加int,float,char1

void incr(void *vp)
{
        (*vp)++;
}
Run Code Online (Sandbox Code Playgroud)

但我知道的问题是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;
}
Run Code Online (Sandbox Code Playgroud)

问题是如何解决这个问题?是否有解决这个问题的方式C只有

要么

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

同样的问题swap()sort().我想交换和整理各种数据类型的功能相同的.

Jer*_*fin 19

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

#define incr(x) (++(x))
Run Code Online (Sandbox Code Playgroud)

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

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

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

void qsort(void *base, size_t nmemb, size_t size,
           int(*cmp)(void const *, void const *));
Run Code Online (Sandbox Code Playgroud)

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

// if (base[j] < base[i]) ...
if (cmp((char *)base+i, (char *)base+j) == -1)
Run Code Online (Sandbox Code Playgroud)

同样,要交换两个项目,它通常会有一个本地数组用于临时存储.它会再从复制字节array[i]到其临时从,然后array[j]array[i]从最后temparray[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
Run Code Online (Sandbox Code Playgroud)


acj*_*jay 13

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

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

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

您可以查看维基百科的说明以获取更多信息.


caf*_*caf 5

你不能完全按照你的要求去做——像 increment 这样的操作符需要使用特定的类型。所以,你可以做这样的事情:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你会这样称呼它:

int i=5;
float f=5.6f;
char c='a';

incr(TYPE_INT, &i);
incr(TYPE_FLOAT, &f);
incr(TYPE_CHAR, &c);
Run Code Online (Sandbox Code Playgroud)

当然,除了定义单独的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 *));
Run Code Online (Sandbox Code Playgroud)

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

您的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

int ai[];
double ad[];

swap(&ai[x], &ai[y], sizeof(int));
swap(&di[x], &di[y], sizeof(double));
Run Code Online (Sandbox Code Playgroud)