当前位置: 首页 > news >正文

现代C++ 如何使用 Lambda 使代码更具表现力、更容易理解?

使用 Lambda 使代码更具表现力

  • 一、Lambda VS. 仿函数
  • 二、总结

一、Lambda VS. 仿函数

Lambda 是 C++11 中最引人注目的语言特性之一。它是一个强大的工具,但必须正确使用才能使代码更具表现力,而不是更难理解。

首先,要明确的是,Lambda 并没有为语言添加新的功能。任何可以用 Lambda 完成的事情,都可以用仿函数(Functor)来完成,虽然仿函数的语法更繁琐,需要更多的类型声明。

例如,比较检查一个整数集合中所有元素是否都在两个整数 a 和 b 之间的两种方式:

  • 仿函数。
  • Lambda 表达式。

仿函数版本:

class IsBetween
{
public:IsBetween(int a, int b) : a_(a), b_(b) {}bool operator()(int x) { return a_ <= x && x <= b_; }
private:int a_;int b_;
};bool allBetweenAandB = std::all_of(numbers.begin(), numbers.end(), IsBetween(a, b));

Lambda 版本:

bool allBetweenAandB = std::all_of(numbers.begin(), numbers.end(),[a,b](int x) { return a <= x && x <= b; });

很明显,Lambda 版本更简洁,更易于编写,这可能是 Lambda 在 C++ 中备受关注的原因。

对于像检查一个数字是否在两个边界之间这样简单的操作,许多人可能会同意 Lambda 是更好的选择。但也并非所有情况下都是如此。

除了编写和简洁性之外,在前面的例子中,Lambda 和仿函数之间的两个主要区别是:

  • Lambda 没有名字。
  • Lambda 不隐藏其代码,而是直接在调用点展示。

但是,通过调用具有有意义名称的函数将代码从调用点移出,是管理抽象级别的一种基本技巧。但是,上面的例子是可以接受的,因为这两个表达式:

IsBetween(a, b)

[a,b](int x) { return a <= x && x <= b; }

读起来很相似。它们的抽象级别是一致的。

但是,当代码变得更加复杂时,结果就会大不相同,以下例子将说明这一点。

一个表示盒子的类的例子,它可以根据尺寸和材质(金属、塑料、木材等)进行构建,并提供对盒子特性的访问:

class Box
{
public:Box(double length, double width, double height, Material material);double getVolume() const;double getSidesSurface() const;Material getMaterial() const;
private:double length_;double width_;double height_;Material material_;
};

有一个这样的盒子集合:

std::vector<Box> boxes = ....

想要选择能够安全地容纳某种产品(水、油、果汁等)的盒子。

通过一些物理推理,可以近似地将产品对盒子四个侧面的压力视为产品的重量,它分布在这些侧面的表面上。如果材料能够承受施加的压力,则盒子足够坚固。

假设材料可以承受的最大压力为:

class Material
{
public:double getMaxPressure() const;....
};

产品提供了它的密度,以便计算它的重量:

class Product
{
public:double getDensity() const;....
};

现在,要选择能够安全地容纳产品 product 的盒子,可以使用 STL 和 Lambda 编写以下代码:

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes),[product](const Box& box){const double volume = box.getVolume();const double weight = volume * product.getDensity();const double sidesSurface = box.getSidesSurface();const double pressure = weight / sidesSurface;const double maxPressure = box.getMaterial().getMaxPressure();return pressure <= maxPressure;});

以下是等效的仿函数定义:

class Resists
{
public:explicit Resists(const Product& product) : product_(product) {}bool operator()(const Box& box){const double volume = box.getVolume();const double weight = volume * product_.getDensity();const double sidesSurface = box.getSidesSurface();const double pressure = weight / sidesSurface;const double maxPressure = box.getMaterial().getMaxPressure();return pressure <= maxPressure;}
private:Product product_;
};

在主代码中:

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), Resists(product));

尽管仿函数仍然需要更多的类型声明,但使用仿函数的算法代码行看起来比使用 Lambda 更清晰。不幸的是,对于 Lambda 版本来说,这一行代码更重要,因为它是主要代码。

在这里,Lambda 的问题在于它展示了如何进行盒子检查,而不是简单地说检查已经完成,因此它的抽象级别太低了。在该示例中,它会影响代码的可读性,因为它迫使读者深入 Lambda 的主体以弄清楚它做了什么,而不是简单地说明它做了什么。

在这里,有必要将代码从调用点隐藏,并为它赋予一个有意义的名称。仿函数在这方面做得更好。

但这是否意味着不应该在任何非平凡的情况下使用 Lambda?当然不是。

Lambda 被设计得比仿函数更轻便、更方便,同时仍然保持抽象级别有序。这里的技巧是通过使用中间函数将 Lambda 的代码隐藏在一个有意义的名称后面。以下是 C++14 中实现此目的的方法:

auto resists(const Product& product)
{return [product](const Box& box){const double volume = box.getVolume();const double weight = volume * product.getDensity();const double sidesSurface = box.getSidesSurface();const double pressure = weight / sidesSurface;const double maxPressure = box.getMaterial().getMaxPressure();return pressure <= maxPressure;};
}

在这里,Lambda 被封装在一个函数中,该函数只是创建它并返回它。这个函数的作用是将 Lambda 隐藏在一个有意义的名称后面。

以下是主代码,它从实现负担中解脱出来:

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), resists(product));

现在,为了使代码更具表现力,在本文的其余部分使用范围(Range)而不是 STL 迭代器:

auto goodBoxes = boxes | ranges::view::filter(resists(product));

当调用算法周围有其他代码时,隐藏实现的必要性变得更加重要。为了说明这一点,添加一个要求,即盒子必须从用逗号分隔的文本测量描述(例如,“16,12.2,5”)和所有盒子的唯一材料进行初始化。

如果直接调用即时 Lambda,结果将如下所示:

auto goodBoxes = boxesDescriptions| ranges::view::transform([material](std::string const& textualDescription){std::vector<std::string> strSizes;boost::split(strSizes, textualDescription, [](char c){ return c == ','; });const auto sizes = strSizes | ranges::view::transform([](const std::string& s) {return std::stod(s); });if (sizes.size() != 3) throw InvalidBoxDescription(textualDescription);return Box(sizes[0], sizes[1], sizes[2], material);})| ranges::view::filter([product](Box const& box){const double volume = box.getVolume();const double weight = volume * product.getDensity();const double sidesSurface = box.getSidesSurface();const double pressure = weight / sidesSurface;const double maxPressure = box.getMaterial().getMaxPressure();return pressure <= maxPressure;});

这变得非常难以阅读。但是,通过使用中间函数来封装 Lambda,代码将变成:

auto goodBoxes = textualDescriptions | ranges::view::transform(createBox(material))| ranges::view::filter(resists(product));

这才是希望代码呈现的样子。

请注意,这种技术在 C++14 中有效,但在 C++11 中略有不同。

Lambda 的类型没有在标准中指定,而是由编译器的实现决定。这里,auto 作为返回值类型允许编译器将函数的返回值类型写为 Lambda 的类型。但在 C++11 中,不能这样做,因此需要指定一些返回值类型。Lambda 可以隐式转换为具有正确类型参数的 std::function,并且可以在 STL 和范围算法中使用。请注意,std::function 会带来与堆分配和虚拟调用间接相关的额外成本。

在 C++11 中,resists 函数的建议代码将是:

std::function<bool(const Box&)> resists(const Product& product)
{return [product](const Box& box){const double volume = box.getVolume();const double weight = volume * product.getDensity();const double sidesSurface = box.getSidesSurface();const double pressure = weight / sidesSurface;const double maxPressure = box.getMaterial().getMaxPressure();return pressure <= maxPressure;};
}

