当前位置: 首页 > news >正文

【C++11】可变模板参数

目录

可变模板的定义方式

参数包的展开方式

递归的方式展开参数包

STL中的emplace相关接口函数

STL容器中emplace相关插入接口函数

​编辑

模拟实现:emplace接口


C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比 C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改 进。

像之前学习的printf就是一个函数参数的可变参数,它可以接收多个任意类型,但它们只函数参数的可变参数,并不是模板的可变参数

printf的使用方法:

int printf( const char *format , ... );
本博客讲解的是函数模板的可变参数,不会涉及到类模板的可变参数

可变模板的定义方式

函数的可变参数模板定义方式如下:

template<class ...Args> //Args全称:arguments
返回类型 函数名(Args... args)
{//函数体
}

下面就是一个基本可变参数的函数模板

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

Args:是一个可变模板参数包

args:是一个函数形参参数包

说明一下:

模板参数Args前面有省略号,代表它是一个可变模板参数,我们将带省略号的参数称为 “参数包”,这个参数包中可以包含0到任意个模板参数,args则是一个函数形参参数包

现在我们可以向这个函数中传入多个不同的类型,并且可以通过sizeof算出参数包的参数个数

以下例代码为例:

template<class ...Args>
void ShowList(Args... args)
{cout << sizeof...(args) << endl;
}int main()
{ShowList(1);ShowList(1, 2);ShowList(1, 2, string("dict"));map<string, int> m1;ShowList(1, 2, 3, m1);return 0;
}

我们无法直接获取参数包args中的每个参数的, 只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

由于C++11语法不支持使用args[i]这样方式获取可变q参数,所以我们的用一些奇招来一一获取参数包的值。

错误示例:

template<class ...Args>
void ShowList(Args... args)
{//errorfor (int i = 0; i < sizeof...(args); ++i){cout << args[i] << endl;}
}

参数包的展开方式

递归的方式展开参数包

方式如下:

1.给函数模板新增一个参数,这样就可以从接收到的参数包分离出来一个参数

2.在函数模板中进行递归,不断的分离参数包中的参数

3.直到接收到最后一个参数结束

结束条件;

->1. 可以创建一个无参的函数来终止递归:当参数包中的参数为0时会调用该函数终止循环

void _ShowList()
{cout << endl;
}template<class T, class ...Args>
void _ShowList(T value, Args... args)
{cout << value << ' ';_ShowList(args...);
}int main()
{_ShowList(1, 2, string("dict"));return 0;
}

->2. 可以创建一个参数的函数来终止递归:当参数包中的参数为1时会调用该函数终止循环

template<class T>
void _ShowList(const T& t)
{cout << t << endl;
}template<class T, class ...Args>
void _ShowList(T value, Args... args)
{cout << value << ' ';_ShowList(args...);
}int main()
{_ShowList(1, 2, string("dict"));return 0;
}

但是使用该方法有一个弊端:我们在调用ShowList函数时必须至少传入一个参数,否则就会报错,因为此时无论是调用递归终止函数还是展开函数,都需要至少传入一个参数

使用sizeof...(args)算出参数个数的特性,利用它的特性做一个递归结束条件可以吗?不行!

template<class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << ' ';if (sizeof...(args)){return;}ShowList(args...);
}

函数模板并不能调用,函数模板需要在编译时根据传入的实参类型进行推演,生成对应的函数,这个生成的函数才能够被调用。
而这个推演过程是在编译时进行的,当推演到参数包args中参数个数为0时,还需要将当前函数推演完毕,这时就会继续推演传入0个参数时的ShowList函数,此时就会产生报错,因为ShowList函数要求至少传入一个参数。
这里编写的if判断是在代码编译结束后,运行代码时才会所走的逻辑,也就是运行时逻辑,而函数模板的推演是一个编译时逻辑。

还有一种特殊的方式,该方法比较抽象,就是使用逗号表达式展开参数包

->3. 逗号表达式展开参数包

template<class T>
void CPPprint(const T& value)
{cout << value << ' ';
}template<class ...Args>
void ShowList(Args... args)
{int array[] = {( CPPprint(args), 0)...};cout << endl;
}

当我们在数组中不标注元素个数时,编译器会帮我们自动推导元素个数,这时它会帮我们展开参数包

如下:

int array[] = {( CPPprint(args), 0), CPPprint(args), 0),  CPPprint(args), 0),  CPPprint(args), 0)};

在调用CPPprint函数的同时,利用逗号运算符的特性进行对数组的初始化

其实也可以不使用逗号运算符完成该操作

template<class T>
int CPPprint(const T& value)
{cout << value << ' ';return 0;
}template<class ...Args>
void ShowList(Args... args)
{int array[] = { (CPPprint(args))... };cout << endl;
}

将被调用的函数设置一个返回值,调用之后返回0,这样就可以在编译器展开参数包调用函数时,通过返回值初始化

STL中的emplace相关接口函数

以便大家更好的理解emplace,先给大家看一段代码,可变模板参数的使用场景:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "Data()~构造函数" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date()~拷贝构造" << endl;}private:int _year;int _month;int _day;
};template<class ...Args>
Date* Init(Args&&... args)
{Date* ret = new Date(args...);return ret;
}int main()
{Date* p1 = Init();Date* p2 = Init(2024);Date* p3 = Init(2024, 11);Date* p4 = Init(2024, 11, 12);Date d1(2, 3, 3);Date* p5 = Init(d1);return 0;
}

我们通过将参数传入参数包在编译期间通过将参数包展开的操作进行对象的构造

STL容器中emplace相关插入接口函数

C++11标准STL中的容器增加emplace版本的插入接口,比如list容器的push_front,push_back和insert函数,都增加了对应的emplace_front,emplace_back,emplace函数。如下:

emplace接口全部都是使用的可变参数模板

注意:两个&&是万能引用并不是右值引用

对比list中的push_back和emplace_back,对于emplace系列接口而言,它的主要优势就是直接在容器内部构造元素可以结合我上面给的场景进行理解,而不是构造一个临时对象在复制或移动到容器中可以有效的避免拷贝和移动操作

以emplace和push_back为例:

调用push_back函数插入元素时,可以传入左值对象或者右值对象,也可以使用列表初始化

调用emplace时可以传左值对象或者右值对象,但是不能使用列表初始化,emplace系列最大的特点就是,插入元素时可以传入用于构造元素的参数包

比如:

int main()
{list<pair<nxbw::string, int>> mylist;pair<nxbw::string, int> kv("nxbw", 10);mylist.emplace_back(kv); //传左值mylist.emplace_back(make_pair("nxbw", 10)); //传右值mylist.emplace_back("nxbw", 10); //传参数包mylist.push_back(kv); //传左值mylist.push_back(make_pair("nxbw", 10)); //传右值mylist.push_back({ "nxbw", 10 }); //使用列表初始化return 0;
}

原地构造:使用emplace,你可以提供构造元素所需的参数,容器会直接在emplace接口的实现中构造该对象

emplace系列接口的工作流程

emplace系列接口的工作流程如下:

  1. 先通过空间配置器为新结点获取一块内存空间,注意这里只会开辟空间,不会自动调用构造函数对这块空间进行初始化。
  2. 然后调用allocator_traits::construct函数对这块空间进行初始化,调用该函数时会传入这块空间的地址和用户传入的参数(需要经过完美转发)。
  3. 在allocator_traits::construct函数中会使用定位new表达式,显示调用构造函数对这块空间进行初始化,调用构造函数时会传入用户传入的参数(需要经过完美转发)。
  4. 将初始化好的新结点插入到对应的数据结构当中,比如list容器就是将新结点插入到底层的双链表中。

emplace系列接口的意义

由于emplace系列接口的可变模板参数的类型都是万能引用,因此既可以接收左值对象,也可以接收右值对象,还可以接收参数包。

  1. 如果调用emplace系列接口时传入的是左值对象,那么首先需要先在此之前调用构造函数实例化出一个左值对象,最终在使用定位new表达式调用构造函数对空间进行初始化时,会匹配到拷贝构造函数
  2. 如果调用emplace系列接口时传入的是右值对象,那么就需要在此之前调用构造函数实例化出一个右值对象,最终在使用定位new表达式调用构造函数对空间进行初始化时,就会匹配到移动构造函数
  3. 如果调用emplace系列接口时传入的是参数包,那就可以直接调用函数进行插入,并且最终在使用定位new表达式调用构造函数对空间进行初始化时,匹配到的是构造函数。

