当前位置: 首页 > article >正文

十四、C++速通秘籍—函数式编程

目录

上一章节:

一、引言

一、函数式编程基础

三、Lambda 表达式

作用:

Lambda 表达式捕获值的方式:

注意:

四、函数对象

函数对象与普通函数对比:

五、函数适配器

1、适配普通函数

2、适配 Lambda 表达式

3、适配函数对象(仿函数)

使用场景

六、bind函数适配器

七、函数式编程的应用

八、总结

下一章节:


上一章节:

十三、C++速通秘籍—PIMPL编程原则-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147190679?spm=1001.2014.3001.5502

一、引言

这里主要介绍一下当下C++编程开发中比较常见的一种范式——函数式编程, 它以函数为核心,强调不可变性、高阶函数等概念,为我们处理复杂逻辑提供了新的视角和方法。这里给大家做一个简单的入门,以及笔者自己接触到的函数式编程的方式方法。

一、函数式编程基础

  • 不可变性:在函数式编程里,数据一旦创建就不可改变。比如在传统 C++ 中,我们可能会这样写代码:
int a = 5;
a = 10; // 修改变量a的值
而在函数式编程理念下,我们更倾向于通过函数调用来产生新的值,而不是修改原有变量。例如:
int add(int num) { return num + 5;
}int result = add(5); // result为10,没有修改传入的参数

  • 高阶函数是指接受函数作为参数,或者返回一个函数的函数。C++ 中的std::for_each 就是一个高阶函数的例子:
#include <iostream>
#include <algorithm>
#include <vector>void print(int num)
{    std::cout << num << " ";
}int main()
{    std::vector<int> vec = {1, 2, 3, 4, 5};std::for_each(vec.begin(), vec.end(), print);return 0;
}

这里std::for_each 接受了print 函数作为参数,对容器中的每个元素进行操作。
  • 闭包闭包是一个函数对象,它可以捕获其创建环境中的变量。在 C++ 中,Lambda 表达式就常用来创建闭包。例如:
#include <iostream>
#include <vector>int main() 
{    int factor = 2;auto multiply = [factor](int num) {return num * factor;    };    std::vector<int> vec = {1, 2, 3};    for (int num : vec) {        std::cout << multiply(num) << " ";    }    return 0;
}

这里multiply 这个 Lambda 表达式捕获了外部的factor 变量,形成了闭包。
  • 惰性求值:惰性求值是指表达式只有在真正需要结果时才进行计算。在 C++ 中,虽然没有像某些函数式编程语言那样原生支持惰性求值,但我们可以通过一些技巧来模拟。比如自定义一个延迟计算的类模板。

三、Lambda 表达式

Lambda 表达式是 C++ 函数式编程中非常重要的一部分。它允许我们在代码中快速定义匿名函数。
其本质是匿名函数,能够捕获一定范围的变量,与普通函数不同,可以在函数内部定义;
例如,要对一个整数数组进行排序,我们可以使用 Lambda 表达式来指定排序规则:
#include <iostream>
#include <algorithm>
#include <vector>int main() 
{    std::vector<int> vec = {5, 3, 1, 4, 2};    std::sort(vec.begin(), vec.end(), [](int a, int b) {        return a < b;    });    for (int num : vec) {        std::cout << num << " ";    }    return 0;
}

这里的 Lambda 表达式[](int a, int b) { return a < b; } 定义了升序排序的比较规则。

作用:

  1. 简化程序结构,因为优化了函数命名与函数传参;
  2. 提高程序运行效率,因为优化了函数调用、函数返回等消耗;
  3. 适用于简单功能的函数优化;

Lambda 表达式捕获值的方式:

捕获方式说明
=按值捕获,lambda内部可以使用,但是无法更改值
&按地址捕获,lambda内部可以使用,同时也更改了实际值
变量名按值捕获,可用不可改
&变量名
引用捕获,可用可改
副本捕获c++14后可以自定义变量名 = 捕获变量,但是无法通过副本名改变变量名
#include <iostream>int main(int argc, char **argv)
{int num1 = 5;int num2 = 6;auto func_add = [&num1,num2]()  //num1可修改,num2不可修改{num1 = 7;return num1 + num2;};auto func_add1 = [=](int a, int b){a = 1;  //这里修改的只是形参a/b的值,不会改变num1与num2的值b = 1;return a + b;};auto func_add2 = [&]{// num1 = 1;  //按引用传递,可用可修改num1与num2的值return num1 + num2;};std::cout<<func_add()<<std::endl;std::cout<<"num1 = "<<num1<<" num2 = "<<num2<<std::endl;std::cout<<func_add1(num1,num2)<<endl;std::cout<<"num1 = "<<num1<<" num2 = "<<num2<<std::endl;std::cout<<func_add2()<<endl;std::cout<<"num1 = "<<num1<<" num2 = "<<num2<<std::endl;return 0;
}

注意:

lambda 不可以包含static修饰的变量及全局变量;且避免复杂化

四、函数对象

  • 函数对象的定义和使用:函数对象是一个类或结构体,它重载了函数调用运算符"()"。例如:
#include <iostream>
#include <string>
#include <functional>using namespace std;template <typename T>
class Add
{
public:Add() = default;void operator()(T &&a, T &&b)  //重载函数运算符,采用的是万能引用{cout<<a+b<<endl;}
};int main(int argc ,char **argv)
{Add<int> c_add;c_add.operator()(5,6);  //利用成员函数的形式调用c_add(5,6);    //采用函数成员方式plus<int> p1;cout<<p1(5,6)<<endl;  //使用系统函数对象库return 0;
}
这里Adder 类就是一个函数对象, 通过重载() 运算符,使得它的对象可以像函数一样被调用。
  • STL 中的函数对象:C++ STL 中提供了很多预定义的函数对象,如std::plus、std::less 等。例如使用std::plus 来对两个数求和:
#include <iostream>
#include <functional>int main() 
{    std::plus<int> plus_op;    int result = plus_op(5, 3);    std::cout << result << std::endl;   return 0;
}
C++ STL 提供了丰富的算法,这些算法很多都体现了函数式编程的思想。比如std::accumulate 可以用来对容器中的元素进行累加:
#include <iostream>
#include <numeric>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};    int sum = std::accumulate(vec.begin(), vec.end(), 0);    std::cout << sum << std::endl;    return 0;
}

函数对象与普通函数对比:

  1. 函数对象比一般函数更灵活,因为它可以拥有状态(state),事实上,对于相同的函数对象可以设置两个状态不同的实例;普通函数没有状态;
  2. 每个函数对象都有其类型,因为你可以将函数对象的类型当做template参数传递,从而指定某种行为;
  3. 执行速度上,函数对象通常比函数指针更快;

五、函数适配器

        在编程中用于封装和管理函数或可调用对象(如函数指针、函数对象、Lambda 表达式等 ),让函数使用更灵活通用。以 C++ 为例,其标准库中的 std::function 是常用的函数适配器,本质是类模板 。它能 存储、复制和调用任何可调用对象,为不同可调用对象提供统一调用接口,使用者无需关心其具体类型。std::function  ,表明可适配接收两个int 参数并返回int 类型值的可调用对象 。
实例:

1、适配普通函数

#include <iostream>
#include <functional>
int add(int a, int b) {return a + b;
}
int main() {std::function<int(int, int)> func = add; std::cout << func(3, 4) << std::endl; return 0;
}

2、适配 Lambda 表达式

#include <iostream>
#include <functional>
int main() {std::function<int(int, int)> func = [](int a, int b) {return a * b;};std::cout << func(3, 4) << std::endl; return 0;
}

3、适配函数对象(仿函数)

#include <iostream>
#include <functional>
struct Subtract {int operator()(int a, int b) const {return a - b;}
};
int main() {std::function<int(int, int)> func = Subtract();std::cout << func(5, 3) << std::endl; return 0;
}

Subtract 结构体定义了函数调用运算符()  ,是函数对象 。std::function 将其包装后,可通过func 调用实现减法。

