C++进阶--C++11(04)

article/2025/9/7 23:16:55

文章目录

  • C++进阶--C++11(04)
    • lambda
      • lambda表达式语法
      • 捕捉列表
      • lambda的应用
      • lambda的原理
    • 包装器
      • function
      • bind
    • 总结
    • 结语

很高兴和大家见面,给生活加点impetus!!开启今天的编程之路!!
在这里插入图片描述
今天我们进一步c++11中常见的新增表达
作者:٩( ‘ω’ )و260
我的专栏:Linux,C++进阶,C++初阶,数据结构初阶,题海探骊,c语言
欢迎点赞,关注!!

C++进阶–C++11(04)

在c语言中,我们的可调用对象其实就只有函数
但是在c语言中,指针又会让我们难以掌握。
在之前的c++学习当中,我们学习到了仿函数,这也是一种可调用对象,但是仿函数要求一个类中必须实现operator()并且是公有,倘若我们只用实现一个最普通的加法函数,这样写会不会太杀鸡用牛刀了。显得有点小题大做了。接下来我们将学习lambda表达式,来解决这样的一个问题。

C++11之后,可调用对象就可以分为:函数指针,仿函数,lambda

lambda

lambda表达式语法

1::lambda表达式本质上是一个匿名函数对象,最好定义在函数体内部,因为没有类型,所以最好和auto一起使用。

为什么我们最好定义在内部?因为如果定义在全局,我不如直接写函数或者是仿函数。lambda表达式与其他两个可调用对象最大的区别就是它能够定义在函数内部,并且能够单独开一个自己的作用域。
为什么需要和auto配合使用:因为lambda表达式是一个匿名函数对象,如果我们想要使用这个可调用对象的话,肯定是需要将lambda表达式赋值给一个具体的对象,然后再对这个对象进行操作。

2:lambda表达式格式:

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

3:[capture-list]:叫做捕捉列表,写在lambda表达式的最前面,编译器根据[ ]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,下面会讲的更具体一点。即使捕捉列表为空,[ ]也不能省略,因为编译器根据这个来判断是不是lambda表达式的

4:(parameters):参数列表,与函数中的参数列表类似,可以传引用,也可以传值等等等,如果里面没有传递参数的话,可以和()一起省略掉

5: -> return type:显示说明我的返回值的类型是什么,没有返回值,此时可以省略,如果返回值确定,也可以省略,此时编译器回推导返回值是什么

6: {function boby }:函数体,更普通函数体类似,函数体是不能被省略的

总结:
可以省略的:返回值类型,参数列表
不能够省略的:捕捉列表,函数体

接下来使用几个示例理解一下:

int main()
{//一个简单的lambda表达式并实施调用auto tmp1 = [](int a, int b)->int {return a + b;};//产生一个可调用对象cout << tmp1(1, 2) << endl;//调用这个可调用对象//注意哪些是可以省略的(2个可以省略,两个不能省略)auto tmp2 = []//产生一个可调用对象{cout << "hello world" << endl;};tmp2();//调用这个可调用对象return 0;
}

在这里插入图片描述

捕捉列表

接下来详细说明捕捉列表的作用,什么场景下需要使用到捕捉列表,捕捉列表的格式问题等:
1:如果lambda表达式只能够使用自己作用域的形参或变量,如果想用外部的形参或是变量,就需要进行捕捉。或者是在lambda表达式中,如果局部域中变量频繁被使用,就可以使用捕捉。因为如果显示写参数,到时候传递的话也麻烦,不如直接捕捉。(不用捕捉全局变量,或是静态局部变量,也不需要捕捉,因为局部作用域下是可以使用全局变量的)
2:捕捉方式1(显示捕捉):值捕捉,引用(&)捕捉。如果同时存在,中间用逗号分隔。值捕捉本质上是拷贝构造(类似于传值传参),不能在lambda表达式中对值捕捉的变量进行修改,可以理解为编译器自己加了一个const

[a,&b];//值捕捉和引用捕捉(都是显示捕捉)

3:捕捉方式2(隐式捕捉):=/&捕捉,如果需要将需要的变量进行值捕捉/将需要的变量进行引用捕捉。此时使用=/&。注意,不一定是全部变量,只要lambda表达式中需要的变量才会被捕捉

[=];//隐式值捕捉
[&];//隐式引用捕捉

4:捕捉方式3:混合捕捉:捕捉方式1和捕捉方式2同时使用。需要满足这些规则:逻辑不能够相矛盾以及顺序问题。
来看一个示例:

[= , a];//逻辑混乱,本身需要的变量已经使用了值捕捉,后面还跟了一个值捕捉
[&&a];//逻辑混乱
[a, &];//;逻辑正确,但是顺序错误,隐式捕捉必须放在前面
[&a, =];//顺序错误
[&, =];//逻辑错误,到底是隐式值捕捉还是隐式引用捕捉

总结:混合捕捉隐式捕捉必须在前,而且逻辑需正确,前面是隐式值捕捉,后面只能是引用捕捉,前面是隐式引用捕捉,后面只能是值捕捉。

5:lambda表达式定义在局部域中,只能捕捉lambda表达式之前的变量,因为编译器向上查找的原则。定义在全局域中,不用捕捉任何东西,也不需要捕捉,捕捉列表必须为空

int main()
{int a = 0;auto tmp1 = [a,b](int a, int b)->int{return a + b;};int b = 0;//b不能被捕捉,因为可调用函数向上查找找不到breturn 0;
}

6:默认情况值捕捉过来的变量是被const修饰的,不能被修改,如果想让其被修改,可以使用mutable修饰,但是此时参数列表即使为空,()不能被省略。

int main()
{int a = 0,b = 1;auto tmp1 = [a,&b]()mutable//此时虽然没有参数,但是()是不能够省略的。{a++;//如果去掉了mutable,这句代码是错的,mutable去掉了a的constb++;};return 0;
}

细节:我虽然在lambda表达式中修改了a,但是因为是值捕捉,是局部域中的a拷贝构造给了lambda表达式中的a。对lambda表达式中的a修改的话是不会影响到外面的

来看示例:

int x = 0;
int main()
{int a = 0, b = 1, c = 2, d = 3;//显示值捕捉auto tmp1 = [a](int x, int y)->int{//a++;//错误代码,a无法被修改return a + x + y;};cout << tmp1(1, 2) << endl;//隐式值捕捉,lambda表达式用到的才去捕捉auto tmp2 = [=]{int ret = 0;ret = a + b + c + d;return ret;};cout << tmp2() << endl;//混合捕捉auto tmp3 = [&]{a++;b++;c++;d++;return a + b + c + d;};cout << tmp3() << endl;static int y = 0;auto tmp4 = []//不能捕捉全局变量和静态局部变量{cout << "hello world" << endl;};tmp3();//mutable去除mutable特性auto tmp5 = [=]()mutable{a++;b--;c++;d--;};tmp5();return 0;
}

lambda的应用

在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。
比如在算法库中,sort是一个函数模版,需要实现一个可调用对象来进行排序比较,以前我们是使用的仿函数,但是现在可以使用lambda表达式。
来看示例:

struct Goods{string _name;int _evaluatee;//评分int _price;//价格
}int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& g1,const Goods& g2){return g1._price < g2._price;});//这里就可以不用再写仿函数传仿函数了,写起来更加方便。return 0;
}

lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的,以后我们会不断接触到

lambda的原理

1:lambda表达式的底层是仿函数,如果去汇编代码看的话,你会发现和仿函数执行的汇编代码相似。编译器会生成一个类似仿函数的类
想一下,如果我们都是用lambda表达式,难道编译器生成的类名不可能重复吗,如果重复了,就会造成调用operator()调用的不是我想调的,这该怎么办呢?
编译器底层使用uuid生成字符串作为类名
这种方式生成相同字符串的概率特别小,比你能中彩票的概率都要小得多,具体可以在网上了解一下uuid,本文不再详细讲解。
2:lambda表达式的参数,返回类型,函数体,就是operator()仿函数的参数,返回类型,函数体。lambda表达式捕捉的本质就是捕捉的内容是operator()的这个类中调用构造函数的实参。即捕获的内容被初始化成员变量的内容了

来看一下下面的这段代码:

class Rate{
public:Rate(double rate):_rate(rate){}operator()(double money, int year){return _rate*money*year;}
private:double _rate;
};
int main()
{double rate = 0.025;auto tmp1 = [rate](double money,int year){return rate*money*year;};tmp1(10000,10);Rate tmp2(rate);tmp2(10000,10);return 0;
}

在这里插入图片描述

包装器

我们先来理解一个点,现在c++11中,可调用对象有三者,分别是函数指针,仿函数,lambda表达式。由于三者的类型不同,我们是不能够将三者存储在一个容器中的,但是c++11有了包装器,就能够做到这一点。

function

被包含在头文件< functional>中
在这里插入图片描述

注意:上面的那个是一个模版,但是只是声明,而下面的那个是模版特化的语法,但是这里特化和普通模版的特化有点区别

std::function是一个包装器,也是一个类模版。

std::function 的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、 lambda 、 bind 表达式等,存储的可调用对象被称为std::function 的目标

为什么function能够包装其他可以调用的对象:
因为function的构造函数重载了函数模版的版本。
在这里插入图片描述
在这里插入图片描述
我们仔细来查看这个接口,能够发现其中提供了operator接口,如果此时我使用了可调用对象来构造function类,如果再调用operator()接口,就能够直接调用到这个用来构造function类的可调用对象。如果说此时没有使用可调用对象来初始化function,但是调用operator(),调用空std::function 的目标导致抛出std::bad_function_call异常。

function的作用:能够对不同的可调用对象统一它们的类型,方便存取,这样在很多地方就方便声明可调用对象的类型。

我们先来看几个简单的使用function来包装可调用对象:

int f(int x, int y)
{return x + y;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};
int main()
{function<int(int, int)> f1(f);function<int(int, int)> f2 = f;function<int(int, int)> f3 = Functor();auto tmp = [](int a, int b) {return a + b;};function<int(int, int)> f4 = tmp;//此时这三个可调用对象就是同一种类型了,可以将其放入一个容器中vector<function<int(int, int)>> funcv = { f,Functor(),tmp };for (auto& e : funcv){cout << e(10, 20) << endl;}return 0;
}

在这里插入图片描述

function包装可调用对象的规则:类型必须和可调用对象一致(包含参数类型,参数个数)
模版特化的规则:可调用对象的返回值(可调用对象形参数据类型,…)(对应着个数就可以)

使用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<double(Plus*, double, double)> f1 = &Plus::plusd;Plus ps;cout << f1(&ps, 1.1, 2.2) << endl;function<double(Plus, double, double)> f2 = &Plus::plusd;cout << f2(ps, 2.2, 3.3) << endl;function<double(Plus&&, double, double)> f3 = &Plus::plusd;cout << f3(move(ps), 3.3, 4.4) << endl;return 0;
}

在这里插入图片描述

注意:普通成员函数还有一个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以

bind

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

bind函数的作用:修改参数的顺序和调整参数个数

在讲解bind函数作用的时候,我们再来讲解一个类叫placeholders。
在这里插入图片描述

形式:auto newCallable = bind(callable,arg_list);
bind也要搭配auto或者function(因为bind也是包装器,可以使用function包装器来接收,但是注意一定类型要匹配)共同使用
newCallable:是bind函数返回的可调用对象(而且第一个一定要传这个)。
callable:是传递的先前已经实现好的可调用对象
arg_list:参数列表(可以手动传递实参,也可写placeholders中的成员变量)。参数列表的顺序最好对应传过来的可调用函数的参数数据类型顺序
placeholders成员变量的作用:
arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表示newCallable的参数,它们占据了传递给newCallable的参数的位置。可以理解为:_1始终代表返回的可调用对象的第一个实参,_2始终代表返回的可调用对象的第二个实参,以此类推

来看下例代码:

int Sub(int a, int b)
{return (a - b) * 10;
}
int main()
{	//auto sub1 = bind(Sub, 10, 20);//固定了两个参数,此时实参中不需要传参auto sub1 = bind(Sub, _1, _2);cout << sub1(1, 2) << endl;auto sub1 = bind(Sub, _2, _1);cout << sub1(1, 2) << endl;return 0;
}

在这里插入图片描述
这样通过placeholders就完成了对参数顺序的变化,其实这个效果用的很少很少。

接下来来看参数个数的调整:

int Sub(int a, int b)
{return (a - b) * 10;
}
int main()
{	auto sub1 = bind(Sub, 10, 20);//固定了两个参数,此时实参中不需要传参cout << sub1() << endl;auto sub2 = bind(Sub, 10, _1);//固定了一个参数,此时实参中只有传一个参数cout << sub2(10) << endl;auto sub3 = bind(Sub, _1, 10);//固定了一个参数,此时实参中只有传一个参数cout << sub3(10) << endl;return 0;
}

那bind这个用作包装器应用在哪里呢?
在上面,我们是不是发现function包装成员函数的时候由于this指针的影响,导致这个第一个参数传递的是类的指针或者是对象。
所以,这个时候我们绑定第一个类的指针或者是对象,接下来调用可调用对象的时候就不用再来传递类的指针或者是对象了
来看代码:

