一、List容器的节点类
为了避免与标准库里面的List容器出现混淆,我们将我们模拟实现的List容器封装在my_list命名空间中,这样就避免了混淆问题。
我们知道list是基于带头双向循环链表的一个数据结构容器,那么既然是个链表的结构那么就一定会有节点类,下面我们来看下list容器的节点类的模拟实现:
// List的节点类
template<class T>
struct ListNode
{
//节点类的构造函数
ListNode(const T& val = T())
:_prev(nullptr)
, _next(nullptr)
, _val(val)
{}
ListNode<T>* _prev;
ListNode<T>* _next;
T _val;
};
这里的节点类我们是通过类模版实现的,在节点类的构造函数中,考虑到我们的List容器存储的元素也可能是自定义类型,我们在构造函数的缺省值中给一个匿名对象,这样的话当我们的数据是一个自定义类型的话会自动调用它们对应类型的构造函数,如果是内置类型的话编译器也会自动处理。写了构造函数之后我们在节点类中定义出节点的前驱节点以及后继节点,以及节点中用来储存数据的成员变量。这个节点类中的元素在后面我们需要进行访问,因此我们将节点类放成完全公有的,也就是struct的类,也可以使用class里面使用public访问限定符。
二、LIst容器中的迭代器类
节点类实现后我们来实现用于遍历list容器的iterator迭代器,在这里我们只模拟实现正向迭代器。
我们先来中的看下我们的迭代器类:
//List的迭代器类
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
//迭代器的构造函数
ListIterator(Node* node = nullptr)
:_node(node)
{}
ListIterator(const Self& l)
:_node(l._node)
{}
T& operator*()
{
return _node->_val;
}
T* operator->()
{
return &_node->_val;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const Self& l)
{
return _node != l._node;
}
bool operator==(const Self& l)
{
return _node == l._node;
}
};
2.1 迭代器类的成员变量
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
我们实现的迭代器类就是封装了一个节点指针,我们先将节点类的类型名给重命名为Node,然后将我们的迭代器类自己的名称给重命名成Self,模版中类型名T是数据类型,Ref是引用类型,Ptr是指针类型。
2.2 迭代器类的构造函数
2.2.1 构造函数
//迭代器的构造函数
ListIterator(Node* node = nullptr)
:_node(node)
{}
对于迭代器类的构造函数我们直接给了一个带缺省值的构造函数,缺省值为nullptr。
2.2.2 拷贝构造函数
//拷贝构造函数
ListIterator(const Self& l)
:_node(l._node)
{}
拷贝构造函数我们直接使用l的节点指针来初始化我们的节点指针。
2.3 迭代器类中的运算符重载
2.3.1 operator*
T& operator*()
{
return _node->_val;
}
对于重载我们的解引用操作符,我们首先要知道解引用的作用是什么,是为了获取我们此节点中存储的数据,因此我们只需要返回我们的存储的元素的引用就可以了,当返回引用类型是可读可写的那么我们就可以通过解引用对节点中数据进行修改,如果返回的是const修饰的引用那么我们就不能通过解引用来修改节点中的值。
2.3.2 operator->
T* operator->()
{
return &_node->_val;
}
我们一般使用->这个符号都是使用结构体指针或者类指针来访问结构体或者类中的元素,那么这个返回值就应该是一个指针,也就是返回我们节点中存储的元素的地址,当然当模版实例化时返回类型是可读可写的话我们就可以对节点中的元素进行修改,如果是const修饰的话我们就不能对节点中的元素进行修改。
2.3.3 operator++
前置++:
Self& operator++()
{
_node = _node->_next;
return *this;
}
我们将我们的迭代器走到下一个位置然后返回这个位置的迭代器,也就是将节点指针向后走一个位置。
后置++:
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
后置++中我们需要先保存我们的当前位置,然后再将节点指针向后走一个位置,返回走之前的位置的迭代器。
2.3.4 operator--
前置--:
Self& operator--()
{
_node = _node->_prev;
return *this;
}
前置--,我们只需要将节点指针向前走一步,然后返回走之后的位置的迭代器即可。
后置--:
Self operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
后置--,我们先保存走之前的位置,然后让节点指针向前走一步,然后返回保存的位置的迭代器即可。
2.3.5 operator== 和 operator!=
operator==:
bool operator==(const Self& l)
{
return _node == l._node;
}
如果节点指针相同就是等于,相等就返回true否则返回false。
operator!=:
bool operator!=(const Self& l)
{
return _node != l._node;
}
节点指针不等就返回true,否则就返回false。
三、list类
list类是我们的List容器的主体类,我们先来看下list类的总体代码,然后再逐函数介绍:
//list类
template<class T>
class list
{
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
// List的构造
list()
{
_pHead = new Node();
_pHead->_next = _pHead;
_pHead->_prev = _pHead;
}
list(int n, const T& value = T())
{
_pHead = new Node();
Node* pcur = _pHead;
while (n--)
{
Node* newnode = new Node(value);
pcur->_next = newnode;
newnode->_prev = pcur;
newnode->_next = _pHead;
pcur = pcur->_next;
}
}
//使用迭代器区间完成构造
template <class Iterator>
list(Iterator first, Iterator last)
{
_pHead = new Node();
Node* pcur = _pHead;
while (first != last)
{
Node* newnode = new Node(first._node->_val);
pcur->_next = newnode;
newnode->_prev = pcur;
newnode->_next = _pHead;
pcur = pcur->_next;
first++;
}
}
//拷贝构造
list(const list<T>& l)
{
_pHead = new Node();
Node* pcur = _pHead;
const_iterator first = l.begin(), last = l.end();
while (first != last)
{
Node* newnode = new Node(first._node->_val);
pcur->_next = newnode;
newnode->_prev = pcur;
newnode->_next = _pHead;
pcur = pcur->_next;
first++;
}
}
//赋值重载
list<T>& operator=(const list<T> l)
{
list<T> tmp(l);
swap(tmp);
return *this;
}
//析构函数
~list()
{
Node* pcur = _pHead;
while (pcur != _pHead)
{
Node* next = pcur->_next;
delete pcur;
pcur = next;
}
delete _pHead;
}
// List Iterator
iterator begin()
{
return iterator(_pHead->_next);
}
iterator end()
{
return iterator(_pHead);
}
const_iterator begin() const
{
return const_iterator(_pHead->_next);
}
const_iterator end() const
{
return const_iterator(_pHead);
}
// List Capacity
size_t size() const
{
Node* pcur = _pHead->_next;
size_t size = 0;
while (pcur != _pHead)
{
size++;
pcur = pcur->_next;
}
return size;
}
bool empty() const
{
if (_pHead->_next == _pHead)
{
return true;
}
else
{
return false;
}
}
// List的头节点和尾结点
T& front()
{
return _pHead->_next->_val;
}
const T& front() const
{
return _pHead->_next->_val;
}
T& back()
{
return _pHead->_prev->_val;
}
const T& back() const
{
return _pHead->_prev->_val;
}
// List Modify
void push_back(const T& val) { insert(end(), val); }
void pop_back() { erase(--end()); }
void push_front(const T& val) { insert(begin(), val); }
void pop_front() { erase(begin()); }
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* newnode = new Node(val);
newnode->_next = cur;
newnode->_prev = cur->_prev;
cur->_prev->_next = newnode;
cur->_prev = newnode;
return iterator(newnode);
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
assert(pos._node != _pHead);
Node* cur = pos._node;
Node* next = cur->_next;
cur->_prev->_next = cur->_next;
cur->_next->_prev = cur->_prev;
delete cur;
return iterator(next);
}
void clear()
{
Node* pcur = _pHead->_next;
while (pcur != _pHead)
{
pcur->_val = 0;
pcur = pcur->_next;
}
}
void swap(list<T>& l)
{
std::swap(_pHead, l._pHead);
}
private:
Node* _pHead;
};
3.1 list类的成员变量
typedef ListNode<T> Node;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
private:
Node* _pHead;
我们的list类将节点类重命名成Node,然后将我们的迭代器类给重命名成标准的迭代器名称,也就是iterator,和不可修改存储数据的迭代器const_iterator。然后再不可访问区域封装了一个头节点。
3.2 list类的构造和析构
3.2.1 无参构造
// List的构造
list()
{
_pHead = new Node();
_pHead->_next = _pHead;
_pHead->_prev = _pHead;
}
无参构造中我们开辟一个头节点,然后将头节点的next指针指向自己,然后将prev指针也指向自己,构成一个循环无元素的链表。
3.2.2 使用n个value构造list
list(int n, const T& value = T())
{
_pHead = new Node();
Node* pcur = _pHead;
while (n--)
{
Node* newnode = new Node(value);
pcur->_next = newnode;
newnode->_prev = pcur;
newnode->_next = _pHead;
pcur = pcur->_next;
}
}
这里我们开辟一个头节点之后定义一个指针pcur指向phead然后使用pcur通过循环遍历将这n个存储数据为value的节点给尾接到链表中。
3.2.3 使用迭代器区间完成构造
//使用迭代器区间完成构造
template <class Iterator>
list(Iterator first, Iterator last)
{
_pHead = new Node();
Node* pcur = _pHead;
while (first != last)
{
Node* newnode = new Node(first._node->_val);
pcur->_next = newnode;
newnode->_prev = pcur;
newnode->_next = _pHead;
pcur = pcur->_next;
first++;
}
}
这里我们也是定义一个pcur指向我们的phead让lpcur代替pcur的去进行循环遍历,在遍历的过程中,我们使用迭代器中的节点的val来构造新节点,然后通过循环将新节点一个个的尾接到我们的链表中。
3.2.4 拷贝构造
//拷贝构造
list(const list<T>& l)
{
_pHead = new Node();
Node* pcur = _pHead;
const_iterator first = l.begin(), last = l.end();
while (first != last)
{
Node* newnode = new Node(first._node->_val);
pcur->_next = newnode;
newnode->_prev = pcur;
newnode->_next = _pHead;
pcur = pcur->_next;
first++;
}
}
通过这里我们通过拿到被拷贝对象的迭代器然后通过与迭代器区间构造相同的方法将节点尾接到链表中,这里我们也可以直接复用迭代器区间构造来实现。
3.2.5 赋值重载
//赋值重载
list<T>& operator=(const list<T> l)
{
list<T> tmp(l);
swap(tmp);
return *this;
}
这里我们直接定义一个tmp对象然后调用拷贝构造,构造一个和l一样的对象,然后交换*this 和 tmp实现。最后返回*this即可。
3.2.6 析构函数
//析构函数
~list()
{
Node* pcur = _pHead;
while (pcur != _pHead)
{
Node* next = pcur->_next;
delete pcur;
pcur = next;
}
delete _pHead;
}
在我们的析构函数中定义一个pcur指向phead通过pcur进行循环遍历delete掉每一个有效节点,然后再delete掉我们的phead。
3.2 List iterator
3.2.1 begin
iterator begin()
{
return iterator(_pHead->_next);
}
const_iterator begin() const
{
return const_iterator(_pHead->_next);
}
begin返回第一个元素位置的迭代器,也就是返回phead的下一个位置的迭代器,对于非const对象返回iterator的迭代器,对于const对象返回const_iteratotr的迭代器。
3.2.2 end
iterator end()
{
return iterator(_pHead);
}
const_iterator end() const
{
return const_iterator(_pHead);
}
end返回最后一个有效数据的下一个位置,由于我们的链表是循环的,因此就是返回phead的位置,对于非const对象返回iterator迭代器,对于const对象返回const_iterator迭代器。
3.3 LIst capacity
3.3.1 size
size_t size() const
{
Node* pcur = _pHead->_next;
size_t size = 0;
while (pcur != _pHead)
{
size++;
pcur = pcur->_next;
}
return size;
}
size成员函数返回当前链表中的有效数据个数,通过循环遍历然后返回计数变量size。
3.3.2 empty
bool empty() const
{
if (_pHead->_next == _pHead)
{
return true;
}
else
{
return false;
}
}
empty用来判断链表是否为空,为空返回true否则返回false,仅需要判断phead的下一个位置是否为phead本身即可,是的话就返回true否则false,也可以通过复用size实现,但是不推荐,因为size需要遍历,时间复杂度为O(N)。
3.4 LIst的访问和修改
3.4.1 front和back
front:
T& front()
{
return _pHead->_next->_val;
}
const T& front() const
{
return _pHead->_next->_val;
}
front返回第一个元素的val,对于非const对象返回非const的引用,对于const对象返回const修饰的引用。
back:
T& back()
{
return _pHead->_prev->_val;
}
const T& back() const
{
return _pHead->_prev->_val;
}
返回最后一个有效元素的val,对于非const对象返回非const的引用,对于const对象返回const引用。
3.4.2 insert 和 erase
insert:
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* newnode = new Node(val);
newnode->_next = cur;
newnode->_prev = cur->_prev;
cur->_prev->_next = newnode;
cur->_prev = newnode;
return iterator(newnode);
}
insert成员函数,按pos位置进行插入节点,返回一个插入后返回当前位置的迭代器。
erase:
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
assert(pos._node != _pHead);
Node* cur = pos._node;
Node* next = cur->_next;
cur->_prev->_next = cur->_next;
cur->_next->_prev = cur->_prev;
delete cur;
return iterator(next);
}
erase成员函数,按pos位置删除节点,删除后返回被删除节点的下一个位置的迭代器。
3.4.3 push 和 pop
void push_back(const T& val) { insert(end(), val); }
void pop_back() { erase(--end()); }
void push_front(const T& val) { insert(begin(), val); }
void pop_front() { erase(begin()); }
这里我们直接复用我们的insert函数来完成头插头删,尾插尾删的操作。
3.4.4 clear
void clear()
{
Node* pcur = _pHead->_next;
while (pcur != _pHead)
{
Node* next = pcur->_next;
delete pcur;
pcur = next;
}
_pHead->_next = _pHead;
_pHead->_prev = _pHead;
}
clear函数将list中的所有有效节点给清空,使之成为一个空链表。
3.4.5 swap
void swap(list<T>& l)
{
std::swap(_pHead, l._pHead);
}
对于swap函数我们直接复用std命名空间中的swap将*this._pHead 和 l.pHead 互换即可。
这篇博客到这里就结束了,我们的模拟实现就是上面这些常用的接口,希望大家能从这些模拟实现中对LIst容器有更深的认识。如需要博客中的代码请点击下方代码仓库的链接: