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

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的使用和模拟实现

创作不易&#xff0c;感谢三连&#xff01;&#xff01; 一、List的介绍 list的文档介绍 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。 2. list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不…...

20个Python函数程序实例

前面介绍的函数太简单了&#xff1a; 以下是 20 个不同的 Python 函数实例 下面深入一点点&#xff1a; 以下是20个稍微深入一点的&#xff0c;使用Python语言定义并调用函数的示例程序&#xff1a; 20个函数实例 简单函数调用 def greet():print("Hello!")greet…...

Wireshark——获取捕获流量的前N个数据包

1、问题 使用Wireshark捕获了大量的消息&#xff0c;但是只想要前面一部分。 2、方法 使用Wireshark捕获了近18w条消息&#xff0c;但只需要前5w条。 选择文件&#xff0c;导出特定分组。 输入需要保存的消息范围。如&#xff1a;1-50000。 保存即可。...

006-浏览器输入域名到返回

浏览器输入域名到返回 1、URL 输入2、DNS 域名解析3、建立 TCP 连接三次握手概念三次握手理解 4、发送 HTTP/HTTPS 请求5、服务器处理&#xff0c;并返回响应6、浏览器解析并渲染页面7、请求结束&#xff0c;端口 TCP 连接四次挥手概念四次挥手理解 1、URL 输入 2、DNS 域名解析…...

【kubernetes】关于k8s集群如何将pod调度到指定node节点?

目录 一、k8s的watch机制 二、scheduler的调度策略 Predicate&#xff08;预选策略&#xff09; 常见算法&#xff1a; priorities&#xff08;优选策略&#xff09;常见的算法有&#xff1a; 三、k8s的标签管理之增删改查 四、k8s的将pod调度到指定node的方法 方案一&am…...

【框架】React和Vue的异同

1. 前言 React对于原生JS要求会高一级&#xff0c;国外React用的多&#xff0c;国内Vue用的多。 2. 共同点 组件化函数式编程 &#xff08;vue3函数式编程、vue2声明式编程&#xff09;单向数据流&#xff0c;数据驱动视图VirtualDOM Diff算法操作DOM社区成熟&#xff0c;…...

如何选择阅读软件技术学习书籍

如何选择阅读软件技术学习书籍 这里以软件技术学习的角度结合自身感悟谈谈&#xff0c;如何选择阅读书籍。 人的时间和精力都是非常有限的&#xff0c;软件技术学习者如何选择阅读书籍。以下是从我的经验教训总结的一些体会&#xff1a; 1、确定自己的兴趣领域和阅读目标 选…...

做抖店用平板能代替电脑操作吗?抖店运营相关注意事项,注意规避

我是王路飞。 之前给你们讲在抖音开店流程的时候&#xff0c;说过开店需要用到电脑&#xff0c;还需要执照、资金、时间等等。 那么做抖店用平板能代替电脑操作吗&#xff1f; 这个问题其实有很多新手问过我&#xff0c;有的甚至是想直接在手机上操作&#xff0c;想着能省点…...

【FastChat】用于训练、服务和评估大型语言模型的开放平台

FastChat 用于训练、服务和评估大型语言模型的开放平台。发布 Vicuna 和 Chatbot Arena 的存储库。 隆重推出 Vicuna&#xff0c;一款令人印象深刻的开源聊天机器人 GPT-4&#xff01; &#x1f680; 根据 GPT-4 的评估&#xff0c;Vicuna 达到了 ChatGPT/Bard 90%* 的质量&…...

从根到叶:深入理解二叉搜索树

我们的心永远向前憧憬 尽管活在阴沉的现在 一切都是暂时的,转瞬即逝, 而那逝去的将变为可爱 &#x1f31d;(俄) 普希金 <假如生活欺骗了你> 1.二叉搜索树的概念 概念:搜索树&#xff08;Search Tree&#xff09;是一种有序的数据结构&#xff0c;用于存储和组…...

网络信息安全:11个常见漏洞类型汇总

一、SQL注入漏洞 SQL注入攻击&#xff08;SQL Injection&#xff09;&#xff0c;简称注入攻击、SQL注入&#xff0c;被广泛用于非法获取网站控制权&#xff0c;是发生在应用程序的数据库层上的安全漏洞。 在设计程序&#xff0c;忽略了对输入字符串中夹带的SQL指令的检查&…...

阿里云服务器使用教程_2024建站教程_10分钟网站搭建流程

使用阿里云服务器快速搭建网站教程&#xff0c;先为云服务器安装宝塔面板&#xff0c;然后在宝塔面板上新建站点&#xff0c;阿里云服务器网aliyunfuwuqi.com以搭建WordPress网站博客为例&#xff0c;来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流…...

【排序算法】推排序算法解析:从原理到实现

目录 1. 引言 2. 推排序算法原理 3. 推排序的时间复杂度分析 4. 推排序的应用场景 5. 推排序的优缺点分析 5.1 优点&#xff1a; 5.2 缺点&#xff1a; 6. Java、JavaScript 和 Python 实现推排序算法 6.1 Java 实现&#xff1a; 6.2 JavaScript 实现&#xff1a; 6.…...

Python爬虫实战(基础篇)—13获取《人民网》【最新】【国内】【国际】写入Word(附完整代码)

文章目录 专栏导读背景测试代码分析请求网址请求参数代码测试数据分析利用lxml+xpath进一步分析将获取链接再获取文章内容测试代码写入word完整代码总结专栏导读 🔥🔥本文已收录于《Python基础篇爬虫》 🉑🉑本专栏专门针对于有爬虫基础准备的一套基础教学,轻松掌握Py…...

常见控件应用

常见控件应用 1.操作Ajax选项2.滑动滑块操作 1.操作Ajax选项 Ajax即Asynchronous JavaScript and XML&#xff08;异步JavaScript和XML&#xff09;&#xff0c;是指一种创建交互式、快速动态网页应用的网页开发技术。通过在后台与服务器进行少量数据交换&#xff0c;Ajax可以…...

什么是降压恒流芯片?它有什么作用?

降压恒流芯片是一种电子元件&#xff0c;用于将高电压或高电流的输入电源转换为稳定的低电压输出电源&#xff0c;并同时保持恒定的电流输出。 降压恒流芯片的作用有以下几点&#xff1a; 将高电压降低到适合驱动车灯的工作电压&#xff0c;确保车灯亮度稳定。 在负载变化时…...

14:00面试,15:00就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到2月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…...

Maven的settings.xml配置

maven的两大配置文件&#xff1a;settings.xml和pom.xml。其中settings.xml是maven的全局配置文件&#xff0c;pom.xml则是文件所在项目的局部配置 标签servers&#xff1a; 一般&#xff0c;仓库的下载和部署是在pom.xml文件中的repositories和distributionManagement元素中定…...

利用redis实现秒杀功能

6、秒杀优化 这个是 图灵 的redis实战里面的一个案例 6.1 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求&#xff0c;此时会请求nginx&#xff0c;nginx会访问到tomcat&#xff0c;而tomcat中的程序&#xff0c;会进行串行操作&#xff0c;分成如下几个步骤…...

vscode 使用ssh进行远程开发 (remote-ssh),首次连接及后续使用,详细介绍

在vscode添加remote ssh插件 首次连接 选择左侧栏的扩展&#xff0c;并搜索remote ssh 它大概长这样&#xff0c;点击安装 安装成功后&#xff0c;在左侧栏会出现远程连接的图标&#xff0c;点击后选择ssh旁加号便可以进行连接。 安装成功后vscode左下角会有一个图标 点击图…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)

旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据&#xff01;该数据集源自2025年4月发表于《地理学报》的论文成果…...