使用场景

  • 泛型编程:模板函数中,可将不同类型可调用对象(函数指针、Lambda、函数对象等)包装后作为参数传递,使模板函数能处理多种调用逻辑,增强代码通用性与灵活性。例如编写通用算法模板,可接收不同比较规则的函数包装器实现自定义排序等操作。
  • 回调函数 :在事件驱动编程(如图形界面开发、网络编程 )中,常需设置回调函数。用函数包装器可方便存储和管理这些回调,在特定事件发生时调用。如注册按钮点击事件回调,可将处理逻辑写成普通函数、Lambda 等,再用函数包装器管理并传递给按钮组件。
  • 异步编程 :多线程或异步任务场景下,函数包装器可存储要在新线程或异步环境执行的函数。如使用std::thread创建线程时,可将函数包装器作为线程执行任务,方便管理任务逻辑
  • 日志记录与性能监控 :通过包装器,可在函数执行前后添加日志记录代码,记录输入参数、执行时间等信息,辅助调试和性能优化;也能进行性能分析,记录函数执行耗时、资源占用等指标。
  • 权限验证与异常处理 :在函数执行前,利用包装器进行权限验证,确保只有有权限用户能调用;执行过程中捕获异常并处理,如打印错误信息、进行重试等操作 ,增强程序稳定性与安全性。

六、bind函数适配器

(1)、主要用在 函数已经存在,但是现有参数较多,减少实际所需参数个数的一种方法
(2)、本质, bind也是一个函数模板,返回值是一个仿函数 ,是可调用对象;
(3)、bind可以绑定的对象:①普通函数;②lambda表达式;③函数对象;④类的成员函数;⑤类的数据成员;
#include <iostream>
#include <functional>
using namespace std;template <typename T>
class Add
{
public:T operator()(T a, T b, T c){print();return a + b;}void operator()(const T &a){cout << a << endl;}void print(){cout << "function add!" << endl;}int m_result;
};int add(int a, int b, int c)
{cout << "a = " << a << " b = " << b << endl;return a + b + c;
}int main()
{//普通函数function<int(int,int)> my_add = std::bind(add,std::placeholders::_1,std::placeholders::_2,0);cout << my_add(5,6) << endl;function<int()> my_add2 = std::bind(add,7,8,0);cout << my_add2() << endl;//lambda表达式auto lambda_func = [=](int a, int b, int c){return a + b + c;};function<int(int,int)> my_add3 = std::bind(lambda_func,std::placeholders::_2,std::placeholders::_1,0);cout << my_add3(3,4) << endl;function<int()> my_add4 = std::bind(lambda_func,3,4,0);cout << my_add4() << endl;//函数对象Add<int> c_add;function<int(int,int)> my_add5 = std::bind(c_add,std::placeholders::_2,std::placeholders::_1,0);cout << my_add5(4,5) << endl;function<int()> my_add6 = std::bind(c_add,5,6,0);cout << my_add6() << endl;return 0;
}

七、函数式编程的应用

  • 数据处理:在处理大量数据时,函数式编程可以让代码更简洁和易于理解。比如对一个包含学生成绩的数组进行筛选,找出成绩大于 80 分的学生,使用函数式编程风格的代码可能如下:
#include <iostream>
#include <vector>
#include <algorithm>
struct Student {    std::string name;    int score;
};int main() 
{    std::vector<Student> students = {{"Alice", 85}, {"Bob", 70}, {"Charlie", 90}};    std::vector<Student> high_scores;    std::copy_if(students.begin(), students.end(), std::back_inserter(high_scores), [](const Student& s) {return s.score > 80;    });    for (const auto& student : high_scores) {        std::cout << student.name << " : " << student.score << std::endl;    }    return 0;
}

  • 并发编程:函数式编程的不可变性等特性在并发编程中很有优势,因为不可变的数据不用担心多线程访问时的竞争问题。例如,在使用std::async 进行异步任务时,可以传递函数式风格的函数对象。
  • 机器学习:在机器学习领域,函数式编程可以用于数据预处理、模型训练过程中的函数组合等场景。比如对数据集进行一系列的变换操作,可以通过组合不同的函数来实现。

八、总结

C++ 函数式编程为我们提供了一种强大且优雅的编程方式,无论是处理简单逻辑还是复杂的应用场景,都能展现出其独特的魅力。通过深入理解和应用这些概念,我们可以编写出更高效、更易维护的代码。

下一章节:

十五、C++速通秘籍—异常处理-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147195953

相关文章:

十四、C++速通秘籍—函数式编程

