C++ STL list

✅<1>主页:我的代码爱吃辣
📃<2>知识讲解:C++之 STL list介绍和模拟实现
☂️<3>开发环境:Visual Studio 2022
💬<4>前言:上次我们详细的介绍了vector,今天我们继续来介绍一下TSTL中的另外一个容器list。list在基础的功能和结构上就是一个双向带头的循环链表,实现起来基本不难,但是list迭代器的封装是非常值得学习的。

一.认识list

list - C++ Reference

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2.  list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)。

结构:list使用的是双向的循环带头结点的链表。

 

 二.list的使用

list的使用非常简单,有了我们之前vector和string的基础。上手list基本就是小菜一碟。

1.构造函数

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

2.增删查改

	list<int> li;
	//尾插
	li.push_back(10);
	//头插
	li.push_front(20);
	//尾删
	li.pop_back();
	//头删
	li.pop_front();
	//迭代器位置插入数据
	li.insert(li.begin(), 100);
	//删除迭代器位置数据
	li.erase(li.begin());

3.list 迭代器

函数声明接口说明
begin +end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin +
rend
返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的
reverse_iterator,即begin位置

list的迭代器使用与string和vector一模一样,在此不多介绍。

四.list 模拟实现

1.链表结点

template<class T>
struct _list_node
{
	_list_node( const T& val = T())
		:_val(val),
		_next(nullptr),
		_prev(nullptr)
	{
	}

	T _val;           //存储数据
	_list_node* _next;//后一个结点的指针
	_list_node* _prev;//前一个结点的指针
};

注意:我们在此处的struct定义了一个类,我们在定义_next和_prev的时候就不用像C语言那样加上struct。

例如:C语言写法

template<class T>
struct _list_node
{
	_list_node(const T& val = T())
		:_val(val),
		_next(nullptr),
		_prev(nullptr)
	{
	}

	T _val;           //存储数据
	struct _list_node* _next;//后一个结点的指针
	struct _list_node* _prev;//前一个结点的指针
};

2.list整体结构

template<class T>
class List
{
public:
    typedef _list_node<T> node;

    //...成员方法 增删查改
    
private:
	node* _head;//头节点
};

3.list构造函数

因为我们的list是一个带哨兵位的双向循环链表。所以这里我们将new出来的结点,设置为循环双向结构。

	List()
		:_head(new node)
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

4.push_back(),pop_back()

push_back()尾插一个数据,pop_back()尾删一个数据。

	//push_back
    void push_back(const T& val )
	{
        //创建结点
		node* newnode = new node(val);
		node* tail = _head->_prev;
        //改变指向
		tail->_next = newnode;
		newnode->_next = _head;
		_head->_prev = newnode;
		newnode->_prev = tail;
	}
	//pop_back
    void pop_back()
	{
		//判空
		assert(!empty());
		node* tail = _head->_prev;
		node* newtail = tail->_prev;
		//改变指向
		_head->_next = newtail;
		newtail->_prev = _head;
		//释放结点
		delete tail;
	}

5.迭代器

list的迭代器不同于string和vector,因为list的物理存储是不连续的,所以我们不能像list和vector一样仅仅使用原生指针来作为list的迭代器。

迭代器的实际就是一种模仿指针的行为来为容器提供一种通用的访问方法的设计。虽然list的迭代器不能使用原生指针来替代,但是我们可以对原生的指针进行类级别的封装来构造迭代器,并且使得迭代器具有指针的行为。

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1.   原生态指针,比如:vector
  2.   将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
  • 指针可以解引用,迭代器的类中必须重载operator*()
  • 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  • 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
  • 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
  • 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

 5.1迭代器结构

迭代器的整体结构如下。

template<class T>
class _list_iterator
{
public:
	typedef _list_node<T> node;    //结点类
	typedef _list_iterator<T> self;//迭代器本身

    //使用原生指针构造迭代器
	_list_iterator(node* n)
		:_node(n)
	{
	}
    
