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

C++修炼:C++11(三)

         Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》

欢迎点赞,关注!

        在C++11及后续版本中,Lambda表达式作为一种革命性的特性被引入,极大地改变了我们编写函数对象的方式。Lambda表达式不仅使代码更加简洁易读,还为STL算法多线程编程等场景提供了极大的便利。本文将全面剖析C++中Lambda表达式的语法、特性、应用场景及最佳实践。

目录

1、 lambda

         1.1、lambda表达式

                  1.2、捕捉列表

         1.3、lambda表达式的实际应用

         1.4、lambda的原理

2、包装器

        2.1、function

        2.2、bind

          2.2.1、使用bind调整参数顺序

          2.2.2、使用bind调整参数个数


1、 lambda

          1.1、lambda表达式

        lambda本质上是一个匿名函数对象。它可以直接定义在函数内部。我们可以简单的理解为更灵活的函数。我们可以快速简洁的定义和使用。以下是lambda表达式的规范格式:

[capture-list] (parameters) -> return type { function boby } 

        其中, [capture-list]是捕捉列表,这个列表出现在lambda函数的开始位置。编译器根据[]来判断接下来的代码是否为lambda函数。捕捉列表可以传值或传引用捕捉。捕捉列表可以为空不能省略。

        (parameters)是参数列表,跟普通函数的参数列表一样。我们要是不需要传参可以省略不写()。

        -> return type是返回类型。当没有返回值时可以省略。返回值特定时也可以省略,由编译器推导函数返回值类型。

         { function boby } 就是函数体,和普通函数一样。

#include<iostream>
using namespace std;int main()
{auto add = [](int x, int y)->int {return x + y;};cout << add(1, 2) << endl;//3int a = 10;auto add1 = [a](int x, int y)->int {return x + y + a;};//传值捕捉cout << add1(1, 2) << endl;//13auto add2 = [&a](int x, int y)->int {return x + y + a;};//传引用捕捉cout << add2(1, 2) << endl;//13return 0;
}

         1.2、捕捉列表

        如果我们lambda表达式想用函数体外的变量就需要自行捕捉。当然如果你想在函数体内用的对象是全局的就不用捕捉了。

        接下来我们介绍三种捕捉方式,首先是第一种,显示的传值或传引用捕捉

        在1.1的代码我们已经展示过传值捕捉和传引用捕捉了。如果想捕捉多个变量需要用逗号隔开。

	auto add2 = [a,b,&c](int x, int y)->int {return x + y + a+b+c;};//a,b传值捕捉,c传引用捕捉

         第二种捕捉方式是在捕捉列表隐式捕捉。这种捕捉方式需要我们在[]中写一个=或者&,然后函数体中用什么我们自动捕捉什么变量:

	auto add2 = [=](int x, int y)->int {return a+b+c;};//隐式值捕捉auto add3 = [&](int x, int y)->int {return a + b + c;};//隐式引用捕捉

        第三种捕捉方式是混合使用隐式捕捉和显示捕捉。我们[]中的第一个元素必须是=或者&,表示是值捕捉或者引用捕捉,接下来的每个参数直接传变量名,他们的捕捉方式和第一个元素传的捕捉方式相反:

	auto add2 = [=,&b,&c](int x, int y)->int {return a+b+c;};//a是传值捕捉,b,c是传引用捕捉auto add3 = [&,b,c](int x, int y)->int {return a + b + c;};//a是传引用捕捉,b,c是传值捕捉

        捕捉列表中不能捕捉全局变量,如果lambda表达式定义在全局中,捕捉列表必须为空。

        labmda捕捉列表是被const修饰的。也就是说传值捕捉的对象也不能在函数体中进行修改。但是我们使用mutable之后可以修改。 

auto add2 = [=,&b,&c](int x, int y)mutable->int {a+=10;b += 10;c += 10;return a+b+c;};
cout << add2(1, 2) << endl;
cout << a << " " << b << " " << c << " " << endl;//10 21 23

        但需要注意的是,对于mutable修饰之后的传值捕捉,我们修改的只是拷贝过来的值,对于原来的变量没有影响。

         1.3、lambda表达式的实际应用

        lambda表达式可以代替仿函数完成一些工作,这个是他很大的一个贡献。因为在lambda之前我们要想写仿函数需要写一个类出来。我们如果不想用仿函数的话用函数指针定义起来也很麻烦。所以说labmda出现之后我们使用的可调用对象定义起来还是很方便的。

