目录


前言

本篇文章的内容仍然为指针进阶的相关内容,继续上一篇文章的内容。 【C语言进阶】——指针(一) (字符指针,数组指针,指针数组)

指针令人头秃!!!!!!!!!


5、函数指针

我们创建函数的时候,就会在内存中开辟一块空间,既然占用了内存空间,那就有对应的内存空间地址。
函数指针,顾名思义就是指向函数的指针。

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsort函数排序

注意:
& 函数名 和 函数名均表示函数的地址!

数组名 != &数组名 函数名 == &函数名


我们怎么把函数的地址存起来呢

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsor函数_02

**注意 :**函数值指针的类型需要和返回函数值类型相同

就像这样:【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _函数指针_03


思考:函数指针如何使用呢?

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsor函数_04


通过函数指针,我们可以找到函数,然后去调用这个函数。 函数指针是 &
函数名,而我们函数调用的时候可以直接使用函数名,那么这里通过函数指针调用函数也可以这样写:

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _函数指针数组_05

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsort函数排序_06

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _函数指针数组_07


既然这个地方的 * 可以省略,那么我们在使用的时候 * 可以用多个,也可以不要, * 号在这里就是一个摆设,这个地方放 * 是为了方便理解 + 学习指针。

#include <stdio.h>
int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 10;
    int b = 20;

    int(*pa)(int, int) = Add;
    printf("%d\n", pa(2, 3));
    printf("%d\n", Add(2, 3));
    printf("%d\n",(*pa)(2, 3));
    printf("%d\n", (**pa)(2, 3));
    printf("%d\n", (***pa)(2, 3));
    //打印结果说明*的个数对函数调用没有影响
    return 0;
}

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsort函数排序_08


阅读两个有意思的(阴间)代码:

//代码1
(*(void (*) ())0)();

//代码2
void(*signal(int, void(*)(int)))(int);

这两个代码均是书籍《C陷阱与缺陷》中提及的内容,推荐阅读这本书。

这两个代码怎么阅读和理解呢?

//代码1 
(*(void (*) ())0)()

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsort函数排序_09

**1.**先将 void () ()理解清楚,这个是一个函数指针,指针指向的函数返回类型是void的。

**2.**再理解(void () ())0 我们之前的学习中学到过强制类型转换,需要将强制转换之后的类型用括号()括起来,这个地方就是将 0 强制类型转换成void() ()类型。 为什么要将0强制类型转换成void() ()类型呢? 原因:想要将0当做某个函数的地址

深入扩展:如果一个数字想要当作一个地址,直接使用这个数字肯定是不行的,而是要将这个数字转换成也给地址编号的类型。这也是代码1中为什么要将0强制类型转换的原因。

**3.接着再看((void () ())0),对一个指针加, 就是对其进行解引用操作,对函数指针解引用就是找到这个函数

4.((void () ( ))0)( ) ,((void () ())0)找到函数后,对其使用(), 就是调用函数,所以((void () ())0)(); 是一个函数调用。 整体理解下来就是:将0强制类型转换成一个函数指针void(* )( ),再通过对这个函数指针进行解引用操作,找到这个函数,对其进行调用!


//代码2
void( *signal(int, void(*)(int)) ) (int);

1.signal是一个函数声明

2.signal函数的参数有两个,第一个是int类型,第二个是函数指针,该函数指针指向的函数的参数是int类型返回类型是void(void(*)(int))

3.signal的返回类型是一个函数指针 因为 void(*signal XXX),该函数指针指向的函数的参数是int类型,返回类型是void(void( )(int) )


这种形式看起来就比较复杂和难以理解,我们可以用typedef类型重定义对其进行简化:

在之前的学习中,我们使用过 typedef 来定义过无符号整型 typedef unsigned int u_int ;

但是我们并没有学过指针类型如何进行类型重定义,比如说 void( )(int) ,如果我们要将其进行重定义,可以写成:

typedef void( )(int) pfun_t; 这种形式吗?

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _函数指针_10

我们尝试将其放到编译器下,就会发现编译器报错,显然这种方式是行不通的!


思考:那么可以将这个类型重定义后的名称类似与定义函数指针一样放到(* )里面吗?也就是typedef void(*pfun_t)(int);

【C语言进阶】——指针(二) (函数指针,回调函数,qsort排序) _qsort函数排序_11

写成这种形式后,编译器没有在报错或者警告,说明这种方式是对的,实际上正确的书写方式也正是这样!

完整应该是这样书写:

//代码2
void(* signal(int, void(*)(int)) )(int);

//那么这个类型可以简化成:
typedef  void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

虽然将一行代码变成了两行,但是简化后的代码更便于阅读和理解。

typedef 在进行类型重定义的时候,如果是函数指针类型,那么名称需要放到* 旁边,也就是说

不能写成这种形式:typedef void(*)(int) pfun_t;

正确的形式是:typedef void(*pfun_t)(int);

深入扩展:当函数的返回类型是一个函数指针的时候,函数名需要放到函数返回类型-- - 函数指针内部,而不是直接放到返回类型— 函数指针后面。
void( signal(int, void( )(int)) )(int);