十四、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

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

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

相关文章

大模型Rag-指令调度

本文主要记录根据用户问题指令&#xff0c;基于大模型做Rag&#xff0c;匹配最相关描述集进行指令调度&#xff0c;可用于匹配后端接口以及展示答案及图表等。 1.指令查询处理逻辑 1.实现思路 指令识别&#xff1a;主要根据用户的问题q计算与指令描述集is [i0, ... , im]和指…

音视频学习 - ffmpeg 编译与调试

编译 环境 macOS Ventrua 13.4 ffmpeg 7.7.1 Visual Studio Code Version: 1.99.0 (Universal) 操作 FFmpeg 下载源码 $ cd ffmpeg-x.y.z $ ./configure nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.If you think configure made a mistake…

golang-常见的语法错误

https://juejin.cn/post/6923477800041054221 看这篇文章 Golang 基础面试高频题详细解析【第一版】来啦&#xff5e; 大叔说码 for-range的坑 func main() { slice : []int{0, 1, 2, 3} m : make(map[int]*int) for key, val : range slice {m[key] &val }for k, v : …

音视频之H.265/HEVC预测编码

H.265/HEVC系列文章&#xff1a; 1、音视频之H.265/HEVC编码框架及编码视频格式 2、音视频之H.265码流分析及解析 3、音视频之H.265/HEVC预测编码 预测编码是视频编码中的核心技术之一。对于视频信号来说&#xff0c;一幅图像内邻近像素之间有着较强的空间相关性,相邻图像之…

基于政务问答的dify接口请求测试

Dify 的智能体后端服务 API 为开发者提供便捷方式&#xff0c;能让前端应用直接调用大语言模型能力。在请求时&#xff0c;需先前往应用左侧导航的 “API Access” 部分&#xff0c;在此可查看文档和管理访问凭据。为保障安全&#xff0c;API 密钥应通过后端调用&#xff0c;避…

VMware Workstation 保姆级 Linux(CentOS) 创建教程(附 iso)

文章目录 一、下载二、创建 一、下载 CentOS-7.9-x86_64-DVD-2009.iso 二、创建 VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包) VMware Workstation 保姆级安装教程(附安装包)

扩增子分析|基于R语言microeco包进行微生物群落网络分析(network网络、Zi-Pi关键物种和subnet子网络图)

一、引言 microeco包是福建农林大学姚敏杰教授团队开发的扩增子测序集成分析。该包综合了扩增子测序下游分析的多种功能包括群落组成、多样性、网络分析、零模型等等。通过简单的几行代码可实现复杂的分析。因此&#xff0c;microeco包发表以来被学界广泛关注&#xff0c;截止2…

GO语言-数据类型

文章目录 变量定义1. 整数类型2. 浮点类型3. 字符类型4. 布尔类型5. 字符串类型5.1 字符串的本质5.2 常用字符串处理函数(strings包)5.3 修改字符串的方式 6. 数据默认值7. 类型转换 变量定义 代码如下&#xff1a; package mainimport "fmt"var i1 1000 var i2 i…

线性代数 | 知识点整理 Ref 2

注&#xff1a;本文为 “线性代数 | 知识点整理” 相关文章合辑。 因 csdn 篇幅合并超限分篇连载&#xff0c;本篇为 Ref 2。 略作重排&#xff0c;未整理去重。 图片清晰度限于引文原状。 如有内容异常&#xff0c;请看原文。 【数学】线性代数知识点总结 阿巴 Jun 于 2024-…

JavaSE学习(前端初体验)

文章目录 前言一、准备环境二、创建站点&#xff08;创建一个文件夹&#xff09;三、将站点部署到编写器中四、VScode实用小设置五、案例展示 前言 首先了解前端三件套&#xff1a;HTML、CSS、JS HTML&#xff1a;超文本标记语言、框架层、描述数据的&#xff1b; CSS&#xf…

java + spring boot + mybatis 通过时间段进行查询

