【數據結構】25_典型問題分析(Bugfix)

ISSUE_1 建立對象時的空指針問題

image.png

strdup 源代碼:
char * __strdup(const char *s)  
{  
   size_t  len = strlen(s) +1;  
   void *new = malloc(len);  
   if (new == NULL)  
      return NULL;  
   return (char *)memecpy(new,s,len);  
}
問題:strdup 未對空指針作處理。
m_message = strdup(message);

==>node

m_message = (message != nullptr) ? strdup(message) : nullptr;

[修復] 文件:Exception.cppios

#include "Exception.h"

#include <cstring>
#include <cstdlib>

namespace DTLib
{

Exception::Exception(const char *message) : Exception(message, nullptr, 0)
{
}

Exception::Exception(const char *file, int line) : Exception(nullptr, file, line)
{
}

Exception::Exception(const char *message, const char *file, int line)
{
    m_message = (message != nullptr) ? strdup(message) : nullptr;  // 修復!!

    if (file != nullptr)
    {
        char sl[16] = {0};

        itoa(line, sl, 10);

        m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2));
        m_location = strcpy(m_location, file);
        m_location = strcat(m_location, ":");
        m_location = strcat(m_location, sl);
    }
    else
    {
        m_location = nullptr;
    }
}

Exception::Exception(const Exception &e)
{
    m_message = strdup(e.m_message);
    m_location = strdup(e.m_location);
}

Exception &Exception::operator= (const Exception &e)
{
    if (this != &e)
    {
        free(m_message);
        free(m_location);

        m_message = strdup(e.m_message);
        m_location = strdup(e.m_location);
    }

    return *this;
}

const char *Exception::message() const
{
    return m_message;
}

const char *Exception::location() const
{
    return m_location;
}

Exception::~Exception()
{
    delete m_message;
    delete m_location;
}

}

ISSUE_2 LinkList 中數據元素刪除

LinkList<Test> list;
Test t0(0), t1(1), t2(2);  // t1 在析構時拋出異常

try
{
}
catch(...)
{
    cout << list.length() << endl;  // ???
}
輸出:[Qt]
說明:Qt中析構函數拋出的異常,沒法被外部捕獲
terminate called after throwing an instance of 'int'
輸出:[vs]
1
3
問題:單鏈表狀態出現問題,指望結果爲 2。
定位:remove 函數未考慮異常安全。
bool remove(int i)
{
    // ...
    destroy(toDel);
    --m_length;
    // ...
}

==>segmentfault

bool remove(int i)
{
    // ...
    --m_length;
    destroy(toDel);
    // ...
}

[修復]文件: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;

        m_step        = 0;
        m_current     = nullptr;
    }

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

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

        if (ret)
        {
            Node *node = create();
            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  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            Node *current = position(i);

            Node *toDel = current->next;
            current->next = toDel->next;

            --m_length;

            destroy(toDel);
        }

        return ret;
    }

    bool set(int i, const T &e) override  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            position(i)->next->value = e;
        }

        return ret;
    }

    T get(int i) const  // O(n)
    {
        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  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            e = position(i)->next->value;
        }

        return ret;
    }

    int  find(const T &e) override  // O(n)
    {
        int ret = -1;

        int i = 0;
        Node *node = m_header.next;
        while (node)
        {
            if (node->value == e)
            {
                ret = i;
                break;
            }
            else
            {
                node = node->next;
                ++i;
            }
        }

        return ret;
    }

    int length() const  // O(1)
    {
        return m_length;
    }

    void clear()  // O(n)
    {
        while (m_header.next)
        {
            Node *toDel = m_header.next;

            m_header.next = toDel->next;

            --m_length;

            destroy(toDel);
        }
    }

    bool move(int i, int step = 1)  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length) && (step > 0));

        if (ret)
        {
            m_current = position(i)->next;
            m_step = step;
        }

        return ret;
    }

    bool end()  // O(1)
    {
        return (m_current == nullptr);
    }

    T current()  // O(1)
    {
        if (!end())
        {
            return m_current->value;
        }
        else
        {
             THROW_EXCEPTION(InvalidOpertionExcetion, " No value at current posotion ...");
        }
    }

    bool next()  // O(n)
    {
        int i = 0;

        while ((i < m_step) && !end())
        {
            m_current = m_current->next;
            ++i;
        }

        return (i == m_step);
    }

    ~LinkList()  // O(n)
    {
        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;
    int m_step;
    Node *m_current;

    Node *position(int i) const  // O(n)
    {
        Node *ret = reinterpret_cast<Node*>(&m_header);

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

        return ret;
    }

    virtual Node *create()  // N(1)
    {
        return new Node();
    }

    virtual void destroy(Node *pn)  // N(1)
    {
        delete pn;
    }
};

}

#endif // LINKLIST_H

ISSUE_3 LinkList 中遍歷操做與刪除操做的混合使用

LinkList<int> list;
for (int i=0; i<5; ++i)
{
    list.insert(i);
}

for (list.move(0); !list.end(); list.next())
{
    if (list.current() == 3)
    {
        list.remove(list.find(list.current()));
        
        cout << list.current->value() << endl; ???
    }
}
輸出:[隨機值]
18022592
緣由:current 指向的堆空間被釋放。
bool remove(int i) override  // O(n)
{
    bool ret = ((0 <= i) && (i < m_length));

    if (ret)
    {
        Node *current = position(i);
        Node *toDel = current->next;
        current->next = toDel->next;
        --m_length;
        destroy(toDel);
    }

    return ret;
}

==>安全

bool remove(int i) override  // O(n)
{
    bool ret = ((0 <= i) && (i < m_length));

    if (ret)
    {
        Node *current = position(i);
        Node *toDel = current->next;
        if (m_current == toDel)
        {
            m_current = toDel->next;
        }
        current->next = toDel->next;
        --m_length;
        destroy(toDel);
    }

    return ret;
}

[修復]LinkList.hide

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

        m_step        = 0;
        m_current     = nullptr;
    }

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

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

        if (ret)
        {
            Node *node = create();
            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  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            Node *current = position(i);

            Node *toDel = current->next;

            if (m_current == toDel)
            {
                m_current = toDel->next;
            }

            current->next = toDel->next;

            --m_length;

            destroy(toDel);
        }

        return ret;
    }

    bool set(int i, const T &e) override  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            position(i)->next->value = e;
        }

        return ret;
    }

    T get(int i) const  // O(n)
    {
        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  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length));

        if (ret)
        {
            e = position(i)->next->value;
        }

        return ret;
    }

    int  find(const T &e) override  // O(n)
    {
        int ret = -1;

        int i = 0;
        Node *node = m_header.next;
        while (node)
        {
            if (node->value == e)
            {
                ret = i;
                break;
            }
            else
            {
                node = node->next;
                ++i;
            }
        }

        return ret;
    }

    int length() const  // O(1)
    {
        return m_length;
    }

    void clear()  // O(n)
    {
        while (m_header.next)
        {
            Node *toDel = m_header.next;

            m_header.next = toDel->next;

            --m_length;

            destroy(toDel);
        }
    }

    bool move(int i, int step = 1)  // O(n)
    {
        bool ret = ((0 <= i) && (i < m_length) && (step > 0));

        if (ret)
        {
            m_current = position(i)->next;
            m_step = step;
        }

        return ret;
    }

    bool end()  // O(1)
    {
        return (m_current == nullptr);
    }

    T current()  // O(1)
    {
        if (!end())
        {
            return m_current->value;
        }
        else
        {
             THROW_EXCEPTION(InvalidOpertionExcetion, " No value at current posotion ...");
        }
    }

    bool next()  // O(n)
    {
        int i = 0;

        while ((i < m_step) && !end())
        {
            m_current = m_current->next;
            ++i;
        }

        return (i == m_step);
    }

    ~LinkList()  // O(n)
    {
        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;
    int m_step;
    Node *m_current;

    Node *position(int i) const  // O(n)
    {
        Node *ret = reinterpret_cast<Node*>(&m_header);

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

        return ret;
    }

    virtual Node *create()  // N(1)
    {
        return new Node();
    }

    virtual void destroy(Node *pn)  // N(1)
    {
        delete pn;
    }
};

}

#endif // LINKLIST_H

ISSUE_4 StaticLinkList 是否須要提供析構函數?函數

int main()
{
    StaticLinkList<int, 10> sll;
    
    for (int i=0; i<10; ++i)
    {
        sll.insert(i);
    }
    
    return 0;  // 如何析構???
}
背景知識:

在構造函數和析構函數中,不會發生多態行爲,只會調用自身版本的成員函數(不管直接仍是間接調用)測試

知識點傳送門this

析構過程分析:

==> StaticLinkList 析構函數執行
==> LinkList 析構函數執行
==> LinkList<T>::clear() 函數執行
==> LinkList<T>::destroy() 函數執行
==> delete 非堆空間指針,行爲未定義spa

解決方案:爲StaicLinkList 提供析構函數
~StaticLinkList()  // O(n)
{
    this->clear();
}
析構過程分析:

==> StaticLinkList 析構函數執行
==> StaticLinkList<T>::clear() 函數執行
==> StaticLinkList<T>::destroy() 函數執行

[修復] 文件:StaticLinkList.h

#ifndef STATICLINKLIST_H
#define STATICLINKLIST_H

#include "LinkList.h"

#include <iostream>

using namespace std;

namespace DTLib
{

template <typename T, int N>
class StaticLinkList : public LinkList<T>
{
public:
    StaticLinkList()  // O(n)
    {
        for (int i=0; i<N; ++i)
        {
            m_used[i] = 0;
        }
    }

    int capacity()  // O(1)
    {
        return N;
    }

    ~StaticLinkList()  // O(n)
    {
        this->clear();
    }

protected:
    //typedef typename LinkList<T>::Node Node;
    using Node = typename LinkList<T>::Node;

    struct SNode : public Node
    {
        void *operator new (unsigned int size, void *loc)
        {
            (void)size;

            return loc;
        }
    };

    unsigned char m_space[N * sizeof(SNode)];
    char m_used[N];

    Node *create() override  // O(n)
    {
        SNode *ret = nullptr;

        for (int i=0; i<N; ++i)
        {
            if (m_used[i] == 0)
            {
                ret = reinterpret_cast<SNode*>(m_space) + i;
                ret = new(ret)SNode;
                m_used[i] = 1;
                break;
            }
        }

        return ret;
    }

    void destroy(Node *pn) override  // O(n)
    {
        SNode *space = reinterpret_cast<SNode*>(m_space);
        SNode *psn = dynamic_cast<SNode*>(pn);

        for (int i=0; i<N; ++i)
        {
            if (psn == (space + i))
            {
                m_used[i] = 0;
                pn->~Node();
                break;
            }
        }
    }
};

}

#endif // STATICLINKLIST_H

ISSUE_5 DTLib 是否有必要增長多維數組類?

No. 多維數組的本質:數組的數組!
main.cpp
#include <iostream>
#include "DynamicArray.h"

using namespace std;
using namespace DTLib;

int main()
{
    DynamicArray< DynamicArray<int> > d;

    d.resize(3);

    for (int i=0; i<d.length(); ++i)
    {
        d[i].resize(i + 1);
    }

    for (int i=0; i<d.length(); ++i)
    {
        for (int j=0; j<d[i].length(); ++j)
        {
            d[i][j] = i + j;
        }
    }

    for (int i=0; i<d.length(); ++i)
    {
        for (int j=0; j<d[i].length(); ++j)
        {
            cout << d[i][j] << " ";
        }

        cout << endl;
    }

    return 0;
}

輸出:

0
1 2
2 3 4

實踐經驗

是軟件就有bug,所以須要不停的迭代升級,解決問題。
庫是一種特殊的軟件產品,也會存在各類bug,也須要迭代升級,解決問題。
只要代碼發生改動,就須要測試。

以上內容整理於狄泰軟件學院系列課程,請你們保護原創!

相關文章
相關標籤/搜索