总结一下:

  • 传入左值对象,需要调用构造函数+拷贝构造函数
  • 传入右值对象,需要调用构造函数+移动构造函数
  • 传入参数包,只需要调用构造函数

当然,这里的前提是容器中存储的元素所对应的类,是一个需要深拷贝的类,并且该类实现了移动构造函数。否则在调用emplace系列接口时,传入左值对象和传入右值对象的效果都是一样的,都需要调用一次构造函数和一次拷贝构造函数。

实际emplace系列接口的一部分功能和原有各个容器插入接口是重叠的,因为容器原有的push_back、push_front和insert函数也提供了右值引用版本的接口,如果调用这些接口时如果传入的是右值对象,那么最终也是会调用对应的移动构造函数进行资源的移动的。

emplace接口的意义:

emplace系列接口最大的特点就是支持传入参数包,用这些参数包直接构造出对象,这样就能减少一次拷贝,这就是为什么有人说emplace系列接口更高效的原因。
但emplace系列接口并不是在所有场景下都比原有的插入接口高效,如果传入的是左值对象或右值对象,那么emplace系列接口的效率其实和原有的插入接口的效率是一样的。
emplace系列接口真正高效的情况是传入参数包的时候,直接通过参数包构造出对象,避免了中途的一次拷贝。
 

通过下面的场景我们来验证一下:

namespace nxbw
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动构造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动语义" << endl;swap(s);}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动语义" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}
int main()
{list<pair<nxbw::string, int>> mylist;pair<nxbw::string, int> kv("nxbw", 10); //构造mylist.emplace_back(kv); //传左值,mylist.emplace_back(pair<nxbw::string, int>("nxbw", 10)); //传右值mylist.emplace_back("nxbw", 10); //传参数包return 0;
}

由于我们在string的构造函数、拷贝构造函数和移动构造函数当中均打印了一条提示语句,因此我们可以通过控制台输出来判断这些函数是否被调用。

下面我们用一个容器来存储模拟实现的string,并以不同的传参形式调用emplace系列函数。比如:

说明一下:

模拟实现string的拷贝构造函数时复用了构造函数,因此在调用string拷贝构造的后面会紧跟着调用一次构造函数。
为了更好的体现出参数包的概念,因此这里list容器中存储的元素类型是pair,我们是通过观察string对象的处理过程来判断pair的处理过程的。
这里也可以以不同的传参方式调用push_back函数,顺便验证一下容器原有的插入函数的执行逻辑。比如:

int main()
{list<pair<nxbw::string, int>> mylist;pair<nxbw::string, int> kv("nxbw", 10);mylist.push_back(kv); //传左值mylist.push_back(pair<nxbw::string, int>("nxbw", 10)); //传右值mylist.push_back({ "nxbw", 10 }); //使用列表初始化return 0;
}

模拟实现:emplace接口

namespace nxbw
{// 模拟实现list在之前的章节有提过,这里只是将原来的代码多增加一些接口的片段代码// 这是list需要用到的节点类template<class T>struct __list_node{__list_node(const T& val = T()):_data(val), _prev(nullptr), _next(nullptr){}// 这里需要在原来的基础上需要增加一个可变模板参数模板的构造函数,方便下面使用newtemplate<class ...Args>__list_node(Args&& ...args): _data(std::forward<Args>(args)...), _prev(nullptr), _next(nullptr){}T _data;__list_node* _prev;__list_node* _next;};template<class T>struct list{template<class ...Args>iterator emplace(iterator position, Args&&... args){node* cur = position._node;node* prev = cur->_prev;// 函数参数包的完美转发node* newnode = new node(forward<Args>(args)...);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(cur);}template<class ...Args>void emplace_back(Args&&... args){// 函数参数包的完美转发emplace(end(), forward<Args>(args)...);}// 获取节点函数,这里更新成了万能引用版的template<class T>node* get_node(T&& val = T()){node* new_node = new node(forward<T>(val)); // 完美转发new_node->_prev = new_node;new_node->_next = new_node;return new_node;}private:__list_node<T>* _head; // 指向节点类的指针};
};

emplace系列和push_back以及insert的区别

效率方面:对于左值引用版本的push_back和insert来说确实有很大的效率提升,对于右值引用版本的push_back和insert来说效率其实差不多,因为移动赋值/拷贝代价足够小

构造复杂对象:当元素的构造比叫复杂时,emplace可以让代码更简洁,直接传入构造参数即可