前端传来的只有日期内容&#xff0c;如&#xff1a;2025-04-17 需要在日期内容的基础上补充时间部分&#xff0c;代码示例&#xff1a; /*** 日志查询&#xff08;分页查询&#xff09;* param recordLogQueryDTO 查询参数对象* return 日志列表*/Overridepublic PageBean<…

解决ubuntu自带火狐浏览器无法播放视频问题

TIPS:一般执行完1 就可以了 首先安装必要的媒体编解码器和插件&#xff1a; # 安装常用媒体编解码器和插件 sudo apt update sudo apt install -y ubuntu-restricted-extras# 安装额外的编解码器 sudo apt install -y ffmpeg# 安装其他视频相关包 sudo apt install -y libavc…

计算机网络:流量控制与可靠传输机制

目录 基本概念 流量控制&#xff1a;别噎着啦&#xff01; 可靠传输&#xff1a;快递必达服务 传输差错&#xff1a;现实中的意外 滑动窗口 基本概念 换句话说&#xff1a;批量发货排队验收 停止-等待协议 SW&#xff08;发1份等1份&#xff09; 超时重传&#xff1a;…

Android组件刷新

Android中刷新View的方法有以下几种&#xff1a; 调用invalidate()方法&#xff0c;该方法会使View树中的所有视图无效或脏&#xff0c;等待下一次绘制时重新绘制。例如&#xff1a; mCustomView.invalidate(); 调用postInvalidate()方法&#xff0c;该方法类似于invalidate()…

Pycharm(十四)函数

一、函数概述 函数也叫方法,可以用function(函数,功能),method(方法)来表示。函数是把具有独立功能的代码封装到一起,使其成为具有独立功能的代码集。 它的好处:1.提高代码的复用性;2.模块化编程。 1.1 定义格式 def 函数名(形式参数1,形式参数2...): 函数体,就是逻…

Oracle测试题目及笔记(多选)

所有题目来自于互联网搜索 在以下概要文件的陈述中&#xff0c;哪两个是正确的&#xff1f; &#xff08;D 和 E&#xff09; A&#xff0e; 概要文件不能被用来为账户加锁 B&#xff0e; 概要文件不能被用来控制资源使用 C&#xff0e; 数据库管理员可以使用概要文件更改用户密…

DDoS攻防实战指南——解析企业级防护五大解决方案

一、流量清洗中心的智能化演进 云清洗服务已从被动响应转向主动防御。基于全球Anycast网络的分布式清洗节点&#xff0c;可在攻击发生时将流量牵引至专用清洗集群。阿里云2023年实测数据显示&#xff0c;其新一代清洗设备对SYN Flood的识别准确率达99.97%&#xff0c;误杀率控…

Ubuntu多用户VNC远程桌面环境搭建:从零开始的完整指南

引言: 在当今远程工作盛行的时代,搭建一个安全、高效的多用户远程桌面环境变得越来越重要。本文将为您提供一个从零开始的完整指南,教您如何在Ubuntu系统上搭建多用户VNC远程桌面环境。无论您是系统管理员、开发团队负责人,还是想要为家庭成员提供远程访问的技术爱好者,这…

数据结构专题 - 线性表

线性表是数据结构中最基础、最常用的数据结构之一&#xff0c;它在实际应用中非常广泛。无论是操作系统中的内存管理&#xff0c;还是数据库中的索引结构&#xff0c;线性表都扮演着重要角色。 一、线性表的概念与抽象数据类型 1.1 线性表的逻辑结构 线性表是由n&#xff08…

使用wpa_cli和wpa_supplicant配置Liunx开发板的wlan0无线网

目录 1 简单介绍下wpa_cli和wpa_supplicant 1.1 wpa_supplicant 简介 1.2 wpa_cli 简介 1.3 它们之间的关系 2 启动wpa_supplicant 3 使用rz工具把wpa_cli命令上传到开发板 4 用wpa_cli配置网络 参考文献&#xff1a; 1 简单介绍下wpa_cli和wpa_supplicant 1.1 wpa_su…