[c++11(二)]Lambda表达式和Function包装器及bind函数

news2024/12/21 22:54:27

1.前言

Lambda表达式着重解决的是在某种场景下使用仿函数困难的问题,而function着重解决的是函数指针的问题,它能够将其简单化。

本章重点:

本章将着重讲解lambda表达式的规则和使用场景,以及function的使用场景及bind函数的相关使用方法。

2.为什么要有Lambda表达式

在C++98中,对自定义类型进行排序时,需要自己写仿函数,并传递给sort库函数
但是如果每次要按照自定义类型的不同成员变量进行排序的话,就要写很多个仿
函数,十分的不方便,于是C++11给出了一个新玩法:

struct Goods
{
string _name;  // 名字
double _price; // 价格
int _evaluate; // 评价
Goods(const char* str, double price, int evaluate)
	:_name(str)
	, _price(price)
	, _evaluate(evaluate)
{}
};
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price < g2._price; });//按照价格升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._price > g2._price; });//按照价格降序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate < g2._evaluate; });//按照评价升序
sort(v.begin(), v.end(), [](Goods g1, Goods g2)->bool
{return g1._evaluate > g2._evaluate; });//按照评价降序

 后面那一坨就完美的代替了仿函数。他其实就是传说中的lambda表达式

上述代码具体分析如下:

3.Lambda表达式的语法

lambda 表达式书写格式: [capture-list] (parameters) mutable -> return-type { statement
}
表达式部分说明:
[capture-list] : 捕捉列表 ,该列表总是出现在 lambda 函数的开始位置, 编译器根据[]来
判断接下来的代码 是否为lambda函数 捕捉列表能够捕捉上下文中的变量供 lambda
函数使用
(parameters) :参数列表。与 普通函数的参数列表一致 ,如果不需要参数传递,则可以
连同 () 一起省略
mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量
性。 使用该修饰符时,参数列表不可省略(即使参数为空)。
->returntype :返回值类型 。用 追踪返回类型形式声明函数的返回值类型 ,没有返回
值时此部分可省略。 返回值类型明确情况下,也可省略,由编译器对返回类型进行推
{statement} :函数体 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
lambda 函数定义中, 参数列表和返回值类型都是可选部分 ,而捕捉列表和函数体可以为
。因此 C++11 最简单的 lambda 函数为: []{} ; lambda 函数不能做任何事情。
例:
int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
   []{}; 
    
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
   [=]{return a + 3; }; 
    
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c){b = a + c; }; 
    fun1(10)
    cout<<a<<" "<<b<<endl;
    
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
    cout<<fun2(10)<<endl;
    
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
    cout << add_x(10) << endl; 
    return 0;
}
通过上述例子可以看出, lambda 表达式实际上可以理解为无名函数,该函数无法直接调
用,如果想要直接调用,可借助 auto 将其赋值给一个变量。

4.Lambda表达式的捕捉列表

lambda表达式的捕捉列表[ ]可以捕捉父作用域的变量供自己使用。

规则如下:

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int x = 10;
    int y = 20;
    std::vector<int> v = {1, 2, 3, 4, 5};

    // 混合捕获
    std::vector<int> filtered;
    std::copy_if(v.begin(), v.end() [x, &y](int z) { return z > x && z < y; });

    // 输出过滤后的结果
    for (int n : filtered) {
        std::cout << n << " ";
    }
    std::cout << std::endl;

    return 0;
}

 在这个例子中,[x, &y] 捕获 x 的值和 y 的引用。

注意:

 lambda表达式之间不能相互赋值,即使看起来类型是相同的。

lambda表达式的使用方法和仿函数非常相似,实际在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理的即:如果定义了一个lambda表达式,
编译器会自动生成一个类,在该类中重载了operator()

mutable关键字详解:

由于lambda表达式是具有常属性的,所以在通常的情况下是无法被修改的,因此在C++14中引入了mutable关键字,可以用于Lambda表达式中,以允许Lambda表达式修改捕获的变量。

例:

#include <iostream>

int main() {
    int x = 10;
    auto lambda = [=]() mutable { ++x; }; // 值捕获,允许修改
    lambda();
    std::cout << x << std::endl; // 输出: 10
    return 0;
}

 分析:为什么这里用了mutable之后,输出还是10呢?

