当前位置: 首页 > news >正文

【C++】list基本接口+手撕 list(详解迭代器)

父母就像迭代器,封装了他们的脆弱...... 

手撕list目录:

一、list的常用接口及其使用

1.1list 构造函数与增删查改

1.2list 特殊接口

1.3list 排序性能分析

二、list 迭代器实现(重点+难点)

关于迭代器的引入知识:

2.1迭代器的分类

2.2 list 迭代器失效问题(和vector有差异)

2.3list 迭代器源码模板

2.4list 整体基本框架

三、手撕list迭代器

3.1重载operator*()

3.2重载++、–、!=

3.3 利用类模板优化

四、增删查改

4.1 insert(参数必须加引用,担心非内置类型)和erase

4.2 push_back和push_front

4.3  pop_back和pop_front

五、list 构造+赋值重载

5.1默认构造+迭代器区间构造+拷贝构造

5.2 赋值重载现代写法

5.3 类名和类型的问题(C++的一个坑)

六、list和vector的对比(重点)

七、源码合集


一、list的常用接口及其使用

1.1list 构造函数与增删查改

list 是可以在常数范围内在任意位置进行插入和删除的序列式容器,其底层是带头双向循环链表;list 常用接口的使用和 string、vector 系列容器的接口使用一样,这里我不详细介绍,请看我们的老朋友:cplusplus.com - The C++ Resources Network

构造函数:

构造函数(constructor)接口说明
list (size_type n, const value_type& val = value_type())构造的 list 中包含n个值为val的元素
list()构造空的 list
构造空的 list拷贝构造函数
list (InputIterator first, InputIterator last)用 [first, last) 区间中的元素构造 list

增删查改: 

函数说明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

注意:

1、由于 list 的物理结构是非连续的 – 前一个节点地址和后一个节点地址的位置关系是随机的,所以 list 不支持随机访问,自然也就不支持 [ ] 操作

2、list 不支持reserve操作,因为 list 的节点是使用时开辟,使用完销毁,不能预留空间;

