本篇是《C指针:基本概念、核心技术及最佳实践》(Pointers In C : A hands on Approach) 第七章“函数指针”的读书笔记。
1 定义方式
函数指针可以实现动态调用函数,适用与在程序运行时才决定调用哪个函数的情形,可以把函数指针看成动态调用所需函数的钩子。
函数指针变量指向内存中存储程序的位置。
函数返回类型 (* 指针变量名)(函数参数表)
实例:
int add(int x, int y);
int (*ptradd)(int , int);
char* stradd(char* a, char* b);
char* (*ptradd)(char*, char*);
2 初始化及赋值方式
有两种方式对函数指针变量初始化,这两种方式等价。
- 使用
&
取址运算符 - 直接赋值 (常用这种方式)
void func1(int num)
{
//函数实现....
}
int* func2(int x, int y))
{
//函数实现....
}
//初始化
void (*pfunc1)(int ) = &func1;
void (*pfunc1)(int ) = func1; // 两种写法等价
int* (*pfunc2)(int, int) = &func2;
int* (*pfunc2)(int, int) = func2; // 两种写法等价
//赋值
void (*pfunc1)(int );
int* (*pfucn2)(int , int);
pfunc1 = &func1;
pfunc1 = func1; // 两种写法等价
pfunc2 = &func2;
pfucn2 = func2; // 两种写法等价
不管哪种赋值方法,pfunc1 pfunc2 都保存的是函数func1 和 func2 的地址。
3 调用函数指针
调用方法也有两种:
- 直接指针变量名加上参数列表就可以了。指针变量名是函数的入口也是函数的地址。
- 取值 * 运算
指针变量名(参数列表) // 第一种形式
(*指针变量名)(参数列表) // 第二种形式
void func1(int m)
{
printf("This is func1\n");
}
int *func2(int x, int y)
{
int *z = (int *)malloc(sizeof(int));
*z = x + y;
return z;
}
int main()
{
void (*pfunc1)(int) = func1;
int *(*pfunc2)(int, int) = func2;
printf("pfunc1 = %p\n", pfunc1);
printf("pfunc2 = %p\n", pfunc2);
pfunc1(10); //打印一句话;
(*pfunc1)(10); // 和上面等价
pfunc1(10,20); //返回相加结果的地址
printf("20 + 10 = %d", *pfunc2(20, 10)); //pfunc2返回地址,所以要加*
printf("20 + 10 = %d", *(*pfunc2)(20, 10)); // 和上面等价
return 0;
}
4 typedef 函数指针
typedef用于定义一个类型的别名,比如typedef int _int_t
将_int_t
定义成int
的别名。
那么typedef作用于函数/函数指针时又是怎么样的呢?
形式:
typdef 返回类型(*新类型)(参数列表)
用typedef给函数/函数指针定义别名时,相当于直接在函数/函数指针的声明前面直接加上typedef。
实例1:
typedef int func1(int, int);
int add(int x, int y)
{
printf("%d\n", x + y);
return x + y;
}
int main()
{
add(1, 2);
// func1 f1 = add;//error
func1 *f1 = add;
// func1 *f1 = &add; // equal to the previous line
f1(2, 3);
return 0;
}
这里定义了一个func1类型,func1是一个有两个int参数、返回int的函数类型。在main中使用func1类型时,只能定义func1*
的指针类型。
实例2:
int add(int x, int y)
{
printf("%d\n", x + y);
return x + y;
}
typedef int (*func2)(int, int);
int main()
{
add(1, 2);
func2 f2 = add;
f2(4, 5);
return 0;
}
这里定义了一个函数指针func2,func2指向有两个int参数、返回int的函数。、
5 函数指针数组
函数指针数组提供了一种利用数组索引的函数切换方式。
形式:
<所指向函数的返回类型> (* 函数指针变量 [ ]) (所指向函数的参数列表)
实例:
int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int y, int y);
int (*operation[])(int x, int y) = { add, sub, mul, div);
// 调用函数
opeartion[0](10,20);
PS: 使用函数指针数组时需要注意,如果索引越界,程序可能跳到任何地方。
6 函数指针作为函数参数
假如我们要设计一个接口search,用来在数据结构中搜索某个数据是否存在,每种数据结构对应的搜索算法实现都不同,这时就可以让用户自行输入搜索算法的实现。
首先有以下几种算法:
bool arraysearch(int n);
bool binarysearch(int n);
bool linkedlistsearch(int n);
我们可以通过函数指针动态调用这些函数。
bool search(bool (*funcptr)(int), int n)
{
return funcptr(n);
// return (*funcptr)(n); // 等价
}
实例:
int linkedlistsearch(int n)
{
printf("this is linkedlistsearch\n");
return 1;
}
int arraysearch(int n)
{
printf("this is arraysearch\n");
return 1;
}
int search(int (*funcptr)(int), int n)
{
// return (*funcptr)(n);
return funcptr(n);
}
int main()
{
search(arraysearch, 20);
search(linkedlistsearch, 20);
return 0;
}
运行结果:
this is arraysearch
this is linkedlistsearch
当然,对于search接口,我们也可以用typedef先定义一个函数指针类型,search参数列表里用函数指针类型表示。
typedef int (*funcptr)( int, int);
int search(funcptr f, int n)
{
return f(n);
}
再比如标准C库函数<stdlib.h>中的快速排序算法qsort(),该算法用来对array排序,但是array中可能存储int,也可能存储double,也可能是char,所以就需要用户手动实现算法中两个数值的比较。
void qsort (void *array, size_t count, size_t size, __comparison_fn_t compare)
最后一个参数compare的类型是__comparison_fn_t,这个类型的定义就是函数指针:
typedef int (*__compar_fn_t) (const void *, const void *)
用户手动实现compare比较算法给qsort函数调用。
应用实例:
int compare_double(const void *x, const void *y)
{
const double *x1 = (const double *)x;
const double *x2 = (const double *)y;
return (*x1 > *x2) - (*x2 - *x1);
}
int main()
{
double data[6] = {1.1, 3.1, 1.4, 5.2, 1.2};
qsort(data, 6, sizeof(double), compare_double);
for (int i = 0; i < 6; i++)
{
printf("%lf ", data[i]);
}
return 0;
}
结果:
0.000000 1.100000 1.200000 1.400000 3.100000 5.200000
7 函数指针作为函数返回值
函数指针用为函数返回值有两种实现方式:一种是先定义函数指针的类型,返回这个类型,这种方法比较容易;另一种是直接返回,这种方法比较复杂。下面举例说明:
方法1:
先typedef一个函数指针类型,然后用函数返回这个类型
typedef int (*funcptr)(int , int);
funcptr foo (int x, int y))
{
funcptr addptr = add;
return addptr(x,y);
}
实例:
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;
}
int (*funcptr[])(int x, int y) = {add, sub, mul, div};
// 定义一个接口,用户输入选择加减乘除运算
typedef int (*calc)(int, int);
calc operationfunc(int index)
{
return funcptr[index];
}
int main()
{
int choice, p1, p2, res;
printf("type 0-add 1-sub 2-mul 3-div\n");
scanf("%d", &choice);
printf("param1:\n");
scanf("%d", &p1);
printf("param2:\n");
scanf("%d", &p2);
/*
calc 是函数指针类型,指向int (*)(int,int)类型的函数
calculator 是函数指针变量,其值是接口函数operationfunc的返回值
operationfunc 是返回函数指针的函数, 返回int (*)(int,int)类型的函数指针,也就是calc类型
*/
calc calculator = operationfunc(choice);
res = calculator(p1, p2);
printf("res = %d", res);
return 0;
}
方法2:
方法2不用typedef定义一个函数指针类型的别名,而是通过一步步推导出一个复杂的函数声明,这与解读一个复杂的函数声明是一个相反的过程。
首先有以下两个声明:
int add(int, int);
int (*addptr)(int, int); //用来指向上面的函数,也是函数要返回的类型
现在我们想要定义一个无任何输入(函数参数列表是void),返回addptr函数指针类型的函数。
- 编写函数名:
funcptr
- 添加函数的参数列表:
funcptr(void)
- funcptr(void)返回函数指针
int (*)(int, int)
,那么添加取值操作“*
”,*funcptr(void)
就是一个函数 *funcptr(void)
是一个函数,就要有参数列表,funcptr返回的指针指向的函数有两个int参数,添加上去,就成了(*funcptr(void))(int , int)
,注意要给取值操作加上括号- 最后把funcptr返回的指针指向的函数的返回值添加上就可以了:
int (*funcptr(void))(int , int)
另一种观点是,只需要给 funcptr(void)
填上返回值类型就可以了,funcptr返回的是int (*)(int, int)
类型,也就是addptr类型,那么直接在addptr的声明 nt (*addptr)(int, int);
把addptr替换成 funcptr(void)
,或者是直接将 funcptr(void)
添加在int (*)(int, int)
里面的“*
”后面,得到int (*funcptr(void))(int , int)
。
8 函数指针声明总结
下面举几个函数指针声明的例子(来源书中)用于理解。
int add(int x, int y); //函数
int (* addfptr)(int x, int y); //函数指针
int* add(int x, int y); //函数
int* (*addfptr)(int x, int y); //函数指针
int add(int x, int y); //函数
int (*addfptr)(int x, int y); //函数指针
void callfunc(int (*addfptr)(int x, int y)); //函数,参数是函数指针
int (*(*addfptr))(int x, int y); //指针,指向返回值是函数指针的函数
//addfptr是一个指针,指向另一个指针,另一个指针指向参数是(int,int)返回值是int的函数
int (*retfuncptr(void)(int x, int y); //函数,返回函数指针
int*(*retfuncptr(void)(int x, int y);//函数,返回函数指针,函数指针指向的函数返回int 指针
如何解读复杂的函数/数组/指针声明?这里引用知乎用户邱昊宇的回答。
• 看见 [N] 读作 an array of N
• 看见 () 读作 a function that returns
• 看见 T * 读作 a pointer to T
然后从变量开始,先读右边的东西,再读左边的东西,然后被括号包裹着的话,就跳出去重复这个动作。比如 int *foo[3]
• 右边是个 [3] :an array of 3…
• 左边是 int * :pointer to int
• 所以完整的就是 an array of 3 pointers to int加上括号 int (*foo)[3]
• 右边没有东西
• 左边是个 * :a pointer to…
• 括号外面右边是 [3] :an array of 3…
• 括号外面左边是 int :int
• 所以完整的就是 a pointer to an array of 3 int再复杂一点 int (*(*vtable)[])()
• 首先是 *vtable
• 右边没东西 • 左边是 * :a pointer to…
• 去掉一层括号是 *X[]
• 右边是 [] :an array of…
• 左边是 * :a pointer to…
• 去掉一层括号是 int X()
• 右边是 () :a function that returns
• 左边是 int :an int
• 所以完整的就是 a pointer to an array of pointers to a function that returns int所以 int *(*f[3])() 就是
• 首先看 *f[3]
• 右边是 [3] :an array of 3…
• 左边是 * :pointer to
• 去掉一层括号是 int *X()
• 右边是 () :a function that returns
• 左边是 int * :a pointer to int
• 所以完整的就是 an array of 3 pointers to a function that returns a pointer to int