C语言基础:通用指针

一、通用指针

通用指针变量用void *来修饰。顾名思义,通用指针可以用来存放任意型的内存地址,char、short、int、float、double等这些类型的地址都可以使用void *型变量来存放。同样char *、short *、int *、float *、double *等指针变量均可以赋值给void *型变量。例如:

#include 
#include 

int main(int argc, char **args)
{
        char v0;
        short v1;
        int v2;
        long v3;
        float v4;
        double v5;

        char *p0 = &v0;
        short *p1 = &v1;
        int *p2 = &v2;
        long *p3 = &v3;
        float *p4 = &v4;
        double *p5 = &v5;

        void *p;

        p = &v0;
        p = &v1;
        p = &v2;
        p = &v3;
        p = &v4;

        p = p0;
        p = p1;
        p = p2;
        p = p3;
        p = p4;
        p = p5;
}


以上代码是合法的,没有任何问题。编译时可以顺利通过。但是通用指针与普通指针的唯一区别是:通用指针无法解引用。实际这是一个比较大的问题,正是因为通用指针可以存放任意类型的地址,所以当给通用指针解引用时编译器无法确定它所指向内存地址中的原始数据类型。例如,我们定义了一个通用指针变量,并尝试让编译器为其解引用:

#include 

int main(int argc, char **args)
{
        int i = 0x11223344;
        void *p = &i;
        *p = 0;
}


这样的代码是错误的,编译器无法对其编译。因为p是一个通用的指针变量,它可以指向任意类型的变量地址,甚至是函数地址。那么当对p做解引用时编译器并不知道p所指向的地址是什么数据类型,所以在对上面代码编译时会出现错误:

main.c:7:3: error: invalid use of void expression


实际上,想要对通用指针解引用必须要对其强制类型转换为指定类型指针,再对其解引用,例如:

#include 

int main(int argc, char **args)
{
        int i = 0x11223344;
        void *p = &i;
        *(char *)p = 0;
        p+=2;
        *(char *)p = 0;

        printf("0x%x\n", i);
}

运行结果:

0x11003300


我们来分析一下int i和void *p在内存中的结构:

C语言基础:通用指针_第1张图片


首先(char *)p就是将通用指针强制转换为char *类型,也就是告诉编译器这是一个指向char型的指针变量,然后再对其解引用*(char *)p,把这个地址中的变量赋值为0。由于编译器认为这是一个char型指针,所以*(char *)p只为地址为0x2000的内存byte赋值。接下来p += 2;表示p的值加2,于是p的值为0x2002,然后同样将其强转为char型指针,再对其解引用并赋值。于是内存中0x2002处的值就被赋值成了0。最后使用printf函数来显示i的值为0x11003300,运行结果正确。

注意:虽然void *p这样的通用指针不能解引用,但是void **p这样的二级指针却可以解引用:

#include 

int main(int argc, char **args)
{
        int i = 7;
        void *p1 = &i;
        void **p2 = &p1;

        printf("0x%x\n", *p2);
}


上面代码的合法的,运行结果为:

0xffc68d28


为什么void *p1不可以解引用,但void **p2却可以呢?这是因为void *p1是一个通用指针,编译无法知道p1所指向的地址是一个什么类型的变量,而对于void **p2而言,编译器知道p2是一个指针,这个指针变量中存放的是一个通用型指针的地址也就是&p1,因为&p1是一个地址,在32位架构下所有指针变量以及内存地址都占用4byte(32bit),所以对p2解引用*p2是合法的,它代表的是一个void *型变量的地址即&p1,它的值是0xffc68d28。

二、通用函数

下面来介绍一下通用指针的实际应用。我们不希望编程工作中出现大量的功能相同、类似的代码,而希望通过一些通用的功能函数来简化编程工作,这样作也便于编写代码的工作量和后期功能修改。例如,我们定义如下3组数组变量:

char array_0[SIZE] = {'H','e','l','l','o'};
char array_1[SIZE];

int array_2[SIZE] = {1,2,3,4,5};
int array_3[SIZE];
        
double array_4[SIZE] = {12.3, 23.4, 34.5, 45.6, 56.7};
double array_5[SIZE];


我们想要如下操作:

  • 将array_0所有的值复制到array_1中;
  • 将array_2所有的值复制到array_3中;
  • 将array_4所有的值复制到array_5中。

实际上我们不想实现3遍数组复制的功能,重复的实现相同的代码可不是一个好习惯。我们应该实现一个通用的函数,专门用于数组的值复制,这就用到了通用指针,我们想使用一个函数使用通用型指针来接收2个数组的首地址,然后进行数组的值复制。即支持char,也支持int,也支持double,实际上这个函数可以支持所有数据类型的数组复制:

void array_copy(void *src, void *tar, int element_size, int array_size)
{
        for (int i = 0; i < element_size * array_size; i++)
        {
                *(char *) tar = *(char *) src;
                tar++;
                src++;
        }
}


array_copy函数有4个参数:

  • void *src 存放来源数组首地址的通用指针
  • void *tar 存放目标数组首地址的通用指针
  • int element_size 数组元素大小在调用时使用sizeof函数来计算
  • int array_size 数组元素个数

有了这个函数就可以方便的对不同类型的数组做复制了:

int main(int argc, char **args)
{
        char array_0[SIZE] =
        { 'H', 'e', 'l', 'l', 'o' };
        char array_1[SIZE];

        int array_2[SIZE] =
        { 1, 2, 3, 4, 5 };
        int array_3[SIZE];

        double array_4[SIZE] =
        { 12.3, 23.4, 34.5, 45.6, 56.7 };
        double array_5[SIZE];

        array_copy(array_0, array_1, sizeof(char), SIZE);
        array_copy(array_2, array_3, sizeof(int), SIZE);
        array_copy(array_4, array_5, sizeof(double), SIZE);

        for (int i = 0; i < SIZE; i++)
        {
                printf("%c ", array_1[i]);
        }
        printf("\n");

        for (int i = 0; i < SIZE; i++)
        {
                printf("%d ", array_3[i]);
        }
        printf("\n");

        for (int i = 0; i < SIZE; i++)
        {
                printf("%f ", array_5[i]);
        }
        printf("\n");
}


运行结果:

H e l l o 
1 2 3 4 5 
12.300000 23.400000 34.500000 45.600000 56.700000


可以看到运行结果非常正确。其实现代码简单又方便。


欢迎关注公众号:编程外星人

你可能感兴趣的:(C语言基础)