lambda表达式c++

介绍

可调用对象

对于一个表达式,如果可以对其使用调用运算符(),则称它为可调用对象。如函数就是一个可调用对象,当我们定义了一个函数f(int)时,我们可以通过f(5)来调用它。

可调用对象有:

  • 函数
  • 函数指针
  • 重载了函数调用运算符的类
  • lambda表达式

lamdba表达式语法结构

lambda表达式又称匿名函数,可以简单的理解为一个内联函数,它通常被写在一个普通函数的内部定义和使用,其语法结构为

使用参数列表

参数列表很好理解,类似于我们在定义一个普通函数的时候,要使用的参数,如

void sol() {
	auto f = [](int a,int b) {return a+b; };
	cout << f(2,3) << endl;
}

 上述代码中,我们定义了一个普通函数sol,并在其中定义了一个lambda表达式,在参数列表中定义了两个参数,并返回这两个参数的和

 需要注意的是,与普通函数不同,lambda表达式中的参数列表中不能使用默认参数

使用捕获列表

捕获列表是一个lamdba表达式所在的普通函数的局部变量的列表,听起来很拗口,我们举例来说

void sol() {
	auto f1 = [](int a,int b) {return a+b; };
	cout << f1(2,3) << endl;

	int x = 3, y = 4;
	auto f2 = [x, y]() {return x + y; };
	cout << f2() << endl;

}

 

上述代码中,我们在sol的普通函数中定义了两个lambda表达式,其中f1使用了参数列表,f2使用了捕获列表,二者的区别在于,lambda表达式f1的参数列表是自定义的,而lambda表达式f2中的捕获列表是使用了sol普通函数中的局部变量x和y

也就是说,如果你想在lambda表达式中使用所在函数的局部变量,就要使用捕获列表进行捕获,否则将无法使用该局部变量

为了强调说明这一问题,我们在sol函数中再次定义一个变量z,在捕获列表中不写入它,但在函数体中使用它,来观察结果

由此可见,捕获列表中存放的是lambad表达式所在函数的已经存在的局部变量 

lambda表达式使用案例

接下来,我们从一个案例中来看lambda表达式的使用场景

问题描述

假如现在我们有一些单词,这些单词包含重复,现在我想知道这些单词的长度大于等于给定长度的单词有几个,比如,有以下单词序列:

"the","quick","red","fox","jumps","over","the","slow","red","turtle"

其中单词长度大于等于5的单词有三个,分别是jumps、quick、turtle

问题分析

首先我们需要明确的是,由于单词序列中包含重复的单词,因此我们首先需要对单词序列进行去重,其次,我们可以将单词按照其长度大小按照从小到大进行排序,此后找到第一个大于等于给定长度的单词,那么后边的单词都满足条件,统计其个数即可,即

  • 对单词序列进行去重
  • 按照单词的长度进行从小到大进行排序
  • 对排序后的单词序列进行遍历,找到第一个大于等于给定长度的单词,此后的单词就都满足条件,统计个数

问题解决

为了方便观察输出结果,我们首先重载输出运算符

ostream& operator<<(ostream& os, vector<string> words)
{
	for (string word : words)
		os << word << " ";
	return os;
}

上述代码中使用到了范围for循环语句,详情请移步

c++范围for语句-CSDN博客

输入数据

vector<string> words = { "the","quick","red","fox","jumps","over","the","slow","red","turtle" };

数据去重

  • 按照单词的字典序进行排序,这样相同的单词必然会紧邻
  • 对排序后的单词使用泛型算法unique进行去重
void elimDups(vector<string>& words)
{
	sort(words.begin(), words.end());//按照字典序进行排序
	//unique算法将重排输入序列,将相邻的重复项进行“消除”,并返回一个指向不重复值范围末尾的迭代器
	auto end_unique = unique(words.begin(), words.end());
	//unique去重后,vector中对多出来删除的空余位置,删除unique后的空余位置
	words.erase(end_unique, words.end());
}