function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;

所以:bind一般用于:绑死一些参数

总结

今天我们学习了lambda表达式,新增了可调用对象(三个),学习了lambda的语法格式,每一个部分的特点(应该怎么填,哪些可以省略),最重要的就是捕捉列表。具有显示捕捉,隐式捕捉,混合捕捉。并且学习了lambda的底层就仿函数。随后学习了包装器,学习了function细节是什么,格式是什么,怎么写,为什么有这个(统一可调用变量的类型),学习了bind,作用是什么,如何使用,格式是什么

结语

感谢大家阅读我的文章,不足之处欢迎留言指出,感谢大家支持!!
学而不思则罔,思而不学则殆!!在这里插入图片描述


http://www.hkcw.cn/article/bpjCmjHeLL.shtml

相关文章

动态设置微信小程序页面标题(navigationBarTitleText属性)

前言&#xff1a; 最近在公司进行小程序研发的时候&#xff0c;产品给出了一个动态加载页面标题的需求&#xff0c;经过调研之后将结果在这里与各位伙伴进行分享。 代码展示&#xff1a; 在.json文件中进行初始配置&#xff1a; { "usingComponents": {}, &q…

PostgreSQL数据库配置SSL操作说明书

背景&#xff1a; 因为postgresql或者mysql目前通过docker安装&#xff0c;只需要输入主机IP、用户名、密码即可访问成功&#xff0c;这样其实是不安全的&#xff0c;可能会通过一些手段获取到用户名密码导致数据被窃取。而ES、kafka等也是通过用户名/密码方式连接&#xff0c;…

基于python 将图像上同一行距离相近的矩形框融合

import os import cv2 import numpy as npdef get_files(path):""" 获取指定路径下所有文件名称 """files []for filename in os.listdir(path):if os.path.isfile(os.path.join(path, filename)):files.append(filename)return filesdef split…

拉取gitlab项目

一、下载nvm管理node 先下载配置好nvm,再用nvm下载node 下载链接&#xff1a;开始 下载nvm - nvm中文官网 情况&#xff1a;npm i 下载依赖缓慢&#xff0c;可能是node版本不对&#xff0c;可能node版本太高 可能得问题&#xff1a;使用nvm 下载低版本的node时&#xff0c;…

Opencv4 c++ 自用笔记 01 Mat类

Mat类 Mat类用来保存矩阵类型的数据&#xff0c;包括向量、矩阵、灰度、通道数。主要分为两部分&#xff0c;矩阵头和矩阵指针。 &#xff08;单通道为灰度图像&#xff0c;3通道或4通道为彩色图像&#xff09; 矩阵头中包含矩阵形状、存储方法、地址、引用次数等。矩阵指针…

Linux `|` 管道符与 `grep` 命令深度解析与高阶应用指南

Linux `|` 管道符与 `grep` 命令深度解析与高阶应用指南 一、核心机制深度解析1. 管道符 `|` 的底层原理2. grep 的匹配引擎二、高阶组合应用技巧1. 多级过滤管道2. 实时监控与告警3. 结构化数据处理三、企业级应用场景1. 安全审计系统2. 性能监控告警3. 日志关联分析四、性能优…

STP配置

由于我们演示的是STP 但是华为交换机默认的都是MSTP所以要换到STP以下是方法 STP mode &#xff1f; 查看模式 STP mode stp 选择stp 换好了后配置交换机优先级 [SWA]stp priority 4096 Apr 15 2013 16:15:33-08:00 SWA DS/4/DATASYNC_CFGCHANGE:OID 1.3.6.1.4.1.2011.5…

CentOS_7.9 2U物理服务器上部署系统简易操作步骤

近期单位网站革新&#xff0c;鉴于安全加固&#xff0c;计划将原有Windows环境更新到Linux-CentOS 7.9&#xff0c;这版本也没的说&#xff08;绝&#xff09;了&#xff08;版&#xff09;官方停止更新&#xff0c;但无论如何还是被sisi的牵挂着这一大批人&#xff0c;毕竟从接…

《仿盒马》app开发技术分享-- 订单详情页(端云一体)

开发准备 在之前的章节中我们实现了订单的提交&#xff0c;以及提交之后跳转到确认订单页面&#xff0c;在确认订单页面我们添加了一个入口&#xff0c;这个入口是查询订单&#xff0c;当我们点击入口时&#xff0c;我们需要跳转到一个新的界面&#xff0c;这个界面通过接收上…