相关文章:

【C++11】可变模板参数

目录 可变模板的定义方式 参数包的展开方式 递归的方式展开参数包 STL中的emplace相关接口函数 STL容器中emplace相关插入接口函数 ​编辑 模拟实现&#xff1a;emplace接口 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板&#xff0c;相比 C9…...

AAAI-2024 | 大语言模型赋能导航决策!NavGPT:基于大模型显式推理的视觉语言导航

作者&#xff1a;Gengze Zhou, Yicong Hong, Qi Wu 单位&#xff1a;阿德莱德大学&#xff0c;澳大利亚国立大学 论文链接&#xff1a; NavGPT: Explicit Reasoning in Vision-and-Language Navigation with Large Language Models &#xff08;https://ojs.aaai.org/index.p…...

@HeadFontStyle注解属性介绍

HeadFontStyle 是一个自定义的 Java 注解&#xff0c;它用于指定 Excel 单元格字体的样式属性。这个注解可以应用于方法中&#xff0c;用来动态地设置 Excel 文件中单元格的字体样式。下面是 HeadFontStyle 注解中各个属性的详细介绍&#xff1a; 1. fontName (String) 类型:…...

Exchange ProxyLogon 攻击链利用详解

目录 ProxyLogon 攻击链 影响版本 CVE-2021-26855 SSRF 复现 验证是否存在漏洞 详细漏洞利用 CVE-2021–27065 任意文件写入复现 ProxyLogon 一键利用 CVE-2021-26855 与 CVE-2021-27065 是微软在2021年3月2日发布的高危漏洞公告。这套组合拳被称为ProxyLogon,可直接获…...

C++小碗菜之五:关键字static

“一个人的命运啊&#xff0c;当然要靠自我奋斗&#xff0c;但也要考虑到历史的行程。” ——2009年4月23日在视察中国联合工程公司时的讲话 目录 ​编辑 前言 static在局部作用域中的作用 给出例子&#xff1a; 修改上面给出的例子&#xff1a; 为什么不使用全局变量…...

deepstream笔记

创建pipeline pipeline gst_pipeline_new("audio-player");创建filesrc类型元素并命名为file-source&#xff1b; GstElement *source gst_element_factory_make("filesrc", "file-source");通过元素名file-source获取元素对应的指针&#x…...

Pinpoint 是一个开源的分布式追踪系统

pinpointagent2.2.2.tar 是 Pinpoint 的一个版本&#xff0c;Pinpoint 是一个开源的分布式追踪系统&#xff0c;专门用于对 Java 应用程序进行性能监控、日志记录和故障诊断。它可以帮助开发人员和运维人员追踪和分析微服务架构中服务之间的调用链&#xff0c;并进行性能分析。…...

H3C交换机远程登录基本配置

设备信息 H3C Comware Software, Version 7.1.070, Release 6312P02 Copyright (c) 2004-2021 New H3C Technologies Co., Ltd. All rights reserved. H3C S6520X-54QC-EI Telnet登录设备基本配置 1、开启telnet服务 system-view telnet server enable 2、telnet登录设备终…...

python关闭线程池来关闭线程

在 Python 中&#xff0c;使用线程池&#xff08;如 concurrent.futures.ThreadPoolExecutor 或 multiprocessing.pool.ThreadPool&#xff09;来管理和执行多个线程是一种常见的并发编程方式。关于关闭线程池以及关闭后线程的状态&#xff0c;以下是详细的解释和指导。 使用 …...

生成式AI:药学科普的新引擎

在信息爆炸的时代&#xff0c;药学知识的普及显得尤为重要。而今&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;正以其强大的内容生成和数据分析能力&#xff0c;悄然改变着传统的药学科普模式。它不仅能加速信息的传递&#xff0c;更能为患者提供个性化、易…...

洛谷 p3392 涂条纹

题目&#xff1a; 思路&#xff1a; 简单的模拟题&#xff0c;模拟题好麻烦&#xff0c;但是思路走好就可以。首先我们可以求出每一行&#xff0c;红&#xff0c;蓝&#xff0c;白的个数。涂蓝色和白色为了涂色更少&#xff0c;所以涂蓝色要选择第i行蓝色个数最多的&#xff0…...

64.基于SpringBoot + Vue实现的前后端分离-新闻资讯系统(项目 + 论文)

