【C++】STL学习——list模拟实现

news2024/9/16 12:52:45

目录

  • list介绍
  • list结构介绍
  • 节点类的实现
  • 迭代器的实现
    • 构造函数
    • ++运算符重载
    • --运算符重载
    • ==运算符重载
    • !=运算符重载
    • *运算符重载
    • ->运算符重载
  • const迭代器的实现
  • 多参数模板迭代器
  • list函数接口总览
  • 默认成员函数
    • 构造函数1
    • 构造函数2
    • 构造函数3
  • 析构函数
    • 拷贝构造函数
    • 赋值重载函数
  • 迭代器
    • begin()和end()
  • 修改相关函数
    • insert
    • push_back和push_front
    • erase
    • pop_back和pop_front
    • empty
    • size
    • swap
    • clear
  • 访问相关函数
    • front和back
  • 模板的拓展应用

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学习参考文档

list的底层是带头双向循环链表,在学习数据结构时也使用C语言对带头双向循环链表进行过模拟实现——
双向链表的模拟实现;得益于其优秀的结构设计,在数据的插入,删除方面有着极高的效率。

带头双向循环链表

但在STL中的list更让人叫绝的是其迭代器是设计。

  • 迭代器的主要作用就是让算法能够不用关心底层数据结构,对外提供一个统一的访问方式,即使不了解容器的底层是什么,也能通过迭代器对容器进行访问。
  • 迭代器其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* ,而今天学习的list的迭代器则是一个封装过的类。

list结构介绍

list得益于其结构,实现增删等功能比前面实现的vector,string更加简单。list更值得我们学习的是其迭代器的的实现。而且迭代期也是我们访问list的主要手段,所以优先实现迭代器。

list的实现需要有三个类:节点类,迭代器类,list类

节点类的实现

节点作为链表组成的基本单位,其成员有:

  • 存储数据的_data
  • 连接前一节点的前驱指针_prev
  • 连接后一节点的后继指针_next

节点类只需要实现默认构造函数即可。负责将成员初始化。

	template<class T>
	struct list_node
	{
		T _data;//数据
		list_node<T>* _prev;//前驱指针
		list_node<T>* _next;//后继指针

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

迭代器的实现

先实现非const版的迭代器;
迭代器的意义就是:

让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。

而list的底层已不再是连续的空间,而是分散的节点,无法再使用[]加下标访问,也不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作。究其原因就是vector,string的底层空间是连续的,其迭代器就是对应的指针,天然就支持++,–这样的操作。

list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装对结点指针的各种operator运算符操作进行重载,使其符合迭代器的行为。如:当你使用list当中的迭代器进行自增操作时,实际上执行了p = p->next语句,只是你不知道而已,我们通过类的封装屏蔽了其细节,符合迭代器方法的使用。
总结:
用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为)。

	template<class T>
	struct __list_iterator//用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为)
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T> self;
		Node* _node;//节点

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

		self& operator++();
		self& operator--();
		self operator++(int);
		self operator--(int);
		bool operator==(const self& it);//it为迭代器,_node为节点
		bool operator!=(const self& it);
		T& operator*();
		T* operator->();
		
	};

该类中只有一个成员,那就是节点。类名太长了,使用typedef__list_const_iterator 重命名为self

构造函数

对于封装的迭代器类,我们只需要实现一个构造函数即可,只需要将获取的链表的节点用来构造迭代器中的节点即可。

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

对于我们模拟实现的迭代器,需要明确我们的目的:我们实现的迭代器是为了帮我们去遍历,访问我们的链表。这也是为什么我们不需要实现拷贝构造和赋值重载函数进行深拷贝的原因,我们使用编译器默认生成的浅拷贝的拷贝构造和赋值重载函数就能达到我们的目的,使用深拷贝反而达不到我们但目的。

++运算符重载

对于前置++,使用原则是:先++,再使用;所以直接将当前节点往后走一位,再返回当前节点即可。引用返回效率更好。

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

后置++使用原则为:先使用,再++,所以需要返回++之前的值,所以先用一个临时对象保存该值,再让节点往后走,最后返回临时对象。不能引用返回,tmp为临时对象,函数结束就销毁,不能使用引用返回。

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

–运算符重载

前置–:先–,再使用:让节点往前走一位,再返回。

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

后置–:先使用,再–;逻辑与后置++一致。

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

==运算符重载

重载节点与迭代期是否相等。分清_node为节点,而it为迭代期,类型不同;对比的是节点_node和迭代器的成员:节点_node。

        bool operator==(const self& it)//it为迭代器,_node为节点
		{
			return _node == it._node;
		}

!=运算符重载

原理与上述==相同,只不过现在是!=。

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

*运算符重载

使用解引用操作符时,是想得到该位置的数据内容。因此,我们直接返回当前结点指针所存储数据即可,但是这里使用引用返回,因为解引用是支持对数据进行修改的。

		T& operator*()
		{
			return _node->_data;//返回引用
		}

->运算符重载

在某些场景下,我们需要使用->来访问数据如指针,或对象类型为自定义类型时。
->操作符会先对指针进行解引用,然后访问其指向对象的成员。所以我们只需要返回数据的地址即可完成任务。

		T* operator->()
		{
			return &_node->_data;//返回其地址
		}

注意:
对于->的使用:当数据的类型为自定义类型时,使用一个->就能取到成员对象实际是编译器优化的结果,完整的应为it1.operator->()->对象:先调用operator->()获取对象的地址,再使用->获取对象。
->
好了,以上就是非const版迭代器所需的重载运算符。那么const迭代器如何实现呢,在现有基础上加个const吗?

const迭代器的实现

如果你认为const迭代器是在非const迭代器上加const,那么我们先回顾一下const对指针的相关限制

	int a = 10;
	int b = 20;
	const int* pa1 = &a;//对指针所指向的内容进行限制
	*pa1 = 30;//err,指向的内容不能变。
	pa1 = &b;//可以改变指针变量的指向。

	int* const pa2 = &a;//对指针变量进行限制
	*pa2 = 30;//所指向内容可以改变。
	pa2 = &b;//err,指针变量不能修改。

const迭代器的目的:
list的const迭代器是允许++,–这样的访问操作的,其需要保证的是const对象所指向的内容不能被修改,也就是不能通过运算符重载operator* ()和operator-> ()来改变数据。这么一看不就是类似const int* pa1 = &a;在*pa1前加上const修饰。但是你别忘了,这些都是对原生指针的而言的,而list的迭代器是我们封装的一个类无论你在哪里加const,都会导致迭代器被const修饰而无法修改,从而连最基本的访问也无法执行了。
在这里插入图片描述
所以list的const迭代器是不可能在非const迭代器变来的,它必须是一个重新的类

const迭代器:

	template<class T>
	struct __list_const_iterator//用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为)
	{
		typedef list_node<T> Node;
		typedef __list_const_iterator<T> self;
		Node* _node;

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

		self& operator++();
	    self& operator--();
	    self operator++(int);
	    self operator--(int);
	    bool operator==(const self& it);//it为迭代器,_node为节点
	    bool operator!=(const self& it);
	    const T& operator*();
		const T* operator->();
	};

仔细一看两个迭代器,虽说是两个不同的类,但是内容高度重合,因为const迭代器不能通过运算符重载operator*()和operator->()来改变数据,所以我们只需要对这两个函数的返回值加const限制即可。逻辑也是一致的。

		const T& operator*()
		{
			return _node->_data;//返回引用
		}

		const T* operator->()
		{
			return &_node->_data;//返回其地址
		}

多参数模板迭代器

对比const迭代器和非const迭代器,你会发现只有operator*()和operator->()处多了一个const,就因为这两个const而重写了一份一模一样的代码,这不是为了吃醋而包饺子了嘛。为了解决这种重复代码的问题,C++拿出了模板来应对。

const迭代器和非const迭代器的代码中有不同的只有两处:T&与const T&,T与const T;再加上原本的T,我们将模板参数设为三个。template<class T,class Ref,class Ptr>T为数据类型,Ref为T&或const T&,Ptr为T*或const T*,再将operator*(),operator->()的类型更为Ref和Ptr即可在使用时根据所需要的迭代器类型来实例化所需的代码。

template<class T,class Ref,class Ptr>//用一个类封装迭代器去模拟指针的行为,该类的成员函数为运算符重载函数(模拟指针行为)
struct __list_iterator
{
	typedef list_node<T> Node;
	typedef __list_iterator<T,Ref,Ptr> self;
	Node* _node;

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

