小言_互联网的博客

C++(标准库):33---Lambda表达式

526人阅读  评论(0)

 一、lambda表达式概述

  • lambda自C++11引入。这带来一种很具为例又十分便利的方法,允许我们提供局部机能,特别是用来具体指明算法和成员函数的细节lambda为C++带来十分重要而深具意义的改善(当我们使用STL时)。因为如今你有了一个直观、易读的方式,可以将独特的行为传递给算法和容器的成员函数
  • 一个lambda表达式可表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数

二、格式

  • 与任何函数相同:一个lambda包含:返回类型、参数列表、函数体
  • 与普通函数不同:lambda使用尾指返回来指定返回类型

  • capture list(捕获列表):是一个lambda所在函数中定义的局部变量的列表(通常为空)
  • return type:返回类型。如果忽略返回类型,lambda根据函数体的代码自动推断出返回类型
  • parameter list:参数列表
  • function body:函数体。如果是一条定义语句花括号后面要有分号;。如果是调用则分号可省去

例如

  • lambda的参数列表/捕获列表为空,忽略了返回类型

   
  1. auto f = [] { return 100; };
  2. cout << f()<< endl; //100

三、向lambda传递参数

  • lambda的实参与形参类型必须匹配,并且不能有默认参数

  
  1. auto f = []( int a, int b)-> int { return a + b; };
  2. cout << f( 1, 2)<< endl;

  
  1. //实现sort的从大到小排序算法
  2. auto f = []( const int& a, const int& b) { return a > b; };
  3. vector< int> vec{ 1, 2, 10, 1545, 151 };
  4. sort(vec.begin(), vec.end(),
  5. []( const int& a, const int& b) { return a > b; });
  6. sort(vec.begin(), vec.end(),f);

四、lambda捕获

  • lambda的函数体中只能使用捕获列表指定的参数,如果捕获列表没有则不能使用

  
  1. int sz= 10;
  2. int a;
  3. //lambda捕获了sz,但是没有捕获a
  4. [sz]( const string &a) {
  5. //a=10; //错误,不能调用a
  6. return a.size() >= sz; //正确
  7. };

  • 捕获分为:
    • 值捕获
    • 引用捕获
    • 隐式捕获
    • 混合隐式捕获

值捕获

  • 值捕获的传参方式是拷贝传参
  • 值的拷贝是在lambda表达式创建时的拷贝,而不是调用时的拷贝
  • 因为是值拷贝,所以当捕获的值在外部改变时,该值在lambda内不会跟随变化
  • 重点:使用值捕获时,该值在lambda内不能被改变(下面会介绍使用mutable关键字改变)

   
  1. size_t v1= 42;
  2. auto f=[v1]() { return v1;};
  3. //auto f=[v1]() {return ++v1;}; //错误,值捕获不能改变值
  4. v1= 0;
  5. auto j=f(); //j=42

引用捕获

  • 引用捕获的值是以引用的方式使用该值
  • 所以当值在外部发生变化时,该值lambda中也会改变
  • 引用捕获可以在lambda内改变该值的值
  • 因为采用引用方式捕获一个变量,所以在调用lambda表达式时,必须保证这个值是存在的(例如局部变量在函数执行完之后消失。例如函数返回一个lambda表达式,而该表达式引用函数内的一个局部变量)

   
  1. size_t v1= 42;
  2. auto f=[&v1]() { return v1;};
  3. v1= 0;
  4. auto j=f(); //j=0

隐式捕获

  • 可以在lambda的[]号中输入符号,让编译器自己捕获的类型。并且可以省去参数
  • 如果在捕获列表写一个&:告诉编译器该lambda使用引用捕获。如果在捕获列表写一个=:告诉编译器该lambda使用值捕获

   
  1. int sz = 2;
  2. vector< string> vec{ "A", "ABC", "ABCD" };
  3. //sz为隐式捕获:值捕获方式
  4. auto wc = find_if(vec.begin(), vec.end(),
  5. [=]( const string &s) { return s.size() >= sz; });

混合隐式捕获方式

  • 使用混合隐式捕获时,第一个元素必须是一个&或者=
  • 显示捕获的变量必须使用与隐式捕获不同的方式:如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因为不能在其名字前使用&。
  • 类似的,如果隐式捕获采用的是值捕获(使用了=),则显式捕获命名变量必须采用引用方式(在名字前使用&)

   
  1. void biggers(vector<string> &words, vector<string>::size_type sz,
  2. ostream &os = cout, char c = ' ')
  3. {
  4. //os隐式捕获:引用捕获;c显式捕获:值捕获方式
  5. for_each(words.begin(), words.end(),
  6. [&, c]( const string &s) {os << s << c; });
  7. //os显式捕获:引用捕获;c隐式捕获:值捕获方式
  8. for_each(words.begin(), words.end(),
  9. [=, &os]( const string &s) {os << s << c; });
  10. }

