【C++】list 相关接口的模拟实现
list 模拟实现
- 回顾
- 准备
- 构造析构函数的构造
- 构造方法
- 析构方法
- 赋值运算符重载
- 容量相关接口
- 元素获取
- 元素修改相关接口
- push 、pop
- insert
- erase
- 清空
- 交换
- 迭代器 **(重点)
- 迭代器基本概念
- 迭代器模拟实现
回顾
在上一篇博客中我们大致了解了 list 相关接口的使用方法并进行了一系列的测试练习,那么这一小节就来模拟实现一些接口吧~
list 是一个带头双向循环的链表:

在模拟实现这些接口之前我们需要先进行节点信息的创建

template<class T> //采用模板------可以定义不同数据类型的 list 链表struct ListNode {ListNode(const T&value = T()) //构造方法:prev(nullptr), next(nullptr), val(value){}ListNode* prev; //前驱节点ListNode* next; //后继节点T val; //值域};
准备
为了与类中 list 进行区分,我们可以自定义一个命名空间,在该命名空间内部来进行接口的模拟实现:
namespace xx {template<class T>struct ListNode //创建节点信息{ListNode(const T& value = T()):prev(nullptr), next(nullptr), val(value){}ListNode* prev;ListNode* next;T val;};
}
为了便于测试,我们在自己的命名空间中定义一个打印信息的函数:
template<class T>void PrintList(const list<T>& L){for (auto e : L)cout << e << " ";cout << endl;}
定义链表信息:
//定义链表信息template<class T>class List {typedef ListNode<T> Node; //取别名public://构造方法//析构方法//接口模拟实现private:Node* _head; //头节点};
构造析构函数的构造
构造方法
由于 list 链表是一个带头的双向循环链表,并且已知 _head 头节点,因此在创建链表时候要注意 prev 与 next 的指向
创建任何类型的构造方法之前,我们首先要定义出一个带头结点的空链表:

void CreateList(){//创建头节点_head = new Node();_head->next = _head->prev = _head; //构造循环}
(1)构造空链表
创建一个空链表也就是创建一个只有头节点的链表,因此我们可以直接在构造方法内部调用头节点创建的函数即可:
List() {CreateList();}
(2)构造具有 n 个值为 val 的链表
构造一个具有 n 个相同节点的链表,我们可以复用尾插(或头插都可以)来进行
List(int n, const T& val = T()){CreateList(); //首先创建头节点信息//构造 n 个值为 val 的节点,我们可以采用 n 次尾插来进行for (int i = 0; i < n; ++i) {push_back(val);}}
注意:

(3)区间构造
template<class Iterator>List(Iterator first, Iterator last){CreateList(); //创建头节点auto it = first;while (it != last) {push_back(*it); //同样采用多次尾插方法来构造,*it 代表获取节点值域++it; //迭代器 it 的自增------表示获取下一个节点的位置}}
注意:
在进行参数构造时候,我们参数类型定义均为模板类型 Iterator ,因此对于上述构造 n 个值相同的节点信息的链表,倘若我们使用 size_t 类型来传入 n 参数,会使得 编译器在进行类型推演时候默认为两个变量类型不一致导致编译器调用错误,这也就解释了为什么将 n 变量类型定义为 int 的原因。
(4)拷贝构造
用一个已有的链表来创建新的链表信息:
List(const List<T>& L){CreateList(); //创建头节点for (auto e : L) {push_back(e); //遍历 L 链表同时将遍历到的节点值插入到 新构造的链表中}}
析构方法
~List(){clear(); //清空所有节点信息delete _head; //删除头节点_head = nullptr;}
赋值运算符重载
我们采用现代版的写法,传递的参数为值类型的参数(会调用一次拷贝构造函数来构造出该参数),然后将该参数与 this 指针指向的内容进行交换即可(可以参考深浅拷贝 : 添加链接描述):
List<T>& operator=(const List<T> L) //以值的方式传参会调用一次拷贝构造方法来创建参数(具有自己独立的地址空间),并在函数调用结束自动销毁临时空间{this->swap(L); //交换之后,L 中空间改变为原来的 this 空间,并在函数调用结束自动进行了析构销毁return *this; }
容量相关接口
由于 list 链表结构为带头双向循环链表,我们只知道 头节点 _head 的信息,因此在进行容量判断时候需要对整个链表进行遍历------注意遍历条件!!!
(1)size 接口
size_t size()const{//统计节点个数int count = 0;Node* cur = _head->next;while (cur != _head) { //循环链表的 next 指针域一定是不为空的,因此遍历条件应该是判断是否回到头节点位置++count;cur = cur->next;}return count;}
(2)判空
循环链表为空时,也就是只有一个头节点存在,故判断条件应为:
bool empty()const{return _head == _head->next;}
(3)resize 修改有效节点的个数
当缩小有效节点个数为 newsize 时,我们需要将 newsize 之后的节点进行删除;

当扩大有效节点个数为 newsize 时,我们需要在原有的节点尾部插入 newsize-oldsize 个新的节点,且新节点的值为 val ;

void resize(size_t newsize,const T& val=T()){size_t oldsize = size(); //统计现有的节点个数//当缩小节点if (newsize < oldsize) {for (int i = newsize; i < oldsize; ++i)pop_back(); //进行尾删}else {//增大节点个数for (int i = oldsize; i < newsize; ++i)push_back(val); //进行尾插操作}}
元素获取
(1)获取首节点信息
//非 const 类型表示可以对节点信息进行修改操作T& front(){//获取首节点值return _head->next->val; //_head 为头节点,它所存储的数据不是有效数据}const T& front()const //只读{//获取首节点值return _head->next->val; //_head 为头节点,它所存储的数据不是有效数据}
(2)获取尾节点信息
T& back(){return _head->prev->val; //_head 为头节点,它的前驱节点为尾节点}const T& back()const{return _head->prev->val; //_head 为头节点,它的前驱节点为尾节点}
元素修改相关接口
push 、pop
由于头部或尾部插入新元素都可以直接复用 insert 任意位置插入方法,因此我们这里直接调用 insert 接口来实现:
void push_front(const T& val){insert(begin(), val); //begin() 指向首节点,因此进行头插 ,直接在第一个节点前插入新节点 }void push_back(const T& val){insert(end(), val); //end() 指向头节点,因此进行尾插,直接在 end() 之前插入新节点}
由于头部或尾部删除之间可以调用 erase 任意位置删除,所有这里也直接调用erase 接口:
void pop_front(){if (empty())return; //链表为空,不能进行删除//头删------删除首节点erase(begin()); //删除 begin() 位置节点}
void pop_back(){if (empty())return; //链表为空,不能进行删除//尾删------删除最后一个节点erase(end()); }
注意看这两段代码是否有问题?
能这么问,当然是有问题啦~
具体什么问题我们来看看:

仔细观察 begin() 与 end() 的位置,我们发现 begin() 指向的就是第一个节点的位置,因此进行头删时候直接可以进行删除,并且删除之后并不会影响之后元素的访问
而 end() 指向的是头节点的位置,而我们要删除的是最后一个有效节点,也就是 end() 的前一个节点位置,因此此处的尾删函数的实现是不对的,具体修改如下:
void pop_back(){if (empty())return; //链表为空,不能进行删除//尾删------删除最后一个节点auto pos = end();--pos; //迭代器向前移动到尾节点位置erase(pos); //删除 end() 前一个位置节点}
list 接口测试中,我们谈到 在插入节点时不会导致迭代器的失效,而在删除元素时候会引发迭代器失效,但是不会导致迭代器位置之后的元素的访问,因此一般在删除节点操作之后我们会接收返回值的信息来防止迭代器失效。
(list 接口使用中我们已经进行了测试,忘记的宝子参考:添加链接描述)
insert
在任意位置进行元素的插入,首先我们需要创建出一个新节点信息,然后对节点的指向进行修改即可:
Iterator insert(Iterator pos, const T& val){//在给定的节点位置 pos 之前进行新节点的插入Node* newnode = new Node(); //创建新节点newnode->val = val;newnode->next = pos;newnode->prev = pos->prev;pos->prev->next = newnode;pos->prev = newnode;return newnode; //返回新插入的节点位置}
画个图来理解一些吧~

erase
Iterator erase(Iterator pos){//删除给定的 pos 位置的节点if (pos == _head)return _head; //头节点不能进行删除Node* cur = pos->next; //记录下一个节点位置//修改指向cur->prev = pos->prev;pos->prev->next = cur;delete pos;return cur; //返回删除节点的位置-------此时迭代器 pos 已经失效了}
删除节点与插入节点过程很类似,注意修改指向的顺序,读者可以自己画画图来理解
清空
clear 是将 list 链表中所有节点进行删除,我们可以采用头删的方法来进行:
void clear(){Node* cur = _head->next; //进行头删while (cur != _head) {cur->next->prev = _head;_head->next = cur->next;delete cur;cur = _head->next; //修改要删除的节点位置}_head->next = _head->prev = _head; //最后删除头节点}
交换
void swap(List<T>& L) {std::swap(_head, L._head); //之间采用全局 swap 函数,交换头节点位置即可}
迭代器 **(重点)
迭代器基本概念
string 、vector 还有现在学习的 list 当中,我们都有使用到迭代器,那么迭代器到底是什么呢?
在前边的学习中我们提到 ,迭代器可以看作是原生态的指针类型,在模拟接口中我们发现,定义的迭代器变量我们可以对其进行以下操作:
1)解引用 *
2)自增自减
3)迭代器的比较
例如在遍历时我们使用到的迭代器(vector 容器下的迭代器):

因此,我们在模拟实现迭代器时候也要能够进行这三种基本操作。
接下来,我们来模拟实现一些迭代器吧~
迭代器模拟实现
在前边模拟实现 string 以及 vector 时,我们将迭代器处理为原生态的指针类型,发现在整个模拟接口测试过程中是没有任何问题的,说明在之前的模拟实现中 ,迭代器就是被当作指针来进行处理的,那么在 list 中我们是否也可以这么处理?
同样将迭代器看作是原生态的指针类型:
typedef Node* Iterator;
当我们进行迭代器的解引用以及自增自减:
auto it = begin();while (it!=end()){cout << *it << " ";++it;}cout << endl;

那么为什么在 string 和 vector 当中,迭代器可以被处理成为 原生态的指针并且可以正常使用,在这里就不行了呢?(大家可以思考思考)
解答
回顾我们在学习 string 和 vector 容器中,使用的是顺序结构,也就是说所采用的容器空间是连续的,因此进行自增自减可以直接获取到它相邻的前后元素位置
而在 list 中,我们知道 list 是多个节点构成的链表,而每一个节点都是在使用时才创建(new)出来的,然后将创建的新节点链接到我们的链表当中,由此可见 list 当中的结构并不一定连续的,故不能直接进行迭代器的自增自减操作;
其次,假如将迭代器定义为原生态的指针类型 Node* ,在进行解引用操作时候取到的类型是 Node 而并非是当前迭代器指向位置的值域(val)信息,因此迭代器不能被简单的处理为 原生态的指针;
再者,我们提到迭代器可以进行比较,而原生态指针类型定义出来的迭代器类型都是一致的,类型间如何进行比较?
由此可见,在 list 中模拟实现迭代器时,我们首先需要对迭代器进行封装操作:
正向迭代器的封装
//封装迭代器类-------------要求能够使用指针解引用,并能够进行自增自减操作,并能够进行迭代器的比较template<class T,class Ptr,class Ref>class ListIterator {typedef ListNode<T> Node;public:typedef Ptr Ptr; //类型重命名,指明当前 Ptr 是类型而非变量typedef Ref Ref;typedef ListIterator<T, Ptr, Ref> Self; //因为节点当中包含值域和指针类型,因此我们设计迭代器时需要能够返回不同的类型的数据信息public:ListIterator(Node* pNode = nullptr) :_pNode(pNode){}// 解引用Ref operator*() //返回值信息应该是节点当中的数据类型{return _pNode->val; //解引用也就是获取当前节点中的值域信息}Ptr operator->() //返回值信息为当前节点数据域的地址{return &(_pNode->val); //在自定义类型中体现很明显}/// 自增自减Self& operator++() //返回值为迭代器自身类型{_pNode = _pNode->next; //自增操作也就是将迭代器的位置向后移动---------获取下一个位置的节点return *this;}Self operator++(int) //后置++{Self tmp(*this); //后置++:先使用,后对其进行修改_pNode = _pNode->next;return tmp;}Self& operator--(){_pNode = _pNode->prev; //自减操作也就是将迭代器向前移动--------获取前一个位置的节点return *this; }Self operator--(int) //后置--{Self tmp(*this);_pNode = _pNode->prev;return tmp;}迭代器能够比较bool operator!=(const Self& s)const{return _pNode != s._pNode;}bool operator==(const Self& s)const{return _pNode == s._pNode;}Node* _pNode;};
反向迭代器的封装
在前边我们介绍到,正向迭代器 begin() 的位置与反向迭代器 rend() 位置相同,正向迭代器 end() 位置与反向迭代器 rbegin() 位置相同,两种迭代器正好相反,因此进行反向迭代器封装我们可以复用正向迭代器的方法:
template<class Iterator>struct ListReverseIterator {//typename 是为了说明 Ref Ptr 是属于正向迭代器 Iterator 中的类型而不是静态成员变量typename typedef Iterator::Ref Ref;typename typedef Iterator::Ptr Ptr;typedef ListReverseIterator<Iterator> Self;public:ListReverseIterator(Iterator it) :_it(it){}Ref operator*(){Iterator tmp = _it; //rbegin() 指向 _head 头节点,不需要进行打印,因此 解引用 获取到的是第一个节点的值域,即需要将 rbegin 向后(++操作)移动-----------即需要将正向迭代器的 end() 向前移动(--操作)--tmp;return *tmp; //返回值是节点数据域中的数据类型}Ptr operator->() //获取当前节点数据域的地址信息{return &(_it->pNode->val);}Self& operator++() {--_it; //反向迭代器的前置++ 等价于 正向迭代器的前置--return *this;}Self operator++(int){_it--; //反向迭代器后置++ 等价于 正向迭代器的后置--return *this; }Self& operator--() {++_it; //反向迭代器的前置-- 等价于 正向迭代器的前置++return *this;}Self operator--(int){_it++; //反向迭代器后置-- 等价于 正向迭代器的后置++return *this;}///bool operator!=(const Self& s)const{return _it != s._it;}bool operator==(const Self& s)const{return _it == s._it;}Iterator _it;};
封装之后我们需要在我们自己定义的 List 类中进行声明:
typedef ListIterator<T,T*,T&> Iterator; //迭代器封装之后要能够返回不同的数据类型(节点值域,节点的地址等)typedef ListIterator<T, const T*, const T&> const_Iterator; //const 迭代器-----只读typedef ListReverseIterator<Iterator> reverse_iterator;typedef ListReverseIterator<const_Iterator> const_reverse_iterator; //const 迭代器-----只读
则迭代器的模拟接口实现如下:
Iterator begin() //begin() 获取首节点的位置{return Iterator(_head->next); //返回值类型的临时对象}Iterator end(){return Iterator(_head);}//反向迭代器reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}//const 迭代器const_Iterator begin()const{return const_Iterator(_head->next);}const_Iterator end()const{return const_Iterator(_head);}const_reverse_iterator rbegin()const{return const_reverse_iterator(end());}const_reverse_iterator rend()const{return const_reverse_iterator(begin());}
由于我们对迭代器进行了封装而并非原始方式定义为原生态的指针类型,因此采用迭代器来进行插入删除元素的函数也要进行相应的修改操作:
Iterator insert(Iterator Itpos, const T& val){//在 pos 位置之前进行插入Node* pos = Itpos._pNode; //此时的迭代器 Itpos 所指向的并不是当前节点的位置信息(而是包含三种数据结构的信息),因此需要进行取节点操作//后续的代码与前边是相同的Node* newnode = new Node();newnode->val = val;newnode->prev = pos->prev;newnode->next = pos;pos->prev->next = newnode;pos->prev = newnode;return newnode;}Iterator erase(Iterator Itpos){//删除 pos 位置的元素Node* pos = Itpos._pNode;//此时的迭代器 Itpos 所指向的并不是当前节点的位置信息,因此需要进行取节点操作//后续的代码与前边是相同if (pos == _head)return pos;Node* cur = pos->next;pos->prev->next = cur;cur->prev = pos->prev;delete pos;return cur;}
好了,今天的学习就到这里啦
对于迭代器部分的理解可能比较困难,读者可以自己在代码当中多调试调试看看

关于本节具体的代码实现请参考(mylist 文件):添加链接描述
有任何问题欢迎评论留言哦!
相关文章:
【C++】list 相关接口的模拟实现
list 模拟实现回顾准备构造析构函数的构造构造方法析构方法赋值运算符重载容量相关接口元素获取元素修改相关接口push 、popinserterase清空交换迭代器 **(重点)迭代器基本概念迭代器模拟实现回顾 在上一篇博客中我们大致了解了 list 相关接口的使用方法…...
快速找到外贸客户的9种方法(建议收藏)
所有外贸企业想要做好外贸出口的头等大事,就是要快速的找到优质的外贸客户和订单,没有订单的达成,所有的努力都是图劳,还有可能会陷入一种虚假的繁荣,每天都很忙,但是没有结果。今天,小编就来分…...
TCP状态转换
欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 TCP状态转换专栏:《Linux从小白到大神》《网络编程》 TCP状态转换示意图如下 针对上面的示…...
3500年里,印度被11个文明征服
转自:3500年里,印度被11个文明征服,如今看似统一,实际上却是缝合怪 (qq.com)今天的印度是亚洲第二大国,南亚第一大国,世界第二人口大国。如果我们将时间线拉长,纵观历史的长河,就会惊…...
Java编程问题top100---基础语法系列(一)
Java编程问题top100---基础语法系列一一、Java 操作符实质二、将InputStream转换为String使用IOUtils自己写轮子三、将数组转换为List四、如何遍历map对象使用For-Each迭代entries(方法一)使用For-Each迭代keys和values(方法二)使…...
【C#基础】C# 异常处理操作
序号系列文章6【C#基础】C# 常用语句讲解7【C#基础】C# 常用数据结构8【C#基础】C# 面向对象编程文章目录前言1,异常的概念2,处理异常3,自定义异常4,编译器异常结语前言 🌷大家好,我是writer桑,…...
系统分析师---操作系统思维导图
进程管理(5星) 进程与线程:共享:内存地址空间、代码、数据、文件等不能共享:独立的cpu运行上下文和栈指针、寄存器 信号量与PV操作:信号量,一种特殊的变量分为:信号量可以表示资源数…...
Linux | Ubuntu20.04系统使用命令从移动硬盘/U盘拷贝文件到服务器上
*确认自己移动硬盘、U盘的格式,本文为exfat格式STEP1:把移动硬盘插到Ubuntu系统的主机上查看disk默认位置#查看移动硬盘/U盘在哪个位置命令 fdisk -l #查询后出现了包含电脑系统的所有硬盘查看最后的位置,我的显示为Device, 位置为 /dev/sdb1…...
【经验总结】10年的嵌入式开发老手,到底是如何快速学习和使用RT-Thread的?
【经验总结】一位近10年的嵌入式开发老手,到底是如何快速学习和使用RT-Thread的? RT-Thread绝对可以称得上国内优秀且排名靠前的操作系统,在嵌入式IoT领域一直享有盛名。近些年,物联网产业的大热,更是直接将RT-Thread这…...
一起Talk Android吧(第五百零九回:约束布局中的组功能一)
文章目录功能介绍使用方法GroupLayer对比总结各位看官们大家好,上一回中咱们说的例子是"多层布局功能",这一回中咱们说的例子是"约束布局中的组功能"。闲话休提,言归正转, 让我们一起Talk Android吧! 功能介…...
2023安徽省“中银杯”职业技能大赛“网络安全” 项目比赛任务书
2023安徽省“中银杯”职业技能大赛“网络安全” 项目比赛任务书2023安徽省“中银杯”职业技能大赛“网络安全” 项目比赛任务书A模块基础设施设置/安全加固(200分)A-1:登录安全加固(Windows, Linux)A-2:Ngi…...
观测云产品更新|新增用户访问监测自动化追踪;新增 CDN 质量分析;新增自定义查看器导航菜单等
观测云更新 用户访问监测优化 新增用户访问监测自动化追踪 用户访问监测新增自动化追踪,通过“浏览器插件”的实现方式,使用浏览器记录用户访问行为,创建无代码的端到端测试。更多详情可参考文档【 自动化追踪 】https://docs.guance.com/…...
大数据技术生态全景一览
大数据技术生态全景一览大数据平台ETL数据接入大数据平台海量数据存储大数据平台通用计算大数据平台各场景的分析运算分布式协调服务任务流调度引擎大数据平台ETL数据接入 大数据有很多的产品,琳琅满目。从架构图上就能看出产品很多。这些产品它们各自的功能是什么…...
CI/CD | 深入研究Jenkins后,我挖掘出了找到了摆脱低效率低下的方法
在本系列的第一篇文章中,您已经了解了一些关于如何管理Jenkins的内容,主要是为无序的人带来秩序。在这篇文章中,我将更深入地探讨我效率低下的问题,提出我们工作流中一些安全性、治理和合规性的挑战。这不仅仅是你在网站上或展览横…...
刷LeetCode
文章目录滑动窗口算法1 涉及知识点 :unordered_set 容器2 参数详情3 例题滑动窗口算法 滑动的窗口,每次记录下窗口的状态,再找出符合条件的窗口使用滑动窗口减少时间复杂度 1 涉及知识点 :unordered_set 容器 说明:…...
Spring 大白话系列:工厂
Spring 大白话系列:工厂 “工厂模式,大家都很熟悉了。说到底,就是解除创建对象和使用对象之间的耦合。这东西没啥啊。” 教室里,老师傅听到小明在嘀嘀咕咕的。老师走过去问: “有什么问题呢小明同学?” 小…...
喜讯!华秋电子荣获第六届“蓝点奖”十佳分销商奖
2 月 25 日,由深圳市电子商会主办的2023 中国电子信息产业创新发展交流大会暨第六届蓝点奖颁奖盛典在深圳隆重举行。 图:华秋商城渠道总监杨阳(右三) 深圳市电子商会连续六年举办“蓝点奖”评选活动,旨在表彰对电子信…...
Linux概述
1:Linux概述1.1:操作系统常见操作系统有:Windows、MacOS、Linux。名称描述Windows微软公司研发的收费操作系统。分为两类:用户操作系统、Server操作系统。用户操作系统:win 95、win 98、win NT、win Me、win xp、vista…...
中级嵌入式系统设计师2015下半年上午试题及答案解析
中级嵌入式系统设计师2015下半年上午试题 单项选择题 1、CPU是在______结束时响应DMA请求的。 A.一条指令执行 B.一段程序 C.一个时钟周期 D.一个总线周期 2、虚拟存储体系由______两级存储器构成。 A.主存-辅存 B.寄存器-Cache C.寄存器-主存...
华为OD机试模拟题 用 C++ 实现 - 删除指定目录(2023.Q1)
最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明删除指定目录题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为…...
基于离散化方法的三维土豆运动微波加热案例:参数化扫描与继承解算子实现离散化
基于离散化方法三维土豆运动微波加热的案例——第一种方法参数化扫描和继承解的算子实现离散化 离散化方式是最常见的实现运动仿真的方法之一,实现离散化的方法有很多,对于COMSOL主要的离散化及种 目前我研究了三种实现离散化的方法,这三种方…...
嵌入式系统代码执行时间测量方法与优化
1. 嵌入式程序运行时间测量的必要性在嵌入式系统开发中,精确测量代码执行时间是每个工程师必备的技能。无论是优化算法效率、调试实时系统,还是验证硬件性能,时间测量都扮演着关键角色。以STM32为例,当我们需要确认一个延时函数是…...
3步实现GitHub资源精准提取:开发者必备的效率工具
3步实现GitHub资源精准提取:开发者必备的效率工具 【免费下载链接】DownGit github 资源打包下载工具 项目地址: https://gitcode.com/gh_mirrors/dow/DownGit 你是否曾遇到这样的困境:急需从GitHub仓库中获取一个特定文件夹,却不得不…...
102. 在控制平面主机名更改后恢复 Rancher 配置的 RKE2 集群
Environment 环境 Rancher provisioned RKE2 downstream cluster control plane node hostname changed, without removing the node from the cluster. Rancher 配置了 RKE2 下游集群控制平面节点的主机名更改,但未将该节点从集群中移除。 Procedure 程序It is …...
微信QQ防撤回神器:RevokeMsgPatcher 2.1 终极使用教程
微信QQ防撤回神器:RevokeMsgPatcher 2.1 终极使用教程 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) 项目地址: https://gitcode.co…...
猫抓插件:浏览器资源嗅探的革命性解决方案
猫抓插件:浏览器资源嗅探的革命性解决方案 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾在浏览网页时,看到心仪的…...
解决Python ssl模块与系统OpenSSL版本不一致的编译指南
1. 为什么Python的ssl模块会与系统OpenSSL版本不一致? 很多开发者都遇到过这样的困惑:明明系统已经升级了OpenSSL,为什么Python的ssl模块还在使用旧版本?这个问题其实源于Python的编译机制。Python在编译安装时,会将当…...
langchain技术栈研究
引言 langchain新版本1.1.0 出来了,老版的api看了一些,api写法看起来有些凌乱,感觉还是SpringAI相对好好记忆一些。不知到1.x版本的会好使一些不。我们先来看看新版的langchain、langgraph、deepagents。 python vscode安装了一个backformate…...
Laya3D美术进阶:巧用Shader实现APP级游戏效果还原
1. 为什么选择Laya3D的Shader技术? 很多开发者第一次接触Laya3D时,都会有个疑问:为什么不用Unity直接开发?特别是在微信小游戏这个特定场景下,Laya3D的Shader技术到底能带来什么优势?我做了三年Laya小游戏…...
MAA游戏助手:如何让《明日方舟》的日常任务自动完成?
MAA游戏助手:如何让《明日方舟》的日常任务自动完成? 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: h…...