(从这个特点也容易看出来,如果需要一直插入删除元素,利用list更好

1.2list 特殊接口

除了上述 STL 容器基本都有的一般接口外,list 还提供一些独有的特殊操作接口,如下:

函数声明接口说明
splice将 list1 中的元素转移到 list2 中
remove移除 list 中的指定元素
unique链表去重(sort之后才可以用)
merge合并两个链表
sort链表排序(探究为什么list自己写sort
reverse链表逆置

题外话: 为什么list需要自己实现sort接口??难道说库中的封装性不好?效率不高?

 我们先使用库中自己的sort函数:

我们使用算法库中的sort函数:

void test_sort()
{list<int> l1{ 5,6,4,8,9,2,7 };//C++ 11写法sort(l1.begin(),l1.end());for(auto l : l1){cout << l << " ";}cout << endl;
}

报错了(意外之中,如果不报错我还写这个知识点干啥 doge) 报错原因说没有-迭代器

让我们看看sort源码~

这一切的一切都是因为sort的迭代器引起的!! 

注意:

1、链表排序只能使用 list 提供的 sort 接口,而不能使用 algorithm 提供的 sort 接口,因为链表物理地址不连续,迭代器为双向迭代器,不支持 + - 操作而算法库中的 sort 函数需要支持 + - 的随机迭代器

2、链表去重之前必须保证链表有序,否则去重不完全;

3、两个有序链表合并之后仍然保存有序;

 最后,虽然 list 提供了这些具有特殊功能的接口,它们也确实有一定的作用,但是实际上这些特殊接口使用频率非常低,包括 sort 接口 (链表排序的效率太低)。

1.3list 排序性能分析

虽然链表排序只能使用 list 提供的 sort 接口,而不能使用 algorithm 提供的 sort 接口,但是其使用频率仍然非常低,这是由于链表排序的效率太低了,我们可以通过对比两组测试数据来直观的感受链表排序的效率。

测试一:vector 排序与 list 排序性能对比

//vector sort 和 list sort 性能对比 -- release 版本下
void test_op1() {srand((size_t)time(0));const int N = 1000000;  //100万个数据vector<int> v;v.reserve(N);list<int> lt;for (int i = 0; i < N; ++i){auto e = rand();v.push_back(e);lt.push_back(e);}//vector sortint begin1 = clock();sort(v.begin(), v.end());int end1 = clock();//list sortint begin2 = clock();lt.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}

测试二:list 直接进行排序与将数据拷贝到 vector 中使用 vector 排序后再将数据拷回 list 中性能对比

//list sort 与 将数据转移到 vector 中进行排序后拷贝回来性能对比 -- release 版本下
void test_op2()
{srand(time(0));const int N = 1000000;  //100万个数据list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt1.push_back(e);lt2.push_back(e);}//list sort -- lt1int begin1 = clock();lt1.sort();int end1 = clock();// 将数据拷贝到vector中排序,排完以后再拷贝回来 -- lt2int begin2 = clock();vector<int> v;v.reserve(N);for (auto e : lt2)  //拷贝{v.push_back(e);}sort(v.begin(), v.end());  //排序lt2.assign(v.begin(), v.end());  //拷贝int end2 = clock();printf("list1 sort:%d\n", end1 - begin1);printf("list2 sort:%d\n", end2 - begin2);
}

 可以看到,list sort 的效率远低于 vector sort,甚至于说,直接使用 list sort 的效率都不如先将数据拷贝到 vector 中,然后使用 vector sort,排序之后再将数据拷贝回 list 中快;所以list中的sort接口是很挫的!!


二、list 迭代器实现(重点+难点)

关于迭代器的引入知识:

迭代器的价值在于封装底层的实现,不具体暴露底层的实现细节,提供统一的访问方式

iterator只是代言人!!真正的牛逼大佬其实是_list_iterator

为什么在 list 中将迭代器搞成指针这招不好用了呢??

在数组中,*指针就是元素,指针++就是 +sizeof(T) 对象大小,没办法,谁叫他们物理空间连续,结构NB,所以对于vector和string类而言,物理空间是连续的,原生的指针就是迭代器了,解引用就是数据了但是对于这里的list而言,空间是不连续的

解决方法:

此时如果解引用是拿不到数据的(空间不连续),更不用说++指向下一个结点了。所以,对于list的迭代器,原生指针已经不符合我们的需求了,我们需要去进行特殊处理:进行类的封装。我们可以通过类的封装以及运算符重载支持,这样就可以实现像内置类型一样的运算符

迭代器的俩个特征:

1.解引用2.++ / --

运算符重载的大任务:

实现解引用operator*()和++函数

2.1迭代器的分类

按照迭代器的功能,迭代器一共可以分为以下三类:

  • 单向迭代器 – 迭代器仅仅支持 ++ 和解引用操作(单链表,哈希)

  • 双向迭代器 – 迭代器支持 ++、-- 和解引用操作,但不支持 +、- 操作(list 双向链表)

  • 随机迭代器 – 迭代器不仅支持 ++、-- 和解引用操作,还支持 +、- 操作,即迭代器能够随机访问(string,vector)

这也充分说明,vector和string是可以用库中的sort函数的


迭代器还可以分成普通迭代器和const迭代器俩类:

//1.const T* p1
list<int>::const_iterator cit = lt.begin();
//2.T* const p2
const list<int>::iterator cit = lt.begin();
//不符合const迭代器的行为,因为保护迭代器本身不能修改,那么我们也就不能++迭代器

灵魂拷问:const迭代器是p1还是p2?p1

const迭代器类似p1的行为,保护指向的对象不被修改,迭代器本身可以修改

2.2 list 迭代器失效问题(和vector有差异)

vector迭代器失效:insert扩容+erase的时候会失效

和 vector 不同,list 进行 insert 操作后并不会产生迭代器失效问题,因为 list 插入的新节点是动态开辟的,同时由于 list 每个节点的物理地址是不相关的,所以插入的新节点并不会影响原来其他节点的地址

但是 list erase 之后会发生迭代器失效,因为 list 删除节点会直接将该节点释放掉,此时我们再访问该节点就会造成越界访问

2.3list 迭代器源码模板

我们知道,迭代器是类似于指针一样的东西,即迭代器要能够实现指针相关的全部或部分操作 – ++、–、*、+、-;对我们之前 string 和 vector 的迭代器来说,迭代器就是原生指针,所以它天然的就支持上述操作;

但是对于 list 来说,list 的节点是一个结构体,同时 list 每个节点的物理地址是不连续的,如果此时我们还简单将节点的指针 typedef 为迭代器的话,那么显然它是不能够实现解引用、++ 等操作的,所以我们需要用结构体/类来对迭代器进行封装,再配合运算符重载等操作让迭代器能够实现解引用、++、-- 等操作

框架代码如下:

//节点定义
template <class T>
struct __list_node {typedef void* void_pointer;void_pointer next;void_pointer prev;T data;
};//迭代器定义
typedef __list_iterator<T, T&, T*>   iterator;
typedef __list_iterator<T, const T&, const T*>  const_iterator;//迭代器类
template<class T, class Ref, class Ptr>
struct __list_iterator {typedef __list_iterator<T, Ref, Ptr>  self;typedef __list_node<T>* link_type;  //节点的指针link_type node; //类成员变量__list_iterator(link_type x) : node(x) {} //将节点指针构造为类对象//... 使用运算符重载支持迭代器的各种行为self& operator++() {...}self& operator--() {...}Ref operator*() const {...}
};

2.4list 整体基本框架

namespace lzy
{//结点template<class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x)//节点的构造函数及初始化列表:_next(nullptr), _prev(nullptr), _data(x){}};template<class T>class list{typedef list_node<T> node;public://迭代器typedef __list_iterator<T> iterator;typedef __list_const_iterator<T> const_iterator;//构造list(){_head = new node(T());_head->_next = _head;_head->_prev = _head;}private:node* _head;size_t _size;};
}

三、手撕list迭代器

迭代器的实现我们需要去考虑普通迭代器和const迭代器。这两种迭代器的不同,也会带来不同的接口。我们可以分别单独去进行实现,我们先来看一看简单的构造迭代器,只需要提供一个结点即可,看一看实现的基本框架:

    template<class T>struct __list_iterator{typedef list_node<T> node;node* _pnode;__list_iterator(node* p):_pnode(p){}}

为什么迭代器不写拷贝构造函数?浅拷贝真的可以吗?

对于迭代器的拷贝构造和赋值重载我们并不需要自己去手动实现,编译器默认生成的就是浅拷贝,而我们需要的就是浅拷贝,这也说明了,并不是说如果有指针就需要我们去实现深拷贝,而且迭代器不需要写析构函数,所以说不需要深拷贝

为什么聊这个问题?因为list<int>::iterator it=v.begin() 这就是一个拷贝构造

3.1重载operator*()

这个比较简单,就是要获取迭代器指向的数据,并且返回数据的引用:

T& operator*()
{return _pnode->_data;
}

 3.2重载++、–、!=

	   __list_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}__list_iterator<T>& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_iterator<T>& it){return _pnode != it._pnode;}

 如果按照上面的做法,我们在来看看此时普通迭代器和const迭代器的区别:

//typedef __list_iterator<T> iterator;
//typedef __list_const_iterator<T> const_iterator;template<class T>struct __list_iterator{typedef list_node<T> node;node* _pnode;__list_iterator(node* p):_pnode(p){}T& operator*(){return _pnode->_data;}__list_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}__list_iterator<T>& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_iterator<T>& it){return _pnode != it._pnode;}};//跟普通迭代器的区别:遍历,不能用*it修改数据template<class T>struct __list_const_iterator{typedef list_node<T> node;node* _pnode;__list_const_iterator(node* p):_pnode(p){}const T& operator*(){return _pnode->_data;}__list_const_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}__list_const_iterator<T>& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_const_iterator<T>& it){return _pnode != it._pnode;}};

代码冗余!!!代码冗余!!!代码冗余!!!

如果是这样子去实现的话,我们就会发现,这两个迭代器的实现并没有多大的区别,唯一的区别就在于operator*的不同。const迭代器和普通迭代器的唯一区别就是普通迭代器返回T&,可读可写,const迭代器返回const T&,可读不可写。我们可以参考源码的实现:类模板参数解决这个问题,这也是迭代器的强大之处

3.3 利用类模板优化

template <class T,class Ref,class Ptr>
//typedef __list_iterator<T, T&, T*> iterator;
//typedef __list_iterator<T, const T&, const T*> const_iterator;

利用类模板参数修正之后的代码:

    //  typedef __list_iterator<T, T&, T*> iterator;//  typedef __list_iterator<T, const T&, const T*> const_iterator;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*p):_pnode(p){}//返回数据的指针Ptr operator->(){return &_pnode->_data;}//模板参数做返回值Ref operator *(){return _pnode->_data;}//++itSelf& operator ++(){_pnode = _pnode->_next;return *this;}//it++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& it)const{return _pnode != it._pnode;}bool operator ==(const Self& it)const{return _pnode == it._pnode;}};

同一个类模板,此时我们传递不同的参数实例化成不同的迭代器了!!!这解决了我们刚刚所说的代码冗余问题


四、增删查改

4.1 insert(参数必须加引用,担心非内置类型)和erase

insert:在pos位置上一个插入,返回插入位置的迭代器,对于list的insert迭代器不会失效,vector失效是因为扩容导致pos位置造成野指针问题

		iterator insert(iterator pos,const T& x){node* newnode = new node(x);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:这里的带头(哨兵位)头结点不可删除,返回值是删除位置的下一个,对于list的erase迭代器是失效的

		iterator erase(iterator pos){assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}

4.2 push_back和push_front

        void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}

注意!list的begin和end的位置

同时这个问题还可以延伸出另一个问题:为什么迭代器访问元素的时候要这样写?

 在vector中,物理地址是连续的,这么写还情有可原,分析过list的begin和end之后,你还敢这么写吗??

 直接就报错了,所以正确的应该是!=,而不是 <

void test3()
{vector<int> vv={1,5,7,8,9,3,4};list<int> l={1,5,6,7};vector<int>::iterator it1=vv.begin();list<int>::iterator it2=l.begin();while(it1 < vv.end()){cout << *it1 << " ";it1++;}cout << endl;// while(it2 < l.end())// {//     cout << *it2 << " ";//     it2++;// }while(it2 != l.end()){cout << *it2 << " ";it2++;}cout << endl;
}

4.3  pop_back和pop_front

尾删和头删,复用erase即可

		void pop_front(){erase(begin());}void pop_back(){erase(--end());}

这里的尾删刚好用上了我们的重载


五、list 构造+赋值重载

5.1默认构造+迭代器区间构造+拷贝构造

默认构造:

list()
{_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;
}

我们可以用empty_initialize()来封装初始化,方便复用,不用每次都写:

void empty_initialize()
{_head = new node(T());_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;}}

