• C++11(三)可变模板参数、lambda表达式、包装器


    🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
    在这里插入图片描述

    一、可变模板参数

    可变参数其实早就见过了,比如printf
    在这里插入图片描述
    可变模板参数这里,设计了一个模板参数包

    //模板参数包
    template<class ...Args>//args是随便起的,但一般都叫Args
    void ShowList(Args...args)
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但是其实我们不好从函数参数包中拿到这些参数,sizeof…(args)只能用来指导参数包中有多少个参数,参数包中的参数是不支持像数组一样去取的

    template <class ...Args>
    void ShowList(Args...args)
    {
    	cout << sizeof...(args) << endl;
    }
    int main()
    {
    	ShowList();//0
    	ShowList(1);//1
    	ShowList(1, 'A');//2
    	string str = "zhupi";
    	ShowList(1, 'A', str);//3
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    那通过什么方式来推导每个参数呢

    1.通过递归推导参数包

    可以通过递归

    template<class T,class ...Args>
    void ShowList(const T&val,Args...args)
    {
    	cout<<"ShowList"<<val<<", "<<sizeof...(args)<<"参数包)"<<endl;
    	ShowList(args...);
    }
    //这样就可以通过递归调用,推导出参数类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    但是别人只传一个参数包,我还要自己加一个T,很好方便,也看着不舒服
    再看看emplace,它也没有加另外的参数T,它是怎么推出来的?
    在这里插入图片描述

    2.通过列表初始化推导参数包

    template<class T>
    int PrintArg(const T&x)
    {
    	cout<<x<<" ";
    	return 0;
    }
    template<class ...Args>
    void ShowList(Args...args)
    {
    	int a[] = {PrintArg(args)...};
    	cout<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的这个了解就行

    二、emplace

    在这里插入图片描述

    vector<int> v1;
    v1.push_back(1);
    v1.emplace_back(2);
    
    • 1
    • 2
    • 3

    这里没有任何却别,因为push_back就是传一个,emplace_back只是传多个而已
    但是这里传多个并没有价值,因为数据类型是int,一个参数的,和一个参数的参数包没什么区别

    vector<pair<string,int>>  v2;
    v2.push_back(make_pair("sort",1));
    v2.emplace_back("sort",1);
    
    • 1
    • 2
    • 3

    push_back(make_pair(…))是一次构造+一次拷贝构造/移动构造(左值/右值)
    而传参数包的话,我只需要传参数过去构造里面的pair(因为里面有列表初始化,可以生成pair)
    所以emplace确实在某些地方更高效

    所以以后的插入的接口,push/insert都推荐去使用emplace提高效率

    三、lambda表达式

    lambda表达式也叫作匿名函数
    在这之前,可以像函数一样使用的有
    ①普通的函数指针
    ②仿函数/函数对象//仿函数是因为C的函数指针太复杂

    但是仿函数也有诸多不便
    比如

    struct Goods
    {
    	string _name;//名字
    	double _price;//价格
    	int _evaluate;//平价
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    要对这个商品进行排序的话,至少要写6个仿函数,就是六个类,每个类去重载operator(),按哪个字段来排序,就有两个对应的Less和Greater,非常的不便捷

    1.lambda表达式语法

    lambda表达式书写格式:
    [capture-list](parameters)mutable->return-type{statemenr}
    捕捉列表 参数列表 mutable -> 返回类型 函数体

    [capture-list] : 捕捉列表,该列表总是出现在lmbda函数开始的位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量工lambda函数使用。
    (paraments) : 参数列表,与普通函数的参数列表一致,如果不需要参数传递,则可以连()一起省略
    mutable : 默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空,因为位置的识别问题)
    ->returntype : 返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
    {statement} : 函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量
    注意:
    在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空,因此,C++最简单的lambda函数为: []{};该lambda函数不能做任何事情

    lambda表达式/匿名函数 也是一个对象!!

    2.使用lambda

    两数相加lambda

    auto add = [](int a,int b)/*mutable*/->int {return a+b;}
    
    • 1

    两数交换lambda
    ①捕捉列表

    auto swap = [a,b]()mutable /*->void*/
    	//mutable使用的时候,不能省略参数列表,不然编译器识别有问题
    	//因为lambda表达式是一个const函数具有常性,所以需要mutable来取消常性,捕获到的a,b
    {
    	int tmp =x;
    	x=y;
    	y=tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    虽然捕捉过来了,但是默认的捕捉是拷贝的捕捉 (传值捕捉),因为lambda是一个函数调用,是有函数栈帧的,在不同的两个作用域。所以mutable在实际当中其实没什么价值,一般要修改都要用传引用捕捉

    ②传引用捕捉

    auto swap = [&a,&b]
    {
    	int tmp =x;
    	x=y;
    	y=tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    比如说刚刚的商品

    struct Goods
    {
    	string _name;//名字
    	double _price;//价格
    	int _evaluate;//平价
    };
    
    sort(v.begin(),v.end(),[](const Goods&g1.const Goods&g2)
    {
    	return g1._name<g2._name;//升序
    });
    sort(v.begin(),v.end(),[](const Goods&g1.const Goods&g2)
    {
    	return g1._name>g2._name;//降序
    });
    //等等sort后面传的是一个比较函数,可以穿函数指针,仿函数,lambda
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3.捕捉列表说明

    如果有很多变量
    [=] 传值捕捉所有变量
    [&] 传引用捕捉所有变量
    [var] 传值捕捉指定变量
    [&var] 传引用捕捉指定变量

    注意:
    a.语法上捕捉列表可由多个捕捉项组成,并以逗号分割(可以混合捕捉
    [=,&a,&b],表示传值捕捉所有变量,传引用捕捉a,b
    [&,a,b],表示传引用捕捉所有变量,传值捕捉a,b
    但是[&,a]编译器会默认他为显示捕捉的那一种,需要加mutable才行
    b.捕捉列表不允许变量以统一方式捕捉两次,否则编译错误
    [=,a]等于传值捕捉a两次,编译报错
    c.lambda函数仅能捕捉父作用域中的局部变量或者全局变量,捕捉任何非此作用域变量都会编译报错
    d.任何lambda的类型都是不一样的,所以相互之间不能赋值

    4.lambda底层原理

    在这里插入图片描述
    lambda的类型是一个lambda_加一个字符串,这个字符串是随机的,它其实是uuid
    uuid是通用唯一识别码的缩写(Universally Unique Identifier),让所有元素,都能有一个唯一表示信息
    这是一个算法,基本上是唯一的字符串

    lambda是匿名的,底层是把这个lambda转成一个仿函数类,类的名称就叫做
    lambda_uuid,保证每个的lambda名称不一样
    底层其实就是一个仿函数,自动生成一个lambda_uuid的方函数类,虽然用起来方便,但是编译阶段做了不少事,也就是说lambda并不是什么新东西,实际编译的时候就没有lambda了,看到的是lambda_uuid这个仿函数,lambda对于我们来说是匿名的,对于编译器来说有特定的名称。

    四、包装器

    ①函数指针
    ②仿函数/函数对象
    ③lambda

    T useF(F f,T x)
    {	
    	static int count=0;
    	cout<<"count:"<<++count<<endl;
    	cour<<"counr:"<<&count<<endl;
    	return f(x);//函数调用
    }
    double f(doubel i)
    {
    	return i/2;
    }
    struct Functor
    {
    	double operator()(double d)
    	{	
    		return d/2;
    	}
    }
    int main()
    {
    	cout<<useF(f,11.11)<<endl;
    	cout<<useF(Functor(),11.11)<<endl;
    	cout<<useF([](double d){return d/4;},11.11)<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这段代码通过静态的count能够看出来,编译器会根据该模板生成三份函数
    也太费事了,所以就有了包装器
    包装器就可以让编译器只生成一份(用哪个函数就生成哪个)

    1.定义包装器

    template<class Ret,class...Args>
    //Ret是返回值类型,Args是参数包
    class function<Ret(Args...args)>;//这是类名
    
    • 1
    • 2
    • 3

    2.使用包装器

    function<int(int,int)> f1 = f;//int f(int a,int b)
    f1(1,2);
    function<int(int,int)> f2 = Functor();//int operator()(int a,int b)
    f2(1,2)
    class Plus
    { 
    public:
        static int plusi(int a,int b)
        {
            return a+b;
        }
        double plusd(double a,double b)
        {
            return a+b;
        }
    }
    function<int(int,int)> f3 = Plus::plusi;
    f3(1,2);
    //function f4 = &Plus::plusd;
    //成员函数的指针不能直接调用,成员函数需要用对象去调用
    function<double(Plus,double,double)> f4 = &Plus::plusd;
    //非静态成员函数的地址必须加这个&,语法规定
    f4(Plus(),1.1,1.2);
    //绑定成员函数的时候需要多传一个类名过去
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述
    这样的话传同类对象给usef就只会推导出一个函数,而不会生成三个

    五、bind绑定

    bind是一个函数模板,他就是是函数包装器(适配器),接收一个可调用对象,生成一个新的可调用对象以适应原对象的参数列表

    template<class Ret,class Fn,class ...Args>
    bind(Fn&&fn,Args&&...args);
    
    • 1
    • 2

    比如

    function<int(int,int)> f1 = f;//int f(int a,int b)
    function<int(int,int)> f2 = Functor();//int operator()(int a,int b)
    class Plus
    { 
    public:
        double plus(double a,double b)
        {
            return a+b;
        }
    }
    function<int(Plus,int,int)> f3 = &Plus::plus;
    map<string,function<int(int,int)>> opFuncMap;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    像这样,需要使用成员函数的时候,还要多传一个类名过去,那这时候写的包装器就用不了了
    bind中有一个placeholder命名空间中的占位符,也就是_1,_2.
    _1代表的是第一个参数,_2代表的是第二个参数,所以调整参数也就是调整占位符

    int x=10,y=2;
    cout<<Div(x,y)<<endl;
    //对Div绑定,调整顺序
    auto bindFun1 = bind(Div,_1,_2);
    auto bindFun2 = bind(Div,_2,_1);
    //交换顺序其实用不上,因为这只需要我们传参的时候反着传就行了
    
    //对于上面传包装器还需要多传一个类名,那么就可以进行绑定来调整参数个数
    function<int(int,int)> funcPlus = Plus;
    function<int(int,int)> funcSub = bind(&Sub::sub,Sub(),_1,_2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过bind把第一个参数绑定死,然后_1,_2就可以占用两个形参的位置,传两个int就可以了,但是对于绑定死的哪个参数是变化的就不行。也就是说,bind真正需要的参数个数就是占位符的个数,想减少原可调用对象的参数个数,就绑定死某些参数

    在这里插入图片描述

  • 相关阅读:
    数字图像处理(入门篇)四 像素关系
    跳台阶问题(类似斐波那契数列)
    性能优化之css层
    uniapp 在线升级(热更新)及java后台
    转行UI设计必看的5个建议优漫动游
    分布式编程工具Akka Streams、Kafka Streams和Spark Streaming大PK
    一张图快速了解 Istio 的 EnvoyFilter
    AI生成内容(AIGC)技术:革新创作与挑战未来
    Maven compile时报错 系统资源不足,出现OOM:GC overhead limit exceeded
    Python如何自动操作电脑桌面应用程序
  • 原文地址:https://blog.csdn.net/zhu_pi_xx/article/details/128091884