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 工具…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
