【C++11(下)】—— 我与C++的不解之缘(三十二)

news2025/4/9 10:02:29

前言

随着 C++11 的引入,现代 C++ 语言在语法层面上变得更加灵活、简洁。其中最受欢迎的新特性之一就是 lambda 表达式(Lambda Expression),它让我们可以在函数内部直接定义匿名函数。配合 std::function 包装器 使用,可以大大提高代码的表达力与可维护性。

一、lambda 表达式基础语法

lambda表达式本质上就是一个匿名函数对象,与普通函数不同的是,它可以定义在函数内部;

一般情况下我们是使用auto或者模版参数定义的对象去接受lambda对象。

lambda表达式本质上是一个可调用对象(函数对象),其语法格式如下:

[capture](parameter_list) -> return_type {
    function_body
};

各部分含义如下:

部分含义
[]捕捉列表(capture list)
()参数列表(与函数类似)
-> return_type返回类型(可省略,自动推导)
{}函数体
int main()
{
    auto add = [](int a, int b) -> int {
    	return a + b;
	};
	std::cout << add(2, 3); // 输出:5
}

也可以省略 -> int,由编译器自动推导:

int main()
{
    auto add = [](int a, int b){
    	return a + b;
	};
	std::cout << add(2, 3); // 输出:5
}

这里呢,参数列表,如果不需要传参,可以省略,()也可以省略;返回值类型可以省略,让编译器自行推导。

而参数列表和函数体,就算为空,参数列表的[]和函数体的{}也不能省略。

捕获列表

捕获列表决定了 Lambda 表达式如何访问其所在作用域的变量

捕获方式语法说明
值捕捉[x]捕获变量 x 的当前值(拷贝)
引用捕捉[&x]捕获变量 x 的引用
隐式值捕捉[=]lambda使用了哪些变量,编译器就会对哪些变量进行值捕捉
隐式引用捕捉[&]lambda使用了哪些变量,编译器就会对哪些变量进行引用捕捉
混合捕捉[=, &y]除 y 外的所有变量为值捕获,y 为引用捕获
混合捕捉[&, x]x外的所有变量为引用捕捉,x为值捕捉
int main()
{
    int x = 10, y = 20;

	auto f1 = [=]() { return x + y; };   // 值捕获
	auto f2 = [&]() { x += y; };         // 引用捕获,修改外部变量
	auto f3 = [x, &y]() { y += x; };     // 混合捕获
    return 0;
}

这里lambda表达式如果在函数局部域中,它可以捕捉lambda位置之前定义的变量,但是不能捕捉静态局部变量和全局变量(静态局部变量和全局变量也不需要捕捉,lambda表达式中也可以直接使用)。

如果lambda定义在全局,那捕捉列表必须为空

值捕获的变量在 Lambda 中是“只读”的,不能修改,除非加上 mutable

mutable 关键字

默认情况下,lambda捕捉列表的值是被const修饰的,值捕获的变量不能在 Lambda 中被修改。要想修改值捕获的副本,可以使用 mutable

int main()
{
    int a = 5;
	auto f = [a]() mutable {
    	a += 10; // 修改的是a的拷贝,不影响外部 a
    	cout << a << endl;
	};
	f();            //  15
	std::cout << a << endl; //  5
    return 0;
}

lambda 的实际应用场景

与 STL 算法结合

std::vector<int> vec = {1, 2, 3, 4, 5};
std::for_each(vec.begin(), vec.end(), [](int x) {
    std::cout << x << " ";
});

条件查找

auto it = std::find_if(vec.begin(), vec.end(), [](int x) {
    return x > 3;
});
if (it != vec.end()) std::cout << *it; // 输出 4

排序自定义规则

std::sort(vec.begin(), vec.end(), [](int a, int b) {
    return a > b; // 降序
});

lambda的原理

lambda的原理和范围for非常相似,编译之后从汇编的角度来看,我们就要发现根本就没有lambda范围for

范围for的底层是迭代器,而lambda的底层是仿函数对象,简单来说我们写了一个lambda,编译器就会生成一对应的仿函数。

而仿函数的类名是编译器按照一定的规则生产的,保证不同的lambda生成的仿函数不同。

  • lambda的参数/返回值/函数体就是仿函数operator()的参数/返回类型/函数体
  • lambda捕捉列表的本质是生成的仿函数的成员变量。

简单来说就是捕捉列表的量都是lambda类构造函数的实参;

这样就很好解释值捕捉引用捕捉了:

  • 值捕捉:lambda生成的仿函数的成员就是对捕捉变量的值拷贝。
  • 引用捕捉:lambda生成的仿函数的成员就是对捕捉变量的引用。

而也支持隐式捕捉,这个就是编译器看需要用哪些对象,就传哪些对象(用多少捕捉多少)。

在这里插入图片描述

二、包装器

function

function是一个类模板,也是一个包装器。std::function的实例化对象可以包装存储其它可以调用的对象:包括函数指针仿函数lambdabind表达式;

其中存储的对象被称为std::function的目标;如果std::function不含目标,那它为空(调用空目标导致抛出std::bad_function_call异常。

template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

以上是 function 的原型,他被定义头⽂件中

#include <functional>
int main()
{
	function<int(int, int)> func;
	func = [](int a, int b) { return a + b; };
	cout << func(3, 4) << endl; // 输出 7
    return 0;
}

function 的优势在于统一函数接口、做函数回调或作为参数传递。

函数指针、仿函数、lambda这些可以调用的对象的类型各不相同,std::function的优势就在于统一类型;

只要它们返回值,参数都相同,function就能对它们进行包装;这样在很多的地方就可以声明这些可调用对象的类型;

int fun(int a, int b)
{
	return a + b;
}
struct Fun
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};
class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return (a + b) * _n;
	}
private:
	int _n;
};
int main()
{
	function<int(int, int)> f1 = fun;//函数指针
	function<int(int, int)> f2 = Fun();//仿函数
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };//lambda
	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;

	//包装静态成员,需要指定类域并且使用&
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 1) << endl;

	//包装非静态成员函数时
	//这里还有一个隐藏的this指针,所以使用function包装后需要传对象或者对象的指针过去才能进行调用
	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus pd;
	cout << f5(&pd, 1.1, 1.1) << endl;
	function<double(Plus, double, double)> f6 = &Plus::plusd;
	cout << f6(pd, 1.1, 1.1) << endl;
	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(move(pd), 1.1, 1.1) << endl;
	cout << f7(Plus(), 1.1, 1.1) << endl;
	return 0;
}

这里再来看一道可以使用function包装优化的题目

150. 逆波兰表达式求值 - 力扣(LeetCode)

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(auto& e:tokens)
        {
            if(e == "+"||e == "-" || e == "*" || e == "/")
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                switch(e[0])
                {
                    case '+':
                        st.push(left + right);
                        break;
                    case '-':
                        st.push(left - right);
                        break;
                    case '*':
                        st.push(left * right);
                        break;
                    case '/':
                        st.push(left / right);
                        break;
                }
            }
            else
            {
                st.push(stoi(e));
            }
        }
        return st.top();
    }
};

这道题,向上述这样写,特别难受好吧,我们可以使用function进行优化:

我们知道+-*/运算它返回值和参数类型都是相同的,那我们不妨将其包装起来;

然后使用map存储运算符和对应的函数/调用对象

这样直接使用map[]就可以访问到要调用的函数/对象。

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string,function<int(int,int)>> mp = {
            {"+",[](int x,int y){return x+y;}},
            {"-",[](int x,int y){return x-y;}},
            {"*",[](int x,int y){return x*y;}},
            {"/",[](int x,int y){return x/y;}}
        };
        for(auto& e:tokens)
        {
            if(mp.count(e))//mp中存在就代表是操作符
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(mp[e](left,right));
            }
            else
                st.push(stoi(e));
        }
        return st.top();
    }
};

这样我们代码看起来简洁了好多,用起来也很方便;

如果再多几个运算符,我们只需要在mp再新增即可。

从这个角度来看:lambda算是统一了那些可调用对象的类型,这样对于可调用对象(函数指针仿函数lambda),只要参数和返回值相同,那我们就可以使用function包装起来,方便调用。

bind 与占位符

simple(1)
template <class Fn, class... Args>
	bind (Fn&& fn, Args&&... args);
with return type (2)
template <class Ret, class Fn, class... Args>
	bind (Fn&& fn, Args&&... args);
  • bind是一个函数模版,它也是一个可调用对象的包装器;简单来说它就是一个函数适配器,可以对接受的Fn可调用对象进行处理后返回一个可调用对象。
  • bind可以用来调整参数个数和参数的顺序。
  • bind也在这个头文件中。
