C++:List的使用和模拟实现
创作不易,感谢三连!!
一、List的介绍
list的文档介绍
1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
要注意的是,list开始就不支持下标访问了,所以要访问都要以迭代器为准
void Print(const list<int>& l)
{//迭代去区间遍历list<int>::const_iterator it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;//范围for遍历for (auto e : l)cout << e << " ";cout << endl;
}
二、List的使用注意事项

博主觉得跟之前vector的基本上差不了多少,如果不会看文档用库里面的list的可以去看博主只管关于string和vector的使用。
C++:String类的使用-CSDN博客
C++:Vector的使用-CSDN博客
下面直接介绍List使用中的易错点
2.1 List的迭代器失效问题
我们之前学习vector的时候,知道了insert和erase都有可能存在迭代器失效的问题,那list会出现这种情况吗??下面来进行分析

insert插入新节点的迭代器,因此insert不可能会出现失效的问题。

而earse必然会失效,因为该迭代器对应的节点被删除了。如果我们想继续用的话,就得利用返回值去更新迭代器,返回值是被删除元素的下一个位置的迭代器。
2.2 List中sort的效率测试
我们用一段代码来测试一下list中sort的性能
void test_op()
{srand((unsigned int)time(NULL));const int N = 1000000;vector<int> v;v.reserve(N);list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){int e = rand();lt1.push_back(e);lt2.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();for (auto e : lt1){v.push_back(e);}sort(v.begin(), v.end());size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();//list调用自己的sortint begin2 = clock();lt2.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}
会发现哪怕我先拷贝到vector排完再拷贝回去效率都比list的sort效率高,所以list的sort实际中意义不是很大!!
三、模拟实现的注意事项
还是跟之前模拟实现一样,先看看SGI版本的源码 ,list本质上是带头双向链表
第一部分 链表节点
第二部分 迭代器
第三部分、链表
这里我们可以先实现链表节点结构体 这里用sturct把权限放开。
//节点的封装
template<class T>
struct list_node
{list_node<T>* _prev;list_node<T>* _next;T _data;//节点的构造函数list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _data(val){}
};
然后封装一个链表,将头结点作为自己的成员变量封装起来
template<class T>
class list
{typedef list_node<T> node;//typedef可以帮助我们简洁代码private:node* _head;};
下面开始进行我们的重中之重——迭代器
四、正向迭代器的实现
2.1 正向迭代器的封装
在学习Vector的时候,我们发现其实vector的迭代器就是一个原生指针,所以我们将他改了名字
这得益于vector空间连续的特点,所以对指针进行加和减再进行解引用可以访问到我们想要的元素,但是链表可以吗?
虽然看似我们好像用箭头连接起来了,但其实他们空间上是不连续的,那我们对一个节点指针进行加减,就很难说能不能找到下一个节点,更多的是找不到的情况
那我们思考一样,如果我们要搞一个迭代器,我们希望怎么去得到我们的数据呢??我们希望我们解引用的时候,可以拿到他的data,希望++的时候,可以拿到他的next,--的时候,可以拿到他的prev。 那我们怎么去改变原生操作符的行为呢?答案就是运算符重载!所以我们可以将迭代器单独封装成一个类去管理节点,改变运算符的行为!!
我们先进行实现,然后再慢慢分析
//封装迭代器template<class T, class Ref, class Ptr>//Ref用于引用是否const,Ptr用于指针是否conststruct list_iterator{typedef list_node<T> node;typedef list_iterator<T, Ref, Ptr> self;node* _node;//迭代器的构造函数list_iterator(node* n)//迭代器的构造:_node(n){}//实现*Ref operator*() const{return _node->_data;}//实现->Ptr operator->() const{return &operator*(); //本来是两个->,为了增强可读性,我们封装了这个函数 比如当我们存储的结构体解引用后有多个成员,那么我们可以通过箭头的直线去找到对应我们想要的成员 }//前置++self& operator++(){_node = _node->_next;return *this;}//后置++self operator++(int){self temp(*this);++*this;return temp;}//前置--self& operator--(){_node = _node->_prev;return *this;}//后置--self operator--(int){self temp(*this);--*this;return temp;}//!=bool operator!=(const self& s) const{return _node != s._node;}//==bool operator==(const self& s) const{return _node == s._node;}};
第一个模版参数是类型,第二个模版参数是引用,第三个模版参数是指针
Ref和Ptr是用来区分正常的迭代器和const修饰的迭代器,Ref是T&或者是const T&,这样可以在某些时候我们去限制data不能被修改。而Ptr是T*或者是const T*,重载箭头的作用是如果我们data存储的是一个自定义类型,这个时候如果直接解引用肯定是不行的,所以我们的箭头可以在解引用的时候先返回data的地址,然后我们就可以通过箭头去访问他不同的成员变量。
下面举个data存的是自定义类型的例子
2.2 迭代器的使用
template<class T>
class list
{typedef list_node<T> node;//typedef可以帮助我们简洁代码
public://正向迭代器typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;//可读可写正向迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}//可读不可写正向迭代器const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}private:node* _head;};
这边我们用到了匿名对象。
思考:这里的const迭代器为什么不能直接用const修饰普通迭代器??
因为typedef碰到const的话,就不是简单的字符串替换 实际上你以为的const T* ,在这里变成了T*const ,因为迭代器我们是希望他可以进行++和--的,而我们只是不希望他指向的内容给改变,所以我们的const要修饰的是指针的内容,而不是修饰指针。
五、list相关的成员函数
3.1 构造函数
1、默认构造函数
因为无论如何都要有哨兵节点,所以我们直接封装一个
void empty_init(){_head = new node;_head->_next = _head;_head->_prev = _head;}
所以可以这么写
//默认构造函数list(){empty_init();}
2、有参构造函数
//有参构造函数
list(size_t n, const T& val = T())
{empty_init();for (size_t i = n; i > 0; --i)push_back(val);
}
3、迭代器区间构造函数
//迭代器区间构造函数
template <class InputIterator>
list(InputIterator first, InputIterator last)
{empty_init();while (first != last){push_back(*first);++first;}
}
4、拷贝构造的传统写法
传统方法就是一个个拷贝过去
//拷贝构造函数传统写法
list(const list<T>& lt)
{empty_init();for (auto e : lt)push_back(e);
}
5、拷贝构造的现代写法+swap
现代写法就是,我先创建一个临时对象让他利用被拷贝对象的迭代器构造出来,然后再交换,窃取革命成功,被利用完后的临时对象会在栈帧结束后被清除(典型的资本家思维)
//交换函数void swap(list<T>& temp){std::swap(_head, temp._head);}//拷贝构造函数的现代写法list(const list<T>& lt){empty_init();list<T> temp(lt.begin(), lt.end());//复用迭代器区间构造,让别人构造好了,我再窃取革命成果swap(temp);}
3.2 clear和析构函数
list不像vector一样,不能直接用头指针delete,因为空间不连续,所以要一个个节点去delete,所以在这之前,我们可以先实现clear,clear的作用是把链表清空,只剩一个头节点,然我们的析构函数再复用clear,然后再单独delete头节点就行了!!
//clear 只留一个头节点void clear(){iterator it = begin();while (it != end())it = erase(it);}//析构函数~list(){clear();delete _head;_head = nullptr;}
3.3 赋值重载和assign
assign和=的本质上都是,先将原来的空间的内容给清空,换成的内容。 只不过区别就是assign可以利用迭代器去控制被替换的范围,也可以自己去换成n个一样的元素。所以我们先实现assign,再实现=
1、assign直接替换
//assign(直接替换)
void assign(size_t n, const T& val)
{clear();for (size_t i = n; i > 0; --i)push_back(val);
}
2、assign迭代器区间替换
//assign(迭代器区间替换)template <class InputIterator>void assign(InputIterator first, InputIterator last){clear();while (first != last){push_back(*first);++first;}}
3、assign直接替换重载(防止间接寻址)
思考:我们的本意是将lt2替换成5个2,我们发现我们调的竟然是迭代器区间构造的assign,为什么会这样呢?????
因为重载类型会优先找最匹配的,assign的第一个版本的n是size_t类型,我们传的整数默认是int所以会发生强制类型转化,而第二个版本恰好可以变成两个int,所以他会走迭代器区间版本。所以此时有两个方案,第一个方案是我们要在第一个参数后面加u,但是这不符合我们的使用习惯,所以我们可以采用第二个方案,写个重载版本。
//assign重载版本 防止间接寻址
void assign(int n, const T& val)
{clear();for (size_t i = n; i > 0; --i)push_back(val);
}
4、赋值重载传统写法
直接复用assign
// 赋值重载的传统写法list<T>& operator=(const list<T>& lt){assign(lt.begin(), lt.end());return *this;}
5、赋值重载的现代写法
list<T>& operator=(list<T> lt)
{swap(lt);//利用值传递拷贝的临时对象进行交换return *this;
}
3.4 修改相关函数(Modifiers)
1、empty、size
//size
size_t size() const
{size_t n = 0;for (auto e : *this)++n;return n;
}
//empty
bool empty() const
{return node->next == node;
}
2、insert
我们先实现insert和erase,其他的就可以直接复用了
//insert
iterator insert(iterator pos, const T& val)
{node* cur = pos._node;//记录当前节点node* prev = cur->_prev;//记录前驱节点node* newnode = new node(val);//建立新节点//开始改变指向newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;newnode->_prev = prev;return iterator(newnode);
}
3、erase
//erase
iterator erase(iterator pos)
{assert(pos != end());//确保不是删除哨兵位置node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);//利用匿名对象返回
}
4、尾插尾删头插头删
//pushback 尾插
void push_back(const T& val)
{insert(end(), val);
}
//pushfront 头插
void push_front(const T& val)
{insert(begin(), val);
}
//popback 尾删
void pop_back()
{erase(--end());
}
//popfront 头删
void pop_front()
{erase(begin());
}
5、resize
//resize 如果n小于当前容量的大小,则内容将减少到前n个元素 当n大于容器大小时,则在末尾插入任意容量的内容。
void resize(size_t n, const T& val = T())
{size_t sz = size();//记录当前的有效元素的个数while (n < sz){pop_back();--sz;}while (n > sz){push_back(val);++sz;}
}
六、反向迭代器的实现
sgi版本下的反向迭代器,其实就是将构建一个反向迭代器的类将正向迭代器封装起来,这个时候正向迭代器的++就是反向迭代器的--
template<class iterator, class Ref, class Ptr>
struct list_reverse_iterator
{typedef list_reverse_iterator<iterator, Ref, Ptr> self;//用正向迭代器去构造反向迭代器list_reverse_iterator(iterator it):_cur(it){}//解引用Ref operator*() const{iterator temp = _cur;--temp;return *temp;}//实现->Ptr operator->() const{return &operator*();}//前置++self& operator++(){--_cur;return *this;}//后置++self operator++(int){iterator temp(_cur);--*this;return temp;}//前置--self& operator--(){++_cur;return *this;}//后置--self operator--(int){iterator temp(_cur);++*this;return temp;}//不等于bool operator!=(const self& s){return _cur != s._cur;}//等于bool operator==(const self& s){return _cur == s._cur;}iterator _cur;
};
思考:为什么解引用的是前一个位置的元素???
通过这个我们来看看vector下的反向迭代器代码:
复用性很高,和list的区别就是因为是随机迭代器,所以多了+和-的接口,第二个就是不需要->,所以其实模版也可少传一个
七、list模拟实现的全部代码
//c++喜欢ListNode驼峰法命名 为了和STL风格一致,我们也用小写
//但是STL版本和java喜欢小写带_
namespace cyx
{//节点的封装template<class T>struct list_node{list_node<T>* _prev;list_node<T>* _next;T _data;//节点的构造函数list_node(const T& val = T()):_prev(nullptr), _next(nullptr), _data(val){}};//封装迭代器template<class T, class Ref, class Ptr>//Ref用于struct list_iterator{typedef list_node<T> node;typedef list_iterator<T, Ref, Ptr> self;node* _node;//迭代器的构造函数list_iterator(node* n)//迭代器的构造:_node(n){}//实现*Ref operator*() const{return _node->_data;}//实现->Ptr operator->() const{return &operator*(); //本来是两个->,为了增强可读性,我们封装了这个函数 比如当我们存储的结构体解引用后有多个成员,那么我们可以通过箭头的直线去找到对应我们想要的成员 }//前置++self& operator++(){_node = _node->_next;return *this;}//后置++self operator++(int){self temp(*this);++*this;return temp;}//前置--self& operator--(){_node = _node->_prev;return *this;}//后置--self operator--(int){self temp(*this);--*this;return temp;}//!=bool operator!=(const self& s) const{return _node != s._node;}//==bool operator==(const self& s) const{return _node == s._node;}};template<class iterator, class Ref, class Ptr>struct list_reverse_iterator{typedef list_reverse_iterator<iterator, Ref, Ptr> self;//用正向迭代器去构造反向迭代器list_reverse_iterator(iterator it):_cur(it){}//解引用Ref operator*() const{iterator temp = _cur;--temp;return *temp;}//实现->Ptr operator->() const{return &operator*();}//前置++self& operator++(){--_cur;return *this;}//后置++self operator++(int){iterator temp(_cur);--*this;return temp;}//前置--self& operator--(){++_cur;return *this;}//后置--self operator--(int){iterator temp(_cur);++*this;return temp;}//不等于bool operator!=(const self& s){return _cur != s._cur;}//等于bool operator==(const self& s){return _cur == s._cur;}iterator _cur;};template<class T>class list{typedef list_node<T> node;//typedef可以帮助我们简洁代码public://正向迭代器typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;//typedef __list_const_iterator<T> const_iterator;不行//反向迭代器typedef list_reverse_iterator<iterator, T&, T*> reverse_iterator;typedef list_reverse_iterator<iterator, const T&, const T*> const_reverse_iterator;//可读可写正向迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}//可读不可写正向迭代器const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}//可读可写的反向迭代器reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}//可读不可写的反向迭代器const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}//默认构造函数list(){empty_init();}//有参构造函数list(size_t n, const T& val = T()){empty_init();for (size_t i = n; i > 0; --i)push_back(val);}//迭代器区间构造函数template <class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);++first;}}//拷贝构造函数传统写法/*list(const list<T>& lt){empty_init();for (auto e : lt)push_back(e);}*///交换函数void swap(list<T>& temp){std::swap(_head, temp._head);}//拷贝构造函数的现代写法list(const list<T>& lt){empty_init();list<T> temp(lt.begin(), lt.end());//复用迭代器区间构造,让别人构造好了,我再窃取革命成果swap(temp);}//assign(迭代器区间替换)template <class InputIterator>void assign(InputIterator first, InputIterator last){clear();while (first != last){push_back(*first);++first;}}//assign(直接替换)void assign(size_t n, const T& val){clear();for (size_t i = n; i > 0; --i)push_back(val);}//assign重载版本 防止间接寻址void assign(int n, const T& val){clear();for (size_t i = n; i > 0; --i)push_back(val);}// 赋值重载的传统写法list<T>& operator=(const list<T>& lt){assign(lt.begin(), lt.end());return *this;}// 赋值重载的现代写法//list<T>& operator=(list<T> lt)//{// swap(lt);//利用值传递拷贝的临时对象进行交换// return *this;//}//析构函数~list(){clear();delete _head;_head = nullptr;}//sizesize_t size() const{size_t n = 0;for (auto e : *this)++n;return n;}//insertiterator insert(iterator pos, const T& val){node* cur = pos._node;//记录当前节点node* prev = cur->_prev;//记录前驱节点node* newnode = new node(val);//建立新节点//开始改变指向newnode->_next = cur;cur->_prev = newnode;prev->_next = newnode;newnode->_prev = prev;return iterator(newnode);}//eraseiterator erase(iterator pos){assert(pos != end());//确保不是删除哨兵位置node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);//利用匿名对象返回}//pushback 尾插void push_back(const T& val){insert(end(), val);}//pushfront 头插void push_front(const T& val){insert(begin(), val);}//popback 尾删void pop_back(){erase(--end());}//popfront 头删void pop_front(){erase(begin());}//clear 只留一个头节点void clear(){iterator it = begin();while (it != end())it = erase(it);}//resize 如果n小于当前容量的大小,则内容将减少到前n个元素 当n大于容器大小时,则在末尾插入任意容量的内容。void resize(size_t n, const T& val = T()){size_t sz = size();//记录当前的有效元素的个数while (n < sz){pop_back();--sz;}while (n > sz){push_back(val);++sz;}}//emptybool empty() const{return node->next == node;}private:node* _head;//用来初始化 类内部自己用,设私有void empty_init(){_head = new node;_head->_next = _head;_head->_prev = _head;}};
接口暂时就搞这些,如果后面有时间再写些比较复杂的接口,这一篇不太好理解,讲解不到位还请见谅
相关文章:
C++:List的使用和模拟实现
创作不易,感谢三连!! 一、List的介绍 list的文档介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。 2. list的底层是双向链表结构,双向链表中每个元素存储在互不…...
20个Python函数程序实例
前面介绍的函数太简单了: 以下是 20 个不同的 Python 函数实例 下面深入一点点: 以下是20个稍微深入一点的,使用Python语言定义并调用函数的示例程序: 20个函数实例 简单函数调用 def greet():print("Hello!")greet…...
Wireshark——获取捕获流量的前N个数据包
1、问题 使用Wireshark捕获了大量的消息,但是只想要前面一部分。 2、方法 使用Wireshark捕获了近18w条消息,但只需要前5w条。 选择文件,导出特定分组。 输入需要保存的消息范围。如:1-50000。 保存即可。...
006-浏览器输入域名到返回
浏览器输入域名到返回 1、URL 输入2、DNS 域名解析3、建立 TCP 连接三次握手概念三次握手理解 4、发送 HTTP/HTTPS 请求5、服务器处理,并返回响应6、浏览器解析并渲染页面7、请求结束,端口 TCP 连接四次挥手概念四次挥手理解 1、URL 输入 2、DNS 域名解析…...
【kubernetes】关于k8s集群如何将pod调度到指定node节点?
目录 一、k8s的watch机制 二、scheduler的调度策略 Predicate(预选策略) 常见算法: priorities(优选策略)常见的算法有: 三、k8s的标签管理之增删改查 四、k8s的将pod调度到指定node的方法 方案一&am…...
【框架】React和Vue的异同
1. 前言 React对于原生JS要求会高一级,国外React用的多,国内Vue用的多。 2. 共同点 组件化函数式编程 (vue3函数式编程、vue2声明式编程)单向数据流,数据驱动视图VirtualDOM Diff算法操作DOM社区成熟,…...
如何选择阅读软件技术学习书籍
如何选择阅读软件技术学习书籍 这里以软件技术学习的角度结合自身感悟谈谈,如何选择阅读书籍。 人的时间和精力都是非常有限的,软件技术学习者如何选择阅读书籍。以下是从我的经验教训总结的一些体会: 1、确定自己的兴趣领域和阅读目标 选…...
做抖店用平板能代替电脑操作吗?抖店运营相关注意事项,注意规避
我是王路飞。 之前给你们讲在抖音开店流程的时候,说过开店需要用到电脑,还需要执照、资金、时间等等。 那么做抖店用平板能代替电脑操作吗? 这个问题其实有很多新手问过我,有的甚至是想直接在手机上操作,想着能省点…...
【FastChat】用于训练、服务和评估大型语言模型的开放平台
FastChat 用于训练、服务和评估大型语言模型的开放平台。发布 Vicuna 和 Chatbot Arena 的存储库。 隆重推出 Vicuna,一款令人印象深刻的开源聊天机器人 GPT-4! 🚀 根据 GPT-4 的评估,Vicuna 达到了 ChatGPT/Bard 90%* 的质量&…...
从根到叶:深入理解二叉搜索树
我们的心永远向前憧憬 尽管活在阴沉的现在 一切都是暂时的,转瞬即逝, 而那逝去的将变为可爱 🌝(俄) 普希金 <假如生活欺骗了你> 1.二叉搜索树的概念 概念:搜索树(Search Tree)是一种有序的数据结构,用于存储和组…...
网络信息安全:11个常见漏洞类型汇总
一、SQL注入漏洞 SQL注入攻击(SQL Injection),简称注入攻击、SQL注入,被广泛用于非法获取网站控制权,是发生在应用程序的数据库层上的安全漏洞。 在设计程序,忽略了对输入字符串中夹带的SQL指令的检查&…...
阿里云服务器使用教程_2024建站教程_10分钟网站搭建流程
使用阿里云服务器快速搭建网站教程,先为云服务器安装宝塔面板,然后在宝塔面板上新建站点,阿里云服务器网aliyunfuwuqi.com以搭建WordPress网站博客为例,来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流…...
【排序算法】推排序算法解析:从原理到实现
目录 1. 引言 2. 推排序算法原理 3. 推排序的时间复杂度分析 4. 推排序的应用场景 5. 推排序的优缺点分析 5.1 优点: 5.2 缺点: 6. Java、JavaScript 和 Python 实现推排序算法 6.1 Java 实现: 6.2 JavaScript 实现: 6.…...
Python爬虫实战(基础篇)—13获取《人民网》【最新】【国内】【国际】写入Word(附完整代码)
文章目录 专栏导读背景测试代码分析请求网址请求参数代码测试数据分析利用lxml+xpath进一步分析将获取链接再获取文章内容测试代码写入word完整代码总结专栏导读 🔥🔥本文已收录于《Python基础篇爬虫》 🉑🉑本专栏专门针对于有爬虫基础准备的一套基础教学,轻松掌握Py…...
常见控件应用
常见控件应用 1.操作Ajax选项2.滑动滑块操作 1.操作Ajax选项 Ajax即Asynchronous JavaScript and XML(异步JavaScript和XML),是指一种创建交互式、快速动态网页应用的网页开发技术。通过在后台与服务器进行少量数据交换,Ajax可以…...
什么是降压恒流芯片?它有什么作用?
降压恒流芯片是一种电子元件,用于将高电压或高电流的输入电源转换为稳定的低电压输出电源,并同时保持恒定的电流输出。 降压恒流芯片的作用有以下几点: 将高电压降低到适合驱动车灯的工作电压,确保车灯亮度稳定。 在负载变化时…...
14:00面试,15:00就出来了,问的问题过于变态了。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到2月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
Maven的settings.xml配置
maven的两大配置文件:settings.xml和pom.xml。其中settings.xml是maven的全局配置文件,pom.xml则是文件所在项目的局部配置 标签servers: 一般,仓库的下载和部署是在pom.xml文件中的repositories和distributionManagement元素中定…...
利用redis实现秒杀功能
6、秒杀优化 这个是 图灵 的redis实战里面的一个案例 6.1 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤…...
vscode 使用ssh进行远程开发 (remote-ssh),首次连接及后续使用,详细介绍
在vscode添加remote ssh插件 首次连接 选择左侧栏的扩展,并搜索remote ssh 它大概长这样,点击安装 安装成功后,在左侧栏会出现远程连接的图标,点击后选择ssh旁加号便可以进行连接。 安装成功后vscode左下角会有一个图标 点击图…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

