关于标准库中的list(涉及STL的精华-迭代器的底层)

目录

关于list

list常见接口实现

          STL的精华之迭代器


关于list

list的文档介绍

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

常见接口:


list常见接口实现(参考部分g++版本下stl底层源码)

namespace dw
{
	template<class T>
	struct list_node //链表节点
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
	};

	//迭代器实现

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		//代码实现


	private:
		node* _head;
	};

	//------------------------------------------------
	void list_test()
	{
		;
	}

}

1.注意:类名不是类型,建议声明类型的时候都加上模板参数

举例来说这里 list_node 类名  list_node<T> 类型

如果不加上模板参数运行程序会报错

typedef list_node<T> node;
typedef list_node node;

2.注意:这里使用的 struct  定义类,struct 定义的类默认访问权限是公开。

构造函数

无参

		void empty_init() //初始化头节点
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

push_back

		void push_back(const T& x)
		{
			node* tail = _head->_prev;
			node* newnode = new node(x);

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

		}

注意,这里测试运行会发现:

原因出在:

node* newnode = new node(x);

所以关于链表节点 list_node 的这个类也需要实现构造函数

		list_node(const T& x = T()) //这里是匿名对象调用构造函数
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{}


   

 STL的精华之迭代器

         这样就完成了需要的准备工作,接下来就可以进行神奇的迭代器部分了,看完大呼 - 还可以这样玩。

         上面的代码可以通过测试以及调试观看到具体状态,那么怎么进行成员访问?

         我们知道,vector 迭代器使用的是原生指针(g++版本) vector相关迭代器的实现,因为 vector 可以看作是一块连续的物理空间,我们通过下标就可以访问到下一个元素

       但是链表可以这样玩吗?肯定是不可以的!所以,这里 list 迭代器的实现不能使用原生指针,而是需要一个类去进行封装。

iterator

//迭代器实现
	template<class T>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

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

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

	};

注意:c++一般不喜欢内部类,所以一般都使用自定义类型

继而我们需要在 list 类进行 typedef 

typedef __list_iterator<T> iterator;

begin()

begin()是第一个节点的位置

        iterator begin()
		{
			iterator tmp(_head->_next);
			return tmp;
		}

		

end()

end()是头节点的位置

        iterator end()
		{
			return iterator(_head);
		}

注:这里和 begin( ) 的写法其实没什么不同,本质上是运用了匿名对象。

  •         其实这里可以看到,虽然vector,list 表面上都是 iterator,但是底层却千差万别,这些都源自于底层的封装

  •         并且,可以明显的感觉到迭代器很好体现了类的设计价值。如果一个内置类型无法满足我们的需求,那么我们可以使用一个自定义类型去封装,然后重载运算符,继而改变它的行为

  •         比如说这里的 iterator 是一个节点的指针,++* 不满足我们的需求,我们可以去进行封装, 用类去封装一个node* 重载++, * 运算符的函数

  •         重载运算符函数的具体实现以及行为完全由我们自己来定义,这是自定义类型+运算符重载+类的定义等等的价值

        其次,关于下面这句代码,我们并没有实现拷贝构造,编译器会默认生成,默认生成的拷贝构造是浅拷贝,那么,这里可以使用浅拷贝嘛?为什么没报错?

list<int>::iterator it = lt.begin();

//首先:这里是需要浅拷贝的,因为这里拷贝的是指向节点的指针

//其次,为什么没报错 ? 是因为迭代器没有写析构函数,那么为什么没写 ? 

//是因为迭代器不需要释放节点。更深层次一些,为什么不需要释放节点 ?

//虽然这里迭代器有指向节点的指针,但是并不支持释放节点,因为释放节点是链表的行为

//链表会有析构函数释放节点,也可以简单把这里迭代器理解为工具,可以支持读或者写

//但是只有使用权限,并没有归属权限,所以这里浅拷贝也就没有问题了。

然后现在就可以丰富一些 迭代器 __list_iterator 这个类,后置 ++ ,--,等等

template<class T>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

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

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

		self& operator++(int) //编译器会默认传一个整型,进行占位,更前置进行区分
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

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

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

	};

const_iterator

接下来是关于 const 修饰的迭代器,如果是下面这样环境,该怎么办?

这里的迭代器就需要使用const 进行修饰了,那么请问这样写法可不可以?

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

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

这里运行编译通过,但是存在一个问题,明明是const修饰,为什么还能构造迭代器?
因为这里的const修饰的是*this 也就是指向的内容,*this 是这个节点的指针,const修饰的是这个指针的本身不能被改变,也就是_head不能被改变,但是可以拷贝。


结果发现并不符合我们的预期,因为这里我们期望迭代器被 const 修饰之后内容不可修改。这里不仅可读,并且可写,显然程序是有些不正确的。

所以这里的写法是我们需要再完成一个类,也就是 __list_const_iterator

    template<class T>
	struct __list_const_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_const_iterator<T> self;

		node* _node;

		__list_const_iterator(node* x)
			:_node(x)
		{}

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

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

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

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

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

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

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

	};

注意:c++一般不喜欢内部类,所以一般都使用自定义类型

继而我们需要在 list 类进行 typedef 

typedef __list_const_iterator<T> const_iterator;

begin()

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

end()

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

代码程序正常运行

但是观察上面这两个类我们会发现,__list_iterator  __list_const_iterator

他们的区别只在于重载运算符解引用的实现不同,更细节一点只是解引用的返回值不同

__list_iterator 

 __list_const_iterator

        但是却写了两个类,这样会显得代码很臃肿,会让人觉得一模一样的代码为什么要写两遍,所以这里提出了一个新的语法知识 -  -  - 添加模板参数

代码如下:

    template<class T, class Ref>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T, Ref> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

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

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

		self& operator++(int) //编译器会默认传一个整型,进行占位,更前置进行区分
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

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

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

	};

这样 __list_iterator  __list_const_iterator 就可以合并为一个类

再修改一些 list ,模板参数不同,调用逻辑也不同

        typedef __list_iterator<T, T&> iterator;

		typedef __list_iterator<T, const T&> const_iterator;

		//typedef __list_const_iterator<T> const_iterator; //注释掉

例:

       如果你认为到这里就结束了,那么不好意思,还差一点。因为观察库里 list 的实现,我们会发现迭代器的模板参数是三个。

附上stl部分底层源码:

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

  typedef bidirectional_iterator_tag iterator_category;
  typedef T value_type;
  typedef Ptr pointer;
  typedef Ref reference;
  typedef __list_node<T>* link_type;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

  link_type node;

  __list_iterator(link_type x) : node(x) {}
  __list_iterator() {}
  __list_iterator(const iterator& x) : node(x.node) {}

  bool operator==(const self& x) const { return node == x.node; }
  bool operator!=(const self& x) const { return node != x.node; }
  reference operator*() const { return (*node).data; }

#ifndef __SGI_STL_NO_ARROW_OPERATOR
  pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

  self& operator++() { 
    node = (link_type)((*node).next);
    return *this;
  }
  self operator++(int) { 
    self tmp = *this;
    ++*this;
    return tmp;
  }
  self& operator--() { 
    node = (link_type)((*node).prev);
    return *this;
  }
  self operator--(int) { 
    self tmp = *this;
    --*this;
    return tmp;
  }
};

源码的  Ptr 是什么?因为这里不仅重载了*,还重载了 ->,那么什么时候要去调用->呢?

1.迭代器要么就是原生指针
⒉.迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为

解释这个原因的话,先看一个测试用例:

这时候我们会发现,之所以会报错是因为AA这个类没有自己实现一个流插入

所以要是想让代码跑起来,有很多的解决办法

方法一便是根据AA这个类型重载一个流插入

所以回过头来也能发现,c++新增运算符重载,而不是继续使用printf函数,是因为printf函数有局限性,printf只能打印内置类型,%d,%lf等等。

但是打印也不是没有其他办法,比如说上面这种,但是看到似乎是有点怪,解引用之后去访问成员
所以这样为了看起来更顺畅一些,我们需要去实现  ->

这里返回值是T*,但是如果是const迭代器呢?

所以这里就不能使用T*,而是需要新增加函数模板参数 Ptr

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T, Ref, Ptr> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

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

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

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

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

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

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

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

	};


以上就是list底层源码的实现逻辑,补充一点:

这里看着是有些怪异的,因为

it-> _a1
it-> ->_a1

本来这里应该是两个 ->

一个是运算符重载的调用