    //operator*()
    //operator++()
    //operator--()
    //operator->()
    //operator==()
    //operator!=()
	
private:
	node* _node;//迭代器是对原生指针的封装
};

5.2迭代器操作

5.2.1operator*()

    T& operator*()
	{
		return _node->_val;
	}

返回该结点的值的引用。

5.2.2 operator ++(),operator --()

++的操作其实就是让迭代器挪到下一个位置,--操作其实就是让迭代器挪到上一个位置。迭代器++或者--操作的返回值还是一个迭代器。

    //后置++
	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

    //前置++
	self operator++()
	{
		_node = _node->_next;
		return *this;
	}

    //前置--
    self operator--()
	{
		_node = _node->_prev;
		return *this;
	}

    //后置--
    self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}


5.2.3operator==(),operator!=()

比较两个迭代器是否相等直接比较迭代器封装的两个指针是否相等就可以了。

	bool operator!=(const self& it)
	{
		return _node != it._node;
	}

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

5.2.4operator->()

我们知道一般的结构体,类指针都是支持使用->访问一些成员的。当list存储的数据是结构体,或者是类试迭代器也应该支持->去访问。

    T*  operator->()
	{
		return &_node->_val;
	}

那么这样我们在外边的访问会不会很奇怪呢?


    struct AA
    {
    	AA(int aa = 10)
    		:_aa(aa)
    	{
    	}
        int _aa;
    };


    list<AA> lli(10, AA());	
    list<AA>::iterator lit = lli.begin();
    //1.调用:
    lit.operator->()->_aa;
    //2.调用:
    lit->_aa;

 在实际调用的时候我们可以直接写成调用2。

5.2.5begin(),end()

	
    typedef _list_iterator<T> iterator;

    iterator begin()
	{
		//使用哨兵位的下一个结点指针,构造begin
		return iterator(_head->_next);
	}
	iterator end()
	{
		//使用哨兵位结点指针,构造end
		return iterator(_head);
	}

可以构成 [ begin,end ).这种左闭右开的结构。

5.3 const 迭代器

const迭代器与普通迭代器最大的不同,就是const迭代器不允许修改迭代器指向的数据。在整个迭代器操作中只有,operator* 和 operator->是对指向数据操作的。我们仅需要将普通迭代器的这两个函数返回值修改即可。并且区分开普通迭代器与const迭代器。

template<class T>
class const_list_iterator
{
public:
	typedef _list_node<T> node;
	typedef const_list_iterator self;

	const_list_iterator(node* n)
		:_node(n)
	{
	}

    //返回值加const,不允许修改
	const T& operator*()
	{
		return _node->_val;
	}

	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	self operator++()
	{
		_node = _node->_next;
		return *this;
	}

	self operator--(int)
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	self operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	bool operator!=(const self& it)
	{
		return _node != it._node;
	}

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

    //返回值加const,不允许修改
	const T*  operator->()
	{
		return &_node->_val;
	}

private:
	node* _node;
};

但是这种设计让人觉得很不舒服,代码的重复太多。从代码的角度来看,const和普通迭代器仅仅只是返回值不同而已。我们只要给在给迭代器不同的类型,我们只需要给模板多架两个参数,如果是 T& 和 T*就实例化出普通迭代器,如果是 const T& 和 const T*就实例化出const迭代器。

list类内部:

template<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;//const迭代器

	List()
		:_head(new node)
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

	iterator begin()
	{
		//使用哨兵位的下一个结点指针,构造begin
		return iterator(_head->_next);
	}
	iterator end()
	{
		//使用哨兵位结点指针,构造end
		return iterator(_head);
	}

	const_iterator begin()const
	{
		return const_iterator(_head->_next);
	}

	const_iterator end()const
	{
		return const_iterator(_head);
	}

private:
	node* _head;
};

迭代器:

//T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
template<class T,class Ref,class Ptr>
class _list_iterator
{
public:
	typedef _list_node<T> node;
	typedef _list_iterator<T,Ref,Ptr> self;