拷贝构造:

 传统:
 

		list(const list<T>& lt){empty_initialize();for (const auto& e : lt){push_back(e);}}

 用范围for进行尾插,但是要注意要加上&,范围for是*it赋值给给e,又是一个拷贝,e是T类型对象,依次取得容器中的数据,T如果是string类型,不断拷贝,push_back之后又销毁

现代:

		void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}		list(const list<T>& lt){empty_initialize();list<T> tmp(lt.begin(), lt.end());swap(tmp);}

5.2 赋值重载现代写法

		list<T>& operator=(list<T> lt){swap(lt);return *this;}

5.3 类名和类型的问题(C++的一个坑)

查看官方文档,我们可以看到list没有类型:

list<T>& operator=(list<T> lt)
list& operator=(list lt) 

对于普通类:类名等价于类型

对于类模板:类名不等价于类型(如list模板,类名:list 类型:list)

类模板里面可以用类名代表类型,但是并不建议,在类外面则必须要带模板参数list


六、list和vector的对比(重点)

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率 O(1)不支持随机访问
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为 O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针 (节点指针) 进行封装
迭代器失效插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效大量插入和删除操作,不关心随机访问

七、源码合集

#pragma once#include <iostream>
#include <assert.h>
#include <algorithm>namespace lzy {template<class T>struct list_node  //list 节点结构定义{list_node<T>* _next;//不加<T>也没错,但是写上好一些list_node<T>* _prev;T _data;list_node(const T& x)//构造:_next(nullptr), _prev(nullptr), _data(x){}};//迭代器最终版//const 迭代器 -- 增加模板参数,解决 operator*() 返回值与 operator->() 返回值问题//typedef __list_iterator<T, T&, T*> iterator;//typedef __list_iterator<T, const T&, const T*> const_iterator;//STL源码中大佬的写法,利用多个模板参数来避免副本造成的代码冗余问题template<class T, class Ref, class Ptr>struct __list_iterator  //迭代器类{typedef list_node<T> node;  //重命名list节点typedef __list_iterator<T, Ref, Ptr> Self;  //这里进行重命名是为了后续再添加模板参数时只用修改这一个地方node* _pnode;  //节点指针作为类的唯一成员变量__list_iterator(node* p):_pnode(p){}Ref operator*()  //解引用{return _pnode->_data;}Ptr operator->()  //->{return &_pnode->_data;}Self& operator++() //前置++{_pnode = _pnode->_next;return *this;}Self& operator++(int) //后置++{Self it(*this);_pnode = _pnode->_next;return it;}Self& operator--() //前置--{_pnode = _pnode->_prev;return *this;}Self& operator--(int) //后置--{Self it(*this);_pnode = _pnode->_prev;return it;}bool operator!=(const Self& it) const //!={return _pnode != it._pnode;}bool operator==(const Self& it) const  //=={return _pnode == it._pnode;}};//list 类template<class T>class list{typedef list_node<T> node;  //list 的节点public:typedef __list_iterator<T, T&, T*> iterator;  //迭代器typedef __list_iterator<T, const T&, const T*> const_iterator; //const 迭代器//迭代器iterator begin() {return iterator(_head->_next);}iterator end() {//iterator it(_head);//return it;//直接利用匿名对象更为便捷return iterator(_head);}const_iterator begin() const {return const_iterator(_head->_next);}const_iterator end() const {return const_iterator(_head);}void empty_initialize() {  //初始化 -- 哨兵位头结点_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;  //空间换时间,用于标记节点个数}list() {  //构造,不是list<T>的原因:构造函数函数名和类名相同,而list<T>是类型empty_initialize();}//迭代器区间构造template <class InputIterator>list(InputIterator first, InputIterator last) {empty_initialize();while (first != last){push_back(*first);++first;//first++;}}//拷贝构造传统写法//list(const list<T>& lt) {//	empty_initialize();//	for (const auto& e : lt)//	{//		push_back(e);//	}//}// 拷贝构造的现代写法//list(const list& lt) 官方库是这样写的,这是由于在类内类名等价于类型,但不建议自己这样写list(const list<T>& lt) {empty_initialize();  //初始化头结点,防止交换后tmp野指针不能正常的调用析构list<T> tmp(lt.begin(), lt.end());swap(tmp);}//赋值重载传统写法//list<T>& operator=(const list<T>& lt) {//	if (this != &lt)//	{//		clear();//		for (const auto& e : lt)//		{//			push_back(e);//		}//	}//	return *this;//}//赋值重载现代写法//list& operator=(list lt)list<T>& operator=(list<T> lt) {  //不能加引用,lt是调用拷贝构造生成的swap(lt);return *this;}~list() {  //析构clear();delete _head;_head = nullptr;}void swap(list<T>& lt) {  //交换两个链表,本质上是交换两个链表的头结点std::swap(_head, lt._head);std::swap(_size, lt._size);}size_t size() const {  //增加一个计数的成员,以空间换时间return _size;}bool empty() {  //判空return _size == 0;}void clear() {iterator it = begin();while (it != end()) {it = erase(it);}_size = 0;}void push_back(const T& x) {//node* newnode = new node(x);//node* tail = _head->_prev;//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;insert(end(), x);  //复用}void push_front(const T& x) {insert(begin(), x);  //复用}void pop_front() {erase(begin());}void pop_back() {erase(--end());}iterator insert(iterator pos, const T& x) {node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;cur->_prev = newnode;newnode->_next = cur;++_size;return iterator(pos);}iterator erase(iterator pos) {assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}private:node* _head;size_t _size;};
}

 

完结撒花~

相关文章:

【C++】list基本接口+手撕 list(详解迭代器)

父母就像迭代器&#xff0c;封装了他们的脆弱...... 手撕list目录&#xff1a; 一、list的常用接口及其使用 1.1list 构造函数与增删查改 1.2list 特殊接口 1.3list 排序性能分析 二、list 迭代器实现&#xff08;重点难点&#xff09; 关于迭代器的引入知识&#xff1a…...

PowerShell pnpm : 无法加载文件 C:\Users\lenovo\AppData\Roaming\npm\pnpm.ps1

1、右键点击【开始】&#xff0c;打开Windows PowerShell&#xff08;管理员&#xff09; 2、运行命令set-ExecutionPolicy RemoteSigned 3、根据提示&#xff0c;输入A,回车 此时管理员权限已经可以运行pnpm 如果vsCode还报该错误 继续输入 4、右键点击【开始】&#xff0c;打…...

mysql面试题33:Blob和text有什么区别

该文章专注于面试&#xff0c;面试只要回答关键点即可&#xff0c;不需要对框架有非常深入的回答&#xff0c;如果你想应付面试&#xff0c;是足够了&#xff0c;抓住关键点 面试官&#xff1a;Blob和text有什么区别 Blob和text是数据库中存储大文本数据的两种数据类型&#…...

