当前位置: 首页> 财经> 创投人物 > 网页界面设计特点_企业差旅服务平台_苹果自研搜索引擎或为替代谷歌_网络营销的概念及内容

网页界面设计特点_企业差旅服务平台_苹果自研搜索引擎或为替代谷歌_网络营销的概念及内容

时间:2025/7/9 6:02:56来源:https://blog.csdn.net/xsc2004zyj/article/details/144961782 浏览次数:0次
网页界面设计特点_企业差旅服务平台_苹果自研搜索引擎或为替代谷歌_网络营销的概念及内容

一.前言

C++11引入了可变模板参数(variadic template parameters)的概念,它允许我们在模板定义中使用可变数量的参数。这样,我们就可以处理任意数量的参数,而不仅限于固定数量的参数。

二.可变模板参数

我们早在C语言的学习过程中就接触过可变参数:printf和scanf都是可变参数的函数

他们的参数可以是1个,也可以是多个。其底层使用了一个二维数组将所有的参数存储起来。

对于之前的模板来说,都是固定参数个数的模板。C++11引入了可变参数的模板,即不仅参数的类型是不确定的,个数也是不确定的。支持可变参数的函数模板和类模板可变数目的参数被称为参数包,而参数包又分为两种:模板参数包和函数参数包

模板参数包:表示0~n个模板参数;函数参数包:表示0~n个函数参数。

template <typename ...Args> // 模板参数包
void Func(Args ...args) // 函数参数包
{}// 左值引用
template <typename ...Args> // 模板参数包
void Func(Args& ...args) // 函数参数包
{}// 万能引用——引用折叠
template <typename ...Args> // 模板参数包
void Func(Args&& ...args) // 函数参数包
{}

可变参数模板的语法规范是在定义模板参数列表的时候typename/class后面跟三个点...,然后写上模板参数名,这个名字是可以随便写的,但是为了规范使用Args更好一些。在定义函数形参表的时候类型名后面跟三个点...然后加上对象名。

函数参数包可以传值,传左值引用或者右值引用,跟普通的模板一样,这里也遵循引用折叠规则。

template <typename ...Args>
void Print(Args...args)
{cout << sizeof...(args) << endl;
}int main()
{Print();Print(1);Print(1,3.14);Print(1,"1111111111",3,14);return 0;
}

对于上面这个Print函数,它支持可变模板参数,我们可以传0~n任意不同的参数。sizeof...可以计算参数包中的参数个数

那么我们在调用Print函数的时候到底发生了什么呢? 

编译器首先会根据我们调用该函数的实参的个数,生成有固定参数的函数模板,然后再根据实参的类型,实例化这些函数模板。

我们也可以反过来想一下:

阶段一:

        当没有模板语法的时候,我们想要实现可变参数的Print函数时,就要写多个函数,0个、1个、2个,n个,再根据实参类型不同,1个参数的函数也要写多个以适应不同的实参,2个、3个、n个的也一样。就很烦~

阶段二:

        当模板出来之后,我们不再需要写那些含有相同参数个数但参数类型不同的函数了,直接一个模板就可以了。这样对于1个参数的函数模板来说,任意类型的参数都可以传了,方便了很多。但是因为参数个数的不同,我们还是需要写很多个参数不同的函数模板。还是很烦~

阶段三:

        当可变模板参数出来了之后,我们也不再需要写多个参数个数不同的函数模板了,直接写一个可变参数的函数模板,他既解决了参数个数的不同,也解决了参数类型的不同。

而且,实参传的是左值还是右值也会影响实际生成的函数的样子,所以可变模板参数确实很方便。

虽然我们是这样分析可变模板参数调用的过程,但是在编译的过程中,编译器可能就不会有中间这步生成固定参数的函数模板,而是直接一步到位生成最终要调用的函数。

三.包扩展

对于一个参数包来说,我们除了计算它的参数个数之外,更多的情况下我们要去使用它,那么我们要怎么使用呢?我们能否在该函数内部直接使用for循环遍历该参数包呢?

当然是不行的了. 

 下面我们介绍两种包扩展的方式:

1.编译时递归包扩展

void ShowList()
{cout << endl;
}template<typename T, typename...Args>
void ShowList(T&& x, Args&&...args)
{cout << x << endl;ShowList(args...);
}template <typename ...Args>
void Print(Args&&...args)
{ShowList(args...);
}

