【C++】STL——list的模拟实现

news2024/9/28 23:37:15

list的模拟实现

在这里插入图片描述

文章目录

  • list的模拟实现
  • 一、list三个基本类的模拟实现总览
  • 二、节点类接口实现
      • 模拟实现
      • 构造函数
  • 三、迭代器类接口实现
    • 1.正向迭代器
      • 默认成员函数
      • 构造函数
      • 六种运算符重载 */->/++/--/!=/==
    • 2.反向迭代器
  • 四、list类接口实现
    • 1.默认成员函数
      • 1.1.构造函数
      • 1.2.析构函数
      • 1.3.拷贝构造函数
      • 1.4.赋值运算符重载函数
    • 2.访问相关函数
      • 2.1.front和back
    • 3.迭代器相关函数
      • 3.1.begin和end
      • 3.2.rbegin和rend
    • 4.插入删除相关函数
      • 4.1.insert
      • 4.2.push_back
      • 4.3.push_front
      • 4.4.erase
      • 4.5.pop_back
      • 4.6.pop_front
    • 5.其它相关函数
      • 5.1.resize
      • 5.2.clear
      • 5.3.empty_initialize
      • 5.4.swap

一、list三个基本类的模拟实现总览

前面list的学习中我们得知,list其实就是一个带头双向循环链表:

image-20221220162602095

现在要模拟实现list,要实现下列三个类:

  1. 模拟实现节点类
  2. 模拟实现迭代器的类
  3. 模拟list主要功能的类

这三个类的实现层层递进,我们的目标是为了实现list类,然而list的模拟实现其基本功能要建立在迭代器类和节点类均已实现好的情况下才得以完成。

namespace list_realize
{
	//模拟实现list当中的结点类
	template<class T>
	struct List_node
	{
		//成员函数
		List_node(const T& val = T()); //构造函数

		//成员变量
		T _data;                 //数据域
		List_node<T>* _next;   //后继指针
		List_node<T>* _prev;   //前驱指针
	};

	//模拟实现list正向迭代器
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef List_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> Self;

		__list_iterator(node* pnode);  //构造函数

		//各种运算符重载函数
		Self& operator++();
		Self& operator--();
		Self operator++(int);
		Self operator--(int);
		bool operator==(const Self& it) const;
		bool operator!=(const Self& it) const;
		Ref operator*();
		Ptr operator->();

		//成员变量
		node* _pnode; //一个指向结点的指针
	};

    //模拟实现list反向迭代器
	template<class Iterator, class Ref, class Ptr>
	class reverse__list_iterator
	{
		typedef reverse__list_iterator<Iterator, Ref, Ptr> Self;

		reverse__list_iterator(Iterator* it);  //构造函数

		//各种运算符重载函数
		Self& operator++();
		Self& operator--();
		Self& operator++(int);
		Self& operator--(int);
		bool operator==(const Self& rit) const;
		bool operator!=(const Self& rit) const;
		Ref operator*();
		Ptr operator->();

		//成员变量
    private:
		Iterator _it;//正向迭代器
	};

	//模拟实现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;
        typedef reverse__list_iterator<iterator, T&, T*> reverse_iterator;
		typedef reverse__list_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

		//默认成员函数
		list(); // 无参构造
        template <class InputIterator> 
		list(InputIterator first, InputIterator last); // 迭代器构造
        list(size_t n, const T& val = T()) // 带参构造
		list(const list<T>& lt); // 拷贝构造
		list<T>& operator=(const list<T>& lt); // 赋值运算符重载
		~list(); // 析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//访问容器相关函数
		T& front();
		T& back();
		const T& front() const;
		const T& back() const;

		//插入、删除函数
		void insert(iterator pos, const T& val = T());
		iterator erase(iterator pos);
		void push_back(const T& x);
		void pop_back();
		void push_front(const T& x);
		void pop_front();

		//其他函数
        void empty_initialize();
		size_t size() const;
		void resize(size_t n, const T& val = T());
		void clear();
		bool empty() const;
		void swap(list<T>& lt);

	private:
		node* _head; //指向链表头节点的指针
        size_t _size; // 链表节点个数
	};
}


