C++11详解(三) -- 可变参数模版和lambda

devtools/2025/2/8 3:12:33/

文章目录

  • 1.可变模版参数
    • 1.1 基本语法及其原理
    • 1.2 包扩展
    • 1.3 empalce系列接口
      • 1.3.1 push_back和emplace_back
      • 1.3.2 emplace_back在list中的使用(模拟实现)
  • 2. lambda
    • 2.1 lambda表达式语法
    • 2.2 lambda的捕捉列表
    • 2.3 lambda的原理

1.可变模版参数

1.1 基本语法及其原理

1. C++11支持可变参数模版,也就是说支持可变数量参数的函数模版和类模版,可变数目的参数为参数包,存在两种参数包:模板参数包,表示零或多个模板参数;函数参数包:表示零或多个函数参数。
2. 用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class…或typename…指出接下来的参数表示零或多个类型列表;
在函数参数列表中,类型名后面跟…指出接下来表示零或多个形参对象列表;
函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。
3. 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
4. 可变模版参数参数类型可变,参数个数可变

…Args:模版参数包
…args:函数参数包
在这里插入图片描述

// 0-N个参数
// ...:表示多个参数
template<class ...Args>
void Print(Args&& ... args)
{// sizeof...是一个新的运算符// sizeof... 计算参数包里面有几个参数cout << sizeof...(args) << endl;
}int main()
{Print(); // 0个参数Print(1);// 1个参数Print(1, 2.2);// 2个参数return 0;
}

在这里插入图片描述

在这里插入图片描述

  • 总结:模版,一个函数模版可以实例化出多个不同类型参数的函数,可变参数模版,一个可变参数模版函数可以实例化出多个不同参数个数的模版函数
  • 主要就是一个可变参数模版实例化出多个函数模版,函数模版再实例化多个函数

1.2 包扩展

1. 包扩展:解析出参数包的内容
2. 编译时递归包括展,其实也不是递归,因为每次都生成不同的函数重载,只是每次用自己这个函数进行传参(包扩展)

参数包的第一种扩展方式(编译时递归):

// 打印参数包内容
template <class ...Args>
void Print(Args... args)
{// 可变参数模板是编译时解析,不是运行时解析// 下面是运行获取和解析,所以不支持这样用//  cout << sizeof...(args) << endl;for (size_t i = 0; i < sizeof...(args); i++){cout << args[i] << " ";}cout << endl;
}// 这是编译时的逻辑,不是运行时逻辑
void ShowList()
{// 编译器递归终止的条件,参数包是0个时,直接匹配这个函数cout << endl;
}template<class T,class ...Args>
void ShowList(T x, Args ...args)
{// 运行时/*if (sizeof...(args) == 0)return;*/cout << x << " ";// args是N个参数的参数包,一个参数传给x,剩下的N-1个参数// 传给args,继续往下递归ShowList(args...);
}// 编译时递归推导解析参数
template<class ...Args>
void Print(Args ... args)
{ShowList(args...);
}int main()
{Print();Print(1);Print(1, string("xxxxxx"));Print(1,string("xxx"),2.2);return 0;
}

模版写给编译器,让编译器生成对应的包括展
在这里插入图片描述
第二种扩展方式(通过函数调用):

// 下面两个GetArg都可以用
// 可以随便返回任何数
template<class T>
int GetArg(const T& x)
{cout << x << " ";return 0;
}//template <class T>
//const T& GetArg(const T& x)
//{
//	cout << x << " ";
//	return x;
//}template <class ...Args>
void Arguments(Args... args)
{}template <class ...Args>
void Print(Args... args)
{// 注意GetArg必须返回或者到的对象,这样才能组成参数包给ArgumentsArguments(GetArg(args)...);有几个参数调用几次Arguments
}// 实际上就是下面这段
// 本质可以理解为编译器编译时,包的扩展模式
// 将上⾯的函数模板扩展实例化为下⾯的函数
//void Print(int x, string y, double z)
//{
//   Arguments(GetArg(x), GetArg(y), GetArg(z));
//}int main()
{Print(1, string("xxxxx"), 2.2);return 0;
}