	self& operator++();
	self& operator--();
	self operator++(int);
	self operator--(int);
	bool operator==(const self& it);
	bool operator!=(const self& it);
	Ref operator*()const;//返回数据的引用
	Ptr operator->()const;//返回数据的地址
	
};

在list类当中,只需要使用对应的迭代器,模板参数的Ref就会替换成const T&或者T&Ptr就会替换为const T*或者T*这样就能使用同一份类模板实例化出两种迭代器

	template<class T>
	class list
	{
		typedef list_node<T> Node;

		//构造时以下操作多,设为私有不公开
		void list_init()//开好头节点
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

	public:
		typedef __list_iterator<T,const T&,const T*> const_iterator;//使用那个迭代器就实例化哪一个迭代器
		typedef __list_iterator<T,T&,T*> iterator;

   };

好了,这就是迭代器的模拟实现,接下来进入list的模拟实现。

list函数接口总览

list类的结构十分简单,只有一个节点类;还记得我们list的底层是带头双向循环链表吗,所以这里的成员_head为头节点,不存储有效数据

	template<class T>
	class list
	{
		typedef list_node<T> Node;
		Node* _head;//头节点

		//构造时以下操作多,设为私有不公开
		void list_init()//开好头节点
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

	public:
		typedef __list_iterator<T,const T&,const T*> const_iterator;//使用那个迭代器就实例化哪一个迭代器
		typedef __list_iterator<T,T&,T*> iterator;

		//typedef __list_iterator<T> iterator;
		//typedef __list_const_iterator<T> const_iterator;

		list();
		list(initializer_list<T> il);
		template <class InputIterator>
		list(InputIterator first, InputIterator last);
		list(const list& x);
		list& operator= (const list& x);
		~list();
		iterator begin();
		iterator end();
		const_iterator begin()const;
		const_iterator end()const;
		iterator insert(iterator position, const T& val);
		void push_back(const T& val);
		void push_front(const T& val);
		iterator erase(iterator position);
		void pop_back();
		void pop_front();
		bool empty() const;
		size_t size() const;
		void swap(list& lt);
		T& front();
		const T& front() const;
		T& back();
		const T& back() const;
		
    };
  • 注意,模拟实现的节点类,迭代器类,lsit类为了不和库的发生冲突,全都放在自己的命名空间里。

默认成员函数

我们实现了三个构造函数:

  • 第一个为默认构造
  • 第二个为迭代器区间构造
  • 第三个为使用initializer_list来构造。

构造函数开头节点的操作都一致,所以将这份代码装一起,并设为私有。

		//构造时以下操作多,设为私有不公开
		void list_init()//开好头节点
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

构造函数1

默认构造,开好头结点即可。

		list()
		{
			list_init();//开好头节点
		}

构造函数2

使用迭代器区间构造,开好头节点后,将传入区间的元素一个一个push_back进容器当中。

		template <class InputIterator>
		list(InputIterator first, InputIterator last)
		{
			list_init();
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

构造函数3

使用initializer_list来构造,这种方法是在C++11提出的,使用方法如下:
直接将所要用添加的元素放在花括号里。

	list<int>lt6 = { 1,2,3,4,5,6 };
	list<int>lt7({ 1,2,3,4,5,6 });

这里的initializer_list是一个类模板,支持迭代器的使用。
initializer_list
initializer_list支持迭代器,所以使用范围for直接将initializer_list中的数据push_back进容器

		list(initializer_list<T> il)
		{
			list_init();
			for (const auto& e : il)
			{
				push_back(e);
			}
		}

析构函数

我们一共实现了三个类,但前两个都没有实现析构函数,那是因为前两个都没有实现深拷贝的拷贝构造和赋值重载函数;而且就像迭代器类,你总不能访问完就把我的节点给释放了吧。所以我们只需要在进行了深拷贝的list中写析构函数。
实现也简单,使用clear先将存储有效数据的节点都释放,最后再将头节点释放

  • clear是不清除头结点的。
		~list()
		{
		    clear();
			delete _head;
			_head->_next = _head->_prev = nullptr;
		}

拷贝构造函数

有了之前vector,string的经验,现在实现拷贝构造也应该信手拈来了:开好头节点,然后使用范围for直接将数据push_back进容器即可。


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

赋值重载函数

使用现代写法,利用不用引用传参的形参会在传参时调用拷贝构造,构造一个一模一样的list对象,然后调用swap函数将原容器与该list对象进行交换即可

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

迭代器

迭代器的使用区间为:左闭右开,所以开始位置应该是第一位有效数据——即头结点的下一位;而结尾则应该是不存储有效数据的头节点

  • 返回类型为迭代器,但返回的是节点,这里其实发生了隐式类型转化,编译器在返回时会用该节点构造一个迭代器。
  • 也可以使用匿名构造,直接构造一个迭代器类型。

begin()和end()

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

		iterator end()
		{
			return _head;
		}

非const迭代器为const对象所调用,防止对容器内容进行修改

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

修改相关函数

insert

使用迭代器在指定位置插入值val:首先使用val创建一个新的节点,紧接着让pos前后位置的前驱或后继指针建立新的双向关系。具体如下:
insert

		iterator insert(iterator position, const T& val)
		{
		    assert(position._node);//防止空指针
			Node* node = new Node(val);
			Node* pos = position._node;//position为迭代器类型,需要获其节点

			node->_prev = pos->_prev;
			node->_next = pos;

			pos->_prev->_next = node;
			pos->_prev = node;
			return node;
		}
  • list中进行插入时是不会导致list的迭代器失效的

push_back和push_front

调用insert,在头或尾插入val;

		void push_back(const T& val)
		{
			insert(end(), val);
		}

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

erase

删除指定位置的数据:需要对传入的迭代器进行合法性审查,防止空指针,而且不能删除头节点;然后删除位置前后的节点重新建立双向关系;删除指定位置节点时记得先保存下一节点,用作返回。
erase

		iterator erase(iterator position)
		{
			assert(position._node);//防止空指针
			assert(position != end());//不能释放头节点
			assert(!empty());//非空才能删
			Node* pos = position._node;//节点

			pos->_next->_prev = pos->_prev;
			pos->_prev->_next = pos->_next;

			iterator ret = pos->_next;
			delete pos;//new的时节点,所以delete节点
			return ret;
		}

erase会有迭代器失效问题,使用前记得更新获取迭代器位置。

pop_back和pop_front

同样调用erase完成任务,pop_front删除头节点的下一位,pop_back删除头节点的前一位——即最后一位。

  • 尾删是删除存储有效数据的节点,end返回的是头节点。
		void pop_back()
		{
			erase(--end());
		}

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

empty

检查链表是否为空:对于空的链表而言,只剩下一个头节点,此时_head的_prev和_next都指向_head,因此可以利用这一点来判断链表是否为空。

empty

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

size

获取容器中数据个数:用size来统计个数,再利用范围for遍历链表。

		size_t size() const
		{
			size_t size = 0;
			for (auto e : *this)
			{
				size++;
			}
			return size;
		}

swap

交换链表:交换两个链表的头节点即可。

		void swap(list& lt)
		{
			std::swap(_head, lt._head);
		}

clear

清空链表:除头节点外,清空剩余全部节点;借助erase,删除节点。

  • erase会返回删除节点的下一个节点,所以不需要++,只需要接受其返回值即可。
		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);//erase返回下一个节点
			}
		}

访问相关函数

front和back

访问一头一尾的数据,直接使用*迭代器的操作访问一头一尾的操作。


		T& front()
		{
			return *begin();
		}

	    T& back()
		{
			return *(--end());
		}

当然少不了const版,因为const对象调用front和back函数后所得到的数据不能被修改。

        const T& front() const
		{
			return *begin();
		}

		const T& back() const
		{
			return *(--end());
		}
		

至此,list的模拟实现基本完成了。

模板的拓展应用

如果我们想要在类外实现一个list的打印函数:利用迭代器可以轻松完成。

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

