0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

周立功手把手教你学嵌入式编程:函数指针与指针函数的应用

AGk5_ZLG_zhiyua 来源:ZLG致远电子 作者:佚名 2017-08-29 11:36 次阅读

周立功教授数年之心血之作《程序设计与数据结构》以及面向AMetal框架与接口编程(上)。书本内容公开后,在电子行业掀起一片学习热潮。经周立功教授授权,本公众号特对《程序设计与数据结构》一书内容进行连载,愿共勉之。

第二章为程序设计技术,本文为2.1.1 函数指针和2.1.2指针函数。

>>>>2.1函数指针与指针函数

>>>2.1.1函数指针

变量的指针指向的是一块数据,指针指向不同的变量,则取到的是不同的数据。而经过编译后的函数都是一段代码,系统随即为相应的代码分配一段存储空间,而存储这段代码的起始地址(又称为入口地址)就是这个函数的指针,即跳转到某一个地址单元的代码处去执行。函数指针指向的是一段代码(即函数),指针指向不同的函数,则具有不同的行为。

因为函数名是一个常量地址,所以只要将函数的地址赋给函数指针即可调用相应的函数。如同数组名一样,我们用的是函数本身的名字,它会返回函数的地址。当一个函数名出现在表达式中时,编译器就会将其转换为一个指针,即类似于数组变量名的行为,隐式地取出了它的地址。即函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋值给指向函数的指针。既然函数指针的值可以改变,那么就可以使用同一个函数指针指向不同的函数。如果有以下定义:

int (*pf)(int); // pf函数指针的类型是什么?

C语言的发明者K&R是这样解释的,“因为*是前置运算符,它的优先级低于(),为了让连接正确地进行,有必要加上括号。”这未免有些牵强附会了,解释来解释去反而将人搞晕了。因为声明中的*、()、[]都不是运算符,而运算符的优先顺序在语法规则中是在其它地方定义的。其详解如下:

int(*pf)(int a); // pf是指向…的指针

int(*pf)(int a); // pf是指向…的函数(参数为int)的指针

int (*pf)(int a); // pf是指向返回int的函数(参数为int)的指针

即pf是一个指向返回int的函数的指针,它所指向的函数接受一个int类型的参数。 “int (*)(int)”类型名被解释为指向返回int函数(参数为int)的指针类型。如果在该定义前添加typedef,比如:

typedef int (*pf)(int a);

未添加typedef前,pf是一个函数指针变量;而添加typedef后,pf就变成了函数指针类型,习惯的写法是类型名pf大写为PF。比如:

typedef int (*PF)(int a);

与其它类型的声明不同,函数指针的声明要求使用typedef关键字。另外,函数指针的声明与函数原型的唯一不同是函数名用(*PF)代替了,“*”在此处表示“指向类型名为PF的函数”。显然,有了PF类型即可定义函数指针变量pf1、pf2。比如:

PF pf1, pf2;

虽然此声明等价于:

int (*pf1)(int a);

int (*pf2)(int a);

但这种写法更难理解。既然函数指针变量是一个变量,那么它的值就是可以改变的,因此可以使用同一个函数指针变量指向不同的函数。使用函数指针必须完成以下工作:

●获取函数的地址,比如,pf = add,pf = sub

●声明一个函数指针,比如,“int (*pf)(int, int);”;

●使用函数指针来调用函数,比如,pf(5, 8),(*pf)(5, 8)。为何pf与(*pf)等价呢?

●一种说法是,由于pf是函数指针,假设pf指向add()函数,则*pf就是函数add,因此使用(*pf)()调用函数。虽然这种格式不好看,但它给出了强有力的提示——代码正在使用函数指针调用函数。

●另一种说法是,由于函数名是指向函数的指针,那么指向函数的指针的行为应该与函数名相似,因此使用pf()调用函数。因为这种调用方式既简单又优雅,所以人们更愿意选择——说明人类追随美好感受的内心是无法抗拒的。

虽然它们在逻辑上互相冲突,但不同的流派有不同的观点,且容忍逻辑上无法自圆其说的观点,正是人类思维活动的特点。

在一个袖珍计算器中,经常需要用到加减乘除开方等各种各样的计算,虽然其调用方法都是一样,但在运行中需要根据具体情况决定选择调用支持某一算法的函数。如果使用如图 2.1(a)所示的直接调用方式,则势必形成了依赖关系结构,策略会受到细节改变的影响,当使用如图 2.1(b)所示的函数指针接口倒置(或反转)了这种依赖关系结构时,则使得细节和策略都依赖于函数指针接口,断开了不想要的直接依赖。

