一、介绍list
-
list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
-
list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
-
list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
-
与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
-
与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
值得注意的是list不会知道下标的随机访问,只能够通过迭代器进行访问


只能用迭代器进行访问。
二、特定接口介绍
接口介绍:

本人觉得这里list的大多数接口与vector和string的差不多,重点介绍几个即可。
list有反向迭代器(待会讲解)
我们来讲讲splice,remove,remove_if,unique,merge,sort,reverse。
2.1 splice

void splice (iterator position, list& x)
cpp
int main()
{
list<int> l1(5, 2);
list<int> l2(5, 3);
list<int>::iterator pos = l1.begin();
//void splice(iterator pos,list&x):转移整个链表
//第一个pos指的是我们要转移到的那个链表的具体位置,list&x代表转移的整个链表
l1.splice(pos, l2);
for (auto& e : l1)
{
cout << e << endl;
}
return 0;
}
void splice (iterator position, list& x, iterator i);
cpp
int main()
{
list<int> l1(5, 2);
list<int> l2(5, 3);
list<int>::iterator pos1 = l1.begin();
list<int>::iterator pos2 = l2.begin();
//void splice (iterator position, list& x, iterator i):转移单个元素
//第一个pos指的是我们要转移到的那个链表的具体位置,list&x代表转移的整个链表,i是x中迭代器指向的
//要转移的元素
l1.splice(pos1, l2,pos2);
for (auto& e : l1)
{
cout << e << endl;
}
return 0;
}
void splice (iterator position, list& x, iterator first, iterator last);
cpp
int main()
{
list<int> l1(5, 2);
list<int> l2(5, 3);
list<int>::iterator pos1 = l1.begin();
list<int>::iterator first = l2.begin(), end = l2.end();
//void splice (iterator position, list& x, iterator first, iterator last):转移迭代器区间
//第一个pos指的是我们要转移到的那个链表的具体位置,list&x代表转移的整个链表,first和last是要转移的迭代器区间。
l1.splice(pos1, l2,first,end);
for (auto& e : l1)
{
cout << e << endl;
}
return 0;
}
2.2 remove的介绍
void remove (const value_type& val);
cpp
int main()
{
list<int> l1(5, 2);
list<int> l2(5, 3);
list<int>::iterator pos1 = l1.begin();
list<int>::iterator first = l2.begin(), end = l2.end();
//void splice (iterator position, list& x, iterator first, iterator last):转移迭代器区间
//第一个pos指的是我们要转移到的那个链表的具体位置,list&x代表转移的整个链表,first和last是要转移的迭代器区间。
l1.splice(pos1, l2,first,end);
l1.remove(3);
for (auto& e : l1)
{
cout << e << endl;
}
return 0;
}
2.3 remove_if的介绍
这里需要等到我们讲完lambda表达式等之后再讲,通俗来说就是删除所有满足自定义条件的元素
2.4 unique的介绍

就是去除重复元素:
cpp
int main()
{
list<int> l1(5, 2);
list<int> l2(5, 3);
list<int>::iterator pos1 = l1.begin();
list<int>::iterator first = l2.begin(), end = l2.end();
//void splice (iterator position, list& x, iterator first, iterator last):转移迭代器区间
//第一个pos指的是我们要转移到的那个链表的具体位置,list&x代表转移的整个链表,first和last是要转移的迭代器区间。
l1.splice(pos1, l2,first,end);
l1.unique();
for (auto& e : l1)
{
cout << e << endl;
}
return 0;
}
2.5 merge的介绍

void merge (list& x);
这是默认使用<来进行比较的,将两个有序链表进行合并,变成一个新的有序链表。
cpp
int main()
{
list<int> l1(5, 2);
list<int> l2(5, 3);
list<int>::iterator pos1 = l1.begin();
//void splice (iterator position, list& x, iterator first, iterator last):转移迭代器区间
//第一个pos指的是我们要转移到的那个链表的具体位置,list&x代表转移的整个链表,first和last是要转移的迭代器区间。
l1.merge(l2);
for (auto& e : l1)
{
cout << e << endl;
}
return 0;
}
还有一个类型的接口是关于仿函数的,后面讲了模版进阶再补充
2.6 sort
void sort();
进行从小到大的排序
2.7 reverse

需要注意的是,他只是改变节点的顺序,1<->2<->3<->4<->5,变成了5<->4<->3<->2<->1,一开始迭代器it指向1,reverse之后仍然指向1!!!
三、迭代器失效问题
3.1 insert是否会导致迭代器失效呢?

insert:在迭代器 pos 之前插入新元素。
list的insert插入并不会产生移动,所以有以下结论:
- 所有已存在的迭代器、指针、引用,全部保持有效!
- 仅仅新增了一个节点,原来所有节点的内存没有动,只是修改相邻节点的指针指向。
- 返回值:指向新插入元素的迭代器。
所以insert并不会导致迭代器失效。
3.2 erase是否会导致迭代器失效呢?

而earse必然会失效,因为该迭代器对应的节点被删除了。如果我们想继续用的话,就得利用返回值去更新迭代器,返回值是被删除元素的下一个位置的迭代器。
四、模拟实现
4.1基础知识介绍:
先看看源代码
第一部分:链表节点

第二部分:迭代器

我们在自己封装时,先整出一个结点类

然后再定义出链表类,链表类的成员变量只需要头结点和大小就可以了。

list类里面的接口实现基本都依赖于迭代器,没有迭代器根本实现不了,所以我们开始我们的重头戏,迭代器类的封装:
4.2迭代器的实现
首先对比一下vector和list的迭代器
我们在学习vector的时候,vector的迭代器本质就是原生指针,这是因为vector的空间是连续的,我们能够直接通过指针的++或者--,以及解引用来获得下一个数据,上一个数据。
但是list可不是啊

list它压根不连续,我们怎么通过++找到next指向的结点,通过--找到prev指向的结点,通过解引用直接找到_data呢?答案是运算符重载,由于绝大多数运算符都要求重载,所以我们就直接封装一个迭代器,在这个迭代器里面实现各种运算符的重载即可,在定义形参的时候,直接用迭代器定义即可。
我们先把代码写出来,待会慢慢分析即可
cpp
template<class T,class Ref,class Ptr>
struct iterator
{
private:
typedef list_node<T> Node;
typedef iterator<T, Ref, Ptr> Self;
Node* _node;//定义一个结点
public:
iterator(Node*node):_node(node)
{}
//解引用:
Ref operator*()const
{
return _node->_data;
}
Ptr operator->()const
{
return &_node->_data;
}
//前置++
Self& operator++()
{
return _node->_next;
}
//前置--
Self& operator--()
{
return _node->_prev;
}
//后置++
Self operator++(int)
{
Self temp(*this);
_node = _node->_next;
return temp;
}
Self operator--(int)
{
Self temp(*this);
_node = _node->_prev;
return temp;
}
//判断相等
bool operator==(const Self& s)const
{
return _node == s._node;
}
bool operator!=(const Self& s)const
{
return _node != s._node;
}
};
4.2.1 解引用
我们要的效果就是解引用之后返回_data。
第一个解引用运算符,返回Node的内置类型_data,直接得到数据,返回的是引用类型,避免了拷贝。
第二个是箭头运算符,返回的是数据成员的地址,这里有个小知识:
it->member 等价于 (it.operator->())->member,箭头运算符支持链式展开,这也是迭代器实现operator->的标准写法。
4.2.2 前置++与前置--
其实就是返回这个结点的_prev指向的结点和_next指向的结点
4.2.3 后置++与后置--
先保存现在的状态,然后再改变指向,最后返回初始状态:
问题1:我们在这里++和--的时候直接返回节点不好吗,为什么要返回迭代器呢?
答案:链表节点(list_node<T>*)是迭代器内部的实现细节,对外绝不暴露!迭代器才是给用户使用的统一接口。
如果说返回结点会怎么样呢?
- 用户可以随便篡改
_prev、_next指针,直接破坏链表结构; - 一旦后续底层实现改动(修改节点结构),外部代码全部崩溃。
迭代器把节点指针包起来,只开放安全允许的操作 :++ -- * -> == !=。 用户无法直接访问、修改 _node->_next、_node->_prev。

而迭代器就不会,迭代器将_node变为了私有成员,外部的代码只能调用我的成员函数,外部拿不到_node,无法直接读写_prev和_next。
还有以下困难

总的来说:迭代器内部封装了什么,外部的才能够用什么,这大大提高了list的安全性。
4.3迭代器的使用
cpp
template<class T>
class list
{
public:
typedef list_node<T> Node;
typedef iterator<T, T&, T*> iterator;
typedef iterator<T, const T&, const T*> const_iterator;
//begin
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
private:
Node* _head;//这里只需要定义我们的头结点即可
size_t _size;//然后这里记录了我们的链表大小
};
这是使用迭代器实现的一些接口
有个小问题:为什么不直接写成typedef const iterator const_iterator呢?
因为typedef碰到const的话,就不是简单的字符串替换 实际上你以为的const T* ,在这里变成了T*const ,因为迭代器我们是希望他可以进行++和--的,而我们只是不希望他指向的内容给改变,所以我们的const要修饰的是指针的内容,而不是修饰指针。
4.4 list成员函数的实现
4.4.1构造函数
1.默认构造函数

