使用迭代器操作单链表

目录

一、引言

二、举例说明

2.1 实现结点类

2.2 实现单链表类 

2.3 实现迭代器 

2.4 实现全局查找函数 

三、测试用例 

四、结束语 


一、引言

迭代器功能类似指针,也可以说迭代器就是一个“指针”。指针最重要的两个功能就是*和->。前者是解引用;后者是指向所指之物的成员。


二、举例说明

为了看起来更加直观,将一个源文件分割成若*分:


2.1 实现结点类

结点类包含2个私有数据成员:元素和指针。公有方法有:构造方法以及两个接口(返回元素的值、返回指针的值)。我们还知道,在其他类中是没有办法访问本类私有成员的,很明显:头插法insert_front(T value)和尾插法insert_back(T value)需要改变结点的(私有)元素和(私有)指针,故而添加有元——封装和暴露之间我们需要做一个均衡。

析构方法在这种情形下,使用默认的析构方法是可以的。因为并没有向堆申请空间!

template <typename T>
class ListItem		//结点类
{
public:
	ListItem(T value = 0, ListItem *next = 0):_value(value),_next(next){}
	T value() const { return _value; }	//返回本结点元素值
	ListItem *next() const { return _next; }	//返回本结点的指针值
private:
	T _value;
	ListItem* _next;
	friend void List<T>::insert_front(T value);
	friend void List<T>::insert_back(T value);
};

2.2 实现单链表类 

单链表的实现方式多种多样,但只要不违背单链表的定义(“结点的序列”表示线性表)就可以了。在这里,我采用不带头结点的实现方式。类中私有成员是:头指针、尾指针、记录当前结点个数的变量。公有方法有:构造、头插、尾插、返回头指针的值、头删、析构(一旦向堆区申请空间必须要实现自己的析构方法,否则会造成内存泄露)、打印输出、返回链表大小。

#include<assert.h>
#include<iostream>
using namespace std;

template<typename T>
class ListItem;

template<typename T>
class List		//单链表类
{
public:
	List() { _front = NULL; _end = NULL; _size = 0; }	//构造函数
	void insert_front(T value);		//头插
	void insert_back(T value);		//尾插
	ListItem<T>* front();        //返回头指针的值
	void delete_front();		//头删
	~List();	//析构
	void display(std::ostream &os = std::cout) const;	//打印输出
	int size();
private:
	ListItem<T> *_front;
	ListItem<T> *_end;
	long _size;
};

template<typename T>
void List<T>::insert_front(T value)		//头部插入
{
	ListItem<T> *p = new ListItem<T>;		//申请一个结点
	assert(NULL != p);
	p->_value = value;
	if (NULL == _front && NULL == _end)		//单链表为空时,头指针和尾指针都要移动;
	{
		_front = _end = p;
	}
	else {			//单链表非空时,只需移动头指针;
		p->_next = _front;
		_front = p;
	}
	_size++;
}

template<typename T>
void List<T>::insert_back(T value)		//尾插法
{
	ListItem<T> *p = new ListItem<T>;		//申请一个结点
	assert(NULL != p);
	p->_value = value;
	if (NULL == _front && NULL == _end)		//单链表为空时,头指针和尾指针都要移动;
	{
		_front = _end = p;
	}
	else
	{
		_end->_next = p;
		_end = p;
	}
	_size++;
}

template<typename T>
ListItem<T>* List<T>::front()        //返回头指针的值
{
	return _front;
}

template<typename T>
void List<T>::delete_front()        //头删,单链表尾删不方便处理
{
	int size = this->size();
	if (0 == size) return;	//单链表已空,无法删除
	if (1 == size)	//单链表中只含1个节点
	{
		delete _front;
		_front = _end = NULL;
	}
	else 
	{
		ListItem<T>*p = _front->next();
		delete _front;
		_front = p;
	}
	--_size;
}

template<typename T>
List<T>::~List()        //析构
{
	cout << "~List()析构函数正被调用" << endl;
	while (this->size())
	{
		delete_front();
	}
}

template<typename T>
void List<T>::display(std::ostream & os) const        //打印输出链表
{
	ListItem<T> *p = _front;
	while (NULL != p && p != _end)
	{
		os << p->_value<<"  ";
		p = p->next();
	}
	os << endl;
}

template<typename T>
int List<T>::size()        //返回当前链表大小
{
	return _size;
}

2.3 实现迭代器 

结构体实现,默认权限为public

迭代器是对指针的一次“封装”,指针能做到的事情,迭代器也自然需要做到(如果做不到的话为什么要使用迭代器呢?)。迭代器的成员是指针。迭代器需要做到的事有:解引用、访问数据成员、自加(前置和后置)、赋值、逻辑(不)相等······并没有列举完,只是将典型的拿出来讨论了。

在撰写这篇博客的过程当中,对自增运算符的重载遇到了比较多的问题,大家引起注意!

一部分代码段:

ListIter& operator ++()	//pre-increment operator
	{
		_ptr = _ptr->next();
		return *_this;
	}
    //必须先实现前置++的重载才能去实现后置++的重载,否则语句++*this;会报错,编译器会提示你没有实现前置++运算符的重载函数!
	ListIter operator++(int)	//post-increment operator
	{
		ListIter tmp = *this;    
		++*this;
		return tmp;        //返回值是迭代器自增之前的值
	}

完整代码段 :

template<class Item>	//迭代器适用于任何形态的结点;因为结构体中含有自增,故而该迭代器特定只为链表服务。
struct ListIter
{
	Item *_ptr;
	ListIter(Item *p = 0):_ptr(p){}		//构造方法
	Item &operator *() const { return *_ptr; }
	Item *operator ->() const { return _ptr; }
	//不必实现copy ctor和operator=,因为编译器提供的缺省行为已经足够
	ListIter& operator ++()	//pre-increment operator
	{
		_ptr = _ptr->next();
		return *_this;
	}
    //必须先实现前置++的重载才能去实现后置++的重载,否则语句++*this;会报错,编译器会提示你没有实现前置++运算符的重载函数!
	ListIter operator++(int)	//post-increment operator
	{
		ListIter tmp = *this;    
		++*this;
		return tmp;        //返回值是迭代器自增之前的值
	}
	bool operator == (const ListIter &i) const
	{
		return _ptr == _i._ptr;
	}
	bool operator !=(const ListIter &i)const
	{
		return _ptr != i._ptr;
	}
};

2.4 实现全局查找函数 

template<typename T>
bool operator != (const ListItem<T>& item, T n)
{
	return item.value() != n;
}
template<class InputIterator, class T>		//全局函数:功能为查找元素
InputIterator ownfind(InputIterator first, InputIterator last, const T& value)
{
	while (first != last && *first != value)
		++first;
	return first;
}

说明:为什么要重载“!=”运算符呢? 可以想象一下,在函数ownfind使用过程中,一定是ownfind(first, last, value),迭代器所指之物是结点本身,那么对迭代器进行解引用的话——是结点类型,结点类型如何能去与结点中元素值去比较呢?那有人有疑问了,结点不是实现了返回元素值的方法value()吗?直接把ownfind函数改造一下不就好了?就像下面的样子。我的回答是:ownfind函数在设计的时候并不知道迭代器所指对象中有无包含该方法,按照下面的写法,是对ownfind函数泛型的限制。我们可以在获取到ownfind函数,再进行处理。就像全局查找函数实现的那样去做。

培养泛型思维是很重要的!

template<class InputIterator, class T>		//全局函数:功能为查找元素
InputIterator ownfind(InputIterator first, InputIterator last, const T& value)
{
	while (first != last && *first.value() != value)
		++first;
	return first;
}

三、测试用例 

int main()
{
	List<int> mylist;
	for (int i = 0; i < 5; ++i)
	{
		mylist.insert_front(i);
		mylist.insert_back(i + 2);
	}
	mylist.display();
	//对于迭代器来说,参数列表中应该填写其所指之物
	ListIter<ListItem<int>>begin(mylist.front());
	ListIter<ListItem<int>>end;		//default 0, NULL
	ListIter<ListItem<int>>iter;	//defaule 0, NULL
	iter = ownfind(begin, end, 90);
	if (iter == end)
		cout << "not found!" << endl;
	else
		cout << "found" << endl;
	system("pause");
	return 0;
}
使用迭代器操作单链表
图1 VS2017下运行结果

四、结束语 

例子虽小,但是帮助我们了解迭代器还是绰绰有余的。计算机科学是讲究实用性的,有时候实用比理论更重要。吾尝终日而思矣,不如登高之博见也。