目录
一.单个节点类实现
二.默认成员函数
三.容量相关函数
四.头尾访问函数
五.修改删除函数
六.其它函数
七.迭代器
1.迭代器的成员函数
2.关于->重载
八.反向迭代器
1.反向迭代器
模拟实现了vector之后,现在来试试难度更高的list,list是一种双向循环带头链表,由一个个节点组成,每一个节点都有一个指针指向前一个和后一个节点,相比于vector和string的迭代器是原生指针,list的迭代器实现起来更有难度,在此基础上还要写反向迭代器。
以下是list的大体框架和成员变量。
template
class list
{
typedef ListNode Node;//重命名节点类为Node,方便书写和观看
typedef Node* PNode;//重命名Node的指针为PNode
public:
typedef ListIterator iterator;//迭代器
typedef ListIterator const_iterator;//const迭代器
typedef _reverse_iterator::Reverse_iterator reverse_iterator;//反向迭代器
public:
//成员函数
private:
PNode _pHead;//头结点
size_t _size;//记录当前链表有几个节点(不包含头结点)
};
list的一个节点不仅存放了数据,还有两个指针指向前后节点,所以需要一个类来封装,为了访问方便就用struct写。(相比于class,struct的成员都是公开的)
template//模板参数
struct ListNode
{
ListNode(const T& val = T())//默认构造函数初始化
:_val(val)
, _pPre(nullptr)
, _pNext(nullptr)
{}
ListNode* _pPre;//指向前一个节点的指针
ListNode* _pNext;//指向后一个节点的指针
T _val;//存放的数据,可以是自定义类型
};
默认成员函数和vector相同,实现的方法也大同小异。
void CreateHead()//初始化头结点
{
_pHead = new Node;//new一个头结点
//因为是双向循环,所以两个指针都指向自己
_pHead->_pNext = _pHead;
_pHead->_pPre = _pHead;
}
list()//默认成员函数
{
CreateHead();//初始化头结点的函数
}
list(int n, const T& value = T())//构造元素个数为n的容器,元素为value
{
CreateHead();//初始化头结点的函数
while (n--)
{
push_back(value);//尾插元素
}
}
template
list(Iterator first, Iterator last)//通过迭代器区间构造
{
CreateHead();//初始化头节点函数
while (first != last)
{
push_back(*first);//尾插元素
first++;
}
}
list(const list& l)//拷贝构造函数
{
CreateHead();//初始化头节点函数
for (auto node : l)
{
push_back(node);//尾插元素
}
}
list& operator=(const list l)
{
if (_pHead != l._pHead)
{
for (auto node : l)
{
push_back(node);//和拷贝构造函数一样的写法
}
}
return *this;
}
~list()//析构函数
{
clear();//清理除头结点外所有节点
delete _pHead;//清理头结点
}
size_t size() const//返回当前节点个数(不包含头结点)
{
return _size;
}
bool empty() const//判断当前链表节点数是不是0(不包含头结点)
{
return _size == 0;
}
需要注意判断(断言)是不是只有头结点一个指针。
T& front()//返回第一个节点的值(不是头结点)
{
assert(_pHead->_pNext != _pHead);//断言是不是没有节点
return _pHead->_pNext->_val;
}
const T& front() const//返回第一个节点的值(不是头结点)
{
assert(_pHead->_pNext != _pHead);//断言是不是没有节点
return _pHead->_pNext->_val;
}
T& back()//返回最后一个节点的值
{
assert(_pHead->_pPre != _pHead);//断言是不是没有节点
return _pHead->_pPre->_val;
}
const T& back() const//返回最后一个节点的值
{
assert(_pHead->_pPre != _pHead);//断言是不是没有节点
return _pHead->_pPre->_val;
}
需要注意的是insert可以不记录前后节点并修改4个指针,但需要注意先后修改顺序。
void push_back(const T& val)//尾插
{
insert(end(), val);//复用insert
}
void pop_back()//尾删
{
erase(--end());//复用erase
}
void push_front(const T& val)//头插
{
insert(begin(), val);//复用insert
}
void pop_front()//头删
{
erase(begin());//复用erase
}
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
_size++;//节点数+1
Node* prev = pos._pNode->_pPre;//记录pos位置前的节点
Node* cur = new Node(val);//创建一个值为val的节点
//修改4个指针(cur的两个,prev的next,pos的prev)
cur->_pNext = pos._pNode;
cur->_pPre = prev;
prev->_pNext = cur;
pos._pNode->_pPre = cur;
return cur;//返回插入节点
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
if (pos._pNode != _pHead)//判断pos是不是头结点
{
_size--;//节点数-1
Node* prev = pos._pNode->_pPre;//记录前节点
Node* next = pos._pNode->_pNext;//记录后节点
delete pos._pNode;//删除pos位置的节点
//修改2个指针(prev的next,next的prev)
prev->_pNext = next;
next->_pPre = prev;
return next;//返回删除节点的下一个节点
}
else
return _pHead;//是头结点就返回头结点
}
void clear()//清理所有节点(不包括头节点)
{
_size = 0;//直接令节点数量为0
Node* cur = _pHead->_pNext;//记录头结点后第一个节点,cur就是准备删除的节点
Node* next = cur->_pNext;//记录cur后的第一个节点
while (cur != _pHead)//如果cur不等于头结点就进循环
{
delete cur;//直接删除cur
cur = next;//让cur等于next节点
next = next->_pNext;//next赋值为下一个节点
}
//然后修改头结点的两个指针
_pHead->_pNext = _pHead;
_pHead->_pPre = _pHead;
}
void swap(list& l)//交换两个list的成员变量
{
std::swap(_pHead, l._pHead);
std::swap(_size, l._size);
}
迭代器也是一个类,里面的成员变量是一个节点的指针。以下是大体框架。
template//三个模板参数,一个是T,一个是T&,一个是T*,这些在list typedef的时候已经写好了,在面对const时也能接收
class ListIterator
{
typedef ListNode* PNode;//重命名节点的指针为PNode
typedef ListIterator Self;//重命名迭代器本身为Self
public:
//成员函数实现
PNode _pNode;//存放当前节点的指针
};
需要注意的是->的重载,下面会有更详细的说明。
ListIterator(PNode pNode = nullptr)//根据传入的节点的指针构造迭代器的默认构造函数
:_pNode(pNode)
{}
ListIterator(const Self& l)//根据传入的迭代器构造迭代器的构造函数
:_pNode(l._pNode)
{}
//因为节点本身是一个类,所以需要重载运算符
Ref operator*()//解引用就是去类里面直接找到值,返回的是引用
{
return _pNode->_val;
}
Ptr operator->()//->就是先返回值的地址,编译器还会自动多加一个->来访问成员
{
return &(_pNode->_val);
}
Self& operator++()//前置++,就是让迭代器走到后面那个节点
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int)//后置++,返回的是临时创建的迭代器
{
PNode tmp = _pNode;
++(*this);
return tmp;
}
Self& operator--()//前置--,就是让迭代器走到前面那个节点
{
_pNode = _pNode->_pPre;
return *this;
}
Self operator--(int)//后置--,返回的是临时创建的迭代器
{
PNode tmp = _pNode;
--(*this);
return tmp;
}
bool operator!=(const Self& l)//!=就是判断两个迭代器的成员_pNode相不相等
{
return _pNode != l._pNode;
}
bool operator==(const Self& l)//==就是判断两个迭代器的成员_pNode相不相等
{
return _pNode == l._pNode;
}
下面的代码能更详细的说明编译器省略了一个->,实际上是有两个的。
class A
{
public:
int a = 1;
int b = 2;
};
int main()
{
_list::list l;
l.push_back(A());
_list::list::iterator it = l.begin();
//这里隐藏了一个->实际上有两个,一个->是返回链表中存放类A对象的地址,第二个隐藏的->是访问成员变量的
cout << it->a << endl;
cout << it->b;
return 0;
}
反向迭代器实际上就是复用正向迭代器及其接口,除此之外的区别就是反向迭代器的begin是头结点,end是头结点的下一个节点,所以在解引用的时候需要先让迭代器往前走一步再复用正向迭代器的解引用,->也是同理。
反向迭代器的用法就像正向迭代器一样,从begin位置开始往后++直到end位置,但输出的结果却和正向迭代器的输出完全相反。
以下是反向迭代器的大体框架。
template
struct Reverse_iterator
{
typedef Reverse_iterator self;//重命名反向迭代器
//成员函数定义
Iterator _it;//反向迭代器类的成员变量就是一个正向迭代器
};
Reverse_iterator(const Iterator& it)//通过正向迭代器构造反向迭代器
:_it(it)
{}
Ref operator*()//解引用重载
{
self tmp = *this;//创建临时变量反向迭代器
--tmp._it;//对临时的反向迭代器进行--,往前走一步
return *(tmp._it);//然后再解引用,这里直接复用了正向迭代器的解引用
}
Ptr operator->()//->重载
{
self tmp = *this;//创建临时变量反向迭代器
--tmp._it;//对临时的反向迭代器进行--,往前走一步
return &*(tmp._it);//先解引用,然后再返回解引用的元素的地址
}
//反向迭代器的++就是正向迭代器的--,其他写法均合正向迭代器相同
self& operator++()
{
--_it;
return *this;
}
self operator++(int)
{
self tmp = *this;//后置就是返回临时变量
--_it;
return tmp;
}
self& operator--()
{
++_it;
return*this;
}
self operator--(int)//后置就是返回临时变量
{
self tmp = *this;
++_it;
return tmp;
}
//比较运算符的重载也是复用正向迭代器的比较运算符
bool operator!=(const self& rit)
{
return _it != rit._it;
}
bool operator==(const self& rit)
{
return _it == rit._it;
}
以上就是list及其迭代器的实现了,比较复杂,不过写完后写代码的能力都升华了,所以还是值得练习的。