C++的STL标准模版库容器--list类

news2024/10/7 0:28:39

前言

list(双向链表)类属于STL标准模版库容器模块,它的迭代器是双向迭代器,也比较好理解,它申请空间不是一次申请一块空间而是每一个节点各自独立的空间,它不再能够支持随机访问和[],如果想要和string类容器或者vector容器一样使用[]进行访问数据,需要重新定义迭代器,重载运算符

单向迭代器:只支持单方向移动也就是++,如单链表 

双向迭代器:它比单向多了一个功能向后移动,支持++和--,但不能够随机访问数据,如双向链表,树

随机迭代器:除了包含以上两个迭代器都有的功能以外还可以通过索引访问就像数组一样,如stirng类和vector类

注意:list类含有哨兵位,所以第一个元素都是哨兵位的下一个,末尾元素是哨兵位的上一个,后面我将不在特意强调


我比较喜欢依靠注释讲解,如有解释不到位的地方,望包含

list类各项接口详细文档传送门:list - C++ Reference

list类迭代器相关:

begin        //返回链表的第一个元素的迭代器

end           //返回链表结尾元素的迭代器

容量相关:

empty       //判空

size          //返回有效元素个数

访问数据:

front         //访问并返回第一元素的数据

back         //访问并返回末尾元素的数据

迭代器类相关:

//我们需要自己进行iterator(迭代器)封装,因为要屏蔽底层细节使其使用起来和vector类一致

operator*          //访问元素里的数据

operator==       //对比两个迭代器内的指针是否相等

operator!=        //对比两个迭代器内的指针是否不相等

operator++       //跳到当前节点的下一个节点

operator--         //跳到当前节点的上一个节点

operator->       //返回一个类型的指针

对元素操作相关的:

push_front        //在哨兵位节点的下一个节点之前插入新元素(头插)

pop_front          //删除哨兵位节点的下一个节点(头删)

push_back        //在末尾元素的后面插入新元素(尾插)

pop_back          //删除末尾元素(尾插)

insert                 //在迭代器pos位置上插入新数据(需要通过Find(查找)找到pos节点返回其迭代器)

erase                 //删除某个节点或者迭代器区间

swap                 //交换两个对象的数据(直接将哨兵位互换即可)

operator=          //将一个对象赋值给另一个对象

resize               //将链表元素缩小或者扩大到N个

clear                 //将链表初始化

目录

list类的成员变量,迭代器类的成员变量:

list类中与迭代器相关的函数及容量相关函数:

 迭代器类相关:

list类中的构造函数,析构函数,拷贝构造:

构造函数:

析构函数:

拷贝构造:

 对元素操作相关的函数:

元素插入:

元素删除:

operator=(赋值),swap(交换),clear(初始化),resize(增大个数或者缩小):


list类的成员变量,迭代器类的成员变量:

首先需要定义一个节点类,定义这个类就当成在C语言中的结构体就行,只不过struct在C++中升级了,它也可以创建类,所以需要构造函数

注意:struct和class两个都可以创建类,但struct默认里面的成员们是公共(public)的,class默认是私有(private)

template <class T>               //节点
struct list_node
{
	T _val;                      //数据
	list_node<T>* _next;         //下一个节点的指针
	list_node<T>* _prev;         //上一个节点的指针

	list_node(const T&val=T())   //构造
		:_val(val)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};

现在节点定义好了,接下来就是list类的成员变量

list类的成员变量是一个指向节点的指针,我这里指向哨兵位;还有一个我自加的count方便查看节点个数

template <class T >
class list
{
public:    //对节点重命名                    
    using Node = list_node<T>;    //这个是C++11新加的重命名方式
  //typedef list_node<T> Node;    //typedef当然还是可以的
private:
	Node* _root=nullptr;          //指向哨兵位
	size_t count=0;               //记录节点个数
};

后面的成员函数们会经常使用到迭代器遍历,但list类与vector类不同,它是由一个个节点链接起来的所以直接++是肯定到不了下一个节点的,现在我们需要在迭代器类中重载“++”运算符来帮助迭代器到达下一个节点

现在我们来实现一个迭代器,迭代器的底层是一个节点指针,它会接收传进来的节点指针指向的地址

template <class T>
struct list_iterator
{
	using Node = list_node<T>;      //对节点重命名
	using self = list_iterator<T>;  //对自己重命名不然作为返回值写老长
	Node* _node;                    //指向当前节点的成员变量

	list_iterator(Node*cur)         //指向节点的指针都可以传过来变成一个迭代器
		:_node(cur){}
};

list类中与迭代器相关的函数及容量相关函数:

现在迭代器类已经创建好了,可以实现begin()和end()函数了,他们的作用分别是创建一个有效元素起始位置的迭代器,末尾元素的迭代器。容量部分函数比较少且简单直接看代码实现的即可。

template <class T >
class list
{
public:
	using Node = list_node<T>;
	using iterator = list_iterator<T>; 
	iterator begin()    //第一个有效元素的迭代器
	{
		return _root->_next;  //返回时隐式类型转成迭代器  
	}
	iterator end()      //末尾元素的迭代器
	{
		return _root;         //道理同上
	}
    bool Empty()        //直接利用count判空即可
    {
	    return count == 0;
    }
    void Size()         //返回有效元素个数即可
    {
	    return count;
    }    
private:
    ...
};

 迭代器类相关:

现在对迭代器的各项运算符进行重载,list的各项初始化,插入,删除,修改,查找都会用到这些函数

<1>operator*()

重载运算符“*”(解引用),解引用是为了找到数据,所以直接将数据返回即可

template <class T>
struct list_iterator
{
   /...
   T& operator*()
   {
	    return _node->_val;   //成员变量就是当前节点指针直接索引即可
   } 
};

<2>operator++和operator--

这两位就非常熟悉了吧,在vector类中指针中的地址加上当前类型(自定义类型,int,char...)的大小就可以到达下一个元素的起始地址,但list类是依靠next指针到达下一个节点,所以直接把list到达下一个节点的逻辑写进重载的运算符即可

template <class T>
struct list_iterator
{
    /...
    using self = list_iterator<T>;
    self& operator++()    //前置++       
	{
		_node = _node->_next;    //把_next中的地址给到当前指针       
		return *this;            //返回*this
	}
	self operator++(int)  //后置++    
	{
        self temp (*this);       //构造一个原来的迭代器(临时对象)
		_node = _node->_next;    
		return temp;             //返回临时对象
	}
	self& operator--()    //前置--       
	{
		_node = _node->_prev;    //把_prev中的地址给当前指针
		return *this;            //返回*this
	}
	self operator--(int)  //后置--
	{
        self temp (*this);      //构造一个原来的迭代器(临时对象)
		_node = _node->_prev;
		return temp;            //返回临时对象
	}     
};

<3>operator==和operator!=

重载==和!=里面的对比逻辑不能对比_val值,如果有多个相同的值就会有漏洞(比如遍历查找返回),正确的做法应该是对比两者的指针,这样就可以保证在有多个同样值的情况下正确判断

注意:对比的两个参数都是迭代器

template <class T>
struct list_iterator
{
    /...
   	bool operator==(const self& l1)
	{
		return _node == l1._node;    //对比节点指针
	}
	bool operator!=(const self& l1)
	{
		return _node != l1._node;
	}
};

<4>operator->

重载->主要是为了应对自定义类型。自定义类型中大概率会有多个数据,又想要快速索引数据,重载->是一个利器。它会返回一个T类型指针

注意:重载->在调用时会有隐藏的效果,使它在调用时看起来就和指针一样

下面实现一个简单的用法,我也会把它实际的样子写出来:

template <class T>           //这个是list的迭代器里的
struct list_iterator
{
    /...
   	T* operator->()
	{
		return &_node->_val; //咱就一个数据直接返回数据的指针即可
	}
};

//operator->()使用试例
struct AA
{
	int a1 = 1;
	int a2 = 2;
};
int main()
{
	list<AA> l1;
	l1.Push_back(AA());    
	l1.Push_back(AA());
	l1.Push_back(AA());
	l1.Push_back(AA());
	auto it = l1.begin();   

	cout << it->a1<<":"<<it->a2<<endl;
    //实际上长这样
    //cout << it.operator->()->a1<<":"<<it.operator->()->a2<<endl;
}

上面是调用试例结果

因为我实现的list类里没有多个数据就不太需要使用->,实现后调用看起来很怪,非常怪

也不要想着返回对象里的节点指针,这个iterator(迭代器)类是为了服务list类,所以他的T类型和list类中的T一致,所以在只有一个数据的情况下->基本没用

