八、單鏈表的實現
阿新 • • 發佈:2018-08-18
需要 實現 大小 urn head 內存大小 int nds 對象
1、鏈式存儲結構線性表的實現:
LinkList
設計要點:類模板
- 通過頭結點訪問後繼節點
- 定義內部結點類型Node,用於描述數據域和指針域
- 實現線性表的關鍵操作(增、刪、改、查等)
2、LinkList
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、小結
通過類模板實現鏈表,包含頭結點和長度成員
定義結點類型,並通過堆中的結點對象構成鏈式存儲
為了避免構造錯誤的隱患,頭結點類型需要重定義
代碼優化是編碼完成後必不可少的環節
八、單鏈表的實現