C++ lambda函数详解
作者:scott198512 发布时间:2023-06-20 07:49:43
Lambda 表达式
Lambda 表达式是现代 C++ 中最重要的特性之一,而 Lambda 表达式,实际上就是提供了一个类似匿名函数的特性, 而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。这样的场景其实有很多很多, 所以匿名函数几乎是现代编程语言的标配。
基础
Lambda 表达式的基本语法如下:
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}
上面的语法规则除了 [捕获列表] 内的东西外,其他部分都很好理解,只是一般函数的函数名被略去, 返回值使用了一个 -> 的形式进行(我们在上一节前面的尾返回类型已经提到过这种写法了)。
所谓捕获列表,其实可以理解为参数的一种类型,Lambda 表达式内部函数体在默认情况下是不能够使用函数体外部的变量的, 这时候捕获列表可以起到传递外部数据的作用。根据传递的行为,捕获列表也分为以下几种:
1. 值捕获
与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 Lambda 表达式被创建时拷贝, 而非调用时才拷贝:
void lambda_value_capture() {
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// 这时, stored_value == 1, 而 value == 100.
// 因为 copy_value 在创建时就保存了一份 value 的拷贝
}
2. 引用捕获
与引用传参类似,引用捕获保存的是引用,值会发生变化。
void lambda_reference_capture() {
int value = 1;
auto copy_value = [&value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// 这时, stored_value == 100, value == 100.
// 因为 copy_value 保存的是引用
}
3. 隐式捕获
手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个 & 或 = 向编译器声明采用引用捕获或者值捕获.
总结一下,捕获提供了 Lambda 表达式对外部值进行使用的功能,捕获列表的最常用的四种形式可以是:
a. [] 空捕获列表
b. [name1, name2, ...] 捕获一系列变量
c. [&] 引用捕获, 让编译器自行推导引用列表
d. [=] 值捕获, 让编译器自行推导值捕获列表
4. 表达式捕获
这部分内容需要了解后面马上要提到的右值引用以及智能指针
上面提到的值捕获、引用捕获都是已经在外层作用域声明的变量,因此这些捕获方式捕获的均为左值,而不能捕获右值。
C++14 给与了我们方便,允许捕获的成员用任意的表达式进行初始化,这就允许了右值的捕获, 被声明的捕获变量类型会根据表达式进行判断,判断方式与使用 auto 本质上是相同的:
#include <iostream>
#include <memory> // std::make_unique
#include <utility> // std::move
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
在上面的代码中,important 是一个独占指针,是不能够被 "=" 值捕获到,这时候我们可以将其转移为右值,在表达式中初始化。
泛型 Lambda
上一节中我们提到了 auto 关键字不能够用在参数表里,这是因为这样的写法会与模板的功能产生冲突。 但是 Lambda 表达式并不是普通函数,所以在没有明确指明参数表类型的情况下,Lambda 表达式并不能够模板化。 幸运的是,这种麻烦只存在于 C++11 中,从 C++14 开始,Lambda 函数的形式参数可以使用 auto 关键字来产生意义上的泛型:
auto add = [](auto x, auto y) {
return x+y;
};
add(1, 2);
add(1.1, 2.2);
函数对象包装器
这部分内容虽然属于标准库的一部分,但是从本质上来看,它却增强了 C++ 语言运行时的能力, 这部分内容也相当重要,所以放到这里来进行介绍。
std::function
Lambda 表达式的本质是一个和函数对象类型相似的类类型(称为闭包类型)的对象(称为闭包对象), 当 Lambda 表达式的捕获列表为空时,闭包对象还能够转换为函数指针值进行传递,例如:
#include <iostream>
using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法
void functional(foo f) { // 参数列表中定义的函数类型 foo 被视为退化后的函数指针类型 foo*
f(1); // 通过函数指针调用函数
}
int main() {
auto f = [](int value) {
std::cout << value << std::endl;
};
functional(f); // 传递闭包对象,隐式转换为 foo* 类型的函数指针值
f(1); // lambda 表达式调用
return 0;
}
上面的代码给出了两种不同的调用形式,一种是将 Lambda 作为函数类型传递进行调用, 而另一种则是直接调用 Lambda 表达式,在 C++11 中,统一了这些概念,将能够被调用的对象的类型, 统一称之为可调用类型。而这种类型,便是通过 std::function 引入的。
C++11 std::function 是一种通用、多态的函数封装, 它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作, 它也是对 C++ 中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的), 换句话说,就是函数的容器。当我们有了函数的容器之后便能够更加方便的将函数、函数指针作为对象进行处理。 例如:
#include <functional>
#include <iostream>
int foo(int para) {
return para;
}
int main() {
// std::function 包装了一个返回值为 int, 参数为 int 的函数
std::function<int(int)> func = foo;
int important = 10;
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
}
std::bind 和 std::placeholder
而 std::bind 则是用来绑定函数调用的参数的, 它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数, 我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调用。 例如:
int foo(int a, int b, int c) {
;
}
int main() {
// 将参数1,2绑定到函数 foo 上,
// 但使用 std::placeholders::_1 来对第一个参数进行占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
bindFoo(1);
}
提示:注意 auto 关键字的妙用。有时候我们可能不太熟悉一个函数的返回值类型, 但是我们却可以通过 auto 的使用来规避这一问题的出现。
来源:https://blog.csdn.net/scott198510/article/details/128991892


