list【2】模拟实现(含迭代器实现超详解哦)
模拟实现list
- 引言(实现概述)
- list迭代器实现
- 默认成员函数
- operator* 与 operator->
- operator++ 与 operator--
- operator== 与 operator!=
- 迭代器实现概览
- list主要接口实现
- 默认成员函数
- 构造函数
- 析构函数
- 赋值重载
- 迭代器
- 容量
- 元素访问
- 数据修改
- insert
- erase
- push_back 与 push_front
- pop_back 与 pop_front
- clear
- swap
- 源码概览
- 总结
引言(实现概述)
在前面,我们介绍了list的使用:
戳我看list的介绍与使用详解哦
在本篇文章中将重点介绍list的接口实现,通过模拟实现可以更深入的理解与使用list
我们模拟实现的 list 底层是一个带头双向循环链表
在实现list时,我们首先需要一个结构体以表示链表中结点的结构list_node
,大致包括数据与指向前后结点的指针:
template<class T>
struct list_node //结点类
{list_node<T>* _prev;list_node<T>* _next;T _date;list_node(const T& date = T()) //匿名对象: _prev(nullptr), _next(nullptr), _date(date){}
};
在有了结点之后,还需要一个 list
类,包括双向链表的头结点指针_pHead
,链表中的元素个数_size
:
template<class T>
class list //带头双向循环链表
{typedef list_node<T> Node;
private:Node* _pHead;size_t _size;
};
大致结构如下:
关于带头双向循环链表的实现可以参考之前的C语言版本实现,在本篇文章中结构将不是最重点的内容:戳我看C语言实现带头双向循环链表详解哦
与vector相同,list是一个类模板,其声明与定义不能分离。我们将模拟实现的list放在我们创建的命名空间内,以防止与库发生命名冲突。
在list的模拟实现中,我们只实现一些主要的接口,包括默认成员函数、迭代器、容量、元素访问与数据修改:
list迭代器实现
与vector不同,list的迭代器是指针的封装,通过运算符重载来实现原生指针的功能。
我们可以通过封装结点的指针,即list_node<T>*
,来实现迭代器:
默认成员函数
对于默认成员函数,其实我们只需要实现构造函数即可,这个__list_iterator
类的属性只有一个结构体指针,并不存在类中申请的资源,所以编译器生成的析构函数与赋值运算符重载就够用了,不需要再实现了。
- 默认构造
对于默认构造函数,我们可以使用缺省参数,即参数类型为结构体指针,缺省值为nullptr
。
直接在初始化列表中用参数初始化类属性即可:
__list_iterator(Node* pNode = nullptr):_pNode(pNode){}
- 拷贝构造
对于拷贝构造,参数必须是类类型的引用,可以加上const修饰(我们可以在类中将类类型__list_iterator<T, Ref, Ptr>
重命名为self
,以方便书写)
在函数中直接赋值即可:
__list_iterator(const self& it){_pNode = it._pNode;}
operator* 与 operator->
迭代器使用时应该是与指针基本类似的,所以也需要重载*
与->
。
- 重载
*
指针当然可以进行解引用的操作,指向容器中元素的指针。对这个指针进行解引用操作结果就应该是该指针指向的元素。对于list的迭代器而言,解引用该迭代器的结果就应该是结点中的_data
元素的引用,类型为&T
:
(我们可以为模板参数加上一个参数,即Ref
,它表示T&
)
Ref operator*(){return _pNode->_date;}
- 重载
->
当T
是自定义类型时,其指针还可以通过->
直接访问到T
类型对象_data
中的元素,起指针作用的迭代器自然也需要实现这个功能。
但是对于这个运算符重载而言,它并不知道要返回T类型对象中的什么元素,所以这个operator->
函数可以直接返回该迭代器对应的结点中的_data
元素的指针,然后在调用的时候再通过->
来访问其中元素:it->->a;
(通过迭代器it访问T类型对象中的a元素)。
但是,这样的形式访问又与指针的用法不一致,所以这里有一个特殊的规定,即规定需要使用it->a;
的方式通过迭代器访问T
类型对象中的元素,以获得与原生指针相同的使用方式:
(我们可以为模板参数加上一个参数,即Ptr
,它表示T*
)
Ptr operator->(){return &(_pNode->_date);}
operator++ 与 operator–
- 重载
++
++操作即实现迭代器的指向向后移动一个元素,list的迭代器底层是一个结构体指针,所以只需要将当前_pNode->_next
赋值给_pNode
即可
且++
分为前置++
与后置++
,在区别这两个的实现时,前面类和对象时已经详细介绍过了,即给后置++
增加一个参数int
,但是不传参,只用于区分前置与后置。
另外后置++
需要创建临时对象,在*this++
后必须要返回临时对象而非引用:
self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}
- 重载
--
重载--
与前面的++
类似,即将_pNode->_prev
赋值给_pNode
即可:
self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}
operator== 与 operator!=
对于==
与!=
重载,即判断两个迭代器对象的属性是否相等即可:
bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}
迭代器实现概览
template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _pNode;__list_iterator(Node* pNode = nullptr):_pNode(pNode){}__list_iterator(const self& it){_pNode = it._pNode;}Ref operator*(){return _pNode->_date;}Ptr operator->(){return &(_pNode->_date);}self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}};
list主要接口实现
在实现list之前,我们可以对一些较为麻烦的类型进行重命名以方便书写:
typedef list_node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
默认成员函数
构造函数
实现构造函数时,我们需要实现无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造:
无参构造:
由于我们模拟实现的list底层为带头双向循环链表,所以在无参构造时虽然没有在list中放入元素,但是还使需要先放入一个空结点作为头节点。
创建头节点的操作在任何构造函数中都要先进行,所以我们将其封装为一个init_empty_list
函数。这个初始化空list的函数需要new
一个结点,然后使节点中的_prev
与_next
都指向它自身,最后将_size
赋值为0:
void init_empty_list(){ _pHead = new Node();_pHead->_prev = _pHead;_pHead->_next = _pHead;_size = 0;}list(){init_empty_list();}
n个指定元素构造:
使用n
个指定元素构造有两个参数,第一个就是int
,第二个是const T&
,缺省值为T()
函数中,首先调用init_empty_list
构建一个头结点;
再循环,使用push_bake
尾插n
个指定元素value
即可(push_back后面会实现):
list(int n, const T& value = T()){init_empty_list();while (n--){push_back(value);}}
迭代器区间构造:
是用迭代器区间构造函数是一个函数模板,可以使用任何容器的迭代器区间来构造list
函数中,首先调用init_empty_list
构建一个头结点;
然后再循环,使用push_bake
尾插first
迭代器的解引用出的元素,当first
与last
相等时出循环:
template <class Iterator>list(Iterator first, Iterator last){init_empty_list();while (first != last){push_back(*first);++first;}}
拷贝构造:
拷贝构造函数的参数是一个const list<T>&
实现时,首先调用init_empty_list
构建一个头结点;
然后范围for,使用push_bake
将l中的元素逐一尾插到list中:
list(const list<T>& l){init_empty_list();for (auto el : l){push_back(el);}}
析构函数
析构函数需要实现释放list中的资源。
首先复用clear
,清空list中的元素。clear
中会实现释放结点中的资源,后面部分会实现;
再delete _pHead;
,释放头节点中的资源,它会调用结点结构体的析构函数:
~list(){clear();delete _pHead;}
赋值重载
对于赋值运算符重载,我们直接使用新写法,即先使用参数l
创建一个临时对象;
然后使用swap
将临时对象与*this
交换(后面会实现swap
函数);
最后返回*this
即可,创建的临时对象就会在函数栈帧销毁时自动释放:
list<T>& operator=(const list<T>& l) //list& operator=(const list l) 对于赋值重载,这样也可{list<T> temp(l);swap(temp);return *this;}
迭代器
在前面已经实现了list的迭代器,它是结点指针的封装;
这里暂时只实现begin
与end
,关于反向迭代器的实现在后面会详细介绍。
begin
返回首元素的地址,即头结点的下一个结点的地址;
end
返回尾元素下一个位置的地址,即头节点的地址,他们分别重载有const版本:
iterator begin(){return iterator(_pHead->_next);}iterator end(){return iterator(_pHead);}const_iterator begin() const{return const_iterator(_pHead->_next);}const_iterator end() const{return const_iterator(_pHead);}
容量
在容器部分,由于list并没有容量的概念,所以我们只需要实现size
与empty
即可;
我们在list的属性中,我们设置了_size
,在插入元素时_size++
,删除元素时_size--
,所以这里只需要返回_size
的值即可;
当_size == 0
时,list为空,empty
返回true
,否者返回false
:
size_t size() const{return _size;}bool empty() const{if (_size == 0)return true;elsereturn false;}
元素访问
由于list在任意位置访问元素的成本较高,就没有提供operator[]
的接口,所以我们只需要实现front
与back
即可。分别返回首尾的元素,有普通对象与const对象两个重载版本:
在实现时,我们可以借助list的属性_pHead
,即头结点的指针来访问首尾元素:
我们模拟实现list的底层是带头双向循环链表,所以list中的第一个元素就是_pHead->_next
指向的结点中的元素;list中的_pHead->_prev
指向的结点中的元素:
front
只需要返回 _pHead->_next->_date
即可;
back返回_pHead->_prev->_date
即可,返回值类型为T&
,const版本就返回const T&
即可:
T& front(){return _pHead->_next->_date;}const T& front()const{return _pHead->_next->_date;}T& back(){return _pHead->_prev->_date;}const T& back()const{return _pHead->_prev->_date;}
数据修改
insert
list 的结构使在任意位置插入数据的效率是较高的,只需要创建结点,再链接到pos
位置前即可:
在实现insert
时,首先new
一个结点,类型为Node
,并用val
初始化(这个Node类型是前面重命名后的类型);
这时我们需要记录pos
位置的结点指针为cur
,pos
位置前面的结点指针为prev
,以方便后续链接;
然后将pos
结点的前一个结点与新结点链接,即newnode
与prev
链接;
再将pos
结点与新结点链接,即newnode
与cur
链接;
最后,更新_size
,并返回新结点的迭代器:
// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._pNode;Node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}
erase
erase
实现时,只需要释放pos
位置的结点,并链接剩余的结点即可:
首先assert
判断list是否为空;
这时我们需要记录pos
位置的结点指针为cur
,pos
位置前面的结点指针为prev
,pos
的后一个结点指针为next
,以方便后续链接;
然后直接链接pos
位置的前一个结点与后一个结点,即链接prev
与next
即可;
最后,释放cur
指向的结点,更新_size
,并返回next
:
// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());Node* cur = pos._pNode;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}
push_back 与 push_front
对于头插与尾插的实现,复用insert
即可:
push_front
,即在首结点的前面一个位置插入一个元素,即在begin()
迭代器位置插入;
push_back
,即在尾结点的后一个位置插入一个元素,即在end()
位置插入:
void push_back(const T& val){insert(end(), val);}void push_front(const T& val){insert(begin(), val);}
pop_back 与 pop_front
对于头删尾删的实现,复用erase
即可:
pop_front
,即删除头节点,即 erase
删除begin()
位置的结点 即可;
pop_back
,删除尾结点,即 erase
删除end()
前面一个结点即可,但是由于list迭代器不支持-操作,所以这里传参为--end()
:
void pop_front(){erase(begin());}void pop_back(){erase(--end());}
clear
clear
用于清理list中的所有元素,可以直接复用erase
来实现清理:
我们可以通过遍历迭代器的方式逐一释放结点:
令it
的初始值为begin()
,循环逐一erase
删除,当it
等于end()
的时候终止循环,就可以实现删除所有结点并保留头节点;
另外,由于erase
删除某节点后,会返回删除节点的下一个位置,所以只要把返回值载赋值给it
就实现了迭代器的向后移动:
void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase返回删除的结点的下一个位置的迭代器}}
swap
实现list的交换函数时,只需要使用库swap
交换list的属性即可,即交换_pHead
与_size
:
void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}
源码概览
(关于反向迭代器的实现在后面会详细介绍,现在可以暂时忽略)
#include<iostream>
#include<cassert>
#include"my_reverse_iterator.h"namespace qqq
{template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _date;list_node(const T& date = T()) //匿名对象: _prev(nullptr), _next(nullptr), _date(date){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> self;Node* _pNode;__list_iterator(Node* pNode = nullptr):_pNode(pNode){}__list_iterator(const self& it){_pNode = it._pNode;}Ref operator*(){return _pNode->_date;}Ptr operator->(){return &(_pNode->_date);}self& operator++(){_pNode = _pNode->_next;return *this;}self operator++(int){self temp(*this);_pNode = _pNode->_next;return temp;}self& operator--(){_pNode = _pNode->_prev;return *this;}self operator--(int){self temp(*this);_pNode = _pNode->_prev;return temp;}bool operator==(const self& it){return _pNode == it._pNode;}bool operator!=(const self& it){return _pNode != it._pNode;}};template<class T>class list //带头双向循环链表{typedef list_node<T> Node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;typedef ReverseIterator<iterator, T&, T*> reverse_iterator;typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;public:/ constructor and destructor void init_empty_list(){ _pHead = new Node();_pHead->_prev = _pHead;_pHead->_next = _pHead;_size = 0;}list(){init_empty_list();}list(int n, const T& value = T()){init_empty_list();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){init_empty_list();while (first != last){push_back(*first);++first;}}list(const list<T>& l){init_empty_list();for (auto el : l){push_back(el);}}list<T>& operator=(const list<T>& l) //list& operator=(const list l) 对于赋值重载,这样也可{list<T> temp(l);swap(temp);return *this;}~list(){clear();delete _pHead;}// List Iterator///iterator begin(){return iterator(_pHead->_next);}iterator end(){return iterator(_pHead);}const_iterator begin() const{return const_iterator(_pHead->_next);}const_iterator end() const{return const_iterator(_pHead);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}/List Capacity//size_t size() const{return _size;}bool empty() const{if (_size == 0)return true;elsereturn false;}///List Access///T& front(){return _pHead->_next->_date;}const T& front()const{return _pHead->_next->_date;}T& back(){return _pHead->_prev->_date;}const T& back()const{return _pHead->_prev->_date;}List Modify//void push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T& val){insert(begin(), val);}void pop_front(){erase(begin());}// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._pNode;Node* prev = cur->_prev;newnode->_prev = prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){assert(!empty());Node* cur = pos._pNode;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}void clear(){iterator it = begin();while (it != end()){it = erase(it);//erase返回删除的结点的下一个位置的迭代器}}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:Node* _pHead;size_t _size;};
};
总结
到此,关于list的模拟实现就到此结束了
模拟实现容器并不是为了造一个更好的轮子,而是为了更好的理解与使用容器
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦
相关文章:

list【2】模拟实现(含迭代器实现超详解哦)
模拟实现list 引言(实现概述)list迭代器实现默认成员函数operator* 与 operator->operator 与 operator--operator 与 operator!迭代器实现概览 list主要接口实现默认成员函数构造函数析构函数赋值重载 迭代器容量元素访问数据修改inserterasepush_ba…...

Nginx+Tomcat的动静分离与负载均衡
目录 前言 一、案例 二、Nginx的高级用法 三、tomcat部署 四、Nginx部署 五、测试 总结 前言 通常情况下,一个 Tomcat 站点由于可能出现单点故障及无法应付过多客户复杂多样的请求等情况,不能单独应用于生产环境下,所以我们需要一套更…...

【设计模式】Head First 设计模式——策略模式 C++实现
设计模式最大的作用就是在变化和稳定中间寻找隔离点,然后分离它们,从而管理变化。将变化像小兔子一样关到笼子里,让它在笼子里随便跳,而不至于跳出来把你整个房间给污染掉。 设计思想 将行为想象为一族算法,定义算法族…...

c#object类中方法的使用
C#中的Object类是所有类的基类,它定义了一些通用的方法和属性,可以在任何对象上使用。以下是Object类中常用的方法和属性的使用: 1.ToString():将对象转换为字符串表示形式。 string str obj.ToString();2.Equals():…...

三种常用盒子布局的方法
在Vue中,可以使用各种CSS布局属性和技巧来设置盒子的布局。以下是一些常用的方法: 1.使用Flexbox布局:在包含盒子的父元素上设置display: flex,然后可以使用flex-direction、justify-content和align-items 等属性来控制盒子的布局…...

GB28181学习(二)——注册与注销
概念 使用REGISTER方法进行注册和注销;注册和注销应进行认证,认证方式应支持数字摘要认证方式,高安全级别的宜支持数字证书认证;注册成后,SIP代理在注册过期时间到来之前,应向注册服务器进行刷新注册&…...

【Linux】线程安全-信号量
文章目录 信号量原理信号量保证同步和互斥的原理探究信号量相关函数初始化信号量函数等待信号量函数释放信号量函数销毁信号量函数 信号量实现生产者消费者模型 信号量原理 信号量的原理:资源计数器 PCB等待队列 函数接口 资源计数器:对共享资源的计…...

数字IC验证——PSS可移植测试用例
PSS是Accellera组织定义的测试用例生成规范,其思想是定义一个抽象模型,EDA工具可以从中生成适用于每个设计层次结构和每个验证平台的测试,即PSS定义了统一的测试场景,而场景的使用可以横跨不同验证层次和配置。 这种特性决定了PSS…...

java设计模式---策略模式
策略模式的定义 策略设计模式是一种行为设计模式。当在处理一个业务时,有多种处理方式,并且需要再运行时决定使哪一种具体实现时,就会使用策略模式。 策略模式的类图: 策略模式的实现 在支付业务中,有三种付款方式&…...

5-redis集群搭建安装
1.先决条件 1.1.OS基础配置 CentOS为了能够正常安装redis,需要对CentOS进行常规的一些基础配置,主要有:关闭防火墙与selinux,设置主机名,配置虚拟机IP地址使其能够与外网ping通,配置IP地址与主机名映射,配置yum源。具体配置参见: Linux常规基础配置_小黑要上天的博客…...

(数字图像处理MATLAB+Python)第十一章图像描述与分析-第七、八节:纹理描述和其他描述
文章目录 一:纹理描述(1)联合概率矩阵法A:定义B:基于联合概率矩阵的特征C:程序 (2)灰度差分统计法A:定义B:描述图像特征的参数 (3)行程…...

MySQL提权
参考: mysql提权篇 | Wh0ales Blog MySQL 提权方法整理 - Geekbys Blog MySQL_UDF提权漏洞复现-云社区-华为云 MYSQL UDF手动提权及自动化工具使用_udf提权工具_小直789的博客-CSDN博客 MySQL提权的三种方法 - FreeBuf网络安全行业门户 ......

FPGA优质开源项目 – UDP万兆光纤以太网通信
本文开源一个FPGA项目:UDP万兆光通信。该项目实现了万兆光纤以太网数据回环传输功能。Vivado工程代码结构和之前开源的《UDP RGMII千兆以太网》类似,只不过万兆以太网是调用了Xilinx的10G Ethernet Subsystem IP核实现。 下面围绕该IP核的使用、用户接口…...

如何中mac上安装多版本python并配置PATH
摘要 mac 默认安装的python是 python3,但是如果我们需要其他python版本时,该怎么办呢? 例如:需要python2 版本,如果使用homebrew安装会提示没有python2。同时使用python --version 会发现commond not found。 所以本…...

window 常用基础命令
0、起步 0-1) 获取命令的参数指引 netstat /? 0-2) 关于两个斜杠: window 文件路径中使用反斜杠:\ linux 文件路径中使用:/ 1、开关机类指令 shutdown /s # 关机shutdown /r # 重启shutdown /l …...

lintcode 1815 · 警报器 【simple vip 前缀和数组】
题目 https://www.lintcode.com/problem/1815 一个烟雾警报器会监测len秒内的烟雾值,如果这段时间烟雾值平均值大于k那么警报器会报警。现在给你n个数代表刚开始工作n秒内警报器监测的烟雾值(警报器从第len秒开始判断是否报警),…...

【强化学习】MDP马尔科夫链
基本元素 状态集:表示智能体所处所有状态的全部可能性的集合。类似的集合,行为集,回报集决策:规定我在某个状态下,我做出某个action马尔可夫链:学术上来说是无记忆性质。说白了就是我只在乎我目前的状态。…...

