C语言-函数的调用

目录:

一、函数名作为函数的输入参数

二、回调函数

1、回调函数的引入

2、回调与普通函数的调用

3、回调函数的作用

4、回调函数的程序编写


一、函数名作为函数的输入参数

函数参数传递分为两种,一种是值传递,一种是地址传递。一般我们传递时用的是地址传递。因为,若是采用值传递的话,比如我们传递一个数组 double a[100],则在调用函数的时候。编译器会把这整个数组复制到函数中,这样使用的空间是100*sizeof(double)=800。若是我们只传递数组名 a 这个地址的话,那么复制进去的空间只有 64/8=8 这么多(假设计算机64位)。这样比较下来,就有了100倍的差距,是不是很吓人。所以,不管是函数作为参数,还是数组,结构体什么的,我们一般都用地址传递,而不用值传递。

1)函数地址如何传递

先说一次传递一个函数: 我们先定义一个函数 

double add(double x, double y)
{
   return x + y;
}

然后接着建立一个函数指针

double (*pf)(double,double) = add;
//这里(*pf)的括号不能省,即把add的地址给了指针pf

我们现在有一个函数:

double calculate(double x1, double y1, double(*f)(double, double))  
{//函数调用里面传递 函数指针数组 的方法
   cout << "add:" << (*f)(x1, y1) << endl;
   // cout相当于printf();是输出函数,endl程序的结束符
   return  1;
}

然后我们来进行值传递

  int x = 2; y = 1;
  calculate(x, y, pf);

最后可以得到输出的结果是 2+1=3。

这是最基本的,下面讲我要说的重点,就是一次传递多个函数进去。想传递多个函数进去,我们要建立一个函数数组 。先定义两个函数:

double add(double x, double y)
{
  return x + y;
}
double add2(double x, double y)
{
  return x - y;
}

然后建立函数数组并赋值  double (*pf[2])(double,double) = {add, add2}; 

接着传递给上面定义的calculate函数。调用方式为:calculate(x, y, pf);

calculate函数的接收方式应为:

double calculate(double x1, double y1, double(**f)(double, double))
//传递的pf是一个数组的数组名且本身也是一个指针

即为二重指针或者

double calculate(double x1, double y1, double(*f[])(double, double))

最后给出完整的代码

#include "iostream"

using namespace std;
double add(double, double);
double add2(double x, double y);
double calculate(double x1, double y1, double(**f)(double, double))
{//函数调用里面传递 函数指针数组 的方法
  cout << "add:" << (*f[0])(x1, y1) << endl;
  cout << "add2:" << (*f[1])(x1, y1) << endl;
  return  1;
}

int main(void)
{
  int x, y;
  double (*pf[2])(double,double) = {add, add2}; 
  x = 2; y = 1;
  calculate(x, y, pf);

  system("pause()");
  return 0;
}

double add(double x, double y)
{
  return x + y;
}

double add2(double x, double y)
{
  return x - y;
}

在函数void function()中,需要将另外一个函数double input()的函数名作为输入参数。
定义函数指针:
typedef double (*P)(
int);
Note:红色字体部分需要注意,函数input()所有的输入参数类型都需要包含在内。

例如 double input(double u[5], int num, double x),则在定义时写作 typedef double (*P)(double*,int,double)。

此时函数作为一种类型,可以直接被其它函数调用。

调用格式:函数声明中定义 void function(P input),调用function(input)即可。例子:

#include "stdio.h"
#include "string.h"

typedef int (*P)(int);
void function(P input);
int input(int a);

int main(void)
{
    function(input);
    return 0;
}

void function(P input)
{
    printf("ok");
}

int input(int a)
{
    return a+5;
}

typedef int (*p) (int *p);的说明:

定义一个函数指针类型(注意是类型)p,指向一个函数,该函数接受一个参数int *型,返回int。也就是说,有这样的定义:

typedef int (*p) (int *p);
int foot(int *); //声明,foot在其它地方定义

可以这样使用:p pf = foot; //&foot也可以,一个意思。

二、回调函数

1、回调函数的引入

应用程序需要采集硬件层的数据,比如串口接收数据、按键采集、ADC值采集。这种硬件层的数据怎么通知应用层来拿,或者怎么主动给它?

我们以往最简单粗暴的方式是不是就是用一个全局变量,比方说硬件层串口接收到数据来了,那么我们把数据丢到数组里,然后把接收完成全局变量标志位置1。比方说全局变量名为RcvFlag,然后应用层程序会轮询判断RcvFlag==1?是的话就开始把数组里的数据取出来解析。

很多人就会说了,你看我用这种方法照样能实现功能啊,为什么还要学习别的架构。这样做当然可以实现功能,但是会存在移植性很差的问题。

2、回调与普通函数的调用

1)普通函数的调用

调用程序发出对普通函数的调用后,程序执行立即转向被调用函数执行,直到被调用函数执行完毕后,再返回调用程序继续执行。从发出调用的程序的角度看,这个过程为“调用-->等待被调用函数执行完毕-->继续执行”。

2)回调函数的调用