int Fun(int a, int b)
{
	return (a - b) * 10;
}
int Func(int a, int b, int c)
{
	return (a - b - c) * 10;
}
int main()
{
	//这里_1始终指接受第一个实参
	//_2指接受第二个实参
	auto fun1 = bind(Fun, _1, _2);
	cout << fun1(10, 5) << endl;//fun1(10,5) -> Fun(10,5)
	auto Fun2 = bind(Fun, _2, _1);
	cout << Fun2(10, 5) << endl;//fun2(10,5) -> Fun(5,10)
	auto fun3 = bind(Fun, 100, _1);
	cout << fun3(5) << endl;//fun3(5) -> Fun(100,5)
	auto fun4 = bind(Fun, _1, 100);
	cout << fun4(5) << endl;//fun4(5) -> Fun(5,100)
	auto fun5 = bind(Func, 100, _1, _2);
	cout << fun5(5, 1) << endl;//fun5(5,1) -> Func(100,5,1)
	auto fun6 = bind(Func, _1, 100, _2);
	cout << fun6(5, 1) << endl;//fun6(5,1) -> Func(5,100,1)
	auto fun7 = bind(Func, _1, _2, 100);
	cout << fun7(5, 1) << endl;//fun7(5,1) -> Func(5,1,100);
	return 0;
}