目录 上一章节&#xff1a; 一、引言 一、函数式编程基础 三、Lambda 表达式 作用&#xff1a; Lambda 表达式捕获值的方式&#xff1a; 注意&#xff1a; 四、函数对象 函数对象与普通函数对比&#xff1a; 五、函数适配器 1、适配普通函数 2、适配 Lambda 表达式 …...

蓝桥杯 2025 C++组 省 B 题解

可分解的正整数 算法&#xff1a;思维 因为可以有负数 所以除了1以外的任何数都可以构造 当这个数为x构造方法为 -(x-1) -(x-2) -(x-3) ....-1 0 1...x-3 x-2 x-1 x 除了x&#xff0c;x以前的数都会被负数抵消 #include <bits/stdc.h> #define ll long long ll a…...

如何让老电脑运行快些(极限榨干老电脑硬件)

要让老电脑运行更快&#xff0c;可以通过增加虚拟内存、优化系统设置和硬件升级等方法实现。以下是具体建议&#xff1a; 1. 增加虚拟内存&#xff08;适合硬盘空间大的老电脑&#xff09; 虚拟内存&#xff08;页面文件&#xff09;是硬盘上的一部分空间&#xff0c;用于扩展…...

博物馆小程序怎么做?从0到1打造数字化文化窗口

博物馆小程序怎么做&#xff1f;从0到1打造数字化文化窗口 一、行业痛点&#xff1a;传统博物馆的数字化困局 在数字化浪潮下&#xff0c;传统博物馆普遍面临三大挑战&#xff1a; ​​客流受限​​&#xff1a;线下接待能力有限&#xff0c;难以触达更广泛人群 ​​互动单一…...

信息学奥赛一本通 1498:Roadblocks | 洛谷 P2865 [USACO06NOV] Roadblocks G

【题目链接】 ybt 1498&#xff1a;Roadblocks 洛谷 P2865 [USACO06NOV] Roadblocks G 【题目考点】 1. 图论&#xff1a;严格次短路径 严格次短路的路径长度必须大于最短路的路径长度。 非严格次短路的路径长度大于等于最短路的路径长度。 【解题思路】 每个交叉路口是一…...

1.5-APP的架构\微信小程序的架构

1.5-APP的架构\微信小程序的架构 APP的三种开发架构&#xff1a; 原生态APP类型 APP-开发架构-原生态-IDEA 演示&#xff1a;remusic项目源码 NP管理器&#xff1a; http://normalplayer.top/ HttpCanary&#xff1a;https://github.com/mingww64/HttpCanary-SSL-Magisk 安全影…...

【服务器端表单字符验证】

文章目录 一、实验目的二、核心代码实现三、调试关键问题四、总结 一、实验目的 掌握JSP表单验证在服务器端的实现技术&#xff0c;实现对用户输入字符的非空及长度为5的验证&#xff0c;返回对应提示信息并优化用户交互。 二、核心代码实现 前端表单 <form action"…...

【玩转全栈】—— Django 连接 vue3 保姆级教程,前后端分离式项目2025年4月最新!!!

本文基于之前的一个旅游网站&#xff0c;实现 Django 连接 vue3&#xff0c;使 vue3 能携带 CSRF Token 发送 axios 请求给后端&#xff0c;后端再响应数据给前端。想要源码直接滑倒底部。 目录 实现效果 解决跨域 获取 csrf-token 什么是 csrf-token &#xff1f; CSRF攻击的…...

react中通过 EventEmitter 在组件间传递状态

要在 Reply 组件中通过 statusChangeEvent 发送状态值&#xff0c;并在 Select 组件中接收这个状态值 status&#xff0c;你可以按照以下步骤实现&#xff1a; //Event.jsimport EventEmitter from events;export const statusChangeEvent new EventEmitter();// 工单状态切换…...

数据库实验:分组查询与聚集函数的使用

目录 引言一、GROUP BY核心规则与常见错误二、高级分组选项&#xff1a;ROLLUP、CUBE与GROUPING SETS三、窗口函数&#xff1a;在原始行中显示分组聚合结果四、UNION ALL合并结果集&#xff1a;解决冗余查询问题五、实验体会 结语 &#xff08;附上实验中表格的信息&#xff09…...