一个是有了结构体的指针再使用 -> 去访问

这里为了增加代码的可读性,省略了一个-> ,可以理解为是一个特殊处理

看似是:

cout << it->_a1 << ":" << it->_a2 << endl;

实际上:

cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

另外需要说明的点是:

迭代器用原生指针只是一个偶然,用类去封装才是一个常态

但是底层的本质都可以认为是指针,只说是嵌入了一个自定义类型去封装指针,在编译器看来是自定义类型而不是指针

并且自定义类型使用运算符只能去重载运算符,至于重载运算符函数的行为,完全是由我们自己来控制的

包括vector的迭代器,  在g++版本下(linux系统)是原生指针,但是vs下也不是原生指针,因为vs需要重载运算符函数,比如 * 用来判断迭代器是否失效。

要注意,不同编译器底层实现不同。以上就是 stl 的精华部分,关于迭代器。
       


swap

		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

迭代器区间构造

        template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

拷贝构造

        //现代写法
		list(const list<T>& lt)
		{
			empty_init();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		//传统写法
		list(const list<T>& lt)
		{
			empty_init();

			for (auto e : lt)
			{
				push_back(e);
			}
		}

赋值

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

insert

		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_next = cur;
			new_node->_prev = prev;
			cur->_prev = new_node;
		}

erase

		void erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;

			next->_prev = prev;
			prev->_next = next;
			delete pos._node;
		}

        //看需求
        iterator erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;

			next->_prev = prev;
			prev->_next = next;
			delete pos._node;

			return iterator(next);
		}

push_front

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

pop_back

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

pop_front

		void pop_front()
		{
			erase(begin());
		}

clear

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
				
				//it = erase(it);  //两种方法都可以,这中 erase 需要有返回值
			}
		}

析构函数

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}

最后附上全部代码以及测试用例:

                                                                                 list.h


namespace dw
{
	template<class T>
	struct list_node //链表节点
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;

		list_node(const T& x = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{}
	};



	//迭代器实现

	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_iterator<T, Ref, Ptr> self;

		node* _node;

		__list_iterator(node* x)
			:_node(x)
		{}

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

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

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

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

		self& operator++(int) //编译器会默认传一个整型,进行占位,更前置进行区分
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

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

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

	};
	

	/*
	template<class T>
	struct __list_const_iterator
	{
	public:
		typedef list_node<T> node;

		typedef __list_const_iterator<T> self;

		node* _node;

		__list_const_iterator(node* x)
			:_node(x)
		{}

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

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

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

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

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

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

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

	};
	*/


	template<class T>
	class list
	{
		typedef list_node<T> node;
	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;

		void empty_init() //初始化头节点
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

	
		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}

		//现代写法
		list(const list<T>& lt)
		{
			empty_init();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		//传统写法
		//list(const list<T>& lt)
		//{
		//	empty_init();

		//	for (auto e : lt)
		//	{
		//		push_back(e);
		//	}
		//}

		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();

			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

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

		iterator begin() 
		{
			iterator tmp(_head->_next);
			return tmp;
		}

		iterator end() 
		{
			return iterator(_head);
		}

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

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

		void push_back(const T& x)
		{
			node* tail = _head->_prev;
			node* newnode = new node(x);

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

		}

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

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

		void pop_front()
		{
			erase(begin());
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());
			node* next = pos._node->_next;
			node* prev = pos._node->_prev;

			next->_prev = prev;
			prev->_next = next;
			delete pos._node;

			return iterator(next);
		}

		void insert(iterator pos, const T& x)
		{
			node* cur = pos._node;
			node* prev = cur->_prev;

			node* new_node = new node(x);

			prev->_next = new_node;
			new_node->_next = cur;
			new_node->_prev = prev;
			cur->_prev = new_node;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it++);
				
				//it = erase(it);  //两种方法都可以,这中 erase 需要有返回值
			}
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;

		}


	private:
		node* _head;
	};




	//------------------------------------------------

	void print_list(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//(*it) *= 2;
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}


	void list_test1()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);

		list<int>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		print_list(lt);

		list<int> lt2(lt);

		for (auto e : lt2)
		{
			cout << e << " ";
		}
		cout << endl;


		list<int> lt3 = lt2;

		for (auto e : lt3)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	void list_test2()
	{
		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);
		lt.push_back(5);

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		lt.push_back(1000); //测试尾插
		lt.push_front(100); // 测试头插

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		lt.pop_back(); // 测试尾删
		lt.pop_front(); //测试头删

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		auto pos = lt.begin();
		++pos;

		lt.insert(pos, 9); //测试任意位置插入
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

		pos = lt.begin();
		++pos;

		lt.erase(pos); // 测试任意位置删除
		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;

	}

}

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

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

相关文章

解析视频美颜SDK的算法:美肤、滤镜与实时处理

如今&#xff0c;美颜技术在视频处理中扮演着关键的角色&#xff0c;为用户提供更加精致的视觉体验。本文将深入探讨视频美颜SDK的算法&#xff0c;聚焦于美肤、滤镜与实时处理等方面&#xff0c;揭示背后的科技奥秘。 一、美肤算法的魅力 视频美颜的一个核心功能就是美肤&am…

Linux自动注册zabbix客户端(脚本化)

参考文档&#xff1a;https://www.zabbix.com/documentation/6.0/zh/manual/api/reference/host/create 根据zabbix版本选择适合的API文档参考 #!/bin/bashusername"Admin" password"zabbix" zabbix_api"http://www.qingtongqing.cc:19080/api_json…

Repo sync 时出现fatal_ couldn‘t find remote ref refs_heads_master问题解决

repo sync默认的origin分支是master&#xff0c;它默认会依赖master&#xff0c;但是我们的manifests分支是main&#xff0c;需要解决这个问题主要执行下面的几步&#xff1a; 更新repo到最新版本 cd .repo/repo git pull # 更新repo前往git库创建origin master 在manifests…

大数据与人工智能——神经网络是如何工作的?

大数据与人工智能——神经网络是如何工作的&#xff1f; 我们习惯于去了解所使用工具、中间件的底层原理&#xff0c;本文则旨在帮助大家了解AI模型的底层机制&#xff0c;让大家在学习或应用各种大模型时更加得心应手&#xff0c;更加适合没有AI基础的小伙伴们。 一、GPT与神…

springMVC-原理及入门案例

基本介绍 (1)springMVC是以spring为基础&#xff0c;因此在使用时&#xff0c;需要先将spring jar引入. (2)SpringMVC是MVC框架,工作在WEB层&#xff0c;替代Strts2.可以超越struts2框架. &#xff08;3&#xff09;SpringMVC相对于Struts2来说&#xff0c;更加简洁&#xff0…

【字符串】ABC324E

退役啦&#xff0c;接下来的博客全是图一乐啦 E - Joint Two Strings 题意 思路 统计两个指针的方案数一定是枚举一个&#xff0c;统计另一个 然后因为拼起来之后要包含 t 这个字符串&#xff0c;隐隐约约会感觉到和前缀后缀子序列有关 考虑预处理每个 s[i] 的最长公共前…

本地 SIEM 与云原生 SIEM:哪一种适合您?

安全信息和事件管理 (SIEM) 解决方案对于各种规模的组织监控其环境中的安全威胁至关重要。 SIEM 解决方案收集并审查来自不同来源&#xff08;例如防火墙、入侵检测系统和 Web 服务器&#xff09;的安全日志。随后可以利用这些数据来检测潜在威胁、检查安全事件并针对网络攻击…

某大厂机器视觉工程师被坑100万!竞业协议到底有多少坑?

特别注意竞业协议是企业与员工双方共同意愿下签订的。如果还在这个行业做&#xff0c;尽量不要去签订竞业协议。 今天看到&#xff0c;看到某大厂员工因为违反竞业协议&#xff0c;被要求赔偿100多万还要返还期间发放竞业协议的补偿金&#xff1b; 实不相瞒&#xff0c;我也被…

java集合的迭代器与遍历

文章目录 迭代器Iterator1、什么是Iterator2&#xff0c;iterator接口的API3、Irerator()方法细节解释4. Irerator的原理示意图5. forEach循环与Iterator遍历的区别与联系 ListIterator1.ListIterator的概述(1) 概念(2) 解析 2.ListIterator的生成3.ListIterator的API4.ListIte…

uniapp 单选按钮 选中默认设备

