序列式容器list模拟实现
一. list的简单介绍
list是一个双向带头循环链表,是一种插入和删除都很高效的数据结构
二. list对应的节点类
作为一个链表节点,应该有三个成员:数据、前驱节点指针、后继节点指针,
并提供一个构造函数
代码:
//链表节点
template<class T>
struct listNode
{
T _val;
listNode* _prev;
listNode* _next;
listNode(const T& val = T())
:_val(val)
, _prev(nullptr)
, _next(nullptr)
{}
};
三. list对应的迭代器类
list的迭代器不能通过原生指针进行操作,因为每个list节点的存储是一种离散的结构,进行指针的++和–操作,移动到的位置有可能不是下一个list节点对应的位置。
所以需要封装一个迭代器类,这个迭代器类中有一个list节点指针成员,并重载++、- - 、!= 、* 、->这些运算符,让迭代器对象模拟指针的行为。
为了实现const迭代器,所以在模板参数中加了一个引用和一个指针,因为const迭代器的操作和非const迭代器的不同仅仅在于 解引用*和箭头运算符->重载的返回值是const与非const. 这样就可以通过构造迭代器对象时传入的模板参数的不同来实现const迭代器和非const迭代器
- 迭代器的 ++ 和 - - 操作都是通过节点指针的指向改变来实现的;
- 迭代器的!=是通过比较两个迭代器对象中的节点指针成员是不是同一个来实现的;
- 迭代器的*是通过返回节点指针成员指向的节点的实际存储数据的引用来实现的,解引用必须返回引用,因为解引用操作需要支持重新赋值的应用场景;
- 迭代器的->是通过返回节点指针成员指向的节点的实际存储数据的指针来实现的;
代码:
//list迭代器:自定义的迭代器类,双向迭代器
//T:数据类型,Ref:引用类型, Ptr:指针类型
//如果Ref是T&,Ptr是T*,则是非const迭代器
//如果Ref是const T&,Ptr是const T*,则是const迭代器
template<class T, class Ref, class Ptr>
struct listIterator
{
typedef listNode<T> Node;
typedef listIterator<T, Ref, Ptr> Self;
Node* _node; //迭代器内部封装一个节点指针
//通过一个节点指针构造一个迭代器对象
listIterator(Node* node)
:_node(node)
{}
//迭代器的前置++
Self& operator++()
{
_node = _node->_next;
return (*this);
}
//迭代器的后置++,后置运算必须返回值,不能返回引用
Self operator++(int)
{
Self cur(_node);
_node = _node->_next;
return cur;
}
//迭代器的前置--
Self& operator--()
{
_node = _node->_prev;
return (*this);
}
//迭代器的后置--
Self operator--(int)
{
Self cur(_node);
_node = _node->_prev;
return cur;
}
//解引用,拿到迭代器所指位置的实际存储的数据
//返回值必须是引用
Ref operator*()
{
return _node->_val;
}
//重定义->运算符,让迭代器对象可以实现间接访问数据成员的操作
//返回一个指针
Ptr operator->()
{
return &(_node->_val);
}
bool operator==(const Self& it)
{
return _node == it._node;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
四. list实现
list中只有一个节点指针成员_head,这个指针指向双向链表的头节点,这里的头节点不存储实际的数据,只是为了方便定位链表的头部和尾部,这个头节点暂且称它为伪头节点,它的下一个位置是第一个实际节点,它的前一个位置是最后一个节点
1. 迭代器接口的实现
提供一个begin和end接口,分别提供const迭代器和非const迭代器两个版本
begin返回的是链表中的第一个节点的指针,也就是_head的下一个节点对应的迭代器
end返回的是链表中最后一个节点的下一个节点的指向,也就是一个指向_head的迭代器
非const迭代器在模板参数中传入的是引用和指针,而const迭代器在模板参数中传入的是const引用和const指针
typedef listIterator<T, T&, T*> iterator;
typedef listIterator<T, const T&, const T*> const_iterator;
2. 基本操作的实现
尾插
void push_back(const T& val);
创建一个新节点,通过_head找到尾节点,将新节点链接到尾节点的下一个位置,具体链接过程是:让新节点的前驱指针指向尾节点,后继指针指向_head,让尾节点的后继指针指向新节点,让_head的前驱指针指向新节点
头插
void push_front(const T& val);
创建一个新节点,通过_head找到第一个节点firstNode,将新节点链接到firstNode的前一个位置,具体链接过程是: _head的后继指针指向新节点,新节点的前驱指针指向_head,新节点的后继指针指向firstNode,firstNode的前驱指针指向新节点
尾删
void pop_back();
- 首先判断链表中是否只有一个伪头节点,如果是返回,因为list必须存储一个这个伪头节点进行定位
- 通过_head找到尾节点,再通过尾节点找到尾节点的前一个节点tail_prev,释放尾节点空间,将tail_prev和_head重新链接起来,具体链接过程是:tail_prev的后继指针指向_head,_head的前驱指针指向tail_prev
头删
void pop_front();
- 首先判断链表中是否只有一个伪头节点,如果是返回,因为list必须存储一个这个伪头节点进行定位
- 通过_head找到第一个节点,再通过第一个节点firstNode找到第二个节点secondNode,释放第一个节点的空间,将_head和secondNode连接起来,具体链接过程是:_head的后继指针指向secondNode,secondNode的前驱指针指向_head
插入
//插入:在pos位置之前插入节点,返回一个指向新插入节点的迭代器
iterator insert(iterator pos, const T& val);
这个接口的功能是在一个迭代器指向的位置之前插入新元素,返回值是返回一个指向新插入节点的迭代器
首先找到pos指向位置的节点curNode,通过curNode找到它的前一个节点prev;
创建一个新节点,将新节点链接到prev的下一个位置,curNode的前一个位置,具体链接过程是:prev的后继指针指向新节点,新节点的前驱指针指向prev,新节点的后继指针指向curNode,curNode的前驱指针指向新节点
删除
//删除:返回删除后的下一个位置
iterator erase(iterator pos)
这个接口的功能是删除pos指向的节点,并返回一个指向被删除节点的下一个节点的迭代器
- 首先判断链表中是否只有一个伪头节点,如果是返回,因为list必须存储一个这个伪头节点进行定位
- 通过pos找到指向的节点curNode,通过curNode找到它的前一个节点prev和后一个节点next,释放curNode的空间,将prev和next链接起来,具体过程是:prev的后继指针指向next,next的前驱指针指向prev
3. 成员函数的实现
构造函数
list();
为伪头节点申请空间,并让它的前驱指针和后继指针都指向自身
拷贝构造
list(const list<T>& lst)
- 为伪头节点申请空间,并让它的前驱指针和后继指针都指向自身
- 将lst中的节点数据从头到尾进行尾插;
赋值运算符重载
//赋值运算符重载
list<int>& operator=(const list<T>& lst)
- 清理自身成员,释放所有节点只保留一个伪头节点
- 将lst中的节点数据从头到尾进行尾插;
析构函数
~list();
- 从头到尾释放节点;
- 释放伪头节点;
4. 模拟实现源码
#pragma once
namespace cxp
{
//链表节点
template<class T>
struct listNode
{
T _val;
listNode* _prev;
listNode* _next;
listNode(const T& val = T())
:_val(val)
, _prev(nullptr)
, _next(nullptr)
{}
};
//list迭代器:自定义的迭代器类,双向迭代器
//T:数据类型,Ref:引用类型, Ptr:指针类型
template<class T, class Ref, class Ptr>
struct listIterator
{
typedef listNode<T> Node;
typedef listIterator<T, Ref, Ptr> Self;
Node* _node; //迭代器内部封装一个节点指针
//通过一个节点指针构造一个迭代器对象
listIterator(Node* node)
:_node(node)
{}
//迭代器的前置++
Self& operator++()
{
_node = _node->_next;
return (*this);
}
//迭代器的后置++,后置运算必须返回值,不能返回引用
Self operator++(int)
{
Self cur(_node);
_node = _node->_next;
return cur;
}
//迭代器的前置--
Self& operator--()
{
_node = _node->_prev;
return (*this);
}
//迭代器的后置--
Self operator--(int)
{
Self cur(_node);
_node = _node->_prev;
return cur;
}
//解引用,拿到迭代器所指位置的实际存储的数据
//返回值必须是引用
Ref operator*() const
{
return _node->_val;
}
//重定义->运算符,让迭代器对象可以实现间接访问数据成员的操作
//返回一个指针
Ptr operator->() const
{
return &(_node->_val);
}
bool operator==(const Self& it)
{
return _node == it._node;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
//list:双向带头循环链表
template<class T>
class list
{
typedef listNode<T> Node;
private:
Node* _head; //一个伪头节点,主要是为了快速定位实际的头部节点和尾部节点
public:
typedef listIterator<T, T&, T*> iterator;
typedef listIterator<T, const T&, const T*> const_iterator;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//成员函数模块
//构造函数
list()
{
_head = new Node;
_head->_prev = _head->_next = _head;
}
//拷贝构造函数
list(const list<T>& lst)
:_head(new Node)
{
_head->_prev = _head->_next = _head;
const_iterator it = lst.begin();
while (it != lst.end())
{
push_back(*it);
++it;
}
}
//赋值运算符重载
list<int>& operator=(const list<T>& lst)
{
if (this != &lst)
{
//释放所有节点除了伪头节点
clear();
//插入节点
const_iterator it = lst.begin();
while (it != lst.end())
{
push_back(*it);
++it;
}
}
return *this;
}
//析构函数
~list()
{
//释放所有节点
clear();
//删除伪头节点
delete _head;
_head = nullptr;
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//成员变更模块
//头插
void push_front(const T& val)
{
Node* newNode = new Node(val);
Node* firstNode = _head->_next; //头节点的下一个节点,也就是实际的第一个节点
//链接
newNode->_prev = _head;
_head->_next = newNode;
newNode->_next = firstNode;
firstNode->_prev = newNode;
}
//尾插
void push_back(const T& val)
{
Node* newNode = new Node(val);
Node* tailNode = _head->_prev; //尾节点
//链接
tailNode->_next = newNode;
newNode->_prev = tailNode;
newNode->_next = _head;
_head->_prev = newNode;
}
//头删
void pop_front()
{
//只剩下一个伪头节点,不能进行删除
if (_head->_prev == _head && _head->_next == _head)
{
return;
}
Node* firstNode = _head->_next;
Node* secondNode = firstNode->_next;
delete firstNode;
_head->_next = secondNode;
secondNode->_prev = _head;
}
//尾删
void pop_back()
{
//只剩下一个伪头节点,不能进行删除
if (_head->_prev == _head && _head->_next == _head)
{
return;
}
Node* tailNode = _head->_prev; //尾节点
Node* tail_prev = tailNode->_prev; //尾节点的前一个节点
delete tailNode;
tail_prev->_next = _head;
_head->_prev = tail_prev;
}
//插入:在pos位置之前插入节点,返回一个指向新插入节点的迭代器
iterator insert(iterator pos, const T& val)
{
Node* newNode = new Node(val);
Node* curNode = pos._node;
Node* prevNode = curNode->_prev;
prevNode->_next = newNode;
newNode->_prev = prevNode;
newNode->_next = curNode;
curNode->_prev = newNode;
return iterator(newNode);
}
//删除:返回删除后的下一个位置
iterator erase(iterator pos)
{
//只剩下一个伪头节点,不能进行删除
if (pos._node == _head)
{
return pos;
}
Node* curNode = pos._node;
Node* prevNode = curNode->_prev;
Node* nextNode = curNode->_next;
delete curNode;
prevNode->_next = nextNode;
nextNode->_prev = prevNode;
return iterator(nextNode);
}
//删除所有节点只保留伪头节点
void clear()
{
Node* curNode = _head->_next;
//清空有效节点
while (curNode != _head)
{
Node* nextNode = curNode->_next;
delete curNode;
curNode = nextNode;
}
//恢复循环结构
_head->_prev = _head->_next = _head;
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//迭代器模块
//返回一个指向链表中第一个节点的迭代器
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);
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//容量模块
//有效元素个数
size_t size() const
{
const_iterator it = begin();
size_t sz = 0;
while (it != end())
{
++it;
++sz;
}
return sz;
}
//判空
bool empty() const
{
return begin() == end();
}
};
}