【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 私有方法四、继承关系 & 实现关系五、抽象类和接口的使用区别一、概念 接口就是规范\规则&…...
安卓设备蓝牙键盘快捷键
安卓设备蓝牙键盘快捷键前言注意鼠标按键系统快捷键桌面快捷键输入法快捷键其它快捷键旧快捷键(已失效)前言 安卓设备可以通过蓝牙或有线外接键盘,值得一提的是,安卓平板连接蓝牙键盘和蓝牙鼠标是一个不错的组合。本文以鸿蒙3.0平…...
Puppeteer项目结构梳理
最近接触了一个个人感觉很奈斯的项目,故记录思路如下: puppeteer项目梳理: 入口文件 run.js 入口命令 node run.js YourConfig.json 1、我们可以在自己的config.json里面设置好 ①、登录的用户名密码;aws或其它服务器的access等id,accessKey…...
(02)Unity HDRP Volume 详解
1.概述这篇文章主要针对HDRP中的Volume和Volume Post-processing进行解释,针对于各个组件只能进行部分参数的解释,具体的信息可参考官方资料,这里只是对官方文档的图片效果补充以及笔者自己的理解。看到这里进入正文,请确保你的Un…...
拒绝B站邀约,从月薪3k到年薪47W,我的经验值得每一个测试人借鉴
有时候,大佬们总是会特立独行。因为像我这样的常人总是想不通,究竟是怎样的情境,连B站这样的大厂面试都可以推掉? 缘起一通电话,踏出了改变人生轨迹的第一步 我是小瑾,今年28岁,2016年毕业于陕…...
分享一种实用redis原子锁的方式
1. setnx(lockkey, 当前时间过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间…...
【华为OD机试】 字符串解密(C++ Java JavaScript Python)
题目描述 给定两个字符串string1和string2。 string1是一个被加扰的字符串。 string1由小写英文字母(’a’’z’)和数字字符(’0’’9’)组成,而加扰字符串由’0’’9’、’a’’f’组成。 string1里面可能包含0个或多个加扰子串,剩下可能有0个或多个有效子串,这些有…...
金三银四,助力你的大厂梦,2023年软件测试经典面试真题(1)(共3篇)
前言 金三银四即将到来,相信很多小伙伴要面临面试,一直想着说分享一些软件测试的面试题,这段时间做了一些收集和整理,下面共有三篇经典面试题,大家可以试着做一下,答案附在后面,希望能帮助到大…...
假如面试官要你手写一个promise
promise 在开发中,经常需要用到promise,promise具有很多特性,这一次将对promise特性进行总结,并从零写一个promise。 步骤一 Promise特点 1,创建时需要传递一个函数,否则会报错2,会给传入的函…...
【leetcode】寻找重复数
题目链接:寻找重复数https://leetcode.cn/problems/find-the-duplicate-number/ 方法一:快慢指针 因为只有一个数字是重复的,且一个数字正好对应一个唯一的下标,所以可以将数组抽象为一个链表,假定数组为{1,2,3,4,5,…...
LeetCode 1247. Minimum Swaps to Make Strings Equal【数学,贪心,字符串】
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...
苏南建设集团网站/nba最新交易汇总
深海才会有鲸鱼 首先,在此,感怀毛星云先生,虽并无交集,但若曾我有幸认识你的话,我想我一定是你的忠实的粉丝,你一定是我的追逐的榜样。 听到这样的消息真的好惋惜,如此勤奋、聪颖、执着且有梦想…...
设计制作个人网站/国内网络销售平台有哪些
学习内容简单查询汇总分析复杂查询多表查询如何提高SQL查询效率简单查询创建学校数据库的表查找学生查询姓‘猴’的学生名单查询姓名中最后一个字是‘猴’的学生名单查询姓名中带‘猴’的学生名单select * from student where 姓名 like 猴%;select * from student where 姓名 …...
网站你的后台管理系统用什么做/职业培训热门行业
在突发大量内存的请求期间,哪些工具或最佳实践可用于在Java服务中正常降级服务? 有问题的应用程序是多线程的。 处理每个请求所需的工作量可能相差很大,并且不容易拆分和并行化。我很担心编写与堆使用和GC有关的应用程序级代码,但…...
建设银行办信用卡网站/网络推广公司专业网络
发这篇心得主要想告诉大家博主的博客虽然是随心情发的,但博主真的没有偷懒,一直在学习!而且对现在的博主来说专业技能固然重要,但在未来规划还没定之前,广度还是更重要的!对的,我要摊牌…...
wordpress文章页设置/产品互联网推广
安装破解教程 1、下载安装包,解压缩并运行安装,选择需要安装的组件 2、阅读软件协议,勾选我接受协议 3、正在安装中,请耐心等待 4、安装成功,点击EXIT INSTALLER退出软件安装向导 5、断开网络连接,以管理员…...
网络公司网站建/西安企业seo外包服务公司
RustDesk一款开源的远程工具,支持Windows,Macos,Linux,Android多个操作系统,安装包大小只有12M左右,无需任何配置,打开就可以直接使用,体验目前看来相当不错,支持自建服务…...