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

【C++11】C++11新特性(下)

目录

  • 一、lambda
    • 1.1 lambda 表达式语法
    • 1.2 lambda 的应用
    • 1.3 捕捉列表
    • 1.4 lambda 的原理
  • 二、包装器
    • 2.1 function
      • function 的应用
    • 2.2 bind
      • 应用

在这里插入图片描述
个人主页:矢望
专栏:C++、Linux、C语言、数据结构

一、lambda

1.1 lambda 表达式语法

lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是它可以定义在函数内部

lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。

lambda表达式的格式: [capture-list(捕捉列表)] (parameters(参数列表))-> return type(返回值类型) {function boby(函数体) }

简单认识lambda表达式:

// 匿名函数对象
[](int x, int y)->int 
{return x + y; 
};
auto add = [](int x, int y)->int {return x + y; };
cout << add(1, 2) << endl;

在这里插入图片描述
这里的add就是lambda对象。

[capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉。捕捉列表为空也不能省略

(parameters)参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()一起省略

->return type返回值类型,用追踪返回类型形式声明函数的返回值类型。通常省略,由编译器对返回类型进行推导

{function boby}函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略

示例:

// 1、捕捉列表为空也不能省略
// 2、函数体为空也不能省略
// 3、返回值通常省略,让编译器自动推导
// 4、参数列表为空可以省略
auto func = []
{cout << "hello world!" << endl;return 0;
};
func();

在这里插入图片描述

1.2 lambda 的应用

在实际应用场景中,比如购物,在各大平台,你都可以以价格,评分等来对商品进行排序,现在,我们要实现一个排序分别对货物以价格和评分进行排序。

货物类

struct Goods
{string _name; // 名字double _price; // 价格int _eval; // 评价Goods(const char* str, double price, int eval):_name(str), _price(price), _eval(eval){ }
};
vector<Goods> v = { { "苹果", 2.1, 5 }, { "柚子", 5.3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 }};

我们要对上面的v对象进行分别以价格和评分排序,有以下几个思路:
1、在类里面重载operator<函数,因为sort排序默认就是调用operator<进行排序的,但是很明显,我们要使用两种排序方式进行排序,重载operator<函数最多只能满足一种,不灵活,这是不符合要求的。

2、实现两个仿函数,分别进行排序,这是可行的。

struct PriceCmp
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};struct EvalCmp
{bool operator()(const Goods& gl, const Goods& gr){return gl._eval < gr._eval;}
};void print(const vector<Goods>& s)
{for (auto& e : s){cout << e._name << " " << e._price << " " << e._eval << endl;}cout << endl;
}

进行排序:

vector<Goods> v = { { "苹果", 2.1, 5 }, { "柚子", 5.3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 }};sort(v.begin(), v.end(), PriceCmp());
print(v);
sort(v.begin(), v.end(), EvalCmp());
print(v);

在这里插入图片描述
使用仿函数进行这里的排序是满足我们的需求的,但是用仿函数进行排序,相对还是比较麻烦的,并且在排序时,如果仿函数名称都取成cmp1、cmp2、cmp3,这样的代码在读代码时可读性不好,读的多了容易上火。

3、使用 lambda 表达式。使用 lambda 去定义可调用对象,既简单又方便。

auto PriceCmp = [](const Goods& gl, const Goods& gr)
{return gl._price < gr._price;
};
sort(v.begin(), v.end(), PriceCmp);

当然在实践角度,人们更喜欢以下这种用法:

sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){return gl._price < gr._price;});
print(v);
sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {return gl._eval < gr._eval;});
print(v);

在这里插入图片描述
优势就地定义,在需要的地方直接定义,代码更紧凑;意图明确,看到lambda就立即知道排序规则;灵活性,轻松实现复杂、动态的排序逻辑;可维护性,不需要为每个排序规则创建单独的类/函数;性能,通常会被编译器内联,没有函数调用开销。

现代C++开发中,lambda表达式让代码既简洁又表达力强,特别是在算法回调这种场景下

1.3 捕捉列表

lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。

第一种捕捉方式是在捕捉列表中显示的传值捕捉传引用捕捉,捕捉的多个变量用逗号,分割。[x, y, &z] 表示xy值捕捉,z引用捕捉。注意:这里的&不是取地址,而是引用

值捕捉的变量是不能进行修改的,引用捕捉到的变量可以修改

int a = 0, b = 3;
auto func = [a, &b]
{// 值捕捉的变量不能修改,引⽤捕捉的变量可以修改// a++;b++;return a + b;
};cout << func() << endl;
cout << "b: " << b << endl;

在这里插入图片描述

第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个=表示隐式值捕捉,在捕捉列表写一个&表示隐式引用捕捉,这样我们 lambda 表达式中使用了哪些变量,编译器就会自动捕捉哪些变量。