2.有参构造函数

3.迭代器区间构造

4.拷贝构造

直接范围for即可
4**.5修改相关的函数**
empty和size
cpp
//返回大小
size_t size() const
{
return _size;
}
//判断是否为空
bool empty()const
{
return _size == 0;
}
insert
cpp
//插入接口
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
newnode->_next = cur;
newnode->_prev = prev;
prev->_next = newnode;
cur->_prev = newnode;
_size++;
return iterator(newnode);
}
erase
cpp
//删除接口
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return iterator(next);
}
尾插尾删头插头删
cpp
//尾插
void push_back(const T& val)
{
insert(end(),val);
}
//头插
void push_front(const T& val)
{
insert(begin(), val);
}
//头删
void pop_back()
{
erase(begin());
}
//尾删
void pop_front()
{
erase(--end());
}
resize
cpp
////resize 如果n小于当前容量的大小,则内容将减少到前n个元素 当n大于容器大小时,则在末尾插入任意容量的内容。
void resize(size_t n, const T& val = T())
{
size_t sz = size();
if (n < sz)
{
while (n < sz)
{
pop_back();
sz--;
}
}
else
{
while (n > sz)
{
push_back(val);
sz++;
}
}
}
4.6 clear和析构函数
cpp
//清理函数:只剩下头结点
void clear()
{
iterator it = begin();
while (it!=end())
{
it=erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
4.7 赋值重载和assign
cpp
//assign直接替换
void assign(size_t n, const T& val)
{
clear();//只剩头结点
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//assign迭代器区间替换
template<class InputIterator>
void assign(InputIterator first, InputIterator last)
{
clear();
while (first != last)
{
push_back(*first);
++first;
}
}
//assign迭代器区间替换重载,防止间接寻址
void assign(int n, const T& val)
{
clear();//只剩头结点
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//赋值重载
list<T>& operator=(const list<T>& lt)
{
assign(lt.begin(), lt.end());
return *this;
}
五、总代码
cpp
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace whx
{
//首先实现一个结点
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T &data = T())
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
};
template<class T>
class list;
template<class T,class Ref,class Ptr>
struct list_iterator
{
private:
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> Self;
Node* _node;//定义一个结点
public:
friend class list<T>;//声明友元
list_iterator(Node*node):_node(node)
{}
//解引用:
Ref operator*()const
{
return _node->_data;
}
Ptr operator->()const
{
return &_node->_data;
}
//前置++
Self& operator++()
{
return _node->_next;
}
//前置--
Self& operator--()
{
return _node->_prev;
}
//后置++
Self operator++(int)
{
Self temp(*this);
_node = _node->_next;
return temp;
}
Self operator--(int)
{
Self temp(*this);
_node = _node->_prev;
return temp;
}
//判断相等
bool operator==(const Self& s)const
{
return _node == s._node;
}
bool operator!=(const Self& s)const
{
return _node != s._node;
}
};
//开始封装链表
//可以在迭代器类内部声明lis为友元函数,这样迭代器的_node仅仅对list开放
template<class T>
class list
{
public:
typedef list_node<T> Node;
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;
//begin
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
//无参构造
list()
{
empty_init();
}
//有参构造
list(size_t n,const T& val=T())
{
empty_init();
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//迭代器区间构造
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
first++;
}
}
//拷贝构造:直接迭代器
list(const list<T>& l)
{
empty_init();
for (auto& e : l)
{
push_back(e);
}
}
//插入接口
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
newnode->_next = cur;
newnode->_prev = prev;
prev->_next = newnode;
cur->_prev = newnode;
_size++;
return iterator(newnode);
}
//删除接口
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
return iterator(next);
}
//尾插
void push_back(const T& val)
{
insert(end(),val);
}
//头插
void push_front(const T& val)
{
insert(begin(), val);
}
//头删
void pop_back()
{
erase(begin());
}
//尾删
void pop_front()
{
erase(--end());
}
////resize 如果n小于当前容量的大小,则内容将减少到前n个元素 当n大于容器大小时,则在末尾插入任意容量的内容。
void resize(size_t n, const T& val = T())
{
size_t sz = size();
if (n < sz)
{
while (n < sz)
{
pop_back();
sz--;
}
}
else
{
while (n > sz)
{
push_back(val);
sz++;
}
}
}
//assign直接替换
void assign(size_t n, const T& val)
{
clear();//只剩头结点
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//assign迭代器区间替换
template<class InputIterator>
void assign(InputIterator first, InputIterator last)
{
clear();
while (first != last)
{
push_back(*first);
++first;
}
}
//assign迭代器区间替换重载,防止间接寻址
void assign(int n, const T& val)
{
clear();//只剩头结点
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//赋值重载
list<T>& operator=(const list<T>& lt)
{
assign(lt.begin(), lt.end());
return *this;
}
//返回大小
size_t size() const
{
return _size;
}
//判断是否为空
bool empty()const
{
return _size == 0;
}
//清理函数:只剩下头结点
void clear()
{
iterator it = begin();
while (it!=end())
{
it=erase(it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
private:
Node* _head;//这里只需要定义我们的头结点即可
size_t _size;//然后这里记录了我们的链表大小
};
}
测试用例:
cpp
#include<list>
#include"list.h"
#include <iostream>
#include <string>
using namespace std;
// 这里放你已实现的 Node、list_iterator、list<T> 代码
void TestBasicInsert()
{
cout << "===== 测试用例1:基础插入与正向遍历 =====\n";
list<int> lt;
auto it1 = lt.insert(lt.end(), 10);
auto it2 = lt.insert(lt.end(), 20);
lt.insert(it2, 15); // 在20前面插入15
// 预期链表:10 <-->15 <-->20
for (auto it = lt.begin(); it != lt.end(); ++it)
{
cout << *it << " ";
}
cout << "\n预期输出:10 15 20\n\n";
}
void TestInsertBegin()
{
cout << "===== 测试用例2:在头部持续插入 =====\n";
list<int> lt;
lt.insert(lt.begin(), 3);
lt.insert(lt.begin(), 2);
lt.insert(lt.begin(), 1);
// 预期:1 2 3
for (auto it = lt.begin(); it != lt.end(); ++it)
{
cout << *it << " ";
}
cout << "\n预期输出:1 2 3\n\n";
}
void TestEmptyListInsert()
{
cout << "===== 测试用例3:空链表调用insert(end()) =====\n";
list<string> lt;
// 空链表 end() == begin(),在end插入等价尾插
auto new_it = lt.insert(lt.end(), "hello");
cout << "插入元素:" << *new_it << "\n";
cout << "size预期等于1\n\n";
}
void TestContinuousMultiInsert()
{
cout << "===== 测试用例4:多次定点插入混合 =====\n";
list<int> lt;
auto pos = lt.insert(lt.end(), 100);
lt.insert(pos, 90);
lt.insert(lt.end(), 110);
lt.insert(lt.begin(), 80);
// 预期:80 90 100 110
for (auto it = lt.begin(); it != lt.end(); ++it)
{
cout << *it << " ";
}
cout << "\n\n";
}
void TestReturnedIteratorValid()
{
cout << "===== 测试用例5:验证insert返回迭代器有效 =====\n";
list<int> lt;
auto ret_it = lt.insert(lt.end(), 500);
if (ret_it != lt.end() && *ret_it == 500)
{
cout << "✅ 返回迭代器有效测试通过\n\n";
}
}
void TestCustomType()
{
cout << "===== 测试用例6:自定义结构体类型测试 =====\n";
struct Student
{
int id;
string name;
bool operator==(const Student& other) const
{
return id == other.id;
}
};
list<Student> stulist;
stulist.insert(stulist.end(), Student{ 1, "Zhang" });
stulist.insert(stulist.end(), Student{ 2, "Li" });
cout << "自定义结构体插入正常\n\n";
}
void TestLargeAmountInsert()
{
cout << "===== 测试用例7:批量大量插入压力测试 =====\n";
list<int> lt;
const int N = 2000;
for (int i = 0; i < N; i++)
{
lt.insert(lt.end(), i);
}
cout << "批量" << N << "个元素插入完成,无崩溃则正常\n\n";
}
int main()
{
TestBasicInsert();
TestInsertBegin();
TestEmptyListInsert();
TestContinuousMultiInsert();
TestReturnedIteratorValid();
TestCustomType();
TestLargeAmountInsert();
cout << "所有基础测试执行完毕!\n";
return 0;
}
结果:
