【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
相关推荐
§ꦿCFོ༉5 小时前
嵌入式八股文—C语言入门学习(四)
c语言·开发语言·学习
投图匠6 小时前
十种不同风格的 CTP接口 框架
开发语言·python
爱笑的k119 小时前
nano-vllm 源码剖析:千行 Python 手写离线推理引擎的调度、显存与并行时序
开发语言·python·vllm
X1A0RAN10 小时前
PPT 图片批量处理教程:导出全部幻灯片 · 提取全部素材
开发语言·javascript·powerpoint
熊猫钓鱼>_>11 小时前
普通用户视角的端侧智能体验手记【鸿蒙心迹】
开发语言·人工智能·华为·智能手机·性能优化·ai编程·harmonyos
瑞雪兆丰年兮11 小时前
JavaWeb-day13-SpringBootWeb事务管理&AOP
java·开发语言·springboot·javaweb·事务·web·aop
布莱克60513 小时前
Swift 常量详解:从 let 到编译期优化
服务器·开发语言·ssh·swift·常量·let
布莱克60513 小时前
Swift 可选类型(Optionals)详解:从原理到实战
开发语言·安全·ssh·swift·optional·可选类型
老歌老听老掉牙14 小时前
案例 7:二维 Rosenbrock 函数最小值(香蕉函数)
c++·pso
Severus_black14 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl