C++:使自定義類支援迭代器

SXWisON發表於2024-09-10

概述


在 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;
};

在該連結串列中,定義了pushpop兩個方法,現假定,我們需要能夠從第一個節點開始,逐步在外部呼叫連結串列的每一個節點,有一種簡單的實現方法:

  • 定義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,指向當前元素。
  • 在連結串列類中,定義beginend函式,分別指向第一個元素和尾節點。
  • 定義!=運算子,以支援比較兩個指標是否相同。
  • 定義++運算子,使得可以更便攜的遍歷節點。
  • 定義*運算子,使得可以採用指標方法訪問到節點的值。

我們可以依次完成實現

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的內部節點,因此將它作為巢狀類可以簡化訪問邏輯,而不需要額外的介面或方法來暴露連結串列的內部結構。

相關文章