c++ lambda

news2024/9/19 11:01:30

Lambda

Lambda 表达式一般用于定义匿名函数,使得代码更加灵活简洁,优点:

  1. 声明式编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或者函数对象。以更直接的方式去写程序,好的可读性和可维护性。
  2. 简洁:不需要额外再写一个函数或者函数对象,避免了代码膨胀和功能分散,让开发者更加集中精力在手边的问题,同时也获取了更高的生产率。
  3. 在需要的时间和地点实现功能闭包,使程序更灵活。

Lambda表达式具体形式:

auto func = [capture] (params) opt -> ret { func_body; };
  • func:Lambda 表达式的名字,作为一个函数使用;
  • capture:捕获列表;
  • params:参数列表,如果没有参数,圆括号()可以省略;
  • opt:函数选项(mutable, noexcept之类);
  • ret:返回值类型,可以不写,让编译器根据返回值自动推导;
  • func_body:函数体。

capture取值:

  • [] //未定义变量.试图在Lambda内使用任何外部变量都是错误的
  • [x, &y] //x 按值捕获, y 按引用捕获.
  • [&] //用到的任何外部变量都隐式按引用捕获
  • [=] //用到的任何外部变量都隐式按值捕获
  • [&, x] //x显式地按值捕获. 其它变量按引用捕获
  • [=, &z] //z按引用捕获. 其它变量按值捕获

捕获本地变量

空的[](Lambda introducer)就不能引用当前范围内的本地变量,只能使用全局变量,或将其他值以参数的形式进行传递。
想要访问一个本地变量,需要对其进行捕获。最简单的方式就是将范围内的所有本地变量都进行捕获,使用[=]就可以。

int a = 0, b = 1;
auto f1 = []{ return a; };               // 错误,没有捕获外部变量
auto f2 = [=]{ return a + b; };          // 正确,捕获所有外部变量,并返回a + b
auto f3 = [=]{ return a++; };            // 错误,a是以复制方式捕获的,无法修改

示例

#include <iostream>
#include <functional>

int main() {
    int i = 111, j = 222, k = 333;
    auto f = [&i, j, k] { return i + j + k; }; 
    i = 1;
    j = 2;
    k = 3;
    std::cout << f() << std::endl;
}

内部原理

编译器为每个Lambda表达式生成唯一闭包。捕获列表将成为闭包中的构造函数的参数,如果将参数按值捕获,那么相应类型的数据成员将在闭包中创建。此外,可以在Lambda表达式的参数中声明变量/对象,它们将成为调用operator()函数的参数。

  • 值捕获
int x = 1; int y = 2;
auto plus = [=] (int a, int b) -> int { return x + y + a + b; };
int c = plus(1, 2);

编译器将翻译为

class LambdaClass {
public:
    LambdaClass(int x, int y)
    : x_(x), y_(y) {}

    int operator () (int a, int b) const {
        return x_ + y_ + a + b;
    }

private:
    int x_;
    int y_;
}

int x = 1; int y = 2;
LambdaClass plus(x, y);
int c = plus(1, 2);
  • 引用捕获
int x = 1; int y = 2;
auto plus = [&] (int a, int b) -> int { x++; return x + y + a + b;};
int c = plus(1, 2);

编译器将翻译为

class LambdaClass {
public:
    LambdaClass(int& x, int& y)
    : x_(x), y_(y) {}

    int operator () (int a, int b) {
        x_++;
        return x_ + y_ + a + b;
    }

private:
    int &x_;
    int &y_;
};

引用捕获变量,和值捕获的方式有3个不同的地方:

  1. 参数引用的方式进行传递;
  2. 引用捕获在函数体修改变量,会直接修改lambda表达式外部的变量;
  3. opeartor()函数不是const的。

lambda的各个成分和类的各个成分对应起来就是如下的关系:

  • 捕获列表,对应LambdaClass类的private成员。
  • 参数列表,对应LambdaClass类的成员函数的operator()的形参列表
  • mutable,对应 LambdaClass类成员函数 operator() 的const属性 ,但是只有在捕获列表捕获的参数不含有引用捕获的情况下才会生效,因为捕获列表只要包含引用捕获,那operator()函数就一定是非const函数。
  • 返回类型,对应 LambdaClass类成员函数 operator() 的返回类型
  • 函数体,对应 LambdaClass类成员函数 operator() 的函数体。
  • 引用捕获和值捕获不同的一点就是,对应的成员是否为引用类型。