docker版jxTMS使用指南:4.6版升级内容

4.6版jxTMS已经发布&#xff0c;升级了多个重大能力&#xff0c;本系列文章将逐一进行讲解。 docker版本的使用&#xff0c;请查看&#xff1a;docker版jxTMS使用指南 4.0版jxTMS的说明&#xff0c;请查看&#xff1a;4.0版升级内容 4.2版jxTMS的说明&#xff0c;请查看&…...

java最优建树算法

建树算法 树的数据结构 {"code": "1111","name": "","parentcode": "0000","children": null }, {"code": "2222","name": "","parentcode": &q…...

mysql面试题30:什么是数据库连接池、应用程序和数据库建立连接的过程、为什么需要数据库连接池、你知道哪些数据库连接池

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:什么是数据库连接池? 数据库连接池是一种用于管理和复用数据库连接的技术。它是在应用程序和数据库之间建立一组数据库连接,并以池的形式存储起…...

【Vue】vscode格式刷插件Prettier以及配置项~~保姆级教程

文章目录 前言一、下载插件二、在项目内创建配置文件1.在根目录创建&#xff0c;src同级2.写入配置3.每个字段含义 总结 前言 vscode格式刷&#xff0c;有太多插件了&#xff0c;但是每个的使用&#xff0c;换行都不一样。 这里我推荐一个很多人都推荐了的Prettier 一、下载插…...

.NET 8 中的调试增强功能

作者&#xff1a;James Newton-King 排版&#xff1a;Alan Wang 开发人员喜欢 .NET 强大且用户友好的调试体验。您可以在您选择的 IDE 中设置断点&#xff0c;启动已经附加上调试器的程序&#xff0c;逐步执行代码并查看 .NET 应用程序的状态。 在 .NET 8 中&#xff0c;我们致…...

1310. 数三角形

知识点&#xff1a;(a, b)与(c, d)两点连线上点的个数为:gcd(x, y) 1(包括端点) &#xff08;设横坐标差的绝对值为x&#xff0c; 纵坐标差的绝对值为y &#xff09; 思路&#xff1a;先算出选三个点的所有情况&#xff0c;再减去三点共线的情况 共线的斜率为0时特判 当共线…...

数据库基础(一)

数据库面试基础 注&#xff0c;本文章内容主要来自于JAVAGUIDE&#xff0c;只是结合网上资料和自己的知识缺陷进行一点补充&#xff0c;需要准备面试的请访问官方网址。 一、范式 参考链接 函数依赖&#xff1a;一张表中&#xff0c;确定X则必定能确定Y&#xff0c;则X->…...

Factory-Method

Factory-Method 动机 在软件系统中&#xff0c;经常面临着创建对象的工作&#xff1b;由于需求的变化&#xff0c;需要创建的对象的具体类型经常变化。如何应对这种变化&#xff1f;如何绕过常规的对象创建方法(new)&#xff0c;提供一种“封装机制”来避免客户程序和这种“具…...

【C++】神奇字符串(力扣481)

神奇字符串的规律&#xff1a; 神奇字符串 s 仅由 ‘1’ 和 ‘2’ 组成&#xff0c;并需要遵守下面的规则&#xff1a; 神奇字符串 s 的神奇之处在于&#xff0c;串联字符串中 1 和 2 的连续出现次数可以生成该字符串。 s 的前几个元素是 s “1221121221221121122……” 。如果…...

elasticsearch索引的数据类型以及别名的使用

在上篇文章写了关于elasticsearch索引的数据类型&#xff0c;这里就详细说下索引的增删改查以及其他的一些操作吧。 1、索引的增、删、改、查 先新建一个索引结构&#xff0c;代码如下 PUT test-3-2-1 {"mappings": {"properties": {"id": {&…...

分布式锁2:基于redis实现分布式锁

一 redis实现分布式锁 1.1 原理 setnxexpiredel 命令实现redis的分布式锁&#xff1b;其中 setnx 不存在则新增&#xff1b;存在则忽略。即先用setnx来抢锁&#xff0c;如果抢到之后&#xff0c;再用expire给锁设置一个过期时间&#xff0c;防止锁忘记了释放。例如&#xf…...

【Vue面试题十六】、Vue.observable你有了解过吗?说说看

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;Vue.observable你有了解…...

Centos7使用nginx搭建rtmp流媒体服务器

为什么写这篇文章 2023年10月份&#xff0c;公司系统中有个需求&#xff0c;需要使用摄像头记录工程师在维修设备时的工作状态&#xff0c;找到了一家做执法记录仪的厂商&#xff0c;通过厂商发过来的文档了解到该执法记录仪支持通过rtmp协议推流至服务器&#xff0c;第一次接…...

Springboot+vue4S店车辆管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue4S店车辆管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的4S店车辆管理系统&#xff0c;采用M&#xff08…...

Docker与Serverless计算的集成: Docker容器如何与Serverless计算结合。

文章目录 1. Docker容器的可移植性2. Serverless计算的自动伸缩性3. 使用Serverless与Docker容器a. 自托管Serverless平台b. 使用容器服务 4. 使用案例&#xff1a;图像处理服务5. 结论 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;…...

Linux下kibana的安装与配置

1. 环境配置 确保Linux服务器上已安装Java 8或更高版本。可以通过运行 java -version 来验证Java的版本。 下载Kibana 7.17.11的压缩文件&#xff0c;可以从Kibana 7.17.11下载 上传服务器&#xff0c;并解压Kibana压缩文件。 2. Kibana配置 编辑Kibana的配置文件 config/k…...

LuatOS-SOC接口文档(air780E)-- http - http 客户端

示例 -- 使用http库,需要引入sysplus库, 且需要在task内使用 require "sys" require "sysplus"sys.taskInit(function()sys.wait(1000)local code,headers,body http.request("GET", "http://www.example.com/abc").wait()log.info(…...

分布式文件服务器——初识MinIO

开篇 MinIO ——开源优秀的分布式对象存储系统。 适用于AI的 高性能分布式云存储 MinIO 提供高性能、与S3 兼容的对象存储系统&#xff0c;让你自己能够构建自己的私有云储存服务。 MinIO原生支持 Kubernetes&#xff0c;它可用于每个独立的公共云、每个 Kubernetes 发行版、私…...

中国34省级行政区及行政区划代码

一、34省级行政区 23个省、4个直辖市、2个特别行政区、5个自治区。 行政区行政区划代码北京市110000天津市120000河北省130000山西省140000内蒙古自治区150000辽宁省210000吉林省220000黑龙江省230000上海市310000 江苏省320000 浙江省330000 安徽省340000 福建省…...

vue、uniapp实现组件动态切换

在Vue中&#xff0c;通过使用动态组件&#xff0c;我们可以实现组件的动态切换&#xff0c;从而达到页面的动态展示效果。 vue 中 component组件 is属性 功能描述 例如&#xff1a;有多个tabs标签&#xff0c;如&#xff1a;推荐、热点、视频等。用户点击标签就会切换到对应组…...

JVM 虚拟机面试知识脑图 初高级

导图下载地址 https://mm.edrawsoft.cn/mobile-share/index.html?uuid3f88d904374599-src&share_type1 类加载器 双亲委派模型 当一个类收到类加载请求,它首先把类加载请求交给父类(如果还有父类,继续往上递交请求).如果父类无法加载该类,再交给子类加载 防止内存中出现…...

PointRend: 将图像分割视为渲染——PointRend:Image Segmentation as Rendering

0.摘要 我们提出了一种新的方法&#xff0c;用于高效、高质量的对象和场景图像分割。通过将经典的计算机图形学方法与像素标记任务中面临的过采样和欠采样挑战进行类比&#xff0c;我们开发了一种将图像分割视为渲染问题的独特视角。基于这个视角&#xff0c;我们提出了PointRe…...

【k8s】ingress-nginx通过header路由到不同后端

K8S中ingress-nginx通过header路由到不同后端 背景 公司使用ingress-nginx作为网关的项目&#xff0c;需要在相同域名、uri&#xff0c;根据header将请求转发到不同的后端中在稳定发布的情况下&#xff0c;ingress-nginx是没有语法直接支持根据header做转发的。但是这个可以利…...

LuatOS-SOC接口文档(air780E)-- httpsrv - http服务端

httpsrv.start(port, func)# 启动并监听一个http端口 参数 传入值类型 解释 int 端口号 function 回调函数 返回值 返回值类型 解释 bool 成功返回true, 否则返回false 例子 -- 监听80端口 httpsrv.start(80, function(client, method, uri, headers, body)-- m…...

Android Studio: unrecognized Attribute name MODULE

错误完整代码&#xff1a; &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd; (1.8.0_291) &#xfffd;г&#xfffd;&#xfffd;&#xfffd;&#xfffd;쳣&#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xff…...

云服务器带宽对上传下载速度的影响

简单来说就是 云服务器收到数据代表入&#xff0c;带宽大小 < 10时&#xff0c;入带宽大小10 带宽大小 > 10时&#xff0c;出入带宽上限 等于实际购买时候的大小...

2023/9/28 -- ARM

【内存读写指令】 int *p0X12345678 *p100;//向内存中写入数据 int a *p;//从内存读取 1.单寄存器内存读写指令 1.1 指令码以及功能 向内存中写&#xff1a; str:向内存中写一个字(4字节)的数据 strh:向内存写半个字&#xff08;2字节&#xff09;的数据 strb:向内存写一个字…...