【C++】STL——list的模拟实现
list的模拟实现
文章目录
- list的模拟实现
- 一、list三个基本类的模拟实现总览
- 二、节点类接口实现
- 模拟实现
- 构造函数
- 三、迭代器类接口实现
- 1.正向迭代器
- 默认成员函数
- 构造函数
- 六种运算符重载 */->/++/--/!=/==
- 2.反向迭代器
- 四、list类接口实现
- 1.默认成员函数
- 1.1.构造函数
- 1.2.析构函数
- 1.3.拷贝构造函数
- 1.4.赋值运算符重载函数
- 2.访问相关函数
- 2.1.front和back
- 3.迭代器相关函数
- 3.1.begin和end
- 3.2.rbegin和rend
- 4.插入删除相关函数
- 4.1.insert
- 4.2.push_back
- 4.3.push_front
- 4.4.erase
- 4.5.pop_back
- 4.6.pop_front
- 5.其它相关函数
- 5.1.resize
- 5.2.clear
- 5.3.empty_initialize
- 5.4.swap
一、list三个基本类的模拟实现总览
前面list的学习中我们得知,list其实就是一个带头双向循环链表:
现在要模拟实现list,要实现下列三个类:
- 模拟实现节点类
- 模拟实现迭代器的类
- 模拟list主要功能的类
这三个类的实现层层递进,我们的目标是为了实现list类,然而list的模拟实现其基本功能要建立在迭代器类和节点类均已实现好的情况下才得以完成。
namespace list_realize {//模拟实现list当中的结点类template<class T>struct List_node{//成员函数List_node(const T& val = T()); //构造函数//成员变量T _data; //数据域List_node<T>* _next; //后继指针List_node<T>* _prev; //前驱指针};//模拟实现list正向迭代器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* pnode); //构造函数//各种运算符重载函数Self& operator++();Self& operator--();Self operator++(int);Self operator--(int);bool operator==(const Self& it) const;bool operator!=(const Self& it) const;Ref operator*();Ptr operator->();//成员变量node* _pnode; //一个指向结点的指针};//模拟实现list反向迭代器template<class Iterator, class Ref, class Ptr>class reverse__list_iterator{typedef reverse__list_iterator<Iterator, Ref, Ptr> Self;reverse__list_iterator(Iterator* it); //构造函数//各种运算符重载函数Self& operator++();Self& operator--();Self& operator++(int);Self& operator--(int);bool operator==(const Self& rit) const;bool operator!=(const Self& rit) const;Ref operator*();Ptr operator->();//成员变量private:Iterator _it;//正向迭代器};//模拟实现listtemplate<class T>class list{public:typedef List_node<T> node;typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;typedef reverse__list_iterator<iterator, T&, T*> reverse_iterator;typedef reverse__list_iterator<const_iterator, const T&, const T*> const_reverse_iterator;//默认成员函数list(); // 无参构造template <class InputIterator> list(InputIterator first, InputIterator last); // 迭代器构造list(size_t n, const T& val = T()) // 带参构造list(const list<T>& lt); // 拷贝构造list<T>& operator=(const list<T>& lt); // 赋值运算符重载~list(); // 析构函数//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//访问容器相关函数T& front();T& back();const T& front() const;const T& back() const;//插入、删除函数void insert(iterator pos, const T& val = T());iterator erase(iterator pos);void push_back(const T& x);void pop_back();void push_front(const T& x);void pop_front();//其他函数void empty_initialize();size_t size() const;void resize(size_t n, const T& val = T());void clear();bool empty() const;void swap(list<T>& lt);private:node* _head; //指向链表头节点的指针size_t _size; // 链表节点个数}; }
二、节点类接口实现
因为list的本质为带头双向循环链表,所以其每个节点都要确保有下列成员:
- 前驱指针
- 后继指针
- data值存放数据
而节点类的内部只需要实现一个构造函数即可。
模拟实现
template<class T> struct List_node //因为成员函数都是公有,就不设计成class了,直接struct {List_node<T>* _next; // 前驱指针List_node<T>* _prev; // 后继指针T _data; // 记录存放数据List_node(const T& val = T()):_next(nullptr), _prev(nullptr), _data(val) {} };
构造函数
构造函数这里是用来对其成员变量的一个初始化。
List_node(const T& val = T()):_next(nullptr), _prev(nullptr), _data(val) {}
三、迭代器类接口实现
这里强调下,因为list的特殊性,其本质是带头双向循环链表,对于链表,我们已然得知其内存空间是不连续的,是通过结点的指针顺次链接,我们不能像先前的string和vector一样直接解引用去访问其数据,结点的指针解引用还是结点,结点指针++还是结点指针,归根结底在于list物理空间不连续。而string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。
为了能让list像vector一样去解引用,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。
- 注意:
迭代器又分为正向迭代器和反向迭代器。
1.正向迭代器
- 注意:
这里我迭代器类的模板参数里面包含了3个参数:
template<class T, class Ref, class Ptr>
而后文list类的模拟实现中,我对迭代器进行了两种typedef:
typedef __list_iterator<T, T&, T*> iterator;//普通迭代器 typedef __list_iterator<T, const T&, const T*> const_iterator;//const迭代器
根据这里的对应关系:Ref对应的是&引用类型,Ptr对应的是*指针类型,这里如果我是普通对象传过来的迭代器,生成对应的普通迭代器,如果是const对象传递过来的迭代器,会生成对应的const迭代器。
这样做的原因在于避免单独写一个支持不能修改迭代器指向结点数据的类而造成的复用性差。
默认成员函数
这里的默认成员函数我们只需要写构造函数。
- 析构函数 – 结点不属于迭代器,不需要迭代器释放
- 拷贝构造 – 默认浅拷贝即可
- 赋值重载 – 默认浅拷贝即可
构造函数
这里我们通过结点的指针即可完成构造。通过结点指针构造一个迭代器。
__list_iterator(node* pnode)//通过结点指针构造一个迭代器:_pnode(pnode) {}
六种运算符重载 */->/++/–/!=/==
- *** 运算符重载**
*解引用的目的是为了获取结点里的_ data数据,因此我们直接return返回结点指向的_ data即可。
Ref operator*()//结点出了作用域还在,用引用返回 {return _pnode->_data;//返回结点指向的数据 }
- -> 运算符重载
假设出现这样的场景,我链表存储的不是内置类型,而是自定义类型,如下:
struct AA {AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2; }; void test() {cpp::list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));lt.push_back(AA(4, 4)); }
对于内置类型和自定义类型成员的指针,其访问方式都是不同的:
int* *it AA* (*it). 或者 it->
而这里我们应该重载一个->运算符。以便于访问自定义类型成员的指针的数据。
//->运算符重载 Ptr operator->() { return &(operator*());//返回结点指针所指结点的数据的地址 //或者return &_pnode->_data; }
实现了->运算符重载后,我们执行it->_ a1,编译器将其转换成it.operator->(),此时获得的是结点位置的地址即AA*,而理应还有一个箭头->才能获取数据,也就是这样:it.operator->()->_a1
- **总结:**编译器为了可读性进行优化处理,如果不优化应该是it->->_a1,优化以后,省略了一个箭头->。
- ++ 运算符重载
++运算符分为前置++和后置++
- 前置++
迭代器++的返回值还是迭代器,这里的++是为了让结点指向下一个结点的指针,注意前置++是要返回自增后的结点指针。
Self& operator++()//迭代器++的返回值还是迭代器 {_node = _pnode->_next;//直接让自己指向下一个结点即可实现++return *this;//返回自增后的结点指针 }
- 后置++
为了区分前置++,后置++通常要加上一个参数以便区别。此外,后置++是返回自增前的结点指针。
Self operator++(int)//加参数以便于区分前置++ {Self tmp(*this);//拷贝构造tmp_node = _pnode->_next;//直接让自己指向下一个结点即可实现++return tmp;//注意返回tmp,才是后置++ }
- – 运算符重载
–运算符也分前置–和后置–
- 前置–
前置–是让此结点指向上一个结点,最后返回自减后的结点指针即可。
self& operator--() {_pnode = _pnode->_prev;//让_node指向上一个结点即可实现--return *this; }
- 后置–
注意传参以区分前置–,最后返回的是自减前的结点指针即可。
self operator--(int)//记得传缺省值以区分前置-- {self tmp(*this);//拷贝构造tmp_pnode = _pnode->_prev;return tmp; }
- != 运算符重载
这里比较是否不等,是两个迭代器的比较,直接返回两个结点的位置是否不同即可。
//!=运算符重载 bool operator!=(const self& it) {return _pnode != it._pnode;//返回两个结点指针的位置是否不同即可 }
- == 运算符重载
这里直接返回俩结点指针是否相同即可。
//==运算符重载 bool operator==(const self& it) {return _pnode == it._pnode;//返回俩结点指针是否相同 }
2.反向迭代器
反向迭代器是一个适配器模式(后文会将适配器)。相较于正向迭代器,反向迭代器有下面三种主要变化:
- 反向迭代器的++执行的操作是正向迭代器里的–,
- 反向迭代器里的–执行的操作是正向迭代器里的++
- 反向迭代器的*解引用和->操作指向的是前一个数据
总代码如下:
template <class Iterator, class Ref, class Ptr> class reverse__list_iterator {typedef reverse__list_iterator<Iterator, Ref, Ptr> Self; public:reverse__list_iterator(Iterator it):_it(it){}Ref operator*(){Iterator prev = _it;return *--prev;}Ptr operator->(){return &operator*();}Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}Self& operator++(int){_it--;return *this;}Self& operator--(int){_it++;return *this;}bool operator!=(const Self& rit) const{return _it != rit._it;}bool operator==(const Self& rit) const{return _it == rit._it;}private:Iterator _it; };
四、list类接口实现
此接口的核心任务是为了模拟实现list类的一些核心功能,好比如插入删除,迭代器等等。
在list类中的唯一成员变量即自定义类型的变量,由先前的结点类构成的头结点。
1.默认成员函数
1.1.构造函数
- 无参构造:
此目的是为了对哨兵位的头结点_head进行初始化:
list() {_head = new Node();//申请一个头结点_head->_next = _head;//头结点的下一个结点指向自己构成循环_head->_prev = _head;//头结点的上一个结点指向自己构成循环_size = 0; }
- 传迭代器区间构造:
先初始化,再利用循环对迭代器区间的元素挨个尾插即可。
//传迭代器区间构造 template <class InputIterator> list(InputIterator first, InputIterator last) {empty_initialize();while (first != last){push_back(*first);++first;} }
- 带参构造:
初始化n个值为val
list(size_t n, const T& val = T()) {empty_initialize();for (size_t i = 0; i < n; i++){push_back(val);} }
1.2.析构函数
这里可以先复用clear函数把除了头结点的所有结点给删除掉,最后delete头结点即可。
~list() {clear();delete _head;_head = nullptr; }
1.3.拷贝构造函数
假设要用lt1拷贝构造lt2。
- 传统写法:
首先复用empty_init对头结点进行初始化,接着遍历lt1的元素,在遍历的过程中尾插将lt1的元素尾插到lt2上即可。这里直接利用push_back自动开辟空间完成深拷贝。
list(const list<T>& lt) //list(const list& lt) // 不建议,不规范 {empty_initialize();for (const auto& e : lt){push_back(e);} }
- 现代写法:
这里先初始化lt2自己,再把lt1引用传参传给lt,传lt的迭代器区间构造tmp,复用swap交换头结点指针即可完成深拷贝的现代写法。
list(const list<T>& lt) {empty_initialize(); // 空初始化list<T> tmp(lt.begin(), lt.end()); // 使用迭代器区间构造tmpswap(tmp); // 交换头结点指针 }
1.4.赋值运算符重载函数
假设要把lt1赋值给lt2。
- 传统写法:
list<T>& operator=(const list<T>& lt) {if (this != <){clear();for (const auto& e : lt){push_back(e);}}return *this; }
- 现代写法:
这里直接给出现代写法。注意这里传值传参把lt1传给lt自定义类型传值传参调用拷贝构造,拷贝构造完成的是深拷贝生成了lt,复用swap函数交换lt1与lt的头结点指针指向即可,最后返回*this。
list<T>& operator=(list<T> lt)//套用传值传参去拷贝构造完成深拷贝 {swap(lt);return *this; }
2.访问相关函数
2.1.front和back
front和back函数分别用于获取第一个有效数据和最后一个有效数据,因此,实现front和back函数时,直接返回第一个有效数据和最后一个有效数据的引用即可。
T& front() {return *begin(); //返回第一个有效数据的引用// return _head->_next->_data; } T& back() {return *(--end()); //返回最后一个有效数据的引用// return _head->_prev->_data; }
我在这里
const T& front() const {return *begin();//return _head->_next->_data; }const T& back() const {return _head->_prev->_val; }
3.迭代器相关函数
3.1.begin和end
- begin的作用是返回第一个位置的结点的迭代器,而第一个结点就是哨兵位头结点的下一个结点,因此,直接return返回_head的_next即可。
- end的作用就是返回最后一个有效数据的下一个位置的迭代器,而这里对于list指的就是头结点_head的位置。
begin和end均分为普通对象调用和const对象调用,因此要写两个版本。
- 普通对象调用版
iterator begin()//begin返回的就是第一个有效数据,即头结点的下一个结点 {return iterator(_head->_next);//构造了一个匿名对象,通过调用构造函数利用头结点指向的第一个结点作为参数,来返回头结点//return _head->_next; 也可以这样写 }iterator end() {return iterator(_head);//end返回的是最后一个结点的下一个结点,就是头结点_head//return _head; 也可以这样写 }
- const对象调用版
const_iterator begin() const {return const_iterator(_head->_next);//return _head->_next; } const_iterator end() const {return const_iterator(_head);//return _head; 也可以这样写 }
3.2.rbegin和rend
rbegin就是正向迭代器里end()的位置,rend就是正向迭代器里begin()的位置。
rbegin和rend同样分为普通对象调用和const对象调用:
- 普通对象调用:
reverse_iterator rbegin() {return reverse_iterator(end()); }reverse_iterator rend() {return reverse_iterator(begin()); }
- const对象调用:
const_reverse_iterator rbegin() const {return const_reverse_iterator(end()); }const_reverse_iterator rend() const {return const_reverse_iterator(begin()); }
4.插入删除相关函数
4.1.insert
实现insert首先创建一个新的结点存储插入的值,接着取出插入位置pos的结点为cur,记录cur的上一个结点位置prev,先链接prev和newnode,再链接newnode和cur即可。最后记得要返回新插入元素的迭代器位置。
//insert,插入pos位置之前 iterator insert(iterator pos, const T& val) {node* newnode = new node(val); //创建新的结点node* cur = pos._pnode; //迭代器pos处的结点指针//链接prev和newnodenode* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;//链接newnode和curnewnode->_next = cur;cur->_prev = newnode;++_size;//返回新插入元素的迭代器位置return iterator(newnode); }
- **补充:**list的insert不存在野指针和意义改变的迭代器失效问题。
4.2.push_back
- 法一:
首先,创建一个新结点用来存储尾插的值,接着找到尾结点。将尾结点和新结点前后链接构成循环,再将头结点和新结点前后链接构成循环即可。
void push_back(const T& x) {Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点//链接tail和newnodetail->_next = newnode;newnode->_prev = tail;//链接newnode和头结点_headnewnode->_next = _head;_head->_prev = newnode; }
- 法二:
这里也可以复用insert函数,当insert中的pos位置为哨兵位头结点的位置时,实现的就是尾插,因为insert插入是在pos位置前插入,而pos位哨兵位头结点时,在其前一个位置(尾部)插入就是实现了尾插。
void push_back(const T& x) {//法二:复用insertinsert(end(), x); }
4.3.push_front
直接复用insert函数,当pos位置为begin()时,获得的pos就是第一个有效结点数据,即可满足头插。
void push_front(const T& x) {insert(begin(), x); }
4.4.erase
erase删除的是pos位置的结点,首先取出pos位置的结点为cur,记录cur上一个结点的位置为prev,再记录cur下一个结点的位置为next,链接prev和next,最后delete释放cur的结点指针即可。最后记得返回删除元素后一个元素的迭代器位置。
iterator erase(iterator pos) {assert(pos != end())node* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;//链接prev和nextprev->_next = next;next->_prev = prev;delete cur;cur = nullptr;_size--;return iterator(next);//返回删除元素下一个元素的迭代器位置}
erase存在迭代器失效问题,为了避免此问题,我们修改了返回值为iterator。
4.5.pop_back
直接复用erase即可,当pos位置为–end()时,pos就是最后一个结点的位置,实现的就是尾删。
void pop_back() {erase(--end()); }
4.6.pop_front
直接复用erase即可,当pos位置为begin()时,pos就是第一个有效数据,实现的就是头删。
void pop_front() {erase(begin()); }
5.其它相关函数
5.1.resize
resize函数的规则:
- 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
- 若当前容器的size大于所给n,则只保留前n个有效数据。
实现resize函数时,不要直接调用size函数获取当前容器的有效数据个数,因为当你调用size函数后就已经遍历了一次容器了,而如果结果是size大于n,那么还需要遍历容器,找到第n个有效结点并释放之后的结点。
这里实现resize的方法是,设置一个变量oldsize,用于记录当前所遍历的数据个数,然后开始变量容器,在遍历过程中:
当oldsize大于或是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
当容器遍历完毕时遍历结束,此时说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。void resize(size_t newsize, const T& val = T()) {size_t oldsize = size();if (newsize <= oldsize){// 有效元素个数减少到newsizewhile (newsize < oldsize){pop_back();oldsize--;}}else{while (oldsize < newsize){push_back(val);oldsize++;}} }
5.2.clear
clear的作用是清除除了头结点外的所有结点,这里可以复用erase并通过遍历的方式逐个删除。
void clear() {//复用eraseiterator it = begin();while (it != end()){it = erase(it);//用it接收删除后的下一个结点的位置} }
5.3.empty_initialize
由于对头结点初始化使用频繁,我们特定把它封装成一个函数,作用就是把哨兵位的头结点开辟出来。
//空初始化 对头结点进行初始化 void empty_initialize() {_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0; }
5.4.swap
对于链表的swap,直接交换头结点指针的指向即可完成。直接复用库函数的swap即可。
void swap(list<T>& lt) {std::swap(_head, lt._head);std::swap(_size, lt._size); }
相关文章:

【C++】STL——list的模拟实现
list的模拟实现 文章目录list的模拟实现一、list三个基本类的模拟实现总览二、节点类接口实现模拟实现构造函数三、迭代器类接口实现1.正向迭代器默认成员函数构造函数六种运算符重载 */->//--/!/2.反向迭代器四、list类接口实现1.默认成员函数1.1.构造函数1.2.析构函数1.3.…...
SpringBoot小区物业管理系统
文章目录 项目介绍主要功能截图:后台登录车位收费管理物业收费管理投诉信息管理保修信息管理基础信息管理数据分析部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获…...

外网跨网远程控制内网计算机3种方案
远程控制,通俗来讲就是在自己个人电脑直接远程访问另台主机电脑桌面操作。 如何远程控制电脑?远程控制别人计算机的方案通常有两种,一种是开启电脑系统自带的远程桌面功能,如果涉及跨网内、外网互通可以同时用快解析内网映射外网&…...
记录偶发更新失败问题
一,代码如下Transactional(rollbackFor Exception.class) public void updateDelivery(){ // 1.新增反馈记录 // 2.更新订单状态,及其他字段 // 3.新增变更履历 // 4.其他新增逻辑及与其他系统交互逻辑 }二,问题偶尔出现(概率极低…...
AI环境搭建步骤(Windows环境)
1. 安装好Anaconda3版本(1) 安装链接:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/?CM&OD本文使用Anaconda3下载链接:Anaconda5(2) 注意安装anaconda时一定要把环境变量加入windows环境中。要没有勾选,安装完后还有手动加入…...
Linux系统之history命令的基本使用
Linux系统之history命令的基本使用一、history命令介绍二、本地环境检查1本地系统版本2.检查操作系统的内核版本三、history的命令帮助四、history命令的基本帮助1.查看所有历史执行命令2.指定历史命令条数3.清除历史命令记录4.引用历史命令5.将历史文件中的信息读入到当前缓冲…...

花7000报了培训班,3个月后我成功“骗”进了阿里,月薪拿16K....
“月薪4000元不如报名学IT,挑战年薪百万”这是大多数培训班在互联网上宣传的口号,简单的16个字却戳中了很多人的痛点,同龄人买车买房,自己却拿着微薄的工资连好一点的房子都租不起,这句口号 彻底激起了底层员工的焦虑&…...

Java-枚举类的使用(详解)
枚举类的使用前言一、何为枚举类?二、自定义枚举类(JDK1.5之前)1、实现1.1 属性1.2 构造器2、代码演示三、用关键字enum定义枚举类(JDK 1.5)1、实现1.1 属性1.2 构造器2、代码演示四、Enum类的方法五、实现接口的枚举类…...

Docker----------Docker轻量级可视化工具Portainer/监控之 CAdvisor+InfluxDB+Granfana
1.是什么 Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理Docker环境,包括单机环境和集群环境。 2 官网 官网 https://www.portainer.io/ https://docs.portainer.io/v/ce-2.9/start/install/server/docker/linux 3.…...
景嘉微7201
220112-驱动与固件-景嘉微7201驱动与固件-三期超翔TF830JM7201显卡黑屏、花屏、竖线或待机唤醒黑屏JM72系列为了让驱动和系统内核解绑,驱动包含核内和核外两个驱动,两个驱动请都务必安装;最近JM7201 替代R7 340 发货了,导致对应通…...

串口、终端应用程序 API termios
UART简介 串口全称为串行接口,也称为COM接口,串行接口指的是比特一位位顺序传输,通信线路简单。使用两根线就可以实现双向通信,一条为TX,一个为RX。串口通信距离远,但速度相对慢,是一种常用的工…...

【服务器搭建】教程七:如何为自己的网站添加运行时间?
前言 哈喽,大家好,我是木易巷! 上一篇服务器搭建个人网站教程是给大家介绍了:网站如何添加备案号? 今天分享:如何为自己的网站添加运行时间? 木易巷添加网页运行时间后的效果 其实和昨天的添…...

【消息中间件】Apache Kafka 教程
文章目录Apache Kafka 概述什么是消息系统?点对点消息系统发布 - 订阅消息系统什么是Kafka?好处用例需要KafkaApache Kafka 基础(一)消息系统1、点对点的消息系统2、发布-订阅消息系统(二)Apache Kafka 简介…...

ARM基础
文章目录1.ARM成长史1.1 ARM发展的里程碑11.2 ARM发展的里程碑21.3 ARM发展的里程碑31.4 ARM发展的里程碑42.ARM的商业模式和生态系统3.先搞清楚各种版本号3.1 ARM 的型号命名问题3.2 ARM 的几种版本号3.3 ARM型号的发展历程4.SoC和CPU的区别 & 外设概念的引入4.1 SoC和CPU…...

Python排序 -- 内附蓝桥题:错误票据,奖学金
排序 ~~不定时更新🎃,上次更新:2023/02/28 🗡常用函数(方法) 1. list.sort() --> sort 是 list 的方法,会直接修改 list 举个栗子🌰 li [2,3,1,5,4] li.sort() print(li) …...
容器化部署是什么意思?有什么优势?
多小伙伴不知道容器化部署是什么意思?不知道容器化部署有什么优势?今天我们就来一起看看。 容器化部署是什么意思? 容器化部署是指将软件代码和所需的所有组件(例如库、框架和其他依赖项)打包在一起,让它…...
1.设计模式简介
一、设计模式的目的 1. 代码重用性 2. 可读性 3. 可扩展性 4. 可靠性 5. 高内聚,低耦合 二、设计模式七大原则 1. 单一职责原则 1)降低类的复杂度,一个类只负责一项职责 2)提高类的可读性,可维护性 3&#x…...
【算法题解】实现一个包含“正负数和括号”的基本计算器
这是一道 困难 题。 题目来自:leetcode 题目 给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。 注意: 不允许使用任何将字符串作为数学表达式计算的内置函数,比如 eval() 。 提示: s 由数字、‘’、‘-’…...
网站服务器如何防护攻击?网站服务器被挂马如何检测
网站服务器是指安装在互联网上的服务器,主要用于提供网站服务。由于网站服务器的重要性,它也是攻击者的活动焦点,因此如何防护攻击就显得尤为重要。本文将分析网站服务器是如何被攻击的以及如何防护攻击。 网站服务器是怎么被攻击的? 网站…...
JavaSE16-面向对象-接口
文章目录一、概念二、格式1.使用interface来定义接口2.implements实现接口三、接口中的成员1.常用成员2.新增成员(不重要)2.1 默认方法2.2 静态方法2.3 私有方法四、继承关系 & 实现关系五、抽象类和接口的使用区别一、概念 接口就是规范\规则&…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
云原生周刊:k0s 成为 CNCF 沙箱项目
开源项目推荐 HAMi HAMi(原名 k8s‑vGPU‑scheduler)是一款 CNCF Sandbox 级别的开源 K8s 中间件,通过虚拟化 GPU/NPU 等异构设备并支持内存、计算核心时间片隔离及共享调度,为容器提供统一接口,实现细粒度资源配额…...

倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...

门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

深入解析光敏传感技术:嵌入式仿真平台如何重塑电子工程教学
一、光敏传感技术的物理本质与系统级实现挑战 光敏电阻作为经典的光电传感器件,其工作原理根植于半导体材料的光电导效应。当入射光子能量超过材料带隙宽度时,价带电子受激发跃迁至导带,形成电子-空穴对,导致材料电导率显著提升。…...