	_list_iterator(node* n)
		:_node(n)
	{
	}

	Ref operator*()
	{
		return _node->_val;
	}

	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	Ptr operator->()
	{
		return &_node->_val;
	}

	bool operator!=(const self& it )
	{
		return _node != it._node;
	}

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

    //返回迭代器内部的结点指针
	node* GetNodePtr()
	{
		return _node;
	}

private:
	node* _node;

};

根据不同的模板参数从而实例化出不同的代码。

5.4 insert(),erase()

insert()可以在某一迭代器位置插入数据,erase可以删除某一迭代器位置的数据。

    void insert(iterator pos,const T& val)
	{
        //创建结点
		node* newnode = new node(val);
		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;
        //改变指向
		newnode->_next = posnode;
		newnode->_prev = prevnode;
		prevnode->_next = posnode;
		posnode->_prev = newnode;
		
	}
    
    iterator erase(iterator pos)
	{
		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;
		node* nextnode = posnode->_next;
        //修改指向
		prevnode->_next = nextnode;
		nextnode->_prev = prevnode;
        //释放结点
		delete posnode;
        return iterator(nextnode);
	}

 erase在返回的删除位置的下一个结点的迭代器,也是为了绝迭代器失效的问题。

5.5 析构函数

    //清除数据,但是头节点需要保留
	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			erase(it++);
		}
	}

	~List()
	{
		//连同头节点一起释放
		clear();
		delete _head;
		_head = nullptr;
	}

5.6再看构造函数

迭代器初始化

   	void emptyInit()
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

    template<class InputIterator>
	List(InputIterator frist, InputIterator lest)
		:_head(new node)
	{
		emptyInit();
		while (frist != lest)
		{
			push_back(*frist);
			frist++;
		}
	}

五.list模拟实现整体代码

#pragma once
#include<iostream>
#include<cassert>
using namespace std;

template<class T>
struct _list_node
{
	_list_node( const T& val = T())
		:_val(val),
		_next(nullptr),
		_prev(nullptr)
	{
	}

	T _val;           //存储数据
	_list_node* _next;//后一个结点的指针
	_list_node* _prev;//前一个结点的指针
};

/*
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:
  1. 原生态指针,比如:vector
  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:
	 1. 指针可以解引用,迭代器的类中必须重载operator*()
	 2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
	 3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
		至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
	 4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
*/

//普通迭代器1.0版本
//template<class T>
//class _list_iterator
//{
//public:
//	typedef _list_node<T> node;
//	typedef _list_iterator self;
//
//	_list_iterator(node* n)
//		:_node(n)
//	{
//	}
//
//	T& operator*()
//	{
//		return _node->_val;
//	}
//
//	self operator++(int)
//	{
//		self tmp(*this);
//		_node = _node->_next;
//		return tmp;
//	}
//
//	self operator++()
//	{
//		_node = _node->_next;
//		return *this;
//	}
//
//	self operator--(int)
//	{
//		self tmp(*this);
//		_node = _node->_prev;
//		return tmp;
//	}
//
//	self operator--()
//	{
//		_node = _node->_prev;
//		return *this;
//	}
//
//	bool operator!=(const self& it)
//	{
//		return _node != it._node;
//	}
//
//	bool operator==(const self& it)
//	{
//		return _node == it._node;
//	}
//
//	T*  operator->()
//	{
//		return &_node->_val;
//	}
//private:
//	node* _node;
//};


//const迭代器1.0版本
//template<class T>
//class const_list_iterator
//{
//public:
//	typedef _list_node<T> node;
//	typedef const_list_iterator self;
//
//	const_list_iterator(node* n)
//		:_node(n)
//	{
//	}
//
//	const T& operator*()
//	{
//		return _node->_val;
//	}
//
//	self operator++(int)
//	{
//		self tmp(*this);
//		_node = _node->_next;
//		return tmp;
//	}
//
//	self operator++()
//	{
//		_node = _node->_next;
//		return *this;
//	}
//
//	self operator--(int)
//	{
//		self tmp(*this);
//		_node = _node->_prev;
//		return tmp;
//	}
//
//	self operator--()
//	{
//		_node = _node->_prev;
//		return *this;
//	}
//
//	bool operator!=(const self& it)
//	{
//		return _node != it._node;
//	}
//
//	bool operator==(const self& it)
//	{
//		return _node == it._node;
//	}
//
//	const T*  operator->()
//	{
//		return &_node->_val;
//	}
//private:
//	node* _node;
//};

//迭代器2.0版本
//T:数据类型,Ref:T&/const T&, Ptr: T*/const T*
template<class T, class Ref, class Ptr>
class _list_iterator
{
public:
	typedef _list_node<T> node;
	typedef _list_iterator<T, Ref, Ptr> self;

	_list_iterator(node* n)
		:_node(n)
	{
	}

	Ref operator*()
	{
		return _node->_val;
	}

	self operator++(int)
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	Ptr operator->()
	{
		return &_node->_val;
	}

	bool operator!=(const self& it)
	{
		return _node != it._node;
	}

	bool operator==(const self& it)
	{
		return _node == it._node;
	}

	node* GetNodePtr()
	{
		return _node;
	}
private:
	node* _node;
};

template<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;

	void emptyInit()
	{
		_head->_next = _head;
		_head->_prev = _head;
	}

	List()
		:_head(new node)
	{
		emptyInit();
	}

	template<class InputIterator>
	List(InputIterator frist, InputIterator lest)
		:_head(new node)
	{
		emptyInit();
		while (frist != lest)
		{
			push_back(*frist);
			frist++;
		}
	}

	void push_back(const T& val )
	{
		node* newnode = new node(val);

		node* tail = _head->_prev;

		tail->_next = newnode;
		newnode->_next = _head;
		_head->_prev = newnode;
		newnode->_prev = tail;
	}

	bool empty()
	{
		return _head->_next == _head;
	}

	void pop_back()
	{
		//判空
		assert(!empty());
		node* tail = _head->_prev;
		node* newtail = tail->_prev;
		//改变指向
		_head->_next = newtail;
		newtail->_prev = _head;
		//释放结点
		delete tail;
	}

	void insert(iterator pos,const T& val)
	{
		node* newnode = new node(val);
		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;

		newnode->_next = posnode;
		newnode->_prev = prevnode;
		prevnode->_next = posnode;
		posnode->_prev = newnode;
		
	}

	iterator erase(iterator pos)
	{

		node* posnode = pos.GetNodePtr();
		node* prevnode = posnode->_prev;
		node* nextnode = posnode->_next;
		prevnode->_next = nextnode;
		nextnode->_prev = prevnode;
		delete posnode;
		return iterator(nextnode);
	}

	iterator begin()
	{
		//使用哨兵位的下一个结点指针,构造begin
		return iterator(_head->_next);
	}
	iterator end()
	{
		//使用哨兵位结点指针,构造end
		return iterator(_head);
	}

	const_iterator begin()const
	{
		return const_iterator(_head->_next);
	}
	const_iterator end()const
	{
		return const_iterator(_head);
	}

	//清除数据,但是需要保留
	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);
		}
	}

	~List()
	{
		//连同头节点一起释放
		clear();
		delete _head;
		_head = nullptr;
	}

private:
	node* _head;
};


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/78349.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

卷积操作后特征图尺寸,感受野,参数量的计算

文章目录 1、输出特征图的尺寸大小2、感受野的计算3、卷积核的参数量 1、输出特征图的尺寸大小 如果包含空洞卷积&#xff0c;即扩张率dilation rate不为1时&#xff1a; 2、感受野的计算 例如&#xff0c;图像经过两个3*3&#xff0c;步长为2的卷积后感受野为&#xff1a; co…

Docker数据卷容器

1.数据卷容器介绍 即使数据卷容器c3挂掉也不会影响c1和c2通信。 2.配置数据卷容器 创建启动c3数据卷容器&#xff0c;使用-v参数设置数据卷。volume为目录&#xff0c;这种方式数据卷目录就不用写了&#xff0c;直接写宿主机目录。 创建c1、c2容器&#xff0c;使用–volum…

nginx反向代理流程

一、nginx反向代理流程 反向代理&#xff1a;使用代理服务器来接受internet上的连接请求&#xff0c;然后将请求转发给内部网络中的上游服务器&#xff0c;并将上游服务器得到的结果返回给请求连接的客户端&#xff0c;代理服务器对外表现就是一个web服务器。Nginx就经常拿来做…

ByteBuffer 使用

ByteBuffer 使用 1 java.nio包中的类定义的缓冲区类型2 缓冲区常用属性2.1缓冲区的容量(capacity)2.2 缓冲区的位置(position)2.3 缓冲区的限制(limit)2.4 缓冲区的标记(mark)2.5 剩余容量 remaining/hasRemaining 3 缓冲区常用方法3.1 创建缓冲区3.1.1 allocate方法3.1.2 wrap…

87-基于stm32单片机粮仓仓库环境温湿度烟雾监测报警系统Proteus仿真+源码

资料编号&#xff1a;087 一&#xff1a;功能介绍&#xff1a; 1、采用stm32单片机OLED显示屏烟雾浓度检测DHT11温湿度电机按键蜂鸣器&#xff0c;制作一个温湿度采集、烟雾浓度采集&#xff0c;OLED显示相关数据&#xff0c; 2、通过按键设置温度上限、烟雾浓度上限&#xff0…

JVM虚拟机:初始化的介绍

本文重点 我们前面学习了三个步骤: 装载 连接 初始化 初始化 初始化的时候,会为静态成员变量赋值初始值,它有两种方式: ①声明类变量是指定初始值 ②使用静态代码块为类变量指定初始值 例子 最后输出的结果为3,它的过程是这样的: main方法中输出T.count,由于count是…

爬虫的代理IP池写哪里了?

亲爱的程序员小伙伴们&#xff0c;想要提高爬虫效率和稳定性&#xff0c;组建一个强大的代理IP池是非常重要的一步&#xff01;今天我就来和你分享一下&#xff0c;代理IP池到底应该写在哪里&#xff0c;以及如何打造一个令人瞩目的代理IP池&#xff01;准备好了吗&#xff1f;…

学习笔记整理-面向对象-03-构造函数

一、构造函数 1. 用new调用函数的四步走 new 函数();JS规定&#xff0c;使用new操作符调用函数会进行"四步走"&#xff1a; 函数体内会自动创建出一个空白对象函数的上下文(this)会指向这个对象函数体内的语句会执行函数会自动返回上下文对象&#xff0c;即使函数没…

STM32单片机SPI通信实战:示例代码详解与应用案例

引言&#xff1a; 单片机SPI&#xff08;串行外设接口&#xff09;通信是一种常用的串行同步通信协议&#xff0c;用于单片机与外设之间的高速数据传输。SPI通信具有简单、高效、可靠等特点&#xff0c;在各种嵌入式系统中被广泛应用。本文将介绍单片机SPI通信的原理、配置和性…

人工智能在网络安全中的应用: 分析人工智能、机器学习和深度学习等技术在预测、检测和应对网络攻击中的作用

第一章&#xff1a;引言 随着信息技术的迅猛发展&#xff0c;网络安全已成为当今社会不容忽视的重要议题。网络攻击手法日益复杂&#xff0c;传统的防御方法已经不再足够。在这一背景下&#xff0c;人工智能&#xff08;AI&#xff09;技术正逐渐崭露头角&#xff0c;为网络安…

【制作npm包5】npm包制作完整教程,我的第一个npm包

制作npm包目录 本文是系列文章&#xff0c; 作者一个橙子pro&#xff0c;本系列文章大纲如下。转载或者商业修改必须注明文章出处 一、申请npm账号、个人包和组织包区别 二、了解 package.json 相关配置 三、 了解 tsconfig.json 相关配置 四、 api-extractor 学习 五、npm包…

在Gazebo中添加悬浮模型后,利用键盘控制其移动方法

前段时间写了文章&#xff0c;通过修改sdf、urdf模型的方法&#xff0c;在Gazebo中添加悬浮模型方法 / Gazebo中模型如何不因重力下落&#xff1a;在Gazebo中添加悬浮模型方法 / Gazebo中模型如何不因重力下落&#xff1a;修改sdf、urdf模型_sagima_sdu的博客-CSDN博客 今天讲…

跨境外贸业务,选择动态IP还是静态IP?

在跨境业务中&#xff0c;代理IP是一个关键工具。它们提供了匿名的盾牌&#xff0c;有助于克服网络服务器针对数据提取设置的限制。无论你是需要经营管理跨境电商店铺、社交平台广告投放&#xff0c;还是独立站SEO优化&#xff0c;代理IP都可以让你的业务程度更加丝滑&#xff…

LaWGPT零基础部署win10+anaconda

准备代码&#xff0c;创建环境 # 下载代码 git clone https://github.com/pengxiao-song/LaWGPT cd LaWGPT # 创建环境 conda create -n lawgpt python3.10 -y conda activate lawgpt pip install -r requirements.txt # 启动可视化脚本&#xff08;自动下载预训练模型约15GB…

LVS-DR模式下(RS检测)ldirectord工具实现部分节点掉点后将请求发往正常设备进行处理

基于前文的LVS-DR集群构建环境 一.下载ldirectord软件 二.将模板文件中的LVS-DR模式相关文件拷贝到/etc/ha.d主配置目录并按实际设备修改 三.配置两台RS匹配规则 四.停止RS1的http服务进行测试 RS1失去工作能力&#xff0c;RS2接替RS1 基于前文的LVS-DR集群构建环境 一.下…

网络安全--linux下Nginx安装以及docker验证标签漏洞

目录 一、Nginx安装 二、docker验证标签漏洞 一、Nginx安装 1.首先创建Nginx的目录并进入&#xff1a; mkdir /soft && mkdir /soft/nginx/cd /soft/nginx/ 2.下载Nginx的安装包&#xff0c;可以通过FTP工具上传离线环境包&#xff0c;也可通过wget命令在线获取安装包…

​Redis概述

目录 Redis - 概述 使用场景 如何安装 Window 下安装 Linux 下安装 docker直接进行安装 下载Redis镜像 Redis启动检查常用命令 Redis - 概述 redis是一款高性能的开源NOSQL系列的非关系型数据库,Redis是用C语言开发的一个开源的高键值对(key value)数据库,官方提供测试…

AWS WAF实战、优势对比和缺陷解决

文章目录 挑战和目标AWS WAF的优势AWS WAF的不足我是怎么做的?什么是比较好的AWS WAF设计? 笔者为了解决公司Web站点防御性问题&#xff0c;较为深入的研究AWS WAF的相关规则。面对上千万的冲突&#xff0c;笔者不得设计出一种能漂亮处理冲突数据WAF规则。 AWS WAF开发人员在…

【OpenCV学习笔记】我的OpenCV学习之路

刚开始接触OpenCV是因为需要进行图像的处理&#xff0c;由于之前没有接触过&#xff0c;所以只能自己进行学习&#xff0c;下面将学习的过程做简单记录分享。 OpenCV专栏链接 OpenCV学习笔记 一、引言 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是…

【仿写tomcat】二、扫描java文件,获取带有@WebServlet注解的类

tomcat仿写 项目结构扫描文件servlet注解map容器servlet工具类启动类调用 项目结构 扫描文件之前当然要确定一下项目结构了&#xff0c;我这里的方案是tomcat和项目同级 项目的话就仿照我们平时使用的结构就好了&#xff0c;我们规定所有的静态资源文件都在webApp目录下存放…