C++修炼:C++11(二)

         Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》

欢迎点赞,关注!

目录

1、可变参数模板

        1.1、基本语法

        1.2、包扩展

        1.3、emplace系列接口

2、类的新功能

        2.1、默认的移动构造和移动赋值

        2.2、委托构造

        2.3、default和delete


1、可变参数模板

        可变参数模板(Variadic Templates)是C++11引入的一个重要特性,它允许模板接受任意数量和类型的参数。

        1.1、基本语法

        可变参数模板可以使函数模板或者类模板支持任意多个类型的变量。可变数目的参数被称为参数包。存在两种参数包,第一种是模板参数包,第二种是函数参数包。参数包可以接受0个参数。

template<class ...Args> void func(Args... args) {}
template<class ...Args> void func(Args&... args) {}
template<class ...Args> void func(Args&&... args) {}

        比如,在上面三个函数模板中,Arges是模板参数包,arges是函数参数包。我们需要注意一下格式,注意一下三个点的位置。

        对于上面的三个函数模板,第三个函数模板我们使用的是右值引用,这意味着对于参数包中的每个类型都是使用的万能引用。如果传左值,这个参数包中的类型被推导为左值引用,引用折叠后为左值引用。如果传右值的话,类型被推导为右值引用,折叠后为右值引用。

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

        我们可以使用sizeof...来计算参数包中参数的个数。

#include<iostream>
using namespace std;template<class ...Args> 
void func(Args... args) 
{cout << sizeof...(args) << endl;
}
int main()
{func();//0func(1);//1func(1, 2);//2func(1, 2,"sss");//3return 0;
}

        这个计算也是编译时计算,因为本质上我们就是实例化出四个函数。其实参数包也是使模板进一步的泛型化。

        本质上是替换了这四个函数:

void func()
{}
void func(int a)
{}
void func(int a,int b)
{}
void func(int a,int b,const char* str)
{}

        1.2、包扩展

        现在我们实现一个print,对于传进函数的每一个变量都打印一次。

         注意参数包不能这样用,因为参数包不是一个容器:

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;
//}

        那么我们怎么实现print函数呢?我们可以使用包扩展来实现。

        包扩展是在编译时进行的。包扩展本质上是递归调用,但是是在编译时递归。

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, "xxxxx");Print(1, "xxxxx", 2.2);return 0;
}

        我们把参数传给print,然后print在调用showlist,每次“剔除”一个变量。知道参数包中变量数为0,此时再去调用showlist直接调用最上面的showlist函数。

        正因为是编译时调用,所以我们不能这样写: 

template <class T, class ...Args>
void ShowList(T x, Args... args)
{if (sizeof...(args) == 0){return;}cout << x << " ";// args是N个参数的参数包 // 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包 ShowList(args...);
}

        因为这个函数结束条件是运行时判断逻辑。 

        还有一种包扩展的方式:

template <class T>
const T& GetArg(const T& x)
{cout << x << " ";return x;
}
template <class ...Args>
void Arguments(Args... args)
{cout << endl;
}
template <class ...Args>
void Print(Args... args)
{// 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments Arguments(GetArg(args)...);
}

        我们把传入print的几个参数组成参数包传给GetArg。对于GetArg来说,我们相当于把参数包中的每个参数都传给GetArg,然后把GetArg的所有返回值在组成一个参数包,传给Arguments. 这种包扩展就是普通的编译推导,并不是递归。

        需要注意的是上面这种的包扩展方式在vs上是倒序输出的,也就是说输出结果和我们的传参顺序恰好相反。造成这种结果的原因是C++标准没有规定函数参数的求值顺序,而大部分编译器默认是从右到左。

        1.3、emplace系列接口

        C++11之后所有的stl容器都新增了emplace接口,empalce系列的接口均为模板可变参数。接下来我们使用list来测试一下emplace系列的接口。

         

        对于emplace_back和push_back来说,无论是传左值,还是传右值,效率都没有区别。唯一有区别的场景就是这种:

int main()
{list<string> li;li.push_back("sss");//构造加移动构造li.emplace_back("sss");//直接构造return 0;
}

        对于push_back来说,因为push_back这个函数的形参就是string&&类型的,如果我们想让形参和实参匹配上,需要先对形参进行构造,构造一个临时对象,然后再移动构造给string对象进行push_back。

        但是对于emplace_back来说,由于他是一个模板,他可以直接接受const char*类型的变量,然后再在插入之前进行一次构造,构造出string对象进行插入就可以了。

         接下来我们看下面这个场景:

#include<iostream>
#include<list>
#include<string>
using namespace std;
int main()
{list<pair<int,double>> li;li.push_back({ 6,5.5 });//li.emplace_back({ 6,5.5 });li.emplace_back(6, 5.5);return 0;
}

         对于pair类型,我们使用emplace接口时不能传花括号,也就是说不能传初始化列表。因为咱们的emplace_back是模板函数,所以在传参是他会去推导类型。由于initializer_list在推导时必须是initializer_list<T>,也就是说列表中的值类型必须是相同的。但是这里一个int,一个double不是同一类型参数,所以说会编译报错。

        但是对于直接传构造底层对象的参数是没有问题的。这也是emplace系列接口的正确用法:在插入值时向emplace系列接口中直接传入构造底层存储对象类型所需的参数。这样相比普通插入效率更高。但如果容器存储的是一些基本类型(如int,double,char)时使用emplace_back或push_back效率上没有差异。

        现在我们对之前模拟实现的list进行一下升级,写一下emplace系列接口:

......
list_node(T&& x):_next(nullptr), _prev(nullptr), _data(std::move(x))
{}
//如果上面两个构造函数都给了默认值=T(),那么当new node时,也就是不传参是无法确定匹配哪个构造函数。
//一般右值版本不给默认值。右值引用通常用于移动语义,而默认构造的临时对象(T())不适合被移动。
// 语义上矛盾:右值引用表示要"窃取"资源,但默认参数会创建一个新对象
template<class ...Args>
list_node(Args&&... args): _next(nullptr), _prev(nullptr), _data(std::forward<Args>(args)...)
{}
......
template<class ...Args>
void emplace_back(Args&&... args)
{emplace(end(), std::forward<Args>(args)...);
}
template<class ...Args>
void emplace(iterator pos, Args&&... args)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(std::forward<Args>(args)...);// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;
}

         注意的是我们的参数包构造不需要解析参数包,也就是说编译器不需要进行包扩展什么的,编译器不需要一个一个看都是什么类型,而是直接拿去和list底层存储类型的构造函数匹配。如果匹配对不上就报错。

        另外就是每次传参数包我们都需要完美转发。让参数包中的参数都保持原有属性。                 

2、类的新功能

        2.1、默认的移动构造和移动赋值

        C++11新增了两个默认成员函数:移动构造函数和移动赋值运算符重载。

        如果你没有自己生成移动构造函数,且没有实现析构函数,拷贝构造,拷贝赋值重载中的任意一个,那么编译器就会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

        同理,如果你没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会 执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。

        如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

        2.2、委托构造

class A
{
public:A(int a, int b):_a(a),_b(b){}A(int a, int b, char c):A(a, b){_c = c;}
private:int _a;int _b;char _c=0;
};

        上面这个类就实现了委托构造,本质上也是一种复用。 

        2.3、default和delete

        default可以强制让编译器生成默认函数:

class MyClass {
public:MyClass() = default; // 显式要求编译器生成默认构造函数MyClass(const MyClass&) = default; // 默认拷贝构造函数
};

        delete就与default相反,不让编译器生成某个默认函数:

class NonCopyable {
public:NonCopyable(const NonCopyable&) = delete;NonCopyable& operator=(const NonCopyable&) = delete;
};

         好了,今天的内容就分享到这,我们下期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908541.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单元测试与QTestLib框架使用

一.单元测试的意义 在软件开发中&#xff0c;单元测试是指对软件中最小可测试单元&#xff08;通常是函数、类的方法&#xff09;进行隔离的、可重复的验证。进行单元测试具有以下重要意义&#xff1a; 1.提升代码质量与可靠性&#xff1a; 早期错误检测&#xff1a; 在开发…

(附实现代码)Step-Back 回答回退策略扩大检索范围

1. LangChain 少量示例提示模板 在与 LLM 的对话中&#xff0c;提供少量的示例被称为 少量示例&#xff0c;这是一种简单但强大的指导生成的方式&#xff0c;在某些情况下可以显著提高模型性能&#xff08;与之对应的是零样本&#xff09;&#xff0c;少量示例可以降低 Prompt…

16-Oracle 23 ai-JSON-Relational Duality-知识准备

一直做DBA的小伙伴&#xff0c;是不是对开发相对陌生一些。JSON 关系二元性是 Oracle Database 23ai 中重要的特性&#xff0c;同时带来的是范式革命。JSON关系二元性解决了数据库领域的根本矛盾​&#xff0c;结构化数据的严谨性与半结构化数据的灵活性之间的矛盾。 JSON Rela…

什么是预训练?深入解读大模型AI的“高考集训”

1. 预训练的通俗理解&#xff1a;AI的“高考集训” 我们可以将预训练&#xff08;Pre-training&#xff09; 形象地理解为大模型AI的“高考集训”。就像学霸在高考前需要刷五年高考三年模拟一样&#xff0c;大模型在正式诞生前&#xff0c;也要经历一场声势浩大的“题海战术”…

思尔芯携手Andes晶心科技,加速先进RISC-V 芯片开发

在RISC-V生态快速发展和应用场景不断拓展的背景下&#xff0c;芯片设计正面临前所未有的复杂度挑战。近日&#xff0c;RISC-V处理器核领先厂商Andes晶心科技与思尔芯&#xff08;S2C&#xff09;达成重要合作&#xff0c;其双核单集群AX45MPV处理器已在思尔芯最新一代原型验证系…

vscode配置lua

官网下载lua得到如下 打开vscode的扩展下载如下三个 打开vscode的此处设置 搜索 executorMap&#xff0c;并添加如下内容

理解 RAG_HYBRID_BM25_WEIGHT:打造更智能的混合检索增强生成系统

目录 理解 RAG_HYBRID_BM25_WEIGHT&#xff1a;打造更智能的混合检索增强生成系统 一、什么是 Hybrid RAG&#xff1f; 二、什么是 RAG_HYBRID_BM25_WEIGHT&#xff1f; 三、参数设置示例 四、什么时候该调整它&#xff1f; 五、实战建议 六、总结 理解 RAG_HYBRID_BM25…

Spring Boot 2 中 default-autowire 的使用

Spring Boot 2 中 default-autowire 的使用 在 Spring Boot 2 中&#xff0c;default-autowire 这个来自传统 XML 配置的概念仍然存在&#xff0c;但它的使用已经大大减少&#xff0c;因为现代 Spring Boot 应用主要使用注解驱动的配置方式。 default-autowire 在 Spring Boo…

Spring Boot + Thymeleaf 防重复提交

在 Spring Boot 与 Thymeleaf 结合的 Web 应用中&#xff0c;防止重复提交可以采用token 机制 客户端禁用按钮的方式实现&#xff0c;在高并发场景下&#xff0c;考虑使用 Redis 存储 token 而非 Session。 第一步&#xff1a;后端实现 Controller public class FormControl…

【20250607接单】Spark + Scala + IntelliJ 项目的开发环境配置从零教学

本教程适用于零基础、一台刚装好 Windows 的全新电脑开始&#xff0c;搭建能运行 Spark Scala IntelliJ 项目的开发环境。以下是超详细、小白级别逐步教程&#xff0c;从“下载什么”到“点击哪里”都帮你列清楚。 &#x1f3af; 目标 操作系统&#xff1a;Windows10/11工具…

【ubuntu】虚拟机安装配置,sh脚本自动化,包含 apt+时间同步+docker+mysql+redis+pgsql

可以说是ubuntu基础环境搭建合集&#xff0c;个人学习用&#xff0c;使用sh一键安装&#xff0c;避免复制各种命令 流程主要包括 0. 可选择不同ubuntu版本对应安装&#xff08;支持 Ubuntu 20.04/22.04/23.04/24.04&#xff09; 1. apt换源aliyun 2. 时间选择上海时区&#x…

Rust 学习笔记:关于智能指针的练习题

Rust 学习笔记&#xff1a;关于智能指针的练习题 Rust 学习笔记&#xff1a;关于智能指针的练习题问题一问题二问题三问题四问题五问题六问题七问题八问题九问题十问题十一 Rust 学习笔记&#xff1a;关于智能指针的练习题 参考视频&#xff1a; https://www.bilibili.com/vi…

JavaScript ES6 解构:优雅提取数据的艺术

JavaScript ES6 解构&#xff1a;优雅提取数据的艺术 在 JavaScript 的世界中&#xff0c;ES6&#xff08;ECMAScript 2015&#xff09;的推出为开发者带来了许多革命性的特性&#xff0c;其中“解构赋值”&#xff08;Destructuring Assignment&#xff09;无疑是最受欢迎的功…

Shell 命令及运行原理 + 权限的概念(7)

文章目录 Shell 命令以及运行原理&#xff08;4-1.22.08&#xff09;Linux权限的概念1. 什么是权限2. 认识人&#xff08;普通用户&#xff0c;root用户&#xff09;以及两种用户的切换认识普通用户和root用户两种用户之间的切换指令提权 3. 文件的属性解析 权限属性指令ll显示…

以智能管理为基础,楼宇自控打造建筑碳中和新路径

在全球气候变化的严峻形势下&#xff0c;“碳中和”已成为各国发展的重要战略目标。建筑行业作为能源消耗与碳排放的“大户”&#xff0c;其运行阶段的能耗占全社会总能耗近40%&#xff0c;碳排放占比与之相当&#xff0c;实现建筑碳中和迫在眉睫。传统建筑管理模式下&#xff…

Python爬虫实战:研究Hyper 相关技术

一、项目概述 本项目展示了如何结合 Python 的异步编程技术与 Hyper 框架开发一个高性能、可扩展的网络爬虫系统。该系统不仅能够高效地爬取网页内容,还提供了 RESTful API 接口,方便用户通过 API 控制爬虫的运行状态和获取爬取结果。 二、系统架构设计 1. 整体架构 系统采…

html 滚动条滚动过快会留下边框线

滚动条滚动过快时&#xff0c;会留下边框线 但其实大部分时候是这样的&#xff0c;没有多出边框线的 滚动条滚动过快时留下边框线的问题通常与滚动条样式和滚动行为有关。这种问题可能出现在使用了自定义滚动条样式的情况下。 注意&#xff1a;使用方法 6 好使&#xff0c;其它…

【Linux】Ubuntu 创建应用图标的方式汇总,deb/appimage/通用方法

Ubuntu 创建应用图标的方式汇总&#xff0c;deb/appimage/通用方法 对于标准的 Ubuntu&#xff08;使用 GNOME 桌面&#xff09;&#xff0c;desktop 后缀的桌面图标文件主要保存在以下三个路径&#xff1a; 当前用户的桌面目录&#xff08;这是最常见的位置&#xff09;。所…

【自然语言处理】大模型时代的数据标注(主动学习)

文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构D 实验设计E 个人总结 A 论文出处 论文题目&#xff1a;FreeAL: Towards Human-Free Active Learning in the Era of Large Language Models发表情况&#xff1a;2023-EMNLP作者单位&#xff1a;浙江大…

【论文解读】DeepSeek-R1

文章目录 概览一、DeepSeek-R1-Zero&#xff1a;在 Base Model 上直接进行 RL&#xff08;一&#xff09;强化学习算法&#xff08;二&#xff09;奖励模型&#xff08;三&#xff09;数据构造&#xff08;四&#xff09;DeepSeek-R1-Zero 的性能、自我进化过程和 Aha Moment1.…