🌟🌟作者主页:ephemerals__
🌟🌟所属专栏:C++、STL
目录
前言
之前我们已经学习了string、vector两个容器的使用方法及模拟实现,今天跟大家介绍list的使用方法。
到了这个阶段,我们应该认识到:在STL中,尽管容器各异,但同名接口的功能往往是相似的。因此,在我们掌握了少数几个容器的使用方法后,对于未曾接触过的其他容器,只要了解其底层数据结构,就基本能够上手使用它们。
list简介
list是STL中的一种容器,用于表示链表结构,底层实现是一个双向带头循环链表。如果你对双向带头循环链表不太了解,可以参阅这篇文章:
list在插入和删除操作方面非常高效,但在遍历和随机访问方面可能不如数组或者vector高效。因此,在选择容器时,需要根据具体的应用场景和需求进行权衡。
我们在使用list时,需要引头文件<list>,并且该容器定义在命名空间std当中。
list相关接口查阅:
一、list的默认成员函数
list显示实现的默认成员函数有三个:分别是构造函数、析构函数和赋值重载。
构造函数(constructor)
c++11下,list共有六个构造函数,其中较为常用的有如下五种:
函数原型 | 功能说明 |
list(); | 无参构造(忽略空间配置器),创建一个空链表 |
list(size_type n, const value_type& val); | 用n个val值构造一个list对象 |
list(const list& x); | 拷贝构造,用一个对象构造另一个对象 |
list(InputIterator first,InputIterator last); | 迭代器区间构造 |
list(initializer_list<value_type> il); | 初始化器构造(大括号赋值) |
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1;//无参构造
list<int> l2(5, 3);//n个val值构造
list<int> l3(l2);//拷贝构造
list<int> l4(l2.begin(), l2.end());//迭代器区间构造
list<int> l5({ 1,2,3,4,5 });//初始化器构造
cout << "l1: ";
print(l1);
cout << "l2: ";
print(l2);
cout << "l3: ";
print(l3);
cout << "l4: ";
print(l4);
cout << "l5: ";
print(l5);
return 0;
}
析构函数
赋值重载
将新内容分配给已经存在的容器,替换其当前内容,并相应地修改其大小。较为常用的赋值重载有两个:
函数原型 | 功能说明 |
list& operator= (const list& x); | 两个list容器之间的赋值 |
list& operator= (initializer_list<value_type> il); | 用初始化器给容器赋值 |
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1;
list<int> l2({ 1,2,3,4,5 });
l1 = l2;//将l2赋值给l1
print(l1);
l1 = { 5,4,3,2,1 };//初始化器赋值
print(l1);
return 0;
}
二、list的迭代器接口
迭代器接口在string和vector中的使用方法大致相同,这里就不多介绍。
我们在这里重点讲一下迭代器的功能分类:
迭代器的功能分类
根据功能强弱,迭代器可以分为以下三种:
这三种迭代器的功能是向下兼容的,即随机迭代器具有双向迭代器的功能,而双向迭代器具有单向迭代器的功能。
为什么会有这样的分类呢?其实这是由底层数据结构的实现导致的。
有些数据结构元素的内存地址连续,还能够支持双向遍历,并且遍历效率高,那么就可以支持随机迭代器(例如string、vector);有些数据结构能够支持双向遍历,但是随机访问的效率低下,那就支持双向迭代器(例如list);有些数据结构只能做到从前向后访问元素,那么就只能支持单向迭代器(例如单链表forward_list)。
三、list的容量接口
三个容量接口当中,前两个比较常用,我们重点介绍一下:
empty
size
代码示例:
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> l1;
list<int> l2({ 1,2,3,4,5 });
cout << "l1.size(): " << l1.size() << endl;
cout << l1.empty() << endl;
cout << "l2.size(): " << l2.size() << endl;
cout << l2.empty() << endl;
return 0;
}
四、list的元素访问接口
front和back
代码示例:
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> l = { 1,2,3,4,5 };
cout << l.front() << endl;
cout << l.back() << endl;
return 0;
}
五、list的增删查改
在涉及增删查改操作的接口中,鉴于部分接口功能有所重复,博主仅挑选几个进行介绍。
push_front
pop_front
push_back
pop_back
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1 = { 1,2,3,4,5 };
cout << "原链表:";
print(l1);
l1.push_back(0);
cout << "尾插:";
print(l1);
l1.push_front(0);
cout << "头插:";
print(l1);
l1.pop_back();
cout << "尾删:";
print(l1);
l1.pop_front();
cout << "头删:";
print(l1);
return 0;
}
insert和erase
代码举例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l = { 1,2,3,4,5 };
l.insert(++l.begin(), 0);//在第二个位置插入一个元素
print(l);
l.erase(----l.end());//删除倒数第二个元素
print(l);
return 0;
}
swap
swap的功能是交换两个list容器的内容。 当然,也有一个非成员函数的swap支持list的交换:
resize
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1;
list<int> l2;
l1.resize(10);
l2.resize(10, 5);
print(l1);
print(l2);
list<int> l3({ 1,2,3,4,5 });
print(l3);
l3.resize(3);
print(l3);
return 0;
}
clear
find
六、list的其他操作接口
除了传统的成员函数外,list还提供了一些特有的与插入删除相关的操作接口供我们使用。通过学习这些接口的使用方法,我们可以初步了解仿函数的相关知识。
splice
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1;
list<int> l2({ 1,2,3,4,5 });
l1.splice(l1.end(), l2);//将l2剪切到l1的末尾
cout << "l1:";
print(l1);
cout << "l2:";
print(l2);
cout << endl;
l2 = { 1,2,3,4,5 };
l1.splice(l1.end(), l2, l2.begin());//将l2的首元素剪切到l1的末尾
cout << "l1:";
print(l1);
cout << "l2:";
print(l2);
cout << endl;
l2 = { 1,2,3,4,5 };
l1.splice(l1.end(), l2, ++l2.begin(), --l2.end());//将l2掐头去尾的部分剪切到l1的末尾
cout << "l1:";
print(l1);
cout << "l2:";
print(l2);
cout << endl;
}
remove
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l = { 1,2,3,2,4,2,2,5 };
print(l);
l.remove(2);//删除所有的2
print(l);
return 0;
}
remove_if
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
//仿函数
class f
{
public:
bool operator()(int value)
{
return value < 10;//将小于10的元素确定为true
}
};
int main()
{
list<int> l = { 20,1,5,9,15,17 };
l.remove_if(f());//删除所有小于10的元素
print(l);
return 0;
}
unique
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1 = { 1,1,2,3,3,4,4,4,5,5,6,7,8,8 };//有序序列
l1.unique();
print(l1);
list<int> l2 = { 2,2,1,2,2 };//无序序列,无法直接删除所有重复元素
l2.unique();
print(l2);
return 0;
}
对于一个无序list序列,如果想要删除所有的重复元素,那么就需要先对list进行排序,然后再调用unique函数。
merge
代码示例:
#include <iostream>
#include <list>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l1 = { 1,3,5,7,9 };
list<int> l2 = { 2,4,6,8,10 };
l1.merge(l2);//合并
cout << "l1:";
print(l1);
cout << "l2:";
print(l2);
return 0;
}
sort
为什么要实现一个成员函数版的sort呢?直接使用算法库中的通用sort不行吗?刚才博主已经提到,list支持的是双向迭代器,并不具备随机迭代器的功能,所以list无法使用通用的sort函数完成排序,会发生报错。所以说list的成员函数当中,实现了一个排序函数sort。
接下来我们尝试使用该函数:
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l = { 5,1,7,2,4,8,0,3 };
l.sort();//排序
print(l);
return 0;
}
reverse
代码示例:
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;
void print(list<int>& l)
{
for (auto& e : l)
{
cout << e << ' ';
}
cout << endl;
}
int main()
{
list<int> l = { 1,2,3,4,5 };
l.reverse();//反转
print(l);
return 0;
}
总结
今天我们学习了STL容器--list的使用方法。当我们需要频繁进行插入和删除操作时,可以考虑使用该容器。之后博主会和大家一起模拟实现list,并且借此来深入学习迭代器的底层实现。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