C语言之函数指针

2016-12-26 09:26:55来源:作者:Linux公社人点击

一、基础研究

这里研究的内容是函数指针,需要我们在研究后构造程序来描述函数指针数组的用法和向函数传函数指针的方法。

指针有很多种:整型指针、结构体指针、数组指针等等,它们的本质是它们的值都是一个地址,只不过整形指针的值是一个 int 型数据的地址,结构体指针的值是一个结构体变量的地址,而这里的函数指针指向的不是一个固定类型数据的地址了,而是一个函数的入口地址。

我们知道 inta ( char , char );是返回值为 int 类型,参数为 char 、 char 类型的函数 a ,而书上说 int ( *a )( char , char );是返回值为 int 类型、参数为 char 、 char 的函数的函数指针变量,要注意这里 a 是一个函数指针,它存放的是一个地址,它的大小为 2 字节,而且它是要当指针用而不是当函数用,即它只能赋值、取值、取址、加减、与同类型指针比较大小,而不能传参、返回值。那么这里 *a 为什么要用括号括起来呢,如果不用会怎样?如果不用括号的话就是 int*a ( char , char ),我们知道其实 int*p ;也可以看成( int* ) p ,即指针 p 是一个 int* 型的数据,所以 int*a ( char , char )可以看成是( int* ) a ( char , char ),那么这就是一个参数为 char 、 char 型,返回值为 int* 型指针的函数了。它是一个函数而不是一个指针,不要以为它返回的是指针类型就是函数指针了。查阅资料可知返回指针型变量的函数叫做指针函数。指针函数可以写成 int*p ( char , char )或者( int* ) p ( char , char ),即返回值的类型可以不加括号,但是函数指针必须写成 int ( *p )( char , char ),也就是 *p 一定要加括号。

那么函数指针的类型怎么描述呢 ? 整形变量 inta 的类型是 int ,函数指针 int ( *a )( char , char )的类型就是 int ( * )( char , char )。

下面我们来看一看程序 1 :

执行结果:

观察程序可以发现,程序打印出了 main 函数和 f 函数的入口地址,将 f 的地址赋给了三个不同类型的变量并将它们打印出来,然后以两种不同的方式调用函数 f 并将返回值打印出来。观察程序可以发现以下问题:

(1)p=f 在这里是将函数的入口地址赋给指针 p ,这说明函数的名字就代表它的入口地址,这不像我们对一些变量的地址进行操作要用到取址符 & ,而变量名则代表变量的值,比如 inta , a 表示 a 存储的值而 &a 才表示它的地址,但是数组的使用和上式很相似,数组名代表的是数组的首地址而不是第一个元素的值。如下面程序所验证的:

(2)由 a=p ( 1 , 2 );语句,我们发现如果把函数 f 的入口地址赋给指针 p ,那么可以用指针 p 来代替 f 调用 f 函数,这是为什么呢?我们之前的研究指出函数名存储的是函数的入口地址,从汇编角度看,要调用函数,先将函数的参数入栈,再跳转到函数名所表示的地址并运行函数里的语句。这里的函数名起的作用就是表示函数的地址,让函数被调用的时候能够跳转到函数的地址,那么我们将函数地址赋给函数指针之后,函数指针的值就是函数的入口地址,那么函数指针完全可以代替函数名来表示函数的地址。那么既然可以用函数名表示函数的地址,那么为什么要再用函数指针呢,函数指针有什么意义呢?这个问题我们后面再研究。

因为我们之前将函数名的值强制转换成 int 型赋给了变量 b ,所以我们也可以用 b 来表示函数的地址,只不过要先将它再转换成函数指针类型,这样才能表示一个地址。

(3)我们发现函数 f 的返回值是 int 型,而它的返回值等于 a+b ,但是 a 和 b 都是 char 型变量,为什么两个 char 型变量相加可以返回一个 int 型变量呢?查阅资料,发现函数在返回时如果要返回的值的类型与函数的类型不同,那么会使用强制类型转换将要返回的值的类型强制转换成函数的类型再返回,即使函数的类型比要返回的值的类型小也是这样,如下图:

再看程序 2 :

这个程序输出了 main 函数的偏移地址还有它的段地址加偏移地址、 f 函数的偏移地址还有它的段地址加偏移地址、还有 p 、 b 、 c 、 a 的值。与上一个程序不同的是这里的函数指针 p 被定义成远指针,这里 far 要写在括号里 *p 的前面,这样将 f 赋给 p 则 p 存储的是 f 的段地址加偏移地址。这里我们可以将 c 强制转换为函数指针的类型再代替 f 调用函数,但不能将 b 强制转换再使用,因为这里 b 是一个 int 型的变量,他只存储了偏移地址的数据而没有段地址的数据,如果转换成 far 指针会出错。

那么再回到开始的问题:怎么构造程序来描述函数指针数组的用法和向函数传函数指针的方法呢?函数指针数组首先是一个数组,数组里的元素是函数指针,也就是每一个元素都是一个地址,指向一个函数入口。所以数组有几个元素,就要有几个函数。而向函数传函数指针,就是把函数指针作为函数的参数,需要在定义和声明时将函数的参数定义为函数指针。程序如下:

这里定义了一个函数指针数组 a ,并将函数 f1 、 f2 、 f3 的地址分别存放到数组中,之后调用函数 f ,并用数组将函数 f1 、 f2 、 f3 的地址作为参数提供给 f 。在函数 f 里根据传进来的参数对函数 f1 、 f2 、 f3 进行了调用,将 f1 、 f2 、 f3 的返回值相加并返回到 main 函数, main 函数再将 f 的返回值打印出来。这里我们定义并利用了函数指针数组,向函数里传递了函数指针。

二、扩展研究

1、既然可以用函数名表示函数的地址,那么为什么要再用函数指针呢,函数指针有什么意义呢?

答:可以说一个函数名就相当于一个函数指针,但是只是这一个函数的函数指针,我们使用函数指针,可以随时改变它的值,让它指向不同的函数以方便使用。比如高级语言实现一个下拉菜单,其实就是每个菜单项是一个函数,定义一个函数指针,当用户选择一个选项时,让函数指针指向它对应的函数执行,即实现了相应功能。

2、我们先来看一个题目:有一段程序存储在起始地址为 0 的一段内存上,如果我们想要调用这段程序,请问该如何去做?答案是 (*(void(*)())0)() 。很显然我们调用的这个函数是没有参数的,而 0 是它的地址,所以我们可以把 0 转换成函数指针,让它指向这个函数,即( void ( * )) 0 。但是这只相当于地址 0000:0000 ,那么它就相当于函数名啊,函数不就是( void ( * )) 0 ()了,为什么答案是 (*(void(*)())0)()呢?还有 int*p 表示从以 p 的值为地址所指向的那个空间里取出大小为 int 类型的值。那么定义( void ( *p ))()的话, *p 表示取的值的大小是多少?结果查阅资料发现 void 型指针不能复引用,即 *p 是错误的用法。

3、函数指针在定义的时候一定要定义函数的参数类型和个数吗?

答:可以不定义。

三、研究总结

我们之前学习指针主要是学习数据类型的指针,这类指针的特点是存储一地址,这个地址存放的是指定的空间,而函数指针指向的是一个函数。但是这里函数指针里的数据类型(如 int ( *p )( char ))表示的是函数的返回值类型,那么程序怎么知道函数有多长,该在内存里取多少数据呢?我觉得是通过函数的返回语句判断函数结束了。

我觉得函数指针容易弄错的地方就是它后面跟了函数的参数类型,导致我们在传参时老想将这些参数传进去,而实际上要传的是一个指针。还有从别的指针的定义可以直接看出指向的空间大小,而函数指针只能看出函数的返回类型。

从宏观来看,函数指针让我们调用函数时也能够直接从内存地址调用了,这充分说明了 c 语言的自由性,我们可以用它写出十分精简的程序,但是这样也容易造成使用出错,我们在使用时要小心。

本文永久更新链接地址 : http://www.linuxidc.com/Linux/2016-12/138778.htm

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台