C++(标准库):33---Lambda表达式
发布日期:2021-06-29 22:36:07 浏览次数:2 分类:技术文章

本文共 9666 字,大约阅读时间需要 32 分钟。

 一、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
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
vec{ "A","ABC","ABCD" };//sz为隐式捕获:值捕获方式auto wc = find_if(vec.begin(), vec.end(), [=](const string &s) {return s.size() >= sz; });

混合隐式捕获方式

  • 使用混合隐式捕获时,第一个元素必须是一个&或者=
  • 显示捕获的变量必须使用与隐式捕获不同的方式:如果隐式捕获是引用方式(使用了&),则显式捕获命名变量必须采用值方式,因为不能在其名字前使用&。
  • 类似的,如果隐式捕获采用的是值捕获(使用了=),则显式捕获命名变量必须采用引用方式(在名字前使用&)
void biggers(vector
&words, vector
::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变为0auto j = f(); //j=2cout << 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 
#include
std::function
returnLambda(){ return [](int x, int y) {return x*y; };}int main(){ auto f = returnLambda(); std::cout << f(3, 7); //21}

八、lambda与binder

  • 例如我们在前面一篇文章中使用函数对象和函数适配器binder来书写代码,如下所示:
#include 
#include
using namespace std::placeholders; int main(){ auto plus10 = std::bind(std::plus
(), _1, 10); //相等于调用std::plus
(7,10) std::cout << "7+10=" << plus10(7) << std::endl; auto plus10times2 = std::bind(std::multiplies
(), std::bind(std::plus
(), _1, 10), 2); //相等于调用std::multiplies
(std::plus
(10,10),2) std::cout << "(10+10)*2=" << plus10times2(10) << std::endl; //相等于调用std::multiplies
(std::multiplies
(7,7),7) auto pow3 = std::bind(std::multiplies
(), std::bind(std::multiplies
(), _1, _1), _1); std::cout << "7*7*7=" << pow3(7) << std::endl; //相等于调用std::divides
(7,49) auto inversDivide = std::bind(std::divides
(), _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 
#include
#include
using namespace std;int main(){ vector
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
(sum) / static_cast
(coll.size());}

演示案例②

  • 例如我们修改C++(标准库):31文章中“七”的演示案例。如下:
#include 
#include
#include
using namespace std;int main(){ std::list
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
::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 
#include
#include
#include
using namespace std;char myTopper(char c){ std::locale loc; return std::use_facet
>(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;}

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

  • 下面的演示案例是前面一篇文章()演示案例的lambda版本。代码如下:
#include 
#include
#include
#include
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
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 
#include
#include
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
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 
#include
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
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
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
coll;
  • 局限2:另外,你也必须把lambda对象传给coll的构造函数,构造coll会调用被传入的排序准则的默认构造函数,而lambda没有默认构造函数,也没有赋值运算符
  • 基于这些局限,“以class定义某个函数对象作为排序准则”说不定该比较直观些 
  • 局限3:它们无法拥有“跨越多次调用”都能被保存下来的内部状态,如果你需要这样的状态,必须在外围作用域中声明一个对象或变量,将其以by-reference方式传入lambda
  • 与此相比,函数对象允许你封装内部状态(见前面几篇“函数对象”文章的介绍)
  • 尽管如此,你还是可以使用lambda为无序容器指出一个hash函数或一个等效准则(见上面“十一”中的演示案例②)

转载地址:https://dongshao.blog.csdn.net/article/details/105486309 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:C++(标准库)34---STL算法之(算法总体概述)
下一篇:C++(标准库):32---STL函数对象之(预定义的函数对象、函数适配器)

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月17日 14时31分01秒