调用程序发出对回调函数的调用后,不等函数执行完毕,立即返回并继续执行。这样调用程序和被调用函数同时在执行。当被调函数执行完毕后,被调函数会反过来调用某个事先指定函数,以通知调用程序:函数调用结束。这个过程称为回调(Callback),这正是回调函数名称的由来。
带参数的回调函数举例:
#include "stdio.h"

int call_back1(int value1)  // 被调用的函数1
{
    printf("This is call_back1,value = %d\n",value1);
    return 0;
}

int call_back2(int value2)  // 被调用的函数2
{
    printf("This is call_back2,value = %d\n",value2)
    return 0
}

int handle_call_back(int value,int (*call_back)(int))  // 回调函数
{
    call_back(value);
    return 0;
}

int main(void)  //主函数
{
    int a = 10;
    int b = 20;
    
    handle_call_back(a,call_back1); // 函数作为参数被调用
    handle_call_back(b,call_back2); // 函数作为参数被调用
    return 0;
}

3、回调函数的作用

那么在讲回调函数之前呢,对于函数调用呢我一般分为2种类型:

1)输出型

不知道大家有没有用过C语言自带的一些库函数,比如说sizeof()获取数据长度的关键词,memcpy()是内存拷贝函数,我们调用这个函数之后呢就能完成相应的功能。

还有我们基于单片机的一些程序函数,比方说控制LED点亮熄灭、继电器吸合断开、LCD驱动等。

那么这些呢,我一般称为输出型的函数。输出型函数我们是主导的角色,我们知道什么时候该调用它。

2)输入型

输入型也称为响应式的函数。比方说接收串口的数据,不知道什么数据什么时候来。
再比方说按键检测的函数,我们不知道什么时候会按下按键,那么这些就要定义成响应式函数来实现,而响应式函数就可以用回调函数来实现。
所以通过这两个种类型的分析我们就可以知道,回调函数基本是用在输入型的处理中。
比方说,串口数据接收、按键检测、ADC值采集,ADC值也是输入到单片机里的,单片机是处于从机角色。
那么它们输入的时间节点都是未知的,这些就能够用回调函数来处理。
回调函数还有一个作用就是为了封装代码。
比如说做芯片或者模组的厂家,我们拿典型的STM32来举例,像外部中断、定时器、串口等中断函数都是属于回调函数,这种函数的目的是把采集到的数据传递给用户,或者说应用层
所以回调函数的核心作用:
(1)把数据从一个.c文件传递到另一个.c文件,而不用全局变量共享数据这么LOW的方法。
(2)对于这种数据传递方式,回调函数更利于代码的封装。

4、回调函数的程序编写

前面说了很多概念性的东西,可能大家也比较难理解,回调函数最终呢是靠函数指针来实现的。
那么我这里通过一些模拟按键的例子来演示下怎么回通过调函数来处理它们。下面是我们用C-Free模拟,参考例程搜索我的百度网盘“CallBack”或移步:CallBack回调函数

从模块化编程的思想来看,整个工程分为2个部分,应用层main.c文件,硬件层key.c和key.h文件。
int main(int argc, char *argv[])
{
  KeyInit();
  KeyScanCBSRegister(KeyScanHandle);
  KeyPoll();
  return 0;
}
KeyInit();   //key.c文件的按键初始化函数
KeyScanCBSRegister(KeyScanHandle);   //key.c的函数指针注册函数
这个函数可能大家会有点蒙,想理解这个回调函数注册函数,要先从硬件层(key.h)头文件的函数指针定义说起,具体看下图。

这里自定义了一个函数指针类型,带两个形参。然后我们在key.c这个文件里定义了一个函数指针变量。

重点来了,我们就是通过这个函数指针,指向应用层的函数地址(函数名)。具体怎么实现指向呢?就是通过函数指针注册函数。

这个函数是在main函数里调用,使用这种注册函数的方式注册灵活性也很高,你想要在哪个.c文件使用按键功能就在哪里调用。

这里要注意,main.c这个文件要定义一个函数来接收硬件层(key.c)过来的数据。这里定义也不是乱定义的,一定要和那个自定义函数指针类型返回值、形参一致。

然后把这个函数名字直接复制给KeyScanCBSRegister函数的形参就可以了。

这样调用后,我们key.c文件的pKeyScanCBS这个指针其实就是指向的KeyScanHandle函数。

也就是说执行pKeyScanCBS的时候,就是执行KeyScanHandle函数。那具体检测按键的功能就是KeyPoll函数,这个在main函数里调用。

当检测到键盘有输入以后,最终会调用pKeyScanCBS。最终执行的是main.c文件的KeyScanHandle函数。所以,我们来看下输出结果。

下面我再给大家捋一捋编写和使用回调函数的流程:

①自定义函数指针,形参作为硬件层要传到应用层的数据。

②硬件层定义一个函数指针和函数指针注册函数。

③应用层定义一个函数,返回值和形参都要和函数指针一致。

④应用层调用函数指针注册函数,把定义好的函数名称作为形参传入。


青春时代是一个短暂的美梦,当你醒来时,它早已消失得无影无踪。觉得不错,动动发财的小手点个赞哦!

  • 15
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱上电路设计

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值