C++ 越来越像函数式编程了!
C++ 越来越像函数式编程了
大家好,欢迎来到今天的博客话题。今天我们要聊的是 C++ 这门老牌的强类型语言是如何一步一步向函数式编程靠拢的。从最早的函数指针,到函数对象(Functor),再到 std::function 和 std::bind,还有 lambda 表达式,最后我们重点讲讲 C++20 的 Ranges。这一路走来,C++ 变得越来越强大,越来越像函数式编程了。

什么是函数式编程?
在深入探讨 C++ 的演变之前,我们先简单介绍一下什么是函数式编程(Functional Programming)。函数式编程是一种编程范式,它把计算视为数学函数的求值,强调引用透明性、纯函数、高阶函数和惰性求值等概念。
- 引用透明性:相同输入总是得到相同的输出,没有副作用。
- 纯函数:函数内部不修改任何外部状态,也不依赖外部状态。
- 高阶函数:可以接受函数作为参数或者返回函数。
- 惰性求值:表达式只在需要时才计算。
一些更接近纯函数式编程范式的编程语言有:
- Haskell
- Lisp (及其变种,如 Scheme 和 Clojure)
- Erlang
- F#
这些语言天生具有函数式编程的特性,但我们的 C++ 也在一步步地引入这些概念,让我们看看 C++ 是如何演变到今天的吧。
函数指针
首先当然是函数指针了,这是 C++ 中最原始的一种“函数式”手段。函数指针可以指向一个函数,然后通过这个指针调用这个函数。
#include <iostream>void hello() {std::cout << "Hello, world!" << std::endl;
}int main() {// 定义一个函数指针void (*funcPtr)() = hello;// 通过指针调用函数funcPtr();return 0;
}
这种方法虽然简单,但是它的局限性也很明显,比如只能指向某一种特定签名的函数,灵活性不足。
函数对象(Functor)
随后 C++ 中引入了函数对象(Functor)。通过重载 operator(),我们可以创建一个像函数一样调用的对象。
#include <iostream>class HelloFunctor {
public:void operator()() const {std::cout << "Hello, world!" << std::endl;}
};int main() {HelloFunctor hello;hello(); // 调用函数对象return 0;
}
函数对象比起函数指针更灵活,因为它可以保存状态和行为。不过,写起代码来多少有些繁琐。
std::function 和 std::bind
到了 C++11,std::function 和 std::bind 出现了。std::function 是一个通用的函数包装器,几乎可以保存任意的可调用对象。而 std::bind 则可以将函数和参数绑定起来生成新的函数。
#include <functional>
#include <iostream>// 普通函数
int add(int a, int b) {return a + b;
}int main() {// 使用 std::function 包装函数std::function<int(int, int)> func = add;std::cout << func(3, 4) << std::endl; // 输出 7// 使用 std::bind 绑定参数auto add_with_2 = std::bind(add, 2, std::placeholders::_1);std::cout << add_with_2(5) << std::endl; // 输出 7return 0;
}
这一步让 C++ 的函数处理能力更上了一个台阶。可以部分绑定参数,再把它们传递或者存储起来,方便多了。
Lambda 表达式
C++11 还引入了 lambda 表达式,让我们可以在代码的任何地方定义匿名函数,极大地提高了代码的简洁性和灵活性。
#include <iostream>int main() {auto hello = []() {std::cout << "Hello, world!" << std::endl;};hello();int x = 42;auto printX = [x]() {std::cout << x << std::endl;};printX();return 0;
}
lambda 表达式不但让代码更加简洁,还可以捕获上下文中的变量,真是灵活至极。
C++20 的 Ranges
重点来了,C++20 引入了 Ranges 库,这真是一大进步,让 C++ 更接近现代的函数式编程风格。用 Ranges,我们可以像处理流一样处理序列,而且不需要手动写那些繁琐的循环。
基本用法
先来看一个简单的例子吧。
#include <iostream>
#include <vector>
#include <ranges>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6};auto result = numbers| std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; });for (auto n : result) {std::cout << n << ' ';}std::cout << std::endl;return 0;
}
这个例子里,我们用 std::views::filter 过滤掉了奇数,然后用 std::views::transform 把每个偶数平方。这种写法简洁优雅,而且更符合人类思维。
惰性求值
Ranges 还有一个厉害的地方,就是它是惰性求值的。意思是说,它不会在定义的时候马上计算,而是在真正需要结果的时候才计算,这样就避免了不必要的开销。
#include <iostream>
#include <ranges>int main() {auto numbers = std::views::iota(1, 1000000)| std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; })| std::views::take(5); // 仅获取前5个结果for (auto n : numbers) {std::cout << n << ' ';}std::cout << std::endl;return 0;
}
这个例子中,我们生成了从 1 到 1000000 的范围,但最后只取了前 5 个结果。因为是惰性求值的,整个过程非常高效。
4 16 36 64 100 ...Program finished with exit code 0
Press ENTER to exit console.
组合视图
Ranges 里的视图可以像管道一样组合起来,用 | 操作符,一看就知道数据是按什么顺序处理的。
#include <iostream>
#include <vector>
#include <ranges>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5, 6};auto result = numbers| std::views::filter([](int n) { return n % 2 == 0; })| std::views::transform([](int n) { return n * n; })| std::views::reverse;for (auto n : result) {std::cout << n << ' ';}std::cout << std::endl;return 0;
}
这个例子里,我们把前面的结果反转了一下,同样用的视图,代码依然非常清晰。
36 16 4 ...Program finished with exit code 0
Press ENTER to exit console.
生成和合并
还有一些其他很有用的视图,比如 std::views::iota 可以生成递增的序列,std::views::join 可以扁平化嵌套的范围。
#include <iostream>
#include <ranges>
#include <vector>int main() {auto numbers = std::views::iota(1) | std::views::take(10); // 1到10for (auto n : numbers) {std::cout << n << ' ';}std::cout << std::endl;std::vector<std::vector<int>> nested = { {1, 2}, {3, 4}, {5, 6} };auto flat_view = nested | std::views::join;for (auto n : flat_view) {std::cout << n << ' ';}std::cout << std::endl;return 0;
}
第一个例子是生成从 1 开始的自然数序列,取前 10 个。第二个例子是把嵌套的 vector 扁平化,这种操作在实际中非常常见而且有用。
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 ...Program finished with exit code 0
Press ENTER to exit console.
结语

通过这一路的演化,我们看到 C++ 引入的这些特性——从函数指针、函数对象、std::function、std::bind、lambda 表达式,再到 C++20 的 Ranges,让我们可以越来越方便地写函数式风格的代码。
这些新特性不仅让我们的代码更简洁、更易读,更高效,还让我们更容易掌握函数式编程的理念。希望大家通过这篇文章,对这些特性有更深的理解,并把它们用到实际开发中,让你的代码更加优雅!
相关文章:
C++ 越来越像函数式编程了!
C 越来越像函数式编程了 大家好,欢迎来到今天的博客话题。今天我们要聊的是 C 这门老牌的强类型语言是如何一步一步向函数式编程靠拢的。从最早的函数指针,到函数对象(Functor),再到 std::function 和 std::bind&…...
maven工程结构说明
1、maven工程文件目录 |-- pom.xml # Maven 项目管理文件 |-- src # 放项目源文件|-- main # 项目主要代码| |-- java # Java 源代码目录| | -- com/example/myapp…...
【GESP】C++一级真题练习(202312)luogu-B3921,小杨的考试
GESP一级真题练习。为2023年12月一级认证真题。逻辑计算问题。 题目题解详见:【GESP】C一级真题练习(202312)luogu-B3921,小杨的考试 | OneCoder 【GESP】C一级真题练习(202312)luogu-B3921,小杨的考试 | OneCoderGESP一级真题练习。为2023…...
游戏中Dubbo类的RPC设计时的注意要点
一.消费方 1.需要使用到动态代理,代理指定的接口,这样子接口被调用时,就可以拿到:"类名 方法名参数返回值" 这些类型。 2.既然是rpc,那么接口被调用时,肯定在动态代理中会进行网络消息的发送&a…...
ARXML汽车可扩展标记性语言规范讲解
ARXML: Automotive Extensible Markup Language (汽车可扩展标记语言) xmlns: Xml name space (xml 命名空间) xsd: Xml Schema Definition (xml 架构定义) 1、XML与HTML的区别,可扩展。 可扩展,主要是…...
Hadoop(HDFS)
Hadoop是一个开源的分布式系统架构,旨在解决海量数据的存储和计算问题,Hadoop的核心组件包括Hadoop分布式文件系统(HDFS)、MapReduce编程模型和YARN资源管理器,最近需求需要用到HDFS和YARN。 文章目录 HDFS优缺点HDFS的读写原理 常…...
机器学习系列----梯度下降算法
梯度下降算法(Gradient Descent)是机器学习和深度学习中最常用的优化算法之一。无论是在训练神经网络、线性回归模型,还是其他类型的机器学习模型时,梯度下降都是不可或缺的一部分。它的核心目标是最小化一个损失函数(…...
AI大模型:软件开发的未来之路
随着AI技术的快速发展,AI大模型正在对软件开发流程产生深远的影响。从代码自动生成到智能测试,AI大模型正在重塑软件开发的各个环节,为软件开发者、企业和整个产业链带来新的流程和模式变化。 首先,AI大模型的定义是指通过大规模…...
指标+AI+BI:构建数据分析新范式丨2024袋鼠云秋季发布会回顾
10月30日,袋鼠云成功举办了以“AI驱动,数智未来”为主题的2024年秋季发布会。大会深度探讨了如何凭借 AI 实现新的飞跃,重塑企业的经营管理方式,加速数智化进程。 作为大会的重要环节之一,袋鼠云数栈产品经理潮汐带来了…...
2024年第四届“网鼎杯”网络安全比赛---朱雀组Crypto- WriteUp
2024年第四届“网鼎杯”网络安全比赛---朱雀组Crypto-WriteUp Crypto:Crypto-2:Crypto-3: 前言:本次比赛已经结束,用于赛后复现,欢迎大家交流学习! Crypto: Crypto-2: …...
关于Markdown的一点疑问,为什么很多人说markdown比word好用?
markdown和word压根不是一类工具,不存在谁比谁好,只是应用场景不一样。 你写博客、写readme肯定得markdown,但写合同、写简历肯定word更合适。 markdown和word类似邮箱和微信的关系,这两者都可以通信,但微信因为功能…...
NoSQL大数据存储技术测试(1)绪论
写在前面:未完成测试的同学,请先完成测试,此博文供大家复习使用,(我的答案)均为正确答案,大家可以放心复习 单项选择题 第1题 以下不属于云计算部署模型的是( ) 公…...
Linux命令学习,git命令
Linux系统,Git是一个强大的版本管理系统,允许用户跟踪代码的更改、管理项目历史以及与他人协作。 Linux Git命令: 初始化仓库:当前目录创建一个Git仓库,生成.git隐藏目录存储版本历史和其他Git相关的元数据。 git init 克隆仓库…...
【AI大模型】Transformer中的编码器详解,小白必看!!
前言 Transformer中编码器的构造和运行位置如下图所示,其中编码器内部包含多层,对应下图encoder1…encoder N,每个层内部又包含多个子层:多头自注意力层、前馈神经网络层、归一化层,而最关键的是多头自注意力层。 自注…...
PostgreSQL 字段按逗号分隔成多条数据的技巧与实践 ️
全文目录: 开篇语前言 📚1. PostgreSQL 字段拆分的基本概念 🎯2. 使用 string_to_array 函数拆分字段 💬示例:使用 string_to_array 拆分字段结果: 3. 使用 unnest 和 string_to_array 结合拆分 ǵ…...
设计模式学习总结(一)
设计模式学习笔记 面向对象、设计原则、设计模式、编程规范、重构之间的关系 面向对象、设计原则、设计模式、编程规范、重构之间的关系 面向对象 现在,主流的编程范式或者是编程风格有三种:面向过程、面向对象和函数式编程。 需要掌握七大知识点&#…...
软考中级 软件设计师 上午考试内容笔记(个人向)Part.1
软考上午考试内容 1. 计算机系统 计算机硬件通过高/低电平来模拟1/0信息;【p进制】: K n K n − 1 . . . K 2 K 1 K 0 K − 1 K − 2... K − m K n r n . . . K 1 r 1 K 0 r 0 K − 1 r − 1 . . . K − m r − m K_nK_{n-1}...K_2K_1K_0K…...
PHP API的数据交互类型设计
PHP API的数据交互类型设计涉及多个方面,包括请求方法、数据格式、安全性考虑等。以下是对PHP API数据交互类型设计的详细探讨: 一、请求方法 在PHP API中,常见的请求方法包括GET、POST、PUT、DELETE等。这些方法在数据交互中各有其用途和特…...
【EFK】Linux集群部署Elasticsearch最新版本8.x
【EFK】Linux集群部署Elasticsearch最新版本8.x 摘要环境准备环境信息系统初始化启动先决条件 下载&安装修改elasticsearch.yml控制台启动Linux服务启动访问验证查看集群信息查看es健康状态查看集群节点查询集群状态 生成service token验证service tokenIK分词器下载 摘要 …...
【大数据测试 Elasticsearch — 详细教程及实例】
大数据测试 Elasticsearch — 详细教程及实例 1. Elasticsearch 基础概述核心概念 2. 搭建 Elasticsearch 环境2.1 安装 Elasticsearch2.2 配置 Elasticsearch 3. 大数据测试的常见方法3.1 使用 Logstash 导入大数据3.2 使用 Elasticsearch 的 Bulk API3.3 使用 Benchmark 工具…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...
在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...
【iOS】 Block再学习
iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
GraphRAG优化新思路-开源的ROGRAG框架
目前的如微软开源的GraphRAG的工作流程都较为复杂,难以孤立地评估各个组件的贡献,传统的检索方法在处理复杂推理任务时可能不够有效,特别是在需要理解实体间关系或多跳知识的情况下。先说结论,看完后感觉这个框架性能上不会比Grap…...
