一、基础研究

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

指针有很多种:整型指针、结构体指针、数组指针等等,它们的本质是它们的值都是一个地址,只不过整形指针的值是一个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. Redis 实现用户积分排行榜

    排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择. 一般排行榜都是有实效性的,比如“用户积分榜”.如果没有实效性一直按照总榜来排,可能榜首总是几个老用户,对 ...

  2. VS2012 无法启动IIS Express Web服务器的解决方案

    本文转载:http://blog.csdn.net/hongleidy5000/article/details/22732621 打开VS2012解决方案资源管理器 -> 点选 Web 项目选择 ...

  3. OpenCV LDA(Linnear Discriminant analysis)类的使用---OpenCV LDA演示样例

    1.OpenCV中LDA类的声明 //contrib.hpp class CV_EXPORTS LDA { public: // Initializes a LDA with num_componen ...

  4. J2EE之WebLogic Server

    WebLogic是用于开发.集成.部署和管理大型分布式Web应用. 网络应用和数据库应 用的Java应用server. 将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用 ...

  5. Java 理论与实践: 用弱引用堵住内存泄漏---转载

    要让垃圾收集(GC)回收程序不再使用的对象,对象的逻辑 生命周期(应用程序使用它的时间)和对该对象拥有的引用的实际 生命周期必须是相同的.在大多数时候,好的软件工程技术保证这是自动实现的,不用我们对对 ...

  6. bootstrap栅格布局

    <!DOCTYPE html> <html lang="en"> <head> <!-- //简介:boststrap内置了一套响应式,移 ...

  7. ASP.NET5中间件

    小的应用组件可以包含到Http请求管道当中,ASP.NET5 集成了中间件,被包在了应用程序的Configure方法当中. 1. 什么是中间件 中间件是一组被装到应用程序管道的请求和响应中的组件.每一 ...

  8. linq的一些用法总结

    获取列表数据. IList<Model> list = dao.getmx(Model, pageInfo);//获取数据列表 1.将列表中id一样的数据进行group by分组,并返回序 ...

  9. Content-Disposition的使用和注意事项

    转载:http://www.cnblogs.com/jzaileen/articles/1281025.html 最近不少Web技术圈内的朋友在讨论协议方面的事情,有的说web开发者应该熟悉web相关 ...

  10. (转)PHP正则表达式的快速学习方法

    1.入门简介 简单的说,正则表达式是一种可以用于模式匹配和替换的强有力的工具.我们可以在几乎所有的基于UNIX系统的工具中找到正则表达式的身影,例如,vi编辑器,Perl或PHP脚本语言,以及awk或 ...