当将直接访问抽象成函数指针倒置(或反转)了依赖的关系时,高层模块不再依赖于低层模块。高层模块依赖于抽象,即一个函数指针形式的接口,同时细节也依赖于抽象,pf()实现了这个接口,即两者都依赖于函数指针接口。在C语言中,通常用函数指针来实现DIP(倒置依赖关系),断开不想要的直接依赖。既可以通过函数指针调用服务(被调用代码),服务也可以通过函数指针回调用户函数。都是一样,但在运行中需要根据具体情况决定选择调用支持某一算法的函数。如果使用如图 2.1(a)所示的直接调用方式,则势必形成了依赖关系结构,策略会受到细节改变的影响,当使用如图 2.1(b)所示的函数指针接口倒置(或反转)了这种依赖关系结构时,则使得细节和策略都依赖于函数指针接口,断开了不想要的直接依赖。

图 2.1 使用函数指针倒置依赖关系

函数指针是程序员经常忽视的一个强大的语言能力,不仅使代码更灵活可测,而且对消除重复条件逻辑有很大的帮助,同时还可以使调用者免于在编译时或链接时依赖于某个特定的函数,其极大地好处是减少了C语言模块之间的耦合。但函数指针的使用是有条件的,如果主调函数与被调函数之间的调用关系永远不会发生改变,则采用直接调用方式是最简单的,在这种情况下,模块之间耦合是合理的,不仅代码简单直截了当,而且开销也是最小的。如果需要在运行时使用一个或多个函数指针调用某一函数,则使用函数指针是最佳的选择,通常将其称之为动态接口,其范例程序详见程序清单 2.1。

程序清单2.1通过函数指针调用函数范例程序(1)

1#include

2 int add(int a, int b)

3 {

4 printf("addition function ");

5 return a + b;

6 }

7

8 int sub(int a, int b)

9 {

10 printf("subtration function ");

11 return a - b;

12 }

13

14 int main(void)

15 {

16 int (*pf)(int, int);

17

18 pf = add;

19 printf("addition result:%d ", pf(5, 8));

20 pf = sub;

21 printf("subtration result:%d ", pf(8, 5));

22 return 0;

23 }

由于任何数据类型的指针都可以给void指针变量赋值,且函数指针的本质就是一个地址,因此可以利用这一特性,将pf定义为一个void *类型指针,那么任何指针都可以赋值给void *类型指针变量。其调用方式如下:

void * pf = add;

printf("addition result:%d ", ((int (*)(int, int)) pf)(5, 8));

在函数指针的使用过程中,指针的值表示程序将要跳转的地址,指针的类型表示程序的调用方式。在使用函数指针调用函数时,务必保证调用的函数类型与指向的函数类型完全相同,所以必须将void *类型转换为((int (*)(int, int)) pf)来使用,其类型为“int (*)(int, int)”。

>>>2.1.2指针函数

实际上,指针变量的用途非常广泛,指针不仅可以作为函数的参数,而且指针还可以作为函数的返回值。当函数的返回值是指针时,则这个函数就是指针函数。当给定指向两个整数的指针时,如程序清单 2.2所示的函数返回指向两个整数中较大数的指针。当调用max时,用指向两个int类型变量的指针作为参数,且将结果存储在一个指针变量中,其中,max函数返回的指针是作为实参传入的两个指针的一个。

程序清单2.2求最大值函数(指针作为函数的返回值)

1 #include

2 int *max(int *p1, int *p2)

3 {

4 if(*p1 > *p2)

5 return p1;

6 else

7 return p2;

8 }

9

10 int main(int argc, char *argv[])

11 {

12 int *p, a, b;

13 a = 1; b = 2;

14 p = max(&a, &b);

15 printf("%d ", *p);

16 return 0;

17 }

当然,函数也可以返回字符串,它返回的实际是字符串的地址,但一定要注意如何返回合法的地址。既可以返回是静态的字符串地址,也可以在堆上分配字符串的内存,然后返回其地址。注意,不要返回局部字符串的地址,因为内存有可能被别的栈帧覆写。

下面我们再来看一看,指针函数与函数指针变量有什么区别?如果有以下定义:

