C++ 中如何优雅地返回一个递归闭包函数?
在刷Leetcode时,我遇到了一道题目(详见Leetcode 第426场周赛分析总结Q3),需要对两棵树建图,然后以每个节点作为根节点进行DFS遍历。一般的实现方法是将重复的逻辑封装起来,写两个函数,一个负责建图,另一个负责DFS,然后将建图后的返回值作为参数传递给DFS。
在Python、JavaScript等高级语言中,有一种叫做闭包函数的编程技巧,能够简化这种逻辑。闭包本质上是一个函数A返回另一个函数B,而函数B捕获了函数A的局部变量,从而使函数B拥有状态信息。在C++中,我们也可以利用闭包函数,将建图与DFS的逻辑绑定,从而避免显式地在调用时传递参数。
以下是该问题的C++实现:
class Solution {
public:vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2, int k) {// 建立一个闭包函数,用于返回DFS函数auto get_dfs = [](decltype(edges1) edges) -> auto {int n = edges.size() + 1;vector graph(n, vector<int>());for (auto &&edge : edges) {auto u = edge[0], v = edge[1];graph[u].push_back(v);graph[v].push_back(u);}function<int(int, int, int)> dfs;dfs = [graph = std::move(graph), &dfs](int u, int fa, int d) -> int {if (d < 0) return 0;int ans = 1;for (auto v : graph[u]) {if (v == fa) continue;ans += dfs(v, u, d - 1);}return ans;};return dfs;};// 对第二棵树进行DFS,计算其最大目标节点数int maxn2 = 0;if (k > 0) {auto dfs = get_dfs(edges2);int n = edges2.size() + 1;for (int i = 0; i < n; ++i) {maxn2 = max(maxn2, dfs(i, -1, k - 1));}}// 对第一棵树进行DFS,结合第二棵树的结果计算答案auto dfs = get_dfs(edges1);int n = edges1.size() + 1;vector<int> ans(n, 0);for (int i = 0; i < n; ++i) {ans[i] = dfs(i, -1, k) + maxn2;}return ans;}
};
闭包函数的核心原理
在C++中,闭包(Closure)是通过lambda
表达式实现的一种语法特性。闭包可以捕获外部上下文中的变量,将其绑定到返回的函数中,从而避免显式传递参数。在上面的代码中,get_dfs
函数返回了一个DFS函数,这个DFS函数通过捕获将graph
变量绑定到其作用域中。
以下是闭包函数的几项核心要点:
-
变量捕获:
- 值捕获(by value):将外部变量的值拷贝到闭包中,闭包对这些变量的修改不会影响外部变量。
- 引用捕获(by reference):闭包捕获外部变量的引用,闭包对变量的修改会影响外部变量。
- C++14后的移动捕获(move capture):通过
std::move
,可以将大对象的所有权转移到闭包中,避免拷贝的开销。
-
闭包的生命周期:
- 在C++中,闭包的生命周期由捕获的变量决定。如果捕获的变量是局部变量,需确保这些变量在闭包的使用过程中始终有效。
实现中的关键点
1. 移动捕获graph
在get_dfs
函数中,我们通过std::move(graph)
将图的所有权转移到闭包中:
dfs = [graph = std::move(graph), &dfs](int u, int fa, int d) -> int { ... };
为什么要使用移动捕获?
- 如果使用值捕获,
graph
会被拷贝,可能造成性能开销,特别是在graph
较大时。 - 如果使用引用捕获,
get_dfs
函数返回后,graph
的生命周期结束,闭包中的引用将变为悬挂指针,导致未定义行为。 - 移动捕获通过转移所有权将
graph
绑定到闭包中,使其生命周期与闭包一致,既避免了拷贝开销,又保证了安全性。 - 需要注意的是,移动捕获会使闭包与捕获的资源绑定,可能导致资源生命周期难以管理。在更复杂的场景下,可以考虑将
graph
提前封装到一个辅助类中,避免直接捕获大对象。
2. 引用捕获自身dfs
在递归的实现中,dfs
函数需要捕获自身。这通过引用捕获实现:
dfs = [graph = std::move(graph), &dfs](int u, int fa, int d) -> int { ... };
如果进行值捕获([dfs]
),编译器会尝试拷贝 dfs
,但在捕获时 dfs
仍未完全定义(不完全类型),因此会报错。
为什么捕获dfs
可以使用引用?
- 返回闭包时,
dfs
作为函数的返回值,在返回值优化(RVO)的作用下,其内存不会被销毁。因此,引用捕获dfs
是安全的。 - RVO 是一种编译器优化技术,用于避免对象的临时拷贝或移动。其核心思想是:在函数返回对象时,直接在调用方的内存中构造返回值,跳过临时对象的拷贝或移动操作。
- 需要注意的是,在不支持 RVO 的情况下,引用捕获
dfs
是不安全的。这时就需要把dfs作为参数进行传递,或者在内层再使用std::function
进行包裹。
闭包的优势与局限性
优势
- 代码简洁:将建图和DFS逻辑封装在一个闭包中,避免显式传递参数。
- 状态绑定:闭包通过捕获机制,将
graph
与DFS逻辑绑定,减少上下文管理的复杂性。 - 灵活性:闭包函数可以作为返回值,方便以声明式方式组织代码。
局限性
- 复杂性增加:闭包的捕获规则较为灵活,但也容易出现因误用而导致的悬挂引用或性能问题。
- 调试困难:闭包在捕获变量时会隐式生成代码,可能导致调试困难。
- 性能开销:虽然可以通过移动捕获减少拷贝,但闭包仍可能引入额外的性能开销,特别是当捕获大量对象时。
总结与反思
通过闭包函数,将建图与DFS逻辑绑定,简化了调用接口,同时减少了显式参数传递的麻烦。这种高级技巧在C++中并不常见,但在特定场景(小型、局部的递归场景)下能够显著提升代码的可读性与复用性。
然而,闭包函数的使用也需要谨慎,特别是在C++中,变量的捕获方式直接影响代码的安全性与性能。通过对捕获规则(值捕获、引用捕获、移动捕获)的深入理解,可以更安全、高效地使用闭包,提高代码质量。
相关文章:
C++ 中如何优雅地返回一个递归闭包函数?
在刷Leetcode时,我遇到了一道题目(详见Leetcode 第426场周赛分析总结Q3),需要对两棵树建图,然后以每个节点作为根节点进行DFS遍历。一般的实现方法是将重复的逻辑封装起来,写两个函数,一个负责建…...
dockerignore文件怎么写
.dockerignore 文件是一个文本文件,告诉 Docker 在构建镜像时应该忽略哪些文件或目录。这个文件的作用类似于 .gitignore,用于排除不需要包含在 Docker 镜像中的文件或目录,以减少镜像的大小和提高构建速度。 .dockerignore 文件的格式 每行…...
利用 AI 高效生成思维导图的简单实用方法
#工作记录 适用于不支持直接生成思维导图的AI工具;适用于AI生成后不能再次编辑的思维导图。 在日常的学习、工作以及知识整理过程中,思维导图是一种非常实用的工具,能够帮助我们清晰地梳理思路、归纳要点。而借助 AI 的强大能力,…...
SpringMVC(一)配置
目录 引入 第一章:Java web的发展历史 一、Model I和Model II 1.Model I开发模式 2.Model II开发模式 二. MVC模式 第二章:SpringMVC的入门案例 搭建SpringMVC的入门程序 1.创建新项目 2.等待加载导入坐标 3.处理xml文件和其他 导入tomcat 运…...
Vue3苦逼的学习之路
从一名测试转战到全栈是否可以自学做到,很多朋友肯定会说不可能,或就算转了也是个一般水平,我很认同,毕竟没有经过各种项目的摧残,但是还是得踏足一下这个领域。所以今天和大家分享vue3中的相关内容,大佬勿…...
github提交不上去,网络超时问题解决
问题出现的原因: DNS服务器数据不同步,github的服务器发送迁移,在本地缓存的ip地址现在无效了。 解决方案: 1)点击这里,查询github.com最新的ip地址 2.0)编辑linux系统地址缓存文件&#x…...
SAP物料主数据界面增加客制化字段、客制化页签的方式
文章目录 前言一、不增加页签,只增加客制化字段二、增加物料主数据页签 前言 【SAP系统MM模块研究】 #SAP #MM #物料 #客制化 #物料主数据 项目上难免会遇到客户要在物料主数据的界面上,增加新字段的需求。 实现方式有: (1&…...
56.在 Vue 3 中使用 OpenLayers 通过 moveend 事件获取地图左上和右下的坐标信息
前言 在现代 Web 开发中,地图应用越来越成为重要的组成部分。OpenLayers 是一个功能强大的 JavaScript 地图库,它提供了丰富的地图交互和操作功能,而 Vue 3 是当前流行的前端框架之一。在本篇文章中,我们将介绍如何在 Vue 3 中集…...
DDoS攻击防御方案大全
1. 引言 随着互联网的迅猛发展,DDoS(分布式拒绝服务)攻击成为了网络安全领域中最常见且危害严重的攻击方式之一。DDoS攻击通过向目标网络或服务发送大量流量,导致服务器过载,最终使其无法响应合法用户的请求。本文将深…...
OLED的显示
一、I2C I2C时序:时钟线SCL高电平下:SDA由高变低代表启动信号,开始发送数据;SCL高电平时,数据稳定,数据可以被读走,开始进行读操作,SCL低电平时,数据发生改变࿱…...
BP神经网络的反向传播算法
BP神经网络(Backpropagation Neural Network)是一种常用的多层前馈神经网络,通过反向传播算法进行训练。反向传播算法的核心思想是通过计算损失函数对每个权重的偏导数,从而调整权重,使得网络的预测输出与真实输出之间…...
CS·GO搬砖流程详细版
说简单点,就是Steam买了然后BUFF上卖,或许大家都知道这点,但就是一些操作和细节问题没那么明白。我相信,你看完这篇文章以后,至少会有新的认知。 好吧,废话少说,直接上实操! 首先准…...
ElasticSearch系列(一)
一.了解ES、倒排索引、es的一些概念、安装es、kibana 二.DSL;索引库操作 三.Java RestClient:索引库操作 一、了解ES、倒排索引、es的一些概念、安装es、kibana kibana、logstash、beats Elasticserach 存储,计算 ,搜索数据 –…...
vue字符串的数字比较大小有问题
问题代码 this.money 9999 //支付金额this.balance 678 //余额if (this.money > this.balance) {this.$message(余额不足,请更换支付方式);}问题原因 this.money和 this.balance不是数值类型而是字符串类型 解决方案 使用parseFloat将变量转换成统一的类…...
Java:缓存:LinkedHashMap实现Lru
文章目录 Lru源码分析 LinkedHashMap维护一个LinkedHashMapEntry<K,V>的双向链表对LinkedHashMap的增删查操作,也会对链表进行相同的操作并改变链表的链接顺序小结使用方法应用总结Lru Least Recently Used,…...
Jetpack Compose 学习笔记(一)—— 快速上手
本篇主要是对 Jetpack Compose 有一个宏观上的了解。 1、Jetpack Compose 是什么与优势 Jetpack Compose 是用于构建原生 Android 界面的新工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,可以帮助您简化并加快 Android 界面开发。 Compose 的优势&am…...
【大模型】7 天 AI 大模型学习
7 天 AI 大模型学习 Day 2 今天是 7 天AI 大模型学习的第二天 😄,今天我将会学习 Transformer 、Encoder-based and Decoder-Based LLMs 等 。如果有感兴趣的,就和我一起开始吧 ~ 课程链接 :2025年快速吃透AI大模型&am…...
JVM对象创建过程
1 类加载检查 jvm通过new指令开始创建对象jvm执行new指令时,首先通过指令参数从常量池中取到需要创建的类名检查该类是否被加载,解析,和初始化过如果没有,则执行类的加载过程new指令对应到java语言具体的操作为 new 关键字创建对象…...
OSPF - SPF算法简述
SPF全称最短路径树算法,相信学过数据结构朋友应该看起来很熟悉 在一个区域内的路由器都会产生描述自己网络连接信息的LSA,包括两种信息,有路由信息和拓扑信息,简单的来说拓扑信息就是我连着谁,路由信息就是链路的地址…...
[实用指南]如何将视频从iPhone传输到iPad
概括 将视频从 iPhone 传输到 iPad 时遇到问题?您可能知道一种方法,但不知道如何操作。此外,您要传输的视频越大,完成任务就越困难。那么如何将视频从 iPhone 传输到 iPad,特别是当您需要发送大视频文件时?…...
如何二次封装组件(vue3版本)
在开发 Vue 项目中我们一般使用第三方组件库进行开发,如 Element-Plus, 但是这些组件库提供的组件并不一定满足我们的需求,这时我们可以通过对组件库的组件进行二次封装,来满足我们特殊的需求。 对于封装组件有一个大原则就是我们应该尽量保…...
基于XGBoost算法的集成学习
目录 一、XGBoost原理1.1 提升方法(Boosting)1.2 提升决策树 (BDT)1.3 梯度提升决策树 (GBDT)1.4 极限梯度提升(XGBoost)1.4.1 XGBoost改进1.4.2 XGBoostcsklearn实现1.4.3 XGBoost回…...
数据库系列之分布式数据库下误删表怎么恢复?
数据的完整性是数据库可用性的基本功能,在实际应用数据库变更操作过程中可能因为误操作导致误删表或者truncate操作影响业务的正常访问。本文介绍了分布式数据库中在误删表场景下的数据恢复方案,并进行了对比。 1、数据库误删表恢复方案 应用数据的完整…...
Beamer-LaTeX学习(教程批注版)【1】
该文档总体由beamer-latex的教程而来,由耳东小白以自身学习路径整理。因其中要点基本按照教程的顺序和结构整理,故而不能称之为完全原创,但也不是翻译,更不是抄袭,是个人自学笔记和批注,其中添加了小白个人…...
数据挖掘——关联规则挖掘
数据挖掘——关联数据挖掘 关联数据挖掘关联规则关联规则挖掘问题:具体挖掘过程Apriori 产生关联规则 关联数据挖掘 关联分析用于发现隐藏在大型数据集中的令人感兴趣的联系,所发现的模式通常用关联规则或频繁项集的形式表示。 关联规则反映一个事物与…...
六种主流服务器的选择与使用
网络的运行离不开各种服务器,它们各司其职,为我们提供稳定的网络服务。本文带大家了解6种常见服务器类型。 服务器的六大种类 第一种:Web服务器 Web服务器是互联网的核心。当你打开一个网站,比如百度或淘宝,浏览器会…...
springboot3 redis 常用操作工具类
在 Spring Boot 3 中,操作 Redis 通常使用 Spring Data Redis 提供的工具类,如 RedisTemplate 和 StringRedisTemplate。以下是一个详细的 Redis 操作工具类的实现,涵盖了常用功能。 完整的 Redis 工具类 以下工具类可以实现基本的 Redis 操…...
OJ随机链表的复制题目分析
题目内容: 138. 随机链表的复制 - 力扣(LeetCode) 分析: 这道题目,第一眼感觉非常乱,这是正常的,但是我们经过仔细分析示例明白后,其实也并不是那么难。现在让我们一起来分析分析…...
如何不修改模型参数来强化大语言模型 (LLM) 能力?
前言 如果你对这篇文章感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 大语言模型 (Large Language Model, LLM, e.g. ChatGPT) 的参数量少则几十亿,多则上千亿,对其的训…...
Win11+WLS Ubuntu 鸿蒙开发环境搭建(二)
参考文章 penHarmony南向开发笔记(一)开发环境搭建 OpenHarmony(鸿蒙南向开发)——标准系统移植指南(一) OpenHarmony(鸿蒙南向开发)——小型系统芯片移植指南(二&…...
Qemu配置QXL显卡支持分辨率
默认情况下,创建的vm的视频RAM限制为16MB。在win操作系统中分辨率最高就只能调到1024x768。 <video><model typecirrus vram16384 heads1 primaryyes/><address typepci domain0x0000 bus0x00 slot0x02 function0x0/> </video>单单修改ram…...
Hack The Box-Starting Point系列Three
答案 How many TCP ports are open?(靶机开了几个TCP端口) 2What is the domain of the email address provided in the “Contact” section of the website?(网站的“CONTACT”部分提供的电子邮件地址的域是什么?)…...
人工智能-Python多任务编程-进程、线程
多任务的实现方式 多进程 多线程 1 多任务的两种表现形式 并发: 在一段时间内交替去执行多个任务(任务数大于CPU核心数)并行: 在一段时间内真正的同时一起执行多个任务(任务数小于等于CPU核心数) 2 进程 进程(Proc…...
智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之9 重新开始 之2
本文要点 对程序设计而言:前者基于一个自上而下的 分类体系--(生物遗传基因),后者者需要一个收集差异的自下而上的差异继承路径--(系统继承源流) 就是 广义和狭义 分类学。 共性对齐 和 差异收集 正是两者…...
Postman[7] 内置动态参数及自定义的动态参数
postman 内置动态参数和自定义的动态参数 1.内置动态参数 格式:{{$参数名}} 1.1时间戳 {{$timestamp}} //生成当前时间的时间戳 1.2随机整数 {{$randomint}} //生成0-1000之间的随机数 1.3GUID字符串 {{$guid}} //生成随机GUID字符串 2.自定义动态参数 格式…...
04-c++类和对象(下)
一、友元 前面学习的类中,只能通过该类的公共方法访问私有数据。而如果将某个函数设置为类的友元,那么这个函数就可以直接访问该类的私有数据,破坏了类的封装性,只在某些特定的情况下使用。 友元的分类:普通全局函数…...
《解密奖励函数:引导智能体走向最优策略》
在强化学习领域,奖励函数是核心要素,它决定了智能体如何学习和决策。设计一个恰当的奖励函数,能让智能体在复杂环境中不断探索、优化,最终实现最优策略。 奖励函数的重要性 奖励函数就像是一个引导者,它告诉智能体什…...
AF3 AtomAttentionEncoder类的init_pair_repr方法解读
AlphaFold3 的 AtomAttentionEncoder 类中,init_pair_repr 方法方法负责为原子之间的关系计算成对表示(pair representation),这是原子转变器(atom transformer)模型的关键组成部分,直接影响对蛋白质/分子相互作用的建模。 init_pair_repr源代码: def init_pair_repr(…...
VScode 格式化代码空格记录
点击 -> “文件” -> “首选项" -> “设置” -> 按下图操作: 怎么格式化代码空格,先看下: 保存代码后,这代码自动格式化发,如下图: 你可以试试看就即可...
28.Marshal.PtrToStringAnsi C#例子
//怎么说呢,这个代码Marshal的英文意思有将军,控制等等, //我的理解是类似于console控制台。 //然后后面这个Ansi是一种ASCII的扩展,还有其他编码方式可选 就是一个把后面的指针转化为字符串的一个代码 这是用法…...
Git的使用流程(详细教程)
目录 01.Git是什么? 1.1 Git简介 1.2 SVN与Git的最主要的区别 1.3 GIt主要特点 02.Git是干什么的? 2.1.Git概念汇总 2.2 工作区/暂存区/仓库 2.3 Git使用流程 03.Git的安装配置 3.1 Git的配置文件 3.2 配置-初始化用户 3.3 Git可视化…...
第R3周:RNN-心脏病预测
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 文章目录 一、前言二、代码流程1、导入包,设置GPU2、导入数据3、数据处理4、构建RNN模型5、编译模型6、模型训练7、模型评估 电脑环境:…...
clickhouse Cannot execute replicated DDL query, maximum retries exceeded报错解决
报错信息 在clickhouse中执行DDL命令对表进行改动时,出现报错Cannot execute replicated DDL query, maximum retries exceeded 解决方案 一、官方解决方案 官方说这是一个特定版本的bug,但是实际我自己用的22.9.34版本,也存在这个问题&a…...
【时时三省】(C语言基础)常见的动态内存错误
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 对NULL指针的解引用操作 示例: malloc申请空间的时候它可能会失败 比如我申请一块非常大的空间 那么空间可能就会开辟失败 正常的话要写一个if(p=&#x…...
【JVM】总结篇-字节码篇
字节码篇 Java虚拟机的生命周期 JVM的组成 Java虚拟机的体系结构 什么是Java虚拟机 虚拟机:指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统 ,是物理机的软件实现。常用的虚拟机有VMWare,Visual Box&…...
二十三种设计模式-抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供了一种方式,用于创建一系列相关或相互依赖的对象,而不需要指定它们具体的类。这种模式主要用于系统需要独立于其产品的创建逻辑时,并且…...
【docker】Dockerfile 中使用宿主机代理的方式
在Dockerfile中配置代理主要有这几种方式,让我系统地整理一下: 构建参数方式(BUILD ARG) # 方式1:在Dockerfile顶部定义 ARG HTTP_PROXYhttp://proxy:7890 ARG HTTPS_PROXYhttp://proxy:7890# 方式2:在构…...
现代无线通信接收机架构:超外差、零中频与低中频的比较分析
写在前面:本博客是对三种接收机架构的学习笔记,仅供个人学习记录使用。内容主要是上网查阅的资料,以及个人的一些理解。如有错误的地方请指出! 文章目录 一、通信机基本架构 1、射频发射级的基本组成及完成功能2、射频接收级的基…...
CSS 图片廊:网页设计的艺术与技巧
CSS 图片廊:网页设计的艺术与技巧 引言 在网页设计中,图片廊是一个重要的组成部分,它能够以视觉吸引的方式展示图片集合,增强用户的浏览体验。CSS(层叠样式表)作为网页设计的主要语言之一,提供…...
Gemini和ChatGPT全面对比分析,有什么区别和优势?
当 AI 聊天机器人首次出现时,每个人都在竞相发布自己的足够好的第一版 AI 聊天机器人,很容易在 Gemini 与 ChatGPT 等应用程序之间进行比较。但随着 Google 和 OpenAI 不断添加新功能、模型和访问其聊天机器人的方式,差异变得不那么明显。 现…...