cout<<*(it.operator->())     //因为返回的数据的指针直接解引用拿数据即可

list类中的构造函数,析构函数,拷贝构造:

构造函数:它有四种类型的构造,同时因为哨兵位的存在需要额外写一个哨兵位初始化

1.无参构造

2.插入N个节点,每个节点用T类型初始化

3.迭代器区间构造

4.数组或者一串元素构造

构造函数:

无参构造只需要初始化哨兵位即可

void Empty_init()
{
	_root = new Node;        //申请空间
	_root->_next = _root;    //哨兵位的两个指针都指向直接
	_root->_prev = _root;
	count = 0;               //计数为0
}
list()
{
	Empty_init();            //无参构造
}

插入N个节点,每个节点用T类型初始化构造:这里需要对N进行一次判断,假设N为0就单独对哨兵位初始化即可。它和vector类一样需要一个int类型作为第一个参数的函数重载,来避免用插入N个数据,节点用int类型初始化的错误匹配到迭代器初始化

注意:在没有实现int类型作为第一个参数的函数重载时,模板会推导两次,两个类型为int,按需实例化是挑做符合要求的所以会跑到迭代器模板初始化去,实现了就推导一次,就得出了两个int所以不再会跑到模板去

list(size_t n, const T& val = T())
{
	if (n == 0)                //判断n是否为0
	{
		Empty_init();	
	}
	else
	{
		Empty_init();          //不能忘了哨兵位初始化
		while (n--)            //连续插入
		{
			Push_back(val);    //调用尾插函数
		}
	}
}
list(int n, const T& val = T())    //这个函数重载主要是为了避免错误匹配
{
	if (n == 0)
	{
		Empty_init();
	}
	else
	{
		Empty_init();
		while (n--)
		{
			Push_back(val);
		}
	}
}

迭代器区间构造:这里需要使用模板,因为要匹配每个类型


template <class InputIterator>
list(InputIterator first, InputIterator last)
{	
    Empty_init();            //哨兵位初始化
	while (first != last)    //迭代器区间遍历
	{
		Push_back(*first);   //获取数据插入即可
		first++;             //不要忘了迭代器++
	}
}

数组或者一串元素构造:实现这个构造需要使用到initializer_list类,它的作用主要是讲一串元素或者数组转变成对象,然后通过迭代器遍历插入

注意:initializer_list类初始化,一组数据或者数组进行传参时会强转成该类对象,它能自动识别类型,并含有迭代器

list(initializer_list<T> li)
{
	Empty_init();            //哨兵位初始化
	for (auto& ch : li)      //迭代器遍历
	{
		Push_back(ch);       //插入
	}
}
析构函数:

析构函数就不使用迭代器遍历删除了(不是说不可以),因为迭代器删除会导致迭代器失效容易出问题,所以这里我用传统的循环释放

~list()
{
	Node* cur = _root;                //从哨兵位开始
	while (cur!=_root)                //不能回到哨兵位
	{
		Node* next = cur->_next;      //储存下一个节点
		delete cur;                    
		cur = next;
	}
	_root = nullptr;                  //哨兵位指针给空
}
拷贝构造:

拷贝构造没什么难度,初始化哨兵位后只需要将属于该类型的对象引用传入,然后遍历插入即可

list(const list<T>& l1)
{
	Empty_init();        //初始化哨兵位
	for (auto ch : l1)   //遍历对象插入
	{
		Push_back(ch);
	}
}

 对元素操作相关的函数:

元素插入:

push_front(头插)和push_back(尾插):

尾插要修改的节点如下图,头插就是插入的位置不同,逻辑还是一致的

void Push_back(const T& val)    //尾插
{
	/*Node* newnode = new Node(val);
	Node* ptail = _root->_prev;        //头节点的prev就是尾

	newnode->_next = _root;            //新节点的_next指向哨兵位
	newnode->_prev = ptail;            //新节点的_prev指向记录的尾
	ptail->_next = newnode;            //原来的尾_next连接新节点
	_root->_prev = newnode;            //哨兵位的_prev连接新节点
	count++;*/                //计数++
    //这里复用了Insert函数
	Insert(end(), val);
}

