上节博客中我们讲到了线性表的链式存储结构(也就是单链表),那么本节我们将会完成具体的实现。结构框架如下

单链表(十一)_线性表

        我们先来看看 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;
}

        我们看看编译结果

单链表(十一)_链式_02

        我们看到编译报错了,原因是我们的 get 函数是 const 类型的,我们直接想赋值,所以肯定不行哈,那么我们在 m_header 前加上 mutable 关键字应该就能解决这个问题,我们看看重新编译的结果

单链表(十一)_链式_03

        我们看到第一次有全部打印出来,第二次就把2对应的位置给去掉了,第三次直接就没有输出了。此时我们已经正确实现了 LinkList 这个类了。

        那么我们来思考下:此时的头结点是否存在隐患呢?实现代码是否需要优化呢?隐患是什么呢?如果我们在类的构造函数中抛出异常,此时会生成相应的结点吗?如下

单链表(十一)_单链表_04

        我们来在 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 ,我们来看看结果

单链表(十一)_存储结构_05

        我们看到程序编译时出现异常了,它提示抛出了一个 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

        我们再次编译,看看结果

单链表(十一)_线性表_06

        我们看到 Hello 已经打印出来了,我们在 main.cpp 中再来创建一个 Test 的类呢,看看会输出什么

单链表(十一)_线性表_07

        编译的时候有报错了,报的是抛出了 int 型的异常。这就跟我们的库没有什么关系了,我们再来试试之前的测试代码,看看我们的改动有没有引起什么新的 bug。

单链表(十一)_线性表_08

        我们看到程序异常结束了,我们来分析下这是什么原因。在 struct Node 中,它是继承自顶层父类 Object 的,因此我们的 m_header 也必要继承自顶层父类 Object。我们来改动下,看看编译结果,改动如下

mutable struct : public Object    
{
    char reserved[sizeof(T)];
    Node* next;
} m_header;

        我们看看编译结果单链表(十一)_单链表_09

        我们看到程序正常结束了,此时的 LinkList 类已经彻底实现好了。通过对单链表的学习,总结如下:1、通过类模板实现链表,包含头结点成员和长度成员;2、定义结点类型,并通过堆中的结点对象构成链式存储;3、为了避免构造错误的信息,头结点类型需要重新定义下;4、代码优化是在我们代码完成后不可或缺的环节。