【mllm】——x64模拟htp的后端无法编译debug

mllm, qnn, x64 code:https://github.com/UbiquitousLearning/mllm 1. 问题 通过自定义qualcomm graph使用高通的htp后端进行llm推理&#xff0c;网络暂时只有mllm&#xff0c;和https://github.com/chraac/llama.cpp。qualcomm是支持x64模拟htp推理的&#xff0c;这样比较好d…...

计算机网络- UDP协议详解

UDP协议详解 5. UDP协议详解5.1 UDP协议基础5.1.1 UDP的基本概念5.1.2 UDP报文结构5.1.3 UDP校验和计算5.1.4 UDP在Linux内核中的实现UDP套接字的创建UDP数据的发送UDP数据的接收UDP校验和的计算 5.1.5 UDP的实际应用音视频流媒体在线游戏DNS查询VoIP通信网络时间同步 5.2 UDP编…...

Open-TeleVision源码解析——宇树摇操方案的重要参考:VR控制人形机器人采集数据

前言 本来针对Open-TeleVision的源码解析&#xff0c;是打算放在此文《从宇树摇操avp_teleoperate到unitree_IL_lerobot&#xff1a;如何基于宇树人形进行二次开发》中的&#xff0c;但考虑到为避免篇幅过长&#xff0c;故独立成此文 第一部分 Open-TeleVision的源码解析 如本…...

Embedding质量评估、空间塌缩、 Alignment Uniformity

Embedding质量的评估和空间塌缩的解决是自然语言处理&#xff08;NLP&#xff09;和推荐系统领域的关键问题。以下是综合多篇研究的总结&#xff1a; 一、Embedding质量评估方法 基准测试与任务指标 MTEB/C-MTEB&#xff1a;使用多语言或中文的基准测试集&#xff08;如58个数据…...

[题解] Educational Codeforces Round 168 (Rated for Div. 2) E - level up

链接 思路 1 注意到在 k ∈ [ 1 , n ] k \in [1,n] k∈[1,n] 可以得到的最高等级分别为: n , n 2 , n 3 . . . . . n n n,\frac{n}{2},\frac{n}{3}.....\frac{n}{n} n,2n​,3n​.....nn​, 总的个数是一个调和级数, s u m n ∗ ln ⁡ n sumn*\ln n sumn∗lnn, 完全可以处…...

Lua 中的 table 类型详解

Lua 中的 table 类型详解 table 是 Lua 中唯一的数据结构机制&#xff0c;它实现了关联数组的功能&#xff0c;可以用来表示普通数组、序列、符号表、集合、记录、图、树等几乎所有数据结构。 基本特性 table 是 Lua 中唯一的数据结构类型可以包含任意类型的值&#xff08;n…...

github进阶使用教程

目录索引 一、基本内容 repository fork star codespaces issue 在一个仓库创建话题讨论&#xff0c;可以由仓库主人选择开始和结束话题的讨论 pull request&#xff08;也称 pr&#xff09; 协同其他仓库开发&#xff0c;请求仓库主人拉取自己的代码合并到仓库的主分支&…...

推流265视频,网页如何支持显示265的webrtc

科技发展真快&#xff0c;以前在网页上&#xff08;一般指谷歌浏览器&#xff09;&#xff0c;要显示265的视频流&#xff0c;都是很鸡肋的办法&#xff0c;要么转码&#xff0c;要么用很慢的hls&#xff0c;体验非常不好&#xff0c;而今谷歌官方最新的浏览器已经支持265的web…...

“破解”GPT-4o生图技术:万物皆可吉卜力的技术路线推测

&#x1f449;目录 1 GPT-4o 的神奇魔法 2 GPT-4o 可能的技术路线推测 3 结语 最近 GPT-4o 生图模型横空出世&#xff0c;效果和玩法上都有突破性的进展&#xff0c;笔者整理了一下目前相关的技术&#xff0c;抛砖引玉一下&#xff0c;希望有更多大神分享讨论。 图源小红书恶魔…...

基于SpringBoot的电影订票系统(源码+数据库+万字文档+ppt)