int a[10] = { 1,5,6,4,8,9,1,5,13,12 };
int main()
{sort(a, a + 10, [](int x, int y) {return x < y;});for (int i = 0;i < 10;i++){cout << a[i] << " ";}return 0;
}

         当然除此以外lambda表达式还在其他地方有很多的应用,比如线程初始化,智能指针定制删除器(下下篇会说),资源管理等等。

         1.4、lambda的原理

        lambda底层其实就是个仿函数对象。我们写一个lambda,编译器底层就会生成一个仿函数的类。对于这个仿函数的类,编译器有几个特定的规则或者说要求:

  1. 编译器为每个Lambda生成唯一的类类型

  2. 该类包含一个重载的operator()

  3. Lambda体成为operator()的实现

  4. 捕获的变量成为该类的成员变量

        那么编译器如何确定lambda生成唯一的类类型呢?在vs系列编译器中时通过uuid来实现的。在编译器底层会和每个类名绑定一个uuid,这个uuid时随机的并且重复概率特别特别低,这就保证了每个lambda表达式都是不同的。其他的编译器有其他的实现方式。但是确定的是需要保证lambda表达式的唯一性。

auto test1 = [](int x, int y) {return x < y;};
auto test2 = [](int x, int y) {return x < y;};

        因为唯一性的确定,对于test1和test2来说虽然他们两个功能相同,但是他们两个的类型时不同的。

2、包装器

        2.1、function

         function是一个类模板,也是一个包装器。我们可以使用function包装可调用对象,包括函数指针,仿函数,lambda,bind等。如果function为空,此时调用function中的目标会抛出异常。 

        function底层其实也是个仿函数。因为他也重载了operator()。function包含在头文件functional中。

#include<iostream>
#include<algorithm>
#include <functional>
using namespace std;
int add(int a, int b) {return a + b;
}
struct Multiply {int operator()(int a, int b) const {return a * b;}
};
class Math{
public:int divide(int a, int b) {return a / b;}
};
int main()
{//     返回值  参数类型function<int(int, int)> func = add;//存储普通对象cout << func(2, 3);  // 输出 5function<int(int, int)> func1 = Multiply();//存储仿函数cout << func1(2, 3);  // 输出 6function<int(int, int)> func2 = [](int a, int b) {return a - b;};//存储lambda表达式cout << func2(5, 3);  // 输出 2Math math;function<int(Math*, int, int)> func3 = &Math::divide;//存储成员函数cout << func3(&math,6, 3);  // 输出 2return 0;
}

        对于成员函数的包装,我们必须指定类域,并且类域前必须加取地址。而且注意我们在function的尖括号中的参数类型中必须加上隐含的this指针。 并且在调用的时候也要注意传过去一个this指针类型的对象。

         但是对于成员函数,在实践中我们更喜欢这样写:

function<int(Math&&, int, int)> func4 = &Math::divide;//存储成员函数
cout << func4(Math(), 6, 3);  // 输出 2

        大家可以这样理解,我们最终的目的是调用到成员函数,那么我们可以通过this指针加->去调用成员函数,如果我们传的就是类对象呢,我们也可以通过.*这个操作符来调用到成员函数指针。所以说其实并不是一定传Math*类型的。那么这样的话我们直接传一个临时对象就可以了,就不用特定实例化出来一个对象了。

        我们可以使用function和map进行结合,可以让我们的代码看起来更简洁:

150. 逆波兰表达式求值 - 力扣(LeetCode)

