上一篇<一起学习C语言:函数(三)> 中,我们了解了变量的储存类别与声明方式,以及函数的递归调用原理。本章节,我们分析函数的指针调用,以及函数指针作函数参数使用的场景。

章节预览:

8. 函数的指针调用
8.1 函数指针作函数参数使用
9. 本章总结
目录预览

章节内容:

8. 函数的指针调用

前面小节中我们了解到全局变量和静态变量在程序编译期间确定逻辑内存地址和内存空间大小,并在程序执行期间分配实际内存地址和对应的内存空间大小。当然,函数也是采用类似的形式,接下来分析函数的加载和执行过程:

在程序编译的过程中,编译器为函数标记一段地址(逻辑地址),这段地址的首地址是函数首地址,并且记录着函数的执行过程以及占用的内存空间大小。

在程序执行的过程中,首先为所有用到的函数分配实际地址(只为动态加载的函数分配实际地址,而静态加载的函数在链接时已经确定实际地址(程序执行时分配这个固定地址)),并在之后调用这个函数时把实际地址压入栈中(在栈中分配函数执行期间需要的内存空间),当函数执行完成后(回收函数执行期间分配的内存)弹出栈。其中,函数的实际地址一直保存到进程退出前释放。

现在我们了解了函数同样具有地址的概念,接下来通过一个示例了解函数调用的过程。

【例8.9】 通过函数地址执行函数:

int func(int a) {
          return a;
      }

int main() {
          void *pFunc = func;
          int res = ((int (*)(int))pFunc)(1);
          printf(“main res:%d\n”, res);
          return 0;
      }

示例结果

main res:1

程序分析

这个示例中,pFunc首先得到func函数的偏移地址(实际地址),然后通过偏移地址在栈中执行func函数并返回int返回值,最后通过printf输出返回值信息。

C语言中,函数地址可以通过长整形保存,如 long pFunc = (long)func;,但不建议用整形(int)保存。因为在32位编译器中地址占4个字节,在64位编译器中地址占8个字节,而整形在32或64位编译器中都占用4字节,并不能满足64位编译器中的地址存储条件。

另外,程序中需要保存某个函数地址时,可以通过函数指针来储存。

函数指针定义形式

返回类型 (*函数名称)(参数列表);

函数指针定义举例

int (*Func)(int);

【例8.10】 通过函数指针执行函数:

int (*Func)(int);

int func(int a) {
              return a;
          }

int main() {
              Func = func; //为函数指针赋实际调用函数的地址
              int res = Func(1); //函数指针执行
              printf(“main res:%d\n”, res);
              return 0;
          }

示例结果

main res:1

使用函数指针需要注意几点

1. 函数指针名称必须写在小括号内,如(*Func),*写在函数名左侧;
          2. 函数指针只是以抽象形式存在,参数为实际调用函数名称;
          3. 函数指针传入的参数应与函数指针原型完全匹配。

8.1 函数指针作函数参数使用

函数指针不仅可以调用函数,也可以作为函数参数使用。比如实现一个算法函数接口,在不清楚函数内应该用哪种实现方式的情况下,可以把函数指针作为参数传入,在传入的实际函数内编写实现方式。

【例8.10】 函数指针作函数参数:

int FUNC(int i, int j, int (*multiplication)(int i, int j)) {
          return multiplication(i, j);
      }

int multiplication(int i, int j)
      {
          if (9 >= i) {
              if (9 >= j) {
                  printf("%d*%d=%2d “, i, j, i * j);
                  multiplication(i, ++j);
              }
              else {
                  ++i;
                  j = i;
                  printf(”\n");
                  multiplication(i, j);
              }
          }

return 0;
      }

int main() {
          int res = FUNC(1, 1, multiplication);
          printf(“main res:%d\n”, res);
      }

示例结果

接下来,我们把实现函数修改为乘法运算:

int FUNC(int i, int j, int (*multiplication)(int i, int j)) {
              return multiplication(i, j);
          }

int mul(int i, int j) {
              return i * j;
          }

int main() {
              int res = FUNC(2, 3, mul);
              printf(“main res:%d\n”, res);
          }

示例结果

main res:6

当然,函数指针的用处不仅如此,在调用系统函数或第三方库函数时,可以通过函数指针提供函数实现。比如linux内核中不同厂家提供的驱动函数实现方式基本不相同,如果把所有厂家的驱动代码都植入linux内核会让内核体积变得异常庞大,而内核方面只需把相关函数原型以抽象形式提供,不同厂家只需实现自己的驱动函数即可,这样便可以减少内核体积。

9. 本章总结

本章节,我们了解了程序编译的过程以及程序执行时的不同内存应用场景。通过上述示例可以了解到,不同存储类别的变量生命周期或作用域也会不同,函数也可以像指针变量那样调用。在日常编程中,栈内存溢出时基本可以通过代码分析出原因。

目录预览

<一起学习C语言:C语言发展历程以及定制学习计划>
<一起学习C语言:初步进入编程世界(一)>
<一起学习C语言:初步进入编程世界(二)>
<一起学习C语言:初步进入编程世界(三)>
<一起学习C语言:C语言数据类型(一)>
<一起学习C语言:C语言数据类型(二)>
<一起学习C语言:C语言数据类型(三)>
<一起学习C语言:C语言基本语法(一)>
<一起学习C语言:C语言基本语法(二)>
<一起学习C语言:C语言基本语法(三)>
<一起学习C语言:C语言基本语法(四)>
<一起学习C语言:C语言基本语法(五)>
<一起学习C语言:C语言循环结构(一)>
<一起学习C语言:C语言循环结构(二)>
<一起学习C语言:C语言循环结构(三)>
<一起学习C语言:数组(一)>
<一起学习C语言:数组(二)>
<一起学习C语言:数组(三)>
<一起学习C语言:初谈指针(一)>
<一起学习C语言:初谈指针(二)>
<一起学习C语言:初谈指针(三)>
<一起学习C语言:函数(一)>
<一起学习C语言:函数(二)>
<一起学习C语言:函数(三)>

一起学习C语言:函数(四)相关推荐

  1. 前辈学习C语言的四种方法,实际上不管学什么语言,都行之有效!

    如果新手要学习编程,一些学长学姐都会建议从Python.PHP.Java开始学. 不过,作为工作多年的一名程序员,我还是建议你从C语言开始,为什么这么说,我相信你以后会慢慢的明白. 那么,如何学习C语 ...

  2. c语言的boolean_0基础学习C语言第四章:三种基本结构

    1.C语言的三种基本结构 顺序结构:从头到尾一句接着一句的执行下来,直到执行完最后一句: 选择结构:到某个节点后,会根据一次判断的结果来决定之后向哪一个分支方向执行: 循环结构:循环结构有一个循环体, ...

  3. HDL4SE:软件工程师学习Verilog语言(十四)

    14 RISC-V CPU初探 前面我们介绍了verilog语言的基本语法特征,并讨论了数字电路设计中常用的状态机和流水线结构,然后我们借鉴SystemC的做法,引入了HDL4SE建模语言,以及相应的 ...

  4. vector 赋值_从零开始学习R语言(一)——数据结构之“向量”(Vector)

    本文首发于知乎专栏:https://zhuanlan.zhihu.com/p/59688569 也同步更新于我的个人博客:https://www.cnblogs.com/nickwu/p/125370 ...

  5. 你可以这样学习C语言

    声明:我已加入"维权骑士"(维权骑士_免费版权监测/版权保护/版权分发)的版权保护计划. 我在今日头条上开了一个专栏,专栏名字是"你可以这样学习C语言".C语言 ...

  6. 【从零开始学习Go语言】三.属于Go的Hello World

    [从零开始学习Go语言]三.属于Go的Hello World 一.安装Visual Studio Code 1.1 安装Go插件 二.创建Go项目文件 2.1 创建Go项目文件夹 2.2 打开创建的项 ...

  7. HDL4SE:软件工程师学习Verilog语言(六)

    6 表达式与赋值 我们终于可以继续学习了,也是没有办法,其实工作的80%的时间都是在忙杂事,就像打游戏一样,其实大部分时间都在打小怪,清理现场,真正打终极BOSS的时间是很少的,但是不清小怪,打BOS ...

  8. HDL4SE:软件工程师学习Verilog语言(十一)

    11 流水线 前面一节介绍了状态机的概念.状态机用于描述事务处理的一个程序性流程,可以组成顺序,分支,循环的事务处理流程.这些概念本来在verilog中的行为级描述中是有的,但是由于不是RTL描述,因 ...

  9. c语言子函数返回值,C语言函数说明与返回值

    在学习C语言函数以前,我们需要了解什么是模块化程序设计方法. 人们在求解一个复杂问题时,通常采用的是逐步分解.分而治之的方法,也就是把一个大问题分解成若干个比较容易求解的小问题,然后分别求解.程序员在 ...

最新文章

  1. 我的WEB之路(一)-2.JAVA学习路线
  2. iis6中FTP配置的技巧和细节
  3. linux系统各文件夹的作用,linux系统文件夹的作用 good
  4. C++若不想使用编译器自动生成的函数,就该明确拒绝
  5. 当 IDENTITY_INSERT 设置为 OFF 时,不能为表中的标识列插入显式值
  6. Activity的四种加载模式(转载)
  7. formdata 嵌套_解决form嵌套
  8. 并发编程-基础概念介绍
  9. 一个双向转换火星文的玩具
  10. 未来计算机的将朝着,未来计算机将朝着哪几个方向发展?
  11. DataX二次开发——(6)kafkareader、kafkawriter的开发
  12. 关于德鲁伊数据源配置的记录
  13. MySQL 索引介绍!
  14. PyTorch单机多卡分布式训练教程及代码示例
  15. 知识普及篇——动手做foc无刷电机电子调速器
  16. 876. 链表的中间结点 (Python 实现)
  17. orb-slam3安装编译运行。opencv3.2 undefined reference to `cblas_zgemm vgg_generated_48.i ippicv_linux_20151
  18. themeleaf渲染富文本
  19. 函数调用function与function()的区别
  20. 瞬变电磁检测原理和特点

热门文章

  1. 二月,劝 Java 工程师不要跳槽!
  2. 为什么阿里巴巴Java开发手册中强制要求接口返回值不允许使用枚举?
  3. 秒懂了微服务架构,看这本书就够了!
  4. 中台实践:新汽车行业的业务、技术和平台转型
  5. JeecgBoot 2.4 微服务正式版发布,基于SpringBoot的低代码平台
  6. JEECG 社区开源项目下载(总览)
  7. zookeeper安装启动报错引发的版本取用思考
  8. MNIST手写数字识别【Matlab神经网络工具箱】
  9. introduce of servlet and filter
  10. 螺旋矩阵 java实现(待消化)