前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++11特性篇】模板的新一力将:可变参数模板 [全解析]

【C++11特性篇】模板的新一力将:可变参数模板 [全解析]

作者头像
YY的秘密代码小屋
发布2024-01-23 10:03:54
4820
发布2024-01-23 10:03:54
举报
文章被收录于专栏:C++系列
一.引入:为什么printf可以支持多个参数的输入?————函数的可变参数

一.引入:为什么printf可以支持多个参数的输入?————函数的可变参数

  • 在我们学习C语言的过程中,我们会发现printf支持如下图所示操作:
  • 底层原理 是: 他会用一个数组把实参存起来,printf会依次访问数组
  • 函数的可变参数如下文档所示:

二.可变参数模板

【1】基本可变参数的函数模板演示:

  • 下面的参数 args 前面有省略号,所以它就是一个 可变模版参数
  • 我们把 带省略号的参数称为“参数包” ,它里面包含了0到N(N>=0)个模板参数
  • 用可变模版参数的一个主要特点:我们无法直接获取参数包args中的每个参数的,只能通过展开参数包(遍历)的方式来获取参数包中的每个参数【可在第3小点查看详解】
  • 虽然 参数包的底层是 ——> 类似数组的形式存储 ,但是语法不支持使用args[i]这样方式获取可变参数【可在第4小点查看详解】
代码语言:javascript
复制
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

【2】使用:求函数包的大小——>【…语法】

  • 代码:sizeof...(args)
代码语言:javascript
复制
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}

【3】使用:递归函数方式展开参数包(遍历/打印)演示:

  • 如下面代码所示:要设计两个函数
  1. 结束条件的函数
  2. 递归函数

分析:

  • 我们可以发现,设计的_ShowList函数的参数是(T val, Args… args)
  • 我们可以这样理解 ,——> 它把参数包的 第一个 拿了出来当作参数T, 剩下的参数包 再整成另一个新的参数包args…
代码语言:javascript
复制
void _ShowList()
{
	// 结束条件的函数————传空
	cout << endl;
}

template <class T, class ...Args>
void _ShowList(T val, Args... args)
{
	cout << val << " ";
	_ShowList(args...);
}

//args代表0-N的参数包
template <class ...Args>
void CppPrint(Args... args)
{
	_ShowList(args...);
}

int main()
{
	CppPrint();
	CppPrint(1);
	CppPrint(1, 2);
	CppPrint(1, 2, 2.2);
	CppPrint(1, 2, 2.2, string("xxxx"));

	// ...

	return 0;
}

【4】使用注意点:参数包(遍历/打印)是不支持类似数组一样的遍历打印方式

  • 参数包不支持如下面代码所示,根据其底层是 类似数组的形式 ,下面代码是想利用数组的方式打印
代码语言:javascript
复制
template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;

	// 不支持这样打印
	for (size_t i = 0; i < sizeof...(args); i++)
	{
		cout << args[i] << endl;
	}
}

【5】使用:"逗号表达式"方式展开参数包(遍历/打印)演示:(看懂即可)

  • 我们知道逗号表达式会 按顺序执行逗号前面的表达式
  • 函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行PrintArg(args),再得到逗号表达式的结果0
  • 同时还用到了C++11的另外一个特性——初始化列表, 通过初始化列表来初始化一个变长数组
  • {(printarg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ) ,最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。
  • 由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了, 这个数组的目的 纯粹是为了在数组构造的过程展开参数包
代码语言:javascript
复制
template <class T>
void PrintArg(T t)
{
 cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
 int arr[] = { (PrintArg(args), 0)... };
 cout << endl;
}
int main()
{
 ShowList(1);
 ShowList(1, 'A');
 ShowList(1, 'A', std::string("sort"));
 return 0;
}

【6】使用:一般(遍历/打印)展开参数包的最常用方式——>【…语法】

  • 用如下面代码所示构建数组即可:int a[] = { PrintArg(args)...};
代码语言:javascript
复制
void CppPrint()//单独讨论参数为空的清空
{
	cout << endl;
}

template <class T>
int PrintArg(T t)
{
	cout << t << " ";

	return 0;
}

//args代表0-N的参数包
template <class ...Args>
void CppPrint(Args... args)
{
	int a[] = { PrintArg(args)...};
	cout << endl;
}

int main()
{
	CppPrint();
	CppPrint(1);
	CppPrint(1, 2);
	CppPrint(1, 2, 2.2);
	CppPrint(1, 2, 2.2, string("xxxx"));

	return 0;
}

三.【可变参数-模板】的优势:——>直接传包,直接构造

【1】简易代码样例——>帮助理解原理

  • 先设计一个日期类如下所示:
代码语言:javascript
复制
class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        , _day(day)
    {
        cout << "Date构造" << endl;
    }

    Date(const Date& d)
        :_year(d._year)
        , _month(d._month)
        , _day(d._day)
    {
        cout << "Date拷贝构造" << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

  • 设计一个可变参数的函数模板Create如下所示:
  • Create函数 接收了传入的"参数包" ,再把参数包拿去构造Date对象,如下面代码所示:
  • 分别传参有p1,p2,p3,p4等等形式, 有缺省的地方,初始化列表中也会自动调用缺省值
  • 这里就体现了 模板调用可变参数的特点: 灵活
代码语言:javascript
复制
template <class ...Args>
Date* Create(Args... args)
{
    Date* ret = new Date(args...);

    return ret;
}

int main()
{
	Date* p1 = Create();
	Date* p2 = Create(2023);
	Date* p3 = Create(2023, 9);
	Date* p4 = Create(2023, 9, 27);//构造

	Date d(2023, 1, 1);
	Date* p5 = Create(d);//拷贝构造

	return 0;
}

【2】实际应用【empalce_back】&【push_back】对比

【1】empalce_back和push_back函数接口的差异

  • 我们会发现,这两个函数都是实现尾插功能
  • 在C++11中,他们也都支持 万能引用
  • 他们最主要的 差异 :empalce系列函数中参数有——> 可变参数包
【2】empalce_back和push_back完成尾插的效率对比

  • 如下图所示
  • emplace系列支持传参数包,如图中所示,都是 直接进行构造
  • 而pushback函数,在C++98版本中还是传统的, 先构造再拷贝构造 (部分编译器可能会直接优化成拷贝构造)
  • pushback函数,在C++11版本中, 先拷贝构造再进行移动拷贝 (部分编译器可能会直接优化成移动拷贝)
  • 但总体而言,直接构造和移动构造在效率上差别不大
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.引入:为什么printf可以支持多个参数的输入?————函数的可变参数
  • 二.可变参数模板
    • 【1】基本可变参数的函数模板演示:
      • 【2】使用:求函数包的大小——>【…语法】
        • 【3】使用:递归函数方式展开参数包(遍历/打印)演示:
          • 【4】使用注意点:参数包(遍历/打印)是不支持类似数组一样的遍历打印方式
            • 【5】使用:"逗号表达式"方式展开参数包(遍历/打印)演示:(看懂即可)
              • 【6】使用:一般(遍历/打印)展开参数包的最常用方式——>【…语法】
              • 三.【可变参数-模板】的优势:——>直接传包,直接构造
                • 【1】简易代码样例——>帮助理解原理
                  • 【2】实际应用【empalce_back】&【push_back】对比
                    • 【1】empalce_back和push_back函数接口的差异
                    • 【2】empalce_back和push_back完成尾插的效率对比
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档