一、核心结构
template <class T>
struct list_node{ //[1]
T _data; //[2]
list_node *_next; //指向下一个节点
list_node *_prev; //指向前一个节点
list_node(const T &val = T())
:_data(val),
_next(nullptr),
_prev(nullptr)
{}
};
template <class T>
class Mylist{
typedef list_node<T> Node; //[3]
Node *_phead; //[4]
//......
};
解释
- [1] 使用struct公开成员变量,方便Mylist类中直接访问节点数据。
- [2] 节点中存储的数据是模版类型,使链表可以存储各种类型包括自定义类型的数据。
- [3] typedef为类型起别名,简化代码,便于后期维护。
- [4] Mylist类只有一个成员变量,即指向哨兵位节点的指针(Mylist是一个带头双向循环链表)。
结构图示:
二、迭代器
2.1 结构及构造
//list_iterator
template <class T, class Ref, class Ptr>
struct list_iterator{ //[1]
typedef list_node<T> Node;
typedef list_iterator<T, Ref, Ptr> iterator;
Node *_pnode; //[2]
list_iterator(Node *pnode) //[3]
:_pnode(pnode)
{}
解释:
- [1] 使用struct公开成员变量,方便Mylist类中直接访问迭代器指针数据。
- [2] list_iterator的成员变量只有一个指向节点的指针。但其中封装了list迭代器的方法:
- 解引用返回数据_data的引用
- 箭头返回数据_data的地址
- ++操作实现了链表指针的在链表上的移动,等等
- [3] 用一个指向节点的指针构造list迭代器。
- [4] list_iterator的拷贝构造,赋值重载,析构使用默认生成的即可(内部只有一个指针,且不涉及资源申请)。
//begin() & end()
template <class T>
class Mylist{
//......
public:
//iterator
typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator; //[2]
iterator begin(){
return iterator(_phead->_next); //[1]
}
iterator end(){
return iterator(_phead);
}
const_iterator begin() const{ //[3]
return const_iterator(_phead->_next);
}
const_iterator end() const{
return const_iterator(_phead);
//......
}
解释:
- [1] 这里用指向节点的指针构造匿名对象做返回值。
- [2] iterator和const_iterator的后两个模版参数不同是两个不同的实例化类型:
- iterator类的*和->操作返回普通引用和普通指针。
- const_iterator类的*和->操作返回const引用和const指针。
- [3] 这里的const用于修饰this指针,即调用函数的对象是const Mylist
- const对象返回const迭代器,const迭代器的*和->操作的返回值是只读的const引用和const指针。
- 也就是说,const迭代器指向的节点不能修改。(类似于常量指针 const int *ptr)
结构图示:
2.2 operator* & operator->
Ref operator*() const{ //[3]
return _pnode->_data; //[1]
}
Ptr operator->() const{ //[3]
return &(_pnode->_data); //[2]
}
//下面的代码使用->访问对象的成员:
struct Pos{
int _row;
int _col;
Pos(int row = 0, int col = 0)
:_row(row),
_col(col)
{}
};
void Test7(){
cout << "Test7: " << endl;
Mylist<Pos> mlt; //链表存储的数据类型是自定义类型
mlt.push_back(Pos(10,10));
mlt.push_back(Pos(20,20));
for(auto it = mlt.begin(); it != mlt.end(); ++it)
{
cout << it->_row << ":" << it->_col << endl; //[4]
}
}
解释:
- [1] 迭代器解引用返回节点数据_data的引用。迭代器解引用后可以直接访问修改节点数据。
- [2] 迭代器的operator->返回节点数据_data的地址。专为自定义类型的_data使用,可以直接使用迭代器加->的方式访问_data的成员。
- [3] *和->操作涉及普通对象和const对象的权限问题,需要传入Ref和Ptr两个模版参数,泛型返回值。
- [3] 普通对象传入普通引用和普通指针,*和->操作后返回的引用和指针可读可写;const对象传入const引用和const指针,*和->操作后返回的引用和指针只能读不能写。
- [4] 这里其实应该有两个箭头
it->->_row
:- 第一个箭头调用operator->函数返回_data对象的地址。
- 第二个箭头用于访问_data对象的成员_row。
- 为了提高可读性,在设计c++语法时省略了一个箭头。这里的操作编译器会做特殊处理。
2.3 operator== & operator!=
bool operator!=(const iterator &it) const{ //[1]
return _pnode!=it._pnode;
}
bool operator==(const iterator &it) const{ //[1]
return _pnode==it._pnode;
}
解释:
- [1] 这里要区分清楚const iterator对象和const Mylist对象:
- [1] 这里的最后一个const用于修饰this指针,即调用的函数的对象是const iterator。
- const iterator表示其成员变量_pnode不能改变,也就是说迭代器的指向不能变。(类似于指针常量 int *const ptr)
- const iterator可以进行解引用,->访问成员变量,进行==/!=的条件判断等。但不能进行++等操作。
2.4 前置++ & 后置++
iterator& operator++(){
_pnode = _pnode->_next; //[1]
return *this;
}
iterator operator++(int){
iterator tmp(*this); //[2]
_pnode = _pnode->_next;
return tmp;
}
解释:
- [1] list迭代器的++操作不同于vector迭代器(原生指针直接++),需要访问节点的_next将指针指向下一个节点。
- [2] 后置++需要先拷贝一个临时迭代器,用于返回++之前的结果。因此需要传值返回。
提示:还有前置--和后置--,只需要访问节点的_prev将指针指向前一个节点即可。
三、默认成员函数
3.1 构造
void empty_initialize(){ //[1]
_phead = new Node;
_phead->_next = _phead;
_phead->_prev = _phead;
}
Mylist(){
empty_initialize();
}
template <typename InputIterator>
Mylist(InputIterator first, InputIterator last){
empty_initialize(); //[2]
while(first!=last)
{
push_back(*first);
++first;
}
}
解释:
- [1] 这里将空初始化单独拿出来,是为了方便下面多处进行复用
- [2] 交换前必须要进行空初始化,否则会访问野指针崩溃
3.2 拷贝构造 & 赋值重载
//void swap(Mylist<T> &mlt)
void swap(Mylist &mlt){ //[1]
std::swap(_phead, mlt._phead);
}
//Mylist<T>(const Mylist<T> &mlt)
Mylist(const Mylist &mlt){ //[1]
empty_initialize(); //交换前必须要进行空初始化,否则会访问野指针崩溃
Mylist tmp(mlt.begin(), mlt.end());
swap(tmp);
}
Mylist& operator=(Mylist mlt){
swap(mlt);
return *this;
}
解释:
- [1] 定义类成员函数时同类类型的类型名可以省略模版参数,但要注意:
- 不同类的类型名不可以省略模版参数。
- 在类外定义函数必须写明模版参数。
3.3 析构
void clear(){
iterator it = begin();
while(it != end())
{
it = erase(it);
}
}
~Mylist(){
clear(); //清除所有数据节点
delete _phead; //释放哨兵位节点
}
注意:一定记得释放哨兵位节点哦!
四、插入删除
4.1 insert
iterator insert(iterator pos, const T &val){
Node *cur = pos._pnode; //[1]
Node *prev = cur->_prev;
Node *newnode = new Node(val);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode); //[2]
}
解释:
- [1] list_iterator使用struct,Mylist类可以直接访问其成员。
- [2] 返回新插入节点的迭代器。
4.2 push_back & push_front
void push_back(const T &val){
insert(end(), val);
}
void push_front(const T &val){
insert(begin(), val);
}
4.3 erase
iterator erase(iterator pos){
assert(pos != end()); //[1]
Node *cur = pos._pnode;
Node *prev = cur->_prev;
Node *next = cur->_next;
next->_prev = prev;
prev->_next = next;
delete cur;
return iterator(next); //[2]
}
解释:
- [1] 不能删除哨兵位节点。
- [2] 返回删除节点的下一个节点的迭代器。
4.4 pop_back & pop_front
void pop_back(){
assert(_phead->_next != _phead); //[1]
erase(--end());
}
void pop_front(){
assert(_phead->_next != _phead);
erase(begin());
}
注意:pop之前要判空!