int a = 0, b = 3, c = 2;
// 隐式值捕捉
auto func1 = [=]
{// 用了哪些变量就捕捉哪些变量return (a + b) * c;
};
cout << func1() << endl;
// 隐式引用捕捉
auto func2 = [&]
{// 用了哪些变量就捕捉哪些变量a++, b++, c++;
};
func2();
cout << "a: " << a << " b: " << b << " c: " << c;

在这里插入图片描述
第三种捕捉方式混合捕捉,在捕捉列表中混合使用隐式捕捉和显示捕捉。[=, &x]表示其他变量隐式值捕捉,x引用捕捉;[&, x]表示其他变量引用捕捉,x值捕捉

注意:当使用混合捕捉时,第一个元素必须是&=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须是引用捕捉,也就是隐式捕捉必须在捕捉列表的开头,不能同时使用隐式捕捉和显式同类型捕捉。不能出现[&, &x, y]、[=, x, &y]

lambda 表达式如果在函数局部域中,它可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉,lambda 表达式中可以直接使用这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空

int x = 0;
// 捕捉列表必须为空
// 因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func = []()
{x++;
};int main()
{//...return 0;
}

默认情况下, lambda 捕捉列表是被const修饰的,传值捕捉过来的对象不能修改,mutable加在参数列表的后面可以取消其常性,使用该修饰符后,传值捕捉的对象就可以修改了,但是修改的还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。

int a = 0, b = 3, c = 5;
// mutable相当于去掉const属性,可以修改了
auto func = [=]() mutable
{a++;b++;c++;cout << "a: " << a << " b: " << b << " c: " << c << endl;
};
func();
cout << "a: " << a << " b: " << b << " c: " << c << endl;

在这里插入图片描述

1.4 lambda 的原理

lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器lambda底层是仿函数对象,也就说我们写了一个lambda后,编译器会生成一个对应的仿函数的类

仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda的参数/返回类型/函数体就是仿函数operator()参数/返回类型/函数体lambda捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 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 r = 0.89;// lambdaauto r1 = [r](double money, int year) {return money * r * year;};// 函数对象Rate r2(r);r1(10000, 3); // lambdar2(10000, 3); // 仿函数类对象return 0;
}

在这里插入图片描述
从底层反汇编可以看出lambda底层本质就是仿函数,和仿函数对象相同都是调用operator()。其中上图中类型是lambda_1,这个类型名的规则是编译器自己定制的,保证不同的lambda不冲突,所以它的类型名需要使用auto进行推演,我们压根不知道。

二、包装器

2.1 function

在这里插入图片描述
std::function 是一个类模板,也是一个包装器。被定义在<functional>的头文件中。 std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、 lambda表达式等,存储的可调用对象被称为 std::function 的目标。若 std::function 不含目标,则称它为空。调用空std::function的目标导致抛出 std::bad_function_call 异常。 链接:std::bad_function_call。

函数指针、仿函数、 lambda 等可调用对象的类型各不相同, std::function 的优势就是统一类型,对它们都可以进行包装,这样就方便声明可调用对象的类型。

示例

int f(int a, int b){ return a + b; }struct func
{
public:int operator() (int a, int b){ return a + b; }
};int main()
{// 包装各种可调⽤对象function<int(int, int)> f1 = f;function<int(int, int)> f2 = func();function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;return 0;
}

在这里插入图片描述
其中function<>()外边的是返回值类型,内部的是函数参数。
在这里插入图片描述

class Plus
{
public:static int add(int a, int b){ return a + b; }int plus1(int a, int b) { return a + b; }
};

包装静态成员函数,成员函数要指定类域并且前面加&才能获取地址,其中包装静态成员函数&可以省略,但建议加上。

// 包装静态成员函数
// 成员函数要指定类域并且前面加&才能获取地址
function<int(int, int)> f4 = &Plus::add;
cout << f4(1, 1) << endl;

在这里插入图片描述

包装普通成员函数,需要注意&不能省略。普通成员函数还有一个隐含的this指针参数,想要调用成员函数指针&Plus::plusi,类对象和类对象的指针都可以,所以绑定时传对象或者对象的指针过去都可以。

// 包装普通成员函数
// 普通成员函数还有一个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以
function<int(Plus*, int, int)> f5 = &Plus::plus1;
Plus ps;
cout << f5(&ps, 1, 1) << endl;
function<int(Plus, int, int)> f6 = &Plus::plus1;
cout << f6(Plus(), 1, 1) << endl;
function<int(Plus&, int, int)> f7 = &Plus::plus1;
cout << f7(ps, 1, 1) << endl;
function<int(Plus&&, int, int)> f8 = &Plus::plus1;
cout << f8(Plus(), 1, 1) << endl;

在这里插入图片描述

function 的应用

150. 逆波兰表达式求值
在这里插入图片描述

