【C++】lambda表达式的理解与运用(C++11新特性)

75e194dacf184b278fe6cf99c1d32546.jpeg

🌈 个人主页:谁在夜里看海.

🔥 个人专栏:《C++系列》《Linux系列》

⛰️ 天高地阔,欲往观之。

d047c7b1ef574257b8397fe5cc5c290b.gif

目录

        前言

        C++11之前的例子

        一、lambda的语法

        lambda函数示例:

        二、lambda的捕获列表

        1.传值捕获

        mutable修饰 

        2.传引用捕获

        3.当前对象this捕获

        4.带初始化的捕获

        5.全捕获

        按值全捕获 [=]:

        按引用全捕获 [&]:

        三、等价于仿函数


前言

C++11引入了 lambda 函数这个概念,用来快速地构建一个闭包,闭包是函数式编程的一个概念,在函数式编程中使用闭包来实现一些高阶函数。

闭包是指一个匿名函数以及它所捕获的上下文环境的组合。闭包允许你在函数中捕获并使用周围作用域的变量,从而实现更灵活的函数行为。

使用 lambda 可以带来以下好处:

1.通过创建lambda对象,可以快速的构建比如谓词函数这种短小并局部使用的函数。这样可以使这部分代码局部化,不污染全局命名空间

2.通过lambda对象可以快速地创建一个可调用对象。和传统的可调用对象的场景方式相比(如仿函数),省略了编写类的构造函数以及调用操作符重载等相关代码

这样说可能不是很理解,下面列举一个例子,让我们感受一下:

C++11之前的例子

在C++11之前,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。

#include <algorithm>

int main()
{
    int array[] = {4,1,8,5,3,7,0,9,2,6};
    // 默认按照小于比较,排出来结果是升序
    std::sort(array, array+sizeof(array)/sizeof(array[0]));
    // 如果需要降序,需要改变元素的比较规则
    std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

struct Goods
{
	string _name;  // 名字
	double _price; // 价格
	int _evaluate; // 评价
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}

每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这都给我们带来了极大的不便,于是C++11就出现了lambda表达式,下面是使用lambda表达式实现上述比较过程的代码:

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
   3 }, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate; });
}

如此一来,是不是与上面讲到的优点对应上了:

①:快速构建了不同比较逻辑的函数,使代码局部化;

②:快速创建了可调用对象,省略了类的构造函数等的代码编写

下面我们来具体聊聊 lambda 的使用方法:

一、lambda的语法

lambda 表达式书写格式如下:

[ capture-list ] ( params ) mutable -> return-type { body }

各部分说明:

[ capture-list ]捕捉列表,能够捕捉上下文中的变量供lambda函数内部使用
( params ) 参数列表,与普通函数一致,没有参数可省略
 mutable修饰符,lambda函数默认为const属性,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略
-> return-type返回值类型,可以省略(由编译器自行推导)
{ body }函数体,可以使用自身参数和捕获变量

注意:

在lambda表达式中,参数列表和返回值类型都可以省略,而捕捉列表和函数体不可省略,但可以为空。因此C++11最简单的lambda表达式为:[]{}; 该表达式没有任何含义。

lambda函数示例:

int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};

	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {return a + 3; };

	// 省略了返回值类型,无返回值类型
	auto fun1 = [&](int c) {b = a + c; };
	fun1(10);
	cout << a << " " << b << endl;

	// 各部分都很完善的lambda函数
	auto fun2 = [=, &b](int c)->int {return b += a + c; };
	cout << fun2(10) << endl;

	// 复制捕捉x
	int x = 10;
	auto add_x = [x](int a) mutable { x *= 2; return a + x; };
	cout << add_x(10) << endl;
	return 0;
}

通过上述例子我们可以看出,lambda表达式可以理解成无名函数,该函数无法直接调用,如果想直接调用,可以借助auto将其赋值给一个变量。

二、lambda的捕获列表

lambda的捕获方式主要分为两大类:传值捕获和传引用捕获

1.传值捕获

传值捕获时,意味着闭包在创建时会拷贝一份变量的当前值,并在闭包中使用这份拷贝。这种捕获方式使得变量在lambda内变成一个只读变量,不能对其进行修改

20e437029efd4481920cb84b4efeb4f5.png

mutable修饰 

如果想要对传值捕获的变量在lambda函数内部进行修改,可以通过mutable关键字修饰来实现,但是要注意,mutable关键字允许对按值捕获的变量进行内部修改,而且不影响外部的变量:

int main()
{
	int num = 0;
	auto func = [num]() mutable
	{
		return ++num;
	};
	cout << func() << endl; // 此时显示为1
	cout << num << endl; // 显示为0,内部的修改不影响外部
}

2.传引用捕获

按引用捕获会直接引用外部变量,因此在lambda内部对该变量的修改会影响外部变量的值。引用捕获不会创建变量的副本,而是共享相同的内存地址

int main()
{
	int num = 0;
	auto func = [&num]()
	{
		return ++num;
	};
	cout << func() << endl; // 此时显示为1
	cout << num << endl; // 显示为1
}

但是要注意,在lambda表达式没有被调用时,内部代码不会执行,也不会发生相应的修改

int main()
{
	int num = 10;
	auto algo = [&num](int a = 1, int b = 2)
	{
		num = 5;
		return (a + b) * num;
	};
	cout << num << endl; // 此时显示为10,没有发生修改
	return 0;
}

只有调用了lambda表达式,才会执行内部的代码:

int main()
{
	int num = 10;
	auto algo = [&num](int a = 1, int b = 2)
	{
		num = 5;
		return (a + b) * num;
	};
	algo();
	cout << num << endl; // 此时显示为5,发生修改
	return 0;
}

那么当传引用捕获时使用了mutable关键字,会有什么效果呢?mutable关键字失效

mutable关键字的作用是允许修改lambda函数内部的拷贝对象,而引用捕获不涉及拷贝,所以在这种情况下,mutable不会发挥作用,没有实际意义。

3.当前对象this捕获

当前对象捕获 [this] 允许lambda表达式访问器所在类的成员变量和成员函数,它实际上是按引用捕获当前对象指针,因此在lambda内部对成员的任何修改都会直接作用于当前对象本身。

class Counter {
public:
    Counter(int start) : count(start) {}

    void increase(int amount) {
        // 使用 [this] 捕获当前对象
        auto increment = [this, amount]() {
            count += amount; // 访问并修改当前对象的成员变量 count
        };
        increment();  // 调用 Lambda 表达式
    }

    int getCount() const { return count; }

private:
    int count;
};

 4.带初始化的捕获

C++14标准中还引入了带初始化的捕获方式。通过这种方式已进行捕获的时候,可以用捕获到的信息初始化一个新的变量,在函数体内使用这个新的变量。同时,这个捕获方式也是有传值和传引用两种方式:

传值,内部修改不会影响外部

int main()
{
	int num = 10;
	auto algo = [n = num](int a = 1, int b = 2) mutable
	{
		n = 5;
		return (a + b) * n;
	};
	algo();
	cout << num << endl; // 此时显示为10,不发生修改
	return 0;
}

传引用,内部修改会影响外部

int main()
{
	int num = 10;
	auto algo = [&n = num](int a = 1, int b = 2)
	{
		n = 5;
		return (a + b) * n;
	};
	algo();
	cout << num << endl; // 此时显示为5,发生修改
	return 0;
}

5.全捕获

全捕获可以通过 [=](按值捕获所有外部变量)或者 [&](按引用捕获所以外部变量)来实现。全捕获非常地便捷,不需要对外部变量一一列举。

按值全捕获 [=]:

#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int y = 20;

    auto calculate = [=]() {
        // 按值捕获,内部可以访问 x 和 y,但无法修改它们的值
        return x + y;
    };

    cout << "Sum: " << calculate() << endl; // 输出 30
    // 由于是按值捕获,x 和 y 的值在外部保持不变
    return 0;
}

按引用全捕获 [&]:

