在内存中函数的存放也是一段连续的内存,函数名就是指向改内存中的首地址,所以也可以将这个函数的首地址赋给一个指针变量,这样通过指针变量就可以访问改函数。
  那么为什么要通过指针来访问函数呢?下面通过一个简单的例子来演示一下。


int (*fun)(int m,int n);int Add(int x,int y)
{return x + y;
}
int Sub(int x,int y)
{return x - y;
}
int Mul(int x,int y)
{return x * y;
}
int Div(int x,int y)
{return x / y;
}int main()
{int ret = 0;int i = 0;for(i=0; i<4; i++){switch(i){case 0:fun = Add;break;case 1:fun = Sub;break;case 2:fun = Mul;break;case 3:fun = Div;break;}ret = fun(10,5);printf("%d\r\n",ret);}system("pause");return 0;
}

  定义了加、减、乘、除四个函数,在for循环中依次将这四个函数传递给函数指针,分别计算两个整数的加、减、乘、除的结果。代码执行结果如下:

  看到这里就会有个疑问?为什么非要用函数指针来实现呢?直接在switch语句中调用不同的函数不是依然能实现这个过程吗?对于这个例子来讲,是没有必要非要用指针啦传递函数。完全可以直接调用具体函数来实现。

  上面的例子中由于所有函数的功能都是自己实现的,所以直接调用和通过指针调用没有多大的区别,但是在很多情况下,函数需要用户自己去实现,那么此时使用指针的优势就比较明显了。比如下面的例子:

int values[] = { 88, 56, 100, 2, 25 };int up (const void * a, const void * b)
{return ( *(int*)a - *(int*)b );
}int down (const void * a, const void * b)
{return ( *(int*)b - *(int*)a );
}int main()
{int n;puts("排序之前的列表:");for( n = 0 ; n < 5; n++ ){printf("%d ", values[n]);}puts("\n升序排列");qsort(values, 5, sizeof(int), up);puts("排序之后的列表:");for( n = 0 ; n < 5; n++ ){printf("%d ", values[n]);}puts("\n降序排列");qsort(values, 5, sizeof(int), down);puts("排序之后的列表:");for( n = 0 ; n < 5; n++ ){printf("%d ", values[n]);}system("pause");return 0;
}

  这里使用了C库中的排序函数,函数原型如下:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
参数:base -- 指向要排序的数组的第一个元素的指针。nitems -- 由 base 指向的数组中元素的个数。size -- 数组中每个元素的大小,以字节为单位。compar -- 用来比较两个元素的函数。

  这里最后一个比较函数就需要用户自己去实现,根据用户的提供的函数,排序函数就可以自动的去选择是升序排列还是降序排列。如果给qsort()函数里面传递的是up()函数,那么数组中的数据就会升序排列,如果传递的是down()函数,那么数组中的数据就降序排列。

  在这个例子中qsort()函数是C库函数,这个函数的最后一个参数也是一个函数,这个函数必须由用户来提供。那么此时用指针来传递函数是最方便的,因为用指针传递函数的时候,函数名可以由用户自己定义。这样qsort()函数的通用性就会非常好了。如果要传递的函数名称和参数都必须是固定的话,在使用起来就有很大的局限性。

  所以使用指向函数的指针来传递参数时,可以更好的封装函数,提高函数的通用性和易读性。

  下面详细的说一下,如何将一个函数替换为指针。
首先看一个函数原型:

int Add(int x,int y);

  Add()函数类型是 “带int int类型参数,返回类型是int型的函数”,将它改写为指针pf指向该函数类型:

int (*pf)(int x,int y);

  替换的方式为 将函数名改为 ( * pf) ,其他部分保持不变。这样就将函数替换为指向指针的函数了,这里一定要加上小括号,如果不加小括号的话就会变成

int *pf(int x,int y);

  由于小括号的优先级高,所以pf就会先和后面的小括号结合,变成 pf(int x,int y),函数的返回类型就变成了 int * ,这样含义完全就变了,变成了 函数pf()有两个参数,它的返回值为 int 指针类型。所以这里一定要加上小括号。

  其中参数列表中可以会将变量省略,只写变量的类型。上面指向函数的指针可以简写为:

int (*pf)(int ,int );

  由于函数名指向了内存中函数的起始地址,所以要将函数的地址可以直接使用下面的方法:

pf = Add;

  将函数Add的首地址直接赋值给指针pf,这样通过指针pf就可以访问Add()函数了。
所以上面的函数 Add(x,y) 可以直接替换为 (*pf)(x,y) ,相当于 Add == (*pf)。但是通过上面第一个例子可以发现,使用 pf(x,y)调用函数的时候也是可以正常使用的。虽然 (*pf)(x,y) 和 pf(x,y)这两种调用方式看起来是矛盾的,但是C语言中对于函数指针来说这两种用法都可以,是一样的。

 fun = Add;ret = fun(10,5);printf("%d\r\n",ret);fun = Sub;ret = (*fun)(10,5);printf("%d\r\n",ret);

  通过这两种方式分别调用函数,输出结果如下:

  如果要更复杂一点,可以声明一个指针数组,依次存储这4个函数。

