当前位置: 首页 > news >正文

C++11之lambda表达式与包装器

lambda与包装器

  • lambda语法
  • 捕捉列表
  • lambda的应用
  • lambda的原理
  • 包装器
    • function
    • bind

lambda语法

lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象
lambda表达式的格式: [capture-list] (parameters)-> return type {function boby }

  1. [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上⽂中的变量供 lambda 函数使用,捕捉列表可以传值和传引⽤捕捉。捕捉列表为空也不能省略。(捕捉列表只能是已经定义的)
  2. (parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()⼀起省略
  3. ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
  4. {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。
auto add1 = [](int x, int y)->int {return x + y; };
cout << add1(1, 2) << endl;
// 1、捕捉为空也不能省略
// 2、参数为空可以省略
// 3、返回值可以省略,可以通过返回对象⾃动推导
// 4、函数题不能省略
auto func1 = []
{cout << "hello bit" << endl;return 0;
};

捕捉列表

  1. lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想用外层作⽤域中的变量就需要进⾏捕捉
  2. 第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x,y, &z] 表⽰x和y值捕捉,z引⽤捕捉。
  3. 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些变量
  4. 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=, &x]表⽰其他变量隐式值捕捉,x引⽤捕捉;[&, x, y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使用混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉。
  5. lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。(但是一般都是将lambda定义在局部区域
  6. 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。
auto func1 = [a, &b]
{// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改//a++;b++;int ret = a + b;return ret;
};
auto func2 = [=]
{int ret = a + b + c;return ret;
};
auto func6 = []
{int ret = x + m;return ret;
};
auto func4 = [&, a, b]
{//a++;//b++;c++;d++;return a + b + c + d;
};
auto func5 = [=, &a, &b]
{a++;b++;/*c++;d++;*/return a + b + c + d;
};
auto func7 = [=]()mutable
{a++;b++;c++;
};

但如果lambda是写在类里面的,那么lambda是不能够捕获私有成员变量的,也是不能直接使用的,如果想使用需要捕获this指针

class A
{
public:void f(){auto add1 = [this](int a,int b){return a + b + _a1 + _a2};}
private:
int _a1 = 1;
int _a2 = 2;
};

lambda的应用

在学习 lambda 表达式之前,可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义⼀个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。

sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
return g1._price < g2._price;
});

因为sort会将迭代器区间传给后面的对象,所以需要两个参数。

lambda的原理

  1. lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
  2. 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。

包装器

function

在这里插入图片描述std::function 是⼀个类模板,也是⼀个包装器。 std::function 的实例对象可以包装存储其他的可以调⽤对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调用对象被称为 std::function 的⽬标。若 std::function 不含⽬标,则称它为空。调⽤空std::function 的⽬标导致抛出 std::bad_function_call 异常

int f(int a, int b)
{return a + b;
}struct Fun
{
public:int operator()(int a,int b){return (a + b) * 10;}
};int main()
{auto lf = [](int a, int b) {return (a + b) * 30; };function<int(int, int)>f1(f);//function<int(int, int)>f2(Fun());编译器会将其解释为函数声明而不是变量定义,这被称为"最令人烦恼的解析"function<int(int, int)>f2 = Fun();function<int(int, int)>f3(lf);vector<function<int(int, int)>>Vf = { f,Fun(),lf };cout << f1(2, 3) << endl;cout << f2(1, 2) << endl;cout << lf(4, 5) << endl;for (auto p : Vf){cout << p(1, 2) << endl;}return 0;
}

函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型。
可以把function当做把可调用对象进行统一为一个类型,这样就可以将其储存到容器中。
如果包的是成员函数的话需要注意,由于类的成员函数的第一个参数是默认为this指针,所以function实例化的类型需要有类的对象的地址或者是对象,即使传的是对象,对象会使用.*操作符对this指针进行调用。

class Plus
{public:Plus(int n = 10):_n(n){}static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return (a + b) * _n;}private:int _n;
};
int main()
{// 包装静态成员函数// 成员函数要指定类域并且前⾯加&才能获取地址//静态成员函数是可以不加&function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装普通成员函数// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;
}

bind

  1. bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在<functional>这个头⽂件中。
  2. 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
  3. arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3…这些占位符放到placeholders的⼀个命名空间中。
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int a, int b)
{return (a - b) * 10;
}
int main()
{auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl;// bind 本质返回的⼀个仿函数对象// 调整参数顺序(不常⽤)// _1代表第⼀个实参// _2代表第⼆个实参// ...auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;// 调整参数个数 (常⽤)auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;cout << bind(SubX, 100, _1, _2)(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;return 0;
}

bind对于我们之前用function来包装成员函数时第一个参数必须传对象的情况做出了改变,可以用bind来绑定第一个参数,这样在后续调用该可调用对象的时候就可以不用传对象了

// 成员函数对象进⾏绑死,就不需要每次都传递了
function<double(Plus&&, double, double)> f6 = &Plus::plusd;
Plus pd;
cout << pd.plusd(1.1, 1.1) << endl;
cout << f6(move(pd), 1.1, 1.1) << endl;
cout << f6(Plus(), 1.1, 1.1) << endl;auto f = bind(&Plus::plusd, &pd, _1, _2);
function<double(double, double)> f2 = bind(&Plus::plusd, &pd, _1, _2);
cout << f(1.1,1.1) << endl;
cout << f2(1.1, 1.1) << endl;
http://www.dtcms.com/a/284304.html

相关文章:

  • 20.轮廓特征与近似,改变图像的轮廓识别画线的精确度,同时画出轮廓对应的矩形
  • GitHub 趋势日报 (2025年07月16日)
  • 【Burp入门第三十八篇】Repeater Strike:AI 驱动的 Burp Suite 插件
  • Effective Modern C++ 条款13:优先考虑const_iterator而非iterator
  • Magenta RT 正式开源!实时生成多种风格音乐,让创作无门槛
  • 【C++详解】STL-stack、queue的模拟实现,容器适配器,deque双端队列介绍
  • Java 大视界 -- Java 大数据在智能交通智能公交站台乘客流量预测与服务优化中的应用(349)
  • 19.删除链表的倒数第 N 个结点
  • 多线程--sem_wait(sem)特殊用法
  • 拿到安全工程师证后,能从事哪些岗位?
  • C函数实现strcopy strcat strcmp strstr
  • javax.servlet.http.HttpServletResponse;API导入报错解决方案
  • Kotlin集合与空值
  • 产品经理如何绘制流程图
  • Linux中的数据库操作基础
  • SpringMVC 执行原理
  • 79、【OS】【Nuttx】【启动】caller-saved 和 callee-saved 示例:r7 寄存器
  • Modbus
  • PyCharm2024安装包社区版和专业版
  • TESOLLO五指灵巧手遥操作解决方案
  • 使用 .NET Core 的原始 WebSocket
  • Spring整合MyBatis详解
  • 概率论与数理统计(四)
  • WCDB soci 查询语句
  • 缓存雪崩、缓存穿透,缓存击穿
  • 使用IntelliJ IDEA和Maven搭建SpringBoot集成Fastjson项目
  • 【git】使用教程
  • CommonJS和ES模块区别对比
  • API开发提速新方案:SmartBear API Hub与ReadyAPI虚拟化整合实践
  • ESP8266服务器建立TCP连接失败AT+CIPSTART=“TCP“,“192.168.124.1“,8080 ERROR CLOSED