输入数据并去重

	vector<string> words = { "the","quick","red","fox","jumps","over","the","slow","red","turtle" };
	cout << words << endl;
	elimDups(words);
	cout << words << endl;
	

 

按照单词长度进行排序

	sort(words.begin(), words.end(), [](string w1,string w2) {
		return w1.size() < w2.size();
		});

 

上述代码中,我们使用sort函数对去重后的单词序列进行排序,排序规则是单词的长度,并且按照从小到大进行排序,在sort函数的第三个参数中使用lambda表达式定义排序规则,上述代码等价于:

bool compare(const string& s1, const string& s2)
{
	return s1.size() < s2.size();
}

void sol()
{
	sort(words.begin(), words.end(), compare);
}

可以看到,我们用lambda表达式代替了compare函数,也就是说

  • lambda表达式可以直接在需要调用函数的位置定义短小精悍的函数,而不需要预先定义好函数

 找到满足条件的单词位置

对单词序列按照长度进行排序后,我们只需要找到单词长度大于等于给定长度的单词位置即可,我们使用find_if函数来查找第一个具有特定大小的元素,如下:

	vector<int>::size_type sz = 5;
	auto wc = find_if(words.begin(), words.end(),
		[sz](string word) {
			return word.size() >= sz;
		});
	decltype(sz) count = words.end() - wc;
	cout << count << endl;

上述代码中,我们同样使用lambda表达式作为find_if函数的查找规则,其中捕获列表中的参数sz是为我们在函数中定义的给定长度,至此案例开头给出的问题就解决了,下边是完整代码,详细解释可见《c++ primer》第五版P343~P349

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;

#if 1
ostream& operator<<(ostream& os, vector<string> words)
{
	for (string word : words)
		os << word << " ";
	return os;
}

void elimDups(vector<string>& words)
{
	sort(words.begin(), words.end());//按照字典序进行排序
	//unique算法将重排输入序列,将相邻的重复项进行“消除”,并返回一个指向不重复值范围末尾的迭代器
	auto end_unique = unique(words.begin(), words.end());
	//unique去重后,vector中对多出来删除的空余位置,删除unique后的空余位置
	words.erase(end_unique, words.end());
}

void sol()
{
	vector<string> words = { "the","quick","red","fox","jumps","over","the","slow","red","turtle" };
	cout << words << endl;
	elimDups(words);
	cout << words << endl;
	
	sort(words.begin(), words.end(), [](string w1,string w2) {
		return w1.size() < w2.size();
		});
	cout << words << endl;

	vector<int>::size_type sz = 5;
	auto wc = find_if(words.begin(), words.end(),
		[sz](string word) {
			return word.size() >= sz;
		});
	decltype(sz) count = words.end() - wc;
	cout << count << endl;
}

int main()
{
	sol();
	return 0;
}
#endif 

lambda表达式语法结构详解

通过以上案例我们已经知道了lambda表达式的使用场景,接下来我们详细说明lambda表达式语法结构中各参数的使用

捕获列表

类似于参数传递,捕获列表在捕获函数中的局部变量时,也可分为值捕获与引用捕获;其中采用值捕获的前提是变量可以被拷贝,但与普通函数的参数值传递不同,被捕获的变量的值是在lambdb创建时拷贝,而不是调用时拷贝。

值捕获

void sol() {
	int a = 41;
	auto f1 = [a]() {return a; };
	a=0;//修改啊的值
	cout << f1() << endl;//41
}

上述代码中,我们使用值捕获来捕获sol函数中的局部变量a,之后改变a的值再次输出,发现lambda表达式中的值仍旧是改变前的值,说明此时被捕获的变量a是在lambda创建时拷贝过去的,因此后续a的改变将不影响lambda内的值

引用捕获

void sol() {
	int a = 41;
	auto f1 = [&a]() {return a; };
	a=0;
	cout << f1() << endl;//0
}

