最近在探究Objective-C中block的实现原理,然后就不自觉的复习了一下C语言的函数指针。正所谓万变不离其宗,虽说OC中的block跟简单的函数指针相比已经大有不同,不过二者的表现形式还是有很多相似的地方。
首先做一个声明:本文中的一些基础理论知识,来自其他的技术博客或者论坛,为了尊重原创,在这里将尽可能完整无损的呈现给想要夯实一下基础知识的小伙伴。
在开始之前,可以先下载作者为这篇文章所写的demo:简易四则运算器,总共代码在100行左右,通过这个小demo来一窥函数指针的大概。
(附赠一款录制gif的工具LICEcap,使用上跟苹果的QuickTime相像,需要划定一个录制范围,当最后结束录制的时候会自动为你生成gif图。)
函数指针是什么?
先来看函数调用是怎么回事。一个函数占用一段连续内存。当调用一个函数时,实际上是跳转到函数入口地址,执行函数体的代码,完成后返回。如何找到对应的入口地址?这是由函数名来标记的,实际上,函数名就是函数的入口地址。
函数指针是一种特殊类型的指针,它指向一个函数的入口地址。
注意:除了void
类型指针是无类型的指针外,其他所有指针都是有对应类型的,例如int *pint
、struct studentdata *psdata
等,只有指明了指针所指的数据类型,编译器才能为指针分配或预计分配相应大小的存储空间,指针的算术运算如pint++等才是有意义的。因此,定义了某种类型的指针之后,除非使用强制类型转换,那么它只能指向相应数据类型的变量或常量,不同类型的指针或数据之间不可混用。所以指针的类型实际上是一种身份标志的作用。
函数指针如何表明自己的身份呢?为了避免混乱,必须也要作出相应规定,不同函数的函数指针不能混用。例如,int func1(int arg11, char arg12)
与int func2(char arg)
的函数指针就不能混用,要定义可以指向func1的函数指针应该这样:
int (*pfunc1)(int, char) = func1;
定义可以指向func2的函数指针则该如下:
int (*pfunc2)(char) = func2;
从函数指针的定义可以看出,函数指针的类型实际上是由函数签名决定的。函数签名就象是函数的身份证,一个函数的函数签名是独一无二的,具有相同函数签名的函数实际上就是同一函数。函数签名包括函数名、函数形参类型的有序列表和函数返回值类型。
一个函数指针的定义规定了它只能指向特定类型的函数。如果两个函数的形参列表和返回值类型相同,只有函数名和函数体不同,则可以使用相同类型的函数指针。
例如,如果还有一个函数int func3(char arg)
,则上面定义的可以指向函数func2
的函数指针也可以用于指向func3
,即:
pfunc2 = func3;
再使用pfunc2(char ARG)
就可以调用函数func3
,这时指令计数器(PC)指向函数入口,从此开始执行函数体代码。
如何使用函数指针?
- 定义合适类型的函数指针变量:
int (*pfunc)(int, int)
; - 给函数指针变量赋值,使它指向某个函数入口:
int example(int, int)
;pfunc = example;
/将函数入口地址赋给函数指针变量/ - 使用函数指针来调用相应的函数;
retval = pfunc(10, 16);
或者:retval = (*pfunc)(10, 16);
上面两句都与retval = example(10, 16);等价。
理解:一个指针变量p实际上也和普通的变量一样,要占存储空间(通常与平台的虚拟地址一样宽),也有其自身的存储地址&p;不同的是,在指针变量p的值有特殊的意义,它是另外一个变量或常量的地址值,也就是说,在地址为&p的存储单元上存放着另外一个数据的地址。因此,p实际上是将p看作它指向的数据的地址来使用,操作符是引用相应地址中的数据,也就是对地址为p的存储单元中存放的数据进行操作。
为什么要使用函数指针?
前面介绍了函数指针的基本知识和使用规范。下面介绍函数指针的实际用途。不过首先要对前面的知识再做一个补充,因为下面的应用很可能用到这一特性。前面指出,除函数名之外的函数签名内容(函数返回值类型和形参列表)决定了函数指针的类型。实际上还有一种特殊的或说通用的函数指针,在定义这类函数指针时,只需要指定函数返回值类型,而留空形参列表,这样就可以指向返回值类型相同的所有函数。例如:
int (*pfunc)();
这样定义的pfunc
就可以指向前面提到的func1
和func2
,因为他们都返回整型值。
注意:int (*pfunc)()
与int (*pfunc)(void)
不是一回事,后者不允许接受任何参数。
函数指针最常见的三个用途是:
作为参数传递给其他函数。这样可以把多个函数用一个函数体封装起来,得到一个具有多个函数功能的新函数,根据传递的函数指针变量值的不同,执行不同的函数功能。这是函数嵌套调用难以实现的。参数的传递可以由程序员设定,也可以由用户输入读取,因此具有较大的灵活性和交互性。另外还可以用于回调函数。使用void配合,还可以将对不同数据类型的数据进行相同处理的多个函数封装为一个函数,增强函数的生命力。
用于散转程序。这种程序首先建立一个函数表(实际上是一个函数指针数组),表中存放了各个函数的入口地址(或函数名),根据条件的设定来查表选择执行相应的函数。这样也可以将多个函数封装为一个函数或者程序,散转分支条件可以由程序员设定,也可以由用户输入读取,甚至是外设的某种特定状态(这种状态可以是不受人为控制的)。
实现C的面向对象的类的封装。C语言中的struct与C++中的class有很大不同,除了缺省的成员属性外(struct的成员缺省为public的,可随意使用,而class成员缺省为private的),struct还很难实现类成员函数的封装。struct的成员一般都是数据成员,而非函数成员。因此,为了在C语言中,为某个struct定义一套自己的函数对结构数据成员进行操作,可以在struct结构体中增加函数指针变量成员,在初始化时使它指向特定函数即可。
基础的理论知识就介绍这些,下面来举例分析四则运算计算器demo中对函数指针的运用。
首先是定义加减乘除的基本运算如下。这是最基础的运算,不需要考虑调用的顺序。
long long add(int a,int b){
return a + b;
}
long long int sub(int a,int b){
return a - b;
}
long long int mul(int a ,int b){
return a*b;
}
long long int divi(int a,int b){
return a/b;
}
然后观察上面的函数,发现除了函数名不一样外,返回值与参数类型都是一样的,所以可以用一个函数指针来指向它们。
函数指针的声明如下所示:
typedef long long int (*FUNC)();
FUNC pfunc;
首先定义了一个函数指针的类型:FUNC
,这样我们就可以更加方便的使用这个类型来声明函数指针变量了。下面的FUNC pfunc;
就是声明了一个名为pfunc的函数指针变量。
最后就是求和运算了
double calculator(long long x,long long y,FUNC func){
double result;
result = (*func)(x,y);
return result;
}
其中的func
函数指针会根据我们所点击的运算符的不同而指向不同的函数,这样就实现了一个非常简单的计算器了。
最后附上demo地址
参考资料:
http://www.360doc.com/content/13/1104/12/13670635_326518097.shtml