C++之迭代器

陌尘(MoChen)發表於2024-07-23
  • 1. 什麼是迭代器?
  • 2. 如何使用迭代器
  • 3. C++迭代器說明
  • 4. 迭代器的高階應用
    • 4.1. Enumerator.hpp
    • 4.2. Iterator.cpp
    • 4.3. 輸出結果
    • 4.4. 更多詳細程式碼

1. 什麼是迭代器?

迭代器(Iterator)是按照一定的順序對一個或多個容器中的元素從前往遍歷的一種機制,比如for迴圈就是一種最簡單的迭代器,對一個陣列的遍歷也是一種的迭代遍歷的過程。GOF給出的定義為:提供一種方法訪問一個容器(container)物件中各個元素,而又不需暴露該物件的內部細節。迭代器有時也稱為列舉器(Enumerator),其結構圖如下:

file
迭代器結構圖

迭代器其實就是維護一個當前的指標,這個指標可以指向當前的元素,可以返回當前所指向的元素,可以移到下一個元素的位置,透過這個指標可以遍歷容器的所有元素。迭代器一般至少會有以下幾種方法:

First(); //將指標移至第一個位置或獲得第一個元素

GetCurrent(); //獲得當前所指向的元素

MoveNext(); //移至下一個元素

2. 如何使用迭代器

既然迭代器是封裝裡面的實現細節,對外提供方便訪問容器元素的介面,那我們就先從使用的角度認識迭代器,看看C++是如何使用迭代器的。

迭代器示例

void TestIterator()
{
    vector<int> vec;   // 定義一容器
    for(int i = 0; i < 5; i++)
    {
        vec.push_back(i*2);  //新增元素
    }
    //用迭代器訪問容器中的每個元素
    cout << "iterator vector:" << endl;
    for(vector<int>::iterator itr = vec.begin(); itr != vec.end(); itr ++)
    {
        cout << *itr << "   "; //itr是一個指標,指向當前的元素, 所以要解引用獲得元素值
    }
    cout << endl;

    map<int, string> student; //建立一個map,對應學號-姓名的鍵值對
    //新增元素
    student.insert(pair<int, string>(1, "張三"));
    student.insert(pair<int, string>(3, "王五"));
    student.insert(pair<int, string>(2, "李四"));
    //遍歷容器中的元素
    cout << "iterator map:" << endl;
    for (map<int, string>::iterator itr = student.begin(); itr != student.end(); itr ++)
    {
        cout << itr->first << "-->" << itr->second << endl;
    }
}

結果:

iterator vector:
0   2   4   6   8
iterator map:
1-->張三
2-->李四
3-->王五

3. C++迭代器說明

c++中的容器(如vector、map、list、set等)一般會提供四個迭代器:

  1. iterator:正向迭代,從前往後遍歷,可修改元素的值
  2. const_iterator:正向常量迭代,但不能修改元素的值,因為指向的是const的引用
  3. reverse_iterator:反向迭代,從後往前遍歷,可修改元素的值
  4. const_reverse_iterator:反向常量迭代,但不能修改元素的值,因為指向的是const的引用

每一種迭代器都提供一對首尾位置的標誌begin和end,其關係如下:

迭代器型別 開始位置標誌 末尾位置標誌 說明
iterator begin() end() 正向迭代
const_iterator cbegin() cend() 正向常量迭代
reverse_iterator rbegin() rend() 反向迭代
const_reverse_iterator crbegin() crend() 反向常量迭代

對應的示意圖如下:

file
圖1:正常的迭代

file
圖2:常量值的迭代

4. 迭代器的高階應用

以上講述的迭代器基本都是集合內部的元素具有相同的資料型別,但實際的開發過程中可能會有更復雜的容器結構,假設有如下的需要:

一個公司有多個部門,每個部門有多個人組成,這些人中有開發人員,有測試人員,和與專案相關的其它人員,其結構如下:

file

現在要遍歷這個公司的所有開發人員,遍歷這個公司的所有測試人員。

針對這個需求,我們可以建立一個定製化的迭代器來遍歷一個公司所有人員,也可以傳入員工型別來遍歷指定型別的員工,其類的結構圖如下:

file

對應的程式碼實現如下。

4.1. Enumerator.hpp

迭代器類Enumerator的實現

#pragma once

#include <string>
#include "Company.h"

class Enumerator
{
public:
    Enumerator(const Company& company, PersonType type)
      : company_(company)
      , person_type_(type)
    {
    }
    ~Enumerator() = default;

public:
    void Reset()
    {
        cur_department_index_ = 0;
        cur_person_index_ = 0;
    }
    const Person* Current()
    {
        if (cur_department_index_ >= 0 && cur_department_index_ < company_.GetDepartmentSize() &&
            cur_person_index_ >= 0 &&
            cur_person_index_ < company_.GetDepartment(cur_department_index_)->GetPersonSize())
        {
            return company_.GetDepartment(cur_department_index_)->GetPerson(cur_person_index_);
        }
        return nullptr;
    }

    bool MoveNext()
    {
        cur_person_index_++;
        bool isLoop = true;
        for (int i = cur_department_index_; i < company_.GetDepartmentSize() && isLoop; ++i)
        {
            for (int j = cur_person_index_;
                 j < company_.GetDepartment(i)->GetPersonSize() && isLoop;
                 ++j)
            {
                const Person* person = company_.GetDepartment(i)->GetPerson(j);
                if (person->GetType() == person_type_ || person_type_ == PersonType::All)
                {
                    isLoop = false;
                }
                if (isLoop)
                {
                    cur_person_index_++;
                }
            }
            if (isLoop)
            {
                cur_department_index_++;
                cur_person_index_ = 0;
            }
        }
        auto result =
          cur_department_index_ >= 0 && cur_department_index_ < company_.GetDepartmentSize() &&
          cur_person_index_ >= 0 &&
          cur_person_index_ < company_.GetDepartment(cur_department_index_)->GetPersonSize();
        return result;
    }

private:
    PersonType person_type_;
    const Company& company_{ nullptr };
    int cur_department_index_{ 0 };
    int cur_person_index_{ -1 };
};

4.2. Iterator.cpp

呼叫程式碼

#include "Company.h"
#include "Enumerator.hpp"
#include "Person.h"
#include <iostream>
#include <memory>
#include <string>

int main()
{
    int empId = 1;

    auto pPerson11 = new Developer(empId++, "Developer11", "C++", "智慧城市");
    auto pPerson12 = new Developer(empId++, "Developer12", "Java", "智慧城市");
    auto pPerson13 = new Developer(empId++, "Developer13", "JavaScript", "智慧城市");
    auto pPerson14 = new Tester(empId++, "Tester15", "LoadRunner");
    auto pPerson15 = new Tester(empId++, "Tester16", "黑盒測試");
    auto pDepartMent1 = new Department("開發1部");
    pDepartMent1->AddPerson(pPerson11);
    pDepartMent1->AddPerson(pPerson12);
    pDepartMent1->AddPerson(pPerson13);
    pDepartMent1->AddPerson(pPerson14);
    pDepartMent1->AddPerson(pPerson15);

    auto pPerson21 = new Developer(empId++, "Developer21", "IOS", "智慧語音");
    auto pPerson22 = new Developer(empId++, "Developer22", "Android", "智慧語音");
    auto pPerson23 = new Tester(empId++, "Tester24", "TestIn");
    auto pDepartMent2 = new Department("開發2部");
    pDepartMent2->AddPerson(pPerson21);
    pDepartMent2->AddPerson(pPerson22);
    pDepartMent2->AddPerson(pPerson23);

    auto pPerson31 = new Developer(empId++, "Developer31", "C++", "電子書核心");
    auto pPerson32 = new Tester(empId++, "Tester35", "LoadRunner");
    auto pDepartMent3 = new Department("核心研發部");
    pDepartMent3->AddPerson(pPerson31);
    pDepartMent3->AddPerson(pPerson32);

    Company company("陽光教育");
    company.AddDepartment(pDepartMent1);
    company.AddDepartment(pDepartMent2);
    company.AddDepartment(pDepartMent3);

    // 遍歷所有開發者
    std::cout << "遍歷所有開發者:" << std::endl;
    Enumerator enumerator1 = company.GetEnumerator(PersonType::Developer);
    while (enumerator1.MoveNext())
    {
        const Person* pPerson = enumerator1.Current();
        if (pPerson)
        {
            pPerson->showInfo();
        }
    }

    // 遍歷所有測試人員
    std::cout << "遍歷所有測試人員:" << std::endl;
    Enumerator enumerator2 = company.GetEnumerator(PersonType::Tester);
    while (enumerator2.MoveNext())
    {
        const Person* pPerson = enumerator2.Current();
        if (pPerson)
        {
            pPerson->showInfo();
        }
    }

    // 遍歷公司所有員工
    std::cout << "遍歷公司所有員工:" << std::endl;
    Enumerator enumerator3 = company.GetEnumerator(PersonType::All);
    while (enumerator3.MoveNext())
    {
        const Person* pPerson = enumerator3.Current();
        if (pPerson)
        {
            pPerson->showInfo();
        }
    }

    return 0;
}

4.3. 輸出結果

遍歷所有開發者:
員工:1-Developer11 開發工程師,擅長語言:C++,負責專案:智慧城市
員工:2-Developer12 開發工程師,擅長語言:Java,負責專案:智慧城市
員工:3-Developer13 開發工程師,擅長語言:JavaScript,負責專案:智慧城市
員工:6-Developer21 開發工程師,擅長語言:IOS,負責專案:智慧語音
員工:7-Developer22 開發工程師,擅長語言:Android,負責專案:智慧語音
員工:9-Developer31 開發工程師,擅長語言:C++,負責專案:電子書核心
遍歷所有測試人員:
員工:4-Tester15 測試工程師,測試型別:LoadRunner
員工:5-Tester16 測試工程師,測試型別:黑盒測試
員工:8-Tester24 測試工程師,測試型別:TestIn
員工:10-Tester35 測試工程師,測試型別:LoadRunner
遍歷公司所有員工:
員工:1-Developer11 開發工程師,擅長語言:C++,負責專案:智慧城市
員工:2-Developer12 開發工程師,擅長語言:Java,負責專案:智慧城市
員工:3-Developer13 開發工程師,擅長語言:JavaScript,負責專案:智慧城市
員工:4-Tester15 測試工程師,測試型別:LoadRunner
員工:5-Tester16 測試工程師,測試型別:黑盒測試
員工:6-Developer21 開發工程師,擅長語言:IOS,負責專案:智慧語音
員工:7-Developer22 開發工程師,擅長語言:Android,負責專案:智慧語音
員工:8-Tester24 測試工程師,測試型別:TestIn
員工:9-Developer31 開發工程師,擅長語言:C++,負責專案:電子書核心
員工:10-Tester35 測試工程師,測試型別:LoadRunner

4.4. 更多詳細程式碼

https://gitee.com/spencer_luo/iterator

這樣就使得程式碼簡潔易懂易讀。

迭代器的應用場景:

  1. 集合的內部結構複雜,不想暴露物件的內部細節,只提供精簡的訪問方式。
  2. 需要提供統一的訪問介面,從而對不同的集合使用同一的演算法。

【SunLogging】

SunLogging

掃碼二維碼,關注微信公眾號,閱讀更多精彩內容

相關文章