编写有迭代器的链表(C++专用)
来源:互联网 发布:网络吃鸡是什么意思啊 编辑:程序博客网 时间:2024/06/08 12:05
为何要编写有迭代器的链表
在编写链表时可以这么做:
template <typename T>class List{public: /****/ typedef List* iterator;};int main(){ List<int>::iterator it;}
注意一定要写在public
中,否则无法使用。
但是这样有一个很严重的问题:那就是由于iterator
的本质仍然是指针,它将无法对越界进行检验。
编写分析
方法
使用嵌套类可以做到。
附注:
C++
中的嵌套类和外层类没有任何关系,相互之间没有访问权限,这点要注意!
大致结构
template <typename T>class List{private: struct Node { T val; Node *next; Node *prev; Node(T va = 0, Node *pre = nullptr, Node *ne = nullptr) : val(va), prev(pre), next(ne) {} };public: class const_iterator { /**/ }; class iterator : public const_iterator { /**/ };public: /*-------------Constuctor & BigThree-------------*/ List() { /**/ } //注意本函数的实现技巧 List(const List &list) { /**/ } //注意返回值 const List &operator=(const List &list) { /**/ } ~List() { /**/ } /*-------------Manipulate-------------*/ void Clear() { while (!IsEmpty()) { Pop_back(); } } iterator Insert(const iterator &iter, const T &val) { /**/ } iterator Erase(const iterator &iter) { /**/ } iterator Erase(const iterator &beg, const iterator &end) { /**/ } void Push_back(const T &val) { Insert(end(), val); } void Pop_back() { Erase(--end(), val); } /*-------------Iterator-------------*/ iterator begin() { /**/ } //尾后的const一定要写,因为它也是签名之一,写上去才能视作有效的重载 const_iterator begin() const { /**/ } iterator end() { /**/ } const_iterator end() const { /**/ } /*-------------Info-------------*/ void Print() { Node *p = head->next; cout << "[" << p->val; p = p->next; while (p != tail) { cout << ", " << p->val; p = p->next; } cout << "]" << endl; } bool IsEmpty() const { return size == 0; } size_t Size() const { return size; } T & front() { return *begin(); } const T & front() const { return *begin(); } T & back() { return *--end(); } const T & back() const { return *--end(); }private: void Init() { size = 0; head = new Node; tail = new Node; tail->prev = head; head->next = tail; } Node *head; Node *tail; size_t size;};
注意到这里我们同时实现了两个迭代器const_iterator
和iterator
。这里的技巧在于让iterator
继承const_iterator
。这是因为这两种迭代器的不同之处只在operator*
时才体现出来————一个返回普通引用,而另一个返回指向常量的引用,其他的都一样。所以我们可以使用继承机制来实现。接下来对继承关系进行分析:iterator
可以当作const_iterator
来使用,而反之不然。也就是说iterator
is-aconst_iterator
,所以我们令const_iterator
为父类。
异常处理
我们编写迭代器的目的就在于要处理异常情况,所以我们需要编写一个异常类来处理相关问题:
class IteratorMisMatchException : public exception{public: IteratorMisMatchException(const char * inf) : info(inf) {} string what() { return info; }private: string info;};
iterator
编写
先考虑数据域的问题,这里很简单我们可以用一个Node *cur
,来表示迭代器现在指向的位置(我们待会会说明这是不够的)。唯一要注意的是,不要写成private
,不然iterator
将无法访问数据域!
构造问题
再考虑迭代器应该给外界留下那些接口:
- 用于构造的接口
- 各种operator
对于后者我们没有什么问题,这是给用户的接口,让我们的迭代器能像指针一样使用,因此编写的运算符重载要包括: ++
, --
, +
, -
, *
, ==
, !=
。理所当然,访问权限要是public
。
关键在于前者,我们要仔细考虑一下要有那些构造方法。首先,一个默认构造函数是必不可少的,这样就可以声明一个不指向任何结点的迭代器。
class const_iterator{public: const_iterator() : cur(nullptr) {}//注意这个protectedprotected: Node *cur;};
重点来了,我们接下来会很理所当然的写出这样的构造函数:
class const_iterator{public: const_iterator() : cur(nullptr) {} //顺便说一下,可以写成Node *p, 但不要写成const Node *p; 否则会出现底层const不相容问题 const_iterator(Node * const p) : cur(p), theList(list) {}protected: Node *cur;};
表面上这没什么问题,但是这样的话就会出现这种情况:
int main(){ const_iterator it(node); return 0;}
似乎没什么,但问题是node
是List
的封装成员,如果将const_iterator(Node * const p)
作为对外的构造接口的话,就势必意味这封装性被破坏。所以const_iterator(Node * const p)
不能设为public
而应该是protected
!
那么外部应该怎样获取一个有效的迭代器呢?我们可以联想到标准库的做法:begin()
和end()
。所以这样一来我们的对外构造模型就成立了:外界通过调用list.begin()
方法,获取一个迭代器对象。而begin()
内部则会构造一个迭代器对象return
出去。
iterator begin(){ /**/}//尾后的const一定要写,因为它也是签名之一,写上去才能视作有效的重载const_iterator begin() const{ /**/}iterator end(){ /**/}const_iterator end() const{ /**/}
这里我们有两个版本的的begin()
,尾后的const
不能省略,它属于方法签名的一员,不加的话会无法重载函数。
现在还有一个问题,虽然我们通过将方法声明为protected
避免了破坏封装,但是现在外部类List<T>
也无法使用该构造方法了。解决该问题的途径用到一个小技巧:将List<T>
声明为友元类。
class const_iterator{public: const_iterator() : cur(nullptr) {}protected: //注意! friend class List<T>; Node *cur; const_iterator(Node * const p) : cur(p), theList(list) {}};
opeartor
编写operator
总体来讲较为简单,要注意的有两点:
- const_iterator
和iterator
存在一些细微差别,需要重写。
- 前置++
与后置++
返回不同。
const_iterator
:
const T &operator*() const{ return cur->val;}//注意前置++返回引用const_iterator &operator++(){ cur = cur->next; return *this;}//后置++不可返回引用const_iterator operator++(int){ const_iterator old(theList, this->cur); //调用前置++ ++(*this); return old;}bool operator==(const const_iterator &iter) const{ return cur == iter.cur;}bool operator!=(const const_iterator &iter) const{ //调用operator== return !(*this == iter);}
iterator
:
T &operator*(){ return cur->val;}//注意const T &operator*() const{ return const_iterator::operator*();}//重写iterator &operator++(){ cur = cur->next; return *this;}
注意这里iterator
中保留了const T &operator*() const
。这是因为要考虑到const List<T>::iterator it
这样的迭代器,显然它不能调用非const
方法。
List<T>
编写
构造函数和BigThree
在这里我们主要展示一下代码重用的思想,其实这几个方法本身并没什么特殊的:
class List{public: List() { Init(); } //注意本函数的实现技巧 List(const List &list) { Init(); operator=(list); } //注意返回值 const List &operator=(const List &list) { if (this == &list) { return *this; } Clear(); for (const_iterator it = list.begin(); it != list.end(); ++it) { Push_back(*it); } return *this; } ~List() { Clear(); delete head; delete tail; }private: void Init() { size = 0; head = new Node; tail = new Node; tail->prev = head; head->next = tail; }};
迭代器函数
较为简单,不做赘述。(不过注意这里写的函数并不完美,因为关于迭代器类我们稍后还要做些优化)
iterator begin(){ return iterator(head->next);}//尾后的const一定要写,因为它也是签名之一,写上去才能视作有效的重载const_iterator begin() const{ return const_iterator(head->next);}iterator end(){ return iterator(tail);}const_iterator end() const{ return const_iterator(tail);}
对于const list
而言我们返回常迭代器,这一点是符合标准的。
插入与删除
iterator Insert(const iterator &iter, const T &val){ Node *p = iter.cur; ++size; //注意技巧 return iterator(this, p->prev = p->prev->next = new Node(val, p->prev, p));}iterator Erase(const iterator &iter){ Node *p = iter.cur; //erase的返回约定 iterator save(this, p->next); p->prev->next = p->next; p->next->prev = p->prev; delete p; --size; return save;}iterator Erase(const iterator &beg, const iterator &end){ //不要++it! for (iterator it = beg; it != end; ) { it = Erase(it); } return end;}
要注意的是参数一定要写成const iterator &
,而非iterator &
。这是因为在我们的程序中会出现这种调用形式:
Erase(--end());
如果写成后者的形式,就会出现左值引用尝试去绑定右值的情形,所以一定要写做const iterator &
。
以上程序存在的问题
到目前为止我们已经把大体的框架打好了,但是,以上程序的健壮性是极差的。
越界检查
这是很理所当然的,毕竟我们构建迭代器类的目的就在于此。我们可以在类中定义一个会抛出异常的函数:
class const_iterator{private: void AssertRange() const { if (this->cur == nullptr) { throw IteratorMisMatchException("Iterator went out of the range!"); } }};
在涉及移动迭代器的操作中就可以这样:
iterator &operator++(){ this->cur = this->cur->next; this->AssertRange(); return *this;}
迭代器有效问题
我们在使用迭代器之前,首先得确定它是一个有效的迭代器。比如Erase(it)
,如果it
无效则很显然会有问题。
我们先讲解前两种情况,也是较为简单的情况
- 迭代器未初始化
- 迭代器指向了尾端
这两种较为简单,如果未初始化,则cur
一定为nullptr
;若在尾端,则cur->next
或cur->prev
一定为nullptr
:
void AssertValidity() const{ if (theList == nullptr || cur == nullptr || cur->next == nullptr || cur->prev == nullptr) { throw IteratorMisMatchException("The iterator is invalid!"); }}
难点在于第三种情况:
- 迭代器有效但不匹配
来考虑这样一个情形:
List<int> l1;List<int> l2;/**添加元素**/l1.Insert(l2.end(), 5);l2.Erase(l1.begin(), l2.end());
可以看到,虽然我们传入的是有效迭代器,但却和链表根本不匹配。尤其是第二个实例,会陷入死循环的境地。
为了改变这一情形,我们这里使用一个技巧。我们在迭代器类中额外增加一个记录了它指向的链表的指针,用作标识。
class const_iterator{ /**/ friend class List<T>; const_iterator(const List<T> *list, Node * const p) : cur(p), theList(list) {} //Attention!!! const List<T> *theList;};
然后我们就可以修改之前的方法,让它们更加健壮:
iterator Insert(const iterator &iter, const T &val){ //检查迭代器是否有效,因为在end()前插入是允许的,所以排除该特殊情况 if (iter != end()) { iter.AssertValidity(); } //检查是否是本链表上的迭代器 if (iter.theList != this) { throw IteratorMisMatchException("Iterator dose not refer to this list"); } Node *p = iter.cur; ++size; return iterator(this,p->prev = p->prev->next = new Node(val, p->prev, p));}iterator Erase(const iterator &iter){ iter.AssertValidity(); if (iter.theList != this) { throw IteratorMisMatchException("Iterator dose not refer to this list"); } Node *p = iter.cur; //erase的返回约定 iterator save(this, p->next); p->prev->next = p->next; p->next->prev = p->prev; delete p; --size; return save;}iterator Erase(const iterator &beg, const iterator &end){ beg.AssertValidity(); if (end != this->end()) { end.AssertValidity(); } //检查是否指向一个表,以及是否指向本表 if (beg.theList != end.theList) { throw IteratorMisMatchException("Beg and end refer to different list!"); } else if (beg.theList != this) { throw IteratorMisMatchException("Iterator dose not refer to this list"); } //不要++it! for (iterator it = beg; it != end; ) { it = Erase(it); } return end;}
然后在修改一下其他函数中的构造函数参数:
iterator begin(){ return iterator(this, head->next);}
至此,一个带有迭代器的且较为健壮的链表就实现成功了!
- 编写有迭代器的链表(C++专用)
- C 编写的链表
- n97mini专用的c盘空间不足内存释放解决办法
- 扫盲专用c语言中的#号的作用
- 线性链表的表示以及实现(C语言编写)
- C编写循环链表,并删除其中的元素
- c语言:编写一个输出链表的函数print
- 触发器的2个专用表
- C编写的通讯录
- c语言的编写
- Unity3D教程:不同版本及发布平台编写专用的程式码
- Unity3D教程:不同版本及发布平台编写专用的程式码
- Unity3D不同版本及发布平台编写专用的程式码
- 外链工作表中文版(SEO专用)
- Socket C/C++ 菜鸟专用
- C语言面试专用题库
- 空空的专用记事本
- Python的专用方法
- nginx-配置location块
- Linux学习(四)
- react-native 填坑之旅 数据库存储 realm
- linux安装node/npm
- maven ssm项目报错javaserver faces 2.2 can not been 。。。
- 编写有迭代器的链表(C++专用)
- 基于矢量切片的电子地图配图(七)配置水系
- 1799:最短前缀( 4.6算法之贪心)
- crf 的视察 (二分答案)
- C语言位运算妙用
- 进制均值
- Go内存对齐
- 基于矢量切片的电子地图配图(八)配置兴趣点
- 利用数组随机点名