C++开发基础——函数指针回调函数

news2024/9/21 12:43:41

一,函数指针

1.函数指针的概念

与数组类似,函数在内存中也有地址,函数在内存中的地址是其机器语言代码的开始位置,而函数指针则存储函数的内存地址作为变量。函数指针可以被当作一个值赋给另一个变量,也可以作为实参传递给其他函数,或者作为其他函数的返回结果。通过传递不同的函数给函数指针,可以让一个函数在不同的时间分别调用不同实现的其他函数,这些函数都有一个共同特点,那就是它们的返回值类型还有参数的类型和个数必须相同,这个类似于int类型的指针只能指向包含int值的位置。 

函数指针的定义方式:

return_type (*function_pointer_name)(list_of_parameter_types);

注意,函数指针名称外面一定要有圆括号,不然就成了定义一个返回值类型为int指针的函数,样例:

int (*fun_ptr)(float*, int); //返回值类型为int的函数指针fun_ptr
int *fun(float*, int); //返回值类型为int*的函数fun

2.函数指针的初始化

方式一,使用nullptr或函数名称来初始化函数指针。

long get_max(const long* array, size_t size); //获得数组的最大值函数
long (*fun_ptr)(const long*, size_t){get_max}; //用函数名初始化的函数指针

方式二,使用auto关键字初始化函数指针。

auto fun_ptr = get_max;
auto* fun_ptr = get_max;
//以上两种方式的初始化效果是一样的,使用auto*来初始化
//可以强调fun_ptr是一个指针变量,使代码可读性更强

//也可以采用地址运算符&来显式获取函数地址
auto* fun_ptr = &get_max;
auto fun_ptr = &get_max;

方式二的初始化操作比较简单,一旦初始化完成,fun_ptr指向的函数的参数列表和返回值类型将是固定不变的。

3.函数指针调用函数

方式一,使用"(*function_pointer_name)"的方式调用,此方式向代码阅读者强调了使用的是函数指针。

long data[]{ 23, 19, 4, 50 };
long max_data = (*fun_ptr)(data, std::size(data));

方式二,和函数调用的方式类似,直接利用函数指针的名称来调用。

long data[]{ 23, 19, 4, 50 };
long max_data = fun_ptr(data, std::size(data));

完整C++代码实现:

#include <iostream>
using namespace std;
long get_max(const long* array, size_t size);
int main()
{
    auto* fun_ptr = &get_max;
    //auto fun_ptr = &get_max;
    //long (*fun_ptr)(const long*, size_t){get_max};
    long data[]{ 23, 19, 4, 50 };
    //long max_data = fun_ptr(data, std::size(data));
    long max_data = (*fun_ptr)(data, std::size(data));
    std::cout << "The max data is: " << max_data << std::endl;
    return 0;
}
long get_max(const long* array, size_t size)
{
    long max = array[0];
    int i = 0;
    for (i = 0; i < size; i++)
    {
        if (max < array[i])
        {
            max = array[i];
        }
    }
    return max;
}

运行结果: 

The max data is: 50

4.函数指针的类型别名

基于using关键字,我们可以给函数指针声明一个类型别名。

类型别名使得函数指针被用作函数参数或者对象的成员变量时的代码量更少。

具体操作如下:

原函数指针的定义:

long (*fun_ptr)(const long*, size_t);

step.01: 去掉函数指针名称,只保留类型关键字。

long (*)(const long*, size_t);

step.02: 基于using关键字的现代语法,左侧是自定义的名称,即类型别名,右侧是函数指针的返回值类型和传参列表。

using Array_max=long (*)(const long*, size_t);

step.03: 用类型别名定义函数指针变量

Array_max fun_ptr;

完整C++代码实现:

#include <iostream>
using namespace std;
using Array_max = long (*)(const long*, size_t);
long get_max(const long* array, size_t size);
int main()
{
       Array_max fun_ptr = &get_max;
       long data[]{ 23, 19, 4, 50 };
       long max_data = fun_ptr(data, std::size(data));
       std::cout << "The max data is: " << max_data << std::endl;
       return 0;
}
long get_max(const long* array, size_t size)
{
       long max = array[0];
       int i = 0;
       for (i = 0; i < size; i++)
       {
              if (max < array[i])
              {
                      max = array[i];
              }
       }
       return max;
}

运行结果: 

The max data is: 50

同理,也可以基于typedef关键字来声明函数指针的类型别名。

用法如下:

typedef long (*fun_ptr_type)(const long*, size_t);
long get_max(const long* array, size_t size);

//用类型别名来初始化函数指针
fun_ptr_type fun_ptr = &get_max;

完整C++代码实现:

#include <iostream>
using namespace std;
typedef long (*fun_ptr_type)(const long*, size_t);
long get_max(const long* array, size_t size);
int main()
{
    fun_ptr_type fun_ptr = &get_max;
    long data[]{ 23, 19, 4, 50 };
    long max_data = fun_ptr(data, std::size(data));
    std::cout << "The max data is: " << max_data << std::endl;
    return 0;
}

long get_max(const long* array, size_t size)
{
    long max = array[0];
    int i = 0;
    for (i = 0; i < size; i++)
    {
        if (max < array[i])
        {
            max = array[i];
        }
    }
    return max;
}

运行结果:

The max data is: 50

二,回调函数

1.回调函数的概念

回调函数是作为参数传递给另一个函数的函数。

学习回调函数的时候,不能被回调(callback)这个字眼给吓到。

通俗地描述:

这里有三个函数:函数A,函数B,函数C。

回调过程:函数C调用函数B的时候,函数B有个形参是函数指针,函数C将函数A的地址作为参数传给了函数B

主函数:函数C

中间函数:函数B

底层函数:函数A

用伪代码表示:

function A{
    ...
}

function C{
    auto* fun_ptr = &A; //函数指针初始化
    auto res = B(*fun_ptr); //调用函数B,并将函数A的地址作为参数传入
}

用C语言来简易实现:

#include<stdio.h>
void A()
{
    printf("I am function A\n");
}
void B(void (*ptr)())
{
    (*ptr) (); // callback to A
}
int main()
{
    void (*ptr)() = &A;
    B(ptr);
    return 0;
}

运行结果:

I am function A

2.回调函数的代码样例

完整C++代码实现:

#include <map>
#include <iostream>
typedef void (*Callback)();
std::map<int, Callback> callback_map;
void RegisterCallback(int event, Callback func)
{
    callback_map[event] = func;
}
bool finished = false;
int GetNextEvent()
{
    static int i = 0;
    ++i;
    if (i == 5)
        finished = true;
    return i;
}
void EventProcessor()
{
    while (!finished)
    {
        int event;
        event = GetNextEvent();
        std::map<int, Callback>::const_iterator it = callback_map.find(event);
        if (it != callback_map.end())
        {
            auto func_addr = it->second;
            Callback func = *func_addr;
            if (func)
            {
                (*func)();
            }
            else
            {
                std::cout << "No callback found\n";
            }
        }
    }
}
void Cat()
{
    std::cout << "Cat\n";
}
void Dog()
{
    std::cout << "Dog\n";
}
void Bird()
{
    std::cout << "Bird\n";
}
int main()
{
    RegisterCallback(1, Cat);
    RegisterCallback(2, Dog);
    RegisterCallback(3, Cat);
    RegisterCallback(4, Bird);
    RegisterCallback(5, Cat);
    EventProcessor();
    return 0;
}

运行结果:

Cat
Dog
Cat
Bird
Cat

3.使用回调函数的原因

回调函数在两个独立的类或函数之间建立了一种通信渠道,可以通过给主函数传递不同的参数来随时让中间函数调用不同的底层函数。回调函数在事件驱动机制和通信场景下使用起来很方便。

三,参考阅读

《Beginning C++17, 5th Edition》

《C++ Primer Plus, 6th Edition》

https://www.geeksforgeeks.org/callbacks-in-c

https://gist.github.com/DaxDeveloper/9915314

https://en.wikipedia.org/wiki/Callback_(computer_programming)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1535319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Linux]条件变量:实现线程同步(什么是条件变量、为什么需要条件变量,怎么使用条件变量(接口)、例子,代码演示(生产者消费者模式))

目录 一、条件变量 1.什么是条件变量 故事说明 2、为什么需要使用条件变量 竞态条件 3.什么是同步 饥饿问题 二、条件变量的接口 1.pthread_cond_t 2.初始化&#xff08;pthread_cond_init&#xff09; 3.销毁&#xff08;pthread_cond_destroy&#xff09; 4.等待&…

OKR与敏捷开发、精益创业等方法如何协同工作?

在快速变化的市场环境中&#xff0c;企业需要更加灵活和高效地应对各种挑战。目标与关键成果法&#xff08;OKR&#xff09;、敏捷开发以及精益创业等方法&#xff0c;作为现代企业管理的重要工具&#xff0c;各自在推动企业发展、提高团队效率、优化产品迭代等方面发挥着不可或…

Excel 使用SQL统计表格数据

一. 需求 ⏹有如下Excel表格&#xff0c;现要求统计每个店铺的每种类别的商品总销量和最大销量 ⏹详细数据如下 店铺商品类别销量一山店苹果水果27729一山店梨水果76175一山店菠萝水果14699一山店香蕉水果61371一山店西兰花蔬菜72822一山店大白菜蔬菜65090一山店小白菜蔬菜13…

LLM - 大语言模型的分布式训练 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/136924304 大语言模型的分布式训练是一个复杂的过程&#xff0c;涉及到将大规模的计算任务分散到多个计算节点上。这样做的目的是为了处…

部署Prometheus+grafana详解

目录 一、prometheus 介绍 二、prometheus 对比 zabbix 三、prometheus 监控插件 四、部署 1、下载所需的包 2.编辑prometheus的配置文件 3、编辑alertmanager 的配置文件 4、tmpl 模板&#xff08;将此文件创建在/opt/alertmanager/tmpl/&#xff09; 5.启动&#xff0…

【漏洞复现】Arris 路由器 basic_sett 信息泄露漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

c++编写菱形图和计算100~200之间的素数

c编写菱形图 #include <stdio.h> int main() {int i,j,k,n;printf("请输入n:\n");scanf("%d",&n);for(i1;i<n;i){for(k1;k<n-i;k)printf(" ");for(j1;j<2*i-1;j)printf("*");printf("\n");}for(i1;i<…

计算机二级(python)【一】

真题1 1、考生文件夹下存在一个文件PY101. py&#xff0c;请写代码替换横线&#xff0c;不修改其他代码&#xff0c;实现以下功能: 键盘输入正整数n&#xff0c;按要求把n输出到屏幕&#xff0c;格式要求:宽度为20个字符&#xff0c;减号字符-填充&#xff0c;右对齐&#xf…

Sora底层技术原理:Stable Diffusion运行原理

AIGC 热潮正猛烈地席卷开来&#xff0c;可以说 Stable Diffusion 开源发布把 AI 图像生成提高了全新高度&#xff0c;特别是 ControlNet 和 T2I-Adapter 控制模块的提出进一步提高生成可控性&#xff0c;也在逐渐改变一部分行业的生产模式。惊艳其出色表现&#xff0c;也不禁好…

MD5源码(C语言描述)

本文介绍MD5源码&#xff08;C语言描述&#xff09;。 MD5(Message-Digest Algorithm 5)&#xff0c;即消息摘要算法5&#xff0c;是一种被广泛使用的消息散列算法。散列算法的基础原理是&#xff1a;将数据&#xff08;如一段文字&#xff09;经过运算转换为一段固定长度&…

使用CUDA 为Tegra构建OpenCV

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;MultiArch与Ubuntu/Debian 的交叉编译 下一篇&#xff1a;在iOS中安装 警告&#xff1a; 本教程可能包含过时的信息。 使用CUDA for Tegra 的OpenCV 本文档是构建支持 CUD…

UE5制作一条底部挂着物体的悬垂的绳子

主要涉及cable&#xff08;缆索&#xff09;组件、PhysicsConstraint&#xff08;物理约束&#xff09;组件的灵活运用&#xff0c;经过摸索&#xff0c;写下本文以供探讨。 一、关卡中制作 关卡中制作最简单 1. cable组件加入场景 打开放置Actor面板&#xff0c;在其中找到…

docker swarm 集群创建

1&#xff0c;目的&#xff1a; 通过docker swarm 工具将一台或者多台安装了docker的服务器组成一个完整的集群&#xff0c;该集群中的node节点可以通过Leader节点管理。在使用docker stack部署时&#xff0c;可以将容器自动分发到合适的节点上。 2&#xff0c;服务器准备&am…

探秘开源隐语:架构深度剖析与隐私计算技术之旅

1.隐语架构 隐语&#xff08;SecretFlow&#xff09;作为蚂蚁集团开源的可信隐私计算框架&#xff0c;其架构设计具有多层次的特点&#xff0c;虽然具体分层名称可能会根据实际描述略有差异&#xff0c;但我们可以依据已有的技术和信息对其进行结构化的拆解&#xff1a; 硬件层…

GraalVM详细安装及打包springboot、java、javafx使用教程(环境安装篇)

下一篇:GraalVM详细安装及打包springboot、java、javafx使用教程(打包普通JAVA项目篇) GraalVM介绍 GraalVM是一款由Oracle公司开发的一款具有高效性能、降低基础设施成本、支持Java发展、与其他编程语言无缝集成、创建本机镜像等优点的跨平台虚拟机。它支持多种编程语言&…

Multi-Raft 架构, 数据Shard分区,数据迁移

Raft 与 Multi Raft PingCAP TiKV课程笔记课程链接 数据是以region&#xff08;也叫Raft Group)为单位进行存储的。一个region默认会有3个副本&#xff0c;存在不同的TiKV Node上。副本中的一个节点为leader。所有的读写流量只走leader&#xff0c;leader定期向follower发送心…

谷歌应用上架,如何选择IP?

在讨论IP对于谷歌上架的重要性或影响时&#xff0c;需要明确一点&#xff1a;开发者账号质量可以直接影响上架成功率&#xff0c;而IP是影响账号质量的重要因素之一。因此&#xff0c;IP对于谷歌上架的重要性&#xff0c;不言而喻。 我们都清楚&#xff0c;谷歌是不允许一个用户…

vue+element 前端实现增删查改+分页,不调用后端

前端实现增删查改分页&#xff0c;不调用后端。 大概就是对数组内的数据进行增删查改分页 没调什么样式&#xff0c;不想写后端&#xff0c;当做练习 <template><div><!-- 查询 --><el-form :inline"true" :model"formQuery">&l…

PHP的IntlChar类:处理Unicode字符的强大工具

PHP的IntlChar类&#xff1a;处理Unicode字符的强大工具 在处理多语言和国际化应用程序时&#xff0c;Unicode字符的解码是必不可少的一环。PHP的IntlChar类为我们提供了强大的工具来解码Unicode字符。本文将深入探讨PHP的IntlChar类&#xff0c;介绍其功能、用法和优势&#x…

鸿蒙:@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

在实际应用开发中&#xff0c;应用会根据开发需要&#xff0c;封装自己的数据模型。对于多层嵌套的情况&#xff0c;比如二维数组&#xff0c;或者数组项class&#xff0c;或者class的属性是class&#xff0c;他们的第二层的属性变化是无法观察到的。这就引出了Observed/Object…