C语言中函数指针和回调函数的详解

2022-06-25 13:24:37 浏览数 (1)

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

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

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

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
void (*pfun)(int data);
void myfun(int data)
{
	printf("get data:%dn",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:%dn",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:%dn",data);
}
int main(int argc,char *argv[])
{
	gfun gcode={
		.pfun = myfun,   //将函数指针指向要调用函数的地址
	};
	gcode.pfun(100);
	return 0;
} 

这三种方法运行的结果一样

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

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
typedef struct gfun{
	int (*pfun)(int);	
}gfun;

int myfun(int data)
{
	printf("get data:%dn",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:%dn",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代码如下

代码语言:javascript复制
#include "fun.h"
#include<stdio.h>
#include<stdlib.h>

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

int myfun(int data)
{
	printf("get data:%dn",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:%dn",ret);
	return 0;
}

fun.h代码:

代码语言:javascript复制
#ifndef _FUN_H_
#define _FUN_H_
int rt_data(int data,int (*tr_fun)());

#endif

fun.c代码:

代码语言:javascript复制
#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函数的结果。 下面是我制作动态链接库的过程:

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

0 人点赞