继vector之后,我们继续来介绍STL容器:list
对于容器的使用其实有着类似的模式,参考之前vector的使用可以让我们更快的上手:
戳我看vector介绍与使用详解哦
在之前C语言部分我们就认识了链表,STL中的list是一个双向链表
相对于vector的底层空间是连续的,list的底层空间是通过指针链接的链状的不连续的空间;
这样的结构相较连续空间扩容更加方便:不需要重开空间移动数据,只需要在开辟一个新的结点后,将其与前面的结点链接起来即可。其在任意位置插入删除都不需要挪动数据,效率较高:只需要释放或增加对应结点的数据,然后将剩下的结点链接起来即可;
相较于vector,list不能实现高效的任意访问其中的元素,要随机访问元素只能从头或尾遍历访问,效率较低。所以list中也就直接没有实现operator[]
所以,list适用于需要经常在任意位置插入删除大量数据,且不需要经常访问任意位置元素的数据的存储
list是一个类模板,可以支持存储任意类型:
与vector类似,list也有默认成员函数、迭代器、容量、元素访问、数据修改等接口(使用库list时需要包含头文件#include
:
构造
list的构造函数重载了4个版本,支持无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造。迭代器区间构造是一个函数模板,即可以使用任一InputIterator
迭代器区间来构造list。
使用时与vector类似,由于list是一个类模板,所以在使用list来实例化对象是,就需要显式指定模板参数,如list
:
#include
#include
#include
using namespace std;
int main()
{
list<int> l1; //无参初始化
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
list<int> l2(10, 6); //使用10个6初始化l2
for (auto e : l2)
{
cout << e << " ";
}
cout << endl;
vector<int> v1(10, 5); //使用10个5初始化v1
list<int> l3(v1.begin(), v1.end()); //使用迭代器区间初始化
for (auto e : l3)
{
cout << e << " ";
}
cout << endl;
list<int> l4(l3); //拷贝构造
for (auto e : l4)
{
cout << e << " ";
}
cout << endl;
}
析构
析构函数会在list对象生命周期结束时由编译器自己调用,以释放其内部资源。
int main()
{
list<int> l1;
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
list<int> l2(10, 6);
for (auto e : l2)
{
cout << e << " ";
}
cout << endl;
l1 = l2; //将l2赋值给l1
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
}
vector的迭代器的底层就是原生指针,与vector不同,list的迭代器是指针的封装(在list实现时会详细介绍),但是在使用list迭代器时是与vector几乎一致的。
但是需要注意的是,list的迭代器由于不是原生指针,在+
、-
数字时的成本太高,所以就不支持迭代器+
、-
,只允许迭代器进行++
、--
的双向移动,即为bidirectional iterator
。
begin
返回首元素位置的迭代器,end
返回尾元素下一个位置的迭代器、rbegin
返回尾元素的反向迭代器、rend
返回首元素前一个位置的反向迭代器。后面的cbegin
、cend
、crbegin
、crend
都是返回其对应的const迭代器,但是前面的函数都有重载const版本:
int main()
{
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);
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
cout << *l1.begin() << endl;
cout << *(--l1.end()) << endl;
cout << *l1.rbegin() << endl;
cout << *(--l1.rend()) << endl;
return 0;
}
对于list,没有容量的概念,也就不需要有扩容的操作。
所以在容量部分,list只提供了size
与empty
,用于返回list中元素个数与判断list是否为空:
int main()
{
vector<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);
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
cout << "size:" << l1.size() << " ";
cout << "is empty:" << l1.empty() << " ";
return 0;
}
list不是连续的空间,想要通过下标访问任意位置的元素时成本较高,所以list没有重载operator[]
,而只能通过迭代器访问元素;
同时,list提供了front
与back
接口来实现对首尾元素的访问:
int main()
{
vector<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);
for (auto e : l1)
{
cout << e << " ";
}
cout << endl;
cout << l1.front() << endl;
cout << l1.back() << endl;
return 0;
}
push_front
、push_back
用于在头、尾插入一个元素;
pop_front
、pop_back
用于在头、尾删除一个元素;
int main()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_front(4);
l.push_front(5);
l.push_front(6);
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
l.pop_back();
l.pop_front();
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
return 0;
}
insert
用于在pos
位置(迭代器位置)插入一些数据,包括单个元素、多个指定元素以及一个迭代器区间中的元素:
int main()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.push_back(5);
l.push_back(6);
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
l.insert(l.begin(), 10); //相当于头插一个10
l.insert(l.begin(), 3, 20); //相当于头插3个20
list<int> l2(3, 30); //使用3个30构造l2
l.insert(l.end(), l2.begin(), l2.end()); //相当于尾插一个l2
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
return 0;
}
erase
用于删除pos
位置的一个元素或一个迭代器区间中的元素:
int main()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.push_back(5);
l.push_back(6);
l.erase(++l.begin(), --l.end());
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
l.erase(l.begin()); //相当于头删
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
return 0;
}
resize
用于修改list中的容量:
当n
小于list的元素个数时,就删除至n个元素;
当n
大于list的元素个数时,就用指定元素value
补足:
int main()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.push_back(5);
l.push_back(6);
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
l.resize(3); //删至3个元素
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
l.resize(10, 6); //补到10个元素,不足的用6补
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
return 0;
}
swap
用于交换两个list中的数据;
clear
用于清理list中的数据,这里就不再演示了。
同时,list还提供了一些算法接口,如reverse
、sort
等。
对于reverse
算法库中的接口,是通过循环左右交换的方式来实现的:
对于list这样,通过逻辑连续的线性结构,我们可以使用更为简单的改变链接顺序的方法来实现反转,list的算法中就是这样实现的(戳我看反转链表OJ 思路与该算法类似)
对于算法库中sort
的接口,使用的是快排的方式来排序的,快排算法是需要三数取中来优化的,即他需要能够+
、-
的RandomAccessIterator
迭代器作为参数:
而list的迭代器却不支持+
、-
的操作,所以不能使用算法库中的sort
函数,所以list提供了这个接口。
但是list中的sort
函数效率远没有快排的效率高,所以在进行大量list数据的排序时,可以将其数据转移到vector中排序后再转移回来;也可以干脆不适用list来存储需要大量排序的数据。
到此,关于list的简介与接口使用就介绍完了
接下来会进行模拟实现,欢迎持续关注哦
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