C语言夜未眠6——深度剖析函数指针

墙,无处不在。即使在网上,我们也被GFW重重围住。在互联网中,我们犹如次等国民,难以自由地和外界交流。google被墙,facebook,twitter被墙,GitHub被墙,网友们纷纷抗议,开复老师的一条微博得到广大的支持。终于Github被解封了。虽然毕业半年了,进入公司后坚持学习技术的心始终未变。基础差也毫无畏惧,奋力追赶。

第一部分 什么是函数指针

一个指针变量,可以指向一个 int类型变量,可以指向一个常量字符串,可以指向一个指针变量或者一个函数。那么,什么是函数指针,他有什么作用?我们如何使用他 

1.1 函数指针概念

指针函数是指向函数的指针,是变量。 C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。
来看一个这样一个声明:
void (*f) ( );
 但由于有括号存在,首先执行的是和 *结合,所以f 是一个指针;接下来执行 ( ),表明f 指向一个函数,而且这个函数不返回任何值。
可以得出这个结论:f是一个指向不接受参数且不返回任何值的函数的指针,简称函数指针 (pointer to function)

1.2 函数指针与指针函数,数组指针与指针数组

例如:
函数指针: void (*fun) ( );
指针函数: int *func ();
数组指针: int (*pArray) [10];
指针数组: int *pt[10];
函数指针与指针函数的区别:
1. 函数指针是指针变量,而指针函数本质上是函数。重点在于末尾两个字是指针还是函数。
2. 函数指针是指针指向函数,指针函数是函数返回指针。
函数指针与数组指针的共同点:
          可以看出,函数指针和数组指针的声明,指针声明符 *和标识符都在()内。因为()的优先级最高。

1.3 函数指针的作用

1.调用函数
2.做函数的参数

第二部分 函数指针的初始化及使用

虽然函数指针指向的是函数,不过函数也有特定类型的,函数的特定类型与返回值类型,形参列表确定,与函数名没有任何关系。对函数指针初始化时可以采用相同类型函数的函数名或函数指针 (当然还有零指针常量)

2.1 初始化函数指针

          例如:void (*func) (int a);
                    void test (int a);
          对于以上的声明,我们可以对 func进行如下初始化:
Func = test;
Func = &test;
一般地,我们可以这样地初始化,事实上, func = *test;也是可以的,至少在vs2010中测试通过了。
          C语言规定函数名会被转换为指向这个函数的指针,除非这个函数名作为  & 操作符或sizeof操作符的操作数 (注意:函数名用于sizeof的操作数是非法的)也就是说f = test; test被自动转换为&test,而 f = &test;中已经显示使用了&test,所以 test就不会再发生转换了。因此直接引用函数名等效于在函数名上应用  & 运算符,两种方法都会得到指向该函数的指针。

2.2 函数名究竟是什么

          为什么func = *test是对的?来看以下例子:
#include <stdio.h>

int test(void );

int main(void )
{
    printf( " test=%p\n",test);
    printf( "&test=%p\n",&test);
    printf( "*test=%p\n",*test);
    return 0;
}

int test(void )
{
    printf( "test invoked!");
    return 0;
}
 

运行结果是:


原来他们都表示同一个地址,当然可以调用函数啦。首先来看函数名 test,它与数组名类似(注意:只是类似 ),是一个符号用来标识一个函数的入口地址,在使用中函数名会被转换为指向这个函数的指针,指针的值就是函数的入口地址, &test在前面已经说了:显示获取函数的地址。对于 *test,可以认为由于test已经被转换成了函数指针 指向这个函数,所以*test就是取这个指针所指向的函数名,而又根据函数名会被转换指向该函数的指针的规则,这个函数也转变成了一个指针,所以 *test最终也是一个指向函数test的指针。对它们采用 %p格式项输出,都会得到以 16进制数表示的函数test的入口地址。注意函数的地址在编译期是未知的,而是在链接时确定的。
通过函数指针调用函数
f = test;
f ( );
(*f) ( );   //指针两侧的括号非常重要,表示先对 f解引用,然后再调用相应的函数

ANSI C标准将 f ( )认为是(*f)( ) 的简写形式,并且推荐使用 f ( )形式,因为它更符合函数调用的逻辑。 要注意的是:如果指向函数的指针没有初始化,或者具有 0(零指针常量 ),那么该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值后指向某个函数才能安全地用来调用函数。

2.3typedef可以用于定义函数指针类型