调用bindauto newCallable = bind(callable, arg_list)(这里newCallable本身就是一个可调用对象,arg_list是参数列表,对应给定的callable的参数(也是可调用对象)

这样我们调用newCallablenewCallable就会调用callable,并传给它arg_list中的参数。

当我们使用function去包装类的非静态成员函数时,我们在调用时总是需要传该类型的对象或者该类型对象的指针,来完成调用;

这样的设计好难看,我每一次调用还要创建一个该类型的对象,那我还不如直接去调用呢

	function<double(Plus&&, double, double)> f1 = &Plus::plusd;
	Plus pd;
	cout << f1(move(pd), 1.1, 1.1) << endl;
	cout << f1(Plus(), 1.1, 1.1) << endl;

bind这个绑定,我们可以同来绑定一些固定的参数;

就比如这里需要传递该类类型的对象或者该类型对象的指针,我们使用bind绑定,直接锁死这个参数,那这样在调用时就不用显示传递了。

	function<double(double, double)> f2 = bind(&Plus::plusd, Plus(), _1, _2);
	cout << f2(1.1, 1.1) << endl;

到这里本篇文章内容就结束了
感谢各位的支持

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows 10/11系统优化工具

家庭或工作电脑使用时间久了&#xff0c;会出现各种各样问题&#xff0c;今天给大家推荐一款专为Windows 10/11系统设计的全能优化工具&#xff0c;该软件集成了超过40项专业级实用程序&#xff0c;可针对系统性能进行深度优化、精准调校、全面清理、加速响应及故障修复。通过系…

浅谈在HTTP中GET与POST的区别

从 HTTP 报文来看&#xff1a; GET请求方式将请求信息放在 URL 后面&#xff0c;请求信息和 URL 之间以 &#xff1f;隔开&#xff0c;请求信息的格式为键值对&#xff0c;这种请求方式将请求信息直接暴露在 URL 中&#xff0c;安全性比较低。另外从报文结构上来看&#xff0c…

LightRAG实战:轻松构建知识图谱,破解传统RAG多跳推理难题

作者&#xff1a;后端小肥肠 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 2025防失业预警&#xff1a;不会用DeepSeek-RAG建知识库的人正在被淘汰_deepseek-embedding-CSDN博客 从PDF到精准答案&#xff1a;Coze…

C++多线程编码二

1.lock和try_lock lock是一个函数模板&#xff0c;可以支持多个锁对象同时锁定同一个&#xff0c;如果其中一个锁对象没有锁住&#xff0c;lock函数会把已经锁定的对象解锁并进入阻塞&#xff0c;直到多个锁锁定一个对象。 try_lock也是一个函数模板&#xff0c;尝试对多个锁…

垃圾回收——三色标记法(golang使用)

三色标记法(tricolor mark-and-sweep algorithm)是传统 Mark-Sweep 的一个改进&#xff0c;它是一个并发的 GC 算法&#xff0c;在Golang中被用作垃圾回收的算法&#xff0c;但是也会有一个缺陷&#xff0c;可能程序中的垃圾产生的速度会大于垃圾收集的速度&#xff0c;这样会导…

Windows环境下开发pyspark程序

Windows环境下开发pyspark程序 一、环境准备 1.1. Anaconda/Miniconda&#xff08;Python环境&#xff09; 如果不怕包的版本管理混乱&#xff0c;可以直接使用已有的Python环境。 需要安装anaconda/miniconda&#xff08;python3.8版本以上&#xff09;&#xff1a;Anaconda…

SSM婚纱摄影网的设计

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式咨询本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 SS…

1110+款专业网站应用程序UI界面设计矢量图标figma格式素材 Icon System | 1,100+ Icons Easily Customize

1110款专业网站应用程序UI界面设计矢量图标figma格式素材 Icon System | 1,100 Icons Easily Customize 产品特点 — 24 x 24 px 网格大小 — 2px 线条描边 — 所有形状都是基于矢量的 — 平滑和圆角 — 易于更改颜色 类别 &#x1f6a8; 警报和反馈 ⬆️ 箭头 &…

Llama 4 家族:原生多模态 AI 创新的新时代开启

0 要点总结 Meta发布 Llama 4 系列的首批模型&#xff0c;帮用户打造更个性化多模态体验Llama 4 Scout 是有 170 亿激活参数、16 个专家模块的模型&#xff0c;同类中全球最强多模态模型&#xff0c;性能超越以往所有 Llama 系列模型&#xff0c;能在一张 NVIDIA H100 GPU 上运…

正则表达式(Regular Expression,简称 Regex)

一、5w2h&#xff08;七问法&#xff09;分析正则表达式 是的&#xff0c;5W2H 完全可以应用于研究 正则表达式&#xff08;Regular Expressions&#xff09;。通过回答 5W2H 的七个问题&#xff0c;我们可以全面理解正则表达式的定义、用途、使用方法、适用场景等&#xff0c…

JMeter脚本录制(火狐)

录制前准备&#xff1a; 电脑&#xff1a; 1、将JMeter证书导入&#xff0c;&#xff08;bin目录下有一个证书&#xff0c;需要安装这个证书到电脑中&#xff09; 2、按winr&#xff0c;输入certmgr.msc&#xff0c;打开证书&#xff0c;点击下一步&#xff0c;输入JMeter证书…

基于SpringBoot的“高校社团管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“高校社团管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 总体功能结构图 局部E-R图 系统首页页面 用户…

C# Winform 入门(3)之尺寸同比例缩放

放大前 放大后 1.定义当前窗体的宽度和高度 private float x;//定义当前窗体的宽度private float y;//定义当前窗台的高度 2.接收当前窗体的尺寸大小 x this.Width;//存储原始宽度ythis.Height;//存储原始高度setTag(this);//为控件设置 Tag 属性 3.声明方法&#xff0c;获…

infinityfree最新免费建站详细教程_无需备案_5G空间_无限流量_免费域名_免费SSL

一、明确目标—是否要使用 1.为什么选择InfinityFree&#xff1f; 对于初学者、学生或只是想尝试网站搭建的个人用户来说&#xff0c;InfinityFree提供了一个绝佳的免费解决方案。这个国外免费的虚拟主机服务提供&#xff1a; 5GB存储空间 - 足以存放个人博客、作品集或小型…

打造高效英文单词记忆系统:基于Python的实现与分析

在当今全球化的世界中,掌握一门外语已成为必不可少的技能。对于许多学习者来说,记忆大量的英文单词是一个漫长而艰难的过程。为了提高学习效率,我们开发了一个基于Python的英文单词记忆系统。这个系统结合了数据管理、复习计划、学习统计和测试练习等多个模块,旨在为用户提…

node_modules\deasync: Command failed.

运行&#xff1a;“yarn install” 时报错 PS D:\WebPro\hainan-mini-program> yarn install yarn install v1.22.19 [1/4] Resolving packages... [2/4] Fetching packages... [3/4] Linking dependencies... warning " > babel-loader8.2.2" has un…

游戏引擎学习第206天

回顾并为当天的工作定下目标 接着回顾了前一天的进展。之前我们做了一些调试功能&#xff0c;并且已经完成了一些基础的工作&#xff0c;但是还有一些功能需要继续完善。其中一个目标是能够展示实体数据&#xff0c;以便在开发游戏逻辑系统时&#xff0c;可以清晰地查看和检查…

Zapier MCP:重塑跨应用自动化协作的技术实践

引言&#xff1a;数字化协作的痛点与突破 在当今多工具协同的工作环境中&#xff0c;开发者与办公人员常常面临数据孤岛、重复操作等效率瓶颈。Zapier推出的MCP&#xff08;Model Context Protocol&#xff09;协议通过标准化数据交互框架&#xff0c;为跨应用自动化提供了新的…

蓝桥云客--破译密码

5.破译密码【算法赛】 - 蓝桥云课 问题描述 在近期举办的蓝桥杯竞赛中&#xff0c;诞生了一场激动人心的双人破译挑战。比赛的主办方准备了N块神秘的密码芯片&#xff0c;参赛队伍需要在这场智力竞赛中展示团队合作的默契与效率。每个队伍需选出一位破译者与一位传输者&#…

React-Diffing算法和key的作用

1.验证Diffing算法 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </he…