项目介绍 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;文章信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…...

Y3编辑器教程8:资源管理器与存档、防作弊设置

文章目录 一、资源管理器简介1.1 界面介绍1.2 资源商店1.3 AI专区1.3.1 AI文生图1.3.2 AI图生图1.3.3 立绘头像 二、导入导出2.1 文件格式2.2 模型导入2.2.1 模型制作后导出2.2.2 模型文件导入Y3编辑器2.2.3 Y3编辑器角色、装饰物模型要求 2.3 纹理导入2.4 材质贴图2.4.1 材质支…...

智慧社区电子商务系统:实现社区资源的数字化管理

2.1vue技术 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。 [5] 与其它大型框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项…...

精准提升:从94.5%到99.4%——目标检测调优全纪录

&#x1f680; 目标检测模型调优过程记录 在进行目标检测模型的训练过程中&#xff0c;我们面对了许多挑战与迭代。从初始模型的训练结果到最终的调优优化&#xff0c;每一步的实验和调整都有其独特的思路和收获。本文记录了我在优化目标检测模型的过程中进行的几次尝试&#…...

【LLM论文日更】| 训练大型语言模型在连续潜在空间中进行推理

论文&#xff1a;https://arxiv.org/pdf/2412.06769代码&#xff1a;暂未开源机构 &#xff1a;Meta领域&#xff1a;思维链发表&#xff1a;arxiv 研究背景 研究问题&#xff1a;这篇文章要解决的问题是如何在大语言模型&#xff08;LLMs&#xff09;中实现一种新的推理范式&…...

智能家居实训室中,STC单片机驱动的“互联网+”智能家居系统设计

一、引言 随着经济的快速发展&#xff0c;人们对家居环境的智能化、网络化需求日益增强&#xff0c;智能家居的研究也因此受到了国内外相关机构的广泛关注。STC单片机凭借其卓越的性能和广泛的应用领域&#xff0c;成为了智能家居系统设计的优选方案。作为一种先进的微控制器&…...

《C++ 赋能强化学习:Q - learning 算法的实现之路》

在当今科技飞速发展的时代&#xff0c;人工智能无疑是最热门的领域之一&#xff0c;而强化学习作为其中的重要分支&#xff0c;正逐渐改变着我们解决复杂问题的方式。Q - learning 算法作为强化学习中的经典算法&#xff0c;在众多领域如游戏、机器人控制、资源管理等有着广泛的…...

三维模型中的UV展开是什么意思?它有什么优势?

UV展开涉及将三维模型的表面展开为一个或多个二维区域&#xff0c;以便将纹理图像正确地映射到模型上。这个过程类似于将一个立体物体的表面切割并平铺开来。UV坐标是用于在二维纹理图像中定位颜色和细节的坐标系统&#xff0c;U和V分别代表纹理图像的水平和垂直轴。 UV展开它…...

怎么在ubuntu系统上安装qt项目的打包工具linuxdeployqt

引言 安装linuxdeployqt方案一方案二 在ubuntu系统上开发的项目最后需要完成打包&#xff0c;qtcreator本身就用一个打包工具&#xff0c;在ubuntu系统上是linuxdeployqt。本文主要记录一下怎么在ubuntu系统上安装qt打包工具linuxdeployqt。 安装linuxdeployqt 前提是已经安装…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

连锁超市冷库节能解决方案:如何实现超市降本增效

在连锁超市冷库运营中&#xff0c;高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术&#xff0c;实现年省电费15%-60%&#xff0c;且不改动原有装备、安装快捷、…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

前端开发面试题总结-JavaScript篇(一)

文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包&#xff08;Closure&#xff09;&#xff1f;闭包有什么应用场景和潜在问题&#xff1f;2.解释 JavaScript 的作用域链&#xff08;Scope Chain&#xff09; 二、原型与继承3.原型链是什么&#xff1f;如何实现继承&a…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

数据结构:递归的种类(Types of Recursion)

目录 尾递归&#xff08;Tail Recursion&#xff09; 什么是 Loop&#xff08;循环&#xff09;&#xff1f; 复杂度分析 头递归&#xff08;Head Recursion&#xff09; 树形递归&#xff08;Tree Recursion&#xff09; 线性递归&#xff08;Linear Recursion&#xff09;…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...

算法—栈系列

一&#xff1a;删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...