void 指针是一种特殊的指针,表示为“无类型指针”,在 ANSI C 中使用它代替“char*”作为一般指针的类型。 由于 void 指针没有特定类型,因此它可以指向任何类型的数据。 也就是说,任何类型的指针都可以直接赋值给void指针,无需进行其他相关的强制类型转换,如下示例代码所示:

void *p1;
int *p2;
…
p1 = p2;

即便如此,这并不意味着void指针可以直接赋值给其他类型的指针而不需要任何类型转换,因为“空类型”可以包含“typed”,而“typed”不能包含“空类型”。 ”。正如我们可以说“男人和女人都是人类”,但不能说“人类是男人”或“人类是女人”。因此,下面的示例代码将编译错误。如果是在 VC++2010 中,会提示错误信息“a value of type ‘void*’ be to an of type ‘int*’”。

void *p1;
int *p2;
…
p2 = p1;

可见,要将void指针赋给另一个类型的指针,必须进行强制类型转换。 如下示例代码所示:

void *p1;
int *p2;
…
p2 = (int*)p1;

避免对 void 指针执行算术运算。 ANSI C标准规定,进行算术运算的指针必须知道它所指向的数据类型的大小,也就是说必须知道内存目的地址的准确值。 如下示例代码所示:

char a[20]="qwertyuiopasdfghjkl";
int *p=(int *)a;
p++;
printf("%s", p);

上述示例代码中,指针变量p的类型为“int*”,指向的类型为int,并初始化为指向整型变量a。

当执行语句“p++”时,编译器是这样处理的:在指针p的值上加上“(int)”(由于int在32位系统中占用4个字节,所以这里加上4个字节),即即p指向的地址从原变量a的地址向高地址方向增加4个字节。 但由于char类型的长度是一个字节,所以语句“(”%s”,p)”会输出“”。

对于void指针,编译器不知道所指向对象的大小,因此对void指针进行算术运算是非法的,如下示例代码所示:

void * p;
p++;      // ANSI:错误
p+= 1;      // ANSI:错误

上面的代码在VC++2010中会提示“must be a to a type”错误信息。

但值得注意的是,GNU 并不这么认为,它指定“void*”与“char*”操作相同。 因此以下陈述在 GNU 编译器中都是正确的:

void * p;
p++;      // GUN:正确
p+=1;      // GUN:正确

以下示例代码演示了 GCC 中 void 指针的自动递增:

#include 
int main(void)
{
    void * p="ILoveC";
    p++;
    printf("%sn", p);
}

运行结果为:

爱C

可见GNU和ANSI还是有一些区别的。 相比之下,GNU比ANSI更加“开放”,提供了更多语法的支持。 但在真实的设计环境中,应尽可能符合ANSI标准,并尽量避免对void指针进行算术运算。

如果函数的参数可以是任意类型的指针,则应声明为void*。 上面说过,void指针可以指向任意类型的数据,任何类型的指针都可以直接赋值给void指针,无需进行其他相关的强制类型转换。 因此,在编程中,如果函数的参数可以是任意类型的指针,那么就应该使用void指针作为函数的形参,这样函数就可以接受任意数据类型的指针作为参数。

典型的函数包括内存操作函数,如下代码所示:

void *memset(void *buffer, int b, size_t size)
{
    assert(buffer!=NULL);
    char* retAddr = (char*)buffer;
    while (size--> 0)
    {
        *(retAddr++) = (char)b;
    }
    return retAddr;
}
void *memcpy (void *dst,  const void *src,  size_t size)
{
    assert((dst!=NULL) && (src!=NULL));
    char *temp_dest = (char *)dst;
    char *temp_src = (char *)src;
    char* retAddr = temp_dest;
    size_t i = 0;
    /* 解决数据区重叠问题*/
    if ((retAddr>temp_src) && (retAddr=0; i--)
        {
            *(temp_dest++) = *(temp_src++);
        }
    }
    else
    {
        for (i=0; i<size; i++)
        {
            *(temp_dest++) = *(temp_src++);
        }
    }
    *(retAddr+size)='';
    return retAddr;
}

这样,任何类型的指针都可以传入函数和函数中,这也真正体现了内存操作函数的意义,因为它操作的对象只是一块内存,无论是什么类型的内存。 以下代码显示了函数调用的示例:

char buf[]="abcdefg";
// buf+2(从c开始,长度3个,即cde)
memcpy(buf, buf+2 ,3);
printf("%sn", buf);

或者拨打以下形式的电话:

int dst[100];
int src[100];
memcpy(dst, src, 100*sizeof(int));

因为参数类型是void*,所以上面的调用都是正确的。 现在假设函数的参数类型不是void*,而是char*,如下代码所示:

char *memcpy(char* dst, const char* src, size_t size)
{
    assert((dst !=NULL) && (src != NULL));
    char *retAddr = dst;
    size_t i = 0;
    if ((retAddr>src) && (retAddr=0; i--)
        {
            *(dst++)= *(src++);
        }
    }
    else
    {
        for (i=0; i<size; i++)
        {
            *(dst++) = *(src++);
        }
    }
    *(retAddr+size)='';
    return retAddr;
}

现在继续进行以下形式的调用:

int dst[100];
int src[100];
memcpy(dst, src, 100*sizeof(int));

由于类型不匹配,编译器会报错。 可见这样的功能也失去了通用性。

好了,今天的主题就讲到这里吧,不管如何,能帮到你我就很开心了,如果您觉得这篇文章写得不错,欢迎点赞和分享给身边的朋友。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注