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

················································索引···························································web


指针数组与数组指针

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

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

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

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。ui

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


函数指针

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

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

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

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


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

  在对函数指针有了必定的了解以后,函数指针数组就很好理解了,其意义就是定义一个数组,数组的内容均是指向函数的指针。
  表达式:例如: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和指针》。内容中凡是有不足疏漏之处欢迎批评指正,谢谢。