马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
1 list 根本结构
l i s t list list 的底层就是我们之前学过的双向链表,它由一个哨兵位头结点 _pHead 和纪录链表长度的 _size 构成。
而链表中的每个节点都是由两个自身范例指针和一个存储数据的变量构成的。因此,我们不光要界说链表的类,还要界说节点的类
下面是 l i s t list list 的成员变量和团体框架
- namespace my_list
- {
- template<class T>
- struct ListNode
- {
- ListNode* _prev;
- ListNode* _next;
- T _val;
-
- //默认构造
- ListNode(const T& val = T())
- :_prev(nullptr)
- ,_next(nullptr)
- ,_val(val)
- {}
- };
- template<class T>
- class list
- {
- typedef ListNode<T> Node;//给节点重命名
- //成员函数···
-
- private:
- Node* _pHead;//哨兵位头结点指针
- size_t _size;
- }
- }
复制代码 由于我们要频仍访问节点,因此我们直接用 s t r u c t struct struct 界说节点,其成员变量默认全公有。
2 默认构造
我们先写一个简朴的无参默认构造出来。
默认构造可以直接如许写吗?
- list()
- :_pHead(nullptr)
- ,_size(0)
- {}
复制代码 不可以的,由于双向链表有个哨兵位。纵然链表中没有任何数据,头节点指针也是指向哨兵位,而不是空,以是我们应创建哨兵位,并将其初始化
而哨兵位的前驱指针 _ p r e v prev prev 和后继指针 _ n e x t next next,由于整个链表只有它自己,它的前一个和后一个节点都是自己,因此 _ p r e v prev prev 和 _ n e x t next next 都是指向哨兵位自己
- list()
- {
- _pHead = new Node;
- _pHead->_next = _pHead;
- _pHead->_prev = _pHead;
- _size = 0;
- }
复制代码 其是不光仅是无参的构造,全部的构造函数第一步都是初始化哨兵位,因此我们不妨单独写一个函数出来
- list()
- {
- CreateHead();
- }
- void CreateHead()
- {
- _pHead = new Node;
- _pHead->_next = _pHead;
- _pHead->_prev = _pHead;
- _size = 0;
- }
复制代码
这里有个标题就是CreateHead()黑白 c o n s t const const 成员函数,那界说 c o n s t const const 成员是否还能来调用呢?答案天然是可以的,由于 c o n s t const const 变量在界说的时间是不具有 c o n s t const const 属性的,界说完成之后才有。好比说:
- //如果在定义之前就具有const属性,那么n就无法赋值
- //const变量只有在定义时可以被赋值
- const int n = 10;
- const list<int> l1;
复制代码
3 迭代器
要模仿实现 l i s t list list ,迭代器的实现是此中的重中之重。
前面我们模仿实现 s t r i n g string string 和 v e c t o r vector vector,他们的迭代器都是原生指针,他们的原生指针完善符合迭代器的全部要求。其本质是由于他们底层物理空间是一连的。
但 l i s t list list 不像 s t r i n g string string 和 v e c t o r vector vector 那样天生丽质,它的底层结构是一个一个节点,并不一连,无法满足迭代器的要求(好比 ++,我们渴望的是迭代器跳到下一个节点,如果利用原生指针,由于不是一连的物理空间,当前节点 ++ 大概率是个野指针)。
但没关系, l i s t list list 可以通过封装,通过运算符重载,来满足迭代器的要求。
3.1 团体框架
迭代器其自己就是模仿指针的举动,既然节点的原生指针无法满足迭代器的要求,我们对节点指针举行封装,通过运算符重载让其满足迭代器的需求
- template<class T>
- struct ListIterator
- {
- typedef ListIterator<T> Self;//给自身类(迭代器)重命名,短一点方便
- typedef ListNode<T> Node;//节点重命名
- //成员变量:节点的指针
- Node* pNode;
- //成员函数
- //···
- };
复制代码 由于待会链表中要大量访问成员变量,我们直接用默认全公有的 s t r u c t struct struct
到如今,我们一共实现了三个类,为什么要实现三个类呢?我们先把每个类的作用过一遍:
- class list:链表这个类是链表的根本结构,指向哨兵位的头结点,管理这整个链表
- struct ListNode:节点这个类是由于链表中每个节点都是自界说范例,每个数据都是存在一个独立的结构体内里
- struct ListIterator:迭代器这个类,遍历整个链表原来是用节点的指针,但是节点的指针是不符合我们的预期,我们渴望有一个迭代器同一的方式举行遍历,因此我们用一个结构去封装节点的指针,封装以后通过重载运算符使节点的指针能到达迭代器那样的举动
3.2 成员函数
有了迭代器这个类,我们就可以运用运算符重载满足迭代器的举动啦
- template<class T>
- struct ListIterator
- {
- typedef ListIterator<T> Self;
- typedef ListNode<T> Node;
- //成员变量
- Node* pNode;
-
- //解引用
- T& operator*()
- {
- return pNode->_val;
- }
-
- //前置++
- Self& operator++()
- {
- pNode = pNode->_next;
- return *this;
- }
-
- //后置++
- Self& operator++(int)
- {
- Self tmp = *this;
- pNode = pNode->_next;
- return tmp;
- }
-
- //前置--
- Self& operator--()
- {
- pNode = pNode->_prev;
- return *this;
- }
-
- //后置--
- Self& operator--(int)
- {
- Self tmp = *this;
- pNode = pNode->_prev;
- return tmp;
- }
-
- //不等于
- bool operator!=(const Self& x)
- {
- return pNode != x.pNode;
- }
-
- //等于
- bool operator==(const Self& x)
- {
- return pNode == x.pNode;
- }
- }
复制代码
3.3 begin() 与 end() 的实现
迭代器的根本举动实现了,我们也可以在list类中实现begin()、end()等函数了
begin()函数是返回第一个迭代器,我们要构造第一个位置的迭代器,那怎么构造呢?我们用第一个节点的指针,即_pHead->_next,就能构造第一个迭代器,但如今ListIterator类中还缺少一个构造函数:
- //默认构造
- ListIterator(Node* p = nullptr)
- :pNode(p)
- {}
- //拷贝构造
- ListIterator(const ListIterator& x)
- {
- pNode = x.pNode;
- }
复制代码 着实上述拷贝构造可以不消实现,编译器会自己天生一个拷贝构造,完成浅拷贝,指向链表的节点。
这里不要以为有指针指向资源就要自己实现深拷贝,而是看指针所指向的资源是不是属于自己的。像 s t r i n g string string、 v e c t o r vector vector 那些就必要自己实现深拷贝,但迭代器指向的是链表的资源,并不是迭代器自己的,而且迭代器自己的目标就是指向链表的节点,以此来访问遍历链表,因此浅拷贝即可。
以是赋值重载和析构与不必要写
如今,万事具备只欠东风,我们还要在list类中将ListIterator类 t y p e d e f typedef typedef 成 i t e r a t o r iterator iterator
- template<class T>
- class list
- {
- typedef ListNode<T> Node;
- typedef ListIterator<T> iterator;//重命名为iterator
- //成员函数···
-
- private:
- Node* _pHead;
- size_t _size;
- }
复制代码 下面是begin()的实现
- iterator begin()
- {
- iterator it(_pHead->_next);
- return it;
- }
复制代码 上述是闻名对象的写法,我们可以用匿名对象的写法
- iterator begin()
- {
- return iterator(_pHead->_next);
- }
复制代码
乃至我们可以用隐式范例转换,直接传指针就好啦
迭代器自己就是节点的指针,指针节点指针自己不满足迭代器的那些需求,以是我们才用一个类把他封装一层,用重载运算符使其到达迭代器的要求。
- iterator begin()
- {
- return _pHead->_next;
- }
复制代码 end()是末了一个数据的下一个位置,就是哨兵位的头结点
- iterator end()
- {
- return _pHead;
- }
复制代码
3.4 operator-> 的实现
既然迭代器模仿的是指针的举动,那它还要实现 operator->
什么情况下用到 -> 运算符呢?
如今我们界说一个范例 AA,链表中存储的数据是 AA 范例,我们想依次遍历链表,打印每个节点 AA 中的两个成员变量
- struct AA
- {
- int _a1 = 1;
- int _a2 = 2;
- }
- void test1()
- {
- list<AA> lta;
- lta.push_back(AA());
- lta.push_back(AA());
- lta.push_back(AA());
- list<AA>::iterator it = lta.begin();
- while (it != lta.end())
- {
- cout << (*it)._a1 << " " << (*it)._a2 << endl;
- cout << it->_a1 << " " << it->_a2 << endl;
- }
- }
复制代码 上述(*it)._a1与it->_a1的写法是等价的,但如今还没重载->运算符
下面是operator->的实现方式
- T* operator->()
- {
- return &(pNode->_val);
- }
复制代码 operator->的实现方式非常奇怪,返回的是T*
着实这里省略了一个 ->,由于太丢脸了,为了可读性省略了一个 ->
- cout << it->_a1 << " " << it->_a2 << endl;
- //本质是这样的
- cout << it->->_a1 << " " << it->->_a2 << endl;
复制代码
第一个->是运算符重载出来的,第二个则是平凡的->
本质是如许的:
- cout << it.operator->()->_a1 << " " << it.operator->()->_a2 << endl;
- cout << &(pNode->_val)->_a1 << " " << &(pNode->_val)->_a2 << endl;
复制代码
3.5 const 迭代器
3.5.1 const 迭代器为什么定名 const_iterator
起首问各人一个标题: c o n s t const const 迭代器为什么是const_iterator,而不是const iterator呢
c o n s t const const 迭代器是自身不能修改还是指向的内容不能修改呢?
就和指针一样,指针的 c o n s t const const 有两个,一个在 * 之前,一个在 * 之后
- T* const ptr1//指针本身不能修改
- const T* ptr2//指向的内容不能修改
复制代码
如果是 const iterator ,const 直接修饰一个变量,就是这个变量自己不能修改,即迭代器自己不能修改,而我们 c o n s t const const 迭代器是要指向的内容不能修改,以是const_iterator更符合
3.5.2 const 迭代器的实现
那我们怎样让迭代器指向的内容不能修改呢?
迭代器修改我们指向的内容是怎么修改的?通过operator* 和operator->,那我们在其返回值上加上 c o n s t const const 就不能修改了
- const T& operator*(){ return pNode->_val;}const T* operator->()
- {
- return &(pNode->_val);
- }
复制代码 那我们就再自己实现一个 c o n s t const const 迭代器的封装吧
- template<class T>
- struct const_ListIterator
- {
- typedef const_ListIterator<T> Self;
- typedef ListNode<T> Node;
- Node* pNode;
- const_ListIterator(Node* p = nullptr)
- :pNode(p)
- {}
-
- const T& operator*()
- {
- return pNode->_val;
- }
- const T* operator->()
- {
- return &(pNode->_val);
- }
- Self& operator++()
- {
- pNode = pNode->_next;
- return *this;
- }
- Self& operator++(int)
- {
- Self tmp = *this;
- pNode = pNode->_next;
- return tmp;
- }
- Self& operator--()
- {
- pNode = pNode->_prev;
- return *this;
- }
- Self& operator--(int)
- {
- Self tmp = *this;
- pNode = pNode->_prev;
- return tmp;
- }
- bool operator!=(const Self& x)
- {
- return pNode != x.pNode;
- }
- bool operator==(const Self& x)
- {
- return pNode == x.pNode;
- }
- };
复制代码
3.5.3 归并两个迭代器
上面我们再重新封装了一个const_iterator,根本满足了需求,但是代码太冗余了,除了operator* 和operator-> 的返回值范例不一样,其他代码满是一样的,有什么办法将他们合二为一呢?
我们来看一下库中是怎么实现的
库中的__list_iterator类,用了三个模板参数,新增了 Ref 与 Ptr 两个参数。
在 l i s t list list 类中,将__list_iterator<T, T&, T*> t y p e d e f typedef typedef 成 i t e r a t o r iterator iterator,将__list_iterator<T, const T&, const T*> t y p e d e f typedef typedef 成 c o n s t const const_ i t e r a t o r iterator iterator。
也就是说 i t e r a t o r iterator iterator 的 R e f Ref Ref 参数即 T&, P t r Ptr Ptr 即参数 T*, c o n s t const const_ i t e r a t o r iterator iterator 的 R e f Ref Ref参数即 c o n s t const const T&, P t r Ptr Ptr 参数即 c o n s t const const T*
以operator* 举例:
T& 传给Ref,Ref 即reference,operator*的返回值是reference,更换过来operator*的返回值就是 T&
对 c o n s t const const_ i t e r a t o r iterator iterator
c o n s t const const T& 传给 R e f Ref Ref,更换过来operator*的返回值就是 c o n s t const const T&
着实,这种写法与上面我们自己实现两个类模板并没有本质区别,他们实例化出来都是两个差异的类。差异的是第一种写法是我们自己写了两个差异的类,而第二种是我们通过控制模板参数让编译器实例化出两个差异的类,把我们干的活交给了编译器
代码如下:
- template<class T, class Ref, class Ptr>
- struct ListIterator
- {
- typedef ListIterator<T, Ref, Ptr> Self;
- typedef ListNode<T> Node;
- Node* pNode;
- ListIterator(Node* p = nullptr)
- :pNode(p)
- {}
-
- Ref operator*()
- {
- return pNode->_val;
- }
- Ptr operator->()
- {
- return &(pNode->_val);
- }
- Self& operator++()
- {
- pNode = pNode->_next;
- return *this;
- }
- Self& operator++(int)
- {
- Self tmp = *this;
- pNode = pNode->_next;
- return tmp;
- }
- Self& operator--()
- {
- pNode = pNode->_prev;
- return *this;
- }
- Self& operator--(int)
- {
- Self tmp = *this;
- pNode = pNode->_prev;
- return tmp;
- }
- bool operator!=(const Self& x)
- {
- return pNode != x.pNode;
- }
- bool operator==(const Self& x)
- {
- return pNode == x.pNode;
- }
- };
复制代码
4 源码
对于 l i s t list list 的其他成员函数,与前面的 s t r i n g string string、 v e c t o r vector vector 实现起来都大同小异,这里就不再赘述了,我们直接看源码
- #pragma once
- #include<iostream>
- #include<assert.h>
- using namespace std;
- namespace my_list
- {
- template<class T>
- struct ListNode
- {
- ListNode* _prev;
- ListNode* _next;
- T _val;
- ListNode(const T& val = T())
- :_prev(nullptr)
- ,_next(nullptr)
- ,_val(val)
- {}
- };
- template<class T, class Ref, class Ptr>
- struct ListIterator
- {
- typedef ListIterator<T, Ref, Ptr> Self;
- typedef ListNode<T> Node;
- Node* pNode;
- ListIterator(Node* p = nullptr)
- :pNode(p)
- {}
-
- Ref operator*()
- {
- return pNode->_val;
- }
- Ptr operator->()
- {
- return &(pNode->_val);
- }
- Self& operator++()
- {
- pNode = pNode->_next;
- return *this;
- }
- Self& operator++(int)
- {
- Self tmp = *this;
- pNode = pNode->_next;
- return tmp;
- }
- Self& operator--()
- {
- pNode = pNode->_prev;
- return *this;
- }
- Self& operator--(int)
- {
- Self tmp = *this;
- pNode = pNode->_prev;
- return tmp;
- }
- bool operator!=(const Self& x)
- {
- return pNode != x.pNode;
- }
- bool operator==(const Self& x)
- {
- return pNode == x.pNode;
- }
- };
- template<class T>
- class list
- {
- typedef ListNode<T> Node;
- public:
- typedef ListIterator<T, T&, T*> iterator;
- typedef ListIterator<T, const T&, const T*> const_iterator;
- list()
- :_pHead(nullptr)
- , _size(0)
- {
- CreateHead();
- }
- list(int n, const T& value = T())
- {
- CreateHead();
- while (n--)
- {
- push_back(value);
- }
- }
- template <class Iterator>
- list(Iterator first, Iterator last)
- {
- CreateHead();
- Iterator it = first;
- while (it != last)
- {
- push_back(*it);
- ++it;
- }
- }
- list(const list<T>& l)
- {
- CreateHead();
- const_iterator it = l.begin();
- while (it != l.end())
- {
- push_back(*it);
- ++it;
- }
- }
- list<T>& operator=(list<T> l)
- {
- swap(l);
- return *this;
- }
- ~list()
- {
- clear();
- delete _pHead;
- _pHead = nullptr;
- }
- iterator begin()
- {
- return _pHead->_next;
- }
- iterator end()
- {
- return _pHead;
- }
- const_iterator begin() const
- {
- return _pHead->_next;
- }
- const_iterator end() const
- {
- return _pHead;
- }
- size_t size()const
- {
- return _size;
- }
- bool empty()const
- {
- return _size == 0;
- }
- T& front()
- {
- return _pHead->_next->_val;
- }
- T& back()
- {
- return _pHead->_prev->_val;
- }
- const T& front() const
- {
- return _pHead->_next->_val;
- }
- const T& back() const
- {
- return _pHead->_prev->_val;
- }
- void push_back(const T& val)
- {
- Node* p = new Node(val);
- p->_next = _pHead;
- _pHead->_prev->_next = p;
- p->_prev = _pHead->_prev;
- _pHead->_prev = p;
- ++_size;
- }
- void pop_back()
- {
- Node* p = _pHead->_prev;
- p->_prev->_next = _pHead;
- _pHead->_prev = p->_prev;
- delete p;
- --_size;
- }
- void push_front(const T& val)
- {
- Node* p = new Node(val);
- p->_next = _pHead->_next;
- p->_prev = _pHead;
- _pHead->_next = p;
- p->_next->_prev = p;
- ++_size;
- }
- void pop_front()
- {
- Node* p = _pHead->_next;
- _pHead->_next = p->_next;
- p->_next->_prev = _pHead;
- delete p;
- --_size;
- }
- iterator insert(iterator pos, const T& val)
- {
- Node* p = new Node(val);
- p->_next = pos.pNode;
- p->_prev = pos.pNode->_prev;
- pos.pNode->_prev->_next = p;
- pos.pNode->_prev = p;
- ++_size;
- return pos;
- }
- iterator erase(iterator pos)
- {
- assert(pos != end());
- iterator ret = pos.pNode->_next;
-
- pos.pNode->_prev->_next = pos.pNode->_next;
- pos.pNode->_next->_prev = pos.pNode->_prev;
- delete pos.pNode;
- --_size;
- return ret;
-
- }
- void clear()
- {
- iterator it = begin();
- while (it != end())
- {
- iterator cur = it++;
- delete cur.pNode;
- }
- _pHead->_next = _pHead;
- _pHead->_prev = _pHead;
- _size = 0;
- }
- void swap(list<T>& l)
- {
- std::swap(_pHead, l._pHead);
- std::swap(_size, l._size);
- }
- private:
- Node* _pHead;
- size_t _size;
- void CreateHead()
- {
- _pHead = new Node;
- _pHead->_next = _pHead;
- _pHead->_prev = _pHead;
- _size = 0;
- }
- };
- }
- template<class Container>
- void print_container(const Container& v)
- {
- auto it = v.begin();
- while (it != v.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
- }
复制代码
好啦,本期关于 l i s t list list 的知识就先容到这里啦,渴望本期博客能对你有所资助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!
|