简单理解就是:类比函数传参,你在这里传的只是x的副本,并不是真正的x,所以外部的x并没有被修改。mutable 关键字的作用是允许 Lambda 表达式修改通过值捕获的变量。然而,值捕获的本质是复制外部变量的值到 Lambda 表达式的内部环境,

5.function包装器

function 包装器 也叫作适配器。 C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要 function 呢?
ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?
//也有可能是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型
//可能会导致模板的效率低下!
template<class F, class T>
T useF(F f, T x)
{
	static int count = 0;
	cout << "count:" << ++count << endl;
	cout << "count:" << &count << endl;
	return f(x);
}
double f(double i)
{
	return i / 2;
}
struct Functor
{
	double operator()(double d)
	{
		return d / 3;
	}
};
int main()
{
// 函数名
 cout << useF(f, 11.11) << endl;
 // 函数对象
 cout << useF(Functor(), 11.11) << endl;
 // lamber表达式
 cout << useF([](double d)->double{ return d/4; }, 11.11) << endl;
 return 0;
}

这样的话一份useF就实例化出了三份代码,这样就比较low了。

那么如果用function的话,那么就可以提高效率了。

Function函数的使用方法:

第一个int表示返回值,()里面的int表示参数的类型。

回到上述要解决的问题,解决方式如下:

#include <functional>
template<class F, class T>
T useF(F f, T x)
{
 static int count = 0;
 cout << "count:" << ++count << endl;
 cout << "count:" << &count << endl;
 return f(x);
}
double f(double i)
{
 return i / 2;
}
struct Functor
{
 double operator()(double d)
 {
 return d / 3;
 }
};

int main()
{
 // 函数名
 std::function<double(double)> func1 = f;
 cout << useF(func1, 11.11) << endl;
 // 函数对象
 std::function<double(double)> func2 = Functor();
 cout << useF(func2, 11.11) << endl;
 // lamber表达式
 std::function<double(double)> func3 = [](double d)->double{ return d /
4; };
 cout << useF(func3, 11.11) << endl;
 return 0;
}

6.function包装器的使用场景

例如:1.可以存储在容器中,使得可以动态地管理和调用函数。

#include <iostream>
#include <vector>
#include <functional>

int main() {
    std::vector<std::function<void()>> functions;

    functions.push_back([]() { std::cout << "Function 1" << std::endl; });
    functions.push_back([]() { std::cout << "Function 2" << std::endl; });

    for (auto& func : functions) {
        func();
    }

    return 0;
}

 在这个例子中,functions 容器存储了多个 std::function<void()> 类型的函数,并在运行时依次调用这些函数。

在操作系统中,不同的线程要执行不同的函数的话,那么就可以用这种方式 来进行封装并且调用函数。

2.函数适配器

std::function 可以用于创建函数适配器,使得可以将不同类型的函数适配为统一的接口。

#include <iostream>
#include <functional>

void functionA(int x) {
    std::cout << "Function A called with " << x << std::endl;
}

void functionB(double x) {
    std::cout << "Function B called with " << x << std::endl;
}

int main() {
    std::function<void(int)> adapterA = functionA;
    std::function<void(double)> adapterB = functionB;

    adapterA(10);
    adapterB(3.14);

    return 0;
}

 在这个例子中,adapterAadapterB 分别适配了不同类型的功能函数,使得它们可以统一调用。

7.bind函数

std::bind 函数定义在头文件中, 是一个函数模板,它就像一个函数包装器 ( 适配器 ) 接受一个可
调用对象( callable object ),生成一个新的可调用对象来 适应 原对象的参数列表 。一般而
言,我们用它可以把一个原本接收 N 个参数的函数 fn ,通过绑定一些参数,返回一个接收 M 个( M
可以大于 N ,但这么做没什么意义)参数的新函数。同时,使用 std::bind 函数还可以实现参数顺
序调整等操作。
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来适应原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的
callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list
的参数
在正式使用bind函数之前,先介绍一下  std::placeholders 占位符
std::placeholders 提供占位符 _1, _2, _3 等,用于表示函数调用时的参数位置。
例如:
#include <iostream>
#include <functional>

void print(int a, int b) {
    std::cout << "a: " << a << ", b: " << b << std::endl;
}

int main() {
    auto bound_func = std::bind(print, std::placeholders::_2, std::placeholders::_1);
    bound_func(20, 10); // 输出: a: 10, b: 20
    return 0;
}

在这个例子中,std::bindprint 函数的参数位置交换了,使得 _2 即func里面的第二个参数作为print的第一个参数,_1 即func里面的第一个参数作为第二个参数传递给 print 函数。

简单总结一下:std::bind 是 C++ 标准库中的一个函数模板,用于绑定函数和对象,以便创建新的可调用对象std::bind 可以用于创建适配器,将函数、成员函数、甚至是函数对象绑定到特定的参数,从而生成新的可调用对象。

8.bind函数的使用场景

1. 绑定带有默认参数的成员函数

#include <iostream>
#include <functional>

class MyClass {
public:
    void print(int a, int b = 0) {
        std::cout << "a: " << a << ", b: " << b << std::endl;
    }

    void bindAndCall() {
        // 使用 std::bind 绑定成员函数和 this 指针
        auto bound_func = std::bind(&MyClass::print, this, std::placeholders::_1, 20);

        // 调用绑定后的函数
        bound_func(10); // 输出: a: 10, b: 20
    }
};

int main() {
    MyClass obj;
    obj.bindAndCall();
    return 0;
}

2.绑定函数对象

#include <iostream>
#include <functional>

class PrintFunc {
public:
    void operator()(int a, int b) {
        std::cout << "a: " << a << ", b: " << b << std::endl;
    }
};

int main() {
    PrintFunc pf;
    auto bound_func = std::bind(pf, std::placeholders::_1, 20);
    bound_func(10); // 输出: a: 10, b: 20
    return 0;
}

9.总结

lambda表达式和function包装器以及bind函数到这就讲解完毕了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2263469.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis数据类型:list

list 的相关命令配合使用的应用场景&#xff1a; 栈和队列&#xff1a;插入和弹出命令的配合&#xff0c;亦可实现栈和队列的功能 实现哪种数据结构&#xff0c;取决于插入和弹出命令的配合&#xff0c;如左插右出或右插左出&#xff1a;这两种种方式实现先进先出的数据结构&a…

基于51单片机的验证码收发系统的仿真设计

一、设计要求 主机、从机均以AT89C52单片机为控制核心。主机生成6位随机验证码、并将验证码发送给从机&#xff1b;从机输入验证码发送给主机&#xff0c;主机接收来自从机发送的验证码并核对两个验证码是否一致。 二、设计内容 主机通过独立按键生成6位随机验证码并发送给从…

WPF实现曲线数据展示【案例:震动数据分析】

wpf实现曲线数据展示&#xff0c;函数曲线展示&#xff0c;实例&#xff1a;震动数据分析为例。 如上图所示&#xff0c;如果你想实现上图中的效果&#xff0c;请详细参考我的内容&#xff0c;创作不易&#xff0c;给个赞吧。 一共有两种方式来实现&#xff0c;一种是使用第三…

[机器学习]AdaBoost(数学原理 + 例子解释 + 代码实战)

AdaBoost AdaBoost&#xff08;Adaptive Boosting&#xff09;是一种Boosting算法&#xff0c;它通过迭代地训练弱分类器并将它们组合成一个强分类器来提高分类性能。 AdaBoost算法的特点是它能够自适应地调整样本的权重&#xff0c;使那些被错误分类的样本在后续的训练中得到…

详细解读TISAX认证的意义

详细解读TISAX认证的意义&#xff0c;犹如揭开信息安全领域的一颗璀璨明珠&#xff0c;它不仅代表了企业在信息安全管理方面的卓越成就&#xff0c;更是通往全球汽车供应链信任桥梁的关键一环。TISAX&#xff0c;即“Trusted Information Security Assessment Exchange”&#…

黑马Redis数据结构学习笔记

Redis数据结构 动态字符串 Intset Dict ZipList QuickList SkipList 类似倍增 RedisObject 五种数据类型 String List Set ZSet Hash

sqlilabs靶场二十一关二十五关攻略

第二十一关 第一步 可以发现cookie是经过64位加密的 我们试试在这里注入 选择给他编码 发现可以成功注入 爆出表名 爆出字段 爆出数据 第二十二关 跟二十一关一模一样 闭合换成" 第二十三关 第二十三关重新回到get请求&#xff0c;会发现输入单引号报错&#xff0c…

Win10将WindowsTerminal设置默认终端并添加到右键(无法使用微软商店)

由于公司内网限制&#xff0c;无法通过微软商店安装 Windows Terminal&#xff0c;本指南提供手动安装和配置新版 Windows Terminal 的步骤&#xff0c;并添加右键菜单快捷方式。 1. 下载新版终端安装包: 访问 Windows Terminal 的 GitHub 发布页面&#xff1a;https://githu…

从地铁客流讲开来:十二城日常地铁客运量特征

随着城市化进程的加速和人口的不断增长&#xff0c;公共交通系统在现代都市生活中扮演着日益重要的角色。地铁作为高效、环保的城市交通方式&#xff0c;已经成为居民日常出行不可或缺的一部分。本文聚焦于2024年10月28日至12月1日期间&#xff0c;对包括北上广深这四个超一线城…

firefox浏览器如何安装驱动?

firefox的浏览器驱动:https://github.com/mozilla/geckodriver/releases 将geckodriver.exe所在文件路径追加到系统环境变量path的后面

2.2.3 进程通信举例

文章目录 PV操作实现互斥PV操作实现同步高级通信 PV操作实现互斥 PV操作实现互斥。先明确临界资源是什么&#xff0c;然后确定信号量的初值。实现互斥时&#xff0c;一般是执行P操作&#xff0c;进入临界区&#xff0c;出临界区执行V操作。 以统计车流量为例。临界资源是记录统…

UE5 移植Editor或Developer模块到Runtime

要将源码中的非运行时模块移植到Runtime下使用,个人理解就是一个解决编译报错的过程,先将目标模块复制到项目的source目录内,然后修改模块文件夹名称,修改模块.build.cs与文件夹名称保持一致 修改build.cs内的类名 ,每个模块都要修改 // Copyright Epic Games, Inc. All …

Qt WORD/PDF(三)使用 QAxObject 对 Word 替换(QML)

关于QT Widget 其它文章请点击这里: QT Widget 国际站点 GitHub: https://github.com/chenchuhan 国内站点 Gitee : https://gitee.com/chuck_chee 姊妹篇: Qt WORD/PDF&#xff08;一&#xff09;使用 QtPdfium库实现 PDF 操作 Qt WORD/PDF&#xff08;二…

RAG基础知识及综述学习

RAG基础知识及综述学习 前言1.RAG 模块1.1 检索器&#xff08;Retriever&#xff09;1.2 检索融合&#xff08;Retrieval Fusion&#xff09;1.3 生成器&#xff08;Generator&#xff09; 2.构建检索器&#xff08;Retriever&#xff09;2.1 分块语料库2.2 编码文本块2.3 构建…

移动网络(2,3,4,5G)设备TCP通讯调试方法

背景&#xff1a; 当设备是移动网络设备连接云平台的时候&#xff0c;如果服务器没有收到网络数据&#xff0c;移动物联设备发送不知道有没有有丢失数据的时候&#xff0c;需要一个抓取设备出来的数据和服务器下发的数据的方法。 1.服务器系统是很成熟的&#xff0c;一般是linu…

深入剖析MyBatis的架构原理

架构设计 简要画出 MyBatis 的架构图 >> ​​ Mybatis 的功能架构分为哪三层&#xff1f; API 接口层 提供给外部使用的接口 API&#xff0c;开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。MyBatis 和数据库的…

android opencv导入进行编译

1、直接新建module进行导入&#xff0c;选择opencv的sdk 导入module模式&#xff0c;选择下载好的sdk&#xff0c;修改module name为OpenCV490。 有报错直接解决报错&#xff0c;没报错直接运行成功。 2、解决错误&#xff0c;同步成功 一般报错是gradle版本问题较多。我的报…

智能座舱进阶-应用框架层-Jetpack主要组件

Jetpack的分类 1. DataBinding&#xff1a;以声明方式将可观察数据绑定到界面元素&#xff0c;通常和ViewModel配合使用。 2. Lifecycle&#xff1a;用于管理Activity和Fragment的生命周期&#xff0c;可帮助开发者生成更易于维护的轻量级代码。 3. LiveData: 在底层数据库更…

设计模式-访问者设计模式

介绍 访问者模式&#xff08;Visitor&#xff09;&#xff0c;表示一个作用于某对象结构中的各元素的操作&#xff0c;它使你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。 问题&#xff1a;在一个机构里面有两种员工&#xff0c;1.Teacher 2.Engineer 员…

springmvc的拦截器,全局异常处理和文件上传

拦截器: 拦截不符合规则的&#xff0c;放行符合规则的。 等价于过滤器。 拦截器只拦截controller层API接口。 如何定义拦截器。 定义一个类并实现拦截器接口 public class MyInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest reque…