指针数组,数组指针,函数指针及应用(回调函数)

················································索引···························································


指针数组与数组指针

  当我们在学习指针与数组时总会遇到两个令人容易混淆的概念:数组指针与指针数组。
  在这里数组指针是指向数组的指针,其本质为指针指向的对象是数组。由于数组的形式多样所以数组指针的表达也十分多样。同理,指针数组就是存放指针的数组,其本质为数组。由于“[ ]”的优先级高于“ * ”的优先级,指针数组与数组指针的表达可做如下表示:

  • int * p1 [10];  // 指针数组  p1先与“[ ]”结合构成一个包含10个元素的数组,int*表示的则是数组的内容。
  • int (* p2)[10];  // 数组指针  p2先与“ * ”构成指针定义,int表示数组内容,[10]表示数组内元素个数。

  由于指向数组的指针与指向普通整型变量的指针不同,在这里可以再对“数组名”与“&数组名”的关系进行理解。
  在一维数组中,数组名表示指向首元素的首地址,是一个指向普通变量的指针常量,当对其+1时偏移量是一个普通数据类型的内存大小。而在数组名前加上取地址符&后,表示的就是一个指向数组的指针常量对其+1时偏移量是一个数组的内存大小。
  观察以下代码:

int main ()
{
    char arr[5] = {'A','B','C','D'};
    char(*p1)[3] = &arr;   //p1指向一个具有三个字符元素的数组
    char(*p2)[3] = arr;    //p2也指向一个具有三个字符元素的数组
    printf("arr = %p\n",arr);
    printf("p1 = %p\n",p1);
    printf("p1+1 = %p\n",p1+1);
    printf("p2+1 = %p\n",p2+1);
    return 0;
}


  在这段代码当中,p1,p2都是指向由3个整型元素组成的数组的指针,但arr却是一个指向整型数据的指针常量,二者的指向内容虽然不同但是由于当变量作为右值时编译器只会去取变量的值,所以在程序运行时编译器会报出如警告却不会运行失败。

e:\code\c语言\test\test.c(16) : warning C4048: “char ()[10]”和“char ()[5]”数组的下标不同
e:\code\c语言\test\test.c(17) : warning C4047: “初始化”: “char ()[10]”与“char ”的间接级别不同

int main ()
{
    char arr[5] = {'A','B','C','D'};
    char(*p1)[10] = &arr;//p1指向一个具有三个字符元素的数组,与arr指向空间一致
    char(*p2)[10] = arr;
    printf("arr = %p\n",arr);
    printf("p1 = %p\n",p1);
    printf("p1+1 = %p\n",p1+1);
    printf("p2+1 = %p\n",p2+1);
    return 0;
}



  总结代码可以看出指向数组的指针初始化时只是用到了原数组提供的地址,其访问的内容大小并不受原数组arr限制,而受到自己指向的数组大小的影响即:p1+1的偏移量是10而不是原数组大小(&arr)的4,p2+1的偏移量也是10而不是原指针指向元素的大小(arr首元素大小)的1。

  对于数组指针最重要的是要理解其本质是指针,而且其偏移量受到自身指向的数组大小影响即可。相较之下,指针数组就很好理解了,不过是本质是数组且每个数组元素是指针罢了(不管是指向何种类型的指针在32位平台下其自身的空间占用量都是4个字节)


函数指针

  知道了数组指针是指向数组的指针,那么同理也可以对函数指针进行相同的理解,函数指针就是指向函数的指针了。
  函数在内存中占用一块地址而且这块地址也是可以赋给一个指针变量的,也就是说可以通过这个地址访问到这个函数。与数组相似,函数名也是指向函数第一条指令的常量指针。所以说函数的调用可以通过函数名,也可以通过指向函数的指针来调用。函数指针还允许将函数作为参数传递给其他函数,也就是回调函数
  函数指针表现形式:type (*func)(type &,type &)该语句声明了一个指针func,它指向了一个函数,这个函数带有了2个type型参数并返回一个type的值。
  需要特别注意的是第一个括号一定要写,如果不写的话表达式就变成了函数声明而非函数指针。
  分析以下两句代码,看看是什么意思:

1.(*(void (*)())0)();
2.void(*signal(int,void(*)(int)))(int);

表达式1:
这里写图片描述
表达式2:
这里写图片描述

  对于函数指针暂时只需要明白其表达方法,以及如何利用函数指针调用函数即可。


函数指针数组&指向函数指针数组的指针

  在对函数指针有了一定的了解之后,函数指针数组就很好理解了,其意义就是定义一个数组,数组的内容均是指向函数的指针。
  表达式:例如:int (*arr1[10])();
  arr1先于[]结合,表明其本质是数组,其指向的类型是int (*)()返回值为int的函数。即函数指针数组。
  其应用参考:利用函数指针数组实现计算器;

  当定义了一个函数指针数组后,能否在定义一个指针用于保存这个数组的地址呢?这个指针就是指向函数指针数组的指针。其表达式为:例如:void (*(*p)[5]) )(void)
  表示 一个指向有5个元素每个元素为指向一个返回值为空的函数的数组的指针。


函数指针实例(回调函数,实现泛型冒泡排序)

#include<stdio.h>
#include<string.h>
void swap(char *p,char *q, int n)
{
    unsigned int i = 0;
    char tmp;
    for(i=0; i<n; i++)
    {
        tmp = *p;
        *p = *q;
        *q = tmp;
        p++;
        q++;
    }
}
int int_cmp(const void *p,const void *q)//实现整型间的排序
{
    return (*(int *)p > *(int *)q);
}
int char_cmp(const void *p,const void *q)//实现字符间的排序
{
    return (*(char *)p > *(char *)q);
}
int str_cmp(const char **p,const char **q)//实现字符串排序
{
    return strcmp(*p,*q);
}
void bubble(void *arr,int sz,int wid,int(*cmp)(const void *p,const void *q))
{
    unsigned int i = 0;
    unsigned int j = 0;
    for(i=0;i<sz-1;i++)
    {
        for(j=0;j<sz-i-1;j++)
        {
            if(cmp((char *)arr+wid*j ,(char *)arr+wid*(j+1))>0)
            //函数指针调用
            {
                swap((char *)arr+wid*j,(char *)arr + wid*(j+1),wid);
            }
        }
    }
}

int main()
{
    int arr[10] = {0,11,33,22,44,55,66,88,77,99};
    int wid = sizeof(arr[0]);
    int sz = sizeof(arr)/sizeof(arr[0]);
    int i = 0;
    bubble(arr,sz,wid,str_cmp);//第四个参数要根据排序的不同类型进行更改
    for(i=0;i<sz;i++)
    {
        printf("%s\n",arr[i]);
    }
    printf("\n");
    return 0;
}

函数指针数组实例(实现计算器)

#include<stdio.h>
int Add(int x, int y)
{
    return x+y;
}
int Sub(int x, int y)
{
    return x-y;
}
int Mul(int x, int y)
{
    return x*y;
}
int Div(int x, int y)
{
    return x/y;
}
void menu()
{
    printf("******************************\n");
    printf("**     1. add     2. sub    **\n");
    printf("**     3. mul     4. div    **\n");
    printf("**          0.exit          **\n");
    printf("******************************\n");
}
void calc(int (*pfun)(int, int))
{
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("请输入要进行计算的值:");
    scanf("%d%d", &x, &y);
    ret = pfun(x, y);
    printf("ret = %d\n", ret);
}

int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    int (*pfun[5])(int , int) = {0, Add, Sub, Mul, Div};
    //创建函数指针的数组,数组中保存的都是返回值为int的函数的指针。
    do
      { 
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch(input)
        {
        case 1:
            calc(pfun[1]);
            //通过数组调用函数的指针进而调用函数,可大量节省代码篇幅
            break;
        case 2:
            calc(pfun[2]);
            break;
        case 3:
            calc(pfun[3]);
            break;
        case 4:
            calc(pfun[4]);
            break;
        case 0:
            printf("退出\n");
            break;
        default:
            printf("输入错误,请重新输入\n");
            break;
        }
    }while(input);
    return 0;
}

本文主要用于学习总结之用,其中主要参考资料有《C语言深度解剖》,《C陷阱与缺陷》,《C和指针》。内容中凡有不足疏漏之处欢迎批评指正,谢谢。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页