C++11可变参数模板,lambda表达式,包装器

news2024/11/17 13:38:41

目录

可变参数模板

lambda表达式

问题的引入

lambda表达式语法

捕捉列表的使用

函数对象和lambda表达式

function包装器


 

可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了。

//Args是一个模板参数包,args是一个函数形参参数包
//声明一个参数包Args ...  args,这个参数包可以包括0到任意个模板参数.
template<class ...Args>
void ShowList(Args ...  args)
{}

以上代码代表ShowList可以有0到任意个模板参数.

int main()
{
    string str = "hello";
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', str);
}

这样写都可以,编译器会自动推导出变量的类型。
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法 这样方式获取可变参数,所以我们的用一些特殊的方法来获取参数包的值。

我们首先可以根据sizeof...(args)算出参数包的 个数。

但是参数包里面的内容该如何取出来呢?

有两种方法,这里给出一种取出的方法.

//递归终止
void ShowList()
{
    cout <<  endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(const T& value, Args... args)
{
    cout << value << " ";
    ShowList(args...);
}

它的思路是:增加了一个模板参数T,每次传入参数包时,第一个参数总是T,然后取出来,把参数包中剩余的再作为一个参数包传给自身,相当于递归调用。当参数为空的时候,说明已经全部取完,会自动调用合适的函数,即无参的函数。

我们运行一下:

可以看到已经全部取出来了.

lambda表达式

问题的引入

在C++98中,我们如果想对一个数组中的元素进行排序,可以进行如下操作:

int main()
{
    int array[] = { 4,1,8,5,3,7,0,9,2,6 };
    // 默认按照小于比较,排出来结果是升序
    std::sort(array, array + sizeof(array) / sizeof(array[0]));
    // 如果需要降序,需要改变元素的比较规则
    std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    return 0;
}

这里说句题外话:我们发现greater<int>后面这个括号有时候加,有时候又不加。那我们该如何分辨呢?

其实如果传入的是对象的话,就需要加上(),毕竟需要构造一个匿名对象来使用。

   sort(arr.begin(), arr.end(), greater<int>());

而如果需要传入的是类型的话,则不需要加(),直接greater<int>即可,例如优先级队列:

    priority_queue<int, vector<int>, greater<int>>;

如果待排序元素为自定义类型,需要用户定义排序时的比较规则。

struct Goods
{
    string _name;
    double _price;
    int _evaluate;
};
struct Compare
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price <= gr._price;
    }
};
int main()
{
    vector<Goods> gds{ { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
    sort(gds.begin(),gds.end(), Compare());
    return 0;
}

但是以上只是一种比较写法,只是按价格大的进行比较,一共有3个属性,如果我们都想用的话,得写6个仿函数,会显得非常麻烦。

所以这个时候我们需要用lambda表达式了.

lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
1. lambda表达式各部分说明
[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来
的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起
省略
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修
饰符时,参数列表不可省略(即使参数为空)。

->returntype:返回值类型。追踪返回类型形式声明函数的返回值类型,没有返回值时此部分
可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

看一个比较简单的lambda例子:

    //两个数相加的lambda 
    [](int a, int b)->int {return a + b; };

那我们该怎么使用这个式子呢,由于lambda函数是一个匿名的函数。

此时auto就发挥作用,用auto一个变量来接收它.

    auto add1 = [](int a, int b)->int {return a + b; };

拿到之后,我们就该调用一下,调用方法如下:

    cout << add1(1, 2) << endl;

就和正常函数调用一样,我们运行:

答案是3,也符合我们的预期。

当然因为返回值编译器会自己推导,我们也可以忽略返回值,这样写:

 

    auto add2 = [](int a, int b) {return a + b;  };

接下来,我们写一个交换变量的lambda.

    int x = 1, y = 0;
    //格式不一定一定要写在一行,可以多行分开
    auto swap1 = [](int& x1, int& x2) {
        int tmp = x1;
        x1 = x2;
        x2 = tmp;
    };
    swap1(x, y);
    cout << "x :" << x << "   " << " y :" << y << endl;

可以发现运行结果也正确了。

但是如果我们不传参数,就交换x和y呢 ?

捕捉列表的使用

这里就用到了捕捉列表:

    int x = 1, y = 0;
    //这里捕捉x和y,然后进行操作
    auto swap2 = [x, y] {
        int tmp = x;
        x = y;
        y = tmp;
    };

那以上这么写可以吗?不可以的,被捕捉的对象不能被修改

所以这个时候需要用mutable.用mutable记住一定要在前加上().

所以应该改成如下这样:

    int x = 1, y = 0;
    auto swap2 = [x, y]()mutable {
        int tmp = x;
        x = y;
        y = tmp;
    };

但是我们调用它,然后输出一下结果:

发现x和y并没有交换.其实是因为:

mutable在这里仅仅是让捕捉的对象可以被修改,但并不是说修改后会影响外面的

也就是相当于只是一份形参,改动不会影响外面的实参.

那我们就是真正想改变捕捉到的外面的对象该怎么办?改成传引用捕捉即可.

    auto swap3 = [&x, &y]{
        int tmp = x;
        x = y;
        y = tmp;
    };

在x和y前面加上&符号。

这样便成功交换了外面捕捉到的对象.

了解了这个,我们再回到一开始那个情景,这样我们就不再需要写6个仿函数了,而是可以这么写:

 

struct Goods
{
    string _name;
    double _price;
    int _evaluate;
};
int main()
{
    vector<Goods> gds{ { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
    sort(gds.begin(), gds.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price <= g2._price; });
    return 0;
}

而且哪个属性和哪个属性比较也一目了然。当然需要别的,可以直接改lambda函数里面的内容即可.

   sort(gds.begin(), gds.end(), [](const Goods& g1, const Goods& g2) {
        return g1._name <= g2._name; });
   sort(gds.begin(), gds.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate <= g2._evaluate; });

 当然捕捉列表还有以下的用法,有兴趣同学可以自行了解.

2. 捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针
注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。

[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同.(lambda --->lambda_uuid)

函数对象和lambda表达式

函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的类对象。

class Rate
{
public:
    Rate(double rate) : _rate(rate)
    {}
    double operator()(double money, int year)
    {
        return money * _rate * year;
    }
private:
    double _rate;
};
int main()
{
    // 函数对象
    double rate = 0.49;
    Rate r1(rate);
    r1(10000, 2);
    // lambda
    auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
    r2(10000, 2);
    return 0;
}

从使用方式上来看,函数对象与lambda表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

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

lambda_xxxxx,后面这一串乱码是uuid,是用来标识唯一,每个lambda表达式都有自己的标识,所以即使类型相同,也不能相互赋值.

function包装器

function包装器也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。那么我们来看看,我们为什么需要function呢?
看一下代码:

#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 Funtor
{
    double operator()(double d)
    {
        return d / 3;
    }
};

然后看测试代码:

int main()
{
    cout << useF(f, 11.11) << endl;
    cout << useF(Funtor(), 11.11) << endl;
    cout << useF([](double d)->double {return d / 4; }, 11.11) << endl;
    return 0;
}

调用了3词useF,T是相同的,而F不同,每次都会被重新实例化成不同类型的对象,可是useeF函数里的count变量是static全局的啊,这样会被实例化成3份吗?我们取地址即可知道:

可以发现f也被实例化成了3份,但我们就是想要1份,该怎么办呢?

这里就需要用到包装器:

#include<functional>
int f(int a, int b)
{
    return a+b;
}
struct Funtor
{
    int operator()(int a, int b)
    {
        return a+b;
    }
};

class  Puls
{
public:
    static int pulsi(int x, int y)
    {
        return x + y;
    }
    double pulsd(double x, double y)
    {
        return x + y;
    }
};

int main()
{
    function<int(int, int)> f1 = f;//包装普通函数
    cout << f1(2, 3) << endl;
    function<int(int, int)> f2 = Funtor();//包装仿函数
    cout << f2(1, 2) << endl;
    function<int(int, int)> f3 = &Puls::pulsi; //包装静态成员函数可以不加&,但是最好加上
    cout << f3(1, 2) << endl;

    function<double(Puls, double, double)> f4 = &Puls::pulsd;包装非静态函数需要增加参数,但是可以通过band来取消这个参数

    f4(Puls(), 1.1, 2.2);

    return 0;
}

这样包装之后,我们便可以解决生成多份的问题了.

function是一个通用的函数封装类,可以包装不同类型的可调用对象(如函数、函数指针、lambda 表达式等),以及它们的参数和返回值类型。它提供了一种统一的方式来处理函数对象,使得函数的类型和参数在运行时确定。

#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 Funtor
{
    double operator()(double d)
    {
        return d / 3;
    }
};
int main()
{
    function<double(double)> f1 = f;  //包装普通函数
    cout << useF(f1, 11.11) << endl;
    function<double(double)> f2 = Funtor();  //包装仿函数
    cout << useF(f2, 11.11) << endl;
    function<double(double)> f3 = [](double d)->double {return  d / 4; }; //包装lamdba表达式
    cout << useF(f3, 11.11) << endl;
    return 0;
}

这样我们再输出出来:

可以看到地址已经一样了,说明只生成了一份了. 这个不是重点,这个是样例。

有的地方必须要求是统一类型,这个时候便需要包装器.

function包装器的大体内容就是这样了.

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

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

相关文章

基于redis实现延时队列(一)

背景 最近项目中需要对一条数据&#xff0c;在半小时候更改其状态&#xff0c;类似于提交商城订单半小时后未支付的订单需要更改为超时状态&#xff0c;当然这个解决方案有很多&#xff0c;最好的解决方案是用MQ的死信队列&#xff1b;但由于项目中没有引入MQ&#xff0c;故本…

PMP-质量管理的重要性

本篇文章主要是方便从事于项目管理的“初学者”们了解质量管理的重要性&#xff01;&#xff01;&#xff01; 一、什么是质量管理 项目质量管理包括把组织的质量政策应用于规划、管理、控制项目和产品质量要求&#xff0c;以满足相关方目标的各个过程。此外&#xff0c;项目质…

Latex公式炫酷技巧

最近看到一个炫酷的latex公式用法&#xff0c;特意在此记录一下 效果如下 latex代码如下 \begin{equation}\mathcal{L}_{mot}^{\textcolor{magenta}{\bullet}} \frac{1}{\sum_{i1}^{N}{s_i^l}}\sum_{i1}^{N}\Big\Vert{s}^{l}_i(\mathbf{\hat{f}}_i-\mathbf{f}^{fg}_i)\Big…

网络安全系统教程+学习路线

一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

【MySQL系列】表的学习及基本操作

「前言」文章内容大致是数据库表的基本操作 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「句子分享」 人生当苦无妨&#xff0c;良人当归即好。 ——烽火戏诸侯《雪中悍刀行》 目录 一、创建表二、修改表三、 删除表 一、创建表 创建…

组合模式的例子

// 组合模式的接口 public interface AccessDecisionVoter {// 投票结果的常量int ACCESS_GRANTED 1;int ACCESS_ABSTAIN 0;int ACCESS_DENIED -1;// 投票方法&#xff0c;根据用户和请求判断是否授权int vote(User user, Request request); }// 组合模式的叶子节点&#xf…

Android中system/bin/Input命令 -- Android12

IMS:Android中Input命令--Android12 1、Android12 Input命令更新1.1 shell脚本1.2 InputShellCommand#onCommand 命令解析 2、Input相关命令参数2.1 text2.2 keyevent2.3 tap2.4 swipe2.5 draganddrop2.6 press2.7 roll2.8 motionevent2.9 keycombination2.10 默认handleDefaul…

2023 WAIC图技术激活数据要素论坛圆满召开!

7月6日&#xff0c;以“智联世界 生成未来”为主题的2023世界人工智能大会&#xff08;WAIC 2023&#xff09;在上海隆重开幕。作为大会唯一的图技术论坛&#xff0c;“图技术激活数据要素论坛”也如期举行。 论坛现场&#xff0c;学术界专家学者、头部银行代表、产业界大咖齐聚…

二分图博弈(知识总结+例题)

思路来源 gzchenben的ppt 算法学习笔记(74): 二分图博弈 - 知乎 https://www.cnblogs.com/Zeardoe/p/16534557.html 知识点总结 以下部分摘自知乎&#xff1a;算法学习笔记(74): 二分图博弈 - 知乎 二分图博弈模型 给出一张二分图和起始点 H &#xff0c; A和B轮流操作…

Endnotes引用

准备不用zetro了&#xff0c;主要学校endnotes免费 该说不说&#xff0c;zetro拖入pdf直接识别并导入的功能是真的好用&#xff0c;添加备注也方便 可惜文献太多放不下了&#xff0c;扩容要加钱。 啧。算了算了。 这里主要介绍Endnotes中的文献怎么在word内引用&#xff0c…

SpringBoot 插件化开发模式,强烈推荐!

一、前言 插件化开发模式正在很多编程语言或技术框架中得以广泛的应用实践&#xff0c;比如大家熟悉的jenkins&#xff0c;docker可视化管理平台rancher&#xff0c;以及日常编码使用的编辑器idea&#xff0c;vscode等&#xff0c;随处可见的带有热插拔功能的插件&#xff0c;…

BitLocker 驱动器加密管理

为了有效地保护数字数据&#xff0c;应对其进行加密&#xff0c;以便只有授权用户才能访问。BitLocker 是某些 Windows 操作系统上可用的本机加密工具&#xff0c;可以为个人用户轻松加密Windows计算机。 什么是 BitLocker 加密 BitLocker 加密是 Windows 操作系统的内置安全…

STM32实现气压传感器测量(BMP180)

目录 0.接线设计 1.功能描述 2.四种方式实现大气压采集 3.模块选择 4.编程环境 5.模块主要参数 6.代码实现 1&#xff09;标准库模拟IIC实现气压值采集 2&#xff09;标准库硬件IIC实现气压值采集 3&#xff09;HAL库模拟IIC实现气压值采集 4&#xff09;HAL库硬件IIC实…

分享一次腾讯云轻量应用服务器被攻击

腾讯云轻量应用服务器&#xff0c;centOS。在上面装了redis、rabbit mq等服务&#xff0c;开着端口&#xff0c;结果被入侵了。 发现问题是通过腾讯云发来的邮件&#xff0c; 首先进到主机安全控制台&#xff0c;左侧这里进主机列表 然后可以看到自己的主机情况&#xff0c;防…

ROS:机器人系统仿真

目录 一、概念二、作用2.1仿真优势:2.2仿真缺陷: 三、组件3.1URDF3.2rviz3.3gazebo 一、概念 通过计算机对实体机器人系统进行模拟的技术&#xff0c;在 ROS 中&#xff0c;仿真实现涉及的内容主要有三:对机器人建模(URDF)、创建仿真环境(Gazebo)以及感知环境(Rviz)等系统性实…

HTML特性(attribute)和DOM属性(property)

文章目录 定义位置不同attributeproperty 范围不同属性映射行为区别数据类型不同大小写敏感区别相同属性返回值可能不同DOM 属性具有写保护 定义位置不同 attribute 是 HTML 标签上的某个属性&#xff0c;如 id、class、value 等以及自定义属性,定义后会呈现在标签上 proper…

赛效:如何在线编辑图片

1&#xff1a;点击导航栏里的“图片编辑”。 2&#xff1a;点击打开图片或者拖放打开图片。 3&#xff1a;左侧几十种工具&#xff0c;你可以用来在线编辑图片。 4&#xff1a;编辑完成后点击页面右上角的“下载”按钮&#xff0c;根据提示登录账号下载图片就可以了。 如果你想…

2023广州建博会:鸿雁总裁王米成详解全屋智能的发展脉络

全屋智能落地的模式有很多&#xff0c;但鸿雁依托其智能面板优势&#xff0c;逐渐探索出一条属于鸿雁的全屋智能发展路径和商业模式。 智哪儿创始人、总编彭安军&#xff08;左&#xff09;&#xff0c;鸿雁电器总裁王米成&#xff08;右&#xff09; 在2023年的广州建博会上&a…

【Linux从入门到放弃】进程状态的理解以及什么是僵尸进程和孤儿进程?

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《Linux从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; 文…

你知道什么是基于StyleNeRF的conditional GAN模型吗

随着深度学习技术的不断发展&#xff0c;生成对抗网络&#xff08;GAN&#xff09;已经成为了人工智能研究和应用中的重要组成部分。其中&#xff0c;GAN可以被用来生成高质量的图像、视频等内容&#xff0c;这为娱乐产业和数字化制作带来了新的机遇和挑战。本文将介绍一种基于…