C# 控制台程序获取用户输入数据验证 不合规返回重新提示输入

在 C# 控制台程序中实现输入验证并循环重试&#xff0c;可以通过以下方式实现高效且用户友好的交互。以下是包含多种验证场景的完整解决方案&#xff1a; 一、通用输入验证框架 public static T GetValidInput<T>(string prompt, Func<string, (bool IsValid, T Val…

Linux:shell脚本常用命令

一、设置主机名称 1、查看主机名称 2、用文件的方式更改主机名称 重启后&#xff1a; 3、 通过命令修改主机名 重启后&#xff1a; 二、网络管理命令 1、查看网卡 2、设置网卡 &#xff08;1&#xff09;网卡未被设置过时 &#xff08;2&#xff09;当网卡被设定&#xff0c…

2025年渗透测试面试题总结-匿名[校招]安全研究员(SAST方向)(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 匿名[校招]安全研究员(SAST方向) 一面问题回答框架 1. 自我介绍 2. 简历深挖&#xff08;漏洞挖掘&#x…

基于Java,SpringBoot,Vue,UniAPP医院预约挂号买药就诊病例微信小程序系统设计

摘要 随着医疗信息化的不断推进以及“互联网医疗”模式的广泛普及&#xff0c;传统医院挂号流程中存在的排队时间长、资源分配不均等问题日益凸显&#xff0c;急需通过数字化手段加以解决。本研究设计并实现了一套基于Java、SpringBoot、Vue与UniAPP技术栈的医院预约挂号微信小…

制作一款打飞机游戏62:添加音效

添加音乐 今天&#xff0c;我们要添加音乐。 首先&#xff0c;打开包含音轨的文件夹&#xff0c;然后使用文本编辑器打开cowshrub文件。接着&#xff0c;打开捐赠卡&#xff0c;复制其中的音乐和音效数据&#xff0c;粘贴到cowshrub文件中&#xff0c;替换原有的音效块&#…

WPF log4net用法

WPF log4net用法 一、在工程中管理NuGet程序包&#xff0c;找到log4net&#xff0c;点击安装&#xff0c;如下图已成功安装&#xff1b; 二、在工程中右键添加新建项&#xff0c;选择应用程序配置文件&#xff08;后缀为.config&#xff09;,然后设置名称&#xff0c;这里设置…

视频监控管理平台EasyCVR安防监控小知识:视频监控AI智能分析的常见部署方式有哪些?

一、方案背景​ 随着视频监控技术迭代&#xff0c;AI智能分析成为提升系统价值的核心。通过实时处理视频流辅助决策&#xff0c;广泛应用于智慧社区、园区等场景。由于AI算法部署方式多样且各有适用场景&#xff0c;因此合理选择部署方式是项目成功的关键。 二、主流AI算法部署…

5G RedCap是什么-与标准5G的区别及支持路由器推荐

技术背景与重要性 从智能穿戴到工业传感器&#xff0c;物联网设备种类繁多&#xff0c;但并非所有设备都需要标准5G的全部功能。为满足这些中端应用的需求&#xff0c;3GPP在Release 17中引入了5G RedCap&#xff08;Reduced Capability&#xff09;&#xff0c;也称为5G NR-L…

vmware虚拟机固定IP

vmware虚拟机固定IP vmware虚拟机端设置 vmware 设置nat模式 打开设置“网络和Internet”&#xff0c;点击“更改适配器选项”&#xff0c;点击适配器VMnet8&#xff0c;修改IP和DNS相关配置 虚拟机端设置 root用户登录虚拟机&#xff0c;输入命令&#xff1a; vi /et…

据传苹果将在WWDC上发布iOS 26 而不是iOS 19

苹果可能会对其操作系统的编号方式做出重大改变&#xff0c;基于年份的新版系统会将iOS 19重新命名为 iOS 26&#xff0c;同时 macOS 也会以同样的方式命名。 苹果的编号系统相当简单&#xff0c;版本号每年都会像钟表一样定期更新。然而&#xff0c;今年秋天情况可能有所不同&…

【深度剖析】义齿定制行业数字化转型模式创新研究(上篇2:痛点和难点分析)

数字化转型正在重塑义齿行业的生态格局,但也面临技术融合与模式变革的深层挑战。当前,义齿定制行业正处于从传统手工制造向全流程数字化制造转型的关键阶段。3D扫描、CAD/CAM(计算机辅助设计与制造)、3D打印等技术的广泛应用,显著提升了义齿制作的精度和效率。传统石膏模型…