C语言--函数指针的用法总结

2023-05-16

函数指针的由来

一个函数在编译时被分配一个入口地址,这个入口地址就称为函数的指针。

函数名代表函数的入口地址,这一点和数组一样。我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。如:假设有一个求两者较大的函数如下:

int max(int x, int y);

当我们调用这个函数时可以这样:

int c;
c max(a,b);

这是通常调用方法,其实我们还可以定义一个函数指针,通过指针来调用,如:

int (*p)(a,b);

有些朋友可能对(*p)()不大理解,其实它的意思就是定义一个指向函数的指针变量p,p不是固定指向哪个函数的,而是专门用来存放函数入口地址的变量。在程序中把哪个函数入口地址赋给它,它就指向哪个函数。但是注意,p不能像指向变量的指针变量一样进行p++p--等无意义的操作。
既然p是一个指针变量,那么久可以作为函数的参数进行传递。其实函数的指针变量最常用的用途之一就是作为函数参数传递到其它函数。这也是c语言中应用的比较深入的部分了。


函数指针的定义

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

函数指针有两个用途:调用函数和做函数的参数。

函数指针的用法

第一种用法(1.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();

第二种用法 (2.c)

#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;
}

第二种用法:typedef 原变量类型 别名
也可以用typedef来定义一个指针函数这样使在大型代码中更加简洁
这里面的 pfun 代表的是函数的类型,通过 pfun 来代表 void (*)(int) 函数类型即 pfun 是指针函数的别名,pfun p相当于定义了一个
void (*p)(int)函数指针。p = myfun 可以理解为将函数指针 p 指向 myfun 函数的地址,p(100);相当于执行myfun(100);

第三种用法(3.c)

#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;
} 

第三种用结构体函数指针的方法。

这三种用法的结果如下:

可以看到上面这三种使用方法其结果一致。

函数指针的作用

其实项目中用到了很多封装在struct中的函数指针,以前在MFC里面经常用到则个作为回调函数,还以为是微软设计的特色呢。在网上查了一下它的用法,做个总结。

1. 提供调用的灵活性。

设计好了一个函数框架,但是设计初期并不知道自己的函数会被如何使用。比如C的“stdlib”中声明的qsort函数,用来对数值进行排序。显然,顺序还是降序,元素谁大谁小这些问题,库程序员在编写qsort的时候不可能决定。这些问题是要在用户调用这个函数的时候才能够决定。那边qsort如何保证通用性和灵活性呢?采用的办法是让函数的使用者来制定排序规则。于是调用者应该自己设计comparator函数,传给qsort函数。这就在程序设计初期保证了灵活性。尽管使用函数指针使得程序有些难懂,但是这样的牺牲还是值得的。

2. 提供封装性能。

有点面向对象编程的特点。比如设计一个栈结构。

typedef struct _c_stack{
    int base_size;
    int point;
    int * base;
    int size;
    int  (*pop)(struct _c_stack *);
    int  (*push)(int,struct _c_stack *);
    int  (*get_top)(struct _c_stack);
}c_stack;

在初始化完之后,用户调用这个结构体上的pop函数,只需要s.pop(&s)即可。即使这个时候,工程内部有另外一个函数名字也叫pop,他们之间是不会发生名字上的冲突的。

原因很简单,因为结构体中的函数指针指向的函数名字可能是int ugly_stupid_no_one_will_use_this_name_pop(c_stack *),只是stack的用户是不知道他在调用s.pop(&s),实际上起作用的是这样一个有着冗长名字的函数。

函数指针这种避免命名冲突上的额外好处对于一些库函数的编写者是很有意义的,因为库可能被很多的用户在许多不同的环境下使用,这样就能有效的避免冲突而保证库的可用性。

关于函数指针,一般的时候用不到。主要还是一个简化结构和程序通用性的问题,也是实现面向对象编程的一种途径。简单的总结为:

实现面向对象编程中的多态性和回调函数


回调函数的定义

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

回调函数的例子(4.c)

#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代码:

#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.c代码:

#include "fun.h"

int rt_data(int data,int (*tr_fun)())
{
	return ((*tr_fun)(data));
}  

fun.h代码:

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

#endif

执行命令:gcc main.c fun.c -o main

运行结果如下:

在linux下制作动态链接库,将fun.c和fun.h打包成一个动态链接库。

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

生成动态库: gcc -shared -fPIC fun.c -o libfun.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函数的结果。

具体操作如下:

(这里编译时出了问题,记录一下,有时间再看看)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言--函数指针的用法总结 的相关文章

  • window10安装双系统ubuntu18.04

    win10安装ubuntu18 04 xff0c 有几点需要注意 xff0c 先罗列一下 BIOS模式 硬盘数 我觉得这两个是最烦的所以先重点标记一下 xff01 xff01 xff01 xff08 此教程只适合BIOS为UEFI xff0
  • PX4 OffBoard Control

    终于还是走上了这一步 xff0c 对飞控下手 xff0c 可以说是一张白纸了 记录一下学习的过程方便以后的查阅 目录 一 ubuntu18 04配置px4编译环境及mavros环境 二 PX4的OffBoard控制 1 搭建功能包 2 编写
  • 2021年电子竞赛四天三夜征程—-信号失真度测量装置(A题)

    2021大学生电子设计大赛 1 前言2 正文3 精彩片段分享4 信号失真度测量装置 xff08 A题 xff09 试题 1 前言 个人博客主页 ID Eterlove 一笔一画 xff0c 记录我的学习生活 xff01 站在巨人的肩上Sta
  • 【ROS】在回调函数中发布消息

    在ROS中 xff0c 想在回调函数中发布消息 xff0c 有两个思路 xff1a xff08 1 xff09 把函数写成类的形式 xff0c 把需要的一些变量在类中声明为全局变量 推荐 xff0c 模块化好 xff08 2 xff09 在
  • C语言-结构体对齐

    详细说明参考博客 1条消息 C语言结构体对齐 xff0c 超详细 xff0c 超易懂 haozigegie的博客 CSDN博客 1条消息 pragma pack详解 OuJiang2021的博客 CSDN博客 pragma pack 以下个
  • PowerShell 远程执行任务的方法和步骤

    PowerShell 远程执行任务的方法步骤 1 查看WinRM服务 Get Service WinRM 如果为关闭状态 xff0c 以管理员权限启动PowerShell窗口 xff0c 执行命令 Enable PSRemoting For
  • 电影网站推荐

    http www amobbs com thread 5599359 1 1 html 作者 solisgood 几年前当我还是一个小白的时候 xff0c 在网上常常会看到一些教人找电影的攻略 xff0c 他们推荐的无非是电影天堂 电影FM
  • Qt调用opencv实现yolov3对视频进行目标检测

    欢迎加QQ学习交流群309798848 依赖 xff1a 支持CUDA的opencv4 3 0 xff0c demo cfg xff0c demo final weights xff0c demo names demo cfg xff0c
  • Vim/VSCode/安装GO语言依赖工具

    由于vscode对go语言的支持还是hin不错滴 xff0c 所以我日常学习go都用vscode xff0c 但这货有个毛病 xff0c 各种lint 补全 nav 调试都依赖go语言的其他扩展工具 xff0c 如果安装补全 xff0c 会
  • 解决chrome添加扩展时的报错:“此项内容已下载并添加到Chrome中”

    chrome是google家的服务 xff0c 下个扩展也是要折腾一番 xff0c 网络质量更是不能保证 xff0c 所以下点东西时不时会出错 这次在下一个扩展的时候发现装了好久还是显示 正在检查 xff0c 遂手动刷新了一下页面 xff0
  • Manjaro终端无法输入中文,亲测有效

    span class token function export span GTK IM MODULE span class token operator 61 span fcitx span class token function ex
  • sh: 1: vue-cli-service: Permission denied

    看报错日志 xff0c 权限被拒绝 进入node modules bin 34 ll 34 查看一下会发现该文件 vue cli service 34 并没有可执行权限 chmod R 755
  • Linux系统Fcitx中文输入法开机启动方法

    Linux系统Fcitx中文输入法开机启动方法 在GNOME下的启动在KDE下的启动 Debian FC Ubuntu的默认中文输入法都是SCIM xff0c 其实也挺好用的 xff0c 有点类似windows下微软拼音输入法 xff0c
  • linux sftp文件上传与下载

    何为sftp sftp是Secure File Transfer Protocol的缩写 xff0c 安全文件传送协议 可以为传输文件提供一种安全的加密方法 回到顶部 连接 linux下直接在终端中输入 xff1a sftp usernam
  • win10专业版 原版安装教程

    WINDOWS10 的安装很是辛酸 xff0c 折腾了很久 xff0c 写下教程 xff0c 以防以后再入坑 Notes 不建议安装Ghost版 xff0c 会有许多问题 xff0c 电脑升级内存条后 xff0c 发现电脑有时候莫名奇妙蓝屏
  • Qt面试以及常用类继承关系图

    关于Qt的事件 事件的产生 xff1a 产生来源有timer事件外设的事件 xff08 mouseMoveEvent xff09 timer事件 xff0c 滚轮事件 xff0c 界面重绘制事件等等事件的接受与处理 xff1a QObjec
  • 无人驾驶虚拟仿真(四)--通过ROS系统控制小车行走

    简介 xff1a 实现键盘控制虚拟仿真小车移动 xff0c w s a d 空格 xff0c 对应向前 向后 向左 向右 急停切换功能 xff0c q键退出 1 创建key control节点 进入工作空间源码目录 xff1a cd myr
  • 云台控制协议

    PELCO D与PELCO P协议 PELCO D 数据格式 xff1a 1位起始位 8位数据 1位停止位 xff0c 无校验位 波特率 xff1a 2400B S 命令格式 xff1a 字节1 字节2 字节3 字节4 字节5 字节6 字节
  • 继承中子类与父类构造\析构的调用和顺序

    1 子类被构造的时候会先调用父类的构造函数 2 子类析构的时候先析构子类后析构父类 3 如果直接用子类构造一个父类的对象 删除这个父类的对象不会调用子类的析构函数 xff0c 这就是引入虚析构函数的原因 xff01
  • 28335GPIO及外部中断配置介绍

    弄了两周终于把28335的启动流程 寄存器及中断向量表的映射方法 内存的划分等有了一个全面的了解 xff0c 今天看到久违的LED灯的闪烁 xff0c 顿扫阴霾 特在此总结下28335GPIO及外部中断配置介绍 其实对于一个微控制器 xff

随机推荐

  • DSP28335与AD7606通过SPI的串行数据交互

    弄了三天的DSP28335与AD7606的通信终于实现了 最终的方案是通过DSP28335控制AD7606的采样 xff0c 采集的数据通过SPI串口发送给28335 xff0c 然后28335通过串口发送给上位机显示 其实程序第一天就写好
  • 利用28335的epwm产生spwm波的总结

    一 SPWM设计简介 设计的内容是产生倍频的SPWM波 xff0c 也即是用的是同一个调制波 xff0c 两个桥臂上的载波相差180度 产生spwm时 xff0c 利用TB产生载波 xff0c 也即是三角波 xff08 计数方式采用增减模式
  • 段错误总结

    最近试着写了华为编程大赛的程序 xff0c 出现较多的一个问题是段错误 xff0c 由此看来对指针与边界的处理还不熟练 网上有些总结的很不错 xff0c 因此结合网上资料整理下 xff08 下面的还有些地方没有深究 xff0c 有时间继续深
  • 启发式算法总结

    下面是一些学习到的算法 xff0c 有些没有具体用到 xff0c 所以只是概念的解释 xff0c 方便自己以后回忆 一 粒子群算法 1 1基本思想 粒子群算法是模拟群体智能所建立起来的一种优化算法 xff0c 粒子群算法可以用鸟类在一个空间
  • 线程、进程通信再总结

    下面这个部分摘抄自网上 xff0c 谢谢贡献的作者 一 进程间的通信方式 管道 pipe xff1a 管道是一种半双工的通信方式 xff0c 数据只能单向流动 xff0c 而且只能在具有亲缘关系的进程间使用 进程的亲缘关系通常是指父子进程关
  • 结构体类型的动态数组操作

    链接 xff1a https www nowcoder com questionTerminal 6fc9a928c7654b0fbc37d16b8bd29ff9 来源 xff1a 牛客网 假如我们有3种月饼 xff0c 其库存量分别为18
  • 基于Linkit 7697的红绿灯控制系统

    1 硬件准备 LinkIt 7697 1 xff0c 继电器模块 1 xff0c 面包板 1 xff0c RGB LED灯 1 xff08 共阳极 xff0c 工作电流20mA xff0c 红灯压降2 2 2V xff0c 绿灯蓝灯压降3
  • 利用背包问题解决的双核处理问题

    一种双核CPU的两个核能够同时的处理任务 xff0c 现在有n个已知数据量的任务需要交给CPU处理 xff0c 假设已知CPU的每个核1秒可以处理1kb xff0c 每个核同时只能处理一项任务 n个任务可以按照任意顺序放入CPU进行处理 x
  • 简单整蛊室友,只需几行bat病毒代码

    为了让整蛊更方便 xff0c 不能搞什么花里胡哨 xff0c 所有直接使用bat代码来编写 首先新建1个txt文件 xff0c 更改为任意名称 xff0c 但后缀名必须更改为bat或com 然后右键编辑 再输入以下代码 xff1a star
  • 四轴飞行器,PID调节过程心得记录

    初次接触四轴 xff0c 编写四轴的姿态PID控制部分 xff0c xff0c 横滚俯仰是把遥控器的杆量转换为目标角度 xff0c 然后目标角度PID运算转换为目标角速度 xff0c 然后目标角速度PID运算转换为电机输出量 xff0c x
  • 天地飞接收机输出信号解析

    今天测试了下天地飞8通道的接收机的pwm输出 接收机的输出信号 xff0c 可以按照50HZ的pwm信号来解析 xff0c 在stm32中 xff0c 使用外部高地电平触发中断的方式 xff0c 来记录一个脉宽的时间 用示波器实际查看信号的
  • Linux-TCP之深入浅出send和recv

    概念 先明确一个概念 xff1a 每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区 xff0c TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态 接收缓冲区把数据缓
  • popen使用方法及场景

    1 popen的应用场景 popen应用于执行shell命令 xff0c 并读取此命令的返值 xff0c 或者与执行的命令进行交互 2 popen的实现 popen 函数通过创建一个管道 xff0c 调用fork 产生一个子进程 xff0c
  • sscanf函数使用详解

    一 描述 sscanf通常被用来解析并转换字符串 xff0c 其格式定义灵活多变 xff0c 可以实现很强大的字符串解析功能 sscanf的原型 include lt stdio h gt int sscanf const char str
  • Linux通过系统函数设置系统时间

    一 描述 通过settimeofday 函数来设置系统时间 xff0c 这个函数设置的精度可以精确到微秒 include lt time h gt int settimeofday const struct timeval tv const
  • 用Eclipse完成C语言编程的几个简单步骤

    Eclipse是一款被广泛应用的开发工具 xff0c 最初它是为编写Java程序而设计的 xff0c 但由于它良好的架构并作为开源软件来发行 xff0c 有很多的公司和个人以它为基础开发了插件 xff0c 使得Eclipse有了越来越丰富的
  • 最适合程序员转行的10大职业

    三十而立 xff0c 源自 论语 为政 xff0c 说的是人到了30岁就应该去面对生活中的一切困难 而对于软件开发领域的从业者来说 xff0c 30岁 xff0c 却是一道槛 30岁以后 xff0c 适合程序员的工作到底是什么 专家和大家一
  • ROS2学习笔记(二)-- 多机通讯原理简介及配置方法

    在ROS1中由主节点 master 负责其它从节点的通信 xff0c 在同一局域网内通过设置主节点地址也可以实现多机通讯 xff0c 但是这种多机通讯网络存在一个严重的问题 xff0c 那就是所有从节点强依赖于主节点 xff0c 一旦运行主
  • C语言--“.”与“->”有什么区别?

    这虽然是个小问题 xff0c 但有时候很容易让人迷惑 xff0c 因为有的时候用混淆了 xff0c 程序编译不通过 下面说说我对它们的理解 一般情况下用 xff0c 只需要声明一个结构体 格式是 xff0c 结构体类型名 43 结构体名 然
  • C语言--函数指针的用法总结

    函数指针的由来 一个函数在编译时被分配一个入口地址 xff0c 这个入口地址就称为函数的指针 函数名代表函数的入口地址 xff0c 这一点和数组一样 我们可以用一个指针变量来存放这个入口地址 xff0c 然后通过该指针变量调用函数 如 xf