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

news2025/4/16 7:47:01

目录

上一章节:

一、引言

一、函数式编程基础

三、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.coloradmin.cn/o/2334955.html

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

相关文章

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

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

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攻击的…

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

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

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

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

推流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…

在线地图支持天地图和腾讯地图,仪表板和数据大屏支持发布功能,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;不论是手写还是识别表…

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 …

pyqtgraph.opengl.items.GLSurfacePlotItem.GLSurfacePlotItem 报了一个错

1. 需求是这个样子的 有一个 pyqtgraph.opengl.GLViewWidget &#xff0c;在应用启动时存在QMainWindow中&#xff0c;即父对象是QMainWindow&#xff0c;当业务需要时&#xff0c;修改它的父对象变为一个QDialog&#xff0c;可以让它从QMainWindow中弹出显示在QDialog里&#…

【C++初学】课后作业汇总复习(六) 函数模板

1、函数模板 思考&#xff1a;如果重载的函数&#xff0c;其解决问题的逻辑是一致的、函数体语句相同&#xff0c;只是处理的数据类型不同&#xff0c;那么写多个相同的函数体&#xff0c;是重复劳动&#xff0c;而且还可能因为代码的冗余造成不一致性。 解决&#xff1a;使用…

【第16届蓝桥杯C++C组】--- 数位倍数

Hello呀&#xff0c;小伙伴们&#xff0c;第16届蓝桥杯也完美结束了&#xff0c;无论大家考的如何&#xff0c;都要放平心态&#xff0c;今年我刚上大一&#xff0c;也第一次参加蓝桥杯&#xff0c;刷的算法题也只有200来道&#xff0c;但是还是考的不咋滴&#xff0c;但是拿不…

Numpy和OpenCV库匹配查询,安装OpenCV ABI错误

文章目录 地址opencv-python&#xff1a;4.x版本的对应numpyopencv-python&#xff1a;5.x版本的对应numpy方法2 ps&#xff1a;装个opencv遇到ABI错误无语了&#xff0c;翻了官网&#xff0c;github文档啥都没&#xff0c;记录下 地址 opencv-python&#xff1a;4.x版本的对应…