五、可变lambda(mutable关键字)

  • 默认情况下:lambda值捕获时,该值在lambda内不能被改变
  • 使用mutable关键字之后:该值可以在lambda表达式内被改变。但是lambda内的值仍与外界值的改变无关

  
  1. int val= 1;
  2. auto f = [val]() mutable { return ++val; };
  3. val = 0; //将val变为0
  4. auto j = f(); //j=2
  5. cout << j << endl;

六、指定lambda的返回类型

  • 一般情况下,lambda会自动推断出表达式的返回值类型
  • 但是某些情况下需要显式的给出返回值类型,返回值类型使用尾指返回类型

  
  1. //隐式的返回类型int
  2. []( int i) { return i < 0 ? -i : i; }

  
  1. //显式给出返回类型int
  2. []( int i)-> int
  3. {
  4. if (i < 0) return -i;
  5. else return i;
  6. };

七、lambda的类型

  • lambda的类型,是个不具名的function object(或functor)
  • 如果你想使用lambda的类型声明对象,可以借助于template std::function<>或auto
  • 如果你想写下该类型,则可以使用decltype()(见下面“十一”的演示案例②)

演示案例


   
  1. #include <iostream>
  2. #include <functional>
  3. std::function< int( int, int)> returnLambda()
  4. {
  5. return []( int x, int y) { return x*y; };
  6. }
  7. int main()
  8. {
  9. auto f = returnLambda();
  10. std:: cout << f( 3, 7); //21
  11. }

八、lambda与binder

  • 例如我们在前面一篇文章中使用函数对象和函数适配器binder来书写代码,如下所示:

  
  1. #include <iostream>
  2. #include <functional>
  3. using namespace std::placeholders;
  4. int main()
  5. {
  6. auto plus10 = std::bind( std::plus< int>(), _1, 10);
  7. //相等于调用std::plus<int>(7,10)
  8. std:: cout << "7+10=" << plus10( 7) << std:: endl;
  9. auto plus10times2 = std::bind( std::multiplies< int>(),
  10. std::bind( std::plus< int>(), _1, 10),
  11. 2);
  12. //相等于调用std::multiplies<int>(std::plus<int>(10,10),2)
  13. std:: cout << "(10+10)*2=" << plus10times2( 10) << std:: endl;
  14. //相等于调用std::multiplies<int>(std::multiplies<int>(7,7),7)
  15. auto pow3 = std::bind( std::multiplies< int>(),
  16. std::bind( std::multiplies< int>(), _1, _1),
  17. _1);
  18. std:: cout << "7*7*7=" << pow3( 7) << std:: endl;
  19. //相等于调用std::divides<double>(7,49)
  20. auto inversDivide = std::bind( std::divides< double>(), _2, _1);
  21. std:: cout << "7/49=" << inversDivide( 49, 7) << std:: endl;
  22. }

  • 对于上面的binder,代码比较复杂,如果使用lambda则可以如下所示:

  
  1. //头文件同上
  2. int main()
  3. {
  4. auto plus10 = []( int i) { return i + 10; };
  5. std:: cout << "7+10=" << plus10( 7) << std:: endl;
  6. auto plus10times2 = []( int i) { return (i + 10) * 2; };
  7. std:: cout << "(10+10)*2=" << plus10times2( 10) << std:: endl;
  8. auto pow3 = []( int i) { return i*i*i; };
  9. std:: cout << "7*7*7=" << pow3( 7) << std:: endl;
  10. auto inversDivide = []( double d1, double d2) { return d2 / d1; };
  11. std:: cout << "7/49=" << inversDivide( 49, 7) << std:: endl;
  12. }

九、lambda与带有状态的函数对象

演示案例①

  • lambda是不带状态的
  • 例如下面我们修改C++(标准库):31文章中的演示案例:

   
  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. int main()
  6. {
  7. vector< int> coll{ 1, 2, 3, 4, 5, 6, 7, 8 };
  8. long sum = 0;
  9. for_each(coll.begin(), coll.end(), [&sum]( int elem) {sum += elem; });
  10. double mv = static_cast< double>(sum) / static_cast< double>(coll.size());
  11. }

演示案例②

  • 例如我们修改C++(标准库):31文章中“七”的演示案例。如下:

   
  1. #include <iostream>
  2. #include <list>
  3. #include <algorithm>
  4. using namespace std;
  5. int main()
  6. {
  7. std:: list< int> coll{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  8. for ( const auto elem : coll)
  9. std:: cout << elem << " ";
  10. std:: cout << std:: endl;
  11. std:: list< int>::iterator pos;
  12. int count = 0;
  13. pos=remove_if(coll.begin(),coll.end(),
  14. [count]( int) mutable { return ++count == 3; }
  15. );
  16. coll.erase(pos, coll.end());
  17. for ( const auto elem : coll)
  18. std:: cout << elem << " ";
  19. }
  • 上面运行的代码与那篇文章演示案例运行的结果一样,第3和第6个元素都会被移除:
    • lambda使用了mutable,remove_if()算法在执行过程中复制了一份,于是存在两个lambda对象都移除第三元素,导致重复行为

  • 如果你已by reference方式传递实参,那么结果就正确了,只移除第三个元素:

   
  1. pos=remove_if(coll.begin(),coll.end(),
  2. [&count]( int) { return ++count == 3; }
  3. );

十、lambda调用全局函数和成员函数

演示案例(调用全局函数)

  • 下面的演示案例是《C++标准库》p490页演示案例的lambda版本。代码如下:

   
  1. #include <iostream>
  2. #include <string>
  3. #include <locale>
  4. #include <algorithm>
  5. using namespace std;
  6. char myTopper(char c)
  7. {
  8. std::locale loc;
  9. return std::use_facet< std::ctype< char>>(loc). toupper(c);
  10. }
  11. int main()
  12. {
  13. string s("Internationlization");
  14. string sub("Nation");
  15. string::iterator pos;
  16. pos=search(s.begin(),s.end(),sub.begin(),sub.end(),
  17. []( char c1, char c2) { return myTopper(c1) == myTopper(c2); }
  18. );
  19. if (pos != s.end())
  20. std:: cout << "\"" << sub << "\" is part of \"" << s << "\"" << std:: endl;
  21. }

演示案例(调用成员函数)


   
  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <algorithm>
  5. using namespace std;
  6. class Person
  7. {
  8. private:
  9. std:: string name;
  10. public:
  11. Person( const std:: string& _name) :name(_name) {}
  12. void print()const { std:: cout << name << std:: endl; }
  13. void print2(const std::string& prefix)const { std:: cout << prefix << name << std:: endl; }
  14. };
  15. int main()
  16. {
  17. std:: vector<Person> vec{ Person( "Tick"),Person( "Trick"),Person( "Track") };
  18. for_each(vec.begin(), vec.end(), []( const Person& p) {p.print(); });
  19. std:: cout << std:: endl;
  20. for_each(vec.begin(), vec.end(), []( const Person& p) {p.print2( "Person:"); });
  21. std:: cout << std:: endl;
  22. return 0;
  23. }

十一、lambda作为Hash函数、排序准则或相等准则

演示案例①

  • 例如,下面我们使用deque<>存储自定义的Person对象,然后调用sort()算法对deque<>内的Person对象进行排序,其中排序的时候使用lambda设计排序准则

   
  1. #include <iostream>
  2. #include <deque>
  3. #include <algorithm>
  4. using namespace std;
  5. class Person
  6. {
  7. public:
  8. std:: string firstname()const { return _firstName; }
  9. std:: string lastname()const { return _lastName; }
  10. private:
  11. std:: string _firstName;
  12. std:: string _lastName;
  13. };
  14. int main()
  15. {
  16. std:: deque<Person> coll;
  17. sort(coll.begin(), coll.end(),
  18. []( const Person& p1, const Person& p2) { return p1.firstname() < p2.firstname() || (p1.firstname() == p2.firstname() && p1.lastname() < p2.lastname()); }
  19. );
  20. }

演示案例②

  • 例如,下面使用lambda指定unordered_set<>的hash函数和等价准则

   
  1. #include <iostream>
  2. #include <unordered_set>
  3. using namespace std;
  4. class Customer
  5. {
  6. public:
  7. Customer( const std:: string& fn, const std:: string& ln, long n) :fname(fn), lname(ln), no(n) {}
  8. std:: string firstname()const { return fname; }
  9. std:: string lastname()const { return lname; }
  10. long number()const { return no; }
  11. private:
  12. std:: string fname;
  13. std:: string lname;
  14. long no;
  15. };
  16. int main()
  17. {
  18. auto hash = []( const Customer& c) {
  19. //这个hash_val()是自定义函数,可以参阅《C++标准库》的hashval.hpp
  20. return hash_val(c.firstname(), c.lastname());
  21. };
  22. auto eq = []( const Customer& c1, const Customer& c2) {
  23. return c1.number() == c2.number();
  24. };
  25. unordered_set<Customer, decltype(hash), decltype(eq)> custset( 10, hash, eq);
  26. custset.insert(Customer( "nico", "josuttis", 42));
  27. }
  • 因为hash和eq都是lambda,因此需要使用decltype()产生lambda的类型,然后再传递给容器
  • 此外你也必须传递一个hash函数和相等准则给构造函数,构造构造函数会调用hash函数和相等准则的默认构造函数,而那对lambda而言是未定义的

十二、lambda的局限

  • 局限1:例如,我们想使用lambda作为关联式容器set的排序准则,但是不能直接将lambda表达式声明为set<>的模板参数2,而是要使用decltype()声明lambda的类型 

   
  1. class Person{};
  2. //使用set的默认排序规则存储Person对象
  3. std:: set<Person> coll;
  4. //使用自定义的lambda准则排序set中的Person对象
  5. auto cmp = []( const Person& p1, const Person& p2) { return p1.firstname() < p2.firstname() || (p1.firstname() == p2.firstname() && p1.lastname() < p2.lastname()); };
  6. 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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场