#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int y = 20;

    auto increment = [&]() {
        // 按引用捕获,允许修改 x 和 y 的值
        x += 5;
        y += 5;
    };

    increment();  // 调用 Lambda,x 和 y 的值将被修改
    cout << "x: " << x << ", y: " << y << endl; // 输出 x: 15, y: 25

    return 0;
}

三、等价于仿函数

lambda 函数对象其实是C++11标准实现的语法糖,其编译器的处理也可以等价于仿函数:根据lambda函数的实现,构造一个等价的仿函数,之后编译处理这个仿函数。        

639b3755f89a41d5a7a621d9a3cc435c.png两者是等价的。


以上就是对C++11lambda表达式的介绍与个人理解,欢迎指正~ 

码文不易,还请多多关注支持,这是我持续创作的最大动力!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

K8s资源对象监控之kube-state-metrics详解(Detailed Explanation of Kube State Metrics)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

儿童安全座椅行业全面深入分析

儿童安全座椅就是一种专为不同体重&#xff08;或年龄段&#xff09;的儿童设计&#xff0c;将孩子束缚在安全座椅内&#xff0c;能有效提高儿童乘车安全的座椅。欧洲强制性执行标准ECE R44/03的定义是&#xff1a;能够固定到机动车辆上&#xff0c;带有ISOFIX接口、LATCH接口的…

传输协议设计与牧村摆动(Makimoto‘s Wave)

有一条活鱼和一条死鱼&#xff0c;你准备怎么做&#xff0c;你会将活鱼红烧或将死鱼清蒸吗&#xff1f;好的食材只需要最简单的烹饪&#xff0c;不好的食材才需要花活儿。 我此前的文字几乎都在阐述一个观点&#xff0c;广域网就是那条死鱼&#xff0c;数据中心则是那条活鱼。…

【极限编程(XP)】

极限编程&#xff08;XP&#xff09;简介 定义与核心价值观&#xff1a;极限编程&#xff08;Extreme Programming&#xff0c;XP&#xff09;是一种轻量级、敏捷的软件开发方法。它强调团队合作、客户参与、持续测试和快速反馈等价值观&#xff0c;旨在提高软件开发的效率和质…

Autosar CP 内存抽象接口MemIf规范导读

一、MemIf规范概述 内存抽象接口(Memory Abstraction Interface,简称MemIf)是AUTOSAR架构中用于访问和管理非易失性随机存取存储器(NVRAM)的重要组成部分。以下是对MemIf的详细概述: 1. 功能和目的 MemIf的主要功能是为上层软件(如NVRAM管理器)提供统一的接口,以便…

动态规划 —— dp 问题-粉刷房子

1. 剑指offer —— 粉刷房子 题目链接&#xff1a; LCR 091. 粉刷房子 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/JEj789/description/ 2. 题目解析 根据上图可以得到costs横坐标&#xff08;行&#xff09;是房子的号数&#xff0c;红色的下标是0&…

将vscode的终端改为cygwin terminal

现在终端是默认的power shell&#xff0c;没有显示cygwin 接下来选择默认配置文件 找到cygwin的选项即可 然后提示可能不安全什么的&#xff0c;点是&#xff0c;就有了

Scala的包及其导入

//1.单个导入 //import com.sala02.A //import com.sala02.B//2.导入多个类 //import com.sala02.{A,B}//3.导入一个包下的所有类&#xff1a;包名._ //import com.sala02._//4.导入一个包中的类&#xff0c;给他改个名字 //格式&#xff1a;import 包名.{原来的名字 > 新名…

SAP B1 认证考试习题 - 解析版(三)

前一篇&#xff1a;《SAP B1 认证考试习题 - 解析版&#xff08;二&#xff09;》 题目纯享版合集&#xff1a;《SAP B1 认证考试习题 - 纯享版》 五、运费&#xff08;附加费用&#xff09; 57. 以下哪个选项能够影响库存商品的价格 A. 仅为总量级别的附加费用 B. 只为行级…

ZABBIX API获取监控服务器OS层信息

Zabbix 是一款强大的开源监控解决方案,能够通过其 API 接口自动化管理和获取监控数据。在这篇文章中,详细讲解如何通过 Zabbix API 批量获取服务器的系统名称、IP 地址及操作系统版本信息,并将数据保存到 CSV 文件中。本文适合对 Python 编程和 Zabbix 监控系统有一定基础的…

【毫米波雷达(七)】自动驾驶汽车中的精准定位——RTK定位技术

一、什么是RTK&#xff1f; RTK&#xff0c;英文全名叫做Real-time kinematic&#xff0c;也就是实时动态。这是一个简称&#xff0c;全称其实应该是RTK&#xff08;Real-time kinematic&#xff0c;实时动态&#xff09;载波相位差分技术。 二、RTK的组装 如上图所示&#x…

跨域问题以及使用vscode的LiveServer插件跨域访问

目录 现象跨域问题的定义&#xff08;文心一言&#xff09;解决办法同源部署后端代理VS Code LiveServer 现象 以下前端代码部署后&#xff0c;在网页访问时报错&#xff1a;No ‘Access-Control-Allow-Origin’ header is present on the requested resource. $.ajax({url:…

Python基础学习_01

目录 1、注释 2、数字和数学计算 3、变量 4、字符串 5、打印 6、本节总结 1、注释 • 什么是注释&#xff1f; 1&#xff09;注释就是用自然语言向代码阅读者说明代码的功能和意义 • 注释 1&#xff09;单行注释使用 # 为开头&#xff1b;并且不能换行…

C语言复习第9章 字符串/字符/内存函数

目录 一、字符串函数1.1 读取字符串gets函数原型Example 1.2 字符串拷贝strcpy函数原型模拟实现官方源码 1.3 求字符串长度strlen函数原型关于返回值size_与算术转换的一个易错点模拟实现:递归模拟实现:指针-指针模拟实现:暴力官方源码 1.4 字符串追加strcat函数原型注意自己给…

使用Matlab神经网络工具箱

综述 在大数据和人工智能时代&#xff0c;神经网络是一种最为常见的数据分析和拟合工具。本报告以常用分析软件Matlab为例&#xff0c;介绍其中神经网络工具箱使用方法。 Step 1: 打开matlab 安装matlab 2018以上版本后&#xff0c;双击图标打开。 Step 2: 打开神经网络拟合…

ffmpeg视频滤镜:组合两个视频为立体视频- framepack

视频描述 framepack 官方网址 > FFmpeg Filters Documentation 这个滤镜会将两个视频进行组合&#xff0c;有个前提是这两个视频的帧率、分别率必须一样。比如输入的是两个852x480 视频&#xff0c;输出可能是1704*480&#xff08;左右拼接&#xff09;、852*960&#xf…

Spring Security 框架篇-深入了解 Spring Security 的授权核心功能(RBAC 权限模型、自定义异常处理器、校验权限方法)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 权限系统 1.1 引入 1.2 RBAC 权限模型 1.3 数据库设计 2.0 Spring Security 核心功能-授权 2.1 思路分析 2.2 编写 SQL 语句 2.3 将用户权限进行封装 2.4 获取用户…

STM32G0xx使用LL库将Flash页分块方式存储数据实现一次擦除可多次写入

STM32G0xx使用LL库将Flash页分块方式存储数据实现一次擦除可多次写入 参考例程例程说明一、存储到Flash中的数据二、Flash最底层操作(解锁&#xff0c;加锁&#xff0c;擦除&#xff0c;读写)三、从Flash块中读取数据五、测试验证 参考例程 STM32G0xx HAL和LL库Flash读写擦除操…

Spark SQL大数据分析快速上手-DataFrame应用体验

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 本节主要介绍如何使用DataFrame进行编程。 4.1.1 SparkSession 在旧版本中&#xff0c;Spark SQL提供…

QT信号和槽与自定义的信号和槽

QT信号和槽与自定义的信号和槽 1.概述 这篇文章介绍下QT信号和槽的入门知识&#xff0c;通过一个案例介绍如何创建信号和槽&#xff0c;并调用他们。 2.信号和槽使用 下面通过点击按钮关闭窗口的案例介绍如何使用信号和槽。 创建按钮 在widget.cpp文件中创建按钮代码如下 …