概述
在 C++ 中,連結串列迭代器是一種用來遍歷連結串列(如 std::list)元素的工具。連結串列是一種資料結構,其中每個元素(節點)包含一個資料值和一個指向下一個節點的指標。連結串列迭代器允許以類似於陣列的方式訪問連結串列中的元素,但不需要直接操作指標。
連結串列迭代器的作用
-
訪問元素:連結串列迭代器使你能夠順序訪問連結串列中的每個元素,就像在陣列中遍歷元素一樣。
-
遍歷連結串列:透過迭代器,你可以在連結串列中前進或後退,從而進行遍歷操作。這使得在連結串列中執行各種操作(如查詢、修改、刪除等)變得簡單而直觀。
-
抽象化操作:迭代器提供了一種統一的方式來訪問不同型別的資料結構。無論是連結串列、陣列還是其他容器,迭代器的使用方式大致相同,這讓程式碼更加通用和易於維護。
使用示例
#include <iostream>
#include <list>
int main() {
std::list<int> myList = {1, 2, 3, 4, 5};
// 使用迭代器遍歷連結串列
for (std::list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " "; // 輸出連結串列元素
}
return 0;
}
為什麼要為自己的類設定迭代器?
參考下述連結串列類
class List {
public:
List(): head(new Node()) { }
~List();
bool push(int x, int y); // 在頭部插入一個新座標
bool pop(int x, int y); // 查詢指定座標,並刪除
private:
Node* head;
};
在該連結串列中,定義了push
和pop
兩個方法,現假定,我們需要能夠從第一個節點開始,逐步在外部呼叫連結串列的每一個節點,有一種簡單的實現方法:
- 定義
search(int i)
函式,從頭開始,向後查詢i
個節點 - 在外部採用for迴圈遞增節點索引
i
。
這裡給出一個虛擬碼:
for (int i = 0; i < 10; ++i) {
Node cur = myList.search(i);
std::cout << cur << std::endl;
}
上述方法能夠實現在外部對連結串列節點的遍歷,但是,當索引較大時,鑑於每一次都需要從頭訪問至索引處,算力開銷極大,因此我們必須採用更高效的方法。
如何為類設定迭代器方法?
觀察標準庫中迭代器的使用方法:
for (std::list<int>::iterator it = myList.begin(); it != myList.end(); ++it) {
std::cout << *it << " "; // 輸出連結串列元素
}
我們瞭解到需要實現如下內容:
- 定義迭代器類,包含一個
Node*
型別的指標cur
,指向當前元素。 - 在連結串列類中,定義
begin
、end
函式,分別指向第一個元素和尾節點。 - 定義
!=
運算子,以支援比較兩個指標是否相同。 - 定義
++
運算子,使得可以更便攜的遍歷節點。 - 定義
*
運算子,使得可以採用指標方法訪問到節點的值。
我們可以依次完成實現
class List {
public:
List() : head(new Node()) { }
~List();
bool push(int x, int y);
bool pop(int x, int y);
// 定義迭代器類
class Iterator {
public:
// 建構函式
Iterator(Node* node) : cur(node) {}
// 指標運算子
Cell& operator*() { return cur->cell; }
// 前置自增運算子
Iterator& operator++() {
if (cur) cur = cur->next;
return *this;
}
// 不等於運算子
bool operator!=(const Iterator& other) const { return cur != other.cur; }
private:
// cur欄位
Node* cur;
}
// 分別定義begin()、end()方法
Iterator begin() const { return Iterator(head->next); }
Iterator end() const { return Iterator(nullptr); }
private:
Node* head;
};
完成上述實現後,我們就可以使用迭代器方法快捷的訪問類成員了。
#include <iostream>
#include "list.h"
int main() {
List ROI;
// 插入節點
ROI.push(0, 0);
ROI.push(0, 1);
ROI.push(0, 2);
ROI.push(0, 3);
for (List::Iterator it = ROI.begin(); it != ROI.end(); ++it) {
std::cout << *it << "\n";
}
return 0;
}
為什麼將迭代器類Iterator
嵌入連結串列類List
中?
-
封裝性:將
Iterator
類作為LinkedList
的巢狀類,可以更好地封裝連結串列內部的實現細節。這樣Iterator
類只能訪問LinkedList
類的私有成員,而其他類不能直接訪問Iterator
類的內部細節。 -
邏輯關聯:
Iterator
是專門用於遍歷LinkedList
的,因此將它放在LinkedList
類內部有助於保持邏輯上的一致性和相關性。這樣可以清晰地表示Iterator
是為LinkedList
量身定做的。 -
簡化訪問:巢狀類可以訪問外部類的私有成員。對於
Iterator
來說,它需要訪問LinkedList
的內部節點,因此將它作為巢狀類可以簡化訪問邏輯,而不需要額外的介面或方法來暴露連結串列的內部結構。