list 容器,又称双向链表容器,即该容器的底层是以双向链表的形式实现的。这意味着,list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。
可以看到,list 容器中各个元素的前后顺序是靠指针来维系的,每个元素都配备了 2 个指针,分别指向它的前一个元素和后一个元素。其中第一个元素的前向指针总为 null,因为它前面没有元素;同样,尾部元素的后向指针也总为 null。
基于这样的存储结构,list 容器具有一些其它容器(array、vector 和 deque)所不具备的优势,即它可以在序列已知的任何位置快速插入或删除元素(时间复杂度为O(1))。并且在 list 容器中移动元素,也比其它容器的效率高。
使用 list 容器的缺点是,它不能像 array 和 vector 那样,通过位置直接访问元素。即必须要遍历到要访问的位置(正确的做法是从容器中第一个元素或最后一个元素开始遍历容器,直到找到该位置),迭代器也不能是随机访问迭代器。
实际场景中,如果需要对序列进行大量添加或删除元素的操作,而直接访问元素的需求却很少,这种情况建议使用 list 容器存储序列。
list容器的创建
list<int> l1;//创建一个空的list容器
list<int> l2(10);//创建一个包含n个元素的list容器
list<int> l3(10,5);//创建一个包含10个5的list容器
//在已有list容器的情况下
list<int> l4(l2);//通过拷贝l2容器创建新的l4容器
array<int,5>arr{11,12,13,14,15};
list<int> l5(arr.begin()+2,arr.end());//拷贝arr容器中的{13,14,15}
list容器可用的成员函数
C++ list(STL list)容器完全攻略(超级详细) (biancheng.net)
注意,list 容器的底层实现结构为双向链表,图 上 这种表示仅是为了方便理解各个迭代器函数的功能。
array、vector、deque 容器的迭代器,和它们相比,list 容器迭代器最大的不同在于,其配备的迭代器类型为双向迭代器,而不再是随机访问迭代器。
这意味着,假设 p1 和 p2 都是双向迭代器,则它们支持使用 ++p1、 p1++、 p1--、 p1++、 *p1、 p1==p2 以及 p1!=p2 运算符,但不支持以下操作(其中 i 为整数):
p1[i]:不能通过下标访问 list 容器中指定位置处的元素。
p1-=i、 p1+=i、 p1+i 、p1-i:双向迭代器 p1 不支持使用 -=、+=、+、- 运算符。
p1<p2、 p1>p2、 p1<=p2、 p1>=p2:双向迭代器 p1、p2 不支持使用 <、 >、 <=、 >= 比较运算符。
遍历 list 容器中的各个元素。
#include <iostream>
#include <list>
using namespace std;
int main()
{
//创建 list 容器
std::list<char> values{'h','t','t','p',':','/','/','c','.','b','i','a','n','c','h','e','n','g','.','n','e','t'};
//使用begin()/end()迭代器函数对输出list容器中的元素
for (std::list<char>::iterator it = values.begin(); it != values.end(); ++it) {
std::cout << *it;
}
cout << endl;
//使用 rbegin()/rend()迭代器函数输出 lsit 容器中的元素
for (std::list<char>::reverse_iterator it = values.rbegin(); it != values.rend();++it) {
std::cout << *it;
}
return 0;
}
输出结果为:
http://c.biancheng.net
ten.gnehcnaib.c//:ptth
值得一提的是,list 容器在进行插入(insert())、接合(splice())等操作时,都不会造成原有的 list 迭代器失效,甚至进行删除操作,而只有指向被删除元素的迭代器失效,其他迭代器不受任何影响。
list容器访问元素的方法
不同于之前学过的 STL 容器,访问 list 容器中存储元素的方式很有限,即要么使用 front() 和 back() 成员函数,要么使用 list 容器迭代器。
list 容器不支持随机访问,未提供下标操作符 [] 和 at() 成员函数,也没有提供 data() 成员函数。
1.通过 front() 和 back() 成员函数,可以分别获得 list 容器中第一个元素和最后一个元素的引用形式。举个例子:
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> mylist{ 1,2,3,4 };
int &first = mylist.front();
int &last = mylist.back();
cout << first << " " << last << endl;
first = 10;
last = 20;
cout << mylist.front() << " " << mylist.back() << endl;
return 0;
}
输出结果为:
1 4
10 20
2.除此之外,如果想访问 list 容存储的其他元素,就只能使用 list 容器的迭代器。例如:
#include <iostream>
#include <list>
using namespace std;
int main()
{
const std::list<int> mylist{1,2,3,4,5};
auto it = mylist.begin();
cout << *it << " ";
++it;
while (it!=mylist.end())
{
cout << *it << " ";
++it;
}
return 0;
}
运行结果为:
1 2 3 4 5
总结
访问/修改 list的头尾元素可以使用front()或back()成员函数,访问其他元素则必须要通过迭代器的遍历来实现。
list添加(插入)元素方法
list 模板类中,与“添加或插入新元素”相关的成员方法有如下几个:
push_front():向 list 容器首个元素前添加新元素;
push_back():向 list 容器最后一个元素后添加新元素;
emplace_front():在容器首个元素前直接生成新的元素;
emplace_back():在容器最后一个元素后直接生成新的元素;
emplace():在容器的指定位置直接生成新的元素;
insert():在指定位置插入新元素;
splice():将其他 list 容器存储的多个元素添加到当前 list 容器的指定位置处。
除了 insert() 和 splice() 方法有多种语法格式外,其它成员方法都仅有 1 种语法格式
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> values{1,2,3};
values.push_front(0);//{0,1,2,3}
values.push_back(4); //{0,1,2,3,4}
values.emplace_front(-1);//{-1,0,1,2,3,4}
values.emplace_back(5); //{-1,0,1,2,3,4,5}
//emplace(pos,value),其中 pos 表示指明位置的迭代器,value为要插入的元素值
values.emplace(values.end(), 6);//{-1,0,1,2,3,4,5,6}
for (auto p = values.begin(); p != values.end(); ++p) {
cout << *p << " ";
}
return 0;
}
insert成员方法
iterator insert(pos,elem)在迭代器 pos 指定的位置之前插入一个新元素 elem,并返回表示新插入元素位置的迭代器。
iterator insert(pos,n,elem)在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,first,last) 在迭代器 pos 指定的位置之前,插入其他容器(例如 array、vector、deque 等)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,initlist)在迭代器 pos 指定的位置之前,插入初始化列表(用大括号 { } 括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。
#include <iostream>
#include <list>
#include <array>
using namespace std;
int main()
{
std::list<int> values{ 1,2 };
//第一种格式用法
values.insert(values.begin() , 3);//{3,1,2}
//第二种格式用法
values.insert(values.end(), 2, 5);//{3,1,2,5,5}
//第三种格式用法
std::array<int, 3>test{ 7,8,9 };
values.insert(values.end(), test.begin(), test.end());//{3,1,2,5,5,7,8,9}
//第四种格式用法
values.insert(values.end(), { 10,11 });//{3,1,2,5,5,7,8,9,10,11}
for (auto p = values.begin(); p != values.end(); ++p)
{
cout << *p << " ";
}
return 0;
}
splice()成员方法
void splice (iterator position, list& x); | position 为迭代器,用于指明插入位置;x 为另一个 list 容器。 此格式的 splice() 方法的功能是,将 x 容器中存储的所有元素全部移动当前 list 容器中 position 指明的位置处。 |
void splice (iterator position, list& x, iterator i); | position 为迭代器,用于指明插入位置;x 为另一个 list 容器;i 也是一个迭代器,用于指向 x 容器中某个元素。 此格式的 splice() 方法的功能是将 x 容器中 i 指向的元素移动到当前容器中 position 指明的位置处。 |
void splice (iterator position, list& x, iterator first, iterator last); | position 为迭代器,用于指明插入位置;x 为另一个 list 容器;first 和 last 都是迭代器,[fist,last) 用于指定 x 容器中的某个区域。 此格式的 splice() 方法的功能是将 x 容器 [first, last) 范围内所有的元素移动到当前容器 position 指明的位置处。 |
#include <iostream>
#include <list>
#include <array>
using namespace std;
int main()
{
std::list<int> values{ 1,2 };
//第一种格式用法
values.insert(values.begin() , 3);//{3,1,2}
//第二种格式用法
values.insert(values.end(), 2, 5);//{3,1,2,5,5}
//第三种格式用法
std::array<int, 3>test{ 7,8,9 };
values.insert(values.end(), test.begin(), test.end());//{3,1,2,5,5,7,8,9}
//第四种格式用法
values.insert(values.end(), { 10,11 });//{3,1,2,5,5,7,8,9,10,11}
for (auto p = values.begin(); p != values.end(); ++p)
{
cout << *p << " ";
}
return 0;
}
list删除元素
实现 list 容器删除元素的成员函数
pop_front()删除位于 list 容器头部的一个元素。
pop_back()删除位于 list 容器尾部的一个元素。
erase()该成员函数既可以删除 list 容器中指定位置处的元素,也可以删除容器中某个区域内的多个元素。
clear()删除 list 容器存储的所有元素。
remove(val)删除容器中所有等于 val 的元素。
unique()删除容器中相邻的重复元素,只保留一份。
remove_if()删除容器中满足条件的元素。
pop_front()、pop_back() 和 clear() 的用法非常简单,这里仅给出一个样例,
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int>values{ 1,2,3,4 };
//删除当前容器中首个元素
values.pop_front();//{2,3,4}
//删除当前容器最后一个元素
values.pop_back();//{2,3}
//清空容器,删除容器中所有的元素
values.clear(); //{}
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
运行程序,可以看到输出结果为“空”。
erase()
erase() 成员函数有以下 2 种语法格式:
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
1.第一种(可实现删除 list 容器中 position 迭代器所指位置处的元素
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int>values{ 1,2,3,4,5 };
//指向元素 1 的迭代器
auto del = values.begin();
//迭代器右移,改为指向元素 2
++del;
values.erase(del); //{1,3,4,5}
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
2.第二种(可实现删除 list 容器中 first 迭代器和 last 迭代器限定区域内的所有元素
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int>values{ 1,2,3,4,5 };
//指定删除区域的左边界
auto first = values.begin();
++first;//指向元素 2
//指向删除区域的右边界
auto last = values.end();
--last;//指向元素 5
//删除 2、3 和 4
values.erase(first, last);
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
remove()函数:按值删除相邻重复元素
erase() 成员函数是按照被删除元素所在的位置来执行删除操作,如果想根据元素的值来执行删除操作,可以使用 remove() 成员函数
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<char>values{'a','b','c','d'};
values.remove('c');
for (auto begin = values.begin(); begin != values.end(); ++begin)
{
cout << *begin << " ";
}
return 0;
}
unique() 函数:可以自定义去重规则
也有以下 2 种语法格式:
void unique()
void unique(BinaryPredicate)//传入一个二元谓词函数
#include <iostream>
#include <list>
using namespace std;
//二元谓词函数
bool demo(double first, double second)
{
return (int(first) == int(second));
}
int main()
{
list<double> mylist{ 1,1.2,1.2,3,4,4.5,4.6 };
//删除相邻重复的元素,仅保留一份
mylist.unique();//{1, 1.2, 3, 4, 4.5, 4.6}
for (auto it = mylist.begin(); it != mylist.end(); ++it)
cout << *it << ' ';
cout << endl;
//demo 为二元谓词函数,是我们自定义的去重规则
mylist.unique(demo);
for (auto it = mylist.begin(); it != mylist.end(); ++it)
std::cout << *it << ' ';
return 0;
}
可以看到,通过调用无参的 unique(),仅能删除相邻重复(也就是相等)的元素,而通过我们自定义去重的规则,可以更好的满足在不同场景下去重的需求。
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> mylist{ 15, 36, 7, 17, 20, 39, 4, 1 };
//删除 mylist 容器中能够使 lamba 表达式成立的所有元素。
mylist.remove_if([](int value) {return (value < 10); }); //{15 36 17 20 39}
for (auto it = mylist.begin(); it != mylist.end(); ++it)
std::cout << ' ' << *it;
return 0;
}
除此之外,通过将自定义的谓词函数(不限定参数个数)传给 remove_if() 成员函数,list 容器中能使谓词函数成立的元素都会被删除。
#include <iostream>
#include <list>
using namespace std;
int main()
{
std::list<int> mylist{ 15, 36, 7, 17, 20, 39, 4, 1 };
//删除 mylist 容器中能够使 lamba 表达式成立的所有元素。
mylist.remove_if([](int value) {return (value < 10); }); //{15 36 17 20 39}
for (auto it = mylist.begin(); it != mylist.end(); ++it)
std::cout << ' ' << *it;
return 0;
}
forward_list容器:高效率的list容器!
C++ STL forward_list容器完全攻略 (biancheng.net)
区别:forward_list底层实现使用的是单链表
效率高是选用 forward_list 而弃用 list 容器最主要的原因,换句话说,只要是 list 容器和 forward_list 容器都能实现的操作,应优先选择 forward_list 容器。