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 工具…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...
数据库正常,但后端收不到数据原因及解决
从代码和日志来看,后端SQL查询确实返回了数据,但最终user对象却为null。这表明查询结果没有正确映射到User对象上。 在前后端分离,并且ai辅助开发的时候,很容易出现前后端变量名不一致情况,还不报错,只是单…...
[拓扑优化] 1.概述
常见的拓扑优化方法有:均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有:有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…...
