目录
实现思路
List是一个类模板,实际上是一个双向循环链表。在上一篇list基本使用的博客中,可以发现list支持了像vector一样的"下标访问",也就是通过迭代器区间去访问链表的每个节点数据。本人在初学阶段也是比较好奇——list的迭代器(正向与反向)是如何实现的;本篇博客将以自己所掌握的知识,详细的介绍list是如何实现的。
list实现结构:
1.节点设计
2.正向迭代器设计
3.反向迭代器设计
4.list各个接口完善(增删查改)
一、list的节点设计
list本身和list的结点是两个不同的结构,需要分开设计。以下是list的节点结构:
/*ListNode.h*/
namespace mlg
{
template<class T>
struct ListNode
{
ListNode<T>* _prev; //节点的前指针
ListNode<T>* _next; //节点的后指针
T _data; //节点的数据
ListNode(const T& data= T())//初始化列表进行初始化
:_prev(nullptr)
,_next(nullptr)
,_data(data)
{}
};
}
首先,我们在自己的命名空间内模拟实现list(为了防止与库冲突),上面的代码就是list节点的结构。在这里是用并没有使用class,因为struct默认访问权限是public,又因为节点是需要经常访问的,所以使用struct更好。
我们将这个类加上 template<class T> 后,就能够实现节点存储不同类型的数据,这也是C++模板的好处。
二、list的初步框架
当我们设计好了list的结点以后,我们就需要对list的基本框架进行设计,list要能够去控制这些节点,它的成员变量就必须是这个节点类;以下是list的结构设计:
/*List.h*/
namespace mlg //命名空间保持一致
{
template<class T> //list是一个类模板,设计时需要这个
class list
{
typedef ListNode<T> Node; //将其类型重命名方便书写,在后续的类型中会有比较多的typedef
public:
/*
成员函数包含了默认成员函数、迭代器和增删查改
*/
private:
Node* _head; //带头结点的双向循环链表
};
}
在以上两个基本的框架搭建完毕以后,接下来重点主要是正向与反向迭代器
三、list的正向迭代器设计
1.实现原理
list的确是支持了迭代器,但是list不在能够像vector一样以普通的指针作为迭代器,因为其节点不保证在存储空间中连续。list的迭代器必须有能力指向list的节点,并有能力进行递增、递减、取值、成员存储等操作。如何具备这样的能力呢?那就是递增时指向下一个结点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取用的是节点的成员;
2. 正向迭代器的结构
/*iterator.h*/
namespace mlg
{
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
//迭代器构造函数
__list_iterator(Node* x)
:_node(x)
{}
//重载*号 —— 实现解引用操作
Ref operator*()
{
return _node->_data;
}
//重载->操作符 —— 实现指针访问元素
Ptr operator->()
{
return &_node->_data;
}
//++it 重载前置++ —— 让链表能够像数组一样去++操作,访问元素
self& operator++()
{
_node = _node->_next;//前置++返回的是++之后的值,直接让当前位置的结点指向下一个节点
return *this;
}
//it++ 重载后置++ —— (这里需要加上int作为一个站位符,与前置++区分)
self operator++(int)
{
self tmp(*this);
_node = _node->_next;//后置++返回的是++之前的值,需要保存当前节点,再指向下一个节点
return tmp;
}
//--it 重载前置-- —— 让链表能够像数组一样去--操作,访问元素
self& operator--()
{
_node = _node->_prev;//前置--返回的是--之后的值,直接让当前位置的结点指向前一个节点
return *this;
}
//it-- 重载后置-- ——(这里需要加上int作为一个站位符,与前置--区分)
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)const
{
return _node != it._node;
}
};
}
在上述代码中,有三个地方没有做解释:
1. template<class T,class Ref,class Ptr>
2. typedef __list_iterator<T, Ref, Ref> self;
它是迭代器类模板,里面包含了三个参数:T、Ref、Ptr;这三个参数表明传给iterator类的类型是不确定的,需要根据实际的情况,将这三个参数匹配到相应的地方。
如:
1. ++/--操作:返回的是一个对象,只要用到了对象,一般都是写成__list_iterator<T, Ref, Ref>,因为类名太长,就有了第2点的typedef __list_iterator<T, Ref, Ref> self;
2. operator* 操作:返回的是对象的数据的值,并且*号是具有读写操作的,我们应该返回的是这个数据类型的引用(T&);
3. operator->操作:返回的是对象的数据的地址,我们应该返回的是这个数据类型的地址(T*);
--------------------------------------------------------------------------------------------------------------------------
3. typedef ListNode<T> Node;
Node* _node;iterator类中的成员变量也是节点,我们刚刚已经解释了list迭代器的原理,它是通过重载++ / --,其内部实现上是节点中的_prev指针(找当前节点的前一个位置,相当于--操作)和_next指针(找当前节点的下一个位置,相当于++操作)的操作来实现的;
所以,list正向迭代器的实现,本质上是用节点的两个指针的操作来代替了传统的++/--操作。再简单点说,其实就是函数调用(包括*/->)
四、list反向迭代器的设计
1.实现原理
我们刚刚对list正向迭代器做了介绍以后,你是否会想,反向迭代器也是同样的原理呢?确实可以这样理解,但是中间还有一个过程,我们先通下面的图解了解一下双链表(list)中和数组(vector)中正向迭代器与反向迭代器的区别。
vector迭代器的位置不需要多说,对于list的迭代器:begin( )是数据的起始位置,end( )是数据的结束位置,也就是头结点;反向迭代器不就是与之相反嘛。
那如何才能通过反向迭代器控制链表的++/--等一系列操作呢?
方法一:我们可以重新写一个,也通过节点的指针去控制(比较麻烦:如果我想用正向迭代器区获取某个节点的位置传个反向迭代器,就需要给反向迭代器增设正向迭代器的接口)
方法二:反向迭代器通过正向迭代器去间接控制list节点,达到想要的效果(在SLT原码中是这样子实现的,其目的是能适应其他容器,其称之为迭代器适配器)
2.反向迭代器的结构
/*reverse_iterator.h*/
namespace mlg
{
template<class Iterator,class Ref,class Ptr>
class __list_reverse_iterator
{
typedef __list_reverse_iterator<Iterator, Ref, Ptr> self;
public:
//反向迭代器构造函数(通过正向迭代器去构出造反向迭代器)
__list_reverse_iterator(Iterator it)
:_it(it)
{}
//重载*
Ref operator*()
{
Iterator prev = _it; //获取正向迭代器end()的位置
return *--prev; //返回的是当前位置执行前置--操作后的解引用
}
//重载->
Ptr operator->()
{
return &operator*(); //调用上面一个重载函数
}
//++it
self& operator++()
{
--_it; //反向迭代器的前置++就是调用正向迭代器的前置--
return *this; //
}
//it++
self operator++(int)
{
self tmp(*this); //后置++返回的是++之前的,所有先记录当前正向迭代器的位置
_it--; //反向迭代器的后置++就是调用正向迭代器的后置--
return tmp;
}
//--it
self& operator--()
{
++_it; //反向迭代器的前置--就是调用正向迭代器的前置++
return *this;
}
//it--
self operator--(int)
{
self tmp(*this); //后置--返回的是--之前的,所有先记录当前正向迭代器的位置
_it++; //反向迭代器的后置--就是调用正向迭代器的后置++
return tmp;
}
//重载!=
bool operator!=(const self& rit)const
{
return _it != rit._it;
}
//重载==
bool operator==(const self& rit)const
{
return _it == rit._it;
}
private:
Iterator _it;//成员变量是一个正向迭代器
};
}
1.反向迭代器的 ++ / -- 操作解析
//++it
self& operator++()
{
--_it; //调用正向迭代器的前置--
return *this;
}
//it++
self operator++(int)
{
self tmp(*this);
_it--; //调用正向迭代器的后置--
return tmp;
}
通过上图可以发现:
1.反向迭代器++(前置/后置):调用正向迭代器的--
2.反向迭代器--(前置/后置):调用正向迭代器的++
2.反向迭代器的 * / -> 操作解析
//重载*
Ref operator*()
{
Iterator prev = _it; //获取正向迭代器end()的位置
return *--prev; //返回的是当前位置执行前置--操作后的解引用
}
//重载->
Ptr operator->()
{
return &operator*(); //调用上面一个重载函数
}
对于->的重载,只要去复用就可以了。
五、list结构的完善
上面我们对节点结构、正向与反向迭代器结构实现原理及注意点一一做了介绍,最后一步也是最重要的一步,那就是将list结构完善起来,实现list的功能。
1.构造函数
list的成员变量是一个节点类,在构造头节点时,需要将这单个头节点构造成一个双向循环链表;
//构造函数
list()
{
_head = new Node; //new一个节点出来
_head->_prev = _head;
_head->_next = _head; //_prev 和 _next 同时指向了头结点,形成了双向循链表
}
2.拷贝构造
拷贝构造是用一个已有对象去构造出另一个对象,首先将待构造对象进行初始化,然后利用迭代器区间去构造一个和lt1一样的临时的tmp对象,再进行数据的交换,达到深拷贝的目的。
//拷贝构造 --- 现代写法 lt2(lt1)
list(const list<T>& lt)
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
list<T> tmp(lt.begin(), lt.end());
std::swap(_head, tmp._head);
}
3.迭代器区间构造
通过传过来的迭代器区间进行初始化
//迭代器区间构造
template<class IputIterator>
list(IputIterator first, IputIterator last)
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
while (first != last)
{
push_back(*first);//尾插数据,会根据不同类型的迭代器进行调用
++first;
}
}
4.用n个val构造
通过用n个val来对对象进行初始化,需要注意这里的T( )是一个匿名对象,作为val的缺省参数,因为我们并不知道传给val的是一个对象还是一个整形(或其他),给缺省参数的好处在于,对于自定义类型编译器会去调用自定义类型的构造函数来对val进行初始化,如果是内置类型,它也是有自己的构造函数
//我们常常对内置类型的定义是这样的 int i = 10; //但其实也可以这样定义 int i = int(10);
//用n个val个构造
list(int n, const T& val = T())
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
对于用n个val进行初始化和迭代器区间初始化,起初我是这样写的(为了和库一致) ,测试时却出现问题:非法的间接寻址
//迭代器区间初始化
template<class IputIterator>
list(IputIterator first, IputIterator last){...}
//用n个val初始化
list(size_t n, const T& val = T()) {...}
//测试日期类
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
};
void test_list4()
{
list<Date> lt1(5, Date(2022, 6, 21));//1
for (auto e : lt1)
{
cout << e._year << "/" << e._month << "/" << e._day << endl;
}
cout << endl;
list<int> lt2(5, 1);//2
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
}
5.赋值重载
//赋值重载 --- 现代写法 lt2 = lt1
list<T>& operator=(list<T> lt)
{
std::swap(_head, lt._head);
return *this;
}
6.析构函数
析构函数可以结合着erase函数看
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
//写法1
erase(it++); //it是一个正向迭代器,采用后置++传给erase
//写法2
iterator del = it++;
delete del._node;
//写法3
iterator del = it++;
erase(del);
}
_head->_prev = _head;
_head->_next = _head;
}
7.迭代器的实现
在list类中,我们typedef了正向与反向迭代器,解释一下其目的及作用
//正向迭代器
typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator;
//反向迭代器
typedef __list_reverse_iterator<iterator, T&, T*> reverse_iterator; typedef __list_reverse_iterator<iterator, const T&, const T*>const_reverse_iterator;
list是一个类模板,参数列表是T类型(未知类型)正向与反向迭代器分别都有三个参数,在list内重命名相当于内嵌定义,显示传参,我们对迭代器中需要返回值的地方不能进行类型的准确判断,通过这种显示传参,确定了迭代器三个参数的基本对于类型。
对于正向迭代器,我们显示传参<T、T&、T*>如果是const迭代器,再重新typedef一个const迭代器;
对于反向迭代器,我们是需要正向迭代器去构造,所有显示传参就可以给到<iterator, T&, T*>
namespace mlg
{
template<class T>
class list
{
typedef ListNode<T> Node;
public:
//正向迭代器
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
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);//返回头节点
}
//反向迭代器
typedef __list_reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef __list_reverse_iterator<iterator, const T&, const T*>const_reverse_iterator;
reverse_iterator rbegin()
{
return reverse_iterator(end()); //返回正向迭代器的结束位置
//return reverse_iterator(_head); //等价与正向迭代器的头节点
}
reverse_iterator rend()
{
return reverse_iterator(begin()); //返回正向迭代器的起始位置
//return reverse_iterator(_head->_next); //等价于正向迭代器头节点的下一个结点
}
const_reverse_iterator rbegin()const
{
return const_reverse_iterator(end()); //返回正向迭代器的结束位置
//return const_reverse_iterator(_head); //等价与正向迭代器的头节点
}
const_reverse_iterator rend()const
{
return const_reverse_iterator(begin()); //返回正向迭代器的起始位置
//return const_reverse_iterator(_head->_next); //等价于正向迭代器头节点的下一个结点
}
};
}
8.增删查改
//头插
void push_front(const T& x)
{
insert(begin(), x);
}
//尾插
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 pop_front()
{
erase(begin());
}
//尾删
void pop_back()
{
erase(--end());
}
//在pos位置插入元素x
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);//在pos位置插入返回的是新插入的节点位置
}
//在pos位置删除元素
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);//返回的是删除pos位置节点的下一个节点
}
六、完整代码
1.ListNode.h
#pragma once
namespace mlg
{
template<class T>
struct ListNode
{
ListNode<T>* _prev;
ListNode<T>* _next;
T _data;
ListNode(const T& data= T())
:_prev(nullptr)
,_next(nullptr)
,_data(data)
{}
};
}
2.iterator.h
#pragma once
namespace mlg
{
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ref reference;
typedef Ptr pointer;
Node* _node;
__list_iterator(Node* x)
:_node(x)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
//++it
self& operator++()
{
_node = _node->_next;
return *this;
}
//it++
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
//--it
self& operator--()
{
_node = _node->_prev;
return *this;
}
//it--
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)const
{
return _node != it._node;
}
};
}
3.reverse_iterator.h
#pragma once
namespace mlg
{
template<class Iterator,class Ref,class Ptr>
class __list_reverse_iterator
{
typedef __list_reverse_iterator<Iterator, Ref, Ptr> self;
public:
__list_reverse_iterator(Iterator it)
:_it(it)
{}
Ref operator*()
{
Iterator prev = _it;
return *--prev;
}
Ptr operator->() {return &operator*();}
//++it
self& operator++()
{
--_it;
return *this;
}
//it++
self operator++(int)
{
self tmp(*this);
_it--;
return tmp;
}
//--it
self& operator--()
{
++_it;
return *this;
}
//it--
self operator--(int)
{
self tmp(*this);
_it++;
return tmp;
}
bool operator!=(const self& rit)const {return _it != rit._it;}
bool operator==(const self& rit)const {return _it == rit._it;}
private:
Iterator _it;
};
}
4.list.h
#pragma once
#include "ListNode.h"
#include "iterator.h"
#include "reverse_iterator.h"
namespace mlg
{
template<class T>
class list
{
typedef ListNode<T> Node;
public:
//正向迭代器
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
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);}
//反向迭代器
typedef __list_reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef __list_reverse_iterator<iterator, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin() {return reverse_iterator(end());}
reverse_iterator rend() {return reverse_iterator(begin());}
const_reverse_iterator rbegin()const {return const_reverse_iterator(end());}
const_reverse_iterator rend()const {return const_reverse_iterator(begin());}
//构造函数
list()
{
_head = new Node; //new一个节点出来
_head->_prev = _head;
_head->_next = _head; //_prev 和 _next 同时指向了头结点,形成了双向循链表
}
//用n个val个构造
list(int n, const T& val = T())
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//迭代器区间构造
template<class IputIterator>
list(IputIterator first, IputIterator last)
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造 --- 现代写法 lt2(lt1)
list(const list<T>& lt)
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
list<T> tmp(lt.begin(), lt.end());
std::swap(_head, tmp._head);
}
//赋值重载 --- 现代写法 lt2 = lt1
list<T>& operator=(list<T> lt)
{
std::swap(_head, lt._head);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
_head->_prev = _head;
_head->_next = _head;
}
void push_front(const T& x){ insert(begin(), x); }
void push_back(const T& x) { insert(end(), x);}
void pop_front() {erase(begin());}
void pop_back(){erase(--end());}
//在pos位置插入元素x
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
//在pos位置删除元素
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
private:
Node* _head;
};
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
}
以上是对list模拟实现的全部结束,大家可以在.c文件下进行测试。如果存在任何问题,或者有不同的见解,大家可以直接在评论区提出来