typedef int (*fp)(int int);
fp fun[4] = {Add,Sub,Mul,Div};

  通过一个函数指针数组可以更方便的调用各个函数。

C语言学习笔记---指向函数的指针相关推荐

  1. c语言putchar_C语言学习笔记(三)指针

    0 往期链接 Chenglin Li:C语言学习(一)全部知识点 Chenglin Li:C语言学习(二)指针函数分配内存 1 指针 指针式C语言中的一个重要概念,也是C语言的一个重要特色. 指针可以 ...

  2. c语言数组与指针的基础知识,C语言学习笔记之数组与指针的关系

    首先,大家先需知道一个关于基类型的概念 基类型:组成一个新类型的基础类型 这句话是什么意思呢?举个例子: int a[3] = {1,2,3}; 上面是由三个int类型的数组成一个新的类型也就是数组, ...

  3. C语言学习笔记---fseek()函数和ftell()函数

    fseek()函数    fseek()函数简单的理解,功能就是用来设置打开文件中光标的位置.比如默认打开一个文件后,光标在文件的最开始位置,但是好多时候操作文件的时候,不一定都是从最开始位置操作的. ...

  4. 【C语言学习笔记】函数指针的定义和用法

    函数指针的定义和用法 先回顾下数组的储存方式,当你在程序中定义了一个数组,那么系统就会自动根据你的数组类型和数组长度申请一块储存空间给你.而且数组名储存的地址刚好就是申请的这块储存空间的首地址,这也是 ...

  5. 翁恺老师C语言学习笔记(十)指针_指针的使用

    指针的应用场景一 · 交换两个变量的值 void swap(int *pa, int *pb);//定义指针*pa和*pb int main(void) {int a = 5; int b = 6;s ...

  6. 翁恺老师C语言学习笔记(十)指针_指针变量就是记录地址的变量

    指针变量就是记录地址的变量 scanf函数 · 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量 · scanf("%d", &i) · ...

  7. C语言学习笔记--数组参数和指针参数

    1. 数组参数退化为指针的意义 (1)C 语言中只会以值拷贝的方式传递参数,当向函数传递数组时,将整个数组拷贝一份传入函数导致执行效率低下,C 语言以高效作是最初的设计目标,所以这种方法是不可取的. ...

  8. Go语言学习笔记(三)---指针,运算符及流程控制

    4.2.8 指针 基本介绍:1)基本数据类型,变量存的就是值,也叫值类型.2)获取变量的地址,用&.3)指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值.4)获取指针类型所指向的 ...

  9. C语言学习笔记-7.函数

    一.函数的使用 1.每个函数在使用之前必须先定义 例:void function();       //有分号 int main() {-} void add() {-}       //无分号 2. ...

最新文章

  1. 【数据结构】单调栈和单调队列 详解+例题剖析
  2. iOS 进阶 - RUNTIME 运行时
  3. 用女朋友动态图做微信二维码,小白都会
  4. x64位windows上程序开发的注意事项
  5. H3C BGP基本配置
  6. JAVA-初步认识-第十四章-线程间通信-多生产者多消费者问题-JDK1.5新特性解决办法-范例...
  7. 《价值投资 从看懂财务报表开始》 读书笔记
  8. 联想微型计算机改win76,联想ideacentre一体机改win7详细教程
  9. 同侪隐修录 (2016-12-25 23:10:21)转载▼
  10. mysql数据表添加列_如何将列添加到MySQL表
  11. ueditor编辑器抓取页面背景图片background-image或background
  12. 蚂蚁链开源跨链技术 加速大规模创新应用“涌现”
  13. [ 电子]STM32驱动28BYJ-48步进电机实现外网控制
  14. Docker-ce最新版在Ubuntu18.04上的安装、更新、卸载方法(存储库方式)
  15. 猎豹浏览器抢票专版 v5.0.8702 官方版
  16. activiti7实战教程(二)作图
  17. RS示波器软件,罗德与施瓦茨示波器上位机软件NS-Scope介绍
  18. js 判断浏览器的语言的方法
  19. 什么是可视化开发平台?拥有什么优势?
  20. 浏览器URL Scheme打开APP的那些坑

热门文章

  1. DDD 领域驱动设计-三个问题思考实体和值对象(续)
  2. tigerVNC的简单使用教程(CentOS 自带VNC包的远程桌面连接)
  3. js模拟3D场景效果
  4. 一、docker临时记录
  5. 学习有五个层次和境界
  6. webstrom 里面使用github
  7. 关于dependency的scope
  8. 【Java】javaWeb中的三大组件与八大监听器
  9. C语言判断两个数组是否有相同元素
  10. 数据库-表记录增删改