文章目录
一. list简介
list是可以在常数范围内任意位置进行插入和删除的序列容器,并且该容器支持前后双向迭代。这是基于list的底层数据结构是带头双向循环链表,因此list容器是不支持随即访问的(也就是不能像数组那样使用 [i]下标来访问元素),list访问元素的时间复杂度是线性时间。
二. list基本使用方法
2.1 list的常见构造函数
list的构造使用代码演示:
#include<list>
using namespace std;
void Test01()
{
list<int> l1;//构造空的list
list<int> l2(5);//l2元素个数为5,都为默认初始值
list<int> l3(5,100);//l3中是5个值为100的元素
list<int> l4(l3);//用l3拷贝构造l4
//以数组为迭代器区间构造l5
int arr[] = {1,2,3,4,5};
len = sizeof(arr) / sizeof(arr[0]);
list<int> l5(arr,arr+len);
//c++11 的列表格式初始化
list<char> l6{'h','e','l','l','o','w'};
}
2.2 list的iterator
[注意]
begin() + end() 是正向迭代器,对迭代器进行++操作,迭代器往后移动
rbegin() + rend() 是反向迭代器,对迭代器进行++操作,迭代器往前移动
iterator使用的代码演示:
#include<list>
using namespace std;
void Test02()
{
list<int> l1{1,2,3,4,5};
list<int>::iterator it = l1.begin();//返回第一个元素的迭代器
list<int>::iterator end = l1.end();//返回最后一个元素的下一个的迭代器
while(it != end)
{
cout << *it << " ";
(*it) *= 2;
++it;
}
cout << endl;
list<int>::reverse_iterator rit = l1.rbegin();//返回最后一个元素的迭代器
list<int>::reverse_iterator rend = l1.rend();//返回第一个元素的前一个元素迭代器
while(rit != rend)
{
cout << *rit <<" " ;
++rit;
}
}
vs2019测试结果如下:
2.3 list的容量相关
一点思考:
为什么list没有像vector提供capacity和reserve接口??这还是基于list的底层数据结构(带头结点的双向循环链表),它不像vector一样是一段连续的空间,所以没有capacity(能容纳元素的最大个数)的说法;也无需为list容器预留空间,因为不存在因往容器加入元素而导致的空间扩容问题,往list加入元素时,需要申请一个节点,然后将该节点连入链表中即可,不会影响其他节点的物理地址。这与vector是不同的。
代码演示使用:
#include<list>
using namespace std;
void Test03()
{
list<int> l1{1,2,3,4,5};
cout << l1.size() << endl;//5
l1.clear();//清空list
cout << l1.empty() << endl;//true
}
2.4 list的加入和删除元素接口
接口使用演示:
#include<list>
#include<string>
using namespace std;
void Test03()
{
list<string> l1{"i"," ","love"," ","c++"};
cout << l1.front() <<endl;
cout << l1.back() <<endl;
//尾插尾删 头插头删
list<char> l2;
l2.push_back('s');// -->s
l2.push_back('m');//s --> sm
l2.push_front('h');//sm--> hsm
list<char>::iterator it = l2.begin();
while (it != l2.end())
{
cout << *it++;
}
cout << endl;
l2.pop_front();//hsm --> sm
l2.pop_back();//sm --> s
//任意位置插入与删除insert and erase
list<int> l3{ 3,4,5,7 };
auto pos = find(l3.begin(), l3.end(), 5); //查找5,返回迭代器
l3.insert(pos, 3); // 3, 4, 5, 7 ---> 3, 4, [3], 5, 7
l3.insert(pos, 4); // 3, 4, 3, 5, 7---> 3, 4, 3,[4], 5, 7
l3.erase(pos); //3, 4, 3, 4, 5, 7---> 3, 4, 3, 4, 7
//l3.erase(pos);//报错,因为迭代器已失效
l3.erase(l3.begin());//3,4,3,4,7--->4,3,4,7
}
迭代器失效问题:
在使用erase()删除pos位置的元素后,pos迭代器已失效,不能再继续使用。如果需继续使用,要给pos重新赋值。
pos = l.erase(pos);//返回删除元素的下一个元素的迭代器
l.erase(pos);
在pos位置插入元素不会使pos失效,插入的元素不会改变原来的其他节点的物理地址。
l.insert(pos,3);
l.insert(pos,4);
l.insert(pos,5);
三、list与vector的对比
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头节点双向循环链表 |
随机访问 | 支持随机访问 | 不支持随机访问,访问元素复杂度为O(n) |
插入和删除 | 任意位置插入和删除元素复杂度高,需要挪动大量元素,甚至是重新申请新的空间,复杂度为O(n) | 任意位置插入和删除都在O(1)时间完成 |
空间利用率 | 空间利用率高,底层为连续空间,不会产生碎片化空间,缓存利用率高 | 空间利用率低,底层节点动态开辟,容易产生碎片化空间,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,可能会导致空间扩容,所以插入元素迭代器会失效; 在删除元素时,只是将后面的元素往前挪动,理论上迭代器是不会失效的,但是如果恰好删除的是尾元素,迭代器就指向了end(),也就失效了,所以为了避免这种巧合,在删除元素迭代器也会失效哦! | 在插入元素时,迭代器不会失效;删除元素会导致当前迭代器失效,其他迭代器不受影响 |
使用常景 | 需要随机访问、需要高效存储、不关心插入删除的效率 | 无需随机访问、插入删除操作频繁 |