搞懂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_ttypedef定义好的话,那么上面这个定义可以省掉,我们可以这样定义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;

上面两个假设全都各自指向xy函数的指针数组:

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");


}

以上就是函数指针的一些声明方式,希望对大家有所帮助。

My Github Page: https://github.com/liweinan

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues