C语言中函数指针和回调函数的详解「建议收藏」

C语言中函数指针和回调函数的详解「建议收藏」函数指针:指向函数的指针变量。因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数…

大家好,又见面了,我是你们的朋友全栈君。

函数指针:指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。

我们首先来看一个函数指针的例子:

#include <stdio.h>
#include <stdlib.h>
void (*pfun)(int data);
void myfun(int data)
{
	printf("get data:%d\n",data);
}
int main(int argc,char *argv[])
{
	pfun = myfun;
	(*pfun)(100);
	return 0;
}
从这个例子可以看到,我们首先定义了一个函数指针pfun ,这个函数指针的返回值为void型,然后我们给函数指针赋值,赋值为myfun,也就是myfun函数的首地址,在C99中myfun函数名就是myfun函数的首地址,此时pfun获得了myfun的地址,pfun的地址等于myfun的地址,所以最终调用pfun();也就相当于调用了myfun();

第二种用法:typedef 原变量类型 别名
也可以用typedef来定义一个指针函数这样使在大型代码中更加简洁
#include <stdio.h>
#include <stdlib.h>
typedef void (*pfun)(int data);
/*typedef的功能是定义新的类型。第一句就是定义了一种pfun的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回void类型。*/
void myfun(int data)
{
	printf("get data:%d\n",data);
}
int main(int argc,char *argv[])
{
	pfun p= myfun;      //函数指针指向执行函数的地址
	p(100);
	return 0;
}
这里面的pfun代表的是函数的类型,通过pfun来代表void (*)(int)函数类型即pfun是指针函数的别名,pfun p相当于定义了一个
void (*p)(int)函数指针。p = myfun可以理解为将函数指针p指向myfun函数的地址,p(100);相当于执行myfun(100);

第三种用结构体函数指针的方法
#include <stdio.h>
#include <stdlib.h>
typedef struct gfun{
	void (*pfun)(int);	
}gfun;
void myfun(int data)
{
	printf("get data:%d\n",data);
}
int main(int argc,char *argv[])
{
	gfun gcode={
		.pfun = myfun,   //将函数指针指向要调用函数的地址
	};
	gcode.pfun(100);
	return 0;
} 

这三种方法运行的结果一样
在这里插入图片描述
回调函数:通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
下面是一个回调函数的例子:

#include <stdio.h>
#include <stdlib.h>
typedef struct gfun{
	int (*pfun)(int);	
}gfun;

int myfun(int data)
{
	printf("get data:%d\n",data);
	return (data*2);
}
int rt_data(int data,int (*tr_fun)())
{
	return ((*tr_fun)(data));
}  
int main(int argc,char *argv[])
{
	int ret;
	gfun gf;
	gf.pfun = myfun;
	ret = rt_data(100,gf.pfun);
	printf("return data:%d\n",ret);
	return 0;
}

运行的结果如下:
在这里插入图片描述
通过上面的例子我们可以看到将结构体中的函数指针指向了myfun函数地址,在回调函数中我们将函数指针gf.pfun作为rt_data(int data,int (*tr_fun)())函数的参数即为int (*tr_fun)();回调函数中的return (*tr_fun)(data)相当于对指针进行了简引用,返回这个指针指向地址的内容值。

回调函数的意义
可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数。简而言之,回调函数就是允许用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。
回调函数在实际中有什么作用?先假设有这样一种情况:我们要编写一个库,它提供了某些排序算法的实现(如冒泡排序、快速排序、shell排序、shake排序等等),为了能让库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,能让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。
回调可用于通知机制。例如,有时要在A程序中设置一个计时器,每到一定时间,A程序会得到相应的通知,但通知机制的实现者对A程序一无所知。那么,就需一个具有特定原型的函数指针进行回调,通知A程序事件已经发生。实际上,API使用一个回调函数SetTimer()来通知计时器。如果没有提供回调函数,它还会把一个消息发往程序的消息队列。
谈完回调函数的意义,我们就有了用户和开发者之间的概念,举个例子,用户是实现myfun这个函数,开发者是实现rt_data函数,根据需求用户将myfun函数以参数的形式传入开发者的rt_data函数中,rt_data函数就能返回给相应的数据给用户,开发者不用告诉用户它实现了什么,用户也并不知道开发者怎么实现,用户只用传入自己的函数,便可以得到开发者实现的函数返回值,开发者可以将内容封装起来,将头文件以及库文件提供给用户。
下面看个封装的例子
main.c是上层用户开发的
fun.c fun.h是开发者开发的

mian.c代码如下

#include "fun.h"
#include<stdio.h>
#include<stdlib.h>

typedef struct gfun{
	int (*pfun)(int);	
}gfun;

int myfun(int data)
{
	printf("get data:%d\n",data);
	return (data*2);
}
 
int main(int argc,char *argv[])
{
	int ret;
	gfun gf;
	gf.pfun = myfun;
	ret = rt_data(100,gf.pfun);
	printf("return data:%d\n",ret);
	return 0;
}

fun.h代码:

#ifndef _FUN_H_
#define _FUN_H_
int rt_data(int data,int (*tr_fun)());

#endif

fun.c代码:

#include "fun.h"
int rt_data(int data,int (*tr_fun)())
{
	return ((*tr_fun)(data));
}  

最后用gcc main.c fun.c -o main编译完成后生成mian执行文件
将执行文件执行后的结果如下:
在这里插入图片描述
在linux下制作动态链接库,将fun.c和fun.h打包成一个动态链接库

先明白以下几个命令是什么意思:

生成动态库:

gcc -shared -fPIC fun.c -o fun.so

-shared : 生成动态库;

-fPIC : 生成与位置无关代码;

-o :指定生成的目标文件;

使用动态库:

gcc main.c -L . –lfun -o main

-L : 指定库的路径(编译时); 不指定就使用默认路径(/usr/lib/lib)

-lfun : 指定需要动态链接的库是谁;

代码运行时需要加载动态库:

./main 加载动态库 (默认加载路径:/usr/lib /lib ./ …)

./main
我们将编译动态生成的libfun.so拷贝到/usr/lib后,现在就不需要fun.c了,此时我们将fun.c移除也可以正常的编译并执行main函数的结果。
下面是我制作动态链接库的过程:
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152192.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • append函数的用法[通俗易懂]

    append函数的用法[通俗易懂]append()函数类似于尾插就是给元素后面追加一个字符串stringstr;stringstr2=“123”;1.str.append(str2);就是给str后面追加一个str2即输出为123str.strappend(str2,1,1);就是给后面追加上str2中从第二个元素开始连续一个元素1232str.append(“abc”);就是给str后面追加上abc1232abcstr.append(“123456”,6);就是给str后面加上字符串

  • 重磅推荐!5 款强大的开源报表工具

    点击上方“Github中文社区”,关注看遍Github好玩的项目编辑:Huberhuber最近发现几款不错的开源报表,还提供源码,现在给大家分享一下,希望能给你带来帮助!1、项目名称:积…

  • webstorm激活码(注册激活)

    (webstorm激活码)JetBrains旗下有多款编译器工具(如:IntelliJ、WebStorm、PyCharm等)在各编程领域几乎都占据了垄断地位。建立在开源IntelliJ平台之上,过去15年以来,JetBrains一直在不断发展和完善这个平台。这个平台可以针对您的开发工作流进行微调并且能够提供…

  • charles导致mac无法上网_charles抓不到请求

    charles导致mac无法上网_charles抓不到请求前言charles关闭后,发现网页突然打开了,那大概率是设置了代理,但明明已经关闭了charles,这是由于mac网络偏好设置中,使用的是手动代理,将其改为自动即可解决方法1打开网络偏好设置,

  • 快速批量去除图片水印方法大全~~

    原文地址:http://blog.163.com/simonyao_cool/blog/static/16512555720107311858809/

  • 【Nginx安装】CentOS7安装Nginx及配置[通俗易懂]

    【Nginx安装】CentOS7安装Nginx及配置[通俗易懂]Nginx是一款轻量级的网页服务器、反向代理服务器。相较于Apache、lighttpd具有占有内存少,稳定性高等优势。**它最常的用途是提供反向代理服务。**安装在Centos下,yum源不提供nginx的安装,可以通过切换yum源的方法获取安装。也可以通过直接下载安装包的方法,**以下命令均需root权限执行**:首先安装必要的库(nginx中gzip模块需要zli…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号