传统写法:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;for(int i=0;i<tokens.size();i++){if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){int x=st.top();st.pop();int y=st.top();st.pop();switch(tokens[i][0]){//一定要注意x和y的顺序case '+':st.push(y+x);break;case '-':st.push(y-x);break;case '*':st.push(y*x);break;case '/':st.push(y/x);break;   }}else{st.push(stoi(tokens[i]));//要用stoi不然无法处理多位数}}return st.top();}
};

高效写法:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调⽤对象的类型 map<string, function<int(int, int)>> opFuncMap = {{"+", [](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;}}};for(auto& str : tokens){if(opFuncMap.count(str)) // 操作符 {int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);}else{st.push(stoi(str));}}return st.top();}
};

        2.2、bind

        bind是一个函数模板,他也是一个可调用对象的包装器。我们可以使用它来调整参数个数和参数顺序。当然我们更常用他来调整参数个数。bind也包含在头文件functional中。

          2.2.1、使用bind调整参数顺序

#include<iostream>
#include<algorithm>
#include <functional>using namespace std;//展开命名空间
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;int test(int a, int b) {return a-b;
}
int main()
{auto test1 = bind(test, _1, _2);cout << test1(10, 5) << endl;//5auto test2 = bind(test, _2, _1);cout << test2(10, 5) << endl;//-5return 0;
}

       std::placeholders是C++标准库中与std::bind配合使用的一个命名空间,它提供了一系列占位符(_1_2_3等),用于表示绑定函数中将来会被提供的参数位置。我们展开他时可以像上面代码那样展开,也可以直接using namespace placeholders。

        其中我们用_1,_2,_3来表示这是传参时的第几个参数,具体的对应关系可以看下图:

          2.2.2、使用bind调整参数个数

        我们可以使用bind绑死参数,在传参时我们只传没有被绑定的位置:

auto test3 = bind(test, _1, 10);//绑死第二个参数
cout << test3(10) << endl;//0
cout << test3(10, 1,0,2,4,6,5,1) << endl;//0,第二个参数已经绑死,不管传不传都是10,而且参数可以传任意多个auto test4 = bind(test,10,_1);
cout << test4(5) << endl;//5

        对于test4,具体对应关系如下: 

        

        注意一点,我们传两个参数,那么绑定时就写_1,_2,不是说如果原函数第二个参数绑死了我们就传_1,_3。

         接下来我们看一下bind和function的结合应用。在上面我们封装成员函数是这样写的:

function<int(Math&&, int, int)> func4 = &Math::divide;
cout << func4(Math(), 6, 3);  // 输出 2

        那么其实我们可以使用bind绑死第一个参数,就不用每次都新建一个临时对象了:

	function<int(int, int)> func4 = bind(&Math::divide, Math(), _1, _2);cout << func4(6, 3) << endl;

         当然我们这也可以直接用auto:

	auto func4 = bind(&Math::divide, Math(), _1, _2);cout << func4(6, 3) << endl;

        bind也可以绑死lambda表达式

	auto lambda = [](int x, int y) { return x * y; };auto f = std::bind(lambda, _1, 5);std::cout << f(10) << std::endl;  // 输出50

        bind也支持嵌套绑定

int add(int a, int b) {return a + b;
}
int main()
{auto f1 = std::bind(add, std::placeholders::_1, std::placeholders::_2);auto f2 = std::bind(f1, 10, std::placeholders::_1);cout<<f2(20);  // 输出30return 0;
}

        好了,今天的内容就分享到这,我们下期再见! 

相关文章:

  • Java并发编程实战 Day 14:并发编程最佳实践
  • 华为OD机考-内存冷热标记-多条件排序
  • 强化学习入门:交叉熵方法数学推导
  • 把二级域名绑定的wordpress网站的指定页面
  • 计组_导学
  • java复习 05
  • wpf在image控件上快速显示内存图像
  • 手动给中文分词和 直接用神经网络RNN做有什么区别
  • 如何利用 OpenCV 进行实时图像处理与对象检测
  • Python实例题:Python计算概率论
  • python打卡day48@浙大疏锦行
  • MCP(Model Context Protocol)模型上下文协议 番外篇 2025-03-26 更新
  • 鸿蒙学习笔记01
  • 第三章支线三 ·异步幻境 · 时间之缝的挑战
  • Redis 知识点一
  • 进程优先级
  • Spring注解开发
  • 原型对象(Prototype)详解
  • 二叉树-226.翻转链表-力扣(LeetCode)
  • Argo CD 入门 - 安装与第一个应用的声明式同步
  • 教你如何用天翼云盘做网站/现在推广什么app最挣钱
  • 怎么做网站一张图/体验式营销
  • 企业网站建设框架图/seo品牌
  • 企业网站建设制作公司哪家好/网站建立的步骤
  • 企业建网站作用/百度爱采购优化排名软件
  • 邢台集团网站建设/八百客crm登录入口