但这样写只是针对list的类型为int,换做其他类型就不行了,所以我们可以套上模板,支持所有list的打印。
注意:
由于我们套上了模板,这样的话在编译时list的类型是不确定的,所以迭代器也不能确认,所以在list<T>::iterator it = lt.begin();处会报错,无法通过编译。此时可以加上前缀typename作为标记,让编译器先通过编译,等模板实例化后再取。

	template<class T>
	void print_list(list<T>& lt)
	{   //由于list<T>还未实例化,所以T的类型不确定,编译不通过。
	    //此时在迭代器前加上typename,作为标记,让编译器编译成功,等实例化后再来确定迭代器具体类型
		typename list<T>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

除此之外,还能实现一个针对所有容器都能打印的函数吗?
我们此时直接将具体的类作为模板参数,传入的是哪种容器就去取该容器的迭代器,从而进行打印。


	template<class Container>//格局打开,直接将具体的类作为模板参数
	void print_containers(Container& con)
	{
		typename Container::iterator it = con.begin();
		while (it != con.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

本篇篇幅有点长,主要对list的迭代器进行介绍及模拟实现,对于不是原生指针的迭代器,其const的迭代器使用了多参数模板进行实现,一份代码实例化两种迭代器,这也是本篇博客的难点及精华,对于模板的学习,需要在日后的实践中不断学习。出错的地方还请指出。Thank~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

八,SpringBoot Web 开发访问静态资源(附+详细源码剖析)

八&#xff0c;SpringBoot Web 开发访问静态资源(附详细源码剖析) 文章目录 八&#xff0c;SpringBoot Web 开发访问静态资源(附详细源码剖析)1. 基本介绍2. 快速入门2.1 准备工作 3. 改变静态资源访问前缀&#xff0c;定义为我们自己想要的4. 改变Spring Boot当中的默认的静态…

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext new ClassPathXmlApplicationContext("bean.xml"); } BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanFactory的功能相似&#xff0c;都是…

2024下学期学习总结加今日学习总结

Vue router Vue Router 是一个为 Vue.js 应用程序设计的官方路由管理器。它使你能够轻松地在 Vue 应用中实现页面导航&#xff0c;处理 URL 和视图的映射。 安装router 在运行框内输入 npm install vue-router4 //vue2专用 npm install vue-router3 //vue3专用 对router进…

uniapp数据缓存和发起网络请求

数据缓存 uni.onStorageSync同步的方式将数据存储到本地缓存 <template><button click"onStorageSync()">存储数据</button> </template><script setup>const onStorageSync () > {// 存储数据uni.setStorageSync(username, 张三)…

【验收交付体系文档】系统验收计划书,软件交付验收成套文档体系

软件系统验收计划书是确保新开发的软件系统符合预期要求并稳定运行的关键步骤。本计划书概述了验收过程的主要环节&#xff0c;包括系统功能的详细测试、性能评估、用户接受度测试以及文档完整性的核查。验收团队将依据项目需求规格说明书和合同要求&#xff0c;对系统进行全面…

axure判断

在auxre中我们也可以实现判断的功能&#xff0c;当目标等于什么内容时则执行下方的功能。 一、判断输入框中是否有值 画布添加一个输入框、一个文本标签删除其中内容&#xff0c;添加一个按钮&#xff0c;输入框命名为【文本显示】文本标签命名为【提示】 给按钮新增一个交互…

多个索引干扰导致索引失效如何解决

视频讲解&#xff1a;索引干扰导致索引失效如何解决_哔哩哔哩_bilibili 1 场景说明 表tb_order有订单状态order_status和创建时间create_time的索引。 现在业务的需求是&#xff0c;查询半年内&#xff0c;已支付订单状态的总数。SQL语句如下&#xff1a; SELECTCOUNT(1) FRO…

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元&#xff0c;传统键盘输入方式正悄然进化。以往&#xff0c;面对实体键盘&#xff0c;我们常需目光游离于屏幕与键盘之间&#xff0c;以确认指尖下的精准位置。而屏幕键盘虽直观可见&#xff0c;却常因占据屏幕空间&#xff0c;迫使我们在操作与视野间做出…

Windows系统下安装JMeter

目录 一、官网下载JMeter 二、运行 JMeter 一、官网下载JMeter JMeter 官网安装地址 Apache JMeter - Apache JMeter™https://jmeter.apache.org/ 下载Windows版本 下载完成后 解压 二、运行 JMeter 打开bin目录 下面两个文件其中一个均可运行双击jmeter.bat 或者使用…

支持黑神话悟空的超长视频理解,Qwen2-VL多模态大模型分享

Qwen2-VL是由阿里巴巴达摩院开发并开源的第二代视觉与语言多模态人工智能模型。 Qwen2-VL结合了视觉理解和自然语言处理的能力&#xff0c;使得它能够处理和理解图像、视频以及文本数据。 Qwen2-VL支持多种语言&#xff0c;包括但不限于英语、中文、大多数欧洲语言、日语、韩…

【分支-快速排序】

【分支-快速排序】 1. 颜色分类1.1 题目来源1.2 题目描述1.3 题目解析 2. 排序数组2.1 题目来源2.2 题目描述2.3 题目解析 3. 数组中的第K个最大元素3.1 题目来源3.2 题目描述3.3 题目解析 4. 库存管理 III4.1 题目来源4.2 题目描述4 .3 题目解析 1. 颜色分类 1.1 题目来源 7…

JS基础学习笔记

1.引入方式 内部脚本 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> <…

为什么要使用大模型RAG一体机

使用大模型RAG&#xff08;Retrieval-Augmented Generation&#xff09;一体机&#xff0c;如AntSKPro AI 离线知识库一体机&#xff0c;有以下几个原因和优势&#xff1a; 提高效率&#xff1a;RAG模型结合了检索&#xff08;Retrieval&#xff09;和生成&#xff08;Generati…

鸿蒙(API 12 Beta6版)GPU加速引擎服务【自适应VRS】

XEngine Kit提供自适应VRS功能&#xff0c;其通过合理分配画面的计算资源&#xff0c;视觉无损降低渲染频次&#xff0c;使不同的渲染图像使用不同的渲染速率&#xff0c;能够有效提高渲染性能。 接口说明 以下接口为自适应VRS设置接口&#xff0c;如要使用更丰富的设置和查询…

windows10-VMware17-Ubuntu-22.04-海康2K摄像头兼容问题,求解(已解决)

文章目录 1.webrtc camera测试2.ffmpeg 测试3.Ubuntu 自带相机4.解决办法 环境&#xff1a;windows10系统下&#xff0c;VMware的Ubuntu-22.04系统 问题&#xff1a;摄像头出现兼容问题&#xff0c;本来是想开发测试的&#xff0c;Ubuntu方便些。买了海康2K的USB摄像头&#xf…

人机交互与现代战争

人机交互技术在现代战争中的应用越来越广泛&#xff0c;它可以帮助士兵更好地完成任务&#xff0c;提高作战效能&#xff0c;减少人员伤亡。人机交互与认知在军事应用方面的进展有很多&#xff0c;比如&#xff1a; &#xff08;1&#xff09;虚拟现实和增强现实技术&#xff1…

PAT甲级-1085 Perfect Sequence

题目 题目大意 在一组数中找到一个完美数列&#xff0c;满足M < mp&#xff0c;M是该数列的最大值&#xff0c;m是最小值&#xff0c;p是题目给定的一个常数。 思路 模拟或者二分法。二分法可用upper_bound()函数实现。 知识点 upper_bound() 和 lower_bound() 函数在&…

C高级编程 第十六天(树 二叉树)

1.树 1.1结构特点 非线性结构&#xff0c;有一个直接前驱&#xff0c;但可能有多个直接后继有递归性&#xff0c;树中还有树可以为空&#xff0c;即节点个数为零 1.2相关术语 根&#xff1a;即根结点&#xff0c;没有前驱叶子&#xff1a;即终端结点&#xff0c;没有后继森…

02-java实习工作一个多月-经历分享

一、描述一下最近不写博客的原因 离我发java实习的工作的第一天的博客已经过去了一个多月了&#xff0c;本来还没入职的情况是打算每天工作都要写一份博客来记录一下的&#xff08;最坏的情况也是每周至少总结一下的&#xff09;&#xff0c;其实这个第一天的博客都是在公司快…

笔记整理—内核!启动!—kernel部分(2)从汇编阶段到start_kernel

kernel起始与ENTRY(stext)&#xff0c;和uboot一样&#xff0c;都是从汇编阶段开始的&#xff0c;因为对于kernel而言&#xff0c;还没进行栈的维护&#xff0c;所以无法使用c语言。_HEAD定义了后面代码属于段名为.head .text的段。 内核起始部分代码被解压代码调用&#xff0c…