一、list的介绍和使用
1、认识list
List和vector一样也是C++标准模板库中的一个容器,其中文名就是我们前面数据结构学习的链表,那么其和我们前面学习的string类和vector类最大的不同就是其物理结构是不连续的,其是一个一个的节点构成的,然后list是一个双向带头循环链表:

上面就是list的简单图示了,可以看到其一个结点4主要是三个部分,一个是存储的数据,然后一个是指向后一个节点,一个是指向前一个节点,所以我们也说在逻辑上我们的list是连续的。
下面我们来看看其使用
2、list的使用
(1)list对象的实例化
其语法和vector的一样:list<类型>+对象名。
其实数据类型可以是内置类型也可以是自定义类型

(2)list对象的初始化

这个是库中的构造函数
第一个就是默认构造一个空的list,然后其有一个默认值。
第二个就是给构造的list对象构造为n给第二个参数的值。
第三个构造就是赋值运算符重载构造
第四个构造就是利用我们的迭代器的方式进行构造
第五个是拷贝构造了

可以看到list不仅支持迭代器,还支持其他类型数据的迭代器进行初始化
那么说完数据的初始化,我们的list主要使用:增删改查这四种功能
(3)尾插元素
其尾插函数的使用和前面学习的string和vector的尾插是一样的,也是使用push_back函数进行尾插:

如上,这是其库中的原型。

(4)尾删
我们的list的尾删函数和前面学习的vector和string类也是一样的名称:


(5)获取list中的元素
前面我们学习string和vector容器的时候,因为这两个容器的物理结构都是连续的,那么其就支持[]访问元素的方式,那么我们的list的结构是不连续的,所以就没有支持这种方式进行访问元素,我们前面学习数据结构的时候,我们对于元素的访问都是通过指向下一个结点的指针进行访问。
那么我们在list中是否也只能这样进行数据的访问呢?
其实不然,我们的C++容器,大部分都支持使用迭代器的方式进行数据的访问,所以list中其也支持这个方式:


那么我们就可以使用++的方式对list的元素进行访问了。

那么支持迭代器就支持范围for:

那么就有同学会很疑惑,lits的空间都不是连续的,那么其怎么通过++就可以找到下一个结点的位置的呢?
这个我们后面模拟实现的时候会进行讲解。
(6)指定结点前插入
这个功能在vector和string类中也都有提供


(7)删除指定位置的元素
我们的list中也提供了指定位置删除的操作:

可以看到其还支持迭代器的方式进行数据的删除。

然后就是对于我们要连续对数据进行删除,然后使用迭代器的问题,前面我们学习vector类的时候讲过迭代器失效的问题,如下面这种情况就会出现:

如果是上面的情况,没有使用接收值来接收返回值,那么就会造成迭代器失效,所以当我们要使用迭代器进行连续的删除的时候,为了避免迭代器失效的问题,我们要使用一个来接收其返回值。
(8)排序sort
在我们的库中还提供了排序的接口,我们可以使用其对数据进行排序
然后我们对于list的排序的话,在数据量少的时候没啥影响,但是当数据很多的时候,其效率就不是很高了,虽然其时间复杂度是O(n log n)。

上面这个是升序的排法,然后我们的sort还支持降序的排序:

(9)链接
这个的话就是将两个原本独立的list拼在一起成为一个新的list,不过其支持很多种情况。
第一个就是两个list进行拼接,然后就是单个元素进行拼接,指定范围拼接等
下面我演示一下整体拼接和单个元素拼接:


(10)去重

要注意的是去重的话要注意我们的链表是有序的:

(11)逆置
这个接口我们在string和vector的时候也遇到了:


注意:
我们前面已经提到了,我们的list链表其在逻辑上是连续的,但是其实际物理结构上不是连续的,所以我们在使用上是不可以通过+n的方式寻找元素。
二、list的模拟实现
(1)整体实现结构
首先我们知道的是我们的库中的list是双向循环带头链表,然后其是由一个一个的结点组成的,然后我们的结点里是由一个存储数据,一个指向下一个结点,一个指向前一个结点的指针,一共三个成员组成的。
然后和string和vector类的实现一样,为了避免和库中的起冲突,我们将其放置在一个命名空间中。