504基于SpringBoot的电影订票系统&#xff0c;系统包含两种角色&#xff1a;管理员、用户主要功能如下。 【用户功能】 首页&#xff1a;浏览系统电影动态。 资讯信息&#xff1a;获取有关电影行业的新闻和资讯。 电影信息&#xff1a;查看电影的详细信息和排片情况。 公告信…...

07-算法打卡-链表-移除链表-leetcode(203)-第七天

1 题目地址 203. 移除链表元素 - 力扣&#xff08;LeetCode&#xff09;203. 移除链表元素 - 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a;[https://assets.leetc…...

[C++面试] 初始化相关面试点深究

一、入门 1、C中基础类型的初始化方式有哪些&#xff1f;请举例说明 ​默认初始化​ 对于全局变量和静态变量&#xff0c;基础类型&#xff08;如int、float、double等&#xff09;会被初始化为 0&#xff1b;而对于局部变量&#xff0c;其值是未定义的&#xff0c;包含随机…...

在线地图支持天地图和腾讯地图,仪表板和数据大屏支持发布功能,DataEase开源BI工具v2.10.7 LTS版本发布

2025年4月11日&#xff0c;人人可用的开源BI工具DataEase正式发布v2.10.7 LTS版本。 这一版本的功能变动包括&#xff1a;数据源方面&#xff0c;Oracle数据源支持获取和查询物化视图&#xff1b;图表方面&#xff0c;在线地图支持天地图、腾讯地图&#xff1b;新增子弹图&…...

粉末冶金齿轮学习笔记分享

有一段小段时间没有更新了&#xff0c;不知道小伙们有没有忘记我。最近总听到粉末冶金齿轮这个概念&#xff0c;花点时间来学习一下&#xff0c;总结一篇笔记分享给大家。废话不多说&#xff0c;直接开始&#xff1a; “粉末冶金”是一种制造工艺&#xff0c;包括在高压下压实…...

Retina:部署在神经硬件的SNN眼动追踪算法

论文链接&#xff1a;Retina : Low-Power Eye Tracking with Event Camera and Spiking Hardware 这是一篇发表在2024CVPRW上的文章&#xff0c;做了三个contribution&#xff1a; 将SNN放在Eye Tracking任务上。提出了Ini-30数据集部署到了Spike硬件上 还是挺有趣的。但是由于…...

OCR API识别对比

OCR 识别DEMO OCR识别 demo 文档由来 最开始想使用百度开源的 paddlepaddle大模型 研究了几天&#xff0c;发现表格识别会跨行&#xff0c;手写识别的也不很准确。最终还是得使用现成提供的api。。 文档说明 三个体验下来 腾讯的识别度比较高&#xff0c;不论是手写还是识别表…...

nodejs构建项目

从零到一搭建 Node.js 框架 搭建一个 Node.js 框架是理解 Web 应用架构的绝佳方式。本指南将带您完成创建一个轻量级但功能完善的 Node.js 框架的全过程&#xff0c;类似于 Express 或 Koa&#xff0c;但规模更小&#xff0c;便于理解。 目录 项目初始化创建核心应用类路由系…...

flutter 桌面应用之右键菜单

​在 Flutter 桌面应用开发中&#xff0c;context_menu 和 contextual_menu 是两款常用的右键菜单插件&#xff0c;各有特色。以下是对它们的对比分析&#xff1a;​ context_menu 集成方式&#xff1a;​通过 ContextMenuArea 组件包裹目标组件&#xff0c;定义菜单项。​掘金…...

Cygwin编译安装Acise

本文记录Windows下使用Cygwin编译安装Acise的流程。 零、环境 操作系统Windows11Visual Studio CodeVisual Studio Code 1.92.0Cygwin 一、工具及依赖 1.1 Visual Studio Code 下载并安装Visual Studio Code, 同时安装以下插件&#xff0c; Task Explorer Output Colorizer …...

基于STM32、HAL库的IP6525S快充协议芯片简介及驱动程序设计

一、简介: IP6525S是一款高性能的同步降压DC-DC转换器芯片,具有以下特点: 输入电压范围:4.5V至32V 输出电压范围:0.8V至30V 最大输出电流:5A 效率高达95% 可编程开关频率(100kHz-1MHz) 支持PWM和PFM模式 内置过流保护、过温保护等功能 该芯片常用于工业控制、通信设备…...