学习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的各个主要部分:
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
- 学习lambda表达式
- Lambda表达式学习
- Lambda表达式学习记录
- Lambda 表达式学习总结
- Lambda表达式学习笔记
- lambda表达式学习
- java8 Lambda 表达式 学习
- 学习lambda表达式
- java8学习 -- lambda表达式
- java学习(lambda表达式)
- Python--lambda表达式学习
- java8学习-Lambda表达式
- Java8 Lambda表达式学习
- lambda表达式学习1
- 【C#学习】lambda表达式
- Lambda表达式 学习
- java8 Lambda表达式学习
- Lambda表达式学习
- 深度学习与深层神经网络等概念
- poj 2083 Factal
- opencv(八)---物体跟踪
- python numpy.shape 和 numpy.reshape函数
- Shipyard -- Docker可视化管理工具安装与配置
- 学习Lambda表达式
- Programming Language Foundations.pdf 英文原版 免费下载
- VPS+SS搭建
- BZOJ2260 商店购物 BZOJ4349 最小树形图 坠小树形图 朱刘算法
- LeetCode-019 Remove Nth Node From End of List
- python字符串的拼接,文件的读入写出
- 虚幻4地形混合材质蓝图连接
- 【Scikit-Learn 中文文档】交叉验证
- Introducing Elixir.pdf 英文原版 免费下载