Gitee仓库:
目录
[Part1. 基本架构](#Part1. 基本架构)
[Part2. list_node的实现](#Part2. list_node的实现)
[Part3. list_iterator的实现](#Part3. list_iterator的实现)
[Part3.1. list_iterator中"->"的重载](#Part3.1. list_iterator中“->”的重载)
[Part3.2. const_iterator的适配](#Part3.2. const_iterator的适配)
[Part4. 基于list_node和list_iterator的list实现](#Part4. 基于list_node和list_iterator的list实现)
[Part4.1. 封装思想在list体现](#Part4.1. 封装思想在list体现)
[Part4.2. 迭代器失效](#Part4.2. 迭代器失效)
[Part5. 其他函数](#Part5. 其他函数)
[Part6. 结语](#Part6. 结语)
前言
C++标准库list类作为STL的重要组成部分,我们有必要去了解它,而了解它最好的方式就是亲自去实现一波。接下来,让我们来看一下吧。
let's go!!!!!!!
Part1. 基本架构
首先,我们要先明确我们要实现的东西的一个基本框架。我们主要有三种结构要实现:
1. list :向外暴露的容器,只存储哨兵位节点,通过这个来对整个链表统一管理。
2. list_node :具体的链表的每个节点,包括存数据的data,前驱节点prev,后驱节点next。有利于我们支持泛型编程(即对一套代码可以实现对不同类型的数据使用,提高代码复用性)和精细化管理。
3. list_iterator:由于链表相较于vector等比较特殊,它不能用原生指针来达到我们想要的效果。因此我们要对链表结点的指针(原生指针)进行封装(对这个进行特殊的处理),通过运算符重载等的方式来达到我们想要的效果。
三种结构彼此相依:list基于list_node来组成,同时list又要依靠list_iterator来向外表现,list_node通过上面两个结构来向外展示。
既然基本的结构我们已经搞清楚了,接下来我们就要来看在这些结构之下衍生出来的东西,也就是它们的成员和成员函数,我们先来看最基础的list_node吧。
Part2. list_node的实现
我们先来看代码:
cpptemplate<class T>//模板 泛型编程 class list_node { public: T data;//存数据 list_node<T>* next;//后驱节点 list_node<T>* prev;//前驱节点 list_node(const T& data=T())//默认构造函数 :data(data) ,next(nullptr) ,prev(nullptr) { } };
这就是我们list_node的实现,看起来还是非常easy的。关键问题有两个:
1.为什么所有成员要设置为公有,按照封装的逻辑不是应该要设置为私有吗?
2.为什么没有析构函数等的其他默认成员函数?
首先,第一个。我们在实现list和list_iterator要频繁调用这个类里面的成员,因此我们要设为公有。
其次,第二个。上述我们也讲过了,我们通过list对list_node进行统一管理,list_node的析构交由list来完成。(关键我们如果不这么搞,创建出来的list_node出作用域就会被销毁,会影响list整个的运作。)
这样我们就介绍完了list_node,接下来我们来看看list_iterator,这个特殊的迭代器。
Part3. list_iterator的实现
我们依旧先来看代码:
cpptemplate<class Ref, class Ptr>//为const_iterator做准备 简化返回值 增加可读性 class list_iterator { private: typedef list_node Node;//简化代码 typedef list_iterator<Ref, Ptr> Self;//同上 Node* _node; public: friend class list<T>;//友元声明 模板类作为内部类 外部无法访问私有 list_iterator(Node* node)//带参构造 :_node(node) { } Ref operator*() { return _node->data; } Ref operator*()const//const重载 { return _node->data; } Self& operator++() { _node = _node->next; return *this; } bool operator!=(const Self& it)const { return _node != it._node; } bool operator==(const Self& it)const { return !(*this != it); } Self& operator--() { _node = _node->prev; return *this; } Self operator++(int)//后置++ { Self tem(*this); _node = _node->next; return tem; } Self operator--(int)//后置-- { Self tem(*this); _node = _node->prev; return tem; } Ptr operator->()//<1> { return &_node->data; } Ptr operator->()const { return &_node->data; } }; typedef list_iterator<T&, T*> iterator;//<2> typedef list_iterator<const T&, const T*> const_iterator;
上文是我们对list_iterator的实现,我们关键要在意两个那就是上文中的<1>和<2>,我们来看看吧:
Part3.1. list_iterator中"->"的重载
<1>:这是->的重载,为啥我们返回的是指针,按道理我们不是应该返回数据类型,也就是T吗?
首先我们先来看对迭代器要访问数据的流程:我们先定义一个迭代器叫it,我们要取到数据要先得到被他封装的list的原生指针也就是list_node,我们通过他才可以得到数据(因为数据data是它的成员)。然后我们通过list_node中的data得到他的地址,得到地址就可以直接->来访问了。
其中最后的操作是原生的(即是系统自己就有的,不与自己定义的类什么有关),所以在实际对于->的重载中,编译器就隐去了最后一步,也就是通过data地址来得到数据的过程。我们只要完成前面的过程,也就是得到他的地址。这就是->重载。
Part3.2. const_iterator的适配
<2>:我们为什么要有两个typedef来搞(生成两种迭代器),我们不是可以通过泛型来自动判断类型来生成相应的迭代器吗?
我们来思考一个场景:此刻我们的list为int类型,此时我们要使用const_iterator可以吗?不行。为什么?因为现在的迭代器类型源于一开始list的类型,我们确定了list的类型,就也确定了iterator的类型,无法改变。因此我们需要另一个const修饰的迭代器来应对不时之需。
这样我们就介绍完了list_iterator。接下来,我们来总览一下list的实现吧,故事来到终章:
Part4. 基于list_node和list_iterator的list实现
先来看看代码吧:
cpptemplate<class T> class list { private: class list_node//将list_node设置为内部私有类 封装的思想 { public: T data; list_node* next; list_node* prev; list_node(const T& data = T()) :data(data) , next(nullptr) , prev(nullptr) { } }; typedef list_node Node; Node* _head; size_t _size; public: template<class Ref, class Ptr> class list_iterator//由于list_iterator要被外部所使用所以要设置为公有 { private: typedef list_node Node; typedef list_iterator<Ref, Ptr> Self;//仅限内部使用的别名 Node* _node; public: friend class list<T>; list_iterator(Node* node) :_node(node) { } Ref operator*() { return _node->data; } Ref operator*()const { return _node->data; } Self& operator++() { _node = _node->next; return *this; } bool operator!=(const Self& it)const { return _node != it._node; } bool operator==(const Self& it)const { return !(*this != it); } Self& operator--() { _node = _node->prev; return *this; } Self operator++(int) { Self tem(*this); _node = _node->next; return tem; } Self operator--(int) { Self tem(*this); _node = _node->prev; return tem; } Ptr operator->() { return &_node->data; } Ptr operator->()const { return &_node->data; } }; typedef list_iterator<T&, T*> iterator; typedef list_iterator<const T&, const T*> const_iterator;//设置为Public 方便外部使用 typedef list_node Node;//同上 list()//list的函数 开始统一调用上面的list_node和list_iterator了 { empty_init();//空构造 因为我们的这个链表一定要有哨兵位而且在一开始要让他的prev和next自指 把这些操作都封装为一个函数 简化代码 } ~list() { clear();//代码复用 delete _head; _head = nullptr; } list(const list<T>& copy_l)//拷贝构造 { empty_init(); const_iterator it = copy_l.begin(); while (it != copy_l.end()) { push_back(*it); ++it; } } list(std::initializer_list<T> il) { empty_init(); for (auto& e : il) { push_back(e); } } list<T>& operator=(list<T> tem)//赋值重载现代写法 { swap(tem); return *this; } void swap(list<T>& l)//自写swap 节省性能 { std::swap(_head, l._head); std::swap(_size, l._size); } void empty_init() { _head = new Node; _head->next = _head; _head->prev = _head; _size = 0; } void clear() { iterator it = begin(); while (it != end()) { it = erase(it); } } /*void push_back(const T&x) { Node* temp = new Node; temp->data = x; Node* prevp = _head->prev; prevp->next = temp; temp->prev = prevp; _head->prev = temp; temp->next = _head; _size++; }*/ iterator begin() { /*iterator it(_head->next); return it;*/ return _head->next; } iterator end() { /*iterator it(_head); return it;*/ return _head; } const_iterator begin()const { return _head->next; } const_iterator end()const { return _head; } iterator insert(iterator it, const T& x = T())//<1> { Node* prevp = it._node->prev; Node* now = it._node; Node* tem = new Node; tem->data = x; tem->prev = prevp; prevp->next = tem; tem->next = now; now->prev = tem; _size++; return tem; } void push_front(const T& x = T()) { insert(begin(), x); } void push_back(const T& x = T()) { insert(end(), x); } size_t size()const { return _size; } iterator erase(iterator pos)//<1> 迭代器失效 { assert(_size > 0); assert(pos != end()); Node* tem = pos._node; Node* prevp = tem->prev; Node* nextp = tem->next; prevp->next = nextp; nextp->prev = prevp; delete tem; tem = nullptr; --_size; return nextp; } void pop_front() { erase(begin()); } void pop_back() { erase(--end()); } bool empty()const { return _size == 0; } void resize(size_t n, const T& val = T()) { if (_size < n) { for (size_t i = _size; i < n; i++) { push_back(val); } } else { iterator it = begin(); for (size_t i = 0; i < n; i++) { ++it; } while (it != end()) { it = erase(it); } } } T& front() { return *begin(); } const T& front()const { return *begin(); } T& back() { return *(--end()); } const T& back()const { return *(--end()); } };
上面主要还是内部类来满足封装的思想,还有一个问题那就是迭代器失效,我们来分别研究一下吧。
Part4.1. 封装思想在list体现
封装思想的一个体现就是:我们要让使用者只看到怎么使用而不让他们染指底层。具体的,在list中的表现就是我们不能让list_node能在外面使用。而我们涉及到这个一共有两个途径:
1.通过list_node本身访问
2.通过list_iterator来间接访问
我们先来看一:我们可以在list中直接把 list_node设置为私有内部类,从而达到一个容器隔离的效果,使得外部不能访问到list_node。
其次二:二是比较麻烦解决的。首先由于list_iterator需要在外面能被访问。因此我们不能把它设置为私有,只能是公有。同时我们在下面的list实现还需要list的_node成员。但这个也简单,由于list_iterator是list的内部类,对于它的私有成员list也是可以直接访问的。但是由于list_iterator是模板类,对于模板类这个是行不通的,所以最后我们只能把list设为list_iterator的友元。这样,_node在list_iterator作为公有的情况下,以私有的身份同时做到了能在list内部使用和在外部不能使用,体现了封装。
Part4.2. 迭代器失效
我们来看看erase会导致的迭代器失效问题:
erase会导致迭代器失效,但同样是改变节点的insert却不会,读者可以自行思考一下。
那我们怎么解决这个问题呢?那就是---返回值,我们通过返回it的下一个节点的迭代器,这个举措同样方便了我们在clear()的删除。
这样我们就介绍完了list,最后来看看其他函数吧。
Part5. 其他函数
我们先来看看代码:
cppnamespace tool { template <class Container>//使用模板 使得这个函数可以适用于所有容器 void printf_container(const Container& con) { typename Container::const_iterator it = con.begin();//可以适用所有容器的原因是可以遍历的容器都会有迭代器这个结构 这是统一接口的思想 while (it != con.end()) { std::cout << *it << std::endl; ++it; } } }
Part6. 结语
上文我们实现了list这个标准库里面经典的一个类,我们也体会到了封装和统一接口的思想。运用好这两个思想我们将来的代码质量会更加的符合工业化的标准。
希望这篇博客会给你带来帮助~~~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!
最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

