目录
[1. 概念及简单定义](#1. 概念及简单定义)
四、可变参数模版
1. 概念及简单定义
所谓可变参数模板,就是允许一个模板接收 任意数量的模板参数。
最基本的写法如下:
cpp
template<class... Args>
void ShowList(Args... args)
{
}
class... Args 表示声明了一个模板参数包。
这里的:Args 并不是一个 普通的模板参数,而是一组模板参数。
也就是说,普通模板可能是:template<class T>,这里的 T 只表示一个类型。
而:template<class... Args> 中的 **Args**可以表示:
0 个类型
1 个类型
2 个类型
3 个类型
......
N 个类型
因此称为 模板参数包。
再来看:Args... args
这里的:args 称为 函数参数包。
Args 保存一组类型
args 保存与这些类型对应的一组实际参数

因此,可变参数模板不仅意味着:参数数量可以变化。还意味着:每个参数的类型也可以不同。这也是可变参数模板非常强大的地方。
可用sizeof...计算参数包中到底有多少个参数

cpp
template<class... Args>
void Func(Args... args)
{
cout << sizeof...(Args) << endl;
cout << sizeof...(args) << endl;
}
2.参数包展开
参数包代表的是"一组参数",不能直接把整个参数包当成一个普通对象使用。
所以这样是不对的:
cpp
template<class... Args>
void ShowList(Args... args)
{
cout << args << endl;
}
所以如果想分别处理参数包中的每一个参数,就需要想办法将参数args一个个拿出来,这个过程就叫:参数包展开。
cpp
#include <iostream>
using namespace std;
// 递归终止函数
void ShowList()
{
cout << endl;
}
// 展开函数
template<class T, class... Args>
void ShowList(T value, Args... args)
{
cout << value << " ";
ShowList(args...);
}
int main()
{
ShowList();
ShowList(1);
ShowList(1, 2);
ShowList(1, 2, 3);
ShowList(1, 2, 3, 4);
return 0;
}


重点看函数参数 T value, Args... args,可以把它理解成两部分:T value 接收当前参数列表中的第一个参数,Args... args 接收剩余的所有参数。也就是说,每调用一次函数,都会先从当前参数中取出第一个参数交给 value 处理,再把后面的参数保存在 args... 中。
处理完当前的 value 后,再通过 ShowList(args...); 递归处理剩余参数。于是整个参数包就会被一个一个展开,直到没有参数为止。因此,递归展开参数包的核心思想就是:每次取出第一个参数进行处理,再把剩余参数继续交给下一次递归调用。

可变参数模板的参数类型可以完全不同
五、emplace系列接口
C++11新增的 emplace 系列接口就是一个非常典型的应用。在 C++11 之前,我们向容器尾部插入元素经常使用:push_back(),现在可以使用更高效的**emplace_back()**了

cpp
list<Date> lt;
lt.push_back(Date(2025, 7, 18));
lt.emplace_back(2025, 7, 18);
cpp
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;
};
int main()
{
list<Date> lt;
lt.emplace_back(2026, 1, 2);
cout << endl;
lt.push_back(Date(2026, 1, 2));
return 0;
}
当节点存储浅拷贝类型时,emplace_back 仅调用一次构造函数,而 push_back 需要调用一次构造函数和一次拷贝构造。因此,对于此类类型,emplace_back 的开销更小。
六、函数包装器
C++11引入了函数包装器,他可以封装函数,方便调用不同的函数。
1.function
function的本质是一个类模版,实例出的对象可以存储其他调用对象,头文件是**<functional>**
cpp
#include <iostream>
#include <functional>
using namespace std;
//函数
int f1(int a, int b)
{
return a + b;
}
//仿函数
struct F2
{
int operator()(int a, int b)
{
return a - b;
}
}f2;
//lambda表达式
auto f3 = [](int a, int b)->int{ return a * b; };
int main()
{
function<int(int, int)> f;
f = f1;
cout << f(8, 10) << endl;
f = f2;
cout << f(8, 10) << endl;
f = f3;
cout << f(8, 10) << endl;
return 0;
}
定义function时,注意要显式实例化要接收对象的参数和返回值;调用时,直接用函数调用的方式进行传参。
注意 :用function包装非静态成员函数时,别忘记第一个参数是this指针,显式实例化时要将对象指针类型写在参数列表中:
cpp
class Student
{
public:
Student(string name, int no)
:_name(name)
, _no(no)
{
}
void Print() const
{
cout << _name << " " << _no << endl;
}
static int add(int x, int y)
{
cout << "static_add" << " ";
return x + y;
}
private:
string _name;
int _no;
};
int main()
{
function<void(Student*)> Func1 = &Student::Print; //包装类内成员函数(内部走->*)
function<void(Student&)> Func2 = &Student::Print; //包装类内成员函数(内部走.*)
function<int(int, int)> Func3 = &Student::add; //包装类内静态成员函数
int x = 1, y = 10;
Student s1 = { "guoshi", 20260101 };
Func1(&s1);
Func2(s1);
cout << Func3(x, y) << endl;
return 0;
}


2.bind
bind 是一个函数模板(头文件 <functional>),它接收一个可调用对象和一组参数,返回一个新的可调用对象 ------新对象可以调整原函数的传参顺序 、给任意位置预置固定的值 ,也可以把成员函数的对象绑死。
cpp
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders; // _1 _2 _3 ... 都在这个空间里
void Print(int a, int b, int c)
{
cout << a << ' ' << b << ' ' << c << endl;
}
int main()
{
auto f = bind(Print, _1, _2, _3); // 包装 Print 函数
f(1, 2, 3);
return 0;
}
运行结果:
1 2 3
三个要点:
- bind 的第一个参数是被包装的可调用对象,后面的位置才是参数
_1 _2 _3是占位符 ,表示"调用时传入的第 1、2、3 个实参"------它跟"函数的第几个参数"没有关系- bind 的返回类型是编译器生成的、手写不出来 的类型,所以要用 auto 接收
占位符也可以按需一个个展开,不必整个空间都放开:
cpp
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
占位符的排放顺序,就是实参交给原函数的顺序:
cpp
int main()
{
auto f = bind(Print, _1, _3, _2); // 把第 2、3 个位置的代号换一下
f(10, 20, 30);
return 0;
}
运行结果:10 30 20
也可以给任意位置预置固定的值:
cpp
int main()
{
auto f = bind(Print, _1, 100, _2); // 中间那个参数固定成 100
f(7, 8);
return 0;
}
7 100 8