class Solution {
public:int evalRPN(vector<string>& tokens) {// 1、运算数:入栈// 2、运算符:取出栈顶两个运算数运算再入栈// function<int(int, int)> 可以存储各种可调用对象(函数指针、lambda表达式等)//   key  ---  valuemap<string, function<int(int, int)>> mp = {{"+", [](int x, int y){ return x + y; } },{"-", [](int x, int y){ return x - y; } },{"*", [](int x, int y){ return x * y; } },{"/", [](int x, int y){ return x / y; } },};stack<int> st;for(auto& e : tokens){if(mp.count(e)) // 运算符 运算再入栈{int a = st.top(); st.pop();int b = st.top(); st.pop();st.push(mp[e](b, a));}else{st.push(stoi(e)); // 运算数入栈}}// 返回结果return st.top();}
};

2.2 bind

在这里插入图片描述
bind 是一个函数模板,它也是一个可调用对象的包装器,可以把它看做一个函数适配器,对接收的可调用对象进行处理后返回一个可调用对象,它也在<functional>头文件中。bind 可以用来调整参数个数和参数顺序

调用bind的一般形式: auto newCallable = bind(callable,arg_list); 其中newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调用对象中参数的位置:_1newCallable的第一个参数,_2为第二个参数,以此类推。_1/_2/_3....这些占位符放到名为placeholders的一个命名空间中。

使用示例

  • 调整参数顺序
#include <functional>using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return a - b;
}int main()
{cout << Sub(7, 2) << endl;//          修改参数顺序auto Sub1 = bind(Sub, _1, _2);cout << "Sub1: " << Sub1(8, 2) << endl;auto Sub2 = bind(Sub, _2, _1);cout << "Sub2: " << Sub2(8, 2) << endl;return 0;
}

代码结果
在这里插入图片描述

过程
在这里插入图片描述
Sub2中的第一个参数在传递的时候,始终传给_1所在位置,以此类推。

  • 调整参数个数
#include <functional>using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int Sub(int a, int b)
{return a - b;
}int main()
{// 调整参数个数auto Sub1 = bind(Sub, _1, 10);cout << "Sub1: " << Sub1(19) << endl;auto Sub2 = bind(Sub, 7, _1);cout << "Sub2: " << Sub2(16) << endl;return 0;
}

代码结果
在这里插入图片描述
过程:
在这里插入图片描述
注意:即使把第一个参数绑死,如上图是7,第二个传的是_1而不是_2

应用

我们在前面包装普通成员函数的时候感觉很繁琐:

function<int(Plus&&, int, int)> f8 = &Plus::plus1;
cout << f8(Plus(), 1, 1) << endl;

有了bind之后就可以进行简化了,可以把第一个参数绑死:

auto f = bind(&Plus::plus1, Plus(), _1, _2);
cout << f(1, 5) << endl;function<int(int, int)> f1 = bind(&Plus::plus1, Plus(), _1, _2);
cout << f1(1, 5) << endl;

在这里插入图片描述

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

http://www.dtcms.com/a/602494.html

相关文章:

  • 免费建站并且绑定域名专业的集团网站建设
  • 接计设做的网站广西网红
  • 技术指标时空编码构建LSTM兼容的量化交易特征工程体系
  • 网站开发培训深圳高端品质网站建设
  • 自然语言编程,举个反面案例
  • Spring 框架核心技术详解:AOP、JDBC 模板与事务管理
  • 杭州做外贸网站网站编辑工具软件
  • 《C++ Primer》和《Effective C++》哪个更厚?
  • 做海报那个网站好一分钟赚50元的游戏
  • 封装了 Android 权限检查和申请功能 PermissionManager工具类,支持权限检查、申请、说明对话框显示和设置页面引导等功能。
  • 2.GPU 网络架构全栈规划与深度分析:从业务需求到落地优化(H100/H200/B200/GB200 实战视角)
  • 企业网站手机端跳转设置门户cms系统
  • 鞍山58路公交车路线苏州百度seo关键词优化
  • 大储和工商储的差异
  • Windows 终端延迟剖析:从“卡顿感”到毫秒账本
  • wordpress图片自动分页插件下载关键词排名优化工具
  • 17.PHP基础-数组
  • 【MyBatis笔记】 - 4 - 缓存 + 逆向工程 + 分页插件
  • jsp和.net做网站的区别好大夫在线医生免费咨询
  • 目标客户精准营销品牌seo推广咨询
  • 企业网站asp一篇网站设计小结
  • 数据库概论实验(黑龙江大学)
  • HCI 数据格式
  • 用wordpress仿站企业宣传方案模板
  • 使用Netlify部署前端项目
  • 网站设计结构图用什么做丝芭传媒有限公司
  • pagehide/beforeunload / unload / onUnmounted 执行顺序与navigator.sendBeacon使用陷阱详解
  • 解决若依框架点击菜单无效的问题(或者main主体白板)vue3版本
  • 回溯-22括号生成
  • 如何做网站卖衣服第一营销网