c/c++补完计划(六): 语法糖lambda
前言
很多语言都有lambda, c++自然不能缺, 在c++11里面加入了, 是程序猿喜欢的语法糖, 便于阅读, 也便于理解. 当然, 它有很多相关概念, 这里尽可能展开说.
lambda表达式
[capture list] (params list) mutable exception-> return type { function body }
- 捕获子句(在c++规范中也称为lambda引导)
- 参数列表可有可无. (也称为lambda 声明符)
- 可变规范可有可无.
- 异常规范可有可无.
- 尾随-返回类型可有可无.
- lambda体, 也就是函数体.
你会觉得和函数声明很像, 但是注意, 不可以有默认值, 也不支持可变参数, 类似printf, 参数必须要有参数名. 甚至你会觉得和python3很像, 尤其是这个->return type, 所以你看, c++也越来越友好了.
先来几个小栗子:
auto f = [](int a, int b) { return a + b; };
cout << f(1, 5) << endl;
可以看到a, b是形参, 1, 5是实参, f似乎是函数, 这个放到后面说.
int x = 3, y = 5;
auto f2 = [x, y] { return x + y; };
cout << f2() << endl;
然后你发现, lambda更神奇的地方在于, 可以捕获外界参数, 是不是很酷, 其实这里本质还是值传递, 当然后面有引用, 但是默认是值传递.
参数捕获
捕获形式 |
说明 |
---|---|
[] |
不捕获任何外部变量 |
[var1,var2, …] |
默认以值得形式捕获指定的多个外部变量(用逗号分隔) |
[this] |
以值的形式捕获this指针 |
[=] |
以值的形式捕获所有外部变量 |
[&] |
以引用形式捕获所有外部变量 |
[=, &var1] |
变量var1以引用形式捕获,其余变量以传值形式捕获 |
[&, var1] |
变量var1以值的形式捕获,其余变量以引用形式捕获 |
很多都是一眼懂, 举个栗子吧:
int x = 1, y = 2, z = 3;
cout << "before: " << x << ' ' << y << ' ' << z << endl;
auto f = [&, x]()mutable {
x = 5;
y = 6;
z = 7;
cout << "in: " << x << ' ' << y << ' ' << z << endl;
};
f();
cout << "after: " << x << ' ' << y << ' ' << z << endl;
before: 1 2 3
in: 5 6 7
after: 1 6 7
x是值传递, y, z是引用传递, 值传递实际上是不能修改的, 但是这里加了mutable, 所以可以在函数体内进行改动. 然后引用传入, 会对外部产生影响, 值传入则不会, 很好理解.
this
甚至可以传入this指针.
class A {
public:
explicit A(int d) : data(d) {}
void foo(vector<int> &v) {
for_each(v.begin(), v.end(), [this](int i) {
cout << "before: " << i << endl;
i += data;
cout << "after: " << i << endl;
});
}
private:
int data;
};
vector<int> v{1, 2, 3};
A a(3);
a.foo(v);
before: 1
after: 4
before: 2
after: 5
before: 3
after: 6
传入this指针之后, 就可以使用成员变量.
function
然后你会发现一个问题, 我这里写的都是auto, 那具体是什么呢?
function<int(int, int)> f = [](int a, int b) { return a + b; };
返回一开始的栗子, 看到function里面写了函数的返回值, 然后括号里面是参数类型. std::function是一个可调用对象包装器,是一个类模板,可以容纳除了类成员函数指针之外的所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。 目的肯定是取代函数指针的, 来看一个栗子:
function<int(int, int)> add = [](int a, int b) { return a + b; };
cout << add(1, 5) << endl;
function<int(int, int)> div = [](int a, int b) { return a / b; };
cout << div(5, 1) << endl;
想想之前的写法, 是不是用宏定义一个函数指针, 类似
typedef int(*fp)(int, int);
, 是不是很像, 用function的话, 整体也更直观好操作.
bind
曾经我被问到, 如何实现lambda获取外部参数的功能, 那答案就是利用bind, 先来看个栗子:
function<int(int, int)> div = [](int a, int b) { return a / b; };
cout << div(5, 1) << endl;
auto divEx = bind(div, placeholders::_2, placeholders::_1);
cout << divEx(5, 1) << endl;
5
0
是不是挺酷的, 什么都没改, 就把参数位置换了.
A a;
auto ret = bind(&A::f, &a, 5, placeholders::_1);
cout << ret(3) << endl;
之前也说了, lambda是不支持默认参数的, 但是通过bind+占位符, 就很好实现了这个点, 甚至可以随意调整参数位置. 而且可以绑定成员函数, 不过你需要传入具体实例在第二个参数.
void print(ostream &os, string &str, char space) {
os << str << space;
}
ostringstream os;
vector<string> data{"cpp", "c", "java"};
char space = ' ';
for_each(data.begin(), data.end(), bind(print, ref(os), placeholders::_1, space));
cout << os.str() << endl;
os.str("");
for_each(data.begin(), data.end(), [&os, space](string &str){os<< str << space;});
cout << os.str() << endl;
os.str("");
auto printL=[&os, space](string&str){os<<str<<space;};
for_each(data.begin(), data.end(), printL);
cout << os.str() << endl;
os.str("");
auto printL=[](ostream &os, string&str, char space){os<<str<<space;};
for_each(data.begin(), data.end(), bind (printL, ref(os), placeholders::_1, space));
cout << os.str() << endl;
最后对比下bind和lambda, 当然二者是可以配合使用的, 怎么用还是看自己. 我的话, 怎么简洁怎么用. ref简单说下, 主要是有些变量不支持值传递, bind这里就要写成ref(os).
最后
真的是很实用的点, 让c++灵活了很多. 就像设计了while之后, 又设计了for, 设计了for之后又设计了for_each, 语言都是不断发展的, 学习新的真的很有必要.
- 深海中的STL—nth_element
- javascript Object与Array用法
- vue class与style 绑定详解——小白速会
- java8-Stream之数值流
- POJ 3694 Network(Tarjan求割边+LCA)
- java之Cookie详解
- Ajax详解
- 1611: [Usaco2008 Feb]Meteor Shower流星雨
- 3893: [Usaco2014 Dec]Cow Jog
- 3892: [Usaco2014 Dec]Marathon
- BZOJ 2793: [Poi2012]Vouchers(调和级数)
- 3891: [Usaco2014 Dec]Piggy Back
- Java8-如何构建一个Stream
- 2016: [Usaco2010]Chocolate Eating
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 在群晖docker上装elmlang可视调试编码器ellie
- Elasticsearch:inverted index,doc_values及source
- 在群晖docker上构建私有云IDE和devops构建链
- 小白学PyTorch | 14 tensorboardX可视化教程
- Apache Solr 漏洞复现
- Elasticsearch rollover API
- 重发和重定向有什么区别与重定向应用
- 为tinycolinux制作应用包
- CrossC2的2.0版本
- 使用OpenCV和Python计算图像的“色彩”
- 为tinycolinux创建应用包-toolchain和编译方法
- [译]在Solidity中如何优化Gas第一部分:变量
- [译]Solidity 0.7.0 新变化
- 两个数组的交集 II
- 常说的手机刷新率60Hz、120Hz有什么不同?