作者主页:lightqjx
本文专栏:C++
目录
[1. 常见接口](#1. 常见接口)
[2. list的操作](#2. list的操作)
[3. list的迭代器问题](#3. list的迭代器问题)
[1. 定义成员变量](#1. 定义成员变量)
[2. 迭代器的实现(重点)](#2. 迭代器的实现(重点))
[3. 插入](#3. 插入)
[4. 删除](#4. 删除)
[5. 构造&析构](#5. 构造&析构)
[6. 求list中的数据个数](#6. 求list中的数据个数)
一、list简介
对list的详细介绍,在链接中的解释是比较详细的:list文档介绍 。
简而言之,lis的结构就是一个带哨兵位的双向循环链表 。它是标准模板库(STL)提供的双向链表容器, 是一种可以在常数范围内在任意位置进行插入和删除的序列式容器。
它的优点是插入/删除高效(O(1)的时间),支持双向遍历。缺陷是不支持随机访问(O(n)的时间)。
list和vector一样,都是通过模板定义的,所以定义时都是需要指明类型的。如图:

使用时要注意需要包含头文件list,如以下代码:
cpp
#include <list>
int main()
{
std::list<int> ls1;
std::list<char> ls2;
std::list<double> ls3;
return 0;
}
二、list的使用
1. 常见接口
在list中的接口有许多,但它也是STL中的一个容器,大多数的接口的使用都比较类似,比如:构造,析构等等,都是非常容易掌握如何正确的使用的。如下图就是第一个list的一些部分接口:
可以发现,它们和string,vector都差不多,提供了迭代器,一些增删查改的操作等等操作。它们的使用其实都是差不多的,这里就不一一介绍了,若有还疑问,可以通过查阅本文章开头的list文档进行更多了解。这里我们来使用list的一些基本操作:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt;
// 尾插
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
auto it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
it++;
}
cout << endl;
// 头插
lt.push_front(10);
lt.push_front(20);
lt.push_front(30);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
//注意list的迭代器没有重载+
//在第3个位置后插入666
auto it2 = lt.begin();
for (int i = 0; i < 3; i++)
{
++it2;
}
lt.insert(it2, 666);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.erase(lt.begin()); //删除第一个位置
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
//......
return 0;
}
2. list的操作
与其他容器不同的常见的几个接口,如下所示:
splice 功能: 将元素从一个列表转移到另一个列表或同一列表的另一个位置。

这里我们来示范第一个:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
list<int> lt2;
lt2.push_back(10);
lt2.push_back(20);
lt2.push_back(30);
lt2.push_back(40);
lt2.push_back(50);
lt1.splice(lt1.begin(), lt2);//将lt2移动到lt1的开头,这是通过结点链接的,直接将结点连接在一起
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
remove 功能: 从列表中移除所有等于特定值的元素,相当于 find+erase,没找到啥也不干。

示例:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
lt1.remove(3); //移除三这个元素
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
return 0;
}
remove_if 涉及仿函数,暂时不说。
unique 功能: 移除连续重复的元素,仅保留一组连续相同 元素中的一个。
示例:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt1;
int arr[10] = { 1,1,1,3,5,5,6,1,1,3 };
for (int i = 0; i < 10; i++)
{
lt1.push_back(arr[i]);
}
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
lt1.unique();
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl; //输出:1 3 5 6 1 3
return 0;
}
merge 功能: 合并两个已排序 的列表,合并后原列表被清空,合并结果存储在当前列表中。
示例:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
list<int> lt2;
lt2.push_back(1);
lt2.push_back(2);
lt2.push_back(3);
lt2.push_back(4);
lt2.push_back(5);
lt1.merge(lt2);
for (auto e : lt1)
{
cout << e << " "; //输出:1 1 2 2 3 3 4 4 5 5
}
cout << endl;
for (auto e : lt2)
{
cout << e << " ";//原列表lt2会被清空
}
cout << endl;
return 0;
}
sort 功能: 对列表中的元素进行排序。

但要注意这里的list库里面的sort实际上没有什么意义,因为在我算法库里面已经实现了一个sort是用快排实现的,而在这里的底层实现是归并排序,效率比较低,不能频繁使用。这里list库里面的sort的实际意义就是使用起来比较方便。使用list中的sort如下所示:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt1;
list<int> lt2;
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
for (int i = 0; i < 10; i++)
{
lt1.push_back(arr[i]);
lt2.push_back(arr[i]);
}
//lt1.sort();
for (auto e : lt1)
{
cout << e << " "; //输出:1,2,3,4,5,6,7,8,9,10
}
cout << endl;
return 0;
}
reverse 功能: 反转列表中元素的顺序。
示例:
cpp
#include <list>
#include<iostream>
using namespace std;
int main()
{
list<int> lt1;
int arr[10] = { 10,5,6,7,6,5,4,3,2,1 };
for (int i = 0; i < 10; i++)
{
lt1.push_back(arr[i]);
}
lt1.reverse();
for (auto e : lt1)
{
cout << e << " "; //输出:1 2 3 4 5 6 7 6 5 10
}
cout << endl;
return 0;
}
3. list的迭代器问题
首先我们要知道在C++中的迭代器是有不同种类的,当下阶段,迭代器大致可以分为单向迭代器,双向迭代器,随机迭代器这三种。

迭代器在某些库里的函数中常常会出现,用来表示调用该函数的容器必须要含有对应的迭代器种类(在模板定义中)才能够调用,比如算法库里的sort,只有是随机迭代器容器(如vector,string等等)才能调用。
如果我们要确认一个容器到底属于哪一种迭代器,就可以查看各个容器文档的这一页:
还有一点需要注意:
以上就是我们使用迭代器需要注意的问题了。
所以为什么在list中就不能使用算法库的sort原因了。
对于sort需要注意,在list中是使用归并排序实现的,而算法库里的sort1一般是使用快排实现的,所以list中的算法效率比较低,一般不频繁使用。所以我们常常时通过先将list中的数据拷贝到vector中去,再使用算法库里的sort进行排序,最后再将vector中的数据拷贝回来(这里拷贝的效率是非常高的,所以一般不会影响整个过程的效率)。使用示例如下:
cpp
#include <list>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
list<int> lt;
srand(time(0));
const int N = 100000;
vector<int> v;
v.reserve(N);
for (int i = 0; i < N; ++i)
{
auto e = rand();
lt.push_back(e);
}
int begin1 = clock();
// 先拷贝到vector
for (auto e : lt)
{
v.push_back(e);
}
// 排序
sort(v.begin(), v.end());
// 拷贝回去
size_t i = 0;
for (auto& e : lt)
{
e = v[i++];
}
// 完成排序
return 0;
}
以上差不多就是list的一些基本使用情况了。
三、list的模拟实现
对list的模拟实现是我们理解list的一个重要操作。
1. 定义成员变量
我们知道list是一个带哨兵位的双向循环链表,也就是说,它是一个一个结点组成的。
我们首先需要将结点的结构实现出来,由于结点需要存储不同的数据,所以这里就需要使用模板。在C++中,这里由于结点中的变量需要再外部进行访问,所以我们可以将其通过struct实现出来,也可以是使用class实现,但需要将其设为公有的变量或者使用友元。
对于list中的变量直接是一个结点的指针,需要设为私有的。
即:
cpp
namespace MyCreat
{
template<class T>
struct list_node
{
list_node<T>* prev;
list_node<T>* next;
T val;
};
template<class T>
class list
{
typedef list_node<T> Node; //重定义一下,更加方便
public:
//......函数......
private:
Node* _head;
};
}
2. 迭代器的实现(重点)
如果我们直接来看库里面的list的实现,会发现它是有三个模板的,可能会看不懂,如图:
现在我们就来揭秘一下 为什么会有三个模板参数?以及list中的迭代器如何实现?
(1)关键部分实现
首先,我们来实现一个迭代器的关键部分操作,能够用来完成遍历。
和之前学习的string,vector的迭代器不同,list的迭代器是不能直接加加的,因为list是链表,所以它的空间是一种不连续的,所以我们在实现迭代器时就不能简单的将结点的指针作为迭代器。
如果我们想要使用迭代器来遍历list,就必须要使用到迭代器的加加,既然list的看就看不连续,那么我们就需要实现一个list的迭代器类,来封装一下迭代器的运算,由于list对不同类型的数据的存储使用的是模板,那么实现一个迭代器类也是需要模板的。
cpp
//实现一个迭代器的类
template<class T>
struct_list_iterator
{
typedef list_node<T> Node;
Node* _node;
_list_iterator(Node* node) // 构造
:_node(node)
{ }
T& operator*()
{
return _node->_val;
}
_list_iterator<T>& operator++()//重载前置++
{
_node = _node->_next;
return *this;
}
bool operator!=(const _list_iterator<T>& it)//加const的原因是因为end是值返回,具有常性
{
return _node != it._node;
}
//通过上面就可以完成对list的遍历了
// ....
};
在list类中,对于begin和end的函数,可以返回它们对应的结点指针:
cpp
//返回值说明:单参构造发生了隐式类型的转换
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
这里两个代码需要结合者看,在上面的 bool operator!=(const _list_iterater<T>& it) 的参数加const的原因就是在遍历访问时,对于以下这种代码:
cpp
MyCreat::_list_iterator<int> it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
因为end()是值返回,返回的是临时变量,具有常性,需要加上const修饰。
以上就是要遍历list的数据需要的迭代器的部分关键实现了
(2)const迭代器
在这里我们会解释第二个模板参数的作用。
list的const迭代器,是要求迭代器可以正常的加加,即自己可以被修改;但是迭代器指向的内容不能被修改。
所以我们的const迭代器就可以这么:将上面的操作*的返回值改为const T&。这样就可以实现一个const的迭代器了,这里我们将上面的代码拷贝一份,在将其 _list_iterater 改为_list_const_iterater ,得到下面的代码:
cpp
//实现一个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->_val;
}
_list_const_iterator<T>& operator++()//重载前置++
{
_node = _node->_next;
return *this;
}
bool operator!=(const _list_const_iterator<T>& it)//加const的原因是因为end是值返回,具有常性
{
return _node != it._node;
}
//通过上面就可以完成对const list的遍历了
// ....
};
在list类中,对于begin和end的函数也需要重载一下:
cpp
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
如此对于const的迭代器也就搞定了。
但是如果是这样写的话,就会有两段比较重复的代码,它们就是有操作*操作符时的返回值是不一样的,其他的都是一样的。所以我们这里可以简化一下,再使用一个模板参数,从而只使用一段迭代器代码来实现list的迭代器。看以下代码:
cpp
template<class T, class Ref>
struct _list_iterator
{
typedef list_node<T> Node;
typedef _list_iterator<T, Ref> self; //为了下面的函数写得方便些,我们可以将该类型重定义一下
Node* _node;
_list_iterator(Node* node) // 构造
:_node(node)
{ }
Ref operator*()
{
return _node->_val;
}
self& operator++()//重载前置++
{
_node = _node->_next;
return *this;
}
bool operator!=(const self& it) //这里加const是因为读写权限不能放大
{
return _node != it._node;
}
//通过上面就可以完成对list的遍历了
// ....
};
这就是第二个模板参数的使用。
(3)完善list的迭代器
在上面操作中,我们实现了普通迭代器和const的迭代器的关键操作实现。比如:重载前置++,重载*,重载!= 。而除了这三种操作,list的迭代器还有几种操作,比如:后置++,前置后置--,重载==,重载-> 等。
其中对于重载 -> ,一般重载的->操作符都是对自定义类型的数据使用的。它需要第三个模板参数。下面是对->重载的原始实现。
使用模板参数:
到这里,我们就知道了库里面的三个模板参数的由来了。
接下来我们再实现其他的重载操作,最后list的迭代器的一个类就实现完成了,如以下代码:
cpp
template<class T, class Ref,class Ptr>
struct_list_iterator
{
typedef list_node<T> Node;
typedef _list_iterator<T,Ref,Ptr> self; //为了下面的函数写得方便些,我们可以将该类型重定义一下
Node* _node;
_list_iterator(Node* node) // 构造
:_node(node)
{ }
Ref operator*()
{
return _node->_val;
}
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& it) //这里加const是因为读写权限不能放大
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
Ptr operator->()
{
return &_node->_val;
}
};
有了 list 的迭代器的认识之后,我们实现后面的list的基本操作就简单了。
3. 插入
list 的插入重载的实现,就是通过insert来实现的。
这里我们实现第一个就可以了,其他的都是类似的。
第一个的意思是在position位置之前插入一个val ,返回新插入结点的迭代器。
下面是insert的实现:
cpp
// pos位置之前插入val
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
newnode->_prev = prev;
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
return newnode;
}
有了insert之后,对于头插和尾插就都一样调用insert1就可以了:
cpp
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
4. 删除
list中的删除操作是erase。