void Push_front(const T& val)   //头插
{
	/*Node* newnode = new Node(val);    //这里的连接方式和上面基本一致
	Node* pnext = _root->_next;

	newnode->_next = pnext;
	newnode->_prev = _root;
	pnext->_prev = newnode;
	_root->_next = newnode;
	count++;*/
    //复用了Insert函数
	Insert(begin(), val);
}

Insert(插入):在pos位置上插入一个元素很轻松,迭代器区间插入则需要在此基础上实现,

因为要复用插入单个元素

注意:迭代器区间遍历使用模板是为了也可以使用其他相同T类型的对象插入,比如vector<int>类型的对象list<int>的迭代器区间插入就支持遍历插入,因为都是int

void Insert(iterator pos, const T& val)        //单个元素pos位置插入
{
	Node* cur = pos._node;              //获取迭代器中存放的节点指针
	Node* newprev = cur->_prev;         //记录插入节点的前一个节点
	Node* newnode = new Node(val);      //获取新节点

	newnode->_next = cur;               //新节点链接前后节点
	newnode->_prev = newprev;           
	
	newprev->_next = newnode;           //前后节点的部分指针修改
	cur->_prev = newnode;
	count++;                            //计数++
}
//迭代器区间插入
template<class Inputiterator>
void Insert(iterator pos, Inputiterator first, Inputiterator last)
{
	while (first != last)               //直接迭代器区间遍历
	{
		T newnode = *first;             //获取迭代器里的val
		Insert(pos, newnode);           //pos位置插入
		first++;
	}
}
元素删除:

尾删需要修改的节点如下图,头删只是删除的节点位置不同,逻辑一致

上面有头插尾插,这里自然有pop_front(头删)和pop_back(尾删)

void Pop_back()                    //这两个函数都可以复用Erase
{
	/*Node* cur = _root->_prev;    //找到尾节点
	Node* Next = cur->_next;       //记录尾节点的前后节点
	Node* Prev = cur->_prev;

	delete cur;                    //这里是直接指向节点的指针所以释放即可
	Next->_prev = Prev;            //连接前后节点
	Prev->_next = Next;
	count--;*/                     //计数--
    //复用Erase
	Erase(end());    
}
void Pop_front()
{
	/*Node* cur = _root->_next;    //这个整体逻辑和上面一致,只换了删除的节点罢了
	Node* Next = cur->_next;
	Node* Prev = cur->_prev;

	delete cur;
	Next->_prev = Prev;
	Prev->_next = Next;
	count--;*/
	Erase(begin());
}

Erase(删除):删除就需要注意一下了,我们通过cur指针获取迭代器里的节点指针,在删除时应该释放迭代器里的指针,删除cur确实能够释放节点,但是!迭代器里的指针依旧指向该空间,那不就是一个野指针了吗?尽管前后节点已经链接起来了,这个问题不会造成结果影响,但不能放过每一个可能出现的错误

下图为错误演示:可以看到我访问那释放后的空间不会报错,获取的值(newval)是随机值

下面是正确做法:

void Erase(iterator pos)
{
	Node* cur = pos._node;        //获取迭代器中的节点
	Node* Next = cur->_next;      //记录前后节点
	Node* Prev = cur->_prev;

	delete pos._node;   //释放迭代器中的节点指针,cur在生命周期结束后自动释放
	Next->_prev = Prev; //链接节点          
	Prev->_next = Next;
	count--;            //记数--
}
//迭代器区间删除
void Erase(iterator first, iterator last)
{
	Node* cur = first._node;      //获取迭代器节点
	Node* final = last._node;
	while (cur!=final)            //循环判断
	{
		Node* curnext = cur->_next;    //储存下一个节点
		Erase(cur);                    //删除当前节点
		cur = curnext;                 //将下一个节点赋值给cur
	}
}
operator=(赋值),swap(交换),clear(初始化),resize(增大个数或者缩小):

赋值需要借助swap(交换)来辅助完成,如果不借助swap的话,也可以将主链表初始化后遍历赋值链表插入,但那手搓也太麻烦了

void clear()                        //初始化
{
	Node* cur = _root->_next;       //指向哨兵位的下一个节点
	while (cur != _root)            //循环删除
	{
		Node* next = cur->_next;
		delete cur;
		cur = next;
	}
	count = 0;                      //计数归零
}

void swap(list<T>& l1)              //交换成员变量
{
	std::swap(_root, l1._root);
	std::swap(count, l1.count);
}

list<T>& operator=(list<T> l1)      //这里传参触发拷贝构造
{
	swap(l1);                       //交换临时对象和主对象的成员变量
	return *this;                   //返回*this指针
}

resize:这个不太需要多少技巧,是增大到N个就尾插,如果缩小到N个就遍历到N个后将它的_next指针修改和哨兵位的_prev指针修改即可

void resize(size_t n, const T& val=T())
{
	if (count>n)                //判断个数大小
	{
		Node* Re = _root;
		while (n--)
		{
			Re = Re->_next;
		}
		Re->_next = _root;
        _root->_prev = Re;
		count = n;
	}
	else 
	{
		while (count < n)      //这里不需要修改count,尾插的时候他已经自加了
		{
			Push_back(val);
		}
	}
}
元素修改:

这个需要自己实现一个find(查找),返回迭代器然后直接解引用修改即可。实际上在上面的操作中就已经完成了,剩下的就是需要遍历查找返回即可

iterator Find(size_t val)
{
	auto it = begin();    //遍历查找返回
	while (it != end())
	{
		if (*it == val)
		{
			return it;
		}
		it++;
	}
	return nullptr;
}

本篇文章的主要内容就到这里了,希望能够对你产生帮助,感谢阅读

下面是所有实现代码和测试用例


template <class T>
struct list_node
{
	T _val;
	list_node<T>* _next;
	list_node<T>* _prev;
	list_node(const T&val=T())
		:_val(val)
		,_next(nullptr)
		,_prev(nullptr)
	{}
};
template <class T,class Ref,class Ptr>
struct list_iterator
{
	using Node = list_node<T>;
	using self = list_iterator<T,Ref,Ptr>;
	Node* _node;

	list_iterator(Node*cur)
		:_node(cur){}