二、节点类接口实现

因为list的本质为带头双向循环链表,所以其每个节点都要确保有下列成员:

  1. 前驱指针
  2. 后继指针
  3. data值存放数据

而节点类的内部只需要实现一个构造函数即可。

模拟实现

template<class T>
struct List_node //因为成员函数都是公有,就不设计成class了,直接struct
{
	List_node<T>* _next; // 前驱指针
   	List_node<T>* _prev; // 后继指针
    	T _data; // 记录存放数据

    List_node(const T& val = T())
        :_next(nullptr)
        , _prev(nullptr)
        , _data(val)
{}
};

构造函数

构造函数这里是用来对其成员变量的一个初始化。

List_node(const T& val = T())
   :_next(nullptr)
   , _prev(nullptr)
   , _data(val)
{}

三、迭代器类接口实现

这里强调下,因为list的特殊性,其本质是带头双向循环链表,对于链表,我们已然得知其内存空间是不连续的,是通过结点的指针顺次链接,我们不能像先前的string和vector一样直接解引用去访问其数据,结点的指针解引用还是结点,结点指针++还是结点指针,归根结底在于list物理空间不连续。而string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

为了能让list像vector一样去解引用,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。

  • 注意:

迭代器又分为正向迭代器和反向迭代器。

1.正向迭代器

  • 注意:

这里我迭代器类的模板参数里面包含了3个参数:

template<class T, class Ref, class Ptr>

而后文list类的模拟实现中,我对迭代器进行了两种typedef:

typedef __list_iterator<T, T&, T*> iterator;//普通迭代器
typedef __list_iterator<T, const T&, const T*> const_iterator;//const迭代器

根据这里的对应关系:Ref对应的是&引用类型,Ptr对应的是*指针类型,这里如果我是普通对象传过来的迭代器,生成对应的普通迭代器,如果是const对象传递过来的迭代器,会生成对应的const迭代器。

这样做的原因在于避免单独写一个支持不能修改迭代器指向结点数据的类而造成的复用性差。

默认成员函数

这里的默认成员函数我们只需要写构造函数。

  • 析构函数 – 结点不属于迭代器,不需要迭代器释放
  • 拷贝构造 – 默认浅拷贝即可
  • 赋值重载 – 默认浅拷贝即可

构造函数

这里我们通过结点的指针即可完成构造。通过结点指针构造一个迭代器。

__list_iterator(node* pnode)//通过结点指针构造一个迭代器
	:_pnode(pnode)
{}

六种运算符重载 */->/++/–/!=/==

  • *** 运算符重载**

*解引用的目的是为了获取结点里的_ data数据,因此我们直接return返回结点指向的_ data即可。

Ref operator*()//结点出了作用域还在,用引用返回
{
	return _pnode->_data;//返回结点指向的数据
}
  • -> 运算符重载

假设出现这样的场景,我链表存储的不是内置类型,而是自定义类型,如下:

struct AA
{
	AA(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}
	int _a1;
	int _a2;
};
void test()
{
	cpp::list<AA> lt;
	lt.push_back(AA(1, 1));
	lt.push_back(AA(2, 2));
	lt.push_back(AA(3, 3));
	lt.push_back(AA(4, 4));
}

对于内置类型和自定义类型成员的指针,其访问方式都是不同的:

int*  *it
AA*   (*it). 或者 it->

而这里我们应该重载一个->运算符。以便于访问自定义类型成员的指针的数据。

//->运算符重载
Ptr operator->()
{
return &(operator*());//返回结点指针所指结点的数据的地址
//或者return &_pnode->_data;
}

实现了->运算符重载后,我们执行it->_ a1,编译器将其转换成it.operator->(),此时获得的是结点位置的地址即AA*,而理应还有一个箭头->才能获取数据,也就是这样:it.operator->()->_a1

  • **总结:**编译器为了可读性进行优化处理,如果不优化应该是it->->_a1,优化以后,省略了一个箭头->。
  • ++ 运算符重载

