一、List容器的介绍
1.list是可以在常数范围内任意位置进行插入和删除的序列式容器,并且该容器可以实现前后双向迭代。
2.list的底层是双向链表结构,双向链表中每个元素储存在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后面一个元素。
3.list和forward_list非常相似,不同点是forward_list是单链表,只能正向迭代,。
4.与其他序列式容器相比(array,vector,deque)list通常在任意位置进行插入,以及移除元素的执行效率更好。
5.与其他序列式容器相比,list和farward_list最大的缺陷,是不支持任意位置的随机访问,比如:要访问list的第5个元素我们只能通过迭代到这个位置才能对这个位置的元素进行访问。
在这段位置的迭代需要线性的时间开销,而且list还需要一些额外的空间,用来保存每个节点的相关信息。
其次string和vector的迭代器都是随机迭代器是可以通过一个指针进行模拟实现的。
list的迭代器是双向迭代器支持++,--,操作但是不支持+和-的操作,因为在lIst中加和减的效率极低。list中实现了专门的排序函数。
注意:使用unique函数可以对链表进行去重操作,但是去重之前要先进行排序操作,不排序的话去重可能不彻底。
二、List容器的常见接口的使用
2.1 List的构造
构造函数 | 接口说明 |
---|---|
list(size_t type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list() | 构造一个空链表 |
list(const list& x) | 拷贝构造函数 |
list(Inputiterator first, Inputiterator last) | 通过迭代器区间中的元素来构造list |
下面我们来看下关于list的构造的使用:
void test1()
{list<int> l1;//构造空的listlist<int> l2(6, 6); // 构造6个值为6的节点list<int> l3(l2); // 拷贝构造函数list<int> l4(l3.begin(), l3.end());//使用迭代器区间构造cout << l1.size() << endl;for (auto e : l2){cout << e << " ";}cout << endl;for (auto e : l3){cout << e << " ";}cout << endl;for (auto e : l4){cout << e << " ";}cout << endl;}
2.2 list的iterator的使用
函数声明 | 接口说明 |
---|---|
begin和end | begin返回第一个元素的迭代器,end返回最后一个元素的下一个位置的迭代器 |
rbegin和rend | rbegin返回最后一个元素的后一个位置,rend返回第一个元素的位置 |
注意:
begin和end是正向迭代器,对迭代器进行++操作,迭代器向后移动。
rbegin和rend是反向迭代器,对迭代器执行++操作,迭代器向前移动。
下面我们来看看迭代器的使用实例:
void test2()
{list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(5);l1.push_back(6);list<int>::iterator it = l1.begin();list<int> l2(l1);list<int>::reverse_iterator rit = l2.rbegin();//正向迭代器while (it != l1.end()){cout << *it << " ";it++;}cout << endl;//反向迭代器while (rit != l2.rend()){cout << *rit << " ";rit++;}cout << endl;
}
2.3 list capacity
函数声明 | 接口说明 |
---|---|
empty | 判断list是否为空,如果list为空就返回ture,否则返回false |
size | 返回list中有效节点的个数 |
我们看下这两个函数的使用:
void test3()
{list<int> l1;list<int> l2;l2.push_back(1);l2.push_back(1);l2.push_back(1);l2.push_back(1);l2.push_back(1);cout << l1.empty() << " " << l1.size() << endl;cout << l2.empty() << " " << l2.size() << endl;
}
2.4 list的访问以及修改操作
函数名 | 接口说明 |
---|---|
front | 返回list的第一个节点储存的值 |
back | 返回list最后一个节点储存的值 |
push_front | 在list首元素的位置插入一个值为val的元素 |
pop_front | 删除list中的第一个元素 |
push_back | 在list尾部插入值为val的元素 |
pop_back | 删除list的最后一个元素 |
insert | 按pos位置插入一个值为val的元素 |
erase | 删除pos位置的元素 |
swap | 交换两个list对象 |
clear | 清空list中的有效元素 |
下面我们来看下这些函数的使用方法:
void test4()
{list<int> l1;list<int> l2;l1.push_front(1);l1.push_front(2);l1.push_back(3);l1.push_back(4);for (auto e : l1){cout << e << " ";}cout << endl;l1.pop_back();l1.pop_front();for (auto e : l1){cout << e << " ";}cout << endl;cout << l1.front() << " " << l1.back() << endl;l1.insert(l1.begin(), 2);l1.insert(l1.end(), 4);for (auto e : l1){cout << e << " ";}cout << endl;l1.erase(l1.begin());l1.erase(--l1.end());for (auto e : l1){cout << e << " ";}cout << endl;l2.swap(l1);for (auto e : l1){cout << e << " ";}cout << endl;for (auto e : l2){cout << e << " ";}cout << endl;l2.clear();cout << l2.size() << endl;}
三、list的迭代器失效问题
迭代器失效的原因是迭代器所指向的节点无效了,导致迭代器失效,即该节点被删除了,因为list的底层是双向带头循环链表,因此在对list进行插入操作时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的迭代器只是指向被删除的那个节点的迭代器,其他迭代器是不受影响的。
下面我们来看下这段程序:
void test_iterator()
{list<int> l1(20, 6);auto it = l1.begin();for (auto e : l1){cout << e << " ";}cout << endl;while (it != l1.end()){//erase函数执行后,it指向的节点已经被删除了,因此it失效,在下一次使用it时必须先给it赋值l1.erase(it++);}cout << l1.empty() << " " << l1.size() << endl;
}
我们在erase时可以使用it++,但是如果erase之后再++程序就会报错,因为这时it已经失效了,也可是使用it = l1.erase(it);来进行更新,因为erase函数返回的是it的下一个迭代器位置。
四、list和vector的对比
vector和list都是stl库中重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同:
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续的内存空间 | 带头节点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素的效率是O(1) | 不支持随机访问,访问某个元素的时间复杂度是O(N) |
插入和删除 | 任意位置插入和删除的效率低,需要挪动元素,时间食杂度为O(n),插入可能需要扩容,扩容:开辟新空间,拷贝元素到新空间,然后释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要挪动元素,时间复杂度为O(1) |
空间利用率 | 底层是连续空间,不容易造成内存碎片,空间利用率高,缓存命中率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存命中率低 |
迭代器 | 原生指针 | 对原生指针进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来的迭代器失效,删除时,当前迭代器需要重新赋值否则会失效报错 | 插入元素不会导致迭代器失效,删除时只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
关于list容器的介绍到这里就结束了,如果需要博客内的代码可以在下面链接中查看或者下载:
list的常用接口使用示例代码