学习Lambda表达式

来源:互联网 发布:瓷砖的重量怎么算法 编辑:程序博客网 时间:2024/06/15 17:30

引言

Lambda 表达式是 C++11 中最重要的新特性之一,它提供了一个类似匿名函数的特性。
在C++中使用Lambda的主要场景是回调(callback)。
Lambda 跟普通的函数有相似之处,例如:
- 你可以给它传递参数
- 可以使用函数外面的参数
- 可以有返回值
- 花括号里包含的body可以任意复杂

当然,它跟普通的函数也有很多不同,比如,它没有名字,不能使用默认参数等。
在更深入地了解Lambda之前,我们按照惯例先从简单的Hello World 说起。

1、从hello world 说起

来看下面的代码片段。

#include <iostream>#include <string>void func1(const std::string& str) {  std::cout << str << std::endl;  return;}int main() {  auto func2 = [](const std::string& str) -> void {    std::cout << str << std::endl;  };  std::string str("Hello World!");  func1(str);  func2(str);  return 0;}

上面的代码片段很简单。
定义一个Lambda函数并保存在func2,后续需要的时候直接调用。上述func1和func2的输出完全一致。
细心的读者可能会发现,Lambda跟函数对象是不是更加相似?

struct Functor {  void operator()(const std::string& str) {    std::cout << str << std::endl;    return;  }};// in main , you can use it like this:std::string str("hello world");Functor functor;functor(str); // 输出结果跟上面一样

很有用吧?现在我们来看下Lambda表达式的语法:
Lambda表达式语法

接下来细分一下Lambda的各个主要部分:

2、参数列表(Parameter List)

用于参数传递,这个跟普通的函数调用一样。可以传值,也可以传指针和引用。
如果你不需要传递参数,这里可以为空。
跟普通函数的差异在于:
- 不能使用默认参数
- 不能使用无名参数

// 示例1 传递参数列表auto func3 = [](int a, int b) -> void {  std::cout << "a:" << a << ", b:" << b << std::endl;  return;};func3(10, 20); // 输出 a:10, b:20

再看个例子,你会发现很多场景下使用Lambda简直太方便了。

#include <vector>#include <algorithm>// 假设我们想遍历一个vector中的所有元素// 如果不使用Lambda,你可以定义这个函数void display(int x) {  std::cout << x << " ";}std::vector<int> vec = {10, 20, 30, 40};std::for_each(vec.begin(), vec.end(), &display);// 使用lambda,简化如下,不再需要displaystd::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << " "; });

3、捕获局部变量(Capture Clause for Local Variable)

主要用于参数传递,使得body内除了可以访问参数列表外,还可以访问外部其它允许的参数。
又分两种:by value方式(传值)和by reference 方式(传引用)。
- by value 方式捕获参数列表
body内需要访问参数列表之前的变量时,只需要在捕获列表中指定它们的名字即可。当然这些变量必须在当前作用域内。
这种方式,Lambda body内使用的是捕获对象的副本。
示例:

int main() {  // 局部变量  int i = 10;  std::string str = "hello world";  double d = 0.2;  // 捕获参数列表内的i和str在函数提内可以访问  // d 不在捕获列表内,不可访问  auto func = [i, str]() {    cout << "i=" << i << endl;      // OK    cout << "str=" << str << endl;  // OK    cout << "d=" << d << endl;      // error,不在捕获列表内,不可访问    i += 1; // error, const 不可修改  };  return 0;}

i和str在捕获参数列表内,会以by value的方式拷贝到lambda body内,因此可访问。
body内如果试图修改这些参数,会出错,因为在body内它们相当于const的。
如果需要在body内修改参数,可以增加 mutable 修饰符,如下:

  auto func = [i, str]() mutable {    i += 1; // OK  }

现在body内可以修改i了,但是这个修改仅限于body内有效,不会影响函数外面原来的i,原因很简单,它们是by value方式传递的,body内修改的只是i的一个副本。

  int i = 10;  auto func = [i]() mutable {    cout << i << endl;  // 10    i++;    cout << i << endl;  // 11  };  func();  cout << i << endl;  // 10 body内修改的只是i的一个副本,不会影响原来的i
  • by reference 方式捕获参数列表
    跟普通的函数调用可以传引用一样,捕获参数列表也可以以by reference的方式来捕获。
    这种情况下,Lambda body内使用的是捕获对象的实体。
std::string msg = "hello";int count = 10;auto func = [&msg, &count]() {  // ...}

这种情况以传引用(by reference)的方式来捕获。
在lambda body内不仅可以访问msg和count的值,还能修改它们,并且修改后的值会影响body外的变量。(跟普通函数传引用效果一样)。

std::string msg = "hello";int count = 10;auto func = [&msg, &count]() {  cout << "msg:" << msg << endl;  // 输出 msg:hello  cout << "count:" << count << endl;  // 输出 count:10  // 修改它们  msg = "world";  count = 20;  cout << "msg:" << msg << endl;    // 输出 msg:workd  cout << "count:" << count << endl;  // 输出 count:20};// 这里调用lambdafunc();// 再次验证变量cout << "msg:" << msg << endl;    // 输出 msg:workdcout << "count:" << count << endl;  // 输出 count:20
  • 捕获所有参数(by value)
    如果需要捕获多个参数,在[]捕获列表中写出所有参数名是可以的,但显然不够优雅。
    可以直接使用[=],让编译器去推导使用了哪些参数。
    这种情况下lambda函数提内可以访问外部所有的参数(当然要在作用域允许范围内)
std::string str1 = "hello";std::string str2 = "world";int i1 = 10;int i2 = 20;auto func = [=]() {  cout << str1 << endl; // OK  cout << str2 << endl; // OK  cout << i1 << endl; // OK  cout << i2 << endl; // OK};func();

这种情况函数提内能访问所有局部变量,by value方式。跟之前介绍的一样,也是const的,只能读不能改。
就算增加mutable修饰符后能修改,也只是修改它们的副本而已。

  • 捕获所有参数(by reference)
    如果需要以by refenence方式捕获所有参数,只需使用[&]即可,编译器会推导使用了哪些参数,并以传引用的方式传递。
std::string str1 = "hello";std::string str2 = "world";int i1 = 10;int i2 = 20;auto func = [&]() {  cout << str1 << endl; // OK  cout << str2 << endl; // OK  cout << i1 << endl; // OK  cout << i2 << endl; // OK  str1 += " test "; //  OK, by reference};func();
  • 混合使用by value和by reference
    这是允许的。但是需要注意特化的情况。
    也即,捕获列表后面出现的参数会覆盖之前出现的。
std::string str1 = "hello";std::string str2 = "world";int i1 = 10;int i2 = 20;// 以by value的方式捕获所有参数,但str2除外,它以by reference的方式捕获.auto func = [=, &str2]() {  cout << str1 << endl; // OK  cout << str2 << endl; // OK  cout << i1 << endl; // OK  cout << i2 << endl; // OK  str1 = "modifyed";  // error ,by value, const  str2 = "modifyed";  // OK, by reference  i1 = 30;  // error ,by value ,const};func();

需要注意的是,lambda body内使用的参数,一定要确保这些变量的生存期。否则会出现未定义行为,危险的。
C++ Lambda闭包并不能通过对捕获对象的引用来延长其生命周期。
例如

std::function<void ()> get_callback() {  // 注意这是函数内的局部变量,是在栈上的  int counter = 10;  auto func = [&counter]() mutable {    counter = 20; // modify it  };  return func;  // 函数接收后,counter 就被销毁了}int main() {  std::function<void ()> func = get_callback();  // 这里调用lambda函数  // 这种情况会有问题,因为这时候get_callback已经调用结束了  // get_callback 内的局部变量counter已经被销毁  // 而 func body内仍然试图去修改该变量的引用  // 结果为定义的,可能会core  func();  return 0;}

4、捕获成员变量(Capture Clause for member variables )

设想我们有一个类MyCounter,其中有一个成员函数里面使用Lambada表达式来访问、修改某些成员变量。
如何做?
先看一个错误示例:

// 统计vector中有多少个偶数class MyCounter { private:  int counter = 0; public:  MyCounter() {}  ~MyCounter() {}  int GetCounter() { return counter; }  void CountVec(const std::vector<int>& vec) {    // 遍历vec的元素并统计偶数个数    // 更新计数器时,试图直接访问类的成员变量counter(通过捕获列表获得)    // 但这里会编译失败    std::for_each(vec.begin(), vec.end(), [counter](int element) {      if (element % 2 == 0) counter++;    });  }};// 统计vector中有多少个偶数class MyCounter { private:  int counter = 0; public:  MyCounter() {}  ~MyCounter() {}  int GetCounter() { return counter; }  void CountVec(const std::vector<int>& vec) {    // 遍历vec的元素并统计偶数个数    // 更新计数器时,试图直接访问类的成员变量counter(通过捕获列表获得)    // 但这里会编译失败    std::for_each(vec.begin(), vec.end(), [counter](int element) {      if (element % 2 == 0) counter++;    });  }};int main() {  std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8};  MyCounter counter;  counter.CountVec(vec);  cout << counter.GetCounter() << endl;  return 0;}

解决办法,Lambda捕获列表中,捕获this即可。
正确示例如下:

// 统计vector中有多少个偶数class MyCounter { private:  int counter = 0; public:  MyCounter() {}  ~MyCounter() {}  int GetCounter() { return counter; }  void CountVec(const std::vector<int>& vec) {    // 捕获列表,捕获this,Lambda body内即可范围该成员变量    std::for_each(vec.begin(), vec.end(), [this](int element) {      if (element % 2 == 0) counter++;    });  }};

5、Lamdba 的一些常见用法示例

到目前位置,Lambda的基本用法介绍完毕了。
下面来看一下实际程序中如果使用Lambda来提供各种便利。
std::find_if

  std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8};  // 从vec中查找6  auto res =      std::find_if(vec.begin(), vec.end(), [](int elem) { return elem == 6; });  cout << *res << endl;

std::remove_if

  std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8};  // 从vec中删除6  auto new_end = std::remove_if(vec.begin(), vec.end(),                                [](int elem) { return elem == 6; });  vec.erase(new_end); // 因为remove_if并没有真正删除,只是将其移动到最后了  for (auto i : vec) {    cout << i << " ";  }  cout << endl;  // 1 2 3 4 5 7 8

std::count_if

  std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8};  // 统计vec中偶数个数  auto count = std::count_if(vec.begin(), vec.end(),                             [](int elem) { return elem % 2 == 0; });  cout << count << endl; // 4

std::sort

  // 按照从小到大排序  std::vector<int> vec = {10, 52, 3, 7, 9, 21, 56, 6};  std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });  for (auto i : vec) {    cout << i << " ";  }  cout << endl; // 输出排序后的 3 6 7 9 10 21 52 56
原创粉丝点击