int *pf(int *, int); // int *(int *, int)类型

int (*pf)(int, int); // int (*)(int, int)类型

虽然两者之间只差一个括号,但表示的意义却截然不同。函数指针变量的本质是一个指针变量,其指向的是一个函数;指针函数的本质是一个函数,即将pf声明为一个函数,它接受2个参数,其中一个是int *,另一个是int,其返回值是一个int类型的指针。

在指针函数中,还有一类这样的函数,其返回值是指向函数的指针。对于初学者,别说写出这样的函数声明,就是看到这样的写法也是一头雾水。比如,下面这样的语句:

int (*ff (int))(int, int); // ff是一个函数

int (* ff (int))(int, int); // ff是一个指针函数,其返回值是指针

int(* ff (int))(int, int); //指针指向的是一个函数

这种写法确实让人非常难懂,以至于一些初学者产生误解,认为写出别人看不懂的代码才能显示自己水平高。而事实上恰好相反,能否写出通俗易懂的代码是衡量程序员是否优秀的标准。当使用typedef后,则PF就成为了一个函数指针类型。即:

typedef int (*PF)(int, int);

有了这个类型,那么上述函数的声明就变得简单多了。即:

PF ff(int);

下面将以程序清单 2.3为例,说明用函数指针作为函数返回值的用法。当用户分别输入d、x和p时,求数组的最大值、最小值和平均值。

程序清单2.3求最值与平均值范例程序

1 #include

2 #include

3 double getMin(double *dbData, int iSize) //求最小值

4 {

5 double dbMin;

6

7 assert((dbData != NULL) && (iSize > 0));

8 dbMin = dbData[0];

9 for (int i = 1; i < iSize; i++){ 

10 if (dbMin > dbData[i]){

11 dbMin = dbData[i];

12 }

13 }

14 return dbMin;

15 }

16

17 double getMax(double *dbData, int iSize) //求最大值

18 {

19 double dbMax;

20

21 assert((dbData != NULL) && (iSize > 0));

22 dbMax = dbData[0];

23 for (int i = 1; i < iSize; i++){

24 if (dbMax < dbData[i]){ 

25 dbMax = dbData[i];

26 }

27 }

28 return dbMax;

29 }

30

31 double getAverage(double *dbData, int iSize) //求平均值

32 {

33 double dbSum = 0;

34

35 assert((dbData != NULL) && (iSize > 0));

36 for (int i = 0; i < iSize; i++){

37 dbSum += dbData[i];

38 }

39 return dbSum/iSize;

40 }

41

42 double unKnown(double *dbData, int iSize) //未知算法

43 {

44 return 0;

45 }

46

47 typede double (*PF)(double *dbData, int iSize); //定义函数指针类型

48 PF getOperation(char c) //根据字符得到操作类型,返回函数指针

49 {

50 switch (c){

51 case 'd':

52 return getMax;

53 case 'x':

54 return getMin;

55 case 'p':

56 return getAverage;

57 default:

58 return unKnown;

59 }

60 }

61

62 int main(void)

63 {

64 double dbData[] = {3.1415926, 1.4142, -0.5, 999, -313, 365};

65 int iSize = sizeof(dbData) / sizeof(dbData[0]);

66 char c;

67

68 printf("Please input the Operation : ");

69 c = getchar();

70 PF pf = getOperation(c);

71 printf("result is %lf ", pf(dbData, iSize));

72 return 0;

73 }

前4个函数分别实现了求最大值、最小值、平均值和未知算法,getOperation()根据输入字符得到的返回值是以函数指针的形式返回的,从pf(dbData, iSize)可以看出是通过这个指针调用函数的。注意,指针函数可以返回新的内存地址、全局变量的地址和静态变量的地址,但不能返回局部变量的地址,因为函数结束后,在函数内部的声明的局部变量的声明周期已经结束,内存将自动放弃。显然,在主调函数中访问这个指针所指向的数据,将会产生不可预料的结果。

想学更多嵌入式课程,请扫描下图二维码,马上学习!

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 指针
    +关注

    关注

    1

    文章

    480

    浏览量

    70544
  • 数据结构
    +关注

    关注

    3

    文章

    573

    浏览量

    40118
  • 程序设计
    +关注

    关注

    3

    文章

    261

    浏览量

    30389
  • 函数指针
    +关注

    关注

    2

    文章

    56

    浏览量

    3778
  • 指针函数
    +关注

    关注

    0

    文章

    10

    浏览量

    2751
  • ametal
    +关注

    关注

    2

    文章

    24

    浏览量

    11398

