课程目标

完成链式存储结构线性表的实现

image.png

LinkList 设计要点

  • 类模板,通过头结点访问后继结点
  • 定义内部结点类型 Node,用于描述数据域和指针域
  • 实现线性表的关键操作(增,删,查,等)

LinkList 的定义

template <typename T>
class LinkList : public List<T>
{
public:
    LinkList();
    // ...
protected:
    struct Node : public Object
    {
        T value;
        Node *next;
    };
    
    Node m_header;
    int m_length;
};

编程实验:链表的实现

文件:LinkList.h

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"

namespace DTLib
{

template <typename T>
class LinkList : public List<T>
{
public:
    LinkList()
    {
        m_header.next = nullptr;
        m_length      = 0;
    }

    bool insert(const T &e) override
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T &e) override
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if (ret)
        {
            Node *node = new Node();
            if (node != nullptr)
            {
                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 ...");
            }
        }

        return ret;
    }

    bool remove(int i) override
    {
        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) override
    {
        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))
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;
    }

    bool get(int i, T &e) const override
    {
        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;
        }
    }

    ~LinkList()
    {
        clear();
    }

protected:
    struct Node : public Object
    {
        T value;
        Node *next;
    };

    mutable Node m_header;
    int m_length;
};

}

#endif // LINKLIST_H

文件:main.cpp

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

int main()
{
    cout << "main begin" << endl;

    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;
    }

    cout << "------------" << endl;

    list.remove(2);

    for (int i=0; i<list.length(); ++i)
    {
        cout << list.get(i) << endl;
    }

    cout << "------------" << endl;

    list.clear();

    for (int i=0; i<list.length(); ++i)
    {
        cout << list.get(i) << endl;
    }

    cout << "main end" << endl;

    return 0;
}

输出:

main begin
16
9
4
1
0
------------
16
9
1
0
------------
main end

问题

头结点是否存在隐患?
实现代码是否需要优化?

代码优化

insert, remove, get, set 等操作都设计元素定位
Node *current = &m_header;

for (int p=0; p<i; ++p)
{
    current = current->next;
}

==>

Node *position(int i);

头结点的隐患

struct Node : public Object
{
    T value;
    Node *next;
};

==>

class Test
{
public:
    Test()
    {
        throw 0;
    }
};

会发生什么呢?

int main()
{
    cout << "main begin" << endl;
    
    LinkList<Test> list;
    
    cout << "main end" << endl;
}

输出:

main begin
terminate called after throwing an instance of 'int'

使用者的疑问:
尽管 Test 类对象会抛出异常,可是我没有定义 Test 对象,只是定义了 LinkList<Test> 对象,为什么会有异常被抛出呢?

代码分析:
=> LinkList<Test> list; 触发成员对象的构造函数调用;
=> Node m_header; 触发成员对象的构造函数被调用;
=> Test (泛指类型) 构造函数被调用,异常抛出。

解决方案:
避免在构造头结点 m_header 时对泛型对象 T 的构造,而又保证内存布局与Node类对象相同。
==》
头结点类型重定义。

struct Node : public Object
{
    T value;
    Node *next;
};

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

编程实验:链表的优化

文件:LinkList.h

#ifndef LINKLIST_H
#define LINKLIST_H

#include "List.h"
#include "Exception.h"

namespace DTLib
{

template <typename T>
class LinkList : public List<T>
{
public:
    LinkList()
    {
        m_header.next = nullptr;
        m_length      = 0;
    }

    bool insert(const T &e) override
    {
        return insert(m_length, e);
    }

    bool insert(int i, const T &e) override
    {
        bool ret = ((0 <= i) && (i <= m_length));

        if (ret)
        {
            Node *node = new Node();
            if (node != nullptr)
            {
                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 ...");
            }
        }

        return ret;
    }

    bool remove(int i) override
    {
        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) override
    {
        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))
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
        }

        return ret;
    }

    bool get(int i, T &e) const override
    {
        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;
        }
    }

    ~LinkList()
    {
        clear();
    }

protected:
    struct Node : public Object
    {
        T value;
        Node *next;
    };

    mutable struct : public Object
    {
        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;
    }
};

}

#endif // LINKLIST_H

文件:main.cpp

#include <iostream>
#include "LinkList.h"

using namespace std;
using namespace DTLib;

class Test
{
public:
    Test()
    {
        throw 0;
    }
};

int main()
{
    cout << "main begin" << endl;

    LinkList<Test> list;

    cout << "main end" << endl;

    return 0;
}

输出:

main begin
main end

小结

  • 通过类模板实现链表,包含头结点成员和长度成员
  • 定义结点类型,并通过堆中的结点对象构造链式存储
  • 为了避免构造错误的隐患,头结点类型需要重定义
  • 代码优化是编码完成后必不可少的环节

提示:只要代码发生改动,就需要重新测试

以上内容整理于狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