目录
前言:
之前我们分别手撕了string类和vector类,今天我们来跟list类打打交道~
list是什么?
通过查c++文档可知,list也是一个模板类,我们主要利用他进行数据的插入和删除操作,并且与vector不同的是,list的插入和删除操作用到的时间复杂度是O(1),而对于vector类的头插或者头删需要O(N)的时间复杂度,接下来让我们探索list是如何实现以及应用的吧!
通过查询文档可知,list的底层使用带头双向循环列表进行实现的,这样才能做到在任意位置删除和插入的时间复杂度都是O(1)。
节点类
因为是链表,我们需要先定义一个节点类,用来存储节点的相关信息。
// List的节点类
template<class T>
struct ListNode
{
ListNode(const T& val = T())
:_pPre(nullptr)
, _pNext(nullptr)
, _val(val)
{
}
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
注意这里的节点类以及下面的迭代器类都是用struct实现的,因为struct类默认成员都是public类型,方便我们在list类中进行操作,不然还要用到友元函数,减少不必要的麻烦。
迭代器类:
下面我们来实现list的迭代器类
我们之前实现的string和vector的迭代器都是原生指针,直接typedef指针即可,因为前者的底层存储空间是连续的,这样我们在使用迭代器进行遍历时,可以直接用指针++即可。
但是list类不同,list底层实现是用一个一个节点组成,是我们自定义类型实现,没有办法保证地址连续,因此迭代器直接++就无用武之地了。
因此我们要将Node*进行运算符重载,但是Node*本身是一个指针,只有自定义类型才能用运算符重载,因此我们需要一个类将Node*封装起来,然后对Node*进行运算符的重载~
//List的迭代器类
template<class T, class Ref, class Ptr>
struct ListIterator
{
typedef ListNode<T>* PNode;
typedef ListIterator<T, Ref, Ptr> Self;
PNode _pNode;
ListIterator(PNode pNode = nullptr)
:_pNode(pNode)
{
//_pNode = pNode;
}
//ListIterator(const Self& l)
//{
// _pNode = l;
//}
T& operator*()
{
return _pNode->_val;
}
//T* operator->()
//{
// return &(_pNode->_val);
//}
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
Self& operator--()
{
_pNode = _pNode->_pPre;
return *this;
}
Self& operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPre;
return tmp;
}
bool operator!=(const Self& l)
{
return l._pNode != _pNode;
}
bool operator==(const Self& l)
{
return l._pNode == _pNode;
}
};
首先这里的迭代器存在一个很严重的问题:如果是一个const对象无法调用这个迭代器!而你可能会想说在创造一个const版本的迭代器类,这样固然是可以,但是这样会使得代码冗余!
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
这里就需要模板出手了!STL里面利用了三个模板参数,T, Ref,Ptr。而Ref,Ptr,分别就是引用和指针。所以当我们传递的是非const的迭代器,编译器就会匹配非const的,反之const就会匹配const。这就是大佬设计的独到之处~
const_iterator自己可以修改,不是const对象,但是指向的内容不能修改
list类
在实现了迭代器之后,我们就可以正式手撕list类了。
template<class T>
class list
{
typedef ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
///
// List的构造
list()
{
CreateHead();
}
list(int n, const T& value = T());
template <class Iterator>
list(Iterator first, Iterator last);
//拷贝构造
list(const list<T>& l)
{
CreateHead();
for (const auto i : l)
{
push_back(i);
}
}
//赋值重载
list<T>& operator=(const list<T> l)
{
swap(l);
return *this;
}
~list()
{
clear();
delete _pHead;
//注意delete之后,要将指针赋值为空指针
_pHead = nullptr;
}
///
// List Iterator
iterator begin()
{
return _pHead->_pNext;
}
iterator end()
{
return _pHead;
}
const_iterator begin()const
{
return _pHead->_pNext;
}
const_iterator end()const
{
return _pHead;
}
///
// List Capacity
size_t size()const
{
size_t num = 0;
const_iterator it = begin();
while (it != end())
{
it++;
num++;
}
return num;
}
//判断是否为空
bool empty()const
{
return size() == 0;
}
// List Access
T& front()
{
return _pHead->_pNext->_val;
}
const T& front()const
{
return _pHead->_pNext->_val;
}
T& back()
{
return _pHead->_pPre->_val;
}
const T& back()const
{
return _pHead->_pPre->_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)
{
PNode newnode = new Node(val);
PNode prev = pos._pNode->_pPre;
PNode cur = pos._pNode;
prev->_pNext = newnode;
newnode->_pPre = prev;
newnode->_pNext = cur;
cur->_pPre = newnode;
//return iterator(newnode);
//单参数的构造函数可以隐式类型转换
return newnode;
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
//assert(pos != end());
PNode cur = pos._pNode;
PNode next = cur->_pNext;
PNode prev = cur->_pPre;
prev->_pNext = next;
next->_pPre = prev;
delete cur;
cur = nullptr;
return next;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void swap(list<T>& l)
{
std::swap(l->_pHead, _pHead);
}
private:
void CreateHead()
{
_pHead = new Node;
_pHead->_pNext = _pHead;
_pHead->_pPre = _pHead;
}
PNode _pHead;
};
注意在实现的时候,可以多复用之前写过的代码,比如前插、前删、尾插、尾删就可以复用insert和erase函数。
拷贝构造函数也可以用push_back函数复用,析构函数使用erase复用~
list的迭代器失效问题
对于insert而言,因为insert方法仅仅只是改变了指针的指向,所以本质pos指向的那个节点的绝对地址并不会随着insert而改变,所以insert不会导致迭代器失效。
反而是erase方法反而因为释放了原来的空间导致出现野指针失效 而和vector的处理方式一致,erase方法也是返回指向被删除元素的下一个位置元素的迭代器。