原文标题:周立功:函数指针与指针函数的应用

文章出处:【微信号:ZLG_zhiyuan,微信公众号:ZLG致远电子】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    手把手教你VC

    手把手教你VC
    发表于 08-20 15:02

    手把手教你CPLD/FPGA与单片机联合设计》-兴华

    。此外,为了帮助读者掌握单片机与CPLD/FPGA的联合设计,还介绍了51单片机的基本知识及单片机c语言编程的基础知识,并通过实例设计进行详解。《手把手教你CPLD/FPGA与单片机
    发表于 12-29 17:10

    手把手教你CPLD/FPGA与单片机联合设计》-兴华

    。此外,为了帮助读者掌握单片机与CPLD/FPGA的联合设计,还介绍了51单片机的基本知识及单片机c语言编程的基础知识,并通过实例设计进行详解。《手把手教你CPLD/FPGA与单片机
    发表于 01-06 17:21

    手把手教你FPGA 编程规范篇

    手把手教你FPGA 编程规范篇
    发表于 02-02 11:32

    手把手教你STM32单片机

    整理资料来源【正点原子】 手把手教你STM32单片机教学视频 嵌入式 之 F103-基于新战舰V]NANO_STM32F103开发指南-HAL库版本_V1.0.pdf其它网络操作等词
    发表于 08-19 07:08

    手把手教你DSP28335_张卿杰

    手把手教你DSP28335张卿杰百度云分享手把手教你DSP28335张卿杰百度云分享
    发表于 01-11 11:45 177次下载

    指针作为函数参数

    手把手教你C语言难点编程,很好的C语言编程基础资料,欢迎下载学习。
    发表于 03-25 15:28 2次下载

    手把手教你电子书制作

    手把手教你电子书制作,可以自己DIY电子书
    发表于 09-13 11:26 0次下载

    c语言函数指针定义,指针函数函数指针的区别

     往往,我们一提到指针函数函数指针的时候,就有很多人弄不懂。下面就由小编详细为大家介绍C语言中函数指针
    发表于 11-16 15:18 3624次阅读

    手把手教你如何开始DSP编程

    手把手教你如何开始DSP编程
    发表于 04-09 11:54 12次下载
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b>如何开始DSP<b class='flag-5'>编程</b>

    手把手教你LabVIEW视觉设计

    手把手教你LabVIEW视觉设计手把手教你LabVIEW视觉设计
    发表于 03-06 01:41 3128次阅读

    理解函数指针函数指针数组、函数指针数组的指针

    理解函数指针函数指针数组、函数指针数组的指针
    的头像 发表于 06-29 15:38 1.5w次阅读
    理解<b class='flag-5'>函数</b><b class='flag-5'>指针</b>、<b class='flag-5'>函数</b><b class='flag-5'>指针</b>数组、<b class='flag-5'>函数</b><b class='flag-5'>指针</b>数组的<b class='flag-5'>指针</b>

    嵌入式秘术】手把手教你如何劫持RTOS(下)

    在《【嵌入式秘术】手把手教你如何劫持RTOS(上)》中,我们做了简单的热身——介绍了一种在你拥有某一个库的源代码或者.lib文件时,如何...
    发表于 01-25 18:51 7次下载
    【<b class='flag-5'>嵌入式</b>秘术】<b class='flag-5'>手把手</b><b class='flag-5'>教你</b>如何劫持RTOS(下)

    手把手教你FPGA仿真

    电子发烧友网站提供《手把手教你FPGA仿真.pdf》资料免费下载
    发表于 10-19 09:17 2次下载
    <b class='flag-5'>手把手</b><b class='flag-5'>教你</b><b class='flag-5'>学</b>FPGA仿真

    面试常考+1:函数指针指针函数、数组指针指针数组

    嵌入式开发领域,函数指针指针函数、数组指针指针
    的头像 发表于 08-10 08:11 803次阅读
    面试常考+1:<b class='flag-5'>函数</b><b class='flag-5'>指针</b>与<b class='flag-5'>指针</b><b class='flag-5'>函数</b>、数组<b class='flag-5'>指针</b>与<b class='flag-5'>指针</b>数组