C++ lambda 表达式
目录一、基本用法1、语法形式二、捕获列表2.1捕获的作用范围2.2捕获特点2.3 值捕获2.3.1值捕获时不捕获数组某个下标的值2.4引用捕获一般不用2.5捕获指针2.6初始化捕获三、捕获具有延时性四、lambda应用拓展概述Lambda表达式是 c11 引入的一个语法糖来源于函数式编程的概念本质可以理解为函数。它可以用来定义并创建匿名的函数对象主要用于方便编程避免全局变量的定义并且变量安全。Lambda表达式的语法类似于一个函数定义但它不需要函数名可以直接定义并使用。Lambda表达式相比于普通函数和普通类有以下几个优点简洁、灵活和安全。Lambda表达式本质上是一个匿名类的对象因此它可以赋值给一个函数指针或函数引用也可以作为模板参数传递给一个泛型函数或类。C14 和 C17 对 Lambda表达式进行了一些扩展和改进使得 Lambda表达式更加强大和灵活主要有以下几个方面泛型 Lambda、初始化捕获和捕获 this 指针优点lambda就地匿名定义目标函数或函数对象不需要额外写一个命名函数或者函数对象避免代码功能膨胀和分散以更直接的方式去写程序有好的可读性和可维护性。并在需要的时间地点实现功能闭包程序更灵活一、基本用法lambda表达式定义了一个匿名函数并且可以捕获一定范围内的变量1、语法形式[capture list] (parameter list) - return type { function body }[捕获列表] (参数表 opt - 返回值类型 {函数体}捕获列表指定 Lambda表达式可以访问的外部变量以及是按值还是按引用的方式访问参数列表用于表示 Lambda表达式的参数可以为空表示没有参数也可以和普通函数 一样指定参数的类型和名称还可以在 c14 中使用auto关键字来实现泛型参 数。opt函数选项默认为常方法const可以修改为mutable-可以理解为指向简单示例//定义一个Add函数并调用它原始写法 int Add(int a, int b) { return a b; } int main() { int c Add(10, 2); cout c endl; }利用lambda表达式实现上面函数//lambda表达式 auto func [](int a, int b)-int {return a b; }; //lambda表达式调用 //方法一先定义表达式在调用 auto c func(12, 20); cout c endl; //方法二在定义的同时表达式后面加边定义边调用 c [](int a)-int {cout a endl; return a 10; }(8);//8在这里直接作为参数lambda表达式中指向-和函数返回值类型要么同时存在要么同时省略示例auto fun1 [](int a) - int {return a 10; };//ok auto fun1 [](int a) {return a 10; };//ok auto fun1 [](int a) int {return a 10; };//error auto fun1 [](int a) - {return a 10; };//error在简写lambda表达式时捕获列表函数体必不可缺[]{}以下示例均是正确写法仅仅是定义了lambda表达式没有调用 auto fun5 []() {cout [] endl; };//参数为空 auto fun6 [] {cout [] endl; };//省略参数 auto fun7 [] {return 10; }; auto fun8 [] {}; 为什么要用到autolambda表达式实际上定义了一个匿名函数不知道函数名auto可以根据表达式内容自动推导出变量的类型二、捕获列表2.1捕获的作用范围捕获只作用域函数域、块域、类域不捕获全局变量全局对象示例捕获不作用于全局变量此处的lambda不捕获 tmp的值#includeiostream using namespace std; class Int // { private: int value; public: //构造函数 Int(int x 0) :value(x) { cout Create Int Object: this endl; } //析构 ~Int() { cout Destroy Int Object : this value: value endl; } //移动构造 Int(const Int it) :value(it.value) { cout this Copy Create Int Object: it endl; } // 重载 Int operator(const Int it) { if (this ! it) { value it.value; } cout this operator(const Int ) it endl; return *this; } //移动构造 Int(Int it) :value(it.value) { cout this Move Copy Create Int Object it endl; it.value -1; } //移动赋值 Int operator(Int it) { value it.value; cout Move operator(Int ) endl; return *this; } void SetValue(int x) { value x; } int GetValue() const { return value; } Int operator(const Int it) const { return Int(this-value it.value); } ostream operator(ostream out) const { out value; return out; } istream operator(istream in) { in value; return in; } }; ostream operator(ostream out, const Int it) { return it out; } istream operator(istream in, Int it) { it in; return in; } /////////////////////////////////////////////// //全局变量像太阳一样所有对象都可以用 Int tmp(10); void func() { tmp.SetValue(100); } int main() { cout tmp.GetValue() endl; auto funa []() { cout tmp.GetValue() endl; };//捕获不作用于全局变量 }这段代码输出2次10调用GetValue打印一次析构函数时调用一次示例基于上面代码去掉全局对象tmp测试以下main函数2.2捕获特点1捕获一个对象时会调用拷贝构造函数调用lambda表达式时会创建一个副本这个副本不可修改2捕获的值只可读不可修改因为函数选项位置默认为const如果将函数选项设置为mutable则捕获来的值可以修改int main() { cout lambda endl; Int a(10), b(20); //定义一个lambda表达式捕获列表里面a表示此处捕获a的值 auto fun [a](int x)-int { //此处的捕获只可读不可写相当于加上了const常性 //a.SetValue(11);//error 只可以读到a的值不可修改a的值 return x a.GetValue(); }; //调用lambda表达式给里面参数x传递实参12 int x fun(12); cout x endl; }代码运行结果将函数选项设置为mutable则捕获的值可读可写测试代码int main() { cout lambda endl; Int a(10), b(20); //定义一个lambda表达式捕获列表里面a表示此处捕获a的值 auto fun [a](int x)mutable-int { //此处的捕获可读可写 a.SetValue(11); return x a.GetValue(); }; //次数二者相加输出的结果为23 //调用lambda表达式给里面参数x传递实参12 int x fun(12); cout x endl; }2.3 值捕获---只读外部变量捕获所有外部变量Lambda 内是变量的副本默认不可修改,只读外部变量不修改总结1只能捕获在lambda表达式之前的之后创建的无法捕获不会一次性全部捕获所有值需要用谁就捕获谁2不可以利用 arr[0] 捕获数组编译器会认为捕获整个数组比如有10个元素的数组救火调用10次拷贝构造示例int main() { cout lambda endl; Int a(10), b(20); //定义一个lambda表达式捕获列表 表示捕获lambda表达式之前所有 auto fun [](int x)mutable-int { a.SetValue(100); int y a.GetValue(); return x x; }; //调用lambda表达式给里面参数x传递实参12 int x fun(12); cout x endl; }程序运行结果结果分析main函数中在lambda表达式之前虽然已经创建了对象a,b 并且创建在lambda之前但是在函数体里面只用到了a故只捕获a 即用谁捕获谁不用不捕获2.3.1值捕获时不捕获数组某个下标的值数组无法满足按照下标捕获只能一次性捕获全部如果是一个很多数的数组则会导致拷贝构造多次int main() { cout lambda endl; Int a(10), b(20); Int ar[4];//数组 //定义一个lambda表达式捕获表达式之前所有的值 auto fun [](int x)mutable-int { //auto fun [ar[0]] (int x) mutable -int //error 这种写法不能实现捕获数组0下标的元素vs会报错 a.SetValue(100); int y a.GetValue(); return x x; }; //调用lambda表达式给里面参数x传递实参12 int x fun(12); cout x endl; }运行结果构建整个数组调用4次拷贝构造2.4引用捕获一般不用----修改外部变量在捕获列表中使用加变量名表示将该变量的引用传递到 Lambda 表达式中作为一个数据成员。引用捕获的变量在 Lambda 表达式调用时才确定会随着外部变量的变化而变化。示例int x 10; auto f [x] (int y) - int { return x y; }; // 引用捕获 x x 20; // 修改外部的 x cout f(5) endl; // 输出 25受外部 x 的影响特点1捕获的值可读可写2不创建副本不调用拷贝构造函数3表达式可以反复多次调用4一般不用为什么不用如下面示例a 的值可以在lambda 内部被改变也可在外部被修改相当于被共享了在多线程环境下会导致同一个值被不同线程反复修改int main() { cout lambda endl; Int a(10), b(20); //定义一个lambda表达式捕获a //引用捕获 auto fun [a](int x)mutable-int { cout a.GetValue() endl; a.SetValue(100); return x 10; }; // lambda第一次调用 int c fun(10); cout c endl; a.SetValue(200); //lambda 第二次调用 c fun(20); cout c endl; }运行结果示例 不加对象或者变量名表示捕获所有int func(Int c) { Int a(10), b(20); //此处的 可以引用lambda表达式之前所有对象即a,b,c auto fun [](int x)-int { c.SetValue(100); return x 10; }; // fun(0);//若执行该语句程序输出100 不执行输出0 定义表达式≠执行 return 0; } int main() { Int x(0); func(x); cout x endl; }2.5捕获指针特点捕获指针时指针自身不可修改但是指针所指之物可以修改区别于普通值捕获示例class Object { Int val; public: Object(int x 0):val(x){} void func(int a, int b)//this { auto fun1 []() { // this nullptr;//error 指针本身不可修改 int x a b; int y val.GetValue(); this-val.Setvalue(100);//指针所指之物可以修改 cout x y endl; }; fun1(); } };2.6初始化捕获C14 引入的一种新的捕获方式它允许在捕获列表中使用初始化表达式从而在捕获列表中创建并初始化一个新的变量而不是捕获一个已存在的变量。这种方式可以使用auto关键字来推导类型也可以显式指定类型。这种方式可以用来捕获只移动的变量或者捕获this指针的值。例如int x 10; auto f [z x 5] (int y) - int { return z y; }; // 初始化捕获 z相当于值捕获 x 5 x 20; // 修改外部的 x cout f(5) endl; // 输出 20不受外部 x 的影响三、捕获具有延时性值捕获的变量在 Lambda 表达式定义时就已经确定不会随着外部变量的变化而变化。值捕获的变量默认不能在 Lambda 表达式中修改除非使用mutable关键字。例如int x 10; auto f [x] (int y) - int { return x y; }; // 值捕获 x x 20; // 修改外部的 x cout f(5) endl; // 输出 15不受外部 x 的影响例如int main() { int v 100; auto f [v]()-int {return v; }; v 10; auto f1 [v]()-int {return v; }; int x f(); cout x endl; int x1 f1(); cout x1 endl; }程序输出100110对于 f 捕获v的值调用表达式时在其内部生成v的副本保存起来 故x输出100对于 f1 在调用之前v10 保存的是v10的结果 故x1输出110四、lambda应用场景lambda表达式在C中广泛应用于多线程多进程以及仿函数中Lambda 最适合「短小、临时、仅用一次」的逻辑4.1 结合多线程完整的捕获示例#include iostream #include thread #include mutex std::mutex mtx; int num 0; int main() { // 示例1空捕获 → 不访问外部变量 std::thread t1([]() { std::lock_guardstd::mutex lock(mtx); std::cout 空捕获仅打印不访问外部变量\n; }); // 示例2按引用捕获num → 可修改原变量 std::thread t2([]() { std::lock_guardstd::mutex lock(mtx); num; // 修改的是外部的num std::cout 按引用捕获num num \n; }); // 示例3按值捕获num → 仅访问副本默认不可修改 std::thread t3([]() { std::lock_guardstd::mutex lock(mtx); // num; // 报错按值捕获的副本默认不可修改 std::cout 按值捕获num副本 num \n; }); // 示例4按值捕获mutable → 可修改副本不影响原变量 std::thread t4([]() mutable { num; // 修改的是副本原num不变 std::cout mutable按值副本num num \n; }); t1.join(); t2.join(); t3.join(); t4.join(); std::cout 主线程num num \n; // 输出1仅t2修改了原变量 return 0; }运行结果空捕获仅打印不访问外部变量按引用捕获num 1按值捕获num副本 1mutable按值副本num 2主线程num 14.2 与STL结合简化排序、遍历#include iostream #include vector #include algorithm int main() { std::vectorint vec {3, 1, 4, 2}; // Lambda作为排序规则降序 std::sort(vec.begin(), vec.end(), [](int a, int b) { return a b; // 降序排序 }); // Lambda遍历打印 std::for_each(vec.begin(), vec.end(), [](int x) { std::cout x ; // 输出4 3 2 1 }); return 0; }五、Lambda 的关键特性匿名性Lambda 没有函数名只能在定义的地方直接使用或赋值给std::function保存捕获仅作用于定义时按值捕获的是「定义时的变量副本」后续外部变量修改不会影响 Lambda 内的副本生命周期Lambda 是临时对象若需长期使用比如传给其他函数可赋值给std::function// 保存Lambda到std::function后续可多次调用 std::functionint(int, int) add [](int a, int b) { return a b; }; std::cout add(1, 2) \n; // 输出3