库里面有两个删除的重载,我们这里只实现第一个,即删除position位置的结点,返回下一个位置的迭代器:
cpp
//删除pos位置的结点
iterator erase(iterator pos)
{
assert(pos!=end());//检查pos是否有效
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
_size--;
return next;
}
同样,通过erase也可以实现尾插和头删:
cpp
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
5. 构造&析构
现在我们实现list的构造函数。我们实现两个常用的构造函数:默认构造和拷贝构造。即:
cpp
list() // 构造
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
}
list(const list<T>& x) // 拷贝构造
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
for (auto& e : x)
{
push_back(e);
}
}
析构函数需要遍历链表,释放各个结点的空间,清理内部的指针(prev和next设为空)。这里我们可以使用另一种方法:使用clear函数。

clear是用来清空链表中的所有元素的,所以析构函数的实现为:
cpp
void clear()
{
iterator it = begin();
while (it != end())
{
it=erase(it);//防止迭代器失效
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
6. 求list中的数据个数
要求list中的数据个数,一种方法是可以直接遍历,通过计数来求;一种方法是通过在list在成员变量里设置一个变量专门来计数。在遇到插入时就加一,遇到删除就减一。很明显第一种方法是有效率的问题的。所以我们下面使用的是第二种。
C++11之前的是O(n),即通过遍历来求size;C++11之后是O(1),在list中缓存一个size变量。
cpp
size_t size()
{
//遍历求size
//size_t sz = 0;
//iterator it = begin();
//while (it != end())
//{
// ++sz;
// ++it;
//}
//return sz;
return _size;
}
在上述的所有操作中,需要修改的插入或删除操作有:
四、总代码参考
cpp
#pragma once
#include <assert.h>
namespace MyCreat
{
template<class T>
struct list_node
{
list_node<T>* _prev;
list_node<T>* _next;
T _val;
list_node(const T& val = T())
: _prev(nullptr)
, _next(nullptr)
, _val(val)
{ }
};
/*
//实现一个迭代器的类
template<class T>
struct _list_iterator
{
typedef list_node<T> Node;
Node* _node;
_list_iterator(Node* node) // 构造
:_node(node)
{ }
T& operator*()
{
return _node->_val;
}
_list_iterator<T>& operator++()//重载前置++
{
_node = _node->_next;
return *this;
}
bool operator!=(const _list_iterator<T>& it)//加const的原因是因为end是值返回,具有常性
{
return _node != it._node;
}
//通过上面就可以完成对list的遍历了
// ....
};
//实现一个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->_val;
}
_list_const_iterator<T>& operator++()//重载前置++
{
_node = _node->_next;
return *this;
}
bool operator!=(const _list_const_iterator<T>& it)//加const的原因是因为end是值返回,具有常性
{
return _node != it._node;
}
//通过上面就可以完成对const list的遍历了
// ....
};
*/
template<class T, class Ref,class Ptr>
struct _list_iterator
{
typedef list_node<T> Node;
typedef _list_iterator<T,Ref,Ptr> self; //为了下面的函数写得方便些,我们可以将该类型重定义一下
Node* _node;
_list_iterator(Node* node) // 构造
:_node(node)
{ }
Ref operator*()
{
return _node->_val;
}
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& it) //这里加const是因为读写权限不能放大
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
Ptr operator->()
{
return &_node->_val;
}
};
template<class T>
class list
{
typedef list_node<T> Node; //重定义一下,更加方便
public:
typedef _list_iterator<T,T&,T*> iterator;
typedef _list_iterator<T,const T&,T*> const_iterator;
//返回值说明:单参构造发生了隐式类型的转换
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
// ------------------------------------------------------
// pos位置之前插入val
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
newnode->_prev = prev;
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
_size++;
return newnode;
}
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
// ------------------------------------------------------
//删除pos位置的结点
iterator erase(iterator pos)
{
assert(pos!=end());//检查pos是否有效
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
_size--;
return next;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
// ------------------------------------------------------
list() // 构造
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
list(const list<T>& x) // 拷贝构造
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
for (auto& e : x)
{
push_back(e);
}
}
//清空元素
void clear()
{
iterator it = begin();
while (it != end())
{
it=erase(it);
}
_size = 0;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
// ------------------------------------------------------
size_t size()
{
//遍历求size
//size_t sz = 0;
//iterator it = begin();
//while (it != end())
//{
// ++sz;
// ++it;
//}
//return sz;
return _size;
}
private:
Node* _head;
size_t _size;
};
}
总体而言,通过模拟实现list,我们那个更加清晰的认识list的底层结构,对list的运用能够更加得熟悉!
感谢各位观看!希望能多多支持!