这个时候就会有同学会发出疑问了,为啥我们这个时候去使用struct来定义list类结点,这是因为我们的链表三个成员都要经常被进行访问,然后要是我们使用class进行定义,然后其被私有,那么我们就还是要将其友元化,那么我们不如直接将其公开即可。
然后我们的list是带头的链表,那么我们还需要一个头结点

那么上面就是我们整个list的初始实现的结构。
(2)尾插
尾插就是在链表的尾部插入一个结点,那么我们就要先找到当前链表的尾结点,那么我们头结点的前一个结点就是当前链表的尾结点,那么我们就可以进行插入了,不过要注意的是我们要先将插入前的尾结点的地址记录下来,然后再进行修改头结点指向前一个结点的指针,将其指向我们要插入的结点,然后我们插入的新的结点的指向下一个结点的指针指向头结点,然后原来的尾部结点的指向下一个结点的指针指向新插入的结点。

(3)尾删
尾删很简单的,就找到尾部结点然后删除即可,但是也要注意的是,当我们这个list只有头结点的时候是不可以进行删除操作的。

(4)构造函数
首先就是我们普通结点的构造:

然后我们的头结点部分也要写一下其构造函数:

(5)运算符重载
这个我们是实现的迭代器上的。
1、解引用
解引用就是返回这个位置存储的数据即可:

2、->运算符重载
其写法如下:

3、前置++
这个就是将我们的迭代器移动到下一个结点,那么我们就可以使用++的方式进行数据的遍历和访问了:

4、后置++
后置++的话,其返回的还是当前的位置,但是使用完后,指针的要指向下一个结点的位置,那么我们可以使用一个临时变量存储当前的位置,然后再将指针 往下一个结点移动,然后返回记录好的原来的结点的位置:

这里我们会发现我们此时没有使用引用返回,这是因为我们的tmp是函数中创建的一个临时变量,那么出了这个函数后,这个变量就会被销毁了,那么就会造成野引用的问题。
5、前置--
我们的list库中,其是一个双向的迭代器,那么其就允许向前--的操作:

6、后置--
这个和前面的后置++一个道理:

7、==
这个就很简单了:

8、!=

(6)const迭代器
我们上面对于迭代器的内容的实现都是对于普通的变量进行的,那么对于const对象,其和普通对象的迭代器的区别就是,要求其指向的内容不可以被进行修改,而迭代器本身是可以进行修改的。
所以我们有的同学一开始会想到使用const iterationor的方式来表示const迭代器,这个const是修饰迭代器本身的。所以我们针对这个情况,我们可以单独实现一个const迭代器的类。

我们会发现,除了解引用和->的部分,其他的代码和普通迭代器没有区别。
这样我们的代码的复用率就比较低了,所以我们考虑另外一种方式来实现。
我们采用模板来进行优化,提高代码的复用率:
由于普通迭代器和const的迭代器其实际上只是返回类型不同,那么我们不妨增加两个模板参数

(7)迭代器begin和end
这两个就很简单了,就是其有两个重载,一个是普通的迭代器,一个是const对象的迭代器:

(8)链表的其他操作
1、insert
这个函数的作用是在pos的位置之前插入一个结点:

2、push_back
这个函数我们上面是按照尾插的逻辑写的,然后我们也可以和下面这种方式写:

3、push_front
这个函数是使用头插的,那么我们和上面的push_back一样复用insert即可:

4、erase
这个函数是删除指定位置的结点:

上面是按照arase的逻辑来实现的,不过我们前面学习vector提到的迭代器失效问题,其主要是出现在删除数据移动数据和扩容上,我们这边删除数据,那么就会使得原来的迭代器变成了一个野指针,那么后面我们进行访问数据的操作的时候 ,编译器就会报错。
所以我们这边对于areas的话,我们对其弄一个返回值,然后对原来的迭代器进行赋值,那么就不会出现这个问题了。