	Ptr operator->()
	{
		return &_node->_val;
	}
	Ref operator*()
	{
		return _node->_val;
	}
	self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	self operator++(int)
	{
		self temp (*this);
		_node = _node->_next;
		return temp;
	}
	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	self operator--(int)
	{
		self temp(*this);
		_node = _node->_prev;
		return temp;
	}
	bool operator==(const self& l1)
	{
		return _node == l1._node;
	}
	bool operator!=(const self& l1)
	{
		return _node != l1._node;
	}
};
template <class T >
class list
{
public:
	using Node = list_node<T>;
	using iterator = list_iterator<T,T&,T*>;
	using const_iterator = list_iterator<T, const T&, const T*>;
	iterator begin()
	{
		return _root->_next;
	}
	iterator end()
	{
		return _root;
	}
	const_iterator begin()const
	{
		return _root->_next;
	}
	const_iterator end()const
	{
		return _root;
	}
	bool Empty()
	{
		return count == 0;
	}
	void Size()
	{
		return count;
	}
	void Empty_init()
	{
		_root = new Node;
		_root->_next = _root;
		_root->_prev = _root;
		count = 0;
	}
	list()
	{
		Empty_init();
	}
	list(size_t n, const T& val = T())
	{
		if (n == 0)
		{
			Empty_init();	
		}
		else
		{
			Empty_init();
			while (n--)
			{
				Push_back(val);
			}
		}
	}
	list(int n, const T& val = T())
	{
		if (n == 0)
		{
			Empty_init();
		}
		else
		{
			Empty_init();
			while (n--)
			{
				Push_back(val);
			}
		}
	}
	template <class InputIterator>
	list(InputIterator first, InputIterator last)
	{
		if (_root == nullptr)
		{
			Empty_init();
		}
		while (first != last)
		{
			Push_back(*first);
			first++;
		}
	}
	list(initializer_list<T> li)
	{
		Empty_init();
		for (auto& ch : li)
		{
			Push_back(ch);
		}
	}
	~list()
	{
		Node* cur = _root;
		while (cur!=_root)
		{
			Node* next = cur->_next;
			delete cur;
			cur = next;
		}
		_root = nullptr;
		count = 0;
	}
	list(const list<T>& l1)
	{
		Empty_init();
		for (auto ch : l1)
		{
			Push_back(ch);
		}
	}
	T& Front()
	{
		return (*begin());
	}
	T& Back()
	{
		return *(--end());
	}
	iterator Find(size_t val)
	{
		auto it = begin();
		while (it != end())
		{
			if (*it == val)
			{
				return it;
			}
			it++;
		}
		return nullptr;
	}
	void Push_back(const T& val)
	{
		/*Node* newnode = new Node(val);
		Node* ptail = _root->_prev;

		newnode->_next = _root;
		newnode->_prev = ptail;
		ptail->_next = newnode;
		_root->_prev = newnode;
		count++;*/
		Insert(end(), val);
	}
	void Push_front(const T& val)
	{
		/*Node* newnode = new Node(val);
		Node* pnext = _root->_next;

		newnode->_next = pnext;
		newnode->_prev = _root;
		pnext->_prev = newnode;
		_root->_next = newnode;
		count++;*/
		Insert(begin(), val);
	}
	void Insert(iterator pos, const T& val)
	{
		Node* cur = pos._node;
		Node* newprev = cur->_prev;
		Node* newnode = new Node(val);

		newnode->_next = cur;
		newnode->_prev = newprev;
		
		newprev->_next = newnode;
		cur->_prev = newnode;
		count++;
	}
	template<class Inputiterator>
	void Insert(iterator pos, Inputiterator first, Inputiterator last)
	{
		while (first != last)
		{
			T newnode = *first;
			Insert(pos, newnode);
			first++;
		}
	}
	void Pop_back()
	{
		/*Node* cur = _root->_prev;
		Node* Next = cur->_next;
		Node* Prev = cur->_prev;

		delete cur;
		Next->_prev = Prev;
		Prev->_next = Next;
		count--;*/
		Erase(end());
	}
	void Pop_front()
	{
		/*Node* cur = _root->_next;
		Node* Next = cur->_next;
		Node* Prev = cur->_prev;

		delete cur;
		Next->_prev = Prev;
		Prev->_next = Next;
		count--;*/
		Erase(begin());
	}
	void Erase(iterator pos)
	{
		Node* cur = pos._node;
		Node* Next = cur->_next;
		Node* Prev = cur->_prev;

		delete pos._node;
		Next->_prev = Prev;
		Prev->_next = Next;
		count--;
	}
	void Erase(iterator first, iterator last)
	{
		Node* cur = first._node;
		Node* final = last._node;
		while (cur!=final)
		{
			Node* curnext = cur->_next;
			Erase(cur);
			cur = curnext;
		}
	}
	void clear()
	{
		Node* cur = _root->_next;
		while (cur != _root)
		{
			Node* next = cur->_next;
			delete cur;
			cur = next;
		}
		count = 0;
	}
	void swap(list<T>& l1)
	{
		std::swap(_root, l1._root);
		std::swap(count, l1.count);
	}
	list<T>& operator=(list<T> l1)
	{
		swap(l1);
		return *this;
	}
	void resize(size_t n, const T& val=T())
	{
		if (count>n)
		{
			Node* Re = _root;
			while (n--)
			{
				Re = Re->_next;
			}
			Re->_next = _root;
            _root->_prev = Re;
			count = n;
		}
		else 
		{
			while (count < n)
			{
				Push_back(val);
			}
		}
	}
private:
	Node* _root=nullptr;
	size_t count=0;
};
template <class T>
void print_container(const T& node)
{
	for (auto ch : node)
	{
		cout << ch << " ";
	}
}
struct AA
{
	int a1 = 1;
	int a2 = 2;
};
//构造测试
void test()
{
	list<int> l1;
	print_container(l1);
	cout << endl;

	list<int> l2(5, 3);
	print_container(l2);
	cout << endl;

	vector<int> v1({1, 2, 3, 4, 5, 6, 7, 8});
	list<int> l3(v1.begin()+1, v1.end()-2);
	print_container(l3);
	cout << endl;

	list<int> l4({ 8,8,8,8,8,8,8 });
	print_container(l4);
	cout << endl;

	list<int> l5(l3);
	print_container(l5);
}
//插入测试
void test1()
{
	vector<int> v1({ 1, 2, 3, 4 });
	list<int> l1;
	for (auto ch : v1)
	{
		l1.Push_back(ch);
	}
	print_container(l1);
	cout << endl;

	list<int> l2;
	for (auto ch : v1)
	{
		l2.Push_front(ch);
	}
	print_container(l2);
	cout << endl;
	
	l1.Insert(l1.begin(), 8);
	print_container(l1);
	cout << endl;

	l1.Insert(++l1.begin(), v1.begin(), v1.end());
	print_container(l1);
	cout << endl;
}
//删除和修改测试
void test2()
{
	list<int> l1({1,2,3,4,5,6,7,8});
	l1.Erase(++l1.begin());
	print_container(l1);
	cout << endl;

	l1.Erase(++l1.begin(), --l1.end());
	print_container(l1);
	cout << endl;

	for (auto& ch : l1)
	{
		ch *= 10;
	}
	print_container(l1);
	cout << endl;

	list<int> l2({ 1,2,3,4,5,6,7,8 });
	auto it = l2.Find(4);
	*it = 9;
	print_container(l2);
	cout << endl;
}

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

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

