C++11——Lambda表达式的用法
目录
一、Lambda表达式的语法结构
表达式主体:
[capature-list] (parameter-list) -> return type {function boby}
主体拆分:
capature-list(捕获列表)
捕获列表主要用于捕获函数体中不存在的局部变量,使函数体内也可以使用该变量,这里只做简单介绍,下文会有capatrue-list的不同使用方式详解。
parameter-list(参数列表)
参数列表到目前为止我们已经相当熟悉了,即我们写的函数传入的参数。
return type
即函数返回类型,与我们平时写的函数名前面的返回类型基本一致。
funtion body
即函数体,也就是实现函数功能的代码。
代码示例:
#include<iostream>
using namespace std;
int main()
{
//lambda表达式
auto add = [](int x, int y)->int { return x + y; };
cout << add(1, 2);
return 0;
}
lambda表达式需要一个auto自动识别类型接收,无法使用其他的具体类型接收。在此情境下,实现add(两数相加)不需要额外捕获参数,因此capatrue-list为空,参数列表传入x和y,最后在函数体实现x+y并返回即可。
二、Lambda表达式的使用细节
可省略的结构
1.return type
返回类型大部分场景基本都不需要写,因为编译器会自动推导出返回类型,不需要我们自己手动去写,而少部分场景建议写也只不过是为了让代码可读性更高。如下
#include<iostream>
using namespace std;
int main()
{
//lambda表达式
auto add = [](int x, int y) { return x + y; };
cout << add(1, 2);
return 0;
}
2.parameter-list
当有参数的时候参数列表必须要写,但是如果不存在参数,那么参数列表乃至圆括号都可以省略掉不写,进一步增加了代码的简洁性。如下
#include<iostream>
using namespace std;
int main()
{
//lambda表达式
auto add = []
{
int x = 1, y = 2;
return x + y;
};
cout << add();
return 0;
}
不可省略的结构
capature-list
即使捕获列表不含有任何元素,方括号依旧需要保留,不可像参数列表一样连同括号一起省略。
funtion body
函数体内即使没有任何表达式也必须保留花括号,不可省略。
三、捕获列表的捕捉方式
显示传值捕捉和显示传引用捕捉
1.传值捕捉
传值捕捉即把函数体外的非全局变量传值到捕获列表,使函数体内可以使用该变量,需要注意的是,参数列表里的参数可以直接使用,不能被捕获,同时,被传值捕捉到捕获列表的变量具有常性,即相当于被const修饰,当然如果非要改变也可以,那就是在参数列表后面加mutable修饰符,可以取消其常量性,也就说使用该修饰符后,传值捕捉的列表就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [a,b]
{
//a++; // 传值具有常性不可修改
//int p = c + d; //未捕获的变量不可使用
return a + b;
};
cout << add();
return 0;
}
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [a,b]()mutable
{
//mutable修饰后可以修改,但参数列表括号不可省略
a++;
b++;
return a + b;
};
cout << add();
return 0;
}
2.传引用捕捉
传引用捕捉到的变量是可以修改的,并不具有常性,且形参会影响实参。需要注意的是要区别传引用和取地址,它们在这里非常相似。
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [&a,&b]
{
a++;
b++;
return a + b;
};
cout << add();
return 0;
}
隐式值捕捉和隐式引用捕捉
1.隐式值捕捉
隐式值捕捉即不显示任何的传值参数,捕获列表里面只有一个‘=’,便可隐式传函数体外的所有可传值变量,但同样的,捕获的值依旧不可以修改,如需要修改需要加mutable修饰符,方法同上。
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [=]()mutable
{
int e = c + d;
a++;
b++;
return a + b;
};
cout << add();
return 0;
}
2.隐式引用捕捉
隐式值捕捉即不显示任何的传引用参数,捕获列表里面只有一个‘&’,便可隐式传函数体外的所有可传引用变量。
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [&]
{
a++; b++; c++; d++;
};
add();
cout << a << b << c << d;
return 0;
}
运行结果如下:

