list
底层是双向带头循环链表
,在链表的任意位置进行插入和删除操作的时间复杂度都是 O(1)
。这是因为链表插入或删除节点只需要修改相邻节点的指针。例如,在实现一个任务调度系统时,任务可能会随时被添加或移除,如果使用std::list
来存储任务,就可以高效地处理这些操作
为了与库里的 list
进行区分,所有的类和函数都放在自定义的命名空间 bit
进行区分
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _val;
list_node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{}
};
经过数据结构的学习我们知道,每个节点都是一个结构体,因为是双向循环链表
,所以有 prev
和 next
,使用初始化列表进行常规的初始化
🔥值得注意的是:
struct
而不是 class
,是因为 struct
的默认访问权限是 public
,class
的默认访问权限是 private
,虽然用 class+友元
的方式也是可行的,但不如用 struct
来的方便const T& val = T()
,而不是常写的 0
,是因为 T
无法确定是自定义类型
还是内置类型
,所以当没有参数传入时就调用各自类型的默认构造函数
进行传参迭代器就是一个桥梁,让容器能通过迭代器实现算法
容器
<–>迭代器
<–>算法
根据迭代器的容器底层结构决定的性质,可以大致分为三类:
forward iterator
):支持运算符重载 ++
,常用容器为 forward_list
、unordered_map
、unordered_set
bidirectional iterator
):支持运算符重载 ++
、--
,常用容器为 list
、map
、set
random access iterator
):支持运算符重载 ++
、--
、+
、-
,常用容器为vector
、string
、deque
从下往上为依次包含的关系,比如 list
迭代器为双向,那么既可以用双向
,也可以用单向
,不能用随机
。通常 std
库里的是随机迭代器
因此这也解释了为什么 vector
迭代器可以使用 std::iterator
,也可以使用 vector<T>::iterator
。但是 list
迭代器不可以使用 std::iterator
,一般使用 list<T>::iterator
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
剖析底层代码先从功能入手,通常我们实现迭代器如代码所示
那么要先实现基本的迭代器模版
template<class T>
struct _list_iterator
{
typedef list_node<T> Node;
Node* _node;
_list_iterator(Node* node)
:_node(node)
{}
};
实现基本的构造函数,_node
是一个指向 Node
类型对象的指针,它用于存储当前迭代器所指向的链表节点
实现 push_back
往链表中放置数据,实现方式还是和双线链表中一样,可以去数据结构专题中回顾
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
//insert(end(), x);
}
接下来实现 begin()
和 end()
,还是比较简单的,这里直接展示放在 list
模版中的效果,后续就只单独列出实现功能的代码了
template<class T>
class list
{
typedef list_node<T> Node;
public:
typedef _list_iterator<T> iterator;
iterator begin() const
{
return _head->_next;
}
iterator end() const
{
return _head;
}
list()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
}
private:
Node* _head;
};
list
模版有一个头节点,作为哨兵位,实现了双链表的构造函数,看似简单的代码,实则隐藏了很多的细节
🔥值得注意的是:
begin()
和 end()
返回的是 iterator
类型,C++
标准库提供了大量基于迭代器的通用算法(如 std::find
、std::sort
、std::for_each
等)。当自定义容器的 begin()
函数返回迭代器时,这些通用算法可以直接应用到自定义容器上,实现代码的复用iterator
是 _list_iterator<T>
模版的重命名,更符合使用习惯begin()
和 end()
返回的值看似是 Node*
类型,实际上单参数的函数支持隐式转换
,Node*
隐式转换为 iterator
类型,return _head->_next
其实是 return iterator(_head->_next)
begin()
和 end()
函数都用 const 修饰,使函数更具普遍性,包括 const
变量的情况,普通类型调用也是权限的缩小,两种情况共用一个函数T& operator*()
{
return _node->_val;
}
这里 _node
应该是一个指向链表节点的指针,_val
是链表节点中存储的数据成员。该函数返回节点数据 _val
//前置
_list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
//后置
_list_iterator<T> operator++(int)
{
self temp(*this);
_node = _node->_next;
return temp;
}
为了区分前置后置,前置 ++
和后置 ++
虽然是运算符重载,但是形式上也构成函数重载,后置 ++
增加这个 int
参数不是为了接受具体的值,仅仅是占位,跟前置 ++
构成重载, int
这个位置传任何值都可以,实际调用的时候前置 ++
和后置 ++
可能分别为 operator++()
和 operator++(0)
,括号内的值是随机的
🔥值得注意的是: 前置 ++
返回的对象还存在,所以可以用引用,而后置 ++
返回的对象是个临时对象,所以不能返回引用
bool operator!=(const _list_iterator<T>& it) const
{
return _node != it._node;
}
该函数通过比较当前对象的 _node
成员变量和传入对象 it
的 _node
成员变量来判断两个对象是否不相等。如果 _node
和 it._node
不相等,则返回 true
,表示两个对象不相等;否则返回 false
,表示两个对象相等
🔥值得注意的是: 调用 end()
时传值返回,具有常性,所以 !=
重载要参数加 const
以上就已经基本实现了迭代器,能够跑起来实现正常功能,范围 for
的底层也是迭代器,所以也能够使用了,但是还有很多需要完善的
通常迭代器分为 iterator
和 const_iterator
那么我们难道再写一个来实现吗,显然是不符合编程范式的
,代码就显得太冗余了
有人或许会想到这么写:
typedef _list_iterator<T> iterator;
typedef const _list_const_iterator<T> const_iterator;
这样看起或许可行,但是我们要思考 const
的使用
举个例子:
const T* ptr1;
T* const ptr2;
这两段代码的含义分别为:不可以修改指向对象的内容
,不可以修改指向别的对象
显然 typedef const _list_const_iterator<T> const_iterator
是第一种 const
的使用,在 ++
运算符重载中有 _node = _node->_next
,需要通过这段代码来到下一个节点,按照我们这样加 const
就和这段代码冲突了。我们只是不想修改其内容,而不是无法跳转节点
所以发明 STL库的人真是个天才,想到了巧妙的办法:
迭代器模版
template<class T, class Ref>
......
list模版
typedef _list_iterator<T, T&> iterator;
typedef _list_iterator<T, const T&> const_iterator;
iterator
和 const_iterator
的区别就在于 *
运算符重载返回的值是否能够被修改,因此增加一个新的模版参数,当使用对应的迭代器就会调用相应的模版
在查看STL库里的list底层代码时,会发现实际上的迭代器代码有三个参数,单纯去看是很难发现为什么要有第三个参数的,必须要结合实际的应用场景
举个例子:
struct A
{
A(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{}
int _a1;
int _a2;
};
void test_list2()
{
list<A> lt;
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
lt.push_back(A(3, 3));
lt.push_back(A(4, 4));
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
/*cout << (*it)._a1 << " " << (*it)._a2 << endl;*/
cout << it->_a1 << " " << it->_a2 << endl;
++it;
}
cout << endl;
}
假设有个类A
,需要遍历输出,通常我们会写成 cout << (*it)._a1 << " " << (*it)._a2 << endl
,但是这并不符合编程范式
可是为什么写成 cout << it->_a1 << " " << it->_a2 << endl 也能通过?
严格来说,应该写成
it->->_a2
,才符合语法,因为运算符重载要求可读性,所以编译器特殊处理,省略一个->
所以我们可以写出 ->
运算符重载
Ptr operator->()
{
return &(_node->_val);
}
同样的道理,为了实现iterator
和 const_iterator
两种迭代器,再增加一个模版参数,同时因为模版参数有点过多,导致类型太长了,对类型也进行重命名
迭代器模版
template<class T, class Ref, class Ptr>;
typedef _list_iterator<T, Ref, Ptr> self;
......
list模版
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
~list()
{
clear();
delete _head;
_head = nullptr;
}
list(const list<T>& lt)
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
//万一拷贝的是string、vector等代价就大了,所以用&
for (auto& x : lt)
{
push_back(x);
}
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
iterator 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;
cur->_prev = newnode;
newnode->_prev = prev;
return newnode;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return next;
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
size_t size()
{
size_t sz = 0;
iterator it = begin();
while (it != end())
{
++sz;
++it;
}
return sz;
}
//前置
self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置
self operator--(int)
{
self temp(*this);
_node = _node->_prev;
return temp;
}
bool operator==(const self& it) const
{
return _node == it._node;
}
#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace bit
{
//节点模版
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _val;
list_node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _val(val)
{}
};
//迭代器模版
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)
{}
//const T& operator*()就可以实现const_iterator
Ref& operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &(_node->_val);
}
//前置
self& operator++()
{
_node = _node->_next;
return *this;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置
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& it) const
{
return _node != it._node;
}
bool operator==(const self& it) const
{
return _node == it._node;
}
};
//list类模版
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;
//typedef const _list_const_iterator<T> const_iterator;
//不能这样写,因为++没法next,就没法遍历了
iterator begin() const
{
return iterator(_head->_next);
//单参数的函数支持隐式转换
}
iterator end() const
{
return _head;
}
list()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
}
list(const list<T>& lt)
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
//万一拷贝的是string、vector等代价就大了,所以用&
for (auto& x : lt)
{
push_back(x);
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void push_back(const T& x)
{
/*Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;*/
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
iterator 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;
cur->_prev = newnode;
newnode->_prev = prev;
return newnode;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return next;
}
size_t size()
{
size_t sz = 0;
iterator it = begin();
while (it != end())
{
++sz;
++it;
}
return sz;
}
private:
Node* _head;
};
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
//调用end的函数时传值返回,具有常性,所以!=重载要参数加const
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
struct A
{
A(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{}
int _a1;
int _a2;
};
void test_list2()
{
list<A> lt;
lt.push_back(A(1, 1));
lt.push_back(A(2, 2));
lt.push_back(A(3, 3));
lt.push_back(A(4, 4));
//严格来说,应该写成it->->_a2,才符合语法
//因为运算符重载要求可读性,所以编译器特殊处理,省略一个->
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
/*cout << (*it)._a1 << " " << (*it)._a2 << endl;*/
cout << it->_a1 << " " << it->_a2 << endl;
++it;
}
cout << endl;
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(5);
lt.push_front(6);
lt.push_front(7);
lt.push_front(8);
lt.pop_front();
lt.pop_back();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.clear();
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
lt.push_back(40);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
cout << lt.size() << endl;
}
void test_list4()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
list<int> lt2;
lt2 = lt1;
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
}
}