与上述实验不同,我们使用引用捕获方式捕获局部变量a,此时在改变a的值后,lambda表达式输出的是改变后的值,因为使用的是引用捕获,因此lambda内所捕获的变量a与sol函数的a使用的是同一份内存,故对局部变量a的改变也会影响到lambda表达式中a的值

隐式捕获

上述捕获都属于显示捕获,也就是说我们想在lambda中使用哪个局部变量就在捕获列表中填入哪个,但有时我们希望使用sol函数中所有的局部变量时,一个一个填入捕获列表显然就会很麻烦,因此使用隐式捕获,就表示我们在lambda表达式中可以使用所在普通函数中所有局部变量。

同显示捕获一样,隐式捕获也分为值捕获与引用捕获

值传递隐式捕获

void sol() {

	int a = 3,b=4;
	auto f = [=]() {return a + b; };
	cout << f() << endl;
}

上述代码的lambda表达式中的捕获列表=表示,该lambda表达式可以使用sol函数的全部的局部变量,且使用方式为值捕获

引用传递隐式捕获

同样的道理,下述lambda表达式可以使用sol函数的全部的局部变量,且使用方式为引用捕获

void sol() {

	int a = 3,b=4;
	auto f = [&]() {return a + b; };
	cout << f() << endl;
}

混合捕获

  • [=, &a, &b]表示以引用传递的方式捕捉变量ab,以值传递方式捕捉其它所有变量。
int index = 1;
int num = 100;
auto function = ([=, &index, &num]{
		num = 1000;
		index = 2;
		std::cout << "index: "<< index << ", " 
            << "num: "<< num << std::endl;
	}
);

function();

可变规则mutable

默认情况下,对于值捕获方式捕获到的变量,lambda在内部不能改变其值,如果想改变就需要使用mutable关键字。

如上述代码的那样报错,如果想在lambda内部改变值捕获到的变量,需要加入mutable关键字,如下所示:
 

void sol() {
	int a = 3,b=4;
	auto f = [=]() mutable{return ++a; };
	cout << f() << endl;
}

 

 对于引用捕获获取到的变量,lambda在内部是否可以改变,取决于该局部变量是否是const的,如:

 

返回类型

Lambda表达式的返回类型会自动推导,但仅限于其函数体内只有一个return语句,否则当lambda函数体中包含了return之外的任何语句,编译器都会假定此lambda返回的是void类型。除非你指定了返回类型,否则不必使用关键字。

void sol() {
	vector<int> v = { -1,2,-3,4 };
	vector<int> result;
	//lambda函数体中只有一个return语句,将自动推断返回类型为int
	transform(v.begin(), v.end(), back_inserter(result),
		[](int i) {return i < 0 ? -i : i; });

	//使用for_each打印vector
	for_each(v.begin(), v.end(), [](int x) {
		cout << x << " ";
		});
	cout << endl;
	for_each(result.begin(), result.end(), [](int x) {
		cout << x << " ";
		});
}

上述代码使用泛型算法transform将v中的数据变为其绝对值,并将转换后的结果存放到result中,其中back_insert是一个插入迭代器(效果等同于push_back),整个transform语句表示遍历vector的每个元素v,判断其是否小于0,如果是就返回其相反数,并将结果push_back到result中

此外代码中还使用了for_each泛型算法遍历vector,在此我们再次看到了在泛型算法中使用lambda表达式的优势所在

但是,如果我们将上述代码改为如下代码,则编译器将会报错:

transform(v.begin(), v.end(), back_inserter(result),
	[](int i)
	{
		if (i < 0)	return -i;
		else return i;
	});

 因为这段代码中的lambda函数体有两个return语句,编译器将返回void类型,但笔者在vs上进行实验时,发现并没有报错,后查阅资料发现是c++进行了隐式类型转换?

但还是建议使用显示说明的返回类型

lambda表达式工作原理

编译器会把一个Lambda表达式生成一个匿名类的匿名对象,并在类中重载函数调用运算符,实现了一个operator()方法。 

如下所示:

auto print = []{cout << "Hello World!" << endl; };

编译器会把上面这一句翻译为下面的代码:

class print_class
{
public:
	void operator()(void) const
	{
		cout << "Hello World!" << endl;
	}
};
// 用构造的类创建对象,print此时就是一个函数对象
auto print = print_class();

 

参考:
《c++ primer》

【精选】C++ Lambda表达式详解-CSDN博客

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

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

相关文章

【Redis】zset常用命令集合间操作内部编码使用场景

文章目录 前置知识列表、集合、有序集合三者的异同点 普通命令ZADDZCARDZCOUNTZRANGEZREVRANGEZRANGEBYSCOREZPOPMAXBZPOPMAXZPOPMINBZPOPMINZRANKZREVRANKZSCOREZREMZREMRANGEBYRANKZREMRANGEBYSCOREZINCRBY 集合之间的操作ZINTERSTOREZUNIONSTORE 命令小结内部编码测试内部编…

滑动窗口练习(一)— 固定窗口最大值问题

题目 假设一个固定大小为W的窗口&#xff0c;依次划过arr&#xff0c; 返回每一次滑出状况的最大值 例如&#xff0c;arr [4,3,5,4,3,3,6,7], W 3 返回&#xff1a;[5,5,5,4,6,7] 暴力对数器 暴力对数器方法主要是用来做校验&#xff0c;不在乎时间复杂度&#xff0c;逻辑上…

GitHub如何删除仓库

GitHub如何删除仓库 删除方法第一步第二步第三步 删除方法 第一步 在仓库的界面选择Settings 第二步 选择General,页面拉到最后。 第三步 删除仓库。

七,vi和vim

Linux系统会内置vi文本编辑器 Vim具有程序编辑的能力&#xff0c;可以看做是Vi的增强版本&#xff0c;可以主动的以字体颜色辨别语法的正确性&#xff0c;方便程序设计。代码补完、编译及错误跳转等方便编程的功能特别丰富&#xff0c;在程序员中被广泛使用。 vi和vim常用的三…

CICD 持续集成与持续交付——gitlab

部署 虚拟机最小需求&#xff1a;4G内存 4核cpu 下载&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/ 安装依赖性 [rootcicd1 ~]# yum install -y curl policycoreutils-python openssh-server perl[rootcicd1 ~]# yum install -y gitlab-ce-15.9.3-ce.0…

LinkWeChat V4.9.8 版本发布

LinkWeChat v4.9.8 已经发布&#xff0c;基于企业微信的 SCRM 系统 LinkWeChat 是国内首个基于企业微信的开源 SCRM&#xff0c;在集成了企微强大的开放能力的基础上&#xff0c;进一步升级拓展灵活高效的客户运营能力及多元化精准营销能力&#xff0c;让客户与企业之间建立强…

Java概述

接触Java后会发现它的体系有一个特点&#xff0c;就是非常喜欢用“J”字母开头的缩写&#xff0c;比如JCP, JSR, JMS, JPA, JSP, JAX-RS......它们有些是规范&#xff0c;有些是组织的名称&#xff0c;表意多样&#xff0c;对第一次接触的人来说很可能会觉得混乱&#xff0c;本…

快速修改ppt | 显得不单调

做完ppt&#xff0c;怎样不显得单调 ----> 加个 主题&#xff0c;首页 改下 字体&#xff08;如 华文行楷&#xff0c;96&#xff0c;字体颜色&#xff09;

开拓经验专栏:从十来天的晨型人体验开始

文章目录 拓新缘起契机实践心得 拓新 确定要新开一个板块&#xff0c;用来记录持续自我提升的经验和教训&#xff0c;着实遭遇了不少阻力。 首先&#xff0c;我的语文功底一向不行&#xff0c;当年高考前&#xff0c;语文分数在及格线上下跳动都是常事&#xff0c;现在却要通…

git使用及常用命令

