上节博客中我们讲到了线性表的链式存储结构(也就是单链表),那么本节我们将会完成具体的实现。结构框架如下
我们先来看看 LinkList 的设计要点:1、类模板,通过头结点访问后继结点;2、定义内部结点类型 Node,用于描述数据域和指针域;3、实现线性表的关键操作(增、删、查等)。
我们来看看 LinkList 具体的实现
LinkList.h 源码
#ifndef LINKLIST_H #define LINKLIST_H #include "List.h" #include "Exception.h" namespace DTLib { template < typename T > class LinkList : public List<T> { protected: struct Node : public Object { T value; Node* next; }; Node m_header; int m_length; public: LinkList() { m_header.next = NULL; m_length = 0; } bool insert(const T& e) { return insert(m_length, e); } bool insert(int i, const T& e) { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { Node* node = new Node(); if( node != NULL ) { Node* current = &m_header; for(int p=0; p<i; p++) { current = current->next; } node->value = e; node->next = current->next; current->next = node; m_length++; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ..."); } } } bool remove(int i) { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { Node* current = &m_header; for(int p=0; p<i; p++) { current = current->next; } Node* toDel = current->next; current->next = toDel->next; delete toDel; m_length--; } return ret; } bool set(int i, const T& e) { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { Node* current = &m_header; for(int p=0; p<i; p++) { current = current->next; } current->next->value = e; } return ret; } T get(int i) const { T ret; if( get(i, ret) ) { return ret; } else { THROW_EXCEPTION(IndexOutOfBoundsException, "Invaild parameter i to get element ..."); } } bool get(int i, T& e) const { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { Node* current = &m_header; for(int p=0; p<i; p++) { current = current->next; } e = current->next->value; } return ret; } int length() const { return m_length; } void clear() { while( m_header.next ) { Node* toDel = m_header.next; m_header.next = toDel->next; delete toDel; } m_length = 0; } ~LinkList() { clear(); } };
我们测试的 main.cpp 代码如下
#include <iostream> #include "LinkList.h" using namespace std; using namespace DTLib; int main() { LinkList<int> list; for(int i=0; i<5; i++) { list.insert(0, i); list.set(0, i * i); } for(int i=0; i<list.length(); i++) { cout << list.get(i) << endl; } list.remove(2); for(int i=0; i<list.length(); i++) { cout << list.get(i) << endl; } list.clear(); for(int i=0; i<list.length(); i++) { cout << list.get(i) << endl; } return 0; }
我们看看编译结果
我们看到编译报错了,原因是我们的 get 函数是 const 类型的,我们直接想赋值,所以肯定不行哈,那么我们在 m_header 前加上 mutable 关键字应该就能解决这个问题,我们看看重新编译的结果
我们看到第一次有全部打印出来,第二次就把2对应的位置给去掉了,第三次直接就没有输出了。此时我们已经正确实现了 LinkList 这个类了。
那么我们来思考下:此时的头结点是否存在隐患呢?实现代码是否需要优化呢?隐患是什么呢?如果我们在类的构造函数中抛出异常,此时会生成相应的结点吗?如下
我们来在 main.cpp 中创建一个这样的类,看看输出
#include <iostream>#include "LinkList.h" using namespace std; using namespace DTLib; class Test { public: Test() { throw 0; } }; int main() { LinkList<Test> list; cout << "Hello" << endl; return 0; }
我们期望的输出是 Hello ,我们来看看结果
我们看到程序编译时出现异常了,它提示抛出了一个 int 类型的异常。这个一看就是使用者造成的问题,在 LinkList 中使用了泛型编程来生成了一个 Test 类型的对象。于是乎,便跑出来异常。但是他们会说我们没有创建 Test 类的对象,便会归咎于是这个库的不稳定造成的,所以我们就得来解决这个问题。我们在 m_header 中加入占位参数,让它在内存上是对齐的,这样便不会出现调用泛指类型的构造函数了。再者我们发现代码中存在大量的寻找地址的语句,因此我们抽象一个 position 函数出来用以优化代码。
LinkList.h 源码如下
#ifndef LINKLIST_H #define LINKLIST_H #include "List.h" #include "Exception.h" namespace DTLib { template < typename T > class LinkList : public List<T> { protected: struct Node : public Object { T value; Node* next; }; mutable struct { char reserved[sizeof(T)]; Node* next; } m_header; int m_length; Node* position(int i) const { Node* ret = reinterpret_cast<Node*>(&m_header); for(int p=0; p<i; p++) { ret = ret->next; } return ret; } public: LinkList() { m_header.next = NULL; m_length = 0; } bool insert(const T& e) { return insert(m_length, e); } bool insert(int i, const T& e) { bool ret = ((0 <= i) && (i <= m_length)); if( ret ) { Node* node = new Node(); if( node != NULL ) { Node* current = position(i); node->value = e; node->next = current->next; current->next = node; m_length++; } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ..."); } } } bool remove(int i) { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { Node* current = position(i); Node* toDel = current->next; current->next = toDel->next; delete toDel; m_length--; } return ret; } bool set(int i, const T& e) { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { position(i)->next->value = e; } return ret; } T get(int i) const { T ret; if( get(i, ret) ) { return ret; } else { THROW_EXCEPTION(IndexOutOfBoundsException, "Invaild parameter i to get element ..."); } } bool get(int i, T& e) const { bool ret = ((0 <= i) && (i < m_length)); if( ret ) { e = position(i)->next->value; } return ret; } int length() const { return m_length; } void clear() { while( m_header.next ) { Node* toDel = m_header.next; m_header.next = toDel->next; delete toDel; } m_length = 0; } ~LinkList() { clear(); } }; } #endif // LINKLIST_H
我们再次编译,看看结果
我们看到 Hello 已经打印出来了,我们在 main.cpp 中再来创建一个 Test 的类呢,看看会输出什么
编译的时候有报错了,报的是抛出了 int 型的异常。这就跟我们的库没有什么关系了,我们再来试试之前的测试代码,看看我们的改动有没有引起什么新的 bug。
我们看到程序异常结束了,我们来分析下这是什么原因。在 struct Node 中,它是继承自顶层父类 Object 的,因此我们的 m_header 也必要继承自顶层父类 Object。我们来改动下,看看编译结果,改动如下
mutable struct : public Object { char reserved[sizeof(T)]; Node* next; } m_header;
我们看看编译结果
我们看到程序正常结束了,此时的 LinkList 类已经彻底实现好了。通过对单链表的学习,总结如下:1、通过类模板实现链表,包含头结点成员和长度成员;2、定义结点类型,并通过堆中的结点对象构成链式存储;3、为了避免构造错误的信息,头结点类型需要重新定义下;4、代码优化是在我们代码完成后不可或缺的环节。