使用C++实现双向链表List

参考教程《数据结构与算法分析 C++描述 》第三版和第四版

这次写的过程中,发现的两个新的 问题:

第一:自己定义的数据结构UDT如果需要使用C++11中的范围for语句,需要定义begin和end函数。如果遍历的是常量对象,则要有相应的常量对象可用的begin和end函数。

声明形式如下:

iterator begin();

const_iterator begin() const;

第二个问题主要的问题集中在operator=函数的实现。有两个大类的实现:

1、使用自赋值检查->先创建临时的对象(防止申请内存过程中出现异常,便于处理异常,而且不损坏原对象)->释放原对象->接管临时对象

2、第二种是使用copy and swap,这种方法使用的时候(1)如果直接使用std::swap,则需要定义移动赋值函数,并且移动赋值函数中需要对类成员一一进行交换,而不能直接交换对象,否则会出现赋值操作符反复的调用,最后堆栈溢出。(2)使用自己定义的swap函数,在这个里面可以使用std::swap()函数,把类成员一一交换。



这个程序还有很多地方需要补充,主要体现在错误控制方面,比如没有对传入erase函数的迭代器进行有效性检查(如果传入的是指向另一个对象的迭代器呢?)等等。。。



记录一下今天的代码:

首先是mylist.h文件,定义了List模板

#ifndef MYLIST_H
#define MYLIST_H

#include // 在 赋值操作符重载的时候,可能会用到std::swap函数
/*
* 双向链表模板,包含表头节点(m_head指向)和尾节点(m_tail指向)
*/
template
class List
{
public: 
	typedef unsigned int size_t;
	typedef Object value_type;
	
private:
	// 节点通过struct定义,数据成员虽然默认都为public的访问级别,但是其本身是private的
	struct Node{
		value_type sNode_data; // 存储的内容
		Node *sNode_pprev; // 前一个节点
		Node *sNode_pnext; // 后一个节点

		Node(const value_type & data = value_type(),
			Node *prev = nullptr,
			Node *next = nullptr)
			: sNode_data(data), sNode_pprev(prev), sNode_pnext(next){}
	};

public:
	// 定义常迭代器和普通迭代器
	class const_iterator{
	public:
		const_iterator() : p_current(nullptr) {}
		const value_type & operator*() const {
			return retrieve();
		}
		const_iterator & operator++(){
			p_current = p_current->sNode_pnext;
			return *this;
		}
		const_iterator & operator++(int){
			const_iterator old = *this;
			++(*this);
			return old;
		}
		const_iterator & operator--(){
			p_current = p_current->sNode_pprev;
			return *this;
		}
		const_iterator & operator--(int){
			const_iterator old = *this;
			--(*this);
			return old;
		}

		bool operator==(const const_iterator & rhs) const
		{
			return p_current == rhs.p_current;
		}
		bool operator!=(const const_iterator & rhs) const
		{
			return p_current != rhs.p_current;
		}

	protected:
		Node *p_current;
		value_type & retrieve() const{
			return p_current->sNode_data;
		}
		const_iterator(Node *p) : p_current(p) {}
		
		friend class List < value_type > ;
	};

	class iterator : public const_iterator {
	public:
		iterator() {}
		value_type & operator*(){
			return retrieve();
		}
		const value_type & operator*() const {
			return retrieve();
		}
		
		iterator & operator++(){
			p_current = p_current->sNode_pnext;
			return *this;
		}
		iterator & operator++(int){
			iterator old = *this;
			++(*this);
			return old;
		}

		iterator & operator--(){
			p_current = p_current->sNode_pprev;
			return *this;
		}
		iterator & operator--(int){
			iterator old = *this;
			--(*this);
			return old;
		}

	protected:
		iterator(Node *p) : const_iterator(p) {}
		friend class List < value_type > ;
	};


public:
	// big-three
	List() {
		init();
	}
	List(const List & rhs) {
		init();
		for (const_iterator citr = rhs.cbegin();
			citr != rhs.cend();
			++citr)
			push_back(*citr);
		// 由于List内定义了begin和end函数,因此可以使用C++11中的范围for语句,如下:
#if 0
		for ( auto & x : rhs)
			push_back(x);
#endif
	}
	~List() {
		clear();
		delete m_head;
		delete m_tail;
	}
	const List & operator=(const List & rhs) {
		if (this == &rhs)
			return *this;
		List *tmp = new List(rhs); // 这里可以检查异常,防止从heap申请空间失败
		// 下面三行释放当前对象空间
		clear();
		delete m_head;
		delete m_tail;
		// 下面三行接管新开辟的内存
		m_theSize = tmp->m_theSize;
		m_head = tmp->m_head;
		m_tail = tmp->m_tail;	

		// 第二种实现方法,可以不用证同测试,也能实现异常安全
		// 但是使用了STL 中的 std::swap 函数 (此时,必须定义移动赋值函数,不然会出现死循环)
		// 因为std::swap中可能又使用了赋值操作符,如果没有定义移动赋值函数,则会出现反复调用自身
		// 从而产生死循环,最后堆栈溢出
		// 如果有的话,则调用了移动构造函数,避免的自身的调用

		// 当然也可以吧移动构造函数中:
		// std::swap(m_theSize, tmp.m_theSize);
		// std::swap(m_head, tmp.m_head);
		// std::swap(m_tail, tmp.m_tail);
		// 直接替换 std::swap(*this, tmp);也可以完成赋值操作
				
	    // 当然,也可以自己为List定制swap,让每个成员对换,实现在后面private区域中
		// 函数结束后,tmp会析构,但是tmp的内容和*this互换了,所以析够的是*this原来的内容
#if 0
		List tmp(rhs);
		swap(*this, tmp);
		/*
		std::swap(m_theSize, tmp.m_theSize);
		std::swap(m_head, tmp.m_head);
		std::swap(m_tail, tmp.m_tail);
		*/
#endif

		return *this;
	}

	// 移动赋值函数
	List & operator= (List && rhs)
	{
		std::swap(m_theSize, rhs.m_theSize);
		std::swap(m_head, rhs.m_head);
		std::swap(m_tail, rhs.m_tail);
		return *this;
	}

	// 返回普通迭代器的begin函数 和 返回常迭代器的begin函数
	// 该迭代器指向第一个元素节点(m_head的下一个节点)
	iterator begin() {
		return iterator(m_head->sNode_pnext);
	}
	const_iterator begin() const {
		return const_iterator(m_head->sNode_pnext);
	}
	const_iterator cbegin() const {
		return const_iterator(m_head->sNode_pnext);
	}

	// 返回普通迭代器的end函数 和 返回常迭代器的end函数
	// 该迭代器指向最后一个元素的下一个节点(即m_tail指向的位置)
	iterator end() {
		return iterator(m_tail);
	}
	const_iterator end() const {
		return const_iterator(m_tail);
	}
	const_iterator cend() const {
		return const_iterator(m_tail);
	}

	// 返回链表中元素的个数
	int size() const {
		return m_theSize;
	}

	// 判断链表是否为空
	bool empty() const{
		return 0 == m_theSize;
	}

	// 清空链表中的元素
	void clear(){
		while (!empty())
			pop_front();
	}

	// 返回链表中第一个的元素
	value_type & front() {
		return *begin();
	}
	const value_type & front() const{
		return *beign();
	}

	// 返回链表中最后一个元素
	value_type & back(){
		return *(--end());
	}
	const value_type & back() const{
		return *(--end());
	}

	// 在链表的第一个元素之前插入x
	void push_front(const value_type & x){
		insert(begin(), x);
	}

	// 在链表最后一个元素之后插入x
	void push_back(const value_type & x){
		insert(end(), x);
	}

	// 删除链表中第一个元素
	void pop_front(){
		erase(begin());
	}

	// 删除链表中最后一个元素
	void pop_back(){
		erase(--end());
	}

	// 在itr之前插入元素x,返回新插入元素的位置
	iterator insert(iterator itr, const value_type & x){
		Node *p = itr.p_current;
		++m_theSize;
		Node *newNode = new Node(x, p->sNode_pprev, p);
		p->sNode_pprev->sNode_pnext = newNode;
		p->sNode_pprev = newNode;
		return iterator(newNode);
	}

	// 删除itr指向的元素,返回被删除元素之后的那一项的位置
	iterator erase(iterator itr){
		Node *p = itr.p_current;
		iterator retVal(p->sNode_pnext);
		p->sNode_pprev->sNode_pnext = p->sNode_pnext;
		p->sNode_pnext->sNode_pprev = p->sNode_pprev;
		delete p;
		--m_theSize;
		return retVal;
	}
	iterator erase(iterator start, iterator end)
	{
		for (auto itr = start;
			itr != end;
			itr = erase(itr)); 
		// 在STL使用中,遍历一个容器时,删除一个元素的方法也是如此。
		// 要注意迭代器失效的问题。
		// 对于List,由于其存储结构,for中的第三部分也可以写成 erase(itr++);
		return end;
	}

private:
	// init函数用于生成一个空链表
	void init() {
		m_theSize = 0;
		m_head = new Node;
		m_tail = new Node;
		m_head->sNode_pnext = m_tail;
		m_tail->sNode_pprev = m_head;
	}
	// 把a和b交换
	void swap(List & a, List &b)
	{
		std::swap(a.m_theSize, b.m_theSize);
		std::swap(a.m_head, b.m_head);
		std::swap(a.m_tail, b.m_tail);
	}

private:
	size_t m_theSize;
	Node *m_head;
	Node *m_tail;

};

#endif


测试用的代码

demoTest.cpp

#include
#include"mylist.h"
#include

using namespace std;

int main()
{
	cout << "*** ==== 测试无参构造函数" << endl;
	List a;
	if (a.empty())
		cout << "    ---- 链表已经调用无参构造函数,此时链表为空" << endl;
	cout << endl;
	//---------
	cout << "*** ==== 测试push_back和push_front函数(相当于测试了insert函数)" << endl;
	a.push_back(1);
	a.push_back(2);
	a.push_back(3);
	a.push_front(10);
	a.push_front(12);
	cout << "*** ==== 测试begin和end函数,以及迭代器中的自增操作符" << endl;
	cout << "    ---- 链表元素为:";
	for (List::const_iterator cit = a.begin();
		cit != a.end();
		++cit)
		cout << *cit << " ";
	cout << endl;
	cout << endl;
	//---------
	cout << "*** ==== 测试pop_front 和 pop_back函数(相当于测试了erase函数)" << endl;
	cout << "*** ==== 测试front和back函数" << endl;
	a.pop_back();
	a.pop_front();
	cout << "    ---- 此时第一个元素为 " << a.front()
		<< " 最后一个元素为 " << a.back() << endl;
	cout << endl;
	//---------
	cout << "*** ==== 测试拷贝构造函数" << endl;
	List b(a);
	cout << "    ---- 链表元素为:";
	for (auto & x : b)
		cout << x << " ";
	cout << endl;
	cout << endl;
	//----------
	cout << "*** ==== 测试赋值函数" << endl;
	List c;
	c = a;
	cout << "    ---- 链表元素为:";
	for (auto & x : c)
		cout << x << " ";
	cout << endl;
	cout << endl;

	return 0;
}


运行的结果如下

使用C++实现双向链表List_第1张图片


运行环境  window10+VS2013

你可能感兴趣的:(c++学习,数据结构)