【C++】14. C++11新特性【下】

目录

四、可变参数模版

[1. 概念及简单定义](#1. 概念及简单定义)

2.参数包展开

五、emplace系列接口

六、函数包装器

1.function

2.bind


四、可变参数模版

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

三个要点:

  1. bind 的第一个参数是被包装的可调用对象,后面的位置才是参数
  2. _1 _2 _3 是占位符 ,表示"调用时传入的第 1、2、3 个实参"------它跟"函数的第几个参数"没有关系
  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
相关推荐
web打印社区1 小时前
汽修 / 4S 店:维修工单、结算单网页怎么静默出纸
开发语言·前端·javascript·chrome·ecmascript
Sirens.1 小时前
Java多线程实例:单例模式、阻塞队列、线程池与定时器
java·开发语言·单例模式
牢姐与蒯1 小时前
c++加餐之位图
c++·算法·哈希
沫璃染墨1 小时前
《从零入门Linux系统篇(五十三):线程篇·六——线程互斥详解:从并发问题到mutex互斥锁》
linux·运维·服务器·开发语言·后端·架构·系统架构
老王爱玩车1 小时前
深入理解指针6
c语言·开发语言·学习
苏辰澈1 小时前
C++自己的族谱规则:继承详解上
c语言·c++·visualstudio
Eloudy1 小时前
全文 - version.A - AMBA CHI Chip-to-Chip(C2C)
java·开发语言·数据库·gpu·chiplet
秋名RG1 小时前
Java历代LTS版本新特性详解(代码增强版)
java·开发语言·python
李少兄1 小时前
深度剖析 Hutool ObjectUtil
java·开发语言