++运算符分为前置++和后置++

  • 前置++

迭代器++的返回值还是迭代器,这里的++是为了让结点指向下一个结点的指针,注意前置++是要返回自增后的结点指针。

Self& operator++()//迭代器++的返回值还是迭代器
{
	_node = _pnode->_next;//直接让自己指向下一个结点即可实现++
	return *this;//返回自增后的结点指针
}
  • 后置++

为了区分前置++,后置++通常要加上一个参数以便区别。此外,后置++是返回自增前的结点指针。

Self operator++(int)//加参数以便于区分前置++
{
	Self tmp(*this);//拷贝构造tmp
	_node = _pnode->_next;//直接让自己指向下一个结点即可实现++
	return tmp;//注意返回tmp,才是后置++
}
  • – 运算符重载

–运算符也分前置–和后置–

  • 前置–

前置–是让此结点指向上一个结点,最后返回自减后的结点指针即可。

self& operator--()
{
	_pnode = _pnode->_prev;//让_node指向上一个结点即可实现--
	return *this;
}
  • 后置–

注意传参以区分前置–,最后返回的是自减前的结点指针即可。

self operator--(int)//记得传缺省值以区分前置--
{
	self tmp(*this);//拷贝构造tmp
	_pnode = _pnode->_prev;
	return tmp;
}
  • != 运算符重载

这里比较是否不等,是两个迭代器的比较,直接返回两个结点的位置是否不同即可。

//!=运算符重载
bool operator!=(const self& it)
{
	return _pnode != it._pnode;//返回两个结点指针的位置是否不同即可
}
  • == 运算符重载

这里直接返回俩结点指针是否相同即可。

//==运算符重载
bool operator==(const self& it)
{
	return _pnode == it._pnode;//返回俩结点指针是否相同
}

2.反向迭代器

反向迭代器是一个适配器模式(后文会将适配器)。相较于正向迭代器,反向迭代器有下面三种主要变化:

  • 反向迭代器的++执行的操作是正向迭代器里的–,
  • 反向迭代器里的–执行的操作是正向迭代器里的++
  • 反向迭代器的*解引用和->操作指向的是前一个数据

总代码如下:

template <class Iterator, class Ref, class Ptr>
class reverse__list_iterator
{
	typedef reverse__list_iterator<Iterator, Ref, Ptr> Self;
public:
	reverse__list_iterator(Iterator it)
		:_it(it)
	{}

    Ref operator*()
    {
        Iterator prev = _it;
        return *--prev;
    }
    Ptr operator->()
    {
    	return &operator*();
    }

    Self& operator++()
    {
        --_it;
        return *this;
    }

    Self& operator--()
    {
        ++_it;
		        return *this;
    }

    Self& operator++(int)
    {
        _it--;
        return *this;
    }

    Self& operator--(int)
    {
   		_it++;
        return *this;
    }

    bool operator!=(const Self& rit) const
    {
        return _it != rit._it;
    }
    
    bool operator==(const Self& rit) const
    {
        return _it == rit._it;
    }

private:
	Iterator _it;
};

四、list类接口实现

此接口的核心任务是为了模拟实现list类的一些核心功能,好比如插入删除,迭代器等等。

在list类中的唯一成员变量即自定义类型的变量,由先前的结点类构成的头结点。

1.默认成员函数

1.1.构造函数

  • 无参构造:

此目的是为了对哨兵位的头结点_head进行初始化:

list()
{
	_head = new Node();//申请一个头结点
	_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;
    }
}
  • 带参构造:

初始化n个值为val

list(size_t n, const T& val = T())
{
	empty_initialize();
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}

1.2.析构函数

这里可以先复用clear函数把除了头结点的所有结点给删除掉,最后delete头结点即可。

~list()
{
	clear();
    delete _head;
	_head = nullptr;
}

1.3.拷贝构造函数

假设要用lt1拷贝构造lt2。

  • 传统写法:

首先复用empty_init对头结点进行初始化,接着遍历lt1的元素,在遍历的过程中尾插将lt1的元素尾插到lt2上即可。这里直接利用push_back自动开辟空间完成深拷贝。