5、swap
这个函数的作用是交换两个链表,然后我们只需要将链表中的成员变量进行交换即可,我们在函数中调用库中的swap函数对其一个一个进行交换即可:

(9)容量
我们在设计链表的时候可以添加一个成员变量,用来记录结点个数,所以我们这个函数直接返回这个成员变量即可:
(10)clear
这个函数的作用是用来直接清空链表的结点的:

(11)拷贝构造
我们的拷贝构造主要是两个,一个是普通的拷贝构造,然后还有一个就是赋值运算符拷贝:
(12)析构函数
这个我们可以先清空链表,然后将头结点释放:

三、完整代码
#pragma once
#include<assert.h>
using namespace std;
namespace cyy
{
//链表结点
template<class T>
struct list_node
{
list_node(const T &x= T())
:_next(nullptr)
,_prev(nullptr)
,_date(x)
{
}
//节点成员
list_node<T> _date;
list_node<T>* _next;
list_node<T>* _prev;
};
//迭代器
template<class T, class Ref, class Ptr >
struct _list_iterator
{
typedef list_node<T> Node;
typedef list_iterator<T,Ref,Ptr> Self;
Node* _node;
Ref operator*()
{
return _node->_date;
}
Ptr operator->()
{
return &_node->_date;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
bool operator!=(const Self& s)const
{
return _node != s._node;
}
};
//const迭代器
//template<class T>
//struct __list_const_iterator
//{
// typedef list_node<T> Node;
// Node* _node;
// __list_const_iterator(Node* node)
// :_node(node)
// {}
// const T& operator*()
// {
// return _node->_data;
// }
// __list_const_iterator<T>& operator++()
// {
// _node = _node->_next;
// return *this;
// }
// __list_const_iterator<T> operator++(int)
// {
// __list_iterator<T> tmp(*this);
// _node = _node->_next;
// return tmp;
// }
// __list_const_iterator<T>& operator--()
// {
// _node = _node->_prev;
// return *this;
// }
// __list_const_iterator<T> operator--(int)
// {
// __list_const_iterator<T> tmp(*this);
// _node = _node->_prev;
// return tmp;
// }
// bool operator!=(const __list_const_iterator<T>& it) const
// {
// return _node != it._node;
// }
// bool operator==(const __list_const_iterator<T>& it) const
// {
// return _node == it._node;
// }
//};
//链表
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
list()
:_head(nullptr)
{
Head = new Node;
Head->_next = _head;
Head->_prev = _head;
}
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
list(const list<T>& it)
{
empty_init();
for (const auto& e : it)
{
push_back(e);
}
}
list<T>& operator=(list<T>& it)
{
swap(it);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
iterator begin()
{
return iterator(_head->_next);
}
const_iterator begin()
{
return const_iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator end()
{
return const_iterator(_head);
}
void pop_back()
{
assert(_head->next != Head);
Node* pp = _head->_prev;
Node* pur = pp->_prev;
head->_prev = pur;
pur->_next = _head;
delete pp;
}
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_next = cur;
newnode->_prev = prev;
cur->_prev = newnode;
++_size;
}
// void push_back(const T& _date)
//{
// //开辟结点空间
// Node* tmp = new Node(_date);
// Node* pp = _head->_prev;
// tmp->_date = _date;
// tmp->_next = _head;
// tmp->_prev = pp;
// pp->_next = tmp;
// _head->_prev = tmp;
//}
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
iterator erase(iterator pos, const T& x)
{
assert(pos != end());//方式删了哨兵位头结点
Node* cur = pos._node;
Node* next = cur->_next;
Node* prev = cur->_prev;
prev->_next = next;
next->_prev = prev;
delete cur;
--_size;
return iterator(next);
}
void swap(list<T>& it)
{
std::swap(_head, it._head);
std::swap(_size, it._size);
}
size_t size()
{
return _size;
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
private:
Node* _head;
size_t _size = 0;
};
}

