一、lambda表达式概述
- lambda自C++11引入。这带来一种很具为例又十分便利的方法,允许我们提供局部机能,特别是用来具体指明算法和成员函数的细节lambda为C++带来十分重要而深具意义的改善(当我们使用STL时)。因为如今你有了一个直观、易读的方式,可以将独特的行为传递给算法和容器的成员函数
- 一个lambda表达式可表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数
二、格式
- 与任何函数相同:一个lambda包含:返回类型、参数列表、函数体
- 与普通函数不同:lambda使用尾指返回来指定返回类型
- capture list(捕获列表):是一个lambda所在函数中定义的局部变量的列表(通常为空)
- return type:返回类型。如果忽略返回类型,lambda根据函数体的代码自动推断出返回类型
- parameter list:参数列表
- function body:函数体。如果是一条定义语句花括号后面要有分号;。如果是调用则分号可省去
例如
- lambda的参数列表/捕获列表为空,忽略了返回类型
auto f = [] { return 100; }; cout << f()<< endl; //100
三、向lambda传递参数
- lambda的实参与形参类型必须匹配,并且不能有默认参数
-
auto f = [](
int a,
int b)->
int {
return a + b; };
-
cout << f(
1,
2)<<
endl;
-
//实现sort的从大到小排序算法
-
auto f = [](
const
int& a,
const
int& b) {
return a > b; };
-
vector<
int> vec{
1,
2,
10,
1545,
151 };
-
-
sort(vec.begin(), vec.end(),
-
[](
const
int& a,
const
int& b) {
return a > b; });
-
-
sort(vec.begin(), vec.end(),f);
四、lambda捕获
- lambda的函数体中只能使用捕获列表指定的参数,如果捕获列表没有则不能使用
-
int sz=
10;
-
int a;
-
-
//lambda捕获了sz,但是没有捕获a
-
[sz](
const
string &a) {
-
//a=10; //错误,不能调用a
-
return a.size() >= sz;
//正确
-
};
- 捕获分为:
- 值捕获
- 引用捕获
- 隐式捕获
- 混合隐式捕获
值捕获
- 值捕获的传参方式是拷贝传参
- 值的拷贝是在lambda表达式创建时的拷贝,而不是调用时的拷贝
- 因为是值拷贝,所以当捕获的值在外部改变时,该值在lambda内不会跟随变化
- 重点:使用值捕获时,该值在lambda内不能被改变(下面会介绍使用mutable关键字改变)
size_t v1= 42; auto f=[v1]() { return v1;}; //auto f=[v1]() {return ++v1;}; //错误,值捕获不能改变值 v1= 0; auto j=f(); //j=42
引用捕获
- 引用捕获的值是以引用的方式使用该值
- 所以当值在外部发生变化时,该值lambda中也会改变
- 引用捕获可以在lambda内改变该值的值
- 因为采用引用方式捕获一个变量,所以在调用lambda表达式时,必须保证这个值是存在的(例如局部变量在函数执行完之后消失。例如函数返回一个lambda表达式,而该表达式引用函数内的一个局部变量)
size_t v1= 42; auto f=[&v1]() { return v1;}; v1= 0; auto j=f(); //j=0
隐式捕获
- 可以在lambda的[]号中输入符号,让编译器自己捕获的类型。并且可以省去参数
- 如果在捕获列表写一个&:告诉编译器该lambda使用引用捕获。如果在捕获列表写一个=:告诉编译器该lambda使用值捕获
int sz = 2; vector< string> vec{ "A", "ABC", "ABCD" }; //sz为隐式捕获:值捕获方式 auto wc = find_if(vec.begin(), vec.end(), [=]( const string &s) { return s.size() >= sz; });
混合隐式捕获方式
- 使用混合隐式捕获时,第一个元素必须是一个&或者=
- 显示捕获的变量必须使用与隐式捕获不同的方式:如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因为不能在其名字前使用&。
- 类似的,如果隐式捕获采用的是值捕获(使用了=),则显式捕获命名变量必须采用引用方式(在名字前使用&)
void biggers(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ') { //os隐式捕获:引用捕获;c显式捕获:值捕获方式 for_each(words.begin(), words.end(), [&, c]( const string &s) {os << s << c; }); //os显式捕获:引用捕获;c隐式捕获:值捕获方式 for_each(words.begin(), words.end(), [=, &os]( const string &s) {os << s << c; }); }
五、可变lambda(mutable关键字)
- 默认情况下:lambda值捕获时,该值在lambda内不能被改变
- 使用mutable关键字之后:该值可以在lambda表达式内被改变。但是lambda内的值仍与外界值的改变无关
-
int val=
1;
-
auto f = [val]()
mutable {
return ++val; };
-
-
val =
0;
//将val变为0
-
auto j = f();
//j=2
-
cout << j <<
endl;
六、指定lambda的返回类型
- 一般情况下,lambda会自动推断出表达式的返回值类型
- 但是某些情况下需要显式的给出返回值类型,返回值类型使用尾指返回类型
-
//隐式的返回类型int
-
[](
int i) {
return i <
0 ? -i : i; }
-
//显式给出返回类型int
-
[](
int i)->
int
-
{
-
if (i <
0)
return -i;
-
else
return i;
-
};
七、lambda的类型
- lambda的类型,是个不具名的function object(或functor)
- 如果你想使用lambda的类型声明对象,可以借助于template std::function<>或auto
- 如果你想写下该类型,则可以使用decltype()(见下面“十一”的演示案例②)
演示案例
#include <iostream> #include <functional> std::function< int( int, int)> returnLambda() { return []( int x, int y) { return x*y; }; } int main() { auto f = returnLambda(); std:: cout << f( 3, 7); //21 }
八、lambda与binder
- 例如我们在前面一篇文章中使用函数对象和函数适配器binder来书写代码,如下所示:
-
#include <iostream>
-
#include <functional>
-
using
namespace
std::placeholders;
-
-
int main()
-
{
-
auto plus10 =
std::bind(
std::plus<
int>(), _1,
10);
-
//相等于调用std::plus<int>(7,10)
-
std::
cout <<
"7+10=" << plus10(
7) <<
std::
endl;
-
-
auto plus10times2 =
std::bind(
std::multiplies<
int>(),
-
std::bind(
std::plus<
int>(), _1,
10),
-
2);
-
//相等于调用std::multiplies<int>(std::plus<int>(10,10),2)
-
std::
cout <<
"(10+10)*2=" << plus10times2(
10) <<
std::
endl;
-
-
//相等于调用std::multiplies<int>(std::multiplies<int>(7,7),7)
-
auto pow3 =
std::bind(
std::multiplies<
int>(),
-
std::bind(
std::multiplies<
int>(), _1, _1),
-
_1);
-
std::
cout <<
"7*7*7=" << pow3(
7) <<
std::
endl;
-
-
//相等于调用std::divides<double>(7,49)
-
auto inversDivide =
std::bind(
std::divides<
double>(), _2, _1);
-
std::
cout <<
"7/49=" << inversDivide(
49,
7) <<
std::
endl;
-
}
- 对于上面的binder,代码比较复杂,如果使用lambda则可以如下所示:
-
//头文件同上
-
int main()
-
{
-
auto plus10 = [](
int i) {
return i +
10; };
-
std::
cout <<
"7+10=" << plus10(
7) <<
std::
endl;
-
-
auto plus10times2 = [](
int i) {
return (i +
10) *
2; };
-
std::
cout <<
"(10+10)*2=" << plus10times2(
10) <<
std::
endl;
-
-
auto pow3 = [](
int i) {
return i*i*i; };
-
std::
cout <<
"7*7*7=" << pow3(
7) <<
std::
endl;
-
-
auto inversDivide = [](
double d1,
double d2) {
return d2 / d1; };
-
std::
cout <<
"7/49=" << inversDivide(
49,
7) <<
std::
endl;
-
}
九、lambda与带有状态的函数对象
演示案例①
- lambda是不带状态的
- 例如下面我们修改C++(标准库):31文章中的演示案例:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector< int> coll{ 1, 2, 3, 4, 5, 6, 7, 8 }; long sum = 0; for_each(coll.begin(), coll.end(), [&sum]( int elem) {sum += elem; }); double mv = static_cast< double>(sum) / static_cast< double>(coll.size()); }
演示案例②
- 例如我们修改C++(标准库):31文章中“七”的演示案例。如下:
#include <iostream> #include <list> #include <algorithm> using namespace std; int main() { std:: list< int> coll{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; for ( const auto elem : coll) std:: cout << elem << " "; std:: cout << std:: endl; std:: list< int>::iterator pos; int count = 0; pos=remove_if(coll.begin(),coll.end(), [count]( int) mutable { return ++count == 3; } ); coll.erase(pos, coll.end()); for ( const auto elem : coll) std:: cout << elem << " "; }
- 上面运行的代码与那篇文章演示案例运行的结果一样,第3和第6个元素都会被移除:
- lambda使用了mutable,remove_if()算法在执行过程中复制了一份,于是存在两个lambda对象都移除第三元素,导致重复行为
- 如果你已by reference方式传递实参,那么结果就正确了,只移除第三个元素:
pos=remove_if(coll.begin(),coll.end(), [&count]( int) { return ++count == 3; } );
十、lambda调用全局函数和成员函数
演示案例(调用全局函数)
- 下面的演示案例是《C++标准库》p490页演示案例的lambda版本。代码如下:
#include <iostream> #include <string> #include <locale> #include <algorithm> using namespace std; char myTopper(char c) { std::locale loc; return std::use_facet< std::ctype< char>>(loc). toupper(c); } int main() { string s("Internationlization"); string sub("Nation"); string::iterator pos; pos=search(s.begin(),s.end(),sub.begin(),sub.end(), []( char c1, char c2) { return myTopper(c1) == myTopper(c2); } ); if (pos != s.end()) std:: cout << "\"" << sub << "\" is part of \"" << s << "\"" << std:: endl; }
演示案例(调用成员函数)
- 下面的演示案例是前面一篇文章(https://blog.csdn.net/qq_41453285/article/details/105486301)演示案例的lambda版本。代码如下:
#include <iostream> #include <string> #include <vector> #include <algorithm> using namespace std; class Person { private: std:: string name; public: Person( const std:: string& _name) :name(_name) {} void print()const { std:: cout << name << std:: endl; } void print2(const std::string& prefix)const { std:: cout << prefix << name << std:: endl; } }; int main() { std:: vector<Person> vec{ Person( "Tick"),Person( "Trick"),Person( "Track") }; for_each(vec.begin(), vec.end(), []( const Person& p) {p.print(); }); std:: cout << std:: endl; for_each(vec.begin(), vec.end(), []( const Person& p) {p.print2( "Person:"); }); std:: cout << std:: endl; return 0; }
十一、lambda作为Hash函数、排序准则或相等准则
演示案例①
- 例如,下面我们使用deque<>存储自定义的Person对象,然后调用sort()算法对deque<>内的Person对象进行排序,其中排序的时候使用lambda设计排序准则
#include <iostream> #include <deque> #include <algorithm> using namespace std; class Person { public: std:: string firstname()const { return _firstName; } std:: string lastname()const { return _lastName; } private: std:: string _firstName; std:: string _lastName; }; int main() { std:: deque<Person> coll; sort(coll.begin(), coll.end(), []( const Person& p1, const Person& p2) { return p1.firstname() < p2.firstname() || (p1.firstname() == p2.firstname() && p1.lastname() < p2.lastname()); } ); }
演示案例②
- 例如,下面使用lambda指定unordered_set<>的hash函数和等价准则
#include <iostream> #include <unordered_set> using namespace std; class Customer { public: Customer( const std:: string& fn, const std:: string& ln, long n) :fname(fn), lname(ln), no(n) {} std:: string firstname()const { return fname; } std:: string lastname()const { return lname; } long number()const { return no; } private: std:: string fname; std:: string lname; long no; }; int main() { auto hash = []( const Customer& c) { //这个hash_val()是自定义函数,可以参阅《C++标准库》的hashval.hpp return hash_val(c.firstname(), c.lastname()); }; auto eq = []( const Customer& c1, const Customer& c2) { return c1.number() == c2.number(); }; unordered_set<Customer, decltype(hash), decltype(eq)> custset( 10, hash, eq); custset.insert(Customer( "nico", "josuttis", 42)); }
- 因为hash和eq都是lambda,因此需要使用decltype()产生lambda的类型,然后再传递给容器
- 此外你也必须传递一个hash函数和相等准则给构造函数,构造构造函数会调用hash函数和相等准则的默认构造函数,而那对lambda而言是未定义的
十二、lambda的局限
- 局限1:例如,我们想使用lambda作为关联式容器set的排序准则,但是不能直接将lambda表达式声明为set<>的模板参数2,而是要使用decltype()声明lambda的类型
class Person{}; //使用set的默认排序规则存储Person对象 std:: set<Person> coll; //使用自定义的lambda准则排序set中的Person对象 auto cmp = []( const Person& p1, const Person& p2) { return p1.firstname() < p2.firstname() || (p1.firstname() == p2.firstname() && p1.lastname() < p2.lastname()); }; std:: set<Person, decltype(cmp)> coll;
- 局限2:另外,你也必须把lambda对象传给coll的构造函数,构造coll会调用被传入的排序准则的默认构造函数,而lambda没有默认构造函数,也没有赋值运算符
- 基于这些局限,“以class定义某个函数对象作为排序准则”说不定该比较直观些
- 局限3:它们无法拥有“跨越多次调用”都能被保存下来的内部状态,如果你需要这样的状态,必须在外围作用域中声明一个对象或变量,将其以by-reference方式传入lambda
- 与此相比,函数对象允许你封装内部状态(见前面几篇“函数对象”文章的介绍)
- 尽管如此,你还是可以使用lambda为无序容器指出一个hash函数或一个等效准则(见上面“十一”中的演示案例②)
转载:https://blog.csdn.net/qq_41453285/article/details/105486309
查看评论