1. 程式人生 > >八、單鏈表的實現

八、單鏈表的實現

需要 實現 大小 urn head 內存大小 int nds 對象

1、鏈式存儲結構線性表的實現:

技術分享圖片

LinkList設計要點:類模板

  • 通過頭結點訪問後繼節點
  • 定義內部結點類型Node,用於描述數據域和指針域
  • 實現線性表的關鍵操作(增、刪、改、查等)
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() {}
};

具體實現

template <typename T>
class LinkList : public List<T>
{
protected:
    struct Node : public Object
    {
        T value;    // 數據域
        Node* next; // 指針域
    };

    mutable 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)
    {
        // 註意i的範圍
        bool ret = ((i>=0) && (i<=m_length));
        cout << "ret  = " << ret <<  endl;
        if (ret)
        {
            Node* node = new Node();
            if (node != NULL)
            {
                // current的目標指向其實都是目標位置的前一個,比如:在第0個位置增加元素,current指向的是header
                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
            {
                cout << "THROW_EXCEPTION" << endl;
                THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element");
            }
        }

        return ret;
    }
    // 刪除指定位置元素
    bool remove(int i)
    {
        // 註意i的範圍
        bool ret = ((i>=0) && (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)
    {
        // 註意i的範圍
        bool ret = ((i>=0) && (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;
    }
    // get函數用起來不方便,重載一下
    T get(int i) const
    {
        T ret;
        if (get(i, ret))
        {
            return ret;
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element...");
        }
    }
    // 獲取指定位置的元素
    bool get(int i, T& e) const
    {
        bool ret = ((i>=0) && (i<m_length));
        if (ret)
        {
            Node* current = &m_header;
            for(int p = 0; p < i; p++)
            {
                current = current->next;
            }

            e = current->next->value;
            // get是const成員函數,按理來說不能修改成員變量的值,Node* current=&m_header,會被誤認為要更改成員變量的值,故報錯
            // 解決方案是對m_header加上mutable,開一個例外
        }

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

問題:頭結點隱患,實現代碼優化

創建m_header時,會調用T value,用泛指類型創建頭結點的數據域,當泛指類型為用戶自定義類型時,用用戶自定義的類類型在庫中創建對象,就有可能出錯了,而且在外部看來,並沒有用該類型創建對象,問題定位很麻煩。

解決辦法:構造頭結點時,不調用泛指類型創建頭結點,而是按內存分布自己重建構造一個類對象,註意一定要和以前的頭結點的內存分布一樣,不僅是成員變量的內存大小,同樣也要和以前一樣繼承於Object

// 直接創建頭結點,存在隱患
mutable Node m_header;

// 重新構造之後的頭結點
mutable struct : public Object
{
    char reserved[sizeof(T)];   // 沒實際作用,占空間
    Node* next;
} m_header;

重新構造後的頭結點在內存布局上和之前沒有差異,差異在於不管泛指類型是什麽,都不會去調用泛指類型的構造函數了。雖然它們在內存布局上是一樣的,但是新構造的頭結點是個空類型,不能直接用,使用時要進行類型轉換

Node* ret = reinterpret_cast<Node*>(&m_header);

優化後的完整代碼:


template <typename T>
class LinkList : public List<T>
{
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;
    }

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)
    {
        // 註意i的範圍
        bool ret = ((i>=0) && (i<=m_length));
        cout << "ret  = " << ret <<  endl;
        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");
            }
        }
        return ret;
    }

    bool remove(int i)
    {
        // 註意i的範圍
        bool ret = ((i>=0) && (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 = ((i>=0) && (i<m_length));
        if (ret)
        {
            position(i)->next->value = e;
        }
        return ret;
    }
    // get函數用起來不方便,重載一下
    T get(int i) const
    {
        T ret;
        if (get(i, ret))
        {
            return ret;
        }
        else
        {
            THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element...");
        }
    }

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

註意每次代碼修改之後都要進行測試,有可能由於修改的代碼引入了新的bug

3、小結

通過類模板實現鏈表,包含頭結點和長度成員

定義結點類型,並通過堆中的結點對象構成鏈式存儲

為了避免構造錯誤的隱患,頭結點類型需要重定義

代碼優化是編碼完成後必不可少的環節

八、單鏈表的實現