相关文章

集合源码1

一、List接口分析 1、list接口的特点 ①List集合的所有元素是由一种线性方式进行存储的。 ②它是一个元素存储有序的集合。即元素的存入顺序和取出顺序有保证。 ③他是一个带有索引的集合&#xff0c;通过索引就可以精确的操作集合中的元素 ④集合中可以有重复的元素&#xff0…

二分查找算法——寻找旋转排序数组中的最小值点名

1.题目解析 题目来源&#xff1a;LCR173.点名——力扣 原名&#xff1a;剑指offer——0~n-1中消失的数字 测试用例 题目来源&#xff1a;153.寻找旋转排序数组中的最小值——力扣 测试用例 2.算法原理 点名 如果要寻找消失的数字&#xff0c;可以判断对应下标的数字是否和下标对…

视觉定位Revisit Anything

Revisit Anything: Visual Place Recognition via Image Segment Retrieval 项目地址 摘要&#xff1a; 准确识别重游地点对于嵌入代理的定位和导航至关重要。这要求视觉表现清晰&#xff0c;尽管摄像机视点和场景外观有很大变化。现有的视觉地点识别管道对“整个”图像进行编码…

制作离线版Koczkatamas工具包

一、下载源码 从https://github.com/koczkatamas/koczkatamas.github.io下载koczkatamas.github.io-master.zip 二、解压 $ unzip koczkatamas.github.io-master.zip三、运行index.html 可以看到输入一个字符后&#xff0c;下面的各种编码都没有显示&#xff0c;则表示运行…

【玩转 JS 函数式编程_008】3.1.2 JavaScript 函数式编程筑基之:箭头函数——一种更流行的写法

文章目录 3.1.2 箭头函数——更流行的方式 Arrow functions - the modern way1. 返回值 Returning values2. this 值的处理 Handling the this value3. arguments 的处理 Working with arguments4. 单参数还是多参数&#xff1f; One argument or many? 写在前面 故天将降大任…

儿童需要学习C++多久才能参加信息学奥赛的CSP-J比赛?

信息学奥赛&#xff08;NOI&#xff09;是国内编程竞赛领域的顶尖赛事&#xff0c;而对于初学者来说&#xff0c;参加NOI的第一步通常是通过CSP-J&#xff08;全国青少年信息学奥林匹克联赛初赛&#xff09;&#xff0c;这也是面向青少年程序员的入门级竞赛。作为信息学奥赛的基…

vue3使用three.js加载.obj模型示例

vue3使用three.js加载.obj模型示例 效果&#xff1a; 代码&#xff1a; 需要先安装three.js npm install three<template><div ref"threeContainer" class"three-container"></div> </template><script> import * as TH…

男单新老对决:林诗栋VS马龙,巅峰之战

听闻了那场激动人心的新老对决&#xff0c;不禁让人热血沸腾。在这场乒乓球的巅峰之战中&#xff0c;林诗栋与马龙的对决无疑是一场视觉与技术的盛宴。 3:3的决胜局&#xff0c;两位选手的每一次挥拍都充满了策略与智慧&#xff0c;他们的每一次得分都让人心跳加速。 林诗栋&am…

10.6学习

1.Hystrix / Sentinel ●服务雪崩场景 自己即是服务消费者&#xff0c;同时也是服务提供者&#xff0c;同步调用等待结果导致资源耗尽 ●解决方案 服务方&#xff1a;扩容、限流&#xff0c;排查代码问题&#xff0c;增加硬件监控 消费方&#xff1a;使用Hystrix资源隔离&a…

JavaSE——面向对象10:抽象类、接口

目录 一、抽象类 (一)抽象类的引出 (二)抽象类基本介绍 (三)注意事项和使用细节 (四)抽象类的最佳实践——模板设计模式 二、接口 (一)接口快速入门 (二)基本介绍 (三)注意事项与使用细节 (四)接口VS继承 (五)接口的多态性 1.多态参数 2.多态数组 3.接口存在多态…

CoreGen项目实战——代码提交信息生成

数据与相关代码见文末 1.概述 源代码与自然语言之间的语义鸿沟是生成高质量代码提交信息的一个重大挑战。代码提交信息对于开发者来说非常重要,因为它们简明扼要地描述了代码更改的高层次意图,帮助开发人员无需深入了解具体实现即可掌握软件的演变过程。手动编写高质量的提交…

Vite多环境配置与打包:

环境变量必须以VITE开头 1.VITE_BASE_API&#xff1a; 在开发环境中设置为 /dev-api&#xff0c;这是一个本地 mock 地址&#xff0c;通常用于模拟后端接口。 2.VITE_ENABLE_ERUDA&#xff1a; 设置为 "true"&#xff0c;表示启用调试工具&#xff0c;通常是为了…

Elasticsearch学习笔记(六)使用集群令牌将新加点加入集群

随着业务的增长&#xff0c;陆续会有新的节点需要加入集群。当我们在集群中的某个节点上使用命令生成令牌时会出现报错信息。 # 生成令牌 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node出现报错信息&#xff1a; Unable to create enrollment…

VMware WorkStation Pro 15.5(低版本安装) 教学用

VMware WorkStation Pro 15.5(低版本安装) 教学用 文章目录 VMware WorkStation Pro 15.5(低版本安装) 教学用前言安装使用 前言 VMware Workstation Pro 15.5 是一款功能强大的桌面虚拟化软件&#xff0c;适用于在单台物理电脑上运行多个操作系统。它被广泛应用于软件开发、测…

【文献阅读】Attention Bottlenecks for Multimodal Fusion

Abstract 在多模态视频分类中&#xff0c;将各模态的最终表示或预测进行后期融合&#xff08;“后期融合”&#xff09;仍然是主流范式。为此&#xff0c;本文提出了一种基于 Transformer 的新型架构&#xff0c;该架构使用“融合瓶颈”在多个层次进行模态融合。与传统的成对自…

科研必备语料库

1. Corpus of Contemporary American English 链接&#xff1a;https://www.english-corpora.org/coca/ 2. Purdue Online Writing Lab 链接&#xff1a;https://owl.purdue.edu/owl/ 3. Academic Phrases and Vocabulary 链接&#xff1a;https://www.ref-n-write.com/blog…

IntelliJ IDE 插件开发 | (十三)自定义项目脚手架(下)

系列文章 本系列文章已收录到专栏&#xff0c;交流群号&#xff1a;689220994&#xff0c;也可点击链接加入。 前言 在上一篇文章中介绍了如何在 IDEA 中自定义项目脚手架&#xff0c;本文将介绍如何在WebStorm、PyCharm、CLion等其它 IntelliJ 主流平台中如何自定义项目脚手…

【论文速看】DL最新进展20241006-视频深度估计、3D、自监督学习

目录 【视频深度估计】【3D】【自监督学习】 【视频深度估计】 [TPAMI 2024] NVDS: Towards Efficient and Versatile Neural Stabilizer for Video Depth Estimation 论文链接&#xff1a;https://arxiv.org/pdf/2307.08695 代码链接&#xff1a;https://github.com/RaymondW…

地理空间数据存储与处理:MySQL空间数据类型的优化与应用!

在 MySQL 数据库中&#xff0c;空间数据类型用于存储和处理地理空间数据。这些数据类型允许我们在开发时可在数据库中存储和操作地理位置、几何形状和地理空间关系等信息。 一、什么是空间数据类型 MySQL 中的空间数据类型主要包括以下几种&#xff1a; GEOMETRY&#xff1a…

【无人水面艇路径跟随控制3】(C++)USV代码阅读: ROS包的构建和管理:包的依赖关系、包含目录、库文件以及链接库

【无人水面艇路径跟随控制3】&#xff08;C&#xff09;USV代码阅读&#xff1a; ROS包的构建和管理&#xff1a;包的依赖关系、包含目录、库文件以及链接库 写在最前面ROS是什么CMakeLists.txt总结详细解释CMake最低版本和项目名称编译选项查找catkin包catkin包配置包含目录添…