✨✨小新课堂开课了,欢迎欢迎~✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:C++:由浅入深篇
小新的主页:编程版小新-CSDN博客
前言:
前面我们已经了解了string,vector的用法以及他们的底层实现,今天我们再来学习了一个新的容器list,这几个容器之间有很大的相通性,我们来一起看一下吧。
一.什么是list ?
std::list是C++模板库(STL)中的一个双向链表容器。它提供了一种灵活的方式来储存和操作一组元素。
和vector,string一样,list也是一个模板类,使用时只需指定元素类型,编译器会确保类型安全。它定义在头文件(include<list>)中,可以使用list来存储和访问任意类型的对象。
二.list的特性
1**.双向链表** ------每个节点包含指向前一个和后一个的指针,因此它可以在进行高效的插入删除操作。
2.动态大小 ------根据需要动态增加和减少元素个数,没有空间浪费。
3.非连续储存 ------元素在内存中是不连续的,不支持下标的随机访问。
4.迭代器支持------支持STL迭代器,可以使用标准的算法进行遍历和操作。在进行插入和删除操作时,除了被指向被删除元素的迭代器失效,其他迭代器通常不失效,这点与vector也不同。vector在进行插入操作时迭代器不一定失效,在进行删除操作时被删除元素及其后面元素的迭代器都失效。
三.list的文档介绍
list - C++ Reference(英文版)
https://zh.cppreference.com/w/cpp/container/list(中文版)
四.list的使用
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理。在实际中我们熟悉常见的接口就可以,下面列出了哪些接口是要重点掌握的 。我们在后面模拟实现的时候,也是实现这些常见的接口。
4.1list的构造
|----------------------------------------------------------|-----------------------------|
| 构造函数( (constructor)) | 接口说明 |
| list (size_type n, const value_type& val =value_type()) | 构造的list中包含n个值为val的元素 |
| list() | 构造空的list |
| list (const list& x) | 拷贝构造函数 |
| list (InputIterator first, InputIterator last) | 用[first, last)区间中的元素构造list |
1.list() :构造一个空的list
cpp
list<int> st1;//构造一个空list
2.list (size_type n, const value_type& val =value_type()) :构造一个list,元素个数为n,元素为val
cpp
list<int> st2(10, 1);//用n个值为val的元素构造一个list
3.list (const list& x) :拷贝构造
cpp
list<int> st3(st2);//拷贝构造
4.list (InputIterator first, InputIterator last) : 使用迭代器区间进行初始化构造
cpp
list<int> st4(st2.begin(), st2.end());//使用一个迭代器区间构造list
补充:构造数组某段区间的复制品
cpp
int myints[] = { 16,2,77,29 };
list<int> st5(myints, myints + sizeof(myints) / sizeof(int));//构造数组某段区间的复制品
4.2list的迭代器
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
|-----------------|---------------------------------------------|
| 函数声明 | 接口说明 |
| begin +end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 。 |
| rbegin+rend | 返回指向列表最后一个元素的反向迭代器+返回指向列表第一个元素之前的反向迭代器。 |
begin+end
cpp
//正向迭代器遍历
void list1()
{
list<int> st(10, 1);
list<int>::iterator it = st.begin();
while (it != st.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
rbegin+rend
cpp
//反向迭代器遍历
void list2()
{
list<int> st(10, 1);
list<int>::reverse_iterator rit = st.rbegin();
while (rit != st.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
}
注意:
begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
4.3list的容量
|-------|------------------------------|
| 函数声明 | 接口说明 |
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |
size和empty
cpp
void list3()
{
list<int>st(10, 1);
cout << st.size() << endl;//list的有效节点个数
cout << st.empty() << endl;//不为空返回0,为空返回非0
}
4.4list的访问
|-------|--------------------|
| 函数声明 | 接口说明 |
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |
front和back
cpp
void list4()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_back(5);
cout << st.front() << endl;//返回第一个节点中值的引用 1
cout << st.back() << endl;//返回最后一个节点值的引用 5
}
4.5list的增删查改
push_front和pop_front
push_front用于头插一个元素,pop_front用于头删一个元素
cpp
void list5()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_front(5);//在首元素前插入
cout << "插入前:";
for (auto ch : st)
{
cout << ch << " ";//5 1 2 3 4
}
cout << endl;
st.pop_front();//删除首元素
cout << "插入后:";
for (auto ch : st)
{
cout << ch << " ";// 1 2 3 4
}
}
push_back和pop_back
push_back用于尾插一个元素,pop_back用于尾删一个元素。
cpp
void list6()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_back(5);//在尾部插入
for (auto ch : st)
{
cout << ch << " ";// 1 2 3 4 5
}
cout << endl;
st.pop_back();//删除尾部元素
cout << "插入后:";
for (auto ch : st)
{
cout << ch << " ";// 1 2 3 4
}
}
insert
list当中的insert函数支持三种插入方式:
- 在指定迭代器位置插入一个数。
- 在指定迭代器位置插入n个值为val的数。
- 在指定迭代器位置插入一段迭代器区间(左闭右开)。
cpp
void list7()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
//在指定迭代器位置插入一个数
st.insert(st.begin(), 0); //在容器开头插入0
//在指定迭代器位置插入n个val
st.insert(st.begin(), 5, -1); //在容器开头插入5个-1
//在指定迭代器位置插入一段迭代器区间
int myarray[] = { 501,502,503 };
st.insert(st.begin(), myarray, myarray + 3);//在容器开头插入 501 502 503
for (auto ch : st)
{
cout << ch << " ";// 501 502 503 -1 -1 -1 -1 -1 0 1 2 3 4
}
}
erase
list当中的erase函数支持两种删除方式:
- 删除指定迭代器位置的元素。
- 删除指定迭代器区间(左闭右开)的所有元素。
cpp
void list8()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_back(5);
st.push_back(6);
//删除指定位置迭代器的元素
st.erase(st.begin()); //删除容器中的第一个元素
//删除指定迭代器区间(左闭右开)的所有元素。
st.erase(st.begin(), st.end()); //删除在该迭代器区间内的元素(左闭右开)
for (auto ch : st)
{
cout << ch << " ";
}
}
resize
resize的两种情况:
- 当所给值大于当前的size时,将size扩大到该值,扩大的数据为第二个所给值,若未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
- 当所给值小于当前的size时,将size缩小到该值。
cpp
void list9()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_back(5);
st.push_back(6);
st.resize(10, 9);//将size扩大到10,以9来填充
for (auto ch : st)
{
cout << ch << " ";//1 2 3 4 5 6 9 9 9 9
}
cout << endl;
st.resize(4);//将size缩小到4
for (auto ch : st)
{
cout << ch << " "; //1 2 3 4
}
cout << endl;
}
assign
list中的assign函数用于将新内容分配给容器,替换其当前内容,新内容的赋予方式有两种:
- 将n个值为val的数据分配给容器。
- 将所给迭代器区间当中的内容分配给容器。
cpp
void list10()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_back(5);
st.push_back(6);
for (auto ch : st)
{
cout << ch << " ";//1 2 3 4 5 6
}
cout << endl;
//将新内容分配给容器,替换当前的内容
st.assign(10, 1);//用n个val替换
for (auto ch : st)
{
cout << ch << " "; //1 1 1 1 1 1 1 1 1 1
}
cout << endl;
//将新内容分配给容器,替换当前的内容
int myints[] = { 1776,7,4 };
st.assign(myints, myints + 3);//迭代器区间所指的内容进行替换
for (auto ch : st)
{
cout << ch << " ";//1776 7 4
}
cout << endl;
}
swap
交换两个list的元素。
cpp
void list11()
{
list<int> st1;
st1.push_back(1);
st1.push_back(2);
st1.push_back(3);
st1.push_back(4);
st1.push_back(5);
st1.push_back(6);
cout << "st1交换前:";
for (auto ch : st1)
{
cout << ch << " ";//1 2 3 4 5 6
}
cout << endl;
list<int> st2;
st2.push_back(1);
st2.push_back(2);
st2.push_back(3);
st2.push_back(4);
cout << "st2交换前:";
for (auto ch : st2)
{
cout << ch << " ";//1 2 3 4
}
cout << endl;
st1.swap(st2);
cout << "st1交换后:";
for (auto ch : st1)
{
cout << ch << " ";//1 2 3 4
}
cout << endl;
cout << "st2交换前:";
for (auto ch : st2)
{
cout << ch << " ";//1 2 3 4 5 6
}
cout << endl;
}
clear
清空容器的内容。
cpp
void list12()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
for (auto ch : st)
{
cout << ch << " ";//1 2 3 4
}
cout << endl;
cout << st.size() << endl;//4
st.clear();//清空容器的内容
cout << st.size() << endl;//0
}
4.6list的操作函数
sort
给容器中的内容排序,默认是排升序。
cpp
void list01()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(9);
st.push_back(3);
st.push_back(5);
st.push_back(4);
st.push_back(10);
st.push_back(6);
st.sort();//默认排升序
for (auto ch : st)
{
cout << ch << " ";//1 2 3 4 5 6 9 10
}
cout << endl;
}
splice
用于两个list的拼接,其有三种拼接方式:
- 将整个容器拼接到另一个容器的指定迭代器位置。
- 将容器当中的某一个数据拼接到另一个容器的指定迭代器位置。
- 将容器指定迭代器区间的数据拼接到另一个容器的指定迭代器位置。
cpp
void list02()
{
list<int> st1(4, 1);
list<int> st2(5, 2);
st1.splice(st1.begin(), st2); //将容器st2拼接到容器st1的开头
for (auto ch : st1)
{
cout << ch << " "; //2 2 2 2 2 1 1 1 1
}
cout << endl;
list<int> st3(4, 1);
list<int> st4(5, 2);
st3.splice(st3.begin(), st4, st4.begin()); //将容器st4的第一个数据拼接到容器st4的开头
for (auto ch : st3)
{
cout << ch << " ";//2 1 1 1 1
}
cout << endl;
list<int> st5(4, 1);
list<int> st6(5,2);
st5.splice(st5.begin(), st6, st6.begin(), st6.end()); //将容器st6的指定迭代器区间内的数据拼接到容器st6的开头
for (auto ch : st5)
{
cout << ch << " ";//2 2 2 2 2 1 1 1 1
}
cout << endl;
}
remove
删除容器中特定元素。
cpp
void list03()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.remove(3);//删除指定元素
for (auto ch : st)
{
cout << ch << " ";//1 2 4
}
cout << endl;
}
remove_if
删除容器中满足条件元素。
cpp
bool single_digit(const int& val)
{
return val < 10;
}
void list04()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(3);
st.push_back(4);
st.push_back(10);
st.push_back(20);
st.push_back(30);
st.push_back(40);
st.remove_if(single_digit);//删除满足条件的元素
for (auto ch : st)
{
cout << ch << " ";//10 20 30 40
}
cout << endl;
}
unique
删除容器中连续重复元素。
cpp
void list05()
{
list<int> st;
st.push_back(1);
st.push_back(2);
st.push_back(2);
st.push_back(4);
st.push_back(10);
st.push_back(10);
st.push_back(10);
st.push_back(40);//有序
//如果list无序,可以使用sort使其有序
st.unique();//删除连续重复元素
for (auto ch : st)
{
cout << ch << " ";//1 2 4 10 40
}
cout << endl;
}
merge
合并两个有序容器,合并后依然有序。
cpp
void list06()
{
list<int> st1;
st1.push_back(8);
st1.push_back(3);
st1.push_back(1);
st1.push_back(6);
list<int> st2;
st2.push_back(0);
st2.push_back(10);
st2.push_back(2);
st2.push_back(5);
st1.sort();
st2.sort();//先满足是两个有序链表
//合并两个有序链表
st1.merge(st2);
//合并后依然有序
for (auto ch : st1)
{
cout << ch << " ";//0 1 2 3 5 6 8 10
}
cout << endl;
}
reverse
将容器中的元素位置进行逆置。
cpp
void list07()
{
list<int> st1;
st1.push_back(8);
st1.push_back(3);
st1.push_back(1);
st1.push_back(6);
cout << "逆置前:";
for (auto ch : st1)
{
cout << ch << " ";//8 3 1 6
}
cout << endl;
st1.reverse();
cout << "逆置后:";
for (auto ch : st1)
{
cout << ch << " ";//6 1 3 8
}
cout << endl;
}
五.list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。vector的迭代器失效理解了,这里就没什么可以说的了。
cpp
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
l.erase(it);
++it;
}
}
运行结果:
改正:万能钥匙:在使用前,对迭代器重新赋值即可。
cpp
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
l.erase(it++); // it = l.erase(it);
}
}
六.list 和vector的对比
|-------------|---------------------------------|--------------------------------------|---------------|-------------------------------------------------------------------------|-----------------------------------|---------------|-------------------------------------------------------------------------|-----------------------------------------------|
| | vector | list |
| | vector | list | 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1)**** | 不支持随机访问,访问某个元素O(N) | 底 层 结 构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随 机 访 问 | 支持随机访问,访问某个元素效率O(1)**** | 不支持随机访问,访问某个元素O(N) | 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 插 入 和 删 除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间导致效率更低 | 任意位置插入和删除效率高, 不需要搬移元素,时间复杂度 为O(1) | 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 使 用 场 景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心 随机访问 | 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容 易造成内存碎片,空间利用率 低,缓存利用率低 | 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行 封装 | 迭 代 器 失 效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行 封装 |
总结:
list的常见接口及其用法,我们已经介绍完了,准备好一起来模拟实现list了嘛,我们下篇见。
感谢各位大佬的观看,创作不易,还请各位大佬支持~