放在专栏【C++知识总结】,会持续更新,期待支持
相对于vector来说,list对于空间的运用有着绝对的精准,不会存在一点的浪费,因为list是每插入或删除一个元素,就配置或释放一个元素空间。而vector则采用1.5或者2倍扩容机制,会存在一定程度上的空间浪费。两者各有优点。
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元素的访问。具体在后面模拟实现时会详细讲解。这里我们先了解其使用即可:
在使用前,需要包含头文件
list l;//构造一个存储元素为int类型的空list
list l(10, 5);//构造一个含有10个元素,元素类型为int,值为10的list
list l1(5, 1);//l1:1 1 1 1 1
//拷贝构造
list l2(l1); //l2:1 1 1 1 1
//也可以写成list l2=l1;
//迭代器区间构造(左闭右开)
string s("hello world!");
list l(s.begin(), s.begin() + 5);
//l:h e l l o
可以看到,整体使用实际上与string或vector并无太大区别,用起来很简单。
这里list只存在size,表示list中有效节点个数,而empty就不用说了,用来判断容器是否为空。
与vector相同,resize可以实现对有效元素的扩容或“缩容”,当resize(n,val)时,如果n的值>当前size值,则list会进行扩容到size为n,扩容出来的元素值用val来充当,当n的值 迭代器是个很奇妙的设计,所有的容器,通过迭代器都可以进行访问容器中的元素,list也不例外,用起来也很简单,就像vector中一样用即可。 这里,begin与end分别指向list中第一个有效元素,以及最后一个有效元素的下一个位置,而我们的begin与end区间,通常都是左闭右开,对于环状的链表来说,只需要在尾端加入一个空白节点,便符合STL规范之“前闭后开”。(实际就是一个双向带头循环链表)如下所示: 当然,list中也存在const迭代器,以及反向迭代器,分别对应cbegin与cend(const正向迭代器)、rbegin与rend(反向迭代器)、crbegin与crend(const反向迭代器),const类型的迭代器的特点就是不能对指向的元素做修改。不过需要注意的是,rbegin与end对应,rend与begin对应。 这里我们在后续模拟实现时也会讲到。 我们知道迭代器失效指的就是迭代器指向了一块不属于它的空间,或者指向的空间已经被销毁了。这里list由于不像vector那样,vector的插入操作可能会引起扩容,从而导致迭代器失效,而list则不会,因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。 如下所示为迭代器失效场景: 解决方法也很简单,更新it即可。如下所示: front用于获取list中的第一个元素,back用于获取list中最后一个元素。如下所示: list相比于vector的优点之一就在于,vector只有在尾部插入删除才是效率最高(O(1)),而list在任意位置插入删除,都可以实现O(1),很高效。接下来将讲解list的插入删除相关接口 这俩函数前者实现尾插,后者实现尾删操作,用起来也很简单,如下所示: push_front实现在头部插入元素,pop_front则为在头部删除一个元素,如下: insert与erase实现在任意位置插入与删除。 在c++98中,提供了insert的三种插入方式,分别为:在pos位置插入一个元素val;在pos位置插入n个元素,每个元素为val;在pos位置插入一段迭代器区间构成的元素(左闭右开)。 erase也提供了两种删除方式:删除pos位置的元素;删除区间[first,last)的元素 list中还提供了一些其它接口,不过有些并不常用,这里只挑选几个进行讲解,可自行翻阅文档查看。《点击跳转》 reverse函数用于实现链表的逆置,如下: 很熟悉了,用于两个链表实现数据交换。 remove(val),用来移除链表中元素为val的节点: sort用来实现对链表进行排序。默认为升序排序,如下: 该函数用来删除相邻的重复元素,我们可以利用sort+unique实现对链表排序+去重。如下所示: end. 生活原本沉闷,但跑起来就会有风!3.3、元素访问
3.3.1迭代器
3.3.2、迭代器失效问题
3.3.3、front与back
3.4、插入删除操作
3.4.1、push_back与pop_back
3.4.2、push_front与pop_front
3.4.3、insert与erase
3.5、其它操作
3.5.1、reverse
3.5.2、swap
3.5.3、remove
3.5.4、sort
3.5.5、unique