【STL】list的使用

 放在专栏【C++知识总结】,会持续更新,期待支持


1、list简介

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

相对于vector来说,list对于空间的运用有着绝对的精准,不会存在一点的浪费,因为list是每插入或删除一个元素,就配置或释放一个元素空间。而vector则采用1.5或者2倍扩容机制,会存在一定程度上的空间浪费。两者各有优点。

2、list的数据结构

list本身与list节点,这两个是完全不同的结构,是需要分开来设计的,对于一个list节点来说,由于list是双向环状链表(双向带头循环链表),所以需要提供两个指针,一个指向前一个元素,一个指向另一个元素,同时还需要一个val,用来存储数据。如下所示为SGI版本的list底层(稍作修改,便于学习):


//list节点
template
struct _list_node
{
	_list_node* _prev;//指向前一个元素
	_list_node* _next;//指向后一个元素
	T _data;             //用来存储数据
};
//list
template
class list
{
	typedef _list_node node;
public:
	//.....
private:
	node* _head;//只需要一个指针,便可以表示整个双向循环链表
};

需要注意到的是,list由于存储空间并不是连续的,因此这里的迭代器并不像string与vector那样,是一个原生指针,这里list的迭代器是用一个对象,来模拟指针的行为,从而实现对list元素的访问。具体在后面模拟实现时会详细讲解。这里我们先了解其使用即可:

3、list的使用

在使用前,需要包含头文件

3.1、构造相关

3.1.1、构造一个空容器

	list l;//构造一个存储元素为int类型的空list

3.1.2、构造含有n个val的某类型容器

	list l(10, 5);//构造一个含有10个元素,元素类型为int,值为10的list

3.1.3、拷贝构造

	list l1(5, 1);//l1:1 1 1 1 1
	//拷贝构造
	list l2(l1);  //l2:1 1 1 1 1
	//也可以写成list l2=l1;

3.1.4、迭代器区间构造

	//迭代器区间构造(左闭右开)
	string s("hello world!");
	list l(s.begin(), s.begin() + 5);
	//l:h e l l o
	

可以看到,整体使用实际上与string或vector并无太大区别,用起来很简单。

3.2、容量相关

3.2.1、size与empty

这里list只存在size,表示list中有效节点个数,而empty就不用说了,用来判断容器是否为空。

【STL】list的使用_第1张图片

3.2.2、resize

与vector相同,resize可以实现对有效元素的扩容或“缩容”,当resize(n,val)时,如果n的值>当前size值,则list会进行扩容到size为n,扩容出来的元素值用val来充当,当n的值

【STL】list的使用_第2张图片        

3.3、元素访问

3.3.1迭代器

迭代器是个很奇妙的设计,所有的容器,通过迭代器都可以进行访问容器中的元素,list也不例外,用起来也很简单,就像vector中一样用即可。

【STL】list的使用_第3张图片

 这里,begin与end分别指向list中第一个有效元素,以及最后一个有效元素的下一个位置,而我们的begin与end区间,通常都是左闭右开,对于环状的链表来说,只需要在尾端加入一个空白节点,便符合STL规范之“前闭后开”。(实际就是一个双向带头循环链表)如下所示:

【STL】list的使用_第4张图片

 当然,list中也存在const迭代器,以及反向迭代器,分别对应cbegin与cend(const正向迭代器)、rbegin与rend(反向迭代器)、crbegin与crend(const反向迭代器),const类型的迭代器的特点就是不能对指向的元素做修改。不过需要注意的是,rbegin与end对应,rend与begin对应。【STL】list的使用_第5张图片

 这里我们在后续模拟实现时也会讲到。

【STL】list的使用_第6张图片 list有关迭代器的相关接口

 3.3.2、迭代器失效问题

我们知道迭代器失效指的就是迭代器指向了一块不属于它的空间,或者指向的空间已经被销毁了。这里list由于不像vector那样,vector的插入操作可能会引起扩容,从而导致迭代器失效,而list则不会,因为list的底层结构为带头结点的双向循环链表,因此list中进行插入时是不会导致list的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

如下所示为迭代器失效场景:

【STL】list的使用_第7张图片

【STL】list的使用_第8张图片

 解决方法也很简单,更新it即可。如下所示:

【STL】list的使用_第9张图片

 3.3.3、front与back

front用于获取list中的第一个元素,back用于获取list中最后一个元素。如下所示:

【STL】list的使用_第10张图片

 3.4、插入删除操作

list相比于vector的优点之一就在于,vector只有在尾部插入删除才是效率最高(O(1)),而list在任意位置插入删除,都可以实现O(1),很高效。接下来将讲解list的插入删除相关接口

3.4.1、push_back与pop_back

这俩函数前者实现尾插,后者实现尾删操作,用起来也很简单,如下所示:

【STL】list的使用_第11张图片

 3.4.2、push_front与pop_front

push_front实现在头部插入元素,pop_front则为在头部删除一个元素,如下:

【STL】list的使用_第12张图片

 3.4.3、insert与erase

insert与erase实现在任意位置插入与删除。

【STL】list的使用_第13张图片

在c++98中,提供了insert的三种插入方式,分别为:在pos位置插入一个元素val;在pos位置插入n个元素,每个元素为val;在pos位置插入一段迭代器区间构成的元素(左闭右开)。

【STL】list的使用_第14张图片

 erase也提供了两种删除方式:删除pos位置的元素;删除区间[first,last)的元素

【STL】list的使用_第15张图片

 3.5、其它操作

list中还提供了一些其它接口,不过有些并不常用,这里只挑选几个进行讲解,可自行翻阅文档查看。《点击跳转》

3.5.1、reverse

reverse函数用于实现链表的逆置,如下:

【STL】list的使用_第16张图片

3.5.2、swap

很熟悉了,用于两个链表实现数据交换。

【STL】list的使用_第17张图片

3.5.3、remove

remove(val),用来移除链表中元素为val的节点:

【STL】list的使用_第18张图片

3.5.4、sort

sort用来实现对链表进行排序。默认为升序排序,如下:

【STL】list的使用_第19张图片

3.5.5、unique

该函数用来删除相邻的重复元素,我们可以利用sort+unique实现对链表排序+去重。如下所示:

【STL】list的使用_第20张图片


end.

生活原本沉闷,但跑起来就会有风!

你可能感兴趣的:(C++知识总结,c++,开发语言,STL,list)