湖北省建设厅/seo刷关键词排名优化
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 工具…...

用ArkTS写一个登录页面(实现简单的逻辑)
登录页面 1.登录页面编码 Extend(TextInput) function customStyle(){.backgroundColor(#fff).border({width:{bottom:0.5},color:#e4e4e4}).borderRadius(1) //让圆角不明显.placeholderColor(#c3c3c5).caretColor(#fa711d) //input获取焦点样式 }Entry Component struct Log…...

matlab将INCA采集的dat文件多个变量批量读取到excel中
参考资料: MATLAB处理INCA采集数据(mdf,dat等)一 使用matlab处理INCF采集数据,mdf(.dat)格式文件,并将将其写入excel文件 这个资料只能一个变量一个变量的提取,本对其进…...

list集合常见去重方式以及效率对比
1.概述 list集合去重是开发中比较常用的操作,在面试中也会经常问到,那么list去重都有哪些方式?他们之间又该如何选择呢? 本文将通过LinkedHashSet、for循环、list流toSet、list流distinct等4种方式分别做1W数据到1000W数据单元测试…...

JavaWeb——Web入门(7/9)-Tomcat-介绍(Tomcat 的简介:轻量级Web服务器,支持Servlet/JSP少量JavaEE规范)
目录 Web服务器的作用 三个方面的讲解 Tomcat 的简介 小结 Web服务器的作用 封装 HTTP 协议操作:Web服务器是一个软件程序,对 HTTP 协议的操作进行了封装。这样开发人员就不需要再直接去操作 HTTP 协议,使得外部应用程序的开发更加便捷、…...

【SpringBoot】19 文件/图片下载(MySQL + Thymeleaf)
Git仓库 https://gitee.com/Lin_DH/system 介绍 从 MySQL 中,下载保存的 blob 格式的文件。 代码实现 第一步:配置文件 application.yml spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8datasource:driver-class-name: com.mysql.…...

陪诊问诊APP开发实战:基于互联网医院系统源码的搭建详解
时下,开发一款功能全面、用户体验良好的陪诊问诊APP成为了医疗行业的一大热点。本文将结合互联网医院系统源码,详细解析陪诊问诊APP的开发过程,为开发者提供实用的开发方案与技术指导。 一、陪诊问诊APP的背景与功能需求 陪诊问诊APP核心目…...

Spark 中 RDD 的诞生:原理、操作与分区规则
Spark 的介绍与搭建:从理论到实践-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交:本地与集群模式全解析-CSDN博客 Spark on YARN:Spark集群模式之Yarn模式的原…...

c++构造与析构
构造函数特性 名称与类名相同:构造函数的名称必须与类名完全相同,并且不能有返回值类型(包括void)。 自动调用:构造函数在对象实例化时自动调用,不需要手动调用。 初始化成员变量:构造函数的主…...

C++(函数重载,引用,nullptr)
1.函数重载 C⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。传参时会自动匹配传入的参数,对应该函数的形参类型,进行函数调用,这样C函数调⽤就表现出了多态⾏为&a…...

django+postgresql
PostgreSQL概述 PostgreSQL 是一个功能强大的开源关系数据库管理系统(RDBMS),以其高度的稳定性、扩展性和社区支持而闻名。PostgreSQL 支持 SQL 标准并具有很多先进特性,如 ACID 合规、复杂查询、外键支持、事务处理、表分区、JS…...