第二十二篇 -- 研究下函数(五)—— 函数指针
所谓函数指针就是指向函数的指针。函数指针也是一个变量,可以指向不同的函数。同时通过函数指针可以调用其指向的函数,从而使得函数调用更加灵活。
函数地址
函数也是有地址的。编译之后的函数,其实是一组指令的集合。这样一组指令在程序运行时存在于内存中,其起始地址就是该函数的地址,也称做函数的入口地址。在编写程序时,可以用函数名来表示函数的地址,也可以在函数名之前加上取地址符号“&”表示函数的地址。这两种方式是等价的。例如,对于下列求和函数:
int Add(int a, int b) { return a + b; }
函数名“Add”以及“&Add”都表示Add函数的地址。如图8.10所示。
typedef int (* FUN_PTR) (int a, int b); //定义函数指针类型FUN_PTR FUN_PTR pf1; FUN_PTR pf2;
图8.10 内存中的Add函数
如图8.10所示,内存地址0x401390即函数Add在内存中的地址。可以使用强制类型转换,将该地址保存在一个长整型变量中:
long a = (long)Add; //使用C语言方式的类型转换 long b = (long)&Add; long c = static_cast<long>(Add); //使用C++语言方式的static_cast操作符 long c = static_cast<long>(&Add);
另外,也可以通过库函数或者cout对象将Add函数的地址输出到显示器上:
printf("%p", Add); //调用print函数,输出Add函数的地址 printf("%p", &Add); cout << hex << Add; //使用cout对象输出Add函数的地址,其中hex表示以16进制方式输出 cout << hex << &Add;
定义函数指针
定义函数指针包括两个部分,即声明和初始化。在函数声明的基础上做一个小小的改变,就可以将其变成一个函数指针的声明。例如对于Add函数,只要将函数名Add替换成一个函数指针名,例如“pf”,并在其前面加上“(*”、在后面加上“)”即可:
int (*pf) (int a, int b); //定义函数指针变量pf
当心:函数指针名“*pf”两侧的括号不能省略,否则就成了一个返回“int *”类型的函数声明。正式这个括号使得星号“*”和标识符“pf”组成了一个整体,表示pf是一个指针。
对于函数指针的声明,参数列表和返回类型是关键部分。只要这两项确定,就确定了该函数指针的类型。这个类型也决定了该函数指针所能指向的函数类型,即具有相同参数列表和返回类型的函数。
有的时候函数的参数列表和返回值比较复杂,每次定义这样的函数指针都要重写一遍比较繁琐。因此可以用类型定义运算符“typedef”为该函数定义一个简单的类型名。有了这样一个类型名之后,就可以用来定义函数指针变量,而不用重写函数参数列表和返回类型。例如:
typedef int(*FUN_PTR) (int a, int b); //定义函数指针类型FUN_PTR FUN_PTR pf1; FUN_PTR pf2;
虽然相比定义函数指针变量,定义函数指针类型只是多了一个typedef关键字。但也正因为如此,FUN_PTR也不再是一个变量,而变成了一个类型。同普通指针一样,如果没有明确初始化,则函数指针的值是一个随机数,使用这样的指针非常危险。因此在使用函数指针之前必须对其进行初始化或者赋一个初值,即将一个函数名赋给该函数指针变量。例如:
int Add(int a, int b) { return a + b; } int(*pf1)(int a, int b) = Add; typedef int(*FUN_PTR) (int a, int b); FUN_PTR pf2 = Add; FUN_PTR pf3 = &Add;
使用函数指针调用函数
使用函数指针调用函数同函数调用一样,只要在函数指针后面加上实参列表即可。例如:
int Add(int a, int b); int main() { int(*pf)(int a, int b) = Add; int x = pf(3, 4); cout << "x = " << x << endl; return 0; } int Add(int a, int b) { return a + b; }
使用函数指针时不必像使用一般指针那样解引用。不过有时为了明确起见,也可以解引用。例如:
int x = (* pf) (3, 4); //函数指针解引用
这样做的好处是可以明确指明pf是一个函数指针,否则只看到定义,才能分辨出pf到底是一个函数指针,还是一个函数。
函数指针的用途
如果仅仅是像上节那样使用函数指针,那就有点儿画蛇添足了,还不如直接使用函数来得方便。实际上,函数指针通常用做回调函数。所谓回调函数是指将一个函数用函数指针保存下来,然后直到需要的时候在进行调用。至于函数指针到底保存的是什么函数,则由设置者决定,而调用者是无从知晓的。这个调用过程称作回调,这个函数指针称作回调函数指针,简称回调函数。
比如现在有一个排序函数sort,用来排序一个数组。但是按照什么标准排序(从大到小,还是从小到大),则需要由sort函数的调用者确定。调用者可以通过给函数传递一个函数指针来确定排序的标准。下面使用回调函数确定排序标准。
void sort(int ary[], int n, FUN_PTR pf); int main() { cout << "——使用函数指针确定排序标准——" << endl; int ary[3] = {5, 3, 6}; bool less(int a, int b); //声明函数less sort(ary, 3, &less); //使用less回调函数,从大到小排列数组 for (int i = 0; i < 3; i++) { cout << ary[i] << ' '; } cout << endl; bool big(int a, int b); //声明函数big sort(ary, 3, &big); //使用big回调函数,从小到大排列数组 for (int i = 0; i < 3; i++) { cout << ary[i] << ' '; } cout << endl; return 0; } void sort(int ary[], int n, FUN_PTR pf) { for (int i = 0; i < n - 1; i++) { int index = i; int val = ary[i]; for (int j = i + 1; j < n; j++) { if (pf(val, ary[j])) { val = ary[j]; index = j; } } if (val != ary[i]) { ary[index] = ary[i]; ary[i] = val; } } } bool less(int a, int b) { return a < b; } bool big(int a, int b) { return a > b; }