【语法】 typedef可以用于定义函数指针类型
【例如】 typedef  int (*ptf)(int,double);

要说明一下,对于typedef  int (*ptf) (double*,char); 注意不要用#define的思维来看待typedef,如果用 #define的思维来看的话会以为 (*ptf)(double*, char)int的别名,但这样的别名看起来好像又不是合法的名字,于是会处于迷茫状态。实际上,上面的语句把 ptf定义为一种函数指针类型的别名,它和函数指针类型 int (*) (double*, char);等价,也就是说ptf现在也是一种类型。

          因此,他可以当作指针函数的返回值,如:
ptf  function(double);

下面分析一个函数指针和指针函数结合的例子:

void (*signal (int sig, void (*func) (intsiga)) ) ( int siga );

看上去确实有些复杂,让我们来分析。现在要分析的是 signal,因为紧邻 signal的是优先级最高的括号,首先与括号结合,所以 signal为一个函数,括号内为 signal的两个形参,一个为 int型,一个为指向函数的指针。接下来从向左看, *表示指向某对象的指针,它所处的位置表明它是 signal的返回值类型,现在可以把已经分析过的 signal整体去掉,得到 void (*) ( int siga ),很清晰了吧。又是一个函数指针,这个指针与 signal形参表中的第二个参数类型一样,都是指向接受一个 int型形参且不返回任何值的函数的指针。同样地,用 typedef可以将这个声明简化:

typedef void (*p_sig) (int);

p_sig signal(int sig, p_sig func);

这个signal 函数是C语言的库函数,在 signal.h中定义,用来处理系统中产生的信号,是 UNIX/Linux编程中经常用到的一个函数,所以在此单独拿出来讲解。

2.4 函数指针数组

还有一种较为常用的关于函数指针的用法——函数指针数组。假设现在有一个文件处理程序,通过一个菜单按钮来选择相应的操作 (打开文件,读文件,写文件,关闭文件 )。这些操作都实现为函数且类型相同,分别为:

void open( );

void read( );

void write( );

void close( );

现在定义一个函数指针类型的别名

PF typedef void (*PF) ( );

把以上4种操作取地址放入一个数组中,得到:

PF file_options[ ] = {

                 &open,

                 &read,

                 &write,

                 &close

};

这个数组中的元素都是指向不接受参数且不返回任何值的函数的指针,因此这是一个函数指针数组。接下来,定义一个函数指针类型的指针 action并初始化为函数指针数组的第一个元素: PF* action = file_options;,如果不好理解,可以类比一下 int ia[4] = {0, 1, 2, 3}; int *ip = ia;,这里PF 相当于int,这样应该比较好懂了。通过对指针 action进行下标操作可以调用数组中的任一操作,如: action[2]( )会调用 write操作,以此类推。在实际中,指针 action可以和鼠标或者其他 GUI对象相关联,以达到相应的目的。

2.5 复杂的函数指针声明

【右左法则】 从未定义的变量名开始阅读声明,先向右看,然后向左看。当遇到括号时就调转阅读的方向。括号内的所有内容都分析完毕就跳出括号。这样一直继续下去,直到整个声明都被分析完毕。
来分析一个的例子: int * (* (*fp) (int) ) [10]; 
阅读步骤:
1.从未定义的变量名开始阅读  --------------------------------------------fp
2.往右看,什么也没有,遇到了 ),因此往左看,遇到一个 * ------ 一个指向某对象的指针
3.跳出括号,遇到了(int) ----------------------------------- 一个带一个int 参数的函数
4.向左看,发现一个* --------------------------------------- (函数)返回一个指向某对象的指针
5.跳出括号,向右看,遇到 [10] ------------------------------  一个10元素的数组
6.向左看,发现一个* --------------------------------------- 一个指向某对象指针
7.向左看,发现int -----------------------------------------int类型
所以 fp是指向函数的指针,该函数返回一个指向数组的指针,此数组有 10int* 型的元素。
第三部分 总结
1. 指针函数是指向函数的指针,是变量
2. ANSI C标准将f ( )认为是(*f)( )的简写形式,并且推荐使用f ( )形式,因为它更符合函数调用的逻辑。
3.  关于函数指针的加减运算没有意义。
4. 在使用中函数名会被转换为指向这个函数的指针,指针的值就是函数的入口地址,函数的地址在编译期是未知的,而是在链接时确定的。
5.  对于复杂的函数指针声明,可以用右左法则来进行分析。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值