list(const list<T>& lt)
//list(const list& lt) // 不建议,不规范
{
	empty_initialize();
	for (const auto& e : lt)
	{
		push_back(e);
	}
}
  • 现代写法:

这里先初始化lt2自己,再把lt1引用传参传给lt,传lt的迭代器区间构造tmp,复用swap交换头结点指针即可完成深拷贝的现代写法。

list(const list<T>& lt)
{
	empty_initialize(); // 空初始化
	list<T> tmp(lt.begin(), lt.end()); // 使用迭代器区间构造tmp
	swap(tmp); // 交换头结点指针
}

1.4.赋值运算符重载函数

假设要把lt1赋值给lt2。

  • 传统写法:
list<T>& operator=(const list<T>& lt)
{
	if (this != &lt)
	{
		clear();
		for (const auto& e : lt)
		{
			push_back(e);
		}
	}
	return *this;
}
  • 现代写法:

这里直接给出现代写法。注意这里传值传参把lt1传给lt自定义类型传值传参调用拷贝构造,拷贝构造完成的是深拷贝生成了lt,复用swap函数交换lt1与lt的头结点指针指向即可,最后返回*this。

list<T>& operator=(list<T> lt)//套用传值传参去拷贝构造完成深拷贝
{
	swap(lt);
	return *this;
}

2.访问相关函数

2.1.front和back

front和back函数分别用于获取第一个有效数据和最后一个有效数据,因此,实现front和back函数时,直接返回第一个有效数据和最后一个有效数据的引用即可。

T& front()
{
	return *begin(); //返回第一个有效数据的引用
    // return _head->_next->_data;
}
T& back()
{
	return *(--end()); //返回最后一个有效数据的引用
    // return _head->_prev->_data;
}

我在这里

const T& front() const
{
    return *begin();
	//return _head->_next->_data;
}

const T& back() const
{
	return _head->_prev->_val;
}

3.迭代器相关函数

3.1.begin和end

  • begin的作用是返回第一个位置的结点的迭代器,而第一个结点就是哨兵位头结点的下一个结点,因此,直接return返回_head的_next即可。
  • end的作用就是返回最后一个有效数据的下一个位置的迭代器,而这里对于list指的就是头结点_head的位置。

begin和end均分为普通对象调用和const对象调用,因此要写两个版本。

  • 普通对象调用版
iterator begin()//begin返回的就是第一个有效数据,即头结点的下一个结点
{
	return iterator(_head->_next);//构造了一个匿名对象,通过调用构造函数利用头结点指向的第一个结点作为参数,来返回头结点
	//return _head->_next;  也可以这样写
}

iterator end()
{
	return iterator(_head);//end返回的是最后一个结点的下一个结点,就是头结点_head
	//return _head;  也可以这样写
}
  • const对象调用版
const_iterator begin() const
{
	return const_iterator(_head->_next);
	//return _head->_next; 
}
const_iterator end() const
{
	return const_iterator(_head);
	//return _head;  也可以这样写
}

3.2.rbegin和rend

rbegin就是正向迭代器里end()的位置,rend就是正向迭代器里begin()的位置。

image-20221222204544241

rbegin和rend同样分为普通对象调用和const对象调用:

  • 普通对象调用:
reverse_iterator rbegin()
{
	return reverse_iterator(end());
}

reverse_iterator rend()
{
	return reverse_iterator(begin());
}
  • const对象调用:
const_reverse_iterator rbegin() const
{
	return const_reverse_iterator(end());
}

const_reverse_iterator rend() const
{
	return const_reverse_iterator(begin());
}

4.插入删除相关函数

4.1.insert

实现insert首先创建一个新的结点存储插入的值,接着取出插入位置pos的结点为cur,记录cur的上一个结点位置prev,先链接prev和newnode,再链接newnode和cur即可。最后记得要返回新插入元素的迭代器位置。

//insert,插入pos位置之前                                                                               
iterator insert(iterator pos, const T& val)
{
	node* newnode = new node(val); //创建新的结点
	node* cur = pos._pnode; //迭代器pos处的结点指针
	//链接prev和newnode
	node* prev = cur->_prev;
	prev->_next = newnode;
	newnode->_prev = prev;
   	//链接newnode和cur
	newnode->_next = cur;
	cur->_prev = newnode;
	++_size;
    //返回新插入元素的迭代器位置
	return iterator(newnode);
}                                                                                                
  • **补充:**list的insert不存在野指针和意义改变的迭代器失效问题。

4.2.push_back

  • 法一:

首先,创建一个新结点用来存储尾插的值,接着找到尾结点。将尾结点和新结点前后链接构成循环,再将头结点和新结点前后链接构成循环即可。

void push_back(const T& x)
{
	Node* tail = _head->_prev;//找尾
	Node* newnode = new Node(x);//创建一个新的结点
	//链接tail和newnode
	tail->_next = newnode;
	newnode->_prev = tail;
	//链接newnode和头结点_head
	newnode->_next = _head;
	_head->_prev = newnode;
}
  • 法二:

这里也可以复用insert函数,当insert中的pos位置为哨兵位头结点的位置时,实现的就是尾插,因为insert插入是在pos位置前插入,而pos位哨兵位头结点时,在其前一个位置(尾部)插入就是实现了尾插。

void push_back(const T& x)
{
	//法二:复用insert
	insert(end(), x);
}

4.3.push_front

直接复用insert函数,当pos位置为begin()时,获得的pos就是第一个有效结点数据,即可满足头插。

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

4.4.erase

erase删除的是pos位置的结点,首先取出pos位置的结点为cur,记录cur上一个结点的位置为prev,再记录cur下一个结点的位置为next,链接prev和next,最后delete释放cur的结点指针即可。最后记得返回删除元素后一个元素的迭代器位置。

iterator erase(iterator pos)
{
     assert(pos != end())
     node* cur = pos._pnode;
     node* prev = cur->_prev;
     node* next = cur->_next;
     //链接prev和next
     prev->_next = next;
     next->_prev = prev;
     delete cur;
     cur = nullptr;
     _size--;
     return iterator(next);//返回删除元素下一个元素的迭代器位置
 }

erase存在迭代器失效问题,为了避免此问题,我们修改了返回值为iterator。

4.5.pop_back

直接复用erase即可,当pos位置为–end()时,pos就是最后一个结点的位置,实现的就是尾删。

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

4.6.pop_front

直接复用erase即可,当pos位置为begin()时,pos就是第一个有效数据,实现的就是头删。

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

5.其它相关函数

5.1.resize

resize函数的规则:

  • 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
  • 若当前容器的size大于所给n,则只保留前n个有效数据。

实现resize函数时,不要直接调用size函数获取当前容器的有效数据个数,因为当你调用size函数后就已经遍历了一次容器了,而如果结果是size大于n,那么还需要遍历容器,找到第n个有效结点并释放之后的结点。

这里实现resize的方法是,设置一个变量oldsize,用于记录当前所遍历的数据个数,然后开始变量容器,在遍历过程中:

当oldsize大于或是等于n时遍历结束,此时说明该结点后的结点都应该被释放,将之后的结点释放即可。
当容器遍历完毕时遍历结束,此时说明容器当中的有效数据个数小于n,则需要尾插结点,直到容器当中的有效数据个数为n时停止尾插即可。

void resize(size_t newsize, const T& val = T())
{
	size_t oldsize = size();
	if (newsize <= oldsize)
	{
		// 有效元素个数减少到newsize
		while (newsize < oldsize)
		{
			pop_back();
			oldsize--;
		}
	}
	else
	{
		while (oldsize < newsize)
		{
			push_back(val);
			oldsize++;
		}
	}
}

5.2.clear

clear的作用是清除除了头结点外的所有结点,这里可以复用erase并通过遍历的方式逐个删除。

void clear()
{
	//复用erase
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);//用it接收删除后的下一个结点的位置
	}    
}

5.3.empty_initialize

由于对头结点初始化使用频繁,我们特定把它封装成一个函数,作用就是把哨兵位的头结点开辟出来。

//空初始化  对头结点进行初始化
void empty_initialize()
{
	_head = new Node();
	_head->_next = _head;
	_head->_prev = _head;
    _size = 0;
}

5.4.swap

对于链表的swap,直接交换头结点指针的指向即可完成。直接复用库函数的swap即可。

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

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

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

相关文章

Hive3.1.3安装部署_最小化部署_元数据MySQL部署_Hiveserver2部署_metastore部署---大数据之Hive工作笔记0012

hbase 实时分析 hive 离线分析 这里是新版本的hive3.1.3的安装 关于hive的原理之前的博客已经详细说了 可以看到上面是hive运行的原理图 词法分析 语法分析

P6软件如何设置权重体系

卷首语 同时&#xff0c;由于项目包含不同专业、不同类型的活动&#xff0c;需通过建立科学的测量体系以将底层活动的进度逐层汇总从而获得项目总体进度数据。 权重体系的确定 进度检测权重体系的建立过程&#xff0c;即是确定进度检测层级中的每项元素对其上一级元素进度的…

Github每日精选(第102期): PyGWalker将panda数据帧转换为Tableau风格的用户界面,用于可视化分析

PyGWalker可以简化Jupyter笔记本的数据分析和数据可视化工作流程&#xff0c;方法是将panda数据帧转换为Tableau风格的用户界面进行可视化探索。 PyGWalker&#xff08;发音像“Pig Walker”&#xff0c;只是为了好玩&#xff09;被命名为“Graphic Walker的Python绑定”的缩写…

ChIP-seq 分析:Peak 注释与可视化(9)

1. 基因注释 到目前为止&#xff0c;我们一直在处理对应于转录因子结合的 ChIPseq 峰。顾名思义&#xff0c;转录因子可以影响其靶基因的表达。 转录因子的目标很难单独从 ChIPseq 数据中确定&#xff0c;因此我们通常会通过一组简单的规则来注释基因的峰&#xff1a; 如果峰与…

二月安全月报 | 45亿条快递数据疑泄露,Twitter史上最大规模宕机

为了让大家更全面的了解网络安全的风险&#xff0c;顶象针对每月值得关注的安全技术和事件进行盘点总结。 国内安全热点 &#x1f449;业务安全 男子注册上万账号薅羊毛获利13万 近日&#xff0c;上海市&#xff0c;由闵行区人民检察院提起公诉的刘某某诈骗一案开庭审理&…

Stream流和不可变集合

一、不可变集合 什么是不可变集合&#xff1f; 不可变集合&#xff0c;就是不可被修改的集合。 集合的数据项在创建的时候提供&#xff0c;并且在整个生命周期中都不可改变。否则报错。 为什么要创建不可变集合&#xff1f; 如果某个数据不能被修改&#xff0c;把它防御性地拷…

Apache Airflow Hive Provider 任意Hive命令执行漏洞

漏洞描述 Apache Airflow 是一个以编程方式管理 workflow 的平台&#xff0c;Airflow Hive Provider 是一个使用 SQL 进行读取、写入和管理分布式存储中的大型数据集的工具包&#xff0c;_prepare_cli_cmd 方法用于创建 Hive 连接命令列表。 由于 Airflow Hive Provider 5.1.…

论文阅读:MPViT : Multi-Path Vision Transformer for Dense Prediction

中文标题&#xff1a;基于多路视觉Transformer的密集预测 提出问题 创新点 提出了一种具有多路径结构的多尺度嵌入方法&#xff0c;以同时表示密集预测任务的精细和粗糙特征。全局到局部的特征交互&#xff08;GLI&#xff09;&#xff0c;以同时利用卷积的局部连通性和转换器…

汽车刹车传感

一、方案概述&#xff1a;刹车传感器&#xff0c;作用于刹车系统的传感器类型&#xff0c;帮助驾驶人员判断刹车片的磨损情况&#xff0c;便于及时检修维护&#xff0c;保证制度系正常稳定工作。刹车片报警基本有两种&#xff0c;第一种是比较简单的机械报警&#xff0c;就是当…

Android Framework-Android进程/线程和程序内存优化

Android进程和线程 进程&#xff08;Process&#xff09;是程序的一个运行实例&#xff0c;以区别于“程序”这一静态的概念&#xff1b;而线程&#xff08;Thread&#xff09;则是CPU调度的基本单位。 Android中的程序和进程具体是什么概念呢&#xff1f; 一个应用程序的主入…

十一、Vben框架部分组件样式的重新封装

在使用vben框架的时候发现部分的样式不符合实际的需求&#xff0c;ant-design-vue的样式也不支持我们的需求&#xff0c;那怎么办呢&#xff0c;只能自己来修改&#xff0c;下面我就改大家说说我遇到的一些修改过的样式和组件。 一、inputNumber带前后标志 先看下目前支持的样…

命令查看Linux服务器内存、CPU、显卡、硬盘使用情况

命令查看Linux服务器内存、CPU、显卡、硬盘使用情况 查看内存使用情况 使用命令&#xff1a;free -m 大致结果类似下图&#xff1a; 内存占用情况 参数解释&#xff1a; Mem行&#xff08;单位均为M&#xff09;&#xff1a; total&#xff1a;内存总数used&#xff1a;已…

4.4 like通配符关键字过滤查询数据

文章目录1.概述2.LIKE关键字3.百分号&#xff08;%&#xff09;通配符3.1 单个百分号&#xff08;%&#xff09;通配符3.2 多个百分号&#xff08;%&#xff09;通配符3.3 在值的中间使用百分号&#xff08;%&#xff09;通配符3.4 注意事项4.下划线&#xff08;_&#xff09;通…

centos7 配置samba

samba概述&#xff1a; Windows与Linux之间通信的桥梁&#xff0c;Samba是一个非常强大的文件服务器。Samba端口&#xff1a;udp 137 udp138&#xff0c;tcp139 tcp445。Samba工作模式&#xff1a;C/S模式&#xff08;客户端-服务器&#xff09; samba应用环境 1、文件共享&…

python库--urllib

目录 一.urllib导入 二.urllib爬取网页 三.Headers属性 1.使用build_opener()修改报头 2.使用add_header()添加报头 四.超时设置 五.get和post请求 1.get请求 2.post请求 urllib库和request库作用差不多&#xff0c;但比较起来request库更加容易上手&#xff0c;但该了…

SpringCloud学习笔记 - 分布式系统全局事务管理 - Seata1.5.2+Nacos+OpenFeign

1. Seata 是什么? 由于业务和技术的发展&#xff0c;单体应用被拆分成微服务应用&#xff0c;原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源&#xff0c;业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证&#xff0c; 但是全…

【跟着ChatGPT学深度学习】ChatGPT带我学情感分析

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

5G+车联网按下加速键,这家企业已经走在行业前列

进入2023年&#xff0c;5G车联网进入了快速增长阶段&#xff0c;并且正在逐步替代4G的存量市场。 为了更好地满足5G车联网市场的需求&#xff0c;移远通信正式推出了符合3GPP Release 16标准的车规级5G NR模组AG59x系列。据了解&#xff0c;全新的产品在5G传输速度、低时延、高…

SpringBoot addResourceHandlers 代理静态资源无法访问 Java获取linux文件中文名乱码 Linux设置中文字符集

SpringBoot addResourceHandlers 代理静态资源无法访问 Java获取linux文件中文名乱码 linux设置中文字符集Windows中使用SpringBoot addResourceHandlers代理静态资源访问Linux中使用SpringBoot addResourceHandlers代理静态资源访问修改路径问题一度以为Linux不能用这种方式代…

概念解读稳定性保障

什么是稳定百度百科关于稳定的定义&#xff1a;“稳恒固定&#xff1b;没有变动。”很明显这里的“稳定”是相对的&#xff0c;通常会有参照物&#xff0c;例如 A 车和 B 车保持相同速度同方向行驶&#xff0c;达到相对平衡相对稳定的状态。那么软件质量的稳定是指什么呢&#…