请注意,在 C++11 和 C++14 的实现中,resists 函数返回的 Lambda 可能不会被复制,因为返回值优化可能会优化掉它。还要注意,返回 auto 的函数必须在其调用点可见。因此,这种技术最适合在与调用代码相同的文件中定义的 Lambda。

二、总结

  • 对于对抽象级别透明的函数,请使用在调用点定义的匿名 Lambda。
  • 否则,将 Lambda 封装在一个中间函数中。

在这里插入图片描述

相关文章:

现代C++ 如何使用 Lambda 使代码更具表现力、更容易理解?

使用 Lambda 使代码更具表现力 一、Lambda VS. 仿函数二、总结 一、Lambda VS. 仿函数 Lambda 是 C11 中最引人注目的语言特性之一。它是一个强大的工具&#xff0c;但必须正确使用才能使代码更具表现力&#xff0c;而不是更难理解。 首先&#xff0c;要明确的是&#xff0c;…...

LeetCode 2644.找出可整除性得分最大的整数:暴力模拟(两层循环)

【LetMeFly】2644.找出可整除性得分最大的整数&#xff1a;暴力模拟&#xff08;两层循环&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/find-the-maximum-divisibility-score/ 给你两个下标从 0 开始的整数数组 nums 和 divisors 。 divisors[i] 的 …...

Python列表,元组,集合,字典详解一篇搞懂

目录 介绍 列表(List) 集合(Set) 字典(Dict) 元组(Tuple) 列表 列表定义 ​编辑 列表切片 列表常用方法 append extend ​编辑 insert ​编辑 remove pop ​编辑 clear ​编辑 列表修改元素 sort 升序 倒序 reverse count ​编辑 index 浅拷贝和深拷贝 …...

Postgresql源码(132)分布式行锁的原理分析

相关 《Postgresql源码&#xff08;131&#xff09;行锁的原理分析》 1 分布式行锁 PG中的行锁在上一片中做了分析《Postgresql源码&#xff08;131&#xff09;行锁的原理分析》&#xff0c;本篇对分布式PG&#xff08;PGXL&#xff09;中的行锁做一些分析。&#xff08;版本…...

前端 防抖和节流

在前端开发中&#xff0c;防抖&#xff08;Debounce&#xff09;和节流&#xff08;Throttle&#xff09;是两种常用的性能优化技术&#xff0c;尤其在处理频繁触发的事件时显得尤为重要。无论是在用户输入、窗口调整大小&#xff0c;还是滚动事件中&#xff0c;这两种技术都可…...

C语言 | Leetcode C语言题解之第109题有序链表转换二叉搜索树

题目&#xff1a; 题解&#xff1a; int getLength(struct ListNode* head) {int ret 0;while (head ! NULL) {ret, head head->next;}return ret; }struct TreeNode* buildTree(struct ListNode** head, int left, int right) {if (left > right) {return NULL;}int …...

【DevOps】Linux 下安装配置 Apache 服务器:打造你的专属 Web 平台

目录 一、准备工作 二、安装 Apache 三、启动和管理 Apache 四、验证安装 五、配置 Apache 5.1 修改网站根目录 5.2 配置虚拟主机 5.2.1 创建虚拟主机配置文件 5.2.2 创建网站目录 5.2.3 启用虚拟主机 5.2.4 重启 Apache 5.3 配置 HTTPS 5.3.1 安装 SSL 证书 5.3…...

23种设计模式之一————外观模式详细介绍与讲解

外观模式详细讲解 一、概念二、 外观模式结构核心思想及解释模式的UML类图模式角色应用场景模式优点模式缺点 三、实例演示图示代码展示运行结果 一、概念 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个统一的接口&#xff0c…...

202109青少年软件编程(Python)等级考试试卷(四级)

第 1 题 【单选题】 执行如下 Python 代码后, 结果是?( ) def inverse(s,n=0): while s:n = n * 10 + s % 10s = s // 10return nprint...

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-17讲 定时器按键消抖

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…...

【系统架构师】-论文考点整理

1、软件架构风格 1.1、概述 1、软件架构为软件系统提供了一个结构、行为和属性的高级抽象。 2、软件架构风格是特定应用领域的惯用模式&#xff0c;架构定义一个词汇表和一组约束。 1.2、作用 1、软件架构是项目干系人进行交流的手段。 2、软件架构是可传递和可复用的模型&…...

Android Activity 设计详解

文章目录 Android Activity 设计说明1. Activity 的生命周期2. Activity 的启动模式3. Activity 的通信4. Activity 的布局和视图管理5. Activity 的配置变化处理6. Activity 的保存和恢复状态7. Activity 的任务和返回栈 总结 Android Activity 设计说明 在 Android 中&#…...

国家开放大学,javaScript程序设计-形考任务-实训五:设计登录和注册页|实训六:设计简单的购物车

实训五&#xff1a;设计登录和注册页 1. 题目 设计登录和注册页。 2. 目的 &#xff08;1&#xff09;掌握表单域的引用方法。 &#xff08;2&#xff09;掌握常用控件的基本方法。 &#xff08;3&#xff09;掌握事件的处理方法。 &#xff08;4&#xff09;理解Cookie…...

微服务可用性之隔离

摘要 ​ 本文主要微服务场景下服务的可用性保障之隔离。隔离又分为几种情况&#xff0c;动静隔离、读写隔离、热点隔离、资源隔离等场景。 为什么要隔离 ​ 本质上是对资源进行分割确保在出现故障的时候服务只是部分不可用&#xff0c;不至于系统陷入整体性瘫痪&#xff0c;…...

设计模式——概述

1.设计模式定义 ​ 设计模式是软件设计中常见问题的典型解决方案,可用于解决代码中反复出现的设计问题。设计模式的出现可以让我们站在前人的肩膀上&#xff0c;通过一些成熟的设计方案来指导新项目的开发和设计&#xff0c;以便于我们开发出具有更好的灵活性和可扩展性&#…...

#P0564. 数组元素查找升级版

问题描述 给你 n 个数&#xff0c;再给你一个数 k&#xff0c;查找 k 在这 n 个数中第一次出现的位置&#xff08;从 0 开始计数&#xff09;&#xff0c;不存在输出 No。 输入 多组测试数据&#xff0c;对于每组测试数据&#xff1a; 第一行输入一个整数 n (1 ≤ n ≤ 100…...

如何修改WordPress网站的域名

我的网站用的是Hostease的虚拟主机&#xff0c;但是域名是之前在其他平台买的&#xff0c;而且已经快到期了&#xff0c;因为主机和域名在不同的平台上&#xff0c;管理不太方便&#xff0c;所以我又在Hostease重新注册了一个域名&#xff0c;然后把网站换成了新的域名&#xf…...

python爬虫[简易版]

python爬数据[简易版] 对于每个网站的爬的原理基本是一样的,但是具体的代码写法的区别就在于爬的数据中解析出想要的数据格式: 以爬取有道词典中的图片为例: 第一步:打开网站,分析图片的数据源来自哪里, https://dict-subsidiary.youdao.com/home/content?invalid&pre…...

128天的创意之旅:从初心到成就,我的博客创作纪念日回顾

文章目录 &#x1f680;机缘&#xff1a;初心的种子——回望创作之旅的启航&#x1f308;收获&#xff1a;成长的果实——128天创作之旅的宝贵馈赠❤️日常&#xff1a;创作与生活的交织&#x1f44a;成就&#xff1a;代码的艺术&#x1f6b2;憧憬&#xff1a;未来的蓝图 &…...

前端绘制流程节点数据

根据数据结构和节点的层级、子节点id&#xff0c;前端自己绘制节点位置和关联关系、指向、已完成节点等 <template><div><div>通过后端节点和层级&#xff0c;绘制出节点以及关联关系等</div><div class"container" ref"container&…...

