搞懂C语言里面的函数指针
本文讲解C语言里面的函数指针的类型声明方式。这篇文章按照难度分几层撰写,大家量力而行进行阅读。
基础
C语言里面的函数指针定义由三部分组成:
(函数的返回类型)(*指针的变量名)(函数的参数类型)
比如我们有这样的代码:
char *x(char *p) {
    return p;
}
那么指向它的指针该怎样定义呢?
根据上面的原则,应该是:
char *(*px)(char *)
因为x函数的返回值是char *,参数是char *p,因此参数类型是char *。所以上面的px指针就声明如上,并使用px指针对x函数进行调用:
px = &x;
px("Hello");
我们可以写代码验证:
#include <stdio.h>
char *x(char *p) {
    return p;
}
int main() {
    char *(*px)(char *);
    px = &x;
    printf("%s\n", px("Hello"));
}
程序输出如下:
Hello
也可以这样写:
px = x;
上面这个和下面的赋值语句是等价的:
px = &x;
如果你觉得上面px的声明看着难受,或者说,我们想定义多个函数指针变量的时候,就可以先用typedef定义好类型声明:
typedef char *(*px_t)(char *);
然后使用定义的这个px_x类型来声明函数的指针变量:
px_t px = x;
px_t px2 = x;
然后使用即可。下面是完整的代码:
#include <stdio.h>
char* x(char *p) {
    return p;
}
int main() {
    typedef char* (*px_t)(char*);
    px_t px = x;
    px_t px2 = x;
    printf("%s\n", px("Hello"));
    printf("%s\n", px2("Hello"));
}
程序输出如下:
Hello
Hello
下面继续讲解更抽象一层的声明。
进阶
接下来我们做一些更复杂的东西。假设我们有两个类型一致的函数:
char *x(char *p) {
    return p;
}
char *y(char *p) {
    return p;
}
我们想用一个函数的指针数组指向它们两个,也是可以的。声明同样遵守一开始的那个三段法则:
char *(*p_xy[2])(char *);
这样,我们声明了一个函数指针的数组,这个数组包含两个函数的指针变量。然后我们让两个指针各指向x函数和y函数:
p_xy[0] = x;
p_xy[1] = y;
然后调用:
printf("%s\n", p_xy[0]("Banana"));
printf("%s\n", p_xy[1]("Apple"));
代码输出如下:
Banana
Apple
我们同样可以使用typedef来定义类型:
typedef char *(*p_xy_t[2])(char *);
p_xy_t p_xy;
p_xy[0] = x;
p_xy[1] = y;
和上面的定义方式是等价的。
困难
接下来,我们再抽象一层,做一个指针,指向p_xy。这里别晕:p_xy是函数指针变量的数组,然后我们要做一个p_to_p_xy指针,指向p_xy。
声明如下:
char *(*(*p_to_p_xy))(char *);
仔细观察,上面声明的p_to_p_xy就是指向p_xy的指针。我们使用它指向p_xy的地址:
p_to_p_xy = &p_xy;
调用方法如下:
printf("%s\n", p_to_p_xy[0]("Earth"));
printf("%s\n", p_to_p_xy[1]("Mars"));
同样地,上面的类型定义也可以用typedef来做:
typedef char *(*(*p_to_p_xy_t))(char *);
然后使用这个类型定义来声明变量:
p_to_p_xy_t p_to_p_xy;
这样对于这个类型的使用者来讲,就简单多了。
此外,如果像之前那样,把p_xy_t用typedef定义好的话,那么上面这个定义可以省掉,我们可以这样定义p_to_p_xy:
p_xy_t *p_to_p_xy;
这个很好理解,我们定义了一个指向p_xy_t类型的指针。这就是使用typedef的好处。
然后我们就可以让p_to_p_xy指向p_xy的地址:
p_to_p_xy = &p_xy;
下面是调用方法:
printf("%s\n", (*p_to_p_xy)[0]("Earth"));
printf("%s\n", (*p_to_p_xy)[1]("Mars"));
以上是抽象一层,下面我们继续再封装一层:
放弃
最后,为了展示这个逻辑,我们再抽象一层,来做一个保存p_to_p_xy这样类型的指针的数组。
首先,我们做两个p_to_p_xy这样类型的变量:
p_xy_t *p_to_p_xy;
p_xy_t *p2_to_p_xy;
上面两个假设全都各自指向x和y函数的指针数组:
p_to_p_xy = &p_xy;
p2_to_p_xy = &p_xy;
然后我们要做指针数组指向它们两个。
为了展示最底层逻辑,我们不用typedef,从最基础类型进行声明:
char *(*(*p_to_p_to_p_xy[2]))(char *);
然后赋值:
p_to_p_to_p_xy[0] = p_to_p_xy;
p_to_p_to_p_xy[1] = p2_to_p_xy;
然后使用:
printf("%s\n", p_to_p_to_p_xy[0][0]("Are"));
printf("%s\n", p_to_p_to_p_xy[0][1]("you"));
printf("%s\n", p_to_p_to_p_xy[1][0]("ok"));
printf("%s\n", p_to_p_to_p_xy[1][1]("?"));
输出如下:
Are
you
ok
?
以上就是对类型声明的一个展示。同样地,给出使用typedef简化后的版本。
骨灰
最后,我们把各种typedef或不使用typedef的声明方式都列出来,供大家参考:
#include <stdio.h>
#include <stdlib.h>
char *x(char *p) {
    return p;
}
char *y(char *p) {
    return p;
}
int main() {
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    char *(*p_x)(char *);
    char *(*p2_x)(char *);
    p_x = x;
    p2_x = &x;
    printf("%s\n", p_x("Red"));
    printf("%s\n", p2_x("Blue"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    typedef char *(*p_x_t)(char *);
    p_x_t p3_x = x;
    printf("%s\n", p3_x("Black"));
    printf("%s\n", p3_x("White"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    char *(*p_xy[2])(char *);
    p_xy[0] = x;
    p_xy[1] = y;
    printf("%s\n", p_xy[0]("Tomato"));
    printf("%s\n", p_xy[1]("Kiwi"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    typedef char *(*p_xy_t[2])(char *);
    p_xy_t p2_xy;
    p2_xy[0] = x;
    p2_xy[1] = y;
    printf("%s\n", p2_xy[0]("Banana"));
    printf("%s\n", p2_xy[1]("Apple"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    p_xy_t *pp_xy;
    pp_xy = &p_xy;
    printf("%s\n", (*pp_xy)[0]("Earth"));
    printf("%s\n", (*pp_xy)[1]("Mars"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    char *(*(*pp2_xy))(char *);
    pp2_xy = &p_xy;
    printf("%s\n", pp2_xy[0]("Jupiter"));
    printf("%s\n", pp2_xy[1]("Sun"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    typedef char *(*(*pp_xy_t))(char *);
    pp_xy_t pp3_xy = &p_xy;
    printf("%s\n", pp3_xy[0]("Mercury"));
    printf("%s\n", pp3_xy[1]("Neptune"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    char *(*(*pPp_xy[2]))(char *);
    pPp_xy[0] = pp_xy;
    pPp_xy[1] = pp2_xy;
    printf("%s\n", pPp_xy[0][0]("Are"));
    printf("%s\n", pPp_xy[0][1]("you"));
    printf("%s\n", pPp_xy[1][0]("ok"));
    printf("%s\n", pPp_xy[1][1]("?"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    pp_xy_t pPp2_xy[2];
    pPp2_xy[0] = pp_xy;
    pPp2_xy[1] = pp2_xy;
    printf("%s\n", pPp2_xy[0][0]("I"));
    printf("%s\n", pPp2_xy[0][1]("am"));
    printf("%s\n", pPp2_xy[1][0]("fine"));
    printf("%s\n", pPp2_xy[1][1]("!"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    p_xy_t *pPp3_xy[2];
    pPp3_xy[0] = pp_xy;
    pPp3_xy[1] = pp2_xy;
    printf("%s\n", (*pPp3_xy[0])[0]("How"));
    printf("%s\n", (*pPp3_xy[0])[1]("are"));
    printf("%s\n", (*pPp3_xy[1])[0]("you"));
    printf("%s\n", (*pPp3_xy[1])[1]("?"));
    printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
}
以上就是函数指针的一些声明方式,希望对大家有所帮助。