List
之前所讲到的vector是模拟顺序表,而和顺序表互补的则是链表,所以List模拟的就是链表的结构。
在之前数据结构阶段我们知道链表其实有两个部分组成,一个是单节点的结构,另一个则是链接它们的结构,所以我们依旧使用老方法,定义两个结构体。
template <class T>
class list_node
{
T _data;
list_node* _next;
list_node* _prev;
list_node(const T& x =T())
:_data(x)
,_next(nullptr)
,_prev(nullptr)
{
}
};
template <class T>
class list
{
private:
node* _head;
size_t _size;
};
在list的基本功能前,我们还需要一些准备来辅助,但是链表和顺序表不同,它无法通过下标和[]来访问内容,所以链表的迭代器就有些麻烦,这需要我们再定义一个结构来实现。
template <class T,class Ref,class Ptr>//我们不能因为const和非const而写两个结构,那太繁琐了
struct __list_iterator
{
//所以我们采用模版的形式 如果是非const 那么它的参数就是T T& T*
typedef list_node<T> node;
//如果是const 那么它的参数就是 T const T&和const T*
typedef list_iterator<T, Ref, Ptr> self;
//开节点
node* _node;
//构造函数
__list_iterator(node* node)
:_node(node)
{
}
//++--我们都不希望它的内容被改变 所以我们用const版本的
self& operator++()
{
_node = _node->_next;
return *this;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
//后置++--
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
//我们此时并不知道它是什么类型,但是它会通过模版实例化确认类型
//它会根据我们传的参数自动匹配是不是const
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const self& s)
{
return _node!= s._node;
}
};
这里需要注意的是,我们想实现const类型的迭代器,并不是直接const iterator就可以的,因为如果这么写,我们反而是限制了迭代器不能修改,那么我们的++--都将无法实现,所以我们的const应该是要加在类型前面的,而我们把这一步交给了使用者,他传的什么类型const我们就用它实例化出来。
迭代器都解决了,那么之后的功能实现就很简单了。
//首先咱先把我们定义的结构体取个别名
typedef list_node<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 _head->prev;
}
//const版本
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
//初始化一个头结点,因为我们是双向带头循环链表
void empty_init()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
_stze = 0;
}
//因为咱是链表只能一个个删
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
//构造函数
list()
{
empty_init();
}
//析构函数
~list()
{
//通过调用上面的clear清理完数据
clear();
//再把头结点释放
delete _head;
_head = nullptr;
}
//拷贝构造
list(const list<T>& it)
{
//这里其实不存在浅拷贝的问题,因为咱节点都是现开的,确实只需要拷贝个数据就可以了
empty_init();
for (auto e : it)
{
push_back(e);
}
}
那么上面的基本运行没有问题之后我们就可以来实现它的功能了。
void swap(list<T>& it)
{
//直接使用系统自带的交换即可
std::swap(_head, it._head);
std::swap(_size, it._size);
}
list<int>& operator=(const list<T>& it)
{
swap(it);
return *this;
}
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简单省事
insert(end(), x);
}
//头删头插尾删 其实只需要看insert和erase就好
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front(const T& x)
{
erase(begin());
}
void pop_back(const T& x)
{
erase(--end());
}
iterator insert(iterator pos const T& val)
{
//和链表插入大同小异 无非是改改前后的指向然后链接上新节点
node* cur = pos->_node;
node* newnode = new node(x);
node* prev = cur->_prev;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
//需要注意的是 我们为了省去计算节点个数 所以加了一个size 没错增加结点需要++size
//删除结点也需要--size;
++_size;
//虽然list插入不会发生迭代器失效,但是我们还是给它加一个返回插入后节点的位置
return iterator(newnode);
}
iterator erase(iterator pos)
{
//链表因为插入的特殊,所以不会有迭代器失效的问题,但是删除一样会出现迭代器失效
//基本操作
node* cur = pos._node;
node* prev = cur->_prev;
node* next = cur->_next;
delete cur;
prev->_next = next;
prev->_prev = prev;
--_size;
//因为迭代器会失效,所以我们得返回个新的给它外部接收更新
return iterator(next);
}
//这个就是返回节点数,而我们写了个size成员 所以简简单单。
size_t size()
{
return _size;
}
关于list,其实和咱数据结构链表差不多,只不过人家各种功能齐全,不需要我们现场写,而我们写是为了知道它为啥实现,咋实现的,并且知道它底层的一些隐藏点,这样真的写起来用起来能避免出问题,就像list迭代器,如果我们不分析,我们很难想到它是咋实现的,反正咱用还是*和->。