淘宝客网站建设平台,漯河英文网站建设,wordpress noren,网络服务器主要有目录 1、lambda表达式
1.1 语法
1.2 捕捉列表
1.3 原理
1.4 捕捉列表 VS 函数参数
1.5 Lambda 在实际开发中的典型应用
2、包装器
2.1 function
2.2 bind
3、可变参数模板
3.1 概念与使用
3.2 包扩展 1、lambda表达式
1.1 语法
# Lambda 表达式本质上是一个匿名函…目录1、lambda表达式1.1 语法1.2 捕捉列表1.3 原理1.4 捕捉列表 VS 函数参数1.5 Lambda 在实际开发中的典型应用2、包装器2.1 function2.2 bind3、可变参数模板3.1 概念与使用3.2 包扩展1、lambda表达式1.1 语法# Lambda 表达式本质上是一个匿名函数对象与普通函数不同之处在于它可以定义在函数内部从语法层面看Lambda 表达式没有显式类型因此通常使用 auto 或模板参数来接收 Lambda 对象。# Lambda 表达式的基本格式[capture-list] (parameters)- return-type { function-body }[ capture-list ] : 捕捉列表该列表总是出现在 lambda 函数的开始位置编译器根据 [ ] 来判断接下来的代码是否为 lambda 函数捕捉列表能够捕捉上下文中的变量供 lambda 函数使用捕捉列表可以传值和传引用捕捉具体细节后文再细讲。捕捉列表为空也不能省略。(parameters) 参数列表与普通函数的参数列表功能类似如果不需要参数传递则可以连同 () ⼀起省略- return type 返回值类型用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。⼀般返回值类型明确情况下也可省略由编译器对返回类型进行推导。{ function boby } 函数体函数体内的实现跟普通函数完全类似在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量函数体为空也不能省略。int main() { // ⼀个简单的lambda表达式 auto add1 [](int x, int y)-{return x y; }; cout add1(1, 2) endl; // 1、捕捉为空也不能省略 // 2、参数为空可以省略 // 3、返回值可以省略可以通过返回对象⾃动推导 // 4、函数体不能省略 auto func1 [] { cout hello bit endl; return 0; }; //func1(); int a 0, b 1; auto swap1 [](int x, int y) { int tmp x; x y; y tmp; }; swap1(a, b); //cout a : b endl; return 0; }#includevector using namespace std; struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 // ... Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) {} }; struct ComparePriceLess { bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; } }; struct ComparePriceGreater { bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; } }; int main() { vectorGoods v { { 苹果, 2.1, 5 }, { ⾹蕉, 3, 4 }, { 橙⼦, 2.2, 3 }, { 菠萝, 1.5, 4 } }; // 类似这样的场景我们实现仿函数对象或者函数指针⽀持商品中 // 不同项的⽐较相对还是⽐较⿇烦的那么这⾥lambda就很好⽤了 sort(v.begin(), v.end(), ComparePriceLess()); sort(v.begin(), v.end(), ComparePriceGreater()); sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) { return g1._price g2._price; }); sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) { return g1._price g2._price; }); sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) { return g1._evaluate g2._evaluate; }); sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) { return g1._evaluate g2._evaluate; }); return 0; }1.2 捕捉列表# lambda 表达式默认只能访问函数体和参数中的变量如需使用外层作用域的变量需通过捕获列表实现。# 显式捕获方式分为值捕获和引用捕获多个捕获变量用逗号分隔。例如[ x, y, z ] 表示对 x 和 y 进行值捕获对 z 进行引用捕获int x 0; // 捕捉列表必须为空因为全局变量不用捕捉就可以用没有可被捕捉的变量 auto func1 []() { x; }; int main() { // 只能用当前lambda局部域和捕捉的对象和全局对象 int a 0, b 1, c 2, d 3; auto func1 [a, b] { // 值捕捉的变量不能修改引用捕捉的变量可以修改 //a; // 错误不能修改 b; int ret a b; return ret; }; cout func1() endl; return 0; }#隐式捕捉方式通过在捕获列表中使用符号自动捕获变量 表示对所有使用变量进行值捕获 表示对所有使用变量进行引用捕获int main() { // 隐式值捕捉 // 用了哪些变量就捕捉哪些变量 auto func2 [] { int ret a b c; return ret; }; cout func2() endl; // 隐式引用捕捉 // 用了哪些变量就捕捉哪些变量 auto func3 [] { a; c; d; }; func3(); cout a b c d endl; }#混合捕捉方式允许同时使用显式和隐式捕获[ , x ] 表示其他变量值捕获x 引用捕获[ , x, y ] 表示其他变量引用捕获x 和 y 值捕获混合捕获时第一个元素必须是 或 且后续捕获类型需与之相反int main() { // 混合捕捉1 auto func4 [, a, b] { //a; //b; c; d; return a b c d; }; func4(); cout a b c d endl; // 混合捕捉1 auto func5 [, a, b] { a; b; /*c; d;*/ return a b c d; }; return 0; }# 局部作用域中的 lambda 表达式只能捕获其定义位置之前的变量不能捕获静态局部变量和全局变量这些变量可直接使用全局定义的 lambda 表达式捕获列表必须为空int x 0; // 捕捉列表必须为空因为全局变量不用捕捉就可以用没有可被捕捉的变量 auto func1 []() { x; }; int main() { // 只能用当前lambda局部域和捕捉的对象和全局对象 int a 0, b 1, c 2, d 3; auto func1 [a, b] { // 值捕捉的变量不能修改引用捕捉的变量可以修改 //a; // 错误不能修改 b; int ret a b; return ret; }; cout func1() endl; // 局部的静态和全局变量不能捕捉也不需要捕捉 static int m 0; auto func6 [] { int ret x m; return ret; }; return 0; }# 捕获变量默认具有 const 属性值捕获的变量不可修改使用 mutable 修饰符可取消 const 属性此时参数列表不可省略修改仅作用于形参不影响实参int main() { // 传值捕捉本质是一种拷⻉,并且被const修饰了 // mutable相当于去掉const属性可以修改了 // 但是修改了不会影响外面被捕捉的值因为是一种拷⻉ auto func7 []()mutable { a; b; c; d; return a b c d; }; cout func7() endl; cout a b c d endl; return 0; }1.3 原理# lambda 的原理和范围 for 很像编译后从汇编指令层的角度看压根就没有 lambda 和范围 for 这样的东西。范围 for 底层是迭代器而lambda 底层是仿函数对象也就说我们写了⼀个lambda 以后编译器会生成⼀个对应的仿函数的类。# 仿函数的类名是编译按⼀定规则 UUID 算法生成的保证不同的 lambda生成的类名不同lambda 的参数/返回类型/函数体就是仿函数 operator() 的参数/返回类型/函数体 lambda 的捕捉列表本质是生成的仿函数类的成员变量也就是说捕捉列表的变量都是 lambda 类构造函数的实参当然隐式捕捉编译器要看使用哪些就传那些对象。class Rate { public: Rate(double rate) : _rate(rate) {} double operator()(double money, int year) { return money * _rate * year; } private: double _rate; }; int main() { double rate 0.49; // lambda auto r2 [rate](double money, int year) { return money * rate * year; }; // 函数对象 Rate r1(rate); r1(10000, 2); r2(10000, 2); auto func1 [] { cout hello world endl; }; func1(); return 0; }# 上面的原理我们可以透过汇编层了解⼀下1.4 捕捉列表 VS 函数参数# Lambda 表达式的捕捉列表Capture List和函数参数是完全不同的设计核心解决「访问函数外部变量」的问题 —— 直接传参只能处理 “调用 Lambda 时传入的临时值”而捕捉列表才能让 Lambda 访问定义它时所在作用域的外部变量如局部变量、类成员二者无法互相替代。1.5 Lambda 在实际开发中的典型应用多线程编程优势直接捕获任务相关参数逻辑封装在任务启动点避免定义额外函数智能指针自定义删除器// 1. 定义Lambda作为文件删除器接管FILE*的销毁逻辑 auto file_deleter [](FILE* f) { if (f) { // 防御性检查避免空指针调用fclosefclose(NULL)未定义 std::cout Closing file\n; fclose(f); // 关闭文件释放系统资源 } }; // 2. 定义unique_ptr绑定FILE*和自定义删除器 std::unique_ptrFILE, decltype(file_deleter) file_ptr(fopen(data.txt, r), file_deleter);对比传统方式// 传统函数指针方式 void file_deleter_func(FILE* f) { /*...*/ } std::unique_ptrFILE, void(*)(FILE*) ptr(fopen(...), file_deleter_func);异步任务封装// 提交异步任务 auto future std::async(std::launch::async, [url https://example.com] { return fetch_data(url); // 捕获URL }); // 获取结果 auto result future.get();GUI 事件处理// 按钮点击事件处理 button.on_click([counter, this](const Event e) { counter; // 捕获计数器引用 update_display(); // 捕获this指针 log_event(e); // 访问事件对象 });回调函数简化// 传统C风格回调 void register_callback(void (*callback)(int, void*), void* data); // 使用lambda封装状态 int state 42; register_callback([](int result, void* data) { int* s static_castint*(data); std::cout Result: result , State: *s; }, state); // C11后更优雅的方式 std::functionvoid(int) callback [state](int result) { std::cout Result: result , State: state; }; register_cpp_callback(callback);2、包装器2.1 function# std::function 是一个通用的函数包装器模板类属于 C11标准库的一部分。它提供了一种类型安全的方式来存储、复制和调用各种可调用对象callable objects。参考文档 std::function - cppreference.com# std::function 的实例对象可以包装存储其他的可以调用对象包括函数指针、仿函数、 lambda 、bind 表达式等存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call 异常。# 基本用法如下std::functionint(int, int) func; // 声明一个接受两个int参数返回int的function对象func [](int a, int b){ return a b; }; // 包装lambda表达式int result func(2, 3); // 调用存储的可调用对象# 函数指针、仿函数、 lambda 等可调用对象的类型各不相同 std::function 的优势就是统⼀类型对他们都可以进行包装这样在很多地方就方便声明可调用对象的类型。// 1. 普通函数 int f(int a, int b) { return a b; } // 2. 仿函数函数对象重载operator() struct Functor { int operator() (int a, int b) { return a b; } }; // 3. 类含静态/非静态成员函数 class Plus { public: Plus(int n 10) :_n(n) {} // 构造函数初始化成员变量_n // 静态成员函数无this指针 static int plusi(int a, int b) { return a b; } // 非静态成员函数隐含this指针依赖对象实例 double plusd(double a, double b) { return (a b) * _n; } private: int _n; // 非静态成员仅对象实例可访问 };2.2 bind# bind 是一个功能强大的函数模板属于可调用对象包装器范畴。它本质上是一个函数适配器也是一个仿函数允许开发者对输入的可调用对象 fn 进行参数绑定和调整最终返回一个新的可调用对象。bind 的主要功能包括调整参数个数减少或重新排列改变参数顺序绑定部分参数值使用占位符表示未绑定参数# 该模板位于 functional 头文件中是标准库提供的重要功能组件。# 典型的 bind 调用形式如下auto newCallable bind(callable, arg_list);newCallable 是生成的新可调用对象callable 是原始可调用对象函数、函数指针、成员函数、函数对象等arg_list 是以逗号分隔的参数列表对应 callable 的参数# 当调用 newCallable 时它会自动调用原始 callable并传递 arg_list 中绑定的参数。# arg_list 中的参数可能包含形如 _n 的名字其中 n 是⼀个整数这些参数是占位符表示 newCallable 的参数它们占据了传递给 newCallable 的参数的位置。数值 n 表示生成的可调用对象中参数的位置_1 为 newCallable 的第⼀个参数_2 为第⼆个参数以此类推。_1/_2/_3… 这些占位符放到一个名为 std::placeholders 的命名空间中使用时需要using namespace std::placeholders; // 或者 using std::placeholders::_1; using std::placeholders::_2; // ...# 调整参数顺序auto sub1 bind(Sub, _1, _2); cout sub1(10, 5) endl; // 输出: (10-5)*10 50auto sub2 bind(Sub, _2, _1); cout sub2(10, 5) endl; // 输出: (5-10)*10 -50sub2(10, 5)→Sub(5, 10)_2对应第一个实参10_1对应第二个实参5# 多参数绑定auto sub3 bind(Sub, 100, _1); cout sub3(5) endl; // 输出: (100-5)*10 950 auto sub4 bind(Sub, _1, 100); cout sub4(5) endl; // 输出: (5-100)*10 -950sub3(5)→Sub(100, 5)sub4(5)→Sub(5, 100)auto sub5 bind(SubX, 100, _1, _2); cout sub5(5, 1) endl; // 输出: (100-5-1)*10 940 auto sub6 bind(SubX, _1, 100, _2); cout sub6(5, 1) endl; // 输出: (5-100-1)*10 -960 auto sub7 bind(SubX, _1, _2, 100); cout sub7(5, 1) endl; // 输出: (5-1-100)*10 -960分别固定第1、2、3个参数剩余参数通过占位符传递# 成员函数绑定1.直接使用std::functionfunctiondouble(Plus, double, double) f6 Plus::plusd;函数类型double(Plus, double, double)需要传递一个Plus对象作为第一个参数Plus pd; cout f6(move(pd), 1.1, 1.1) endl; // 输出: 2.2 cout f6(Plus(), 1.1, 1.1) endl; // 输出: 2.2传递左值对象使用std::move传递右值对象临时对象2. 使用std::bind绑定对象functiondouble(double, double) f7 bind(Plus::plusd, Plus(), _1, _2); cout f7(1.1, 1.1) endl; // 输出: 2.2bind固定了Plus对象临时对象创建只需两个参数的新函数对象3、可变参数模板3.1 概念与使用# C11 支持可变参数模板允许定义带有可变数量参数的函数模板和类模板。可变参数被称为参数包主要分为两类模板参数包表示零个或多个模板参数函数参数包表示零个或多个函数参数template class... Args void Func(Args... args) {} template class... Args void Func(Args... args) {} template class... Args void Func(Args... args) {}使用省略号(...)表示参数包在模板参数列表中class...或typename...表示零个或多个类型参数在函数参数列表中类型名后的...表示零个或多个形参函数参数包可以使用左值引用 () 表示使用右值引用 () 表示遵循模板实例化时的引用折叠规则实现原理可变参数模板的实现机制与普通模板类似本质上是通过实例化生成对应类型和参数数量的多个函数# 可以使用sizeof...运算符可获取参数包中的参数数量。template class ...Args void Print(Args... args) { cout sizeof...(args) endl; } int main() { double x 2.2; Print(); // 包里有0个参数 Print(1); // 包里有1个参数 Print(1, string(xxxxx)); // 包里有2个参数 Print(1.1, string(xxxxx), x); // 包里有3个参数 return 0; }3.2 包扩展# 对于参数包除了计算其参数数量外唯一能做的操作就是扩展它。扩展参数包时需要为每个元素指定应用模式。扩展的本质是将包分解为构成元素对每个元素应用给定模式从而获得扩展后的列表。# 通过在表达式右侧添加...操作符来触发扩展操作# C 还支持更高级的包扩展方式可以直接将参数包中的元素依次展开作为实参传递给函数进行处理。# 特别注意不支持使用 arg[i] 这样的方式来获取参数包中的参数因为可变参数模板是在编译时解析而不是在运行时解析的如下代码template class ...Args void Print(Args... args) { // 可变参数模板是在编译时解析 // 下面是运行获取和解析所以不支持这样用 cout sizeof...(args) endl; for (size_t i 0; i sizeof...(args); i) { cout args[i] ; } cout endl; }# 一般使用递归扩展来获取参数包中的参数如下代码void ShowList() { // 编译器时递归的终止条件参数包是0个时直接匹配这个函数 cout endl; } template class T, class ...Args void ShowList(T x, Args... args) { cout x ; // args是N个参数的参数包 // 调用ShowList参数包的第一个传给x剩下N-1传给第二个参数包 ShowList(args...); } // 编译时递归推导解析参数 template class ...Args void Print(Args... args) { ShowList(args...); } int main() { Print(); Print(1); Print(1, string(xxxxx)); Print(1, string(xxxxx), 2.2); return 0; }# 递归展开包含两个关键部分递归函数模板处理至少一个参数的情况递归终止函数处理参数包为空的情况# 调用时本质上编译器将可变参数模板通过模式的包扩展编译器推导的几个重载函数函数# 还可以借助逗号表达式来展开参数包// 1. 单个参数处理函数打印参数并返回引用 template class T const T GetArg(const T x) { cout x ; // 核心逻辑打印单个参数 return x; // 返回引用保证能组成参数包传递 } // 2. 空可变参数函数仅用于接收展开后的参数包无实际逻辑 template class ...Args void Arguments(Args... args) {} // 3. 核心可变参数打印函数展开参数包并调用GetArg template class ...Args void Print(Args... args) { // 包扩展GetArg(args)... 等价于 GetArg(1), GetArg(xxxxx), GetArg(2.2) // 扩展后的多个返回值作为Arguments的参数包传入 Arguments(GetArg(args)...); } int main() { Print(1, string(xxxxx), 2.2); return 0; }# C17 引入折叠表达式Fold Expression大幅简化包扩展的写法无需递归即可展开参数包。具体细节以后再介绍。