需求1&#xff1a;选中默认设备&#xff0c;113 和114 和139都可以选中一个默认设备 选中多个默认设备方法&#xff1a; async toSwitch(typeItem, title) {const res await this.setDefaultDev(typeItem.ibdr_devsn, typeItem.ibdr_pid)if (!res) {this.common.toast(切换默…

空气污染大屏,UI可视化大屏设计(PSD源文件)

大屏组件可以让UI设计师的工作更加便捷&#xff0c;使其更高效快速的完成设计任务。现分享科技空气污染大数据、空气污染大数据平台、大气环境信息资源中心、大气检测大数据中心、环境信息资源中心界面的大屏Photoshop源文件&#xff0c;开箱即用&#xff01; 若需 更多行业 相…

input 获取焦点后样式的修改

一、实现目标 1.没有获取焦点时样子 2.获取焦点时 代码&#xff1a; <input class"input"placeholder"请输入关键字" input"loadNode" />css .input {border-radius: 14px;border:1px solid #e4e4e4;margin: 5px;margin-top: 10px;wi…

Java - 异常(三)- 声明异常(throws)和手动抛出异常throw

目录 6.3 方式2&#xff1a;声明异常&#xff08;throws&#xff09; 6.4 手动抛出异常throw 6.4.1 概述 6.4.2 使用格式&#xff1a; 6.4.3 实例代码 6.4.4 为什么要手动抛出异常对象&#xff1f; 6.4.5 如何理解“自动”和“手动” 抛出异常对象 6.4.6 注意点 ❓面试…

基于SSM架构的超市管理系统设计

基于SSM架构的超市管理系统设计 目录 基于SSM架构的超市管理系统设计 1 环境及工具1.1 IDEA软件安装1.2 JDK环境配置1.3 MySQL数据库安装1.3.1常规情况1.3.2非常规情况 1.4 Tomcat安装 2 部署与设计2.1 数据库信息创建2.2项目创建与部署 3 相关说明4 功能操作说明4.1 管理员操作…

物联网在能源管理中的应用——青创智通工业物联网解决方案

随着全球能源资源的日益紧张和环境问题的日益突出&#xff0c;能源管理已成为当今社会的重要议题。物联网技术的快速发展为能源管理提供了新的解决方案。本文将介绍物联网在能源管理中的应用及其优势。 一、物联网在能源管理中的应用 1. 智能电网 智能电网是物联网在能源管理中…

el-date-picker限制选择7天内禁止内框选择

需求&#xff1a;elementPlus时间段选择框需要满足&#xff1a;①最多选7天时间。②不能手动输入。 <el-date-picker v-model"timeArrange" focus"timeEditable" :editable"false" type"datetimerange" range-separator"至&qu…

电脑手机文件无线互传方法?利用备忘录更方便

在忙碌的工作生活中&#xff0c;文件传输和分享已经成为了我们日常生活中的一部分。从厚厚的文件夹到电子化的文件&#xff0c;从线下到线上&#xff0c;这一转变让我们的工作和生活变得更加方便高效。 而在这个数字化时代&#xff0c;备忘录成为了我们实现电脑手机文件无线互…

ELADMIN - 免费开源 admin 后台管理系统,基于 Spring Boot 和 Vue ,包含前端和后端源码

一款简单好用、功能强大的 admin 管理系统&#xff0c;包含前端和后端源码&#xff0c;分享给大家。 ELADMIN 是一款基于 Spring Boot、Jpa 或 Mybatis-Plus、 Spring Security、Redis、Vue 的前后端分离的后台管理系统。 ELADMIN 的作者在 Github 和 Gitee 上看了很多的项目&…

ceph的osd盘删除操作和iscsi扩展

ceph的osd盘删除操作 拓展:osd磁盘的删除(这里以删除node1上的osd.0磁盘为例) 1, 查看osd磁盘状态 [rootnode1 ceph]# ceph osd tree ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 0.00298 root default -3 0.00099 host node10 hdd 0.000…

利用python批量压缩图教程:如何轻松减小图片大小

在日常的编程工作中&#xff0c;我们经常需要处理图像&#xff0c;例如上传、下载、显示、编辑等。有时候&#xff0c;我们需要对图像进行压缩&#xff0c;以减少占用的空间和带宽&#xff0c;提高加载速度和用户体验。那么&#xff0c;如何用Python来实现图像压缩呢&#xff1…