Mutable Lambda表达式

Lambda函数的call-operator(调用运算符)隐式为const-by-value(常量,按值捕获),这意味着它是不可变的。 但是函数内部想修改这变量,但是又不想影响lambda表达式外面的值的时候,就直接添加mutable属性,这样调用lambda表达式的时候,会像函数传递参数一样,在内部定义一个变量并拷贝这个值。

#include <iostream>
using namespace std;

int main()
{
	int t = 9;
	auto f = [t] () mutable {return ++t; };
	cout << f() << endl;
	cout << f() << endl;
	cout << "t:" << t << endl;
	return 0;
}

在这里插入图片描述
捕获的变量t,它在刚开始被捕获的初始值是9,调用一次f之后,变成了10,再调用一次,就变成了11。 但是最终的输出t,也就是main()函数里面定义的t,由于是值捕获,所以它的值一直不会变,最终还将输出9。

Lambda 表达式的类型

lambda 表达式的类型在 C++11 中被称为“闭包类型(Closure Type)”。它是一个特殊的,匿名的非 nunion 的类类型。因此,可以认为它是一个带有 operator() 的类,即仿函数。可以使用 std::function 和 std::bind 来存储和操作 lambda 表达式:

std::function<int(int)>  f1 = [](int a){ return a; };
std::function<int(void)> f2 = std::bind([](int a){ return a; }, 123);

对于没有捕获任何变量的 lambda 表达式,还可以被转换成一个普通的函数指针(必须是没有捕获任何变量):

using func_t = int(*)(int);
func_t f1 = [](int a){ return a; };  // 正确,没有捕获的的lambda表达式可以直接转换为函数指针
f1(123);
func_t f2 = [&](int a){ return a; };  // 错误,有捕获的lambda表达式不能直接转换为函数指针

lambda 表达式可以说是就地定义仿函数闭包的“语法糖”。它的捕获列表捕获住的任何外部变量,最终均会变为闭包类型的成员变量。而一个使用了成员变量的类的 operator(),如果能直接被转换为普通的函数指针,那么 lambda 表达式本身的 this 指针就丢失掉了。而没有捕获任何外部变量的 lambda 表达式则不存在这个问题。这里也可以很自然地解释为何按值捕获无法修改捕获的外部变量。因为按照 C++ 标准,lambda 表达式的 operator() 默认是 const 的。一个 const 成员函数是无法修改成员变量的值的。而 mutable 的作用,就在于取消 operator() 的 const。

Lambda auto参数

在C++ 14中引入的泛型Lambda,它可以使用auto标识符捕获参数。参数声明为auto是借助了模板的推断机制

auto func = [] (auto x, auto y) {
    return x + y;
};
// 上述的lambda相当于如下类的对象
class X {
public:
    template<typename T1, typename T2>
    auto operator() (T1 x, T2 y) const { // auto借助了T1和T2的推断
        return x + y;
    }
};

func(1, 2);
// 等价于
X{}(1, 2);

constexpr Lambda表达式

C++17前lambda表达式只能在运行时使用,C++17引入了constexpr lambda表达式,可以用于在编译期进行计算。
constexpr lambda 表达式有如下限制:函数体不能包含汇编语句、goto语句、label、try块、静态变量、线程局部存储、没有初始化的普通变量,不能动态分配内存,不能有new delete等,不能虚函数。

#include <iostream>
#include <functional>

int main() {
    constexpr auto lamb = [] (int n) { return n * n; };
    static_assert(lamb(3) != 9, "a");
}

也可将 lambda 表达式声明为常量表达式或在常量表达式中使用

#include <iostream>
#include <string>

constexpr int Increment(int n) {
    auto add1 = [n]()    //Callable named lambda
    {
        return n + 1;
    };
    return add1();  //call it
}

int main() {
    constexpr int number3 = Increment(2);
    std::cout << number3 << std::endl;
}

this拷贝

C++17中,可在lambda表达式的捕获类别里[]写上*this,表示传递到lambda中的是this对象的拷贝。
lambda中的[*this]就是一个对象的拷贝,这意味着传递了d的一个拷贝。因此,线程在调用d的析构函数后使用传递的对象是没有问题的。
如果用[this]、[=]或[&]捕获了,那么线程将运行未定义的行为,因为在传递给线程的lambda中打印name时,lambda将使用已销毁对象的成员。

#include <iostream>
#include <string>
#include <thread>
 
class Data {
private:
	std::string name;
public:
	Data(const std::string& s) : name(s) {
	}

	std::thread startThreadWithCopyOfThis() const 
    {
	    // start and return new thread using this after 3 seconds:
	    std::thread t([*this]
        {
	        std::cout << "I will shellp 3 seconds" << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(3));
            std::cout << name << std::endl;
	    });
	    return t;
	}
};

int main()
{
	std::thread t;
	{
	    Data d{ "This copy capture in C++17" };
	    t = d.startThreadWithCopyOfThis();
	} // d已经销毁
	std::cout << "the main thread wait for sub thread end." << std::endl;
	t.join();
	return 0;
}

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

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

相关文章

cinder的配额参数说明

概述 openstack默认为了防止用户随意使用存储空间&#xff0c;针对cinder做了限制。cinder的quota有一个专门的驱动去完成。当超过quota时&#xff0c;使用cinder将会失败。 cinder中quota的默认配置 quota_drivercinder.quota.DbQuotaDriver quota的驱动&#xf…

C#获取屏幕缩放比例

现在1920x1080以上分辨率的高分屏电脑渐渐普及了。我们会在Windows的显示设置里看到缩放比例的设置。在Windows桌面客户端的开发中&#xff0c;有时会想要精确计算窗口的面积或位置。然而在默认情况下&#xff0c;无论WinForms的Screen.Bounds.Width属性还是WPF中SystemParamet…

【git】合并分支代码失败fix conficts and then commit the result

目录 一、合并代码 二、打开冲突文件 三、手动处理冲突文件 四、将修改文件添加提交 前言&#xff1a;合并分支代码到主干报错fix conficts and then commit the result 一、合并代码 切换到要合并到的分支&#xff0c;例如主干 git merge 被合并分支的文件名 如果没有冲…

Redis.conf 配置文件详解

1、units 单位 配置大小单位&#xff0c;开头定义了一些基本的度量单位&#xff0c;只支持 bytes&#xff0c;不支持bit&#xff0c;并且对大小写 不敏感。 2、INCLUDES 包含 类似于 Spring 配置文件&#xff0c;可以通过 includes 包含&#xff0c;redis.conf 可以作为总文件…

机器学习:争取被遗忘的权利

随着越来越多的人意识到他们通过他们经常访问的无数应用程序和网站共享了多少个人信息&#xff0c;数据保护和隐私一直在不断讨论。看到您与朋友谈论的产品或您在 Google 上搜索的音乐会迅速作为广告出现在您的社交媒体提要中&#xff0c;这不再那么令人惊讶。这让很多人感到担…

响应式营销型H5建站平台系统源码 可视化后台+自助建站+搭建部署教程

分享一个响应式营销型H5建站平台系统源码&#xff0c;含700多套多行业模板&#xff0c;含完整代码包和详细的搭建部署教程。 自助建站是响应式营销型H5建站平台系统的特色功能之一&#xff0c;用户可以通过简单的操作&#xff0c;自主搭建网站。常规自助建站的步骤&#xff1a…

Flutter 苹果审核被拒2.1

1、拒绝原因 Guideline 2.1 - Performance - App Completeness We were unable to review your app as it crashed on launch. We have attached detailed crash logs to help troubleshoot this issue. Review device details: Device type: iPadOS version: iOS 16.6 Nex…

Invalid bound statement (not found) 报错

常规的问题都检查了&#xff0c;还是报错。 用mp代码生成器的目录结构如下&#xff1a; xml文件没有放在resources路径下 这样会导致xml文件不在target目录下&#xff0c;解决的方式是在pom.xml文件中加入&#xff1a; <build><resources><resource><…

C语言练习题第三弹!!!绝对典中典!!!

目录 1.单身狗1 1.1 题目 1.2 分析推理 1.3 代码实现 2.单身狗2 2.1 题目 2.2 分析推理 2.3 代码实现 3.字符串左旋 3.1 题目 3.2 分析推理 3.3 代码实现 3.3.1 方法一 3.3.2 优化一 3.3.2.1 思路分析 3.3.2.2 strcpy函数和strncat函数 3.3.2.3 代码实现 3.3.…

国产操作系统开放麒麟安装

国产操作系统 开放麒麟 银河麒麟 中科方德 统信UOS 红旗Linux 深度系统 优麒麟系统 开放麒麟操作系统 “开放麒麟1.0”是通过开放操作系统源代码的方式、由众多开发者共同参与研发的国产开源操作系统&#xff0c;系统的发布将有助于推动面向全场景的国产操作系统迭代更新&…

iOS 推送证书 Apple Push Services:不受信任的解决办法

2022年1月27日需要请求中间G4证书 ​​​​​​​ 链接 Apple PKI - Apple​​​​​​​

软件测试面试题:压测时,QPS一直上不去,如何排查?

在进行系统压测时&#xff0c;QPS&#xff08;Queries Per Second&#xff09;即每秒查询数&#xff0c;无法达到预期值是一个常见的问题&#xff0c;本文就来介绍下QPS一直上不去时应该如何排查。 一. 检查硬件资源 CPU使用率 使用top或nmon命令来查看CPU使用率。如果CPU使…

.netcore grpc日志记录配置

一、日志记录配置概述 通过配置文件appsettings.json进行配置通过Program.cs进行配置通过环境变量进行配置客户端通过日志通道进行配置 二、实战案例 配置环境变量:Logging__LogLevel__GrpcDebug配置Appsettings.json配置Program.cs配置客户端工厂以上截图是目前为止已知的可…

QT基础教程之六布局管理器和常用控件

QT基础教程之六布局管理器和常用控件 布局管理器 所谓 GUI 界面&#xff0c;归根结底&#xff0c;就是一堆组件的叠加。我们创建一个窗口&#xff0c;把按钮放上面&#xff0c;把图标放上面&#xff0c;这样就成了一个界面。在放置时&#xff0c;组件的位置尤其重要。我们必须…

C++ deque底层原理

deque底层原理 一、目的二、底层实现三、原理图四、类结构五、push_back六、pop_back 一、目的 实现双端数组 二、底层实现 双向开口的连续线性空间 三、原理图 四、类结构 class deque : protected Deque base _Deque_base._Deque_impl M_map 指针数组 _M_map_size …

java JUC并发编程 第五章 volatile与JMM

系列文章目录 第一章 java JUC并发编程 Future: link 第二章 java JUC并发编程 多线程锁: link 第三章 java JUC并发编程 中断机制: link 第四章 java JUC并发编程 java内存模型JMM: link 第五章 java JUC并发编程 volatile与JMM: link 文章目录 系列文章目录1 volatile 2大特…

KVM虚拟化ubuntu

KVM&#xff08;Kernel-based Virtual Machine&#xff09;是一种基于Linux内核的虚拟化技术&#xff0c;它将Linux内核作为虚拟机的底层操作系统&#xff0c;利用硬件虚拟化支持创建和管理虚拟机。KVM虚拟化技术被广泛应用于云计算、虚拟化服务器、虚拟化桌面等场景。 KVM虚拟…

最新无代码排名出炉,哪个平台最适合你?

随着无代码技术的迅速发展&#xff0c;国内外涌现出许多优秀的无代码平台提供商&#xff0c;企业在选择合适的无代码平台时可能会感到困惑&#xff0c;无从下手。为了帮助大家更好地了解国内真正的无代码平台厂商&#xff0c;本文将为您介绍几家具有代表性的厂商。 1.云表平台&…

数组中的第K个最大元素

题目链接 数组中的第K个最大元素 题目描述 注意点 需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素 解答思路 本题可以使用快速排序、堆排序或优先队列解决&#xff0c;快排可以比较快速找到某个元素在数组中排序后的位置&#xff0c;所以找…

Redis——》Redis的部署方式对分布式锁的影响

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…