我们通过这两个ShowList函数来实现包扩展。当我们在主函数调用Print函数的时候,实参会形成一个参数包,传给Print函数,Print函数内部无法直接解析包的内容,我们将参数包传给另一个函数,该函数也是一个函数模板,他会把参数包进行拆分:提取出参数包的第一个参数,然后打印该参数,然后自己调用自己将其他的参数作为参数包继续进行拆分。当参数包只剩一个参数的时候,T获取该参数,参数包变成空包,此时就会匹配那么无参数的ShowList,此时包扩展的过程完成。

由上述过程可以看出来,无参的ShowList起到了终止条件的作用。

该过程底层就类似于下图这个过程:

我们在调用Print函数的时候,编译器就会生成右边这几个函数。

那么我们是否可以将这个结束条件写道有参数的ShowList内部呢?

不行!

我们需要注意,我们上述包扩展的过程是编译时完成的,编译阶段,编译器对参数进行解析,生成若干个重载函数,实际上我们是调用那些重载函数来完成操作的。

而上图中if判断则是运行时逻辑,在程序运行的过程中,当参数包为空,递归结束。但是实际上,当参数包为空后,此时ShowList调用不到该函数本身,因为该函数本身必须接受一个参数T。

2. 通过返回值做函数参数包扩展

template<typename T>
const T& getArg(T&& x)
{cout << x << endl;return x;
}template<typename...Args>
void Arguments(Args...args)
{}template <typename ...Args>
void Print(Args&&...args)
{Arguments(getArg(args)...);
}

上面这种办法,我们在Print函数内部调用Arguments函数,但是调用该函数我们就得有实参,所以程序会先求该函数的实参,而实参是通过getArg函数解析参数包来获取的,所以我们也可以通过这种方式进行包扩展。

因为参数传参是从右往左的,所以这样解析参数包是反着解析的

四.emplace系列接口

1.emplace和push系列的对比

C++11引入了可变模板参数之后,STL的容器都引入emplace这一系列的接口。该系列的接口使用了可变模板参数,可以传任意类型和任意数量的参数。

那么emplace_back和push_back有什么区别呢? 我们接下来根据插入数据的类别来进行对比:

插入左值,push_back和empalce_back一样,都是拷贝构造。

插入左值push_back和emplace_back会进行引用折叠,最后都会调用左值引用版本,最后在函数内部,会new一个新节点,此时传给data的是一个左值引用,而data的类型是string,所以调用string的拷贝构造

插入右值,push_back和emplace_back一样,都调用移动构造。

插入右值,不会发生引用折叠,就会匹配对应的右值引用版本,在该函数内部,new一个新节点的时候,传给data的是一个右值,就是匹配data的移动构造

传匿名对象,push_back走隐式类型转换,先构造出临时对象,在函数内部引用该临时对象的调用移动构造;而emplace_back直接构造 

emplace_back之所以会直接构造,是因为emplace_back的参数类型是不确定的,只有当传参的时候才知道是const char*,所以在函数内部会直接调用string的构造函数来生成对象,list节点存储该对象。

当插入的是多参数的时候,规律和上面是一样的:

当插入左值时,两者都是拷贝构造;插入右值时,两者都是移动构造;插入匿名对象的时候,emplace_back是直接构造,而push_back是构造+移动构造

综上,在大部分情况下,push_back和emplace_back效率是一样的。但是在有些情况下,emplace_back直接构造,而push_back需要构造+移动构造(深拷贝的类型)或者构造+拷贝构造(浅拷贝的类型)。虽然移动构造的消耗很小,但毕竟有消耗;emplace_back和push_back真正的差异在于浅拷贝的类型,浅拷贝的类型没有移动构造,只能拷贝构造。

总的来说,emplace_back的效率整体上还是优于push_back的,所以以后用emplace系列的接口来替代push系列和insert系列的接口。

2.emplace的模拟实现

emplace_back就是可变模板参数类型的函数。但是在函数内部,我们没有必要对参数包进行扩展,我们并不在函数内使用参数,所以我们直接向下一层传递包即可。

template<typename...Args>
void emplace_back(Args...args)
{insert(end(), std::forward<Args>(args)...); // 直接向下转递参数包
}


完~ 

关键字:网页界面设计特点_企业差旅服务平台_苹果自研搜索引擎或为替代谷歌_网络营销的概念及内容

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: