c++STL——list的使用和模拟实现
文章目录
- list的使用和模拟实现
- 使用部分
- list的结构声名
- 默认成员函数
- initializer_list
- 容量和访问操作
- 修改操作
- 其他接口
- list的迭代器
- 迭代器的种类
- list的模拟实现
- 明确基本结构
- 预处理函数
- 迭代器部分(重点)
- 思路
- 进一步考虑
- 最终代码
- operator->的重载
- 总结
- begin和end
- 访问接口
- 修改操作
- 默认成员函数
list的使用和模拟实现
学习完vector后,我们将进入一个新的容器的学习,就是list,其实就是我们很熟悉的链表。
但是链表我们之前讲到有8种,根据是否带头节点、单向或双向、是否循环来分类。
而STL库种list的实现是哪一种呢?答案是:双向带头循环链表。正巧的是,在学习c语言数据结构部分的时候就已经实现过这个逻辑了。所以对于链表的操作部分我们已经很熟悉了。
使用部分
由于c++的高封装性,所以使用部分也是很简单的。我们今天的重点不在于使用,而是在于模拟实现。所以使用仅仅只简单带过一下即可。
学习的时候最好可以配合上文档进行使用:https://cplusplus.com/reference/list/list/
list的结构声名
list也是一个类模板,可以针对于不同类型的数据进行构建链表。c++种还有一个叫forward_list的链表,经查阅文档后发现,forward_list就是单链表,只可以向前遍历。这不就是以前学过的单链表。只不过效率上肯定是不如双链表的。
默认成员函数
我们只需要了解一下有哪些方式进行构造链表即可:
有不传参的默认构造,构造出的是空的链表(但其实有头节点)。还有传入n个相同的值的构造,还支持迭代器区间的构造。再加上一个拷贝构造,这些在以往的容器学习中早已接触过,就不再赘述了。
对于其他的默认成员函数,对于使用来讲都是换汤不换药的,所以也不再过多讲解。
initializer_list
还有一个是我们需要了解并且学习使用的,这个学会了构造的时候会非常方便:
就是图中标示的initializer_list,这个是c++11后才有的内容。
其实这个我们经常看见到过,比如我们在刷题网站刷题的时候,我们要传一个链表,输入实例一般都是{1,5,9,4,3,2,4},这样所示。这就是int类型的initializer_list。
我们来具体看一下initializer_list是什么:
其实也是一个类模板,也是支持构造,迭代器的使用的。
所以以后我们构造一个链表想一次性输入不同的数据的时候就不用一直使用push_back接口了,可以直接将initializer_list走隐式类型转换,将initializer_list的一次赋值给list:
我们发现是可以正常使用的,那这样子就很方便了。
容量和访问操作
除了max_size是不常用的,其他的还是比较常用。使用的用法也没有什么太大的区别。就是访问的时候就不再支持[]的运算符重载了。
这涉及到迭代器的原理。我们先简单说一下,等下将迭代器部分的时候会重点讲解。
最大的原因就是因为链表的物理空间不连续。对于string和vector两个容器来讲,它们本质其实就是顺序表,顺序表物理空间连续,是可以很快速的通过原生指针结合[]来进行数据的随机访问。这是效率很高的。
而链表是由一个又一个节点构成,每个节点存储着前后节点的地址。但是链表的节点都是在堆上开辟空间存储的,极大概率下是不会有连续的地址的。如果想要遍历到某个节点,就得从头开始不断地找下一个节点直到能找到,对这一个机制进行[]的函数重载。这个其实是效率很低的。时间复杂度是O(N),而顺序表的这个操作是O(1)。所以这里只支持front和back两个接口,用去获取链表头尾的元素。4
修改操作
注意当前来说:emplace_back、emplace_front、emplace和push_back、push_front、insert也没太大区别。具体的用法需要等以后来讲。当前姑且先一样的用法即可。
其他的接口其实和以前学的都是一样的用法。这里就很好的体现了封装的优点了。我们只需要管如何调用这些接口。这是非常方便的。
其他接口
还有一些对链表操作的接口,又或是关系比较的全局函数,这些用的其实都不多,需要用的时候只需要查阅一下文档即可。
list的迭代器
对于链表来说,最重要的就是迭代器。因为不能再使用原生指针进行操作了。所以对于迭代器的一系列操作我们都需要进行一些特殊的处理,使其能够达到以往一样的使用效果。所以对于list来讲,迭代器其实就是封装成了一个类,类里面对各种成员函数进行重载达到指定要求。
当然,具体的讲解还是会放在模拟实现部分进行讲解。如果只是使用的话和其他容易的表层使用都是一样的,只不过对于底层的实现是有很大的区别。
我们仔细查阅一下文档发现,list类中是特意实现了一个sort函数,这是为什么呢?
迭代器的种类
这个时候就得稍微了解一下迭代器的种类。
最主要的三种就是:
迭代器名称 | 种类 |
---|---|
Random Access | 随机迭代器 |
Bidirectional | 双向迭代器 |
Forward | 单向(向前)迭代器 |
什么意思呢?
对于string和vector来说,它们的底层空间连续,支持随机访问,所以它们的迭代器是隶属于随即迭代器部分的。
而对于list这个双向链表来说,很明显就是双向的迭代器。
单向迭代器就是只能单向遍历的容器,如forward_list,即单向链表。
而还有Input和Output是什么意思呢?这些具体内容都是继承的时候才会细讲,现在先做了解。
它们其实代表的是任意的迭代器,当然Input用的多。就在list的迭代器区间构造函数就能看到
它们的关系是这样子的:
其实就是集合包含的关系。
而alogrithm库中的sort函数是只支持随即迭代器的,所以要求是很高的。而list的迭代器是双向迭代器,所以只能内部自行实现了。
当然,链表的排序效率是很低的,数据较多的情况下都是将数据传入到vector中排序后,再将数据传回list。
对于list的使用就不再讲那么多了,重点是学会如何模拟实现。
list的模拟实现
现在来进入本篇文章的重点——如何手撕一个list
list的实现和string、vector是有很大的不同,甚至难度会更加困难。
明确基本结构
namespace MyList {template<class T>struct list_node {};template<class T>class list {public:typedef list_node<T> Node;private:Node* _head;size_t _size; };
定义一个自己的类模板list,由于链表是由一个个节点组成的。所以需要再声名节点的结构。节点的结构其实也是类,但是我们使用struct来声名。这是因为其默认内部所有成员都是公有。再加上我们肯定是要不断地访问节点中的内容的,所以直接使用struct就可以。
对于list这个类,我们使用class来声名。私有成员变量分别是指向链表头节点的指针和当前链表有效数据个数。
构造链表的时候,我们会new很多个节点,会调用list_node的默认构造函数。没写就是编译器自己生成。但是我们希望达到的效果是:
通过传值生成一个节点,将节点的数据赋值为我们传入的数据。所以是需要我们自己写默认构造函数的。
所以我们最终调整一下基本结构:
namespace MyList {template<class T>struct list_node {T _data;list_node<T>* _prev = nullptr;list_node<T>* _next = nullptr;list_node(const T& Val = T()):_data(Val),_prev(nullptr),_next(nullptr){}};template<class T>class list {public:typedef list_node<T> Node;private:Node* _head;size_t _size; };
预处理函数
还是一样的,我们希望可以多复用一些已有的接口,有些接口会被多次调用,所以我们可以把一些比较重要的接口先行实现:
对于双链表的操作早已学习过,所以不再进行过多的讲解。
1.push_back接口
void push_back(const T& x) {Node* newnode = new Node(x);Node* tail = _head->_prev;newnode->_prev = tail;newnode->_next = _head;tail->_next = newnode;_head->_prev = newnode;++_size;
}
2.size接口
size_t size() {return _size;
}
3.empty接口
bool empty() {return _size == 0;
}
4.swap接口
void swap(list& lt) {std::swap(_head, lt._head);std::swap(_size, lt._size);
}
5.list()构造函数
这个函数主要是构造出一个头节点(哨兵位节点),具体的实现:
list() {_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;
}
6.print_container全局函数
这个函数已经在vector的模拟实现中讲到过了,就是为了方便测试的:
void Print_container(const Container& con) {for (auto& x : con) {cout << x << " ";}cout << endl;
}
迭代器部分(重点)
因为list的迭代器并不是原生指针了,所以是需要特殊处理的。我们在前面介绍使用部分的时候就说了,链表的迭代器其实是一个类,经过封装的。
思路
我们来厘清一下思路:
对于链表的迭代器,对其解引用其实就是获得节点的数据。对其重载++或–运算符,其实就是移动到下一个或上一个节点。判断两个迭代器是否相等也就是判断是否指同一个节点。
这不也是指针的逻辑吗?只不过对于某些操作我们需要进行重载后封装在类里面。这个类我们叫他list_iterator,在list内调用的时候对其typedef一下变为iterator即可。
但是现在还有一个问题就是,这样写当然可以。但是如果要实现const_iterator这么办?
那有的人就说,直接在前面加个const不就好了。但实际不然,这样是大错特错。有这样的想法完全是之前实现原生指针给误导了,我们之前在实现原生指针迭代器的时候,const_iterator就是const T *,iterator就是T *,看着就是在前面加const一样。
这是因为这使用指针实现的,这样直接在前面加const确实是能够限制指定的数据不被修改。但是我们现在实现的迭代器是一个类。这个类前面加const就代表这个类(本身)不能被修改,这就错误了。我们需要的是指向的数据不被改,也就是我们访问的时候数据返回的都是const引用或者const指针。
我们可以写出一个基础版本:
template<class T>
struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T> Self;list_iterator(Node* node = nullptr){_node = node;}T& operator*() {return _node->_data;}T* operator->() {return &(_node->_data);}Self& operator++() {_node = _node->_next;return *this;}Self& operator--() {_node = _node->_prev;return *this;}Self operator++(int) {list_iterator tmp(*this);_node = _node->_next;return tmp;}Self operator--(int) {list_iterator tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const Self& x) {return _node == x._node;}bool operator!=(const Self& x) {return _node != x._node;}Node* _node;
};
进一步考虑
对于const_iterator,它和普通迭代器的区别也就是访问的时候返回值做了特殊处理。其他逻辑完全是一样的。如果按照上面那个思路去写,那会导致我们需要写两遍极其类似的代码。非常冗余。
我们学过模板,模板就是为了这种情况而生的,我们也没有办法能够让编译器自行来决定返回值带不带const呢?
当然是可以的,我们可以多声名几个模板参数嘛:
template<class T, class Ref, class Ptr>
//T代表数据类型(节点中存储的数据
//Ref代表对数据的引用 区分const和非const
//Ptr为指向数据的指针 也是区分const和非const
struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;
};
我们来看看基本结构:
我们把返回的引用变为Ref参数,返回的指针变为Ptr参数。具体的返回值就看我们传什么。
如果我们显示实例化:
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
这样子不就能区分迭代器的不同了吗?
所以哦我们也发现,其实参数也是可以传给声名的模板的参数的。这点早已说过。因为模板参数列表和函数参数列表是很像的,只不过一个传具体数据,一个传类型。只不过类型的推导就是依靠编译器去做,走的是按需实例化的操作。
最终代码
template<class T, class Ref, class Ptr>
struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;list_iterator(Node* node = nullptr){_node = node;}Ref& operator*() {return _node->_data;}Ptr operator->() {return &(_node->_data);}Self& operator++() {_node = _node->_next;return *this;}Self& operator--() {_node = _node->_prev;return *this;}Self operator++(int) {list_iterator tmp(*this);_node = _node->_next;return tmp;}Self operator--(int) {list_iterator tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const Self& x) {return _node == x._node;}bool operator!=(const Self& x) {return _node != x._node;}Node* _node;
};
这里还是需要写一下构造函数的。因为我们需要让迭代器指向具体的节点,其本质也就是让迭代器这个类内部的指针指向具体的节点。所以我们是需要将节点的指针赋值给类里面的指针的。所以我们需要写一个构造函数。
operator->的重载
很多人看不明白,为什么要声名这个运算符,也不明白为什么写的如此奇怪。
这是因为,链表中的_data数据类型很可能不是内置类型,可能是struct这种自定义类型。那我们可能希望使用->来获取其内部的数据。
struct AA {AA(int a = 1, int b = 1) :_a(a),_b(b){}int _a;int _b;
};list<AA> lta;
lta.push_back({ 1,2 });
lta.push_back({ 3,4 });
lta.push_back({ 5,6 });
lta.push_back({ 7,8 });ita = lta.begin();
while (ita != lta.end()) { cout << ita->_a << ita->_b << " "; ++ita;
}
我们希望达成这个效果。
我们回忆一下这个操作符的使用。使用在结构体的指针的,通过箭头来访问数据。也就是说:箭头的左边是指针,右边是数据。
所以我们重载这个运算符返回指针是很合理的。但是不应该是这么写吗:
ita.operator->() - >_a
,这样子才更符合逻辑。
这里就得讲到特殊情况了,我们希望达到的效果是直接通过重载运算符就能直接使用,就像上面代码展示的那个情况一样。编译器是做了优化的,即自动省略,即不能写两个->,写一个即可,这一点我们需要特别注意。
总结
迭代器部分就是这些要注意的内容,它的方式是很新颖的,我们这是刚学习,所以需要多花时间进行理解。
begin和end
实现了迭代器后,我们就需要提供迭代器的相关接口:
//iterator//
iterator begin() {return _head->_next;
}
iterator end() {return _head;
}
const_iterator begin() const{return _head->_next;
}
const_iterator end() const{return _head;
}
//
我们这里走的是隐式类型转换,直接将节点指针通过转换赋值给迭代器。
当然不嫌麻烦地可以调用一下构造函数然后再返回。
访问接口
//access//
T& front() {assert(!empty());return _head->_next->_data;
}
T& back() {assert(!empty());return _head->_prev->_data;
}
const T& front() const{assert(!empty());return _head->_next->_data;
}
const T& back() const{ assert(!empty());return _head->_prev->_data;
}
//
修改操作
iterator insert(iterator pos, const T& val) {Node* newnode = new Node(val);Node* prev = (pos._node)->_prev, *next = pos._node;prev->_next = newnode;newnode->_prev = prev;newnode->_next = next;next->_prev = newnode;++_size;return newnode;//隐式类型转换返回
}iterator erase(iterator pos) {assert(pos != end());Node* POS = pos._node;Node* prev = (pos._node)->_prev, *next = (pos._node)->_next;prev->_next = next;next->_prev = prev;delete POS; return next;
}void clear() {list<T>::iterator it = begin();while (it != end()) {it = erase(it);}_size = 0;
}void push_front(const T& val) {insert(begin(), val);
}void pop_front(){erase(begin());
}
有了迭代器,我们就能很轻松的在某个位置进行插入或者删除了。具体的流程参考以往的双向链表增删查改操作。
对于清空数据,尾删,头插,头删操作,我们都可以复用一下erase和insert的操作就可以了,这是十分简单的。
当然可以把尾插的操作也进行简单复用修改,我在这里就不做演示了。
默认成员函数
由于很多情况下的构造,都是需要进行创建头节点,故可专门的实现一个创建头节点的函数:
void HeadNode() {_head = new Node;_head->_prev = _head;_head->_next = _head;_size = 0;
}
这其实就是预处理的时候的默认构造函数。
构造函数:
//默认构造
list() {HeadNode();
}//构造出n个相同值的list
list(int n, const T& value = T()) {HeadNode();for (int i = 0; i < n; ++i) {push_back(value);}
}//拷贝构造
list(const list<T>& l) {HeadNode();typename list<T>::const_iterator it = l.begin();while (it != l.end()) {push_back(*it);++it;}
}//迭代器区间构造
template <class PushIterator>
list(PushIterator first, PushIterator last) { HeadNode();PushIterator it = first;while (it != last) {push_back(*it);++it;}
}//initializer_list构造 上面讲过
template <class T>
list(const initializer_list<T>& x) {HeadNode();typename initializer_list<T>::iterator it = x.begin();while (it != x.end()) {push_back(*it);++it;}
}
析构函数:
~list() {clear();delete _head;_head = nullptr;
}
可以把数据情况后再将指向链表的头节点的指针释放并置空。
赋值重载:
list<T>& operator=(list<T> tmp) {swap(tmp);return *this;
}
现代写法,这样子可以不用判断是否是自己给自己赋值。
相关文章:
c++STL——list的使用和模拟实现
文章目录 list的使用和模拟实现使用部分list的结构声名默认成员函数initializer_list容量和访问操作修改操作其他接口list的迭代器迭代器的种类 list的模拟实现明确基本结构预处理函数迭代器部分(重点)思路进一步考虑最终代码operator->的重载总结 begin和end访问接口修改操…...
交换机端口安全
端口安全 端口安全(PortSecurity)通过将接口学习到的动态MAC地址转换为安全MAC地址(包括安全动态MAC、安全静态MAC和Sticky MAC),阻止非法用户通过本接口和交换机通信,从而增强设备的安全性。 1、安全mac地址分类 安全动态MAC地址…...
【Oracle专栏】Oracle中的虚拟列
Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 1.背景 在EXP方式导出时,发现 出现如下提示 EXP-00107: virtual column 不支持,因此采用expdp方式导出。于是本文针对oracle虚拟列进行简单介绍。 2. 相…...
shell 正则表达式与文本处理器
目录 前言 一、正则表达式 (一)定义与用途 (二)基础正则表达式 (三)基础正则表达式元字符 (四)扩展正则表达式 二、文本处理器:Shell 编程的得力助手 ࿰…...
ZYNQ笔记(九):定时器中断
版本:Vivado2020.2(Vitis) 任务:使用定时器 (私有定时器) 中断 实现 LED(PS端) 定时1s亮灭翻转 目录 一、介绍 二、硬件设计 三、软件设计 四、效果 一、介绍 Zynq系列是Xilinx(现为AMD)推出的集成了AR…...
idea中运行groovy程序报错
我的项目是使用的 gradle 构建的。 在 idea 中运行Groovy的面向对象程序报错如下: Execution failed for task :Person.main(). > Process command G:/Program Files/jdk-17/jdk-17.0.12/bin/java.exe finished with non-zero exit value 1* Try: Run with --s…...
具身智能零碎知识点(四):联合嵌入预测架构(JEPAs)详解
联合嵌入预测架构(JEPAs)详解 联合嵌入预测架构(JEPAs)详解一、核心思想二、技术原理1. 核心组件2. 训练目标 三、与传统方法的对比四、具体实例例1:视频预测(如Meta的I-JEPA)例2:多…...
linux 搭建 dvwa 渗透测试环境
linux 安装 dvwa 1、分为4个部分,搭建dvwa渗透测试环境2、安装centos 7.63、安装apache http server4、安装mysql5、安装php6、运行dvwa 1、分为4个部分,搭建dvwa渗透测试环境 本文基于centos 7.6 搭建 dvwa 渗透测试环境 安装一个linux系统安装apache…...
C++项目 —— 基于多设计模式下的同步异步日志系统(4)(双缓冲区异步任务处理器(AsyncLooper)设计)
C项目 —— 基于多设计模式下的同步&异步日志系统(4)(双缓冲区异步任务处理器(AsyncLooper)设计) 异步线程什么是异步线程?C 异步线程简单例子代码解释程序输出关键点总结扩展:使…...
【Linux学习笔记】Linux的环境变量和命令行参数
【Linux学习笔记】Linux的环境变量和命令行参数 🔥个人主页:大白的编程日记 🔥专栏:Linux学习笔记 文章目录 【Linux学习笔记】Linux的环境变量和命令行参数前言一.环境变量1.1基本概念1.2常见环境变量1.3和环境变量相关的命令1…...
排序算法-快速排序
描述: 基准值选择:选取数组的最后一个元素 arr[high] 作为基准值 p。初始化索引:i 初始化为 low - 1,其作用是指向比基准值小的最后一个元素的索引。遍历数组:借助 for 循环从 low 到 high - 1 遍历数组。若当前元素 …...
软考高级系统架构设计师-第16章 数学与经济管理
【本章学习建议】 根据考试大纲,本章主要考查系统架构设计师单选题,预计考2分左右。主要是运筹学的计算问题,范围广、难度大,超纲题较多,不用深究。 16.1 线性规划 线性规划是研究在有限的资源条件下,如果…...
爱在冰川-慢就是快
【游资大佬の搞钱心法🔥|小白逆袭必看冰川语录真实案例‼️】 💡刚扒完爱在冰川的万字访谈 发现游资搞钱真的靠"反人性思维" 总结6条狠人法则真实案例 建议收藏反复背诵👇 1️⃣【周期为王】💫 "行情…...
Mac-VScode-C++环境配置
mac上自带了clang所以不是必须下载Homebrew 下面是配置文件(注释记得删一下) package.json {"name": "git-base","displayName": "%displayName%","description": "%description%",&quo…...
【JAVA EE初阶】多线程(1)
这样的代码,虽然也能打印hello thread,但是没有创建新的线程,而是直接在main方法所在的主线程中执行了run的逻辑 start方法,是调用系统api,真正在操作系统内部创建一个线程。这个新的线程会以run作为入口方法ÿ…...
PHP伪协议读取文件
借鉴php伪协议实现命令执行,任意文件读取_ctf php文件读取-CSDN博客 总结 在ctf中常用的有data:// , php://input , php://filter ,file:// php://input ,data://用来执行命令 1.php://input 的用法 http://127.0.0.1/include.php?filephp://input [P…...
动态调整映射关系的一致性哈希负载均衡算法详解
一、核心原理与设计要点 双重映射结构 一致性哈希负载均衡通过 哈希环 和 槽动态分配 实现双重映射关系: • 哈希环构建:将节点(物理或虚拟)和数据键(Key)通过哈希函数(如MD5、CRC32)…...
控制反转(IOC)和依赖注入(DI)
Target Retention Documented 元注解 Component 将类交给IOC容器管理,成为IOC容器中的bean Autowired 注入运行时所需要依赖的对象 因为Mabatis DAO层注解Reponsitory 基本不用了,现在Mapper层Mapper注解,这里的Mapper层相当于原来的DAO层…...
【每日八股】复习 MySQL Day1:事务
文章目录 复习 MySQL Day1:事务MySQL 事务的四大特性?并发事务会出现什么问题?MySQL 事务的隔离级别?不同事务隔离级别下会发生什么问题?MVCC 的实现原理?核心数据结构版本链构建示例可见性判断算法MVCC 可…...
【数据结构和算法】1. 数据结构和算法简介、二分搜索
本文根据 数据结构和算法入门 视频记录 文章目录 1. 数据结构和算法简介1.1 什么是数据结构?什么是算法?1.2 数据结构和算法之间的关系1.3 “数据结构和算法”有那么重要吗? 2. 二分搜索(Binary Search)2.1 算法概念2…...
4月19日记(补)算了和周日一块写了 4月20日日记
周六啊 昨天晚上又玩的太嗨了。睡觉的时候有点晚了,眼睛疼就没写日记。现在补上 实际上现在是20号晚上八点半了。理论上来说应该写今天的日记。 周六上午打比赛啦,和研究生,输了,我是替补没上场。没关系再练一练明天就可以变强…...
面试常用基础算法
目录 快速排序归并排序堆排序 n n n皇后问题最大和子数组爬楼梯中心扩展法求最长回文子序列分割回文串动态规划求最长回文子序列最长回文子串单调栈双指针算法修改 分割回文串滑动窗口栈 快速排序 #include <iostream> #include <algorithm>using namespace std;…...
微服务与 SOA:架构异同全解析与应用指南
微服务和 SOA(面向服务的架构)是两种不同的软件架构风格,它们在很多方面存在相似之处,但也有一些区别。以下是对它们的详细介绍: 一、概念 1.微服务 微服务架构将一个大型应用程序拆分成多个小型、独立的服务&#…...
Dijkstra 算法入门笔记 (适用于算法竞赛初学者) - C++ 代码版
目录 算法是做什么的?核心思想:贪就完事了!算法前提:不能有负权边!需要哪些工具?(数据结构)算法具体步骤关键操作:松弛 (Relaxation)两种实现方式 (C 代码) 朴素版 Dijkstra (O(V^2))堆优化版 …...
脑影像分析软件推荐| GraphVar介绍
目录 1.软件界面 2.工具包功能简介 3.软件安装注意事项 1.软件界面 2.工具包功能简介 GraphVar是一个用户友好的 MATLAB 工具箱,用于对功能性大脑连接进行全面的图形分析。这里我们介绍了该工具箱的全面扩展,使用户能够无缝探索跨功能连接测量的可轻…...
如何优雅地实现全局唯一?深入理解单例模式
如何优雅地实现全局唯一?深入理解单例模式 一、什么是单例模式? 单例模式是一种创建型设计模式,旨在确保一个类只有一个实例,并为该实例提供全局访问点,从而避免全局变量的命名污染,并支持延迟初始化Wiki…...
【Flutter】使用LiveKit和Flutter构建实时视频聊天应用
引言 在当今快速发展的数字世界中,实时视频通信已成为许多应用程序的核心功能。无论是远程工作、在线教育还是社交网络,高质量的实时视频功能都至关重要。LiveKit作为一个开源的WebRTC解决方案,提供了构建可扩展实时音视频应用所需的一切工具…...
Android Jetpack Compose 状态管理解析:remember vs mutableStateOf,有啥不一样?为啥要一起用?
🌱《Jetpack Compose 状态管理解析:remember vs mutableStateOf,有啥不一样?为啥要一起用?》 在 Jetpack Compose 的世界里,UI 是响应式的。这意味着当状态发生变化时,UI 会自动重组࿰…...
QT6 源(37):界面组件的总基类 QWidget 的源码阅读(下,c++ 代码部分)
(1) QT 在 c 的基础上增加了自己的编译器,以支持元对象系统和 UI 界面设计,有 MOC 、 UIC 等 QT 自己的编译器。本节的源代码里,为了减少篇幅,易于阅读,去除了上篇中的属性部分, 上篇…...
进程与线程:01 CPU管理的直观想法
多进程图像与操作系统核心 好从今天开始,我们就要开始学习操作系统,最核心的图像是多进程图像。前面我们讲过,多进程图像对操作系统来说非常重要,它是操作系统的核心图像。明白了它以后,对于理解操作系统的一大部分内…...
19. git reflog
基本概述 git reflog 的作用是:查看本地仓库的引用日志(reference log),例如分支、HEAD等。它可以帮助你找回误删的提交、恢复被覆盖的分支,或回溯操作历史。 基本用法 1.查看完整的reflog git reflog这会显示所有…...
C语言 —— 铭纹织构未诞之镜 - 预处理详解
目录 1. 什么是预处理(预编译) 编辑 2. 预定义符号 3. #define 定义常量 4. #define定义宏 5. 带副作用的宏参数 6. 宏替换的规则 7. 宏和函数的对比 8. #和## 8.1 #运算符 8.2 ## 运算符 9. #undef 10. 条件编译 1. 什么是预处理…...
Linux 文件系统目录结构详解
Linux 文件系统目录结构详解 Linux 文件系统遵循 Filesystem Hierarchy Standard (FHS) 标准,定义了各个目录的用途和文件存放规则。无论是开发者、运维工程师还是普通用户,理解这些目录的作用都至关重要。本文将全面解析 Linux 的目录结构,…...
2025-4-19 情绪周期视角复盘(mini)
我本以为市场进化规律下产生龙头战法的末法时代导致情绪周期逐步混乱或者说混沌期漫长。所谓的市场进化无非也是量化的发展和各类资金逐步量化化的充分博弈下的结果。通过逐步向上思考发现,不仅仅我们的市场是处于一个存量的时代背景,重要的是我们的思维…...
-实用类-
1. API是什么 2.什么是枚举 !有点类似封装! 2.包装类 注意: 1.Boolean类构造方法参数为String类型时,若该字符串内容为true(不考虑大小写),则该Boolean对象表示true,否则表示false 2.当包装类构造方法参…...
Unity3D仿星露谷物语开发36之锄地动画2
1、目标 当角色锄地之后,地面会显示开垦后的样貌。 2、思路 上一篇中,虽然角色dig了hoe,同时grid属性也改变了,但是没有任何可视化的反馈。我们现在将添加新的功能,动态地将"dug ground"瓷砖添加到"…...
【备考高项】模拟预测题(一)案例分析及答案详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题一【问题 1】(10分)【问题 2】(5分)【问题 3】(4分)【问题 4】(6分)试题二【问题 1】(12分)【问题 2】(3分)【问题 3】(6分)【问题 4】(4分)试题三【问题 1】(4分)【问题 2】(10分)【问题 3】…...
7、sentinel
控制台访问地址:http://localhost:8080/ 依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>配置文件 spring:cloud:sentinel:transpo…...
状态管理最佳实践:Provider使用技巧与源码分析
状态管理最佳实践:Provider使用技巧与源码分析 前言 Provider是Flutter官方推荐的状态管理解决方案,它简单易用且功能强大。本文将从实战角度深入讲解Provider的使用技巧和源码实现原理,帮助你更好地在项目中应用Provider进行状态管理。 基…...
INFINI Console 系统集群状态异常修复方案
背景介绍 运行 INFINI Console 1.29.0 和 1.29.1 版本 的用户在 新初始化 平台后可能会遇到一个特定问题。如果后台的系统 Easysearch/Elasticsearch 集群(存储 Console 元数据的集群,通常名为 .infini_cluster 或类似名称)包含超过一个节点…...
Spring Boot自动装配原理(源码详细剖析!)
什么是Spring Boot的自动装配? 自动装配是Spring Boot的核心功能,它能够根据应用程序的依赖和配置自动配置Spring。这意味着我们只需要添加大量的依赖,Spring Boot就能自动完成配置,减少了人工配置的工作量。 自动装配的核心注…...
大数据驱动的高效能量管理:智能优化与实践探索
大数据驱动的高效能量管理:智能优化与实践探索 在全球能源需求不断增长的背景下,如何提高能源利用效率成为各行业关注的焦点。传统的能源管理方式往往依赖固定规则和人工监测,难以适应复杂多变的应用场景。而大数据技术的兴起,为能量管理提供了新的解决方案——通过数据驱…...
《银行数字化风控-业务于实战》读后知识总结
引言 在金融科技高速发展的今天,银行的风控体系正经历从“人工经验驱动”向“数据智能驱动”的深刻变革。《银行数字化风控-业务于实战》一书以实战为导向,系统性地剖析了数字化风控的核心逻辑、技术实现路径及业务落地方法论。作为深耕风控领域多年的从…...
初级达梦dba的技能水准
在x86环境(windows、linux)安装单机软件,安装客户端创建过至少20套数据库,优化参数并更新过正式许可会用逻辑导出导入以及dmrman备份了解manager工具的使用配置sqllog日志,并能解释输出内容能够分析因磁盘空间不足、内…...
C++初阶-类和对象(中)
目录 1.类的默认成员函数 2.构造函数(难度较高) 编辑 编辑 编辑 3.析构函数 4.拷贝构造函数 5.赋值运算符重载 5.1运算符重载 5.2赋值运算符重载 6.取地址运算符重载 6.1const成员函数 6.2取地址运算符重载 7.总结 1.类的默认成员函数…...
Linux网络UDP与TCP
基础知识 传输层 负责数据能够从发送端传输接收端。 端口号(Port)标识了一个主机上进行通信的不同的应用程序; 在 TCP/IP 协议中, 用 “源 IP”, “源端口号”, “目的 IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n 查看); 端口号范…...
23、.NET和C#有什么区别?
1、定义与范畴 .NET 定义 .NET 是一个由微软开发的开发平台(Platform),它提供了一套完整的工具、库和运行时环境,用于构建各种类型的应用程序。 范畴 包括 .NET Framework、.NET Core(现称为 .NET 5 及以上版本&a…...
Qt6离线安装过程
Qt6离线安装过程 说明解决方案联网笔记本安装qt6拷贝到离线电脑修改qtenv2.bat文件 说明 现在qt6已经不能通过离线的方式下载安装包安装了,只能通过登陆的方式在线安装,但是,又有离线安装运行的需求,那么怎么办呢?请跟…...
如何在 Go 中创建和部署 AWS Lambda 函数
AWS Lambda 是一个无服务器计算平台,您可以使用自己喜欢的编程语言编写代码,无需担心设置虚拟机。 您只需为 Lambda 函数的调用次数和运行时间(毫秒)付费。 我们大多数人都了解 JavaScript 和 Python,但它们的内存效率…...
【后端】【Django】Django 模型中的 `clean()` 方法详解:数据校验的最后防线
Django 模型中的 clean() 方法详解:数据校验的最后防线 在 Django 的模型系统中,我们经常使用字段级别的校验器(validators)来约束某个字段的取值范围。但当校验逻辑涉及多个字段之间的关系时,字段级别校验就无能为力…...