SpringBoot自写项目记录
设置静态资源映射 Slf4j 用来打印日志 Configuration Slf4j //设置静态资源映射 public class WebMvcConfig extends WebMvcConfigurationSupport {Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("开始静态资源配置");r…...

Windows10上使用llama-recipes(LoRA)来对llama-2-7b做fine-tune
刚刚在Windows10上搭建环境来对llama2做finetune,里面坑还是挺多的,这里把印象中的坑整理了一下以作备忘。 llama-recipes是meta的开源项目,Github地址为:GitHub - facebookresearch/llama-recipes: Examples and recipes for Ll…...

06-限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景?【Java面试题总结】
限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景? 常见的限流算法有固定窗口、滑动窗口、漏桶、令牌桶等。 6.1 固定窗口 概念:固定窗口(又称计算器限流),对一段固定时间窗口内的请求进行…...

2021年06月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试
C/C++编程(1~8级)全部真题・点这里 第1题:逆波兰表达式 逆波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3的逆波兰表示法为+ 2 3。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4的逆波兰表示法为* + 2 3 …...

Tuxera NTFS for Mac2023苹果电脑Mac硬盘读写工具
Tuxera NTFS for Mac是一款高效稳定的NTFS读写工具,可以让你在Mac上完整地读写兼容NTFS格式驱动器,对磁盘进行访问、编辑、存储和传输文件等操作。Tuxera NTFS for Mac软件是一款高效稳定的NTFS读写工具,可以让你在Mac上完整地读写兼容NTFS格…...

系统调用的过程
系统调用也是库函数的底层实现,当高级语言代码中如调用了库函数,在编译为机器语言指令后,指令包含前期处理相关命令、传参指令、陷入指令、后续处理相关指令。在执行陷入指令时发生内中断,使CPU进入核心态,执行对系统调…...

Python将多个文件的名称或后缀名由大写字母修改为小写的方法
本文介绍基于Python语言,基于一个大文件夹,遍历其中的多个子文件夹,并对于每一个子文件夹中的大量文件,批量将其文件的名称或后缀名中的字母由大写修改为小写的方法。 本文期望实现的需求为:现有一个大文件夹ÿ…...

Debezium的三种部署方式
Debezium如何部署 debezium 有下面三种部署方式,其中最常用的就是 kafka connect。 kafka connect 一般情况下,我们通过 kafka connect 来部署 debezium,kafka connect 是一个框架和运行时: source connectors:像 debezium 这样将记录发送到 kafka 的source connectors…...

通讯协议057——全网独有的OPC HDA知识一之接口(十二)IOPCHDA_DataCallback
本文简单介绍OPC HDA规范的IOPCHDA_DataCallback(客户端接口)接口方法,更多通信资源请登录网信智汇(wangxinzhihui.com)。 1)HRESULT OnDataChange(dwTransactionID, hrStatus, dwNumItems, pItemValues, phrErrors) 此方法由客…...

后端SpringBoot+前端Vue前后端分离的项目(一)
前言:后端使用SpringBoot框架,前端使用Vue框架,做一个前后端分离的小项目,需求:实现一个表格,具备新增、删除、修改的功能。 目录 一、数据库表的设计 二、后端实现 环境配置 数据处理-增删改查 model…...

docker 安装 MySQL5.7
1、拉取镜像 docker pull mysql:5.7 2、创建容器 docker run \ -d \ -p 3306:3306 \ --name mysql \ --privilegedtrue \ -v /var/docker/mysql/log:/var/log/mysql \ -v /var/docker/mysql/data:/var/lib/mysql \ -v /var/docker/mysql/conf:/etc/mysql/conf.d \ -e MYSQL_…...

分布式session的4种解决方案
分布式session的4种解决方案 1、cookie和session cookie和session都是用来跟踪用户身份信息的会话方式。 cookie存储的数据保存在本地客户端,用户获取容易,但安全性不高,存储数据小。 session存储的数据保存在服务器,用户不易获取…...

SQL Server2008下载地址
SQL Server2008下载地址 https://www.microsoft.com/zh-CN/download/details.aspx?id30438 版本说明 Microsoft SQL Server 2008 R2 Express Service Pack 2 是功能丰富的 SQL Server 免费版本,是学习、开发桌面、Web 及小型服务器应用程序并为它们提供功能的理…...