目录
一、list成员定义
1.1 list 的结点定义
实现 list,首先我们要先自定义创建一个结点类型 ,定义如下:
1.2 list的结构
STL中的 list 结构为带头双向循环链表,所以当调用了 list 时,就默认创建了 _head 结点,并将_next、_prev指向自身。
1.3 实现的函数
以下来看看我们接下来要实现的函数接口。本篇要实现三个类,一个是 list 的结点类,一个 list 的迭代器类,最后是 list 类。
namespace Brant
{
template<class T>
struct list_node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& x = T());
};
template <class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> iterator;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{}
bool operator !=(const iterator& it);
bool operator !=(const iterator& it) const;
bool operator ==(const iterator& it);
bool operator ==(const iterator& it) const;
Ref operator* ();
Ptr operator->() const;
iterator& operator++();
iterator operator++(int);
iterator& operator--();
iterator operator--(int);
};
template<class T>
class list
{
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();
iterator end();
const_iterator begin() const;
const_iterator end() const;
void empty_init();
list();
template <class InputIterator>
list(InputIterator first, InputIterator last);
list(const list<T>& lt);
list<T>& operator=(list<T> lt);
~list();
void push_back(const T& x);
iterator insert(iterator pos, const T& x);
iterator erase(iterator pos);
void push_front(const T& x);
void pop_back();
void clear();
private:
Node* _head;
};
}
二、list 的迭代器
list 的迭代器不同于 string、vector的迭代器,它的迭代器并不是原生指针,list的迭代器指向的是每个结点。所以,我们要单独实现 list 的迭代器——__list_iteartor(像指针一样的对象),才能在 list 中进行使用。
首先我们先来是实现 list 的push_back,然后使用 list 的迭代器将其数据全部输出。
2.1 push_back
思路:
- 尾插的是在 _head->prev 处进行插入,保存 _head->prev 为 tail 。
- 创建 (new) 一个 newnode 。(注意,Node 类型向初始化要调用构造函数)
- 链表的链接
好的,接下来实现一个 test 函数来检验成果。
2.2 iterator 的实现
在上面实现了 push_back 后,我们只能在监视里观察插入的数据。因为我们无法像 string、vector 直接使用指针进行访问,因为 list 不是连续的物理空间。所以,我们要自己实现 list 的专用指针,才能在控制台输出数据。
接下来,我们就开始编写我们自己的 iterator 类
如上,我们便实现了属于 list 的迭代器,本质是在 list 类中调用 __list_iteartor。
所以,如果我们创建一个 list 的迭代器。就可以在 list 类中构造一个__list_iteartor的迭代器。
接下来,我们使用实现一个成员函数,具体功能如下:
在 list 中将结点传入__list_iteartor,构造出一个 list 的迭代器。最常用的便是 begin()、end()。
注意上面使用的是匿名对象,一样是在用结点构造一个迭代器。
2.3 iterator 的基本功能
定义了 list 迭代器,我们接下来就是要实现其作为迭代器的基本功能。让其能便捷的操作每个结点。
2.3.1 operator !=
这里为了便捷书写、提高代码可读性,我们在__list_iteartor 中再 typedef 一下。
便可以简便、清晰地书写
2.3.2 operator *
如果我们想访问、修改一个迭代器所指向的结点中的数据,所以我们要再提供(*)操作符重载.
2.3.3 operator ++
这个功能就更重要,++iterator就可以让迭代器指向下一个结点,并返回++迭代器后的值(*this)。
2.3.4 operator ->
重载出这个运算符重载,可以更方便我们访问迭代器的成员变量。
使用场景:
当我们list中存放的内置类型,此时我们直接 *iterator 就可以很好的拿到数据,可是如果我们list 中存放的 自定义类型的数据,此时我们呢 *iterator 取到的是 data ,而data又是一个内置类型,我们要再次解引用 (*data) ,使用起来就十分别扭。
这时,我们就可以重载出 -> 运算符重载,使用起来非常方便。
我们来看看是如何实现这个运算符的。
其中 this 调用了 operator*()函数
其中C++对这里的书写进行了简化,直接省略了一个 ->,为了可读性而进行了特殊处理。
2.4 iteartor 的效果检验
实现了上面的基本功能,就可以使用迭代器循环打印出一串链表了
我们范围 for 同样也可以使用,在编译中范围 for的编译就是直接替换为迭代器,如果迭代器是正确的,那范围 for 就可以运行。
2.5 const 迭代器
以上我们我们实现的都是非 const 型的迭代器,如果现在我们的 list 是 const 型,所以迭代器也应该为 const 型。如下图,将对象进行引用传参,则需配套使用 const 进行传参。如果使用 const 型 list 便无法使用一系列函数。
如果想解决上面的问题,我们需要提供一套 const 作返回值的成员函数(只读)。
不难发现,其实就是返回值加上了 const 。但是 返回值不同不能构成函数重载,我们不可能因为要实现 const 作返回值的迭代器,而再去实现一个const__list_iteartor。这便是这个地方的难点。
接下来我们来看一下STL源码中是如何解决这样的问题的
如果 list 是 const 成员, 则会调用 const 型的 begin() 函数,然后 const begin() 函数会构造一个const_iterator,并且模板中的Ref、Ptr就是 const 型的。
此时,迭代器内部无需关心到底是 const 型还是非 const 型,全凭外部传来的值决定。
这样,一些需要 const 型也需要普通类型做返回值得地方,我们就可以用此模板参数作返回值。
如果模板参数传来是const型的,就是以 const 类型的作返回值。
在 list 类中,我们便要对这 const_iterator 和 iterator做区别
三、list 的功能实现
基于之前学过数据结构,list 这些接口的实现其实非常相似,这里不做过多讲解。
3.1 insert
insert就是在pos位置的前面进行插入,尾插相当于在end()位置进行 insert 。
3.2 erase
3.3 pop/push
实现了 insert 和 erase ,那push_back、push_front、pop_back、pop_front都可以复用实现。
3.4 拷贝构造函数
如果我们不提供拷贝构造函数,那 list 中的拷贝构造就是值(浅)拷贝,如下图:
为了实现 list 的深拷贝,所以我们要实现 list 的拷贝构造。
我们在 string、vector中都实现了传统写法、现代写法,在 list 中我们直接书写现代写法。
- 创建以迭代器区间为参数的带参默认构造函数
- 使用该构造函数根据 lt 构造一个 temp 的临时变量
- 创建一个头节点
- 直接交换哨兵位的头结点
3.5 赋值运算符重载
同样采用现代写法,使用传值传参调用了拷贝构造创建了临时变量,再使用swap函数进行交换。
3.6 析构函数
实现 list 的析构函数主要分为两步:1.释放除_head 外所有的结点 2.释放头节点,并置空。