在初入公司中&#xff0c;若使用的是git管理工具&#xff0c;需要做以下步骤&#xff1a; 1&#xff0c;常用命令在&#xff1a; &#xff08;1&#xff09;&#xff0c;git config --global user.name xxx(名字) //若不设置 那么下次提交代码时会报错 其次该设置名字和…

从零开始:Rust环境搭建指南

大家好&#xff01;我是lincyang。 今天&#xff0c;我们将一起探讨如何从零开始搭建Rust开发环境。 Rust环境搭建概览 Rust是一种系统编程语言&#xff0c;以其安全性、并发性和性能闻名。搭建Rust环境是学习和使用这一语言的第一步。 第一步&#xff1a;安装Rust Rust的…

二维码智慧门牌管理系统升级解决方案:查询功能大提升,让地址查找变得轻松便捷!

文章目录 前言一、支持地址名称、小区等信息进行模糊查询二、支持地图上绘制多边形、圆形、矩形进行范围查询三、高效的数据处理能力&#xff0c;保证查询速度四、灵活的应用场景&#xff0c;满足多种需求 前言 随着科技的快速发展和城市化的加速推进&#xff0c;传统的门牌管…

二叉树oj题集(LeetCode)

100. 相同的树 关于树的递归问题&#xff0c;永远考虑两方面&#xff1a;返回条件和子问题 先考虑返回条件&#xff0c;如果当前的根节点不相同&#xff0c;那就返回false&#xff08;注意&#xff0c;不要判断相等时返回什么&#xff0c;因为当前相等并不能说明后面节点相等…

常用组合逻辑verilog实现之8-3优先编码器

文章目录 一、问题描述二、verilog源码三、综合及仿真结果一、问题描述 本例中将实现一个8-3优先编码器。优先编码器允许多个输入信号同时有效,输出针对优先级别高的信号进行编码。 8-3优先编码器有对应的芯片实现比如TI公司的CD4532,可以从下面链接下载其手册。 CD4532数…

论文-分布式-拜占庭将军问题

目录 0-前言 1-导引 2-不可能性 3将军(1叛徒)问题不存在解/不能达成共识 少于3m1个将军(有m个叛徒)不存在解/不能达成共识 精确一致性与近似一致性是同等困难的 3-使用口头消息的解 “口头消息”的含义 OM(m)算法的步骤 OM(m)算法的正确性推导 4-使用签名消息情况下…

传奇手游白日门【龙城霸业】win服务端+双端+GM后台+详细教程

搭建资源下载地址&#xff1a;传奇手游白日门【龙城霸业】win服务端双端GM后台详细教程-海盗空间

【实习】串口通信

modbus介绍 详解Modbus通信协议—清晰易懂 Modbus协议是一个master/slave架构的协议。有一个节点是master节点&#xff0c;其他使用Modbus协议参与通信的节点是slave节点。每一个slave设备都有一个唯一的地址。在串行和MB网络中&#xff0c;只有被指定为主节点的节点可以启动一…

SpringBoot——入门及原理

SpringBoot用来简化Spring应用开发&#xff0c;约定大于配置&#xff0c;去繁从简&#xff0c;是由Pivotal团队提供的全新框架。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff08;有特殊需求可以添加自己的配置覆盖默认配…

本地Git项目同时推送至GitHub和Gitee

分别在gitee和github新建一个仓库 github: gitee: 添加远程仓库 git remote add origin1 [你的GitHub仓库URL] git remote add origin2 [你的Gitee仓库URL] 在本地中初始化创建一个git本地分支 git init 进入.git目录下修改config文件 [remote "origin"] url g…

ubuntu安装完qt后发现找不到图标

layout: post # 使用的布局&#xff08;不需要改&#xff09; title: Qt启动问题 # 标题 subtitle: ubuntu安装完Qt #副标题 date: 2023-11-18 # 时间 author: BY ThreeStones1029 # 作者 header-img: img/about_bg.jpg #这篇文章标题背景图片 catalog: true # 是否归档 tags: …
最新文章