list
本节目标
- list的介绍及使用
- list的深度剖析及模拟实现
- list与vector的对比
list的介绍及使用
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
其前一个元素和后一个元素。 - list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
效。 - 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
更好。 - 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这
可能是一个重要的因素) - list他其实也是一种序列式容器,序列式容器其实就相当于是数据结构中的线性数据结构,那么为什么已经给出了vector还要给出list呢?因为vector底层的空间是一顿连续的空间,你在连续的空间内部进行插入和删除的操作的时候效率是非常底下的,不是很适合插入的操作,list他其实就是一种链式的结构,list是通过双向链表来实现的
list的使用
- list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口。
- list底层的结构是双向链表,再细化一下就是带头结点的双向循环链表,为什么要带有头节点,因为带有头节点其实实现起来会比较方便一下,为什么循环,因为如果是循环链表的话,那么我么处理最后一个元素的时候会相对来说简单一些
和list相关的操作
- 迭代器
- 因为需要保证左闭右开的区间,所以end的位置只能再head的位置上,begin的位置在链表中第一个元素的位置上,而且将end的位置放在head的位置上还有一个优点就是,当end–的时候我们就可以快速的取到链表中的最后一个元素,比较方便一些
- 下图表示的是正向迭代器,反向迭代器的位置和正向迭代器的位置相反,rend的位置在链表的第一个结点的位置,rbegin的位置在head的位置上
- 容量相关
- 修改的操作
- 还有一些其他的特殊操作,比如说—unique/sort/merge/reverse
list的构造
- list() 构造空的list
- list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素
- list (const list& x) 拷贝构造函数
- list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList1()
{
//因为list中的元素是不支持随机访问的,所以我们不能使用下标的方式去访问了
//我们只能使用迭代器的方式对list中的元素进行打印的操作了
list<int> l1;
list<int> l2(10, 5); //给l2里面放置10个值为5的元素
list<int>::iterator it = l2.begin();
while (it != l2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
vector<int> v{ 1,2,3,4,5 };
list<int> l3(v.begin(), v.end());
//上面既然已经将list的迭代器给出了,那么我们就可以使用范围for的方式来进行
//打印的操作,这样子的话,会方便一些
for (auto e : l3)
{
cout << e << " ";
}
cout << endl;
int array[] = { 1,2,3,4,5 };
list<int> l33(array, array + sizeof(array) / sizeof(array[0]));
for (auto e : l33)
{
cout << e << " ";
}
cout << endl;
list<int> l4(l3);
for (auto e : l4)
{
cout << e << " ";
}
cout << endl;
list<int> l5{ 1,2,3,4,5,6,7,8,9,0 };
for (auto e : l5)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestList1();
return 0;
}
插入删除相关的操作
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList2()
{
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);
cout << L.size() << endl;
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
L.pop_back();
L.pop_back();
cout << L.size() << endl;
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
L.push_front(0);
cout << L.size() << endl;
cout << L.front() << endl; //看一下第一个元素是什么
cout << L.back() << endl; //看一下最后一个元素是什么
L.pop_front();
cout << L.size() << endl;
cout << L.front() << endl; //看一下第一个元素是什么
cout << L.back() << endl; //看一下最后一个元素是什么
}
int main()
{
TestList2();
return 0;
}
asign的操作
- assign其实就是进行重新赋值的操作,我可以去使用一段空间进行赋值,也可以使用n个值为data的元素对其进行赋值的操作,assign和构造的区别就是构造还没有对象呢,你需要先去把对象构造出来,而assign是已经有对象了,是可以直接进行赋值操作的
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList3()
{
list<int> L{ 1,2,3,4,5,6 };
L.assign(10, 5);
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
int array[] = { 6,7,8 };
L.assign(array, array + sizeof(array) / sizeof(array[0]));
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
//使用C++11提供的使用列表的方式来进行初始化的操作
L.assign({ 1,2,3,4,5,6,7,8,9,0 });
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestList3();
return 0;
}
Insert方法
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList4()
{
list<int> L{ 1,2,3,4,5,6 };
L.insert(L.begin(), 0);
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
//如果说要在某一个元素的位置处去插入一个元素的话,首先你需要先找到这个元素
//所在的位置之后,然后才能去进行插入的操作
L.insert(find(L.begin(), L.end(), 3), 10, 5);
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
//C++11中新增了列表方式的插入
L.insert(L.begin(), { 10,10,10,10 });
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
vector<int> v{ 7,8,9,0 };
L.insert(L.end(), v.begin(), v.end());
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestList4();
return 0;
}
erase方法
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList5()
{
list<int> L{ 1,2,3,4,5,6 };
L.erase(L.begin());
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
//删除4以及4之后的元素
L.erase(find(L.begin(), L.end(), 4), L.end());
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
L.erase(L.begin(), L.end());
}
int main()
{
TestList5();
return 0;
}
特殊的操作
- 在某些情况下我们可能需要按照指定的值来进行元素的删除,那么在这种情况下,我们就需要去用到remove的方法
- 还有remove_if,这个是按照指定的条件来进行删除的操作,就比如说我希望将list中所有偶数全部删掉,所有为3的倍数的元素删掉登这样的情况的时候,就需要用到remove_if的方法
- unique操作是进行去重的操作,但是这个去重的操作有一个点就是你不需要保证所给的序列是有序的,你才可以去进行去重的操作,否则是不可以去进行去重的操作的
- merge是将两个序列,合并成一个序列,合并两个有序的list
#include<iostream>
#include<list>
#include<vector>
using namespace std;
bool IsMod3(int data)
{
return 0 == data % 3;
}
void TestList6()
{
list<int> L1{ 1, 1, 3, 3, 6, 6, 2, 2, 5, 5, 4, 4, 0, 0, 9, 9, 7, 7, 8, 8 };
L1.unique();
for (auto e : L1)
{
cout << e << " ";
}
cout << endl;
//L1的结果就是成功去重了, 因为重复性的元素连在一起
list<int> L2{ 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0 };
L2.unique();
for (auto e : L2)
{
cout << e << " ";
}
cout << endl;
//L2的结果是去重失败,因为重复性的元素并没有连在一起
//那如果我们想对没有连在一起的有重复元素的序列去重怎么办呢,我们可以先通过
//排序的方式,使得其变为一个有序的序列,然后再对其进行去重的操作
L2.sort();
L2.unique();
for (auto e : L2)
{
cout << e << " ";
}
cout << endl;
list<int> L3{ 4,5,6 };
//在使用merge的时候需要注意的一点就是
//在合并的期间,必须要保证两个链表是有序的才是可以进行合并的
L2.merge(L3);
for (auto e : L2)
{
cout << e << " ";
}
cout << endl;
//删除所有指定值的元素、
L2.remove(4);
for (auto e : L2)
{
cout << e << " ";
}
cout << endl;
//按照特定的条件进行删除
//这里的条件我们可以通过三种方式去给出
//1. 函数指针 2.仿函数 3. lambda表达式给出
//那么现在我将所有是3的倍数的元素给他删掉
//根据用户所提供的参数来进行检测的操作
L2.remove_if(IsMod3);
for (auto e : L2)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestList6();
return 0;
}
list迭代器失效的问题
- 那么,怎么解决迭代器失效的问题,就是给迭代器进行重新赋值的操作
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList7()
{
list<int> L{ 1,2,3,4,5,6 };
auto it = L.begin();
L.push_back(7);
L.push_back(8);
L.push_back(9);
L.push_back(0);
//list再插入的时候不会导致迭代器的失效
//那么什么情况下会导致迭代器的失效呢
while (it != L.end())
{
cout << *it << " ";
++it;
}
cout << endl;
L.erase(L.begin());
//将链表的第一个结点删除掉
//在删除之前it迭代器指向的就是第一个结点
//那么在该结点删除掉之后,it迭代器指向的就是一个已经被释放的结点
//那么该迭代器就已经失效了
//如果在使用该迭代器之前,没有给迭代器重新赋值的话,那么代码就会崩溃
it = L.begin();
//也可以修改成it=L.erase(L.begin()); 这样子代码也是不会发生崩溃的操作的
while (it != L.end())
{
cout << *it << " ";
++it;
}
}
int main()
{
TestList7();
return 0;
}
#include<iostream>
#include<list>
#include<vector>
using namespace std;
void TestList8()
{
list<int> L{ 1,2,3,4,5,6,7,8,9,0 };
auto it = L.begin();
//那么假如说,我现在希望去删除掉所有的偶数
while (it != L.end())
{
if (*it % 2 == 0)
{
it=L.erase(it);
}
else
++it;
}
for (auto e : L)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
TestList8();
return 0;
}
list的模拟实现
#pragma once
namespace bite //放在这个命名空间的下面,就不会和标准库中的list发生冲突了
{
//因为list在底层用的是结点,所以我们需要先给出结点的类型
template<class T>
struct ListNode //ListNode是节点的类型,我们每次在使用的时候都需要在
//后面加上一个尖括号,表示的是对这个模板的实例化操作
{
ListNode<T>* next;
ListNode<T>* prev;
T val;
ListNode(const T& value)
:next(nullptr)
,prev(nullptr)
,val(value)
{
}
};
//然后我们在去给出list的结构
template<class T>
class list
{
//那么,在这个位置只需要有一个结点的指针就可以了
typedef ListNode<T> Node; //进行重命名的操作
typedef T* iterator; //进行重命名的操作
public:
list()
{
CreateHead();
}
list(int n, const T& value = T())
{
CreateHead();
for (int i = 0; i < n; i++)
{
push_back(value);
}
}
//为什么要在前面加上一个模板呢?
//因为我们list方法里面他是没有迭代器的现在
template<class Iterator>
list(Iterator first, Iterator last)
{
CreateHead();
while (first != last)
{
push_back(*first);
}
}
list(const list<T>& L)
{
CreateHead();
auto it = L.begin();
while (it != L.end())
{
push_back(*it);
}
}
list<int>& operator=(const list<T>& L);
~list()
{
//析构函数
//首先,删除有效结点
//其次,删除头结点
erase(begin(), end());
delete head;
head = nullptr;
}
//迭代器的操作
iterator begin();
iterator end();
/
//容量相关的操作
size_t size()const
{
//需要将链表进行遍历一遍或者说给出一个变量用来记录元素的个数
//两种方法都是可以的
size_t count = 0;
auto it = begin();
while (it != v.end()) ++it, ++count;
return count;
}
//当list为空的时候,也就是说list里面一个元素都没有
//但是就它里面一个元素都没有,带头结点的链表里面也是存在有一个头节点的
bool empty(const)
{
//如果头节点的next还是他自己的话,就说明这个链表是一个空的链表
return head->next == head;
}
void resize(size_t newsize, const T& data = T())
{
size_t oldsize = size();
if (newsize < oldsize)
{
for (size_t i = newsize; i < oldsize; i++)
{
popback();
}
}
else
{
for (size_t i = oldsize; i < newsize; i++)
{
push_back(data);
}
}
}
/
//元素访问的操作
//访问链表中第一个元素
T& front()
{
return head->next->val;
}
const T& front()const
{
return head->next->val;
}
T& back()
{
return head->prev->val;
}
//访问链表中最后一个元素
const T& back()const
{
return head->prev->val;
}
//元素修改的操作
void push_back(const T& data)
{
insert(v.end(), data);
}
void pop_back(const T& data)
{
auto it = v.end();
erase(--it);
}
void push_front(const T& data)
{
insert(v.begin(), data);
}
void pop_front(const T& data)
{
erase(begin());
}
//对任意位置进行的插入和删除操作
//iterator insert(iterator pos, const T& data);
//iterator erase(iterator pos);
//iterator erase(iterator first,iterator last);
void cleaar()
{
erase(begin(), end());
}
void swap(list<T>& l)
{
std::swap(head, L.head);
}
//用来创建头结点的操作
private:
void CreateHead()
{
head = new Node;
head->prev = head;
head->next = head;
}
private:
Node *head;
};
}
- 让其具有类似指针的行为