C++笔记---可变参数模板

embedded/2024/12/28 15:10:59/

1. 简单介绍与基本语法

可变参数模板是指模板的类型参数列表的的参数个数可变

C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:

模板参数包:表示零或多个模板参数。

函数参数包:表示零或多个函数参数。

参数包的加入使得我们可以更加方便地设计出参数个数可变的函数,就比如模拟实现printf。

但C语言由于不支持模板,所以实现printf的方式十分复杂,查了我也没看明白。

声明该类型模板的语法

template<class ...Args> 
Type Func(Args... args) 
{}template<class ...Args> 
Type Func(Args&... args) // 每个参数都按左值引用方式接收
{}template<class ...Args> 
Type Func(Args&&... args) // 每个参数都按万能引用方式接收
{}

省略号用来指出一个由模板参数或函数参数构成的一个包:

在模板参数列表中,class...或typename...指出接下来的参数表示零或多个类型列表。

在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表。

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

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

"sizeof..."运算符

这里我们引入一个新的运算符 "sizeof..." ,用于计算参数包中参数的个数:

template <class ...Args>
void Print(Args&&... args)
{cout << sizeof...(args) << endl;
}int main()
{double x = 2.2;Print(); // 包⾥有0个参数Print(1); // 包⾥有1个参数Print(1, string("xxxxx")); // 包⾥有2个参数Print(1.1, string("xxxxx"), x); // 包⾥有3个参数return 0;
}

2. 包扩展(参数包展开)

参数包并不能如我们所想地去直接访问其内容,我们只能采取如下两种方式来访问其包含的参数:

递归展开

虽然我们不能直接取得参数包中的参数,但是在用参数包进行传参时,参数包中的参数会自动匹配形参。

由此为启发,我们可以设计一个参数列表为一个参数和一个参数包的递归模板函数:

Type Func()
{// ...
}template<class T, ...Args>
Type Func(T& x, Args... args)
{// ...Func(args...);
}

函数体中是对单个参数 "x" 进行操作的逻辑。

注意,不能将无参重载版本去掉而改成下面这样:

template<class T, ...Args>
Type Func(T& x, Args... args)
{// ...if(sizeof...(args) != 0)Func(args...);
}

假如按照这样的方式来写,无参的Func虽然不会被调用,但是在编译阶段,编译器会尝试实例化出无参的版本。

而我们给出的模板至少接收一个参数,编译器无法实例化出无参Func,会直接报错。 

通过这样的方式,我们可以将参数包中的参数从前向后一个一个地拆出来,分别操作。

例如:

void ShowList()
{// 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数cout << endl;
}template <class T, class ...Args>
void ShowList(T x, Args... args)
{cout << x << " ";// args是N个参数的参数包// 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包ShowList(args...);
}// 编译时递归推导解析参数
template <class ...Args>
void Print(Args... args)
{ShowList(args...);
}int main()
{Print();Print(1);Print(1, string("xxxxx"));Print(1, string("xxxxx"), 2.2);return 0;
}

也可一次拆出多个参数,在递归函数中增加几个形参即可。但此时就需要多写几个重载函数来处理参数个数不够多的情况了。 

复合函数

template <class T>
const T& GetArg(const T& x)
{// ...// 返回值不一定是x,可根据需要确定return x;
}template <class ...Args>
void Arguments(Args... args)
{}template <class ...Args>
void test(Args... args)
{// 注意GetArg必须返回或者到的对象,这样才能组成参数包给ArgumentsArguments(GetArg(args)...);
}

"Arguments(GetArg(args)...);" 的含义是,将参数包中的参数依次传入GetArg函数中进行调整之后,形成新的参数包传给Arguments函数。

这样的写法不仅可以用于展开参数包,还可以用于需要对参数包每个参数都进行处理之后进行函数调用的场景。

注意:Arguments函数的调用是必要的,括号内的 "GetArg(args)..." 并不能独立作为表达式而存在。

3. emplace系列接口

template <class... Args> 
void emplace_back (Args&&... args);template <class... Args> 
iterator emplace (const_iterator position, Args&&... args);

C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,能够完成与push_back,insert等相同的功能,而参数包的存在使得emplace系列接口不止可以接受要插入的对象,还可以接受构造要插入对象的参数。

以前我们在进行插入时,要么用已有对象,要么用匿名对象,要么用隐式类型转换,总之在进入函数之前,对象就需要被构造好,进入函数之后再通过拷贝构造或移动构造,构造出形参进行插入。

而emplace系列接口的参数包可以直接接受构造对象的参数而不用发生隐式类型转换,在函数内部直接将参数包层层传递(如果复用了其他函数的话)给构造函数构造出对象,并进行插入。

template <class... Args>
void emplace_back(Args&&... args)
{insert(end(), std::forward<Args>(args)...);
}template <class... Args>
iterator emplace(const_iterator pos, Args&&... args)
{Node* cur = pos._node;Node* newnode = new Node(std::forward<Args>(args)...);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}

也就是说emplace系列接口可以将函数外的构造转移到函数内进行构造,以此节省掉一次拷贝构造或移动构造。

emplace系列总体而言是更高效的,推荐以后使用emplace系列替代insert和push系列。

#include<list>
int main()
{list<string> lt;// 传左值,跟push_back一样,走拷贝构造string s1("111111111111");lt.emplace_back(s1);// 右值,跟push_back一样,走移动构造lt.emplace_back(move(s1));// 直接把构造string参数包往下传,直接用string参数包构造string// 这里达到的效果是push_back做不到的lt.emplace_back("111111111111");list<pair<string, int>> lt1;// 跟push_back一样// 构造pair + 拷贝/移动构造pair到list的节点中data上pair<string, int> kv("苹果", 1);lt1.emplace_back(kv);// 跟push_back一样lt1.emplace_back(move(kv));// 直接把构造pair参数包往下传,直接用pair参数包构造pair// 这⾥达到的效果是push_back做不到的lt1.emplace_back("苹果", 1);return 0;
}

http://www.ppmy.cn/embedded/133973.html

相关文章

安宝特分享 | AR技术引领:跨国工业远程协作创新模式

在当今高度互联的工业环境中&#xff0c;跨国合作与沟通变得日益重要。然而&#xff0c;语言障碍常常成为高效协作的绊脚石。安宝特AR眼镜凭借其强大的多语言自动翻译和播报功能&#xff0c;正在改变这一局面&#xff0c;让远程协作变得更加顺畅。 01 多语言翻译优势 安宝特A…

HTTP 响应头信息与前后端交互时content-type重要性

以下是响应头的大部分属性 响应头信息中文翻译描述Date日期响应生成的日期和时间。例如&#xff1a;Wed, 18 Apr 2024 12:00:00 GMTServer服务器服务器软件的名称和版本。例如&#xff1a;Apache/2.4.1 (Unix)Content-Type内容类型响应体的媒体类型&#xff08;MIME类型&#…

MiniWord

1.nuget 下载配置 2.引用 3. var value = new Dictionary<string, object>() { ["nianfen"] = nianfen, ["yuefen"] = yuefen, ["yuefenjian1"] = (int.Par…

企业数据安全举报投诉如何有效处理?

相关制度、流程图等获取请联系作者&#xff01;&#xff01; 在当今数字化和信息化的浪潮中&#xff0c;企业数据安全问题越来越受到重视&#xff0c;而对于数据安全的举报和投诉处理是保障企业数据安全、提升用户信任度的重要手段之一。一个完善的举报投诉处理机制能够有效应对…

Redis有什么不一样?

Redis作为一种高性能的内存数据库&#xff0c;以其卓越的性能、丰富的数据类型和强大的功能特性&#xff0c;成为了许多应用的首选数据存储方案。本文介绍Redis内存数据库&#xff0c;并与其他常见的key-value数据库&#xff08;如Memcached&#xff09;进行比较&#xff0c;及…

【产品经理】工业互联网企业上市之路

树根互联2022年6月2日提交招股书之后&#xff0c;因财务资料超过六个月有效期加三个月延长期&#xff0c;2022年9月30日上市审核中止&#xff1b;2022年12月26日树根互联更新了2022年半年度财务资料&#xff0c;又九个月过去了&#xff0c;其上市进程将面临再一次中止。 处于上…

CSP-S 2024 题解

前言&#xff1a;所给代码只过了民间数据&#xff0c;可能与实际成绩略有出入。 T1&#xff1a;决斗 为了让退出游戏的怪兽数量尽可能多&#xff0c;肯定是 r r r 大的打 r r r 小的&#xff0c;所以考虑用桶维护 r r r 的出现次数&#xff0c;对于一个新的 r r r 值&…

(蓝桥杯C/C++)—— 编程基础

文章目录 一、C基础格式 1.打印hello, world 2.基本数据类型 二、string 1.string简介 2.string的声明和初始化 3.string其他基本操作 (1)获取字符串长度 (2) 拼接字符串( 或 append) (3&#xff09;字符串查找&#xff08;find&#xff09; (4)字符串替换 (5)提取子字符串…