c++11可变模版参数 emplace接口 新的类功能 lambda 包装器

可变参数模板

在模板参数列表中 class...或 typename...指出接下来的参数表示零或多个类型列表 在使用时候 函数参数列表中 类型名后面跟...指出接下来表示零或多个形参对象列表

函数参数包可以用左值引用或右值引用表示,跟前面普通模板 ⼀样,每个参数实例化时遵循引用折叠规则。

• 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。

• 可以使用sizeof...运算符去计算参数包中参数的个数

如下 本质其实是编译器通过这个可变参数模版会生成对应需要的函数

再详细一点来看 先由可变模版参数推出函数模版 然后再经过引用折叠实例化出对应的函数 但是这些我们都不需要考虑这样的过程是由编译器完成的

打印时候不能用范围for或者[]下标访问的方式 因为没有支持

打印方式1

具体展开过程如下

那么可不可以用下面这样的方式来实现递归终止呢

不可以

因为包的展开实际上是在编译阶段进行 编译阶段编译器就已经把包一个个展开 直到调用包为空的函数 但是这里的==0的判断是在运行的时候才会执行的 所以编译阶段包展开找不到无参的函数

打印方式2

先把参数包传给Print函数 Print函数再把参数包传给Arguments函数 但是打印结果不是该函数完成的 在编译阶段就会把参数包给展开

例如最后一行的三个参数会展开正最下面红色框中的内容 然后由GetArg函数来打印 打印之后把其返回值作为一个参数包传给Arguments 在里面不需要进行处理

empalce系列接口

在c++11后容器支持emplace接口 它的参数类型就是可变模版参数

push_back 和emplace-back对比

他们都支持左值引用和右值引用

先在使用上看一下他们的区别

下面这种情况他们才有区别

因为对于push_back的参数在创建对象实例化的时候就已经确定了 而emplace_back插入数据的时候才会进行推导 这里推出的是const char*类型

那么为什么会出现这样的情况呢 在下面自己实现emplace_back后就可以理解

emplace_back实现

emplace_back在接收参数包后 不会直接把其给扩展开 而是一层层传递给下一个函数 直接地用到到了初始化列表这里构造 对于之前的const string*就会直接调用string的构造 所以 emplace_back只调用了一次构造

自己使用的时候也和之前的现象一致

总结

很多情况下emplace_back和push_back是一样的(如果插入的数据就是创建对象时候实例化的类型)

部分场景下 emplace_back只需要构造 而push_back需要构造+移动构造或者拷贝构造 (push_back如果插入的是需要深拷贝的自定义类型例如string map这样 他们一般自己会实现移动构造 那么此时需要构造+移动构造 如果是需要浅拷贝的 如Data这样的 它自己就不用实现移动构造 那么此时调用的就是构造+拷贝构造 )

所以 在之后使用的时候 我们其实可以用emplace来替代insert和push 部分场景效率会快上一点点

新的类功能

默认的移动构造和移动赋值

原来C++类中,有6个默认成员函数:构造函数/析构函数/拷贝构造函数/拷贝赋值重载/取地址重 载/const 取地址重载,最后重要的是前4个,默认成员函数就是我们不写编译器 会⽣成⼀个默认的。C++11新增了两个默认成员函数,移动构造函数和移动赋值运算符重载。

如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成⼀个默认移动构造 。默认生成的移动构造函数,对于内置类型成员会执 行逐成员按字节拷贝(浅拷贝) 对于自定义类型成员 则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

(默认移动赋值跟上⾯移动构造完全类似)

• 如果你提供了移动构造或者移动赋值,编译器不会⾃动提供拷贝构造和拷贝赋值。

成员变量声明时给缺省值

可以给类里面成员变量声明的时候给缺省值 在c++初始化列表那里学习过了

defult和delete

C++11可以让你更好的控制要使⽤的默认函数。假设你要使⽤某个默认的函数,但是因为⼀些原因 这个函数没有默认生成 比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private并且只声明, 这样只要其他⼈想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

例如ostream的构造函数

final与override

在继承多态那里我们了解过了

final可以让父类不能被继承 在父类的虚函数后面加 在子类中就不能对这个虚函数重写了

override可以检查派生类是否对父类的虚函数是否被重写

STL中⼀些变化

下图1圈起来的就是STL中的新容器,但是实际最有⽤的是unordered_map和unordered_set

STL中容器的新接口也不少,最重要的就是右值引用和移动语义相关的push/insert/emplace系列 接口和移动构造和移动赋值,还有initializer_list版本的构造等

效率提高上主要就是新容器 unordered_set及unordered_map和右值的移动语义 使用上有了initializer_list及范围for

lambda

lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部

1.基本语法

2.lambda的使用

例如此时有下面商品Goods的类 里面有三个成员变量 _namd _prive _evaluate

如果此时我们要根据不同的比较逻辑对商品进行排序 此时就需要写多个仿函数

但是之前有一些缺陷 如果用到了仿函数 但是并没有注释告诉我们这里仿函数是做什么的 我们就需要找到仿函数 然后再进行观察

而如果我们用下面lambda的方式 不仅可以让代码简化 而且在这里就可以直接观察这个仿函数做了什么

3.捕捉列表

lambda 表达式中默认只能用参数列表中的全局域的和静态的 如果想要使用其他的就需要进行捕捉

第⼀种 捕捉方式是在捕捉列表中显示的传值捕捉和传引⽤捕捉

第⼆种 捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表示隐式值捕捉,在捕捉列表 写⼀个&表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些 变量

第三种 捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。当使用混合捕捉时,第⼀个元素必须是 &或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后面的捕捉变量必须须是引用捕捉。

默认情况下 lambda传值捕捉的过来的对象不能修改 mutable加在参数列表的后⾯传值捕捉的对象就可以修改了 但是不会影响实参

4.lambda的原理

lambda和范围for一样 其实从汇编指令来看 就根本不存在范围for lambda这样的东西 范围for的底层是迭代器 而lambda的底层其实就是仿函数 lambda的就是返回了一个仿函数对象

仿函数的类名是编译按⼀定规则生成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返 回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象

包装器

function

介绍及使用

function是⼀个类模板,也是⼀个包装器 它的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、lambda或者之后学习的bind

function<返回值类型(参数列表)> 对象 function的参数列表要和包装的调用对象的参数列表相对应

通过下面的题目可以感受到它的使用优势

传统方式

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;
        for(string x:tokens)
        {
            if(x=="+"||x=="-"||x=="*"||x=="/")
             {
                int right=s.top();
                s.pop();
                int left=s.top();
                s.pop();

                switch(x[0])
                {
                    case '+':
                    s.push(left+right);break;
                    case '-':
                    s.push(left-right);break;
                     case '*':
                    s.push(left*right); break;
                     case '/':
                     s.push(left/right); break;
                }
             }
             else
             s.push(stoi(x));
        }
        return s.top();
    }
};

现代写法

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        map<string,function<int(int,int)>>m({
         {"+",[](int a,int b){return a+b;}},   
         {"-",[](int a,int b){return a-b;}},
         {"*",[](int a,int b){return a*b;}},
         {"/",[](int a,int b){return a/b;}}
        });
        stack<int>s;
        for(string x:tokens)
        {
            if(m.count(x))
            {
                 int right = s.top();
                 s.pop();
                 int left = s.top();
                 s.pop();

                 s.push(m[x](left,right));
            }
            else
             s.push(stoi(x));
        }
        return s.top();
    }
};

这种方法用到了map 第一个模版参数是string类型 第二个参数是包装器的方式 相应的运算符对应着相应功能的函数

相较于传统方法 这种方式如果增加新的功能会更加方便 更容易维护 如果操作符非常多的情况 这种方法还会更快 因为map底层是红黑树 查找的速度相较于上一种switch依次判断的方式更快 当然只有在运算符非常多的情况下才会有些差距

包装成员函数

function还可以包装成员函数 包装时候需要用类域指定访问的成员函数 对于非静态的成员还需要在前面加上&(静态成员函数可加ke不加)

另外对于非静态的成员函数 有隐含的this指针 还需要再传一个参数 可以是类类型的指针也可以是类类型

bind

bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收 的可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在functional头文件中。

调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list);

其中 newCallable本⾝是⼀个可调用对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的 参数。当我们调用newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰ newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表示生成的可调⽤对象 中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3....这些占 位符放到placeholders的⼀个命名空间中。

改变参数的顺序(不常用)

改变参数的个数(常用)

下面的Sub函数需要传两个参数 用bind对其包装的时候已经固定了第二个参数 此时包装后的函数只需要传一个参数

在之前function对类内非静态成员函数使用的时候很麻烦 第一个参数需要创建再传(如果第一个参数是类类型地址)或者第一个参数是类类型(第一个参数是类类型) 用到这里的bind就可以解决这样的问题

bind使用的例子

如下的func1是一个计算利息的函数 传的时候需要传三个参数(年利率 本金 年数) 如果我们正常使用的时候需要传三个参数才会计算出利息

用下面bind结合function就可以实现出专门处理一些常用的处理某一存储年数及其年利率 这样使用的时候只需直接调用对应的函数然后传一个本金参数就可以计算出相应的利息

相关推荐
Laplaces Demon3 小时前
Spring 源码学习(十四)—— HandlerMethodArgumentResolver
java·开发语言·学习
崎岖Qiu3 小时前
【OS笔记11】:进程和线程9-死锁及其概念
笔记·操作系统·os
郝学胜-神的一滴3 小时前
使用Linux系统函数递归遍历指定目录
linux·运维·服务器·开发语言·c++·软件工程
guygg883 小时前
Java 无锁方式实现高性能线程
java·开发语言
青衫码上行4 小时前
【从0开始学习Java | 第22篇】反射
java·开发语言·学习
一念&4 小时前
每日一个C语言知识:C 字符串
c语言·开发语言
choice of4 小时前
Sentinel:阿里云高并发流量控制
笔记·spring cloud·sentinel
0110_10244 小时前
tauri + rust的环境搭建---初始化以及构建
开发语言·后端·rust
会开花的二叉树5 小时前
C++微服务 UserServer 设计与实现
开发语言·c++·微服务