目录
一、函数指针变量的创建
1、什么是函数指针变量?
2、函数是否有地址?
3、创建函数指针变量
4、函数指针类型解析
二、函数指针变量的使用
三、两段有趣的代码
1、解释 (*(void (*)())0)();
2、解释 void (*signal(int, void(*)(int)))(int);
四、typedef关键字
1、基本用法
2、对于数组指针和函数指针的重命名
3、使用typedef简化代码void (*signal(int, void(*)(int)))(int);
五、函数指针数组
一、函数指针变量的创建
1、什么是函数指针变量?
类比整型指针和数组指针的概念,我们可以得出:函数指针变量是用来存放函数地址的变量,通过这个地址我们可以调用相应的函数。
2、函数是否有地址?
通过以下测试代码可以验证:
#include <stdio.h>void test() {printf("hehe\n");
}int main() {printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}
输出结果:
结果表明函数确实有地址,且函数名就是函数的地址,也可以通过&函数名
的方式获取函数地址。(重点!!!)
3、创建函数指针变量
函数指针变量的声明语法与数组指针类似:
void test() {printf("hehe\n");
}// 两种等效的函数指针声明方式
void (*pf1)() = &test;
void (*pf2)() = test;int Add(int x, int y) {return x + y;
}// 函数指针声明,参数名可省略
int (*pf3)(int, int) = Add;
int (*pf4)(int x, int y) = &Add;
4、函数指针类型解析
以int (*pf3)(int x, int y)
为例:int (*) (int x, int y) 为pf3函数指针变量的类型
二、函数指针变量的使用
通过函数指针调用函数的示例:
#include <stdio.h>int Add(int x, int y) {return x + y;
}int main() {int(*pf)(int, int) = Add;// 两种等效的调用方式printf("%d\n", (*pf)(2, 3)); // 显式解引用printf("%d\n", pf(3, 5)); // 隐式调用return 0;
}
输出结果:
三、两段有趣的代码
以下两段有趣的代码出自《C陷阱和缺陷》:
1、解释 (*(void (*)())0)();
这段代码的功能是:调用位于内存地址0处的函数。
让我们逐步解析:
-
void (*)():
这是一个函数指针类型,表示"指向一个没有参数且返回void的函数的指针"。 -
(void (*)())0:
将整数值0强制转换为上述函数指针类型。这表示"把地址0当作一个函数的地址"。 -
*(void (*)())0:
解引用这个函数指针,得到位于地址0处的函数。 -
(*(void (*)())0)():
最后调用这个函数(通过函数指针调用)。
实际意义:这段代码尝试调用内存地址0处的函数。在嵌入式系统中,这可能是调用复位向量或启动代码的方式。但在大多数现代操作系统中,这会引发段错误(segmentation fault),因为地址0通常是被保护的区域。
2、解释 void (*signal(int, void(*)(int)))(int);
这是一个函数声明,声明了名为signal
的函数。让我们分解它:
-
最内层:
void(*)(int):
这是一个函数指针类型,表示"指向一个接受int参数且返回void的函数的指针"。 -
signal(int, void(*)(int)):
signal
是一个函数,它接受两个参数:一个int、一个上述类型的函数指针 -
整个声明:
void (*signal(int, void(*)(int)))(int):
表示signal
函数返回一个函数指针,这个指针指向"接受int参数且返回void的函数"。
更易读的写法(使用typedef,下面会讲解):
typedef void (*sighandler_t)(int); // 定义函数指针类型sighandler_t signal(int signum, sighandler_t handler);
实际意义:这是Unix/Linux系统中用于设置信号处理器的标准函数声明。它:
-
接受一个信号编号(int)和一个处理该信号的函数指针
-
返回之前为该信号设置的处理函数指针
四、typedef关键字
typedef用于类型重命名,可以简化复杂类型的声明。
1、基本用法
typedef unsigned int uint; // 将unsigned int重命名为uint
typedef int* ptr_t; // 将int*重命名为ptr_t
2、对于数组指针和函数指针的重命名
新的类型名必须在*的右边:
typedef int(*parr_t)[5]; // 将数组指针类型int(*)[5]重命名为parr_t
typedef void(*pfun_t)(int); // 将函数指针类型void(*)(int)重命名为pfun_t
3、使用typedef简化代码void (*signal(int, void(*)(int)))(int);
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
五、函数指针数组
数组是存放相同类型数据的存储空间。我们已经学过指针数组:
int *arr[10]; // 数组的每个元素是int*
如果要存储函数地址,就需要使用函数指针数组。函数指针数组的定义方式:
int (*parr1[3])(); // 正确:包含3个函数指针的数组,每个指针指向返回int的无参函数
下面是错误用法:
int *parr2[3](); // 错误:错误语法
int (*)() parr3[3]; // 错误:错误语法
解析parr1:
-
parr1
先与[]
结合,说明它是一个数组 -
数组的内容是
int (*)()
类型的函数指针
函数指针数组常用于实现状态机或回调函数机制,是C语言中一种强大的编程技术。