1.3 empalce系列接口

1.3.1 push_back和emplace_back

  1. 传左值:都会走拷贝构造
    在这里插入图片描述
  2. 传右值:都走移动构造
    在这里插入图片描述
  3. 对于深拷贝
    直接传参,push_back,类模版实例化出string,会构造临时对象+移动构造
    emplace_back,会直接用const char* 构造
    所以emplace_back会稍微快一点
    对于浅拷贝
    比如Date,字节大小不大,push_back变为构造 + 拷贝构造,emplace_back还是直接构造
    在这里插入图片描述
    多参数的:
  4. 对于pair键值对,It1.emplace_back({“苹果”,1})是不支持的,因为emplace_back支持传多个参数类型,而不知道你传的是键值对pair
    在这里插入图片描述

1.3.2 emplace_back在list中的使用(模拟实现)

其实并不都是要像Print中解析包扩展,这样往下传即可

// 初始化列表
list_node() = default;template <class... Args>
list_node(Args&&... args): _next(nullptr), _prev(nullptr), _data(std::forward<Args>(args)...){}// emplace_back()
template <class... Args>
void emplace_back(Args&&... args)
{insert(end(), std::forward<Args>(args)...);
}// insert()
template <class... Args>
iterator insert(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);
}

2. lambda

2.1 lambda表达式语法

1. lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部,也可以写在全局,lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。
2.lambda表达式的格式:
[捕捉列表](参数列表)->返回值类型{函数体}
3. lambda可以传给模版的参数,也可以传给auto,让auto自动推导

#include<algorithm>struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价// ...Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};// 仿函数,价格升序
struct Compare1
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};// 价格降序
struct Compare2
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3}, { "菠萝", 1.5, 4 } };// 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中// 不同项的⽐较,相对还是⽐较⿇烦的,因为只能比较一项,那么这⾥lambda就很好⽤了// 写法一// sort(v.begin(), v.end(), Compare1());// sort(v.begin(), v.end(), Compare2());// 写法二// 用 lambda就可以用匿名函数对象比较多种数据,不用写专门的仿函数单独比较一项了sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate;});sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate;});// [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate; }// 匿名的函数对象return 0;
}

2.2 lambda的捕捉列表

1. lambda 表达式中默认只能用 lambda 函数体参数中的变量,如果想用外层作用域中的变量就需要进行捕捉
2. 第⼀种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x,y, &z] 表示x和y传值捕捉,z引用捕捉
3. lambda支持的是轻量级的

int y = 0;// lambda可以写在全局
auto func2 = []()
{y++;
};int main()
{int a = 0, b = 1, c = 2, d = 3;// 可以用lambda局部域,捕捉对象和全局对象auto func1 = [a, &b]{// 传值捕捉,捕捉过来的是被const修饰的,不能修改// 引用捕捉可以修改// a++;b++;int ret = a + b + y;// 可以直接使用全局域的东西return ret;};cout << func1() << endl;func2();return 0;
}

3. 第二种捕捉方式叫隐式捕捉(全部捕捉):我们在捕捉列表写一个=表示隐式值捕捉,在捕捉列表写一个&表示隐式引用捕捉

// 隐式值捕捉
auto func3 = [=]
{int ret = a + b + c;return ret;
};
cout << func3() << endl;
// 匿名函数对象 + ()是调用函数// 隐式引用捕捉
auto func4 = [&]
{a++;b++;c++;
};
func4();
cout << a << ":" << b << ":" << c << endl;

4. 混合捕捉:

  1. 有些传值捕捉,其他引用捕捉
  2. 有些引用捕捉,其他传值捕捉
混合捕捉,有些传值捕捉,其他引用捕捉
auto func5 = [&, a, b]
{// a++;// b++;c++;d++;return a + b + c + d;
};
func5();
cout << a << " " << b << " " << c << " " << d << endl;混合捕捉,有些传引用捕捉,其他传值捕捉
auto func6 = [=, &a, &b]
{a++;b++;return a + b + c;
};
func6();
cout << a << " " << b << " " << c << endl;

5. 局部的静态和全局不用捕捉,也不能捕捉,直接可以使用

static int t = 0;
// 局部的静态和全局变量不用捕捉直接使用
auto func7 = []
{int ret = t + y;return ret;
};

6. 默认传值捕捉是被const修饰的,但是在参数列表后面加mutable可以取消它的const属性,还有就是在lambda体内是可以修改它了,到外面它的值是不变的

int f = 0;
auto func8 = [=]()mutable
{a++;b++;c++;f++;return a + b + c + f;
};

f++后到外面值不变,还是传值捕捉
在这里插入图片描述

2.3 lambda的原理

1. lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for
这样的东西。范围for底层是迭代器,而lambda底层是仿函数对象,也就说我们写了一个lambda 以后,编译器会生成一个对应的仿函数的类
2. 仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传哪些对象(用谁就捕捉谁,并不是全部都捕捉


http://www.ppmy.cn/devtools/157005.html

相关文章

w193基于Spring Boot的秒杀系统设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

C# List 列表综合运用实例⁓Hypak原始数据处理编程小结

C# List 列表综合运用实例⁓Hypak原始数据处理编程小结 1、一个数组解决很麻烦引出的问题1.1、RAW 文件尾部数据如下:1.2、自定义标头 ADD 或 DEL 的数据结构如下&#xff1a; 2、程序 C# 源代码的编写和剖析2.1、使用 ref 关键字&#xff0c;通过引用将参数传递&#xff0c;以…

DeepSeek与llama本地部署(含WebUI)

DeepSeek从2025年1月起开始火爆&#xff0c;成为全球最炙手可热的大模型&#xff0c;各大媒体争相报道。我们可以和文心一言一样去官网进行DeepSeek的使用&#xff0c;那如果有读者希望将大模型部署在本地应该怎么做呢&#xff1f;本篇文章将会教你如何在本地傻瓜式的部署我们的…

spacemacs gnuplot

个人博客地址&#xff1a;spacemacs gnuplot | 一张假钞的真实世界 环境 Ubuntu 16.10Emacs 24 安装过程 spacemacs安装 安装Emacs sudo apt-get install emacs 安装spacemacs &#xff08;1&#xff09;如果已经存在Emacs配置文件&#xff0c;首先备份&#xff1a; c…

Oracle(windows安装遇到的ORA-12545、ORA-12154、ORA-12541、ORA-12514等问题)

其实出现该问题就是监听或者服务没有配好。 G:\xiaowangzhenshuai\software\Oracle\product\11.2.0\dbhome_1\NETWORK\ADMINlistener.ora SID_LIST_LISTENER (SID_LIST (SID_DESC (SID_NAME CLRExtProc)(ORACLE_HOME G:\xiaowangzhenshuai\software\Oracle\product\11.2.0\d…

实施工程师:面试基础宝典

一.运维工程师和实施工程师的区别&#xff1a;工作内容不同、职能不同、工作形式不同 1.工作内容不同&#xff1a; 运维工程师要对公司硬件和软件进行维护。 硬件包括&#xff1a;机房、机柜、网线光纤、PDU、服 务器、网络设备、安全设备等。 实施工程师包括常用操作系统、应…

OS10 固件更新步骤-U 盘方式

OS10 固件更新步骤-U 盘方式 1.准备工作 (1)将 U 盘格式化成 FAT32 格式 ,大小不超过16G.当然也可以使用FTP/TFTP 等方式上传文件,U盘是最方便的一种方式. (2)将 OS10 下载下来之后,解压文件,将最大的文件,后缀名:.bin 文件放在 U 盘的根目录(一定要放在 U 盘根目录…

Nginx中替换即将到期的SSL/TLS证书

在 Nginx 中替换即将到期的 SSL/TLS 证书是一个常见的运维任务。以下是详细的步骤 1. 准备新证书 确保您已经获取了新的 SSL/TLS 证书文件和私钥文件。通常,证书颁发机构(CA)会提供以下文件: 证书文件 :通常是 .crt 或 .pem 文件(例如 example.com.crt)。私钥文件 :通…