1. 程式人生 > >第22課 單鏈表的實現

第22課 單鏈表的實現

.cn toa nod 越界 bool 遞歸調用 isp {} log

1. LinkList類的設計要點

技術分享 技術分享

(1)用類模板實現,通過頭結點訪問後繼結點

(2)定義內部結點類型Node(註意繼承於自定義的Object頂層父類),用於描述數據域指針域.

(3)實現線性表的關鍵操作(增、刪、查等)

2. 單鏈表的內部結構

技術分享

(1)頭結點在單鏈表中的意義:輔助數據元素的定位,方便插入和刪除操作

(2)頭結點不存儲實際的數據元素

3. 單鏈表的操作

(1)插入元素

技術分享

  ①從頭結點開始,通過current指針定位到目標位置(註意:current->next保留著目標元素的地址

  ②從堆空間中申請新的Node結點

  ③執行插入操作

node->value = e;

node->next = current->next;

current->next = node;

(2)刪除元素

技術分享

  ①從頭結點開始,通過current指針定位到目標位置(toDel地址保存在curr->next中

  ②使用toDel指針指向需要刪除的結點

  ③執行刪除操作

toDel = current->next;

current->next = toDel->next;

delete toDel;

【編程實驗】單鏈表的實現

//Object.h

技術分享
#ifndef _OBJECT_H_
#define _OBJECT_H_ namespace DTLib { class Object { public: //以下四個重載函數用於統一不同編譯器new失敗時的結果不同的問題。 //throw()表示不拋出異常,即如果申請內請失敗時,統一返回NULL而不拋異常 void* operator new(unsigned int size) throw(); void operator delete(void* p); void* operator new[](unsigned int size) throw(); void operator delete
[](void* p); virtual ~Object() = 0; }; } #endif // _OBJECT_H_
View Code

//Object.cpp

技術分享
#include "Object.h"
#include <cstdlib>

using namespace std;

namespace DTLib {

void * Object::operator new(unsigned int size)  throw()
{
    return malloc(size);
}

void Object::operator delete(void *p)
{
    free(p);
}

void *Object::operator new[](unsigned int size) throw()
{
    //當用new Test[5]時,只須傳入數組元素的個數,編譯器會向operator new[](...)函數的參數
    //傳入5*sizeof(Test) + sizeof(unsigned int),其中的sizeof(unsigned int)為額外
    //空間,用於保存元素的個數。
    return malloc(size);
}

void Object::operator delete[](void *p)
{
    free(p);
}

Object::~Object()
{

}

}
View Code

//Exception.h

技術分享
#ifndef _EXCEPTION_H_
#define _EXCEPTION_H_
#include "Object.h"

namespace DTLib
{

#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))

class Exception : public Object
{
protected:
    char* m_message;
    char* m_location;

    void init(const char* message, const char* file, int line);

public:
    //構造函數
    Exception(const char* message);
    Exception(const char* file, int line);
    Exception(const char *message, const char *file, int line);
    //拷貝構造函數
    Exception(const Exception& e);
    //重載賦值操作符
    Exception& operator=(const Exception& e);

    virtual const char* message() const;
    virtual const char* location() const;

    //註意:
    //(1)析構函數是較為特殊的函數,一旦定義了析構函數,不管這個函數是不是純虛函數,就
    //必須提供實現。因為,對象在銷毀時,最後都會調用父類的析構函數。如果父類不提供實現,
    //當對象銷毀過程中調用到父類析構函數時,就找不到析構函數,也就不知該如何析構下去。
    //因此,盡管這裏將析構函數聲明為純虛函數,但Exception類仍提供析構函數的實現。以便
    //最後正確釋放掉m_message和m_location所指的堆空間.
    //(2)此外,聲明為純虛函數,可以讓該類只能作為接口使用,而且也強迫子類必須
    //提供析構函數的實現。
    virtual ~Exception() = 0; //純虛函數
};

//計算異常類
class ArithmeticException: public Exception
{
public:
    ArithmeticException():Exception(0){}
    ArithmeticException(const char* message):Exception(message){}
    ArithmeticException(const char*file, int line):Exception(file, line){}
    ArithmeticException(const char *message, const char* file, int line):Exception(message, file, line){}

    ArithmeticException(const ArithmeticException& e): Exception(e){}
    ArithmeticException& operator=(const ArithmeticException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

//空指針異常類
class NullPointerException: public Exception
{
public:
    NullPointerException():Exception(0){}
    NullPointerException(const char* message):Exception(message){}
    NullPointerException(const char*file, int line):Exception(file, line){}
    NullPointerException(const char *message, const char* file, int line):Exception(message, file, line){}

    NullPointerException(const NullPointerException& e): Exception(e){}
    NullPointerException& operator=(const NullPointerException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

//越界異常類
class IndexOutOfBoundsException: public Exception
{
public:
    IndexOutOfBoundsException():Exception(0){}
    IndexOutOfBoundsException(const char* message):Exception(message){}
    IndexOutOfBoundsException(const char*file, int line):Exception(file, line){}
    IndexOutOfBoundsException(const char *message, const char* file, int line):Exception(message, file, line){}

    IndexOutOfBoundsException(const IndexOutOfBoundsException& e): Exception(e){}
    IndexOutOfBoundsException& operator=(const IndexOutOfBoundsException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

//內存不足異常類
class NotEnoughMemoryException: public Exception
{
public:
    NotEnoughMemoryException():Exception(0){}
    NotEnoughMemoryException(const char* message):Exception(message){}
    NotEnoughMemoryException(const char*file, int line):Exception(file, line){}
    NotEnoughMemoryException(const char *message, const char* file, int line):Exception(message, file, line){}

    NotEnoughMemoryException(const NotEnoughMemoryException& e): Exception(e){}
    NotEnoughMemoryException& operator=(const NotEnoughMemoryException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

//參數錯誤異常類
class InvalidParameterException: public Exception
{
public:
    InvalidParameterException():Exception(0){}
    InvalidParameterException(const char* message):Exception(message){}
    InvalidParameterException(const char*file, int line):Exception(file, line){}
    InvalidParameterException(const char *message, const char* file, int line):Exception(message, file, line){}

    InvalidParameterException(const InvalidParameterException& e): Exception(e){}
    InvalidParameterException& operator=(const InvalidParameterException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

//無效操作異常類(成員函數調用時,如果狀態不正確則拋出異常)
class InvalidOperationException: public Exception
{
public:
    InvalidOperationException():Exception(0){}
    InvalidOperationException(const char* message):Exception(message){}
    InvalidOperationException(const char*file, int line):Exception(file, line){}
    InvalidOperationException(const char *message, const char* file, int line):Exception(message, file, line){}

    InvalidOperationException(const InvalidOperationException& e): Exception(e){}
    InvalidOperationException& operator=(const InvalidOperationException& e)
    {
        Exception::operator =(e);

        return *this;
    }
};

}

#endif // _EXCEPTION_H_
View Code

//Exception.cpp

技術分享
#include "Exception.h"
#include <cstring>
#include <cstdlib>

using namespace std;

namespace DTLib
{

void Exception::init(const char *message, const char *file, int line)
{
    m_message = strdup(message); //復制message的內容

    m_location = NULL;

    if(file != NULL){
        char sl[16]={0};
        itoa(line, sl, 10);//將整數line轉為字符串,其中的10表示轉換為十進制格式

        //m_location的格式為:file:line\0;
        m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2));

        //註意:申請內存失敗時無須再拋NotEnoughMemoryException異常,從宏觀上看,父類
        //是無法拋出子類型的異常的。從邏輯上看也不能拋出這個異常,因為當父類構造時出現異常時,
        //如果去拋出子類異常,則必然需要構造子類,但這又得先調用父類構造函數(會再一次產
        //生異常,從而造成Exception構造函數的遞歸調用,從而造成死循環!)
        if(m_location != NULL){   //內存申請成功
            m_location = strcpy(m_location, file);
            m_location = strcat(m_location, ":");
            m_location = strcat(m_location, sl);
        }
    }
}

//構造函數
Exception::Exception(const char* message)
{
    init(message, NULL, 0);
}

Exception::Exception(const char* file, int line)
{
    init(NULL, file, line);
}

Exception::Exception(const char *message, const char *file, int line)
{
    init(message, file, line);
}

//拷貝構造函數
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()
{
    free(m_message);
    free(m_location);
}

}
View Code

//List.h

技術分享
#ifndef _LIST_H_
#define _LIST_H_

#include "Object.h"

namespace DTLib {

template <typename T>
class List : public Object
{
protected:
    //禁用拷貝構造函數和賦值操作符
    //List(const List&);
    //List& operator=(const List&);
public:
    //由於以上添加了拷貝構造函數,編譯將不再提供
    //默認的一些構造函數。這裏需要手工加上無參構造函數
    List(){}
    virtual bool insert(const T& e) = 0;//往線性表尾部插入元素
    virtual bool insert(int index, const T& elem) = 0;
    virtual bool remove(int index) = 0;
    virtual bool get(int index, T& elem) const = 0;
    virtual int length() const = 0;
    virtual void clear() = 0;
};

}

#endif // _LIST_H_
View Code

//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; //指針域s
    };

    //由於m_header在定義時會調用T的構造函數。如果T的設計者在構造函數中拋出異常,當LinkList<T> list時會產生異常。這時調用者可能會懷疑是
    //LinkList設計出了問題,而不會認為是T的問題,因為他根本就沒有手工定義T的對象(如:T t),為了防止這種現象的出現,可“借屍還魂”,防止在定義
    //m_header時調用T的構造函數。
//mutable Node m_header; //頭結點 //“借屍還魂”:定義一個與Node內存模型完全一樣的結構體 mutable struct : public Object{ char reserved[sizeof(T)]; Node* next; } m_header; int m_length; //鏈表長度 //獲取指定下標的元素 Node* position(int index) const { Node* ret = (Node*)&m_header; //由於const對象不能修改成員變量的值 //對m_header取地址就可能會修改m_header //的值,可將m_header聲明為mutable for(int pos=0; pos<index; ++pos){ ret = ret->next; } return ret; //註意要查找的元素地址保存在該節點的next指針域中 } public: LinkList() { m_header.next = NULL; m_length = 0; } bool insert(const T &elem) { return insert(m_length, elem); } bool insert(int index, const T& elem) { bool ret = ((0 <= index) && (index <= m_length)); if (ret){ Node* node = new Node(); Node* current = position(index); node->value = elem; node->next = current->next; current->next = node; ++m_length; }else{ THROW_EXCEPTION(NotEnoughMemoryException, "Not enougth memory to insert new element ..."); } return ret; } bool remove(int index) { bool ret = ((0 <= index) && (index < m_length)); if(ret){ Node* current = position(index); //要刪除的元素地址保存在curr->next中 Node* toDel = current->next; current->next = toDel->next; delete toDel; --m_length; } return ret; } bool set(int index, const T& elem) { bool ret = (0 <= index) && (index < m_length); if(ret){ Node* current = position(index); (current->next)->value = elem; } return ret; } T get(int index) const { T ret; if(get(index, ret)){ return ret; }else{ THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid paramter index to get element ..."); } } bool get(int index, T& elem) const { bool ret = (0 <= index) && (index < m_length); if(ret){ Node* current = position(index); elem = (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(); } }; } #endif // _LINKLIST_H_

//main.cpp

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

using namespace std;
using namespace DTLib;

class Test
{
public:
    Test()
    {
        throw 0; //在構造函數中拋異常,故意制造出來的!
    }
};

int main()
{
    //測試1
    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) << " ";
    }

    cout << endl;

    list.remove(2);

    for(int i=0; i<list.length(); i++){

        cout << list.get(i) << " ";
    }

    cout << endl;

    list.clear();

    for(int i=0; i<list.length(); i++){

        cout << list.get(i) << endl;
    }

    //測試2
    LinkList<Test> lt;  //如果LinkList類不采用借屍還魂來解決Test拋異常的話,
                        //LinkList的使用者會以為這個異常是LinkList類設計
                        //的問題,因為他們認為自己並沒有調用Test的構造函數,
                        //所以不會懷疑是Test類設計的問題,而是LinkList類的問題。

    return 0;
}
/*輸出結果:
16 9 4 1 0
16 9 1 0
*/

4. 小結

(1)通過類模板實現鏈表,包括頭結點成員和長度成員。

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

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

(4)插入和刪除操作需要保證鏈表的完整性

第22課 單鏈表的實現