18

I have an incr function to increment the value by 1 I want to make it generic,because I don't want to make different functions for the same functionality.

Suppose I want to increment int,float,char by 1

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

But the problem I know is Dereferencing a void pointer is undefined behaviour. Sometimes It may give error :Invalid use of void expression.

My main funciton is :

int main()
{

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

incr(&i);
incr(&f);
incr(&c);

return 0;
}

The problem is how to solve this ? Is there a way to solve it in Conly

or

will I have to define incr() for each datatypes ? if yes, then what's the use of void *

Same problem with the swap() and sort() .I want to swap and sort all kinds of data types with same function.

4

7 回答 7

21

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

#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]到它的临时,然后从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
于 2012-11-20T09:03:06.130 回答
13

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

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

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

您可能需要查看 Wikipedia 解释以获取更多信息。

于 2012-11-20T08:54:44.070 回答
7

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

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));
于 2012-11-20T09:01:21.760 回答
2

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

此代码交换两个内存块。

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<size; ++i) {
        ch=     pc1[i];
        pc1[i]= pc2[i];
        pc2[i]= ch;
      }
}

你这样称呼它:

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

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

于 2012-11-20T08:51:14.117 回答
1

抱歉,如果这可能无法回答广泛的问题“如何在 c 中使用 void * 制作通用函数? ”.. 但您似乎遇到的问题(增加任意类型的变量,并交换 2 个变量未知类型)使用宏比函数和指向 void 的指针更容易完成。

增量很简单:

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

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

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

...根据我的测试,它适用于整数、双精度和字符指针(字符串)。

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

我知道那种回避了原来的问题;但希望它仍然可以解决您最初的问题。

于 2017-11-05T08:52:22.660 回答
1

您可以使用类型通用工具(C11 标准)。如果您打算使用更高级的数学函数(比++运算符更高级),您可以转到<tgmath.h>,这是 和 中函数的类型泛型<math.h>定义<complex.h>

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

#include <stdio.h>

#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 的编程博客中的这篇文章中找到有关语言标准的更多信息和更复杂的示例。

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

于 2018-01-23T14:16:20.940 回答
0

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

于 2012-11-20T08:54:28.023 回答