Cursor 工具项目构建指南: Uniapp Miniprogram 环境下的 Prompt Rules 约束

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 Cursor 工具项目构建指南: Uniapp Miniprogram 环境下的 Prompt Rules 约束前言项目简…...

算法-数论

C-小红的数组查询&#xff08;二&#xff09;_牛客周赛 Round 95 思路&#xff1a;不难看出a数组是有循环的 d3,p4时&#xff0c;a数组&#xff1a;1、0、3、2、1、0、3、2....... 最小循环节为4&#xff0c;即最多4种不同的数 d4,p6时&#xff0c;a数组&#xff1a;1、5、3、…...

MCP(Model Context Protocol)与提示词撰写

随着大模型&#xff08;LLM&#xff09;在复杂任务中的普及&#xff0c;如何让模型高效调用外部工具和数据成为关键挑战。传统函数调用&#xff08;Function Calling&#xff09;依赖开发者手动封装 API&#xff0c;而 MCP&#xff08;Model Context Protocol&#xff09; 通过…...

Codeforces Educational 179(ABCDE)

前言 byd这组题纯靠感觉是吧…^_^ b题赛时举了无数个例子都没想明白&#xff0c;然后一直卡到结束&#xff0c;后面题都没看到&#xff0c;结果补题的时候c题d题直接秒了…-_-|| A. Energy Crystals #include <bits/stdc.h> using namespace std;typedef long long …...

STM32H562----------串口通信(UART)

1、串口介绍 1.1、 数据通信概念 在单片机中我们常用的通信方式有 USART、IIC、SPI、CAN、USB 等; 1、数据通信方式 根据数据通信方式可分为串行通信和并行通信两种,如下图: 串行通信基本特征是数据逐位顺序依次传输,优点:传输线少成本低,抗干扰能力强可用于远距离传…...

PublishSubject、ReplaySubject、BehaviorSubject、AsyncSubject的区别

python容易编辑&#xff0c;因此用pyrx代替rxjava3做演示会比较快捷。 pyrx安装命令&#xff1a; pip install rx 一、Subject&#xff08;相当于 RxJava 的 PublishSubject&#xff09; PublishSubject PublishSubject 将对观察者发送订阅后产生的元素&#xff0c;而在订阅前…...

HarmonyOS:如何在启动框架中初始化HMRouter

应用启动时通常需要执行一系列初始化启动任务&#xff0c;如果将启动任务都放在应用主模块&#xff08;即entry类型的Module&#xff09;的UIAbility组件的onCreate生命周期中&#xff0c;那么只能在主线程中依次执行&#xff0c;不但影响应用的启动速度&#xff0c;而且当启动…...

BeanFactory 和 FactoryBean 有何区别与联系?

导语&#xff1a; Spring 是后端面试中的“常青树”&#xff0c;而 BeanFactory 与 FactoryBean 的关系更是高频卡人点。很多候选人混淆两者概念&#xff0c;答非所问&#xff0c;轻则失分&#xff0c;重则直接被“pass”。本文将从面试官视角&#xff0c;深入剖析这一经典问题…...

华为云Flexus+DeepSeek征文 | 基于DeepSeek-V3构建企业知识库问答机器人实战

作者简介 我是摘星&#xff0c;一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型&#xff0c;将实际使用经验分享给大家&#xff0c;希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 作者简介 1. 引言 2. 技术选型与架构设计 2.1 技…...

Java Map完全指南:从基础到高级应用

文章目录 1. Map接口概述Map的基本特性 2. Map接口的核心方法基本操作方法批量操作方法 3. 主要实现类详解3.1 HashMap3.2 LinkedHashMap3.3 TreeMap3.4 ConcurrentHashMap 4. 高级特性和方法4.1 JDK 1.8新增方法4.2 Stream API结合使用 5. 性能比较和选择建议性能对比表选择建…...