一、基础研究

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

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

我们知道int a(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一定要加括号。

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

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

执行结果:

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

(1)p=f在这里是将函数的入口地址赋给指针p,这说明函数的名字就代表它的入口地址,这不像我们对一些变量的地址进行操作要用到取址符&,而变量名则代表变量的值,比如int a,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语言的自由性,我们可以用它写出十分精简的程序,但是这样也容易造成使用出错,我们在使用时要小心。

c语言之函数指针的更多相关文章

  1. c语言之函数指针应用

    c语言之函数指针应用 1.函数指针与指针函数 在开始运用函数指针前,我们需要将两个概念即:函数指针与指针函数搞清楚. 函数指针,指明这个一个函数,但返回值为指针类型,语法格式为: 类型名* 函数名A( ...

  2. 谈谈自己对C语言中函数指针的一些理解 (第一次写博客,有点小兴奋哈)

    1.函数指针声明的格式及简单的使用 (1)格式:(返回值)(*函数指针名)(参数列表)    例如:声明一个无参数无返回值的函数指针(void)(*p)(void). (2)将函数指针指向某个无参数无 ...

  3. C语言的函数指针数组(好绕啊~看完这篇估计就通关了)

    转自https://www.cnblogs.com/chr-wonder/p/5168858.html int *(*p(int))[3] 今天有人问这个是啥?我一看直接就懵逼了…… 下面做一些简单的 ...

  4. C语言基础:函数指针 分类: iOS学习 c语言基础 2015-06-10 21:55 15人阅读 评论(0) 收藏

    函数指针:指向函数的指针变量. 函数名相当于首地址. 函数指针定义:返回值类型  (*函数指针变量名)(参数类型1,参数类型2,....)=初始值 函数指针类型:返回值类型  (*)(参数类型1,参数 ...

  5. c语言定义函数指针和typedef简写

    二种方法来定义函数指针 #include<stdio.h> #include<stdlib.h> #include<Windows.h> int add(int a ...

  6. C语言之函数指针、回调函数的使用

    一.背景 首先看下如下代码,这个定义是放在头文件的,在程序中tCdrvCallbackFkt也定义了另一个变量,而且括号后面还跟定义了几个变量,不理解这个定义. typedef void (PUBLI ...

  7. c语言的函数指针和函数指针数组的简单demo

    今天,简单记录一下,函数指针和函数指针数组的使用,废话不多说,直接贴上代码,里面有详细的注释,方便以后查阅. #include <cstdio> #include <Windows. ...

  8. c语言的函数指针

    简单定义并间接调用 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<time.h> void singas ...

  9. 可读性很强的C语言的函数指针定义

    通常C/C++程序里面要用到大量的指针,其语法非常难以阅读.比如下面的vp指针类型: #include <iostream> using namespace std; typedef vo ...

随机推荐

  1. JavaScript笔记(一),

    加法函数 javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显 //调用:accAdd(arg1,arg2) //返回值:arg1加上arg2的精确结果 function accA ...

  2. SQL Server不区分大小写的问题

    SQL Server不区分大小写的问题   默认情况下,SQL Server不区分大小写,如果数据表TEST的TNAME列中有数据“abcd”和“Abcd”, 如果使用查询语句:select * fr ...

  3. 互联网TCP/IP五层模型(一)

    转载自:阮一峰 我们每天使用互联网.你是否想过,它是怎样实现的? 全世界几十亿台电脑,连接在一起,两两通信. 上海的某一块网卡送出信号,洛杉矶的还有一块网卡竟然就收到了.两者实际上根本不知道对方的物理 ...

  4. Linux基本配置和管理 2 ---- Linux多命令协作----管道及重定向

    1 管道和重定向 1 在Linux中大多数命令都很简单,很少出现复杂的命令,每个命令只是实现一个简单的功能,我们可以通过组合不同的命令来实现复杂的功能 2 在Linux中几乎所有的命令返回的数据都是纯 ...

  5. 第一篇:K-近邻分类算法原理分析与代码实现

    前言 本文介绍机器学习分类算法中的K-近邻算法并给出伪代码与Python代码实现. 算法原理 首先获取训练集中与目标对象距离最近的k个对象,然后再获取这k个对象的分类标签,求出其中出现频数最大的标签. ...

  6. iOS 10 个实用小技巧(总有你不知道的和你会用到的)

    在开发过程中我们总会遇到各种各样的小问题,有些小问题并不是十分容易解决.在此我就总结一下,我在开发中遇到的各种小问题,以及我的解决方法.比较普遍的我就不再提了,这里主要讲一些你可能不知道的(当然,也有 ...

  7. Android实现网络多线程断点续传下载(转)

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  8. Android(java)学习笔记233: 远程服务的应用场景(移动支付案例)

    一. 移动支付:       用户需要在移动终端提交账号.密码以及金额等数据 到 远端服务器.然后远端服务器匹配这些信息,进行逻辑判断,进而完成交易,返回交易成功或失败的信息给移动终端.用户提交账号. ...

  9. Ubuntu下全命令行安装Android SDK

    为了在AWS云服务器上实现自动化打包Android APP的APK包,我需要远程命令行环境下安装Android SDK,当然还要用代理或者科学上网,这里简单整理一下过程: 首先,由于墙的原因,Andr ...

  10. Android客户端与服务端交互之登陆示例

    Android客户端与服务端交互之登陆示例 今天了解了一下android客户端与服务端是怎样交互的,发现其实跟web有点类似吧,然后网上找了大神的登陆示例,是基于IntentService的 1.后台 ...