混合使用隐式捕捉和显示捕捉
1.隐式引用+显示传值
隐式引用会传引用所有的外部变量,它们的改变都可以改变实参,当然列表内显示传值的参数除外,它们具有常性,同样不可修改,即使加了mutable修饰符也不会改变实参。
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [&,c,d]
{
a++; b++;
//c++; d++; //不可修改
};
add();
cout << a << b << c << d;
return 0;
}
2.隐式传值+显示引用
同理,只有传引用的变量才可以修改其实参,其他的修改均不会改变实参。
#include<iostream>
using namespace std;
int main()
{
int a = 0, b = 1, c = 2, d = 3;
//lambda表达式
auto add = [=,&c,&d]
{
//a++; b++;
c++; d++;
};
add();
cout << a << b << c << d;
return 0;
}
由此我们便可以灵活运用其捕获的规则,达到我们的目的,比如想要使用外部的变量但是又不希望全部都可以修改,想修改实参的只有几个,那就可以混合使用隐式和显示捕捉。
四、Lambda表达式的应用
Lambda表达式是为了简化一些简单函数,然后用auto接收表达式,就可以进行使用,比如上面add,简化可以写成如下:
#include<iostream>
using namespace std;
int main()
{
int x = 1, y = 2;
auto add = [x, y] {return x + y; };
return 0;
}
我们来看另一个场景,一般我们都学过排序算法,C++算法库中也有现成的sort可以调用,但事实上在大部分场景中,纯排数字的非常少见,至少都是对结构体进行排序,比如一个商品的优先级曝光,可能根据其名字、价格乃至评价而定。
class goods
{
public:
string _name; //名字
double _price; //价格
int _evaluate; //评价
};
那么我们到底是要根据哪个排序,需要我们自己来指定规则,第一个想到的莫过于仿函数了,这也是sort的第三个参数给我们的留白,让我们有机会能够自己制定排序规则。如下
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
class goods
{
public:
string _name; //名字
double _price; //价格
int _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());
return 0;
}
类似这样的场景,我们实现仿函数对象支持商品中不同项的比较,相对还是很麻烦的,那么这里Lambda就很好用了。
auto ComparePriceLess = [](const goods& g1, const goods& g2) {
return g1._price < g2._price;
};
auto ComparePriceGreater = [](const goods& g1, const goods& g2) {
return g1._price > g2._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);
return 0;
}
Lambda表达式的返回类型不仅可以用auto接收,还可以用模板接收,直接把表达式放入第三个参数。
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;
});
return 0;
}
如此就不需要额外写多余的函数,简洁且可读性高,排序规则在表达式中一目了然。
下面我们来尝试两道算法题应用Lambda表达式

算法思路:先用unordered_map把每个数字出现的次数记录下来,然后利用迭代器初始化把无序图的数据初始化给vector,再调用算法库里的sort对vector进行排序,但是由于图的映射关系,第一个数对应的是元素,第二个才是对应出现的次数,因此如果不自己指定排序规则,则最后排序是按照第一个数进行排序的,得不到我们想要的结果,如果没学过lambda表达式,我们一般会想到用仿函数来做模板第三个参数,但此时我们直接用Lambda表达式即可简化代码,最后排序好了之后利用for循环打印出前k个元素即可。代码如下:
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int>CountMap;
for(auto e : nums)
{
CountMap[e]++;
}
vector<pair<int, int>> v(CountMap.begin(), CountMap.end());
sort(v.begin(), v.end(), [](const pair<int, int>& kv1, const pair<int, int>& kv2)
{
return kv1.second > kv2.second;
});
vector<int> ret;
for(int i = 0; i < k; i++)
{
ret.push_back(v[i].first);
}
return ret;
}
};
再来最后一题,也是很经典的一道题目,逆波兰表达式求值

算法思路:首先需要结合图的映射关系还有包装器,将运算符(加减乘除)与Lambda表达式做一个映射关系,遇到相应的运算符便可以开始运算,把运算结果再入栈,如果不是运算符,则转化为整型直接入栈。最后返回栈顶元素即可。代码如下:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
map<string,function<int(int,int)>> OpFunctionMap =
{
{"+",[](int x, int y){return x + y;}},
{"-",[](int x, int y){return x - y;}},
{"*",[](int x, int y){return x * y;}},
{"/",[](int x, int y){return x / y;}}
};
stack<int>st;
for(auto str:tokens)
{
if(OpFunctionMap.count(str))
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(OpFunctionMap[str](left,right));
}
else{
st.push(stoi(str));
}
}
return st.top();
}
};
本期介绍到此结束,观看到这里相信你已经对lambda表达式的结构和应用场景有了深刻的认识,一起加油,再接再厉!
更多推荐


所有评论(0)