猜你喜欢
- 前言我们在搭建完集群环境后,不得不考虑的一个问题就是用户访问产生的session如何处理。session的处理有很多种方法,详情见转载的上篇
- 有经验的程序员应该都见过,一个方法坐拥几十上百个参数。方法为何要有参数?因为不同方法间需共享信息。但方法间共享信息的方式不止一种,除了参数列
- 实例如下://首先要添加 System.ServiceProcess.dll 引用 ServiceController sc
- Pom依赖<parent> <groupId>org.springframework.bo
- 本文实例为大家分享了Android自定义控件EditText的具体代码,供大家参考,具体内容如下自定义控件分三种: 1. 自绘控件 2. 组
- 前置知识在微服务项目中,如果我们想实现服务间调用,一般会选择Feign。之前介绍过一款HTTP客户端工具Retrofit,配合SpringB
- 以下是tcp socket客户端和服务端源码,代码简单大家参考使用吧Tcp Server#include <WinSock2.h>
- 运用到的MongoDB支持的C#驱动,当前版本为1.6.0下载地址:https://github.com/mongodb/mongo-csh
- 一、Android Studio 主题的设置1.1 设置Android Studio 自带的主题及包名字体大小1.2 导入第三方主题:下载了
- Mutex 中文为互斥,Mutex 类叫做互斥锁。它还可用于进程间同步的同步基元。Mutex 跟 lock 相似,但是 Mutex 支持多个
- 一、Tomcat集成使用的成本越低,内部封装越复杂;1、依赖层级在SpringBoot框架的web依赖包中,引入的是内嵌Tomcat组件,基
- Spring 提供了自动代理机制,可以让容器自动生成代理,从而把开发人员从繁琐的配置中解脱出来 。 具体是使用 BeanPostProces
- 本文实例为大家分享了Android自定义view贝塞尔曲线,供大家参考,具体内容如下贝塞尔曲线以一个简单的贝塞尔曲线为例,二阶曲线原理贝塞尔
- 您好,我是贾斯汀,欢迎又进来学习啦!【学习背景】学习Java的小伙伴,都知道想要提升个人技术水平,阅读JDK源码少不了,但是说实话还是有些难
- 1 概念方面List是接口,ArrayList是List接口的一个实现类2 初始化方面2.1 List2.1.1 错误写 * ist list
- spring security中遇到的问题1.An Authentication object was not found in the S
- 很久没写文章了,一方面是最近几个月比较忙,没太多时间,另一方面是最近拖延症严重,写文章的想法总是一拖再拖。今天找一个小案例写一下,与懒惰对抗
- 本文实例为大家分享了Android原生视频播放VideoView的具体代码,供大家参考,具体内容如下布局文件activity_video.x
- c#里面封装了几乎所有我们可以想到的和我们没有想到的类,流是读取文件的一般手段,那么你真的会用它读取文件中的数据了么?真的能读完全么?通常我
- 前言本身我是一个比较偏向少使用Stream的人,因为调试比较不方便。但是, 不得不说,stream确实会给我们编码带来便捷。Stream流