主頁 > 後端開發 > C++ 如何快速實作一個容器的迭代器

C++ 如何快速實作一個容器的迭代器

2023-05-20 07:21:08 後端開發

C++ 如何快速實作一個容器的迭代器

引言

C++的標準庫中的容器都會提供迭代器,如果一個容器滿足forward_range,那么這個容器一般會提供以下成員型別和函式:

  • iterator
  • const_iterator
  • begin
  • end
  • begin
  • cend

如果該容器還滿足bidirectional_range,那么該容器還會額外提供以下成員型別和函式:

  • reversed_iterator
  • const_reversed_iterator
  • rbegin
  • rend
  • rcbegin
  • rcend

筆者曾經在網上搜索過如何實作C++迭代器的文章,但是大多數文章都只是實作了一個最普通的迭代器,從學習原理的角度來說是非常好的,但是相比于標準庫或者其他工業化的庫來說可能是非常不完整的,所以筆者打算在本文中盡可能將容器中的迭代器部分完整地實作出來,

大多數人可能由于種種原因,C++的標準還停留在C++11,所以我們這里分別使用C++11和最新的標準來撰寫迭代器,


什么是迭代器

我們這里參考cppreference原話:

Iterators are a generalization of pointers that allow a C++ program to work with different data structures (for example, containers and ranges (since C++20)) in a uniform manner.

從定義不難看出,迭代器是一個泛化的指標,


指標

既然迭代器是泛化的指標,那么我們可以通過指標來了解迭代器,我們知道指標共有兩個位置可以添加const關鍵字,共有4種可能,我們將他們對應到迭代器型別:

  • iterator => T *
  • const_iterator => const T *
  • const iterator => T * const
  • const const_iterator => const T * const

對于指標型別,const在前面表示說不可以通過該指標去修改所指的變數,而const在后面則表示該指標只能指向某個變數,不能修改指標本身,但是可以修改變數,我們在實作迭代器成員型別和函式的時候只需要關注前兩者即可,后面的可以按照C++正常的寫法來,可以加const的地方直接加上即可,


基于C++11的具體實作

由于我們的重點在于迭代器部分,所以我們盡可能地選取一個簡單的資料結構來實作容器,這里我們實作一個雙鏈表,同時我們也假設讀者閱讀過關于迭代器實作的文章,對迭代器的核心實作有一定的了解,

首先定義一下鏈表基本的實作:

template <typename T>
class LinkList {
    
    struct ListNodeBase {

        ListNodeBase* m_next;
        ListNodeBase* m_prev;

        ListNodeBase() { reset(); }

        void reset() { m_prev = m_next = this; }
    };

    struct ListNode : ListNodeBase {
        T m_value;
    };

    // 回圈鏈表頭結點,next指向第一個元素,prev指向最后一個元素
    ListNodeBase m_header;  
}

接下來是實作迭代器部分,對于很多容器來說,iterator和const_iterator是兩個不同類,但是實際上這兩個類里面的實作幾乎是完全一樣的,這也許很容易讓人想到繼承的寫法,但是在C++中我們還有一個更好的方法,那就是使用模板,使用模板的代碼量也許會少于使用繼承,如果讀者是一個非常排斥使用模板的人,不妨嘗試著去接受一下,畢竟模板是C++中非常重要的一部分,同時作者也相信在注釋和自身基本功到位的情況下,模板的撰寫和維護也不是非常困難的,

template <bool Const>
struct LinkListIterator {
    // ...
};

using iterator = LinkListIterator<false>;
using const_iterator = LinkListIterator<true>;

我們使用一個bool型別的變數(Const)來讓LinkListIterator在二者之間進行切換,當該變數為true時,他是const_iterator,否則是iterator,

迭代器一般會定義以下成員型別:

  • value_type
  • reference
  • pointer
  • difference_type
  • iterator_category

value_type在容器的迭代器中一般是當前容器元素的型別,
reference在容器的迭代器中一般是當前容器元素的參考型別,
pointer在容器的迭代器中一般是當前容器元素的指標型別,C++20之后無需定義,
difference_type表示兩個迭代器距離的型別,在容器中一般是ptrdiff_t,
iterator_category表示該迭代器的種類,

需要注意的是,容器中的迭代器并不能夠代表所有的情況,比如迭代器reference并不一定是參考型別,iterator_category也僅僅只是一個必要條件,

using value_type = typename std::conditional<Const, const T, T>::type;
using reference = value_type&;
using pointer = value_type*;   
using difference_type = std::ptrdiff_t;
using iterator_category = std::bidirectional_iterator_tag;

然后是定義建構式:

node_type* m_ptr;

LinkListIterator() = default;

LinkListIterator(const LinkListIterator&) = default;

LinkListIterator(node_type* ptr) : m_ptr(ptr) { }

// 我們可以使用一個int* 來初始化一個const int*
// 同理,我們可以使用iterator來初始化一個const_iterator
template <bool IsConst, typename = typename std::enable_if<(Const && !IsConst)>::type>
LinkListIterator(const LinkListIterator<IsConst>& other) 
    : m_ptr(other.m_ptr) { }

C++迭代器的操作一般是基于運算子的,所以我們需要對相應的運算子進行多載,bidirectional_iterator需要多載以下運算子,

// C++11
bool operator==(const LinkListIterator& other) const {
    return m_ptr == other.m_ptr;
}

bool operator!=(const LinkListIterator& other) const {
    return !this->operator==(other);
}

reference operator*() const { return m_ptr->m_value; }

pointer operator->() const { return &this->operator*(); }

LinkListIterator& operator++() {
    m_ptr = m_ptr->m_next;
    return *this;
}

// it++
LinkListIterator operator++(int) {
    auto old = *this;
    ++*this;
    return old;
}

// --it
LinkListIterator& operator--() {
    m_ptr = m_ptr->m_prev;
    return *this;
}

// it--
LinkListIterator operator--(int) {
    auto old = *this;
    --*this;
    return old;
}

到此為止,一個雙鏈表的迭代器就全部實作完畢了,至于reversed_iterator,標準庫為我們提供了std::reverse_iterator,我們只需要將我們的迭代器傳給它即可,

using iterator = LinkListIterator<false>;
using const_iterator = LinkListIterator<true>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;

最后我們提供一下相關的成員函式:

iterator begin() { return m_header.m_next; }
iterator end() { return &m_header; }

const_iterator begin() const { return const_cast<LinkList&>(*this).begin(); }
const_iterator end() const { return const_cast<LinkList&>(*this).end(); }

const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }

reverse_iterator rbegin() { return end(); }
reverse_iterator rend() { return begin(); }

const_reverse_iterator rbegin() const { return end(); }
const_reverse_iterator rend() const { return begin(); }

const_reverse_iterator rcbegin() const { return end(); }
const_reverse_iterator rcend() const { return begin(); }

由于const版本的多載實作和non-const版本是完全一致的,并且我們也提供了建構式使得iterator可以轉化為const_iterator,所以我們直接使用const_cast實作了const版本的多載,


基于C++23的具體實作

隨著技術的不斷進步,很多時候我們不必再使用以往繁瑣的方式來實作我們想要的東西,既然reverse_iterator可以直接使用標準庫的組件完成,那么const_iterator是否也可以使用類似的方法來實作呢?畢竟看起來我們只需要對iterator進行簡單封裝就可以把它變成一個const_iterator,

答案是肯定的,那就是std::const_iterator,當然這個東西涉及的細節還是非常多的,不是看起來那么簡單,讀者有興趣的話可以自行閱讀相關文獻,

我們來嘗試簡化一下上述代碼:

// template <bool Const>
struct LinkListIterator {
    using value_type = T;
    using reference = value_type&;
    // using pointer = value_type*;
    using difference_type = ptrdiff_t;
    using iterator_category = std::bidirectional_iterator_tag;

    // 我們可以使用一個int* 來初始化一個const int*
    // 同理,我們可以使用iterator來初始化一個const_iterator
    // template <bool IsConst, typename = typename std::enable_if<(Const && !IsConst)>::type>
    // LinkListIterator(const LinkListIterator<IsConst>& other) 
    //     : m_ptr(other.m_ptr) { }

};

using iterator = LinkListIterator;
using const_iterator = std::const_iterator<iterator>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;

我們只需要實作一個普通的iterator即可,因為標準庫已經為我們提供了新的組件,同時額外的建構式也自然不再需要,

pointer這一成員型別也是不必要的,std::iterator_traits會自動幫我們推導,沒有pointer之后我們可以直接使用auto來完成->運算子多載,

auto operator->() const { return &this->operator*(); }

同時對于==這一運算子,許多實作無非就是簡單比較每一個成員欄位是否相等,而!=等于運算子往往也是等價于==運算結果取反,

bool operator==(const LinkListIterator& other) const = default;

// 不寫則自動生成
// bool operator!=(const LinkListIterator& other) const { ... } 

默認生成的==運算子的,有如下規則:

A class can define operator== as defaulted, with a return value of bool. This will generate an equality comparison of each base class and member subobject, in their declaration order. Two objects are equal if the values of their base classes and members are equal. The test will short-circuit if an inequality is found in members or base classes earlier in declaration order.

需要注意的是,陣列型別的比較是按照range的規則來的,即比較陣列中每一個元素是否相等,而非將陣列看成一個指標進行比較:

struct F
{
    int data[2];
    constexpr F(int a, int b) : data{a, b} { }
    constexpr bool operator==(const F&) const = default;
};

constexpr F f1(1, 2), f2(3, 4), f3(1, 2);

static_assert(f1 != f2);
static_assert(f1 == f3);

我們的簡化程序到此為止就結束了,剩下的部分保持不變即可,關于簡化之后的代碼,讀者可以點擊這里獲取,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552929.html

標籤:其他

上一篇:分享一下mybatisPlus新代碼生成器3.5.1以上

下一篇:返回列表

標籤雲
其他(159359) Python(38156) JavaScript(25439) Java(18078) C(15229) 區塊鏈(8267) C#(7972) AI(7469) 爪哇(7425) MySQL(7202) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5340) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4573) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2433) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1975) 功能(1967) Web開發(1951) HtmlCss(1940) python-3.x(1918) C++(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1861) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • C++ 如何快速實作一個容器的迭代器

    # C++ 如何快速實作一個容器的迭代器 ## 引言 C++的標準庫中的容器都會提供迭代器,如果一個容器滿足forward_range,那么這個容器一般會提供以下成員型別和函式: - iterator - const_iterator - begin - end - begin - cend 如果該 ......

    uj5u.com 2023-05-20 07:21:08 more
  • 分享一下mybatisPlus新代碼生成器3.5.1以上

    pom引入:有MP了就不要再引入mybatis了,會出bug的 ```xml com.baomidou mybatis-plus-boot-starter 3.5.3.1 com.baomidou mybatis-plus-generator 3.5.3.1 junit junit 4.13.2 ` ......

    uj5u.com 2023-05-20 07:21:04 more
  • c++函式引數和回傳值

    - [c++函式引數和回傳值](#c函式引數和回傳值) - [函式存盤位置](#函式存盤位置) - [函式引數入堆疊順序](#函式引數入堆疊順序) - [初始化串列](#初始化串列) - [函式的回傳值](#函式的回傳值) - [用引數參考來回傳](#用引數參考來回傳) - [回傳一個引數指標](#回傳 ......

    uj5u.com 2023-05-20 07:21:00 more
  • 使用EasyExcel實作通用匯出功能

    ## 一、環境介紹 * JDK 1.8+ * EasyExcel 2.2.7 ## 二、功能實作 此功能可以實作根據傳入自定義的 匯出物體類或Map 進行excel檔案匯出。若根據Map匯出,匯出列的順序可以自定義。 **話不多說,直接看代碼** ### 匯出物體類 點擊查看代碼 ``` impor ......

    uj5u.com 2023-05-20 07:20:55 more
  • SpringBoot實作WebSocket發送接收訊息 + Vue實作SocketJs接收發

    # SpringBoot實作WebSocket發送接收訊息 + Vue實作SocketJs接收發送訊息 ### 參考: 1、https://www.mchweb.net/index.php/dev/887.html 2、https://itonline.blog.csdn.net/article/d ......

    uj5u.com 2023-05-20 07:20:34 more
  • 【K哥爬蟲普法】你很會寫爬蟲嗎?10秒搶票、10秒入獄,了解一下?

    ![01](https://img2023.cnblogs.com/other/2501174/202305/2501174-20230519165542353-407579772.png) > 我國目前并未出臺專門針對網路爬蟲技術的法律規范,但在司法實踐中,相關判決已屢見不鮮,K 哥特設了“K哥爬 ......

    uj5u.com 2023-05-20 07:09:16 more
  • 從零玩轉Nginx

    01【熟悉】實際開發中的問題? 現在我們一個專案跑在一個tomcat里面 當一個tomcat無法支持高的并發量時。可以使用多個tomcat 那么這多個tomcat如何云分配請求 |-nginx 02【熟悉】服務器概述 1,目前常見的web服務器 1,Apache(http://httpd.apach ......

    uj5u.com 2023-05-19 14:45:43 more
  • 驅動開發:通過應用堆實作多次通信

    在前面的文章`《驅動開發:運用MDL映射實作多次通信》`LyShark教大家使用`MDL`的方式靈活的實作了內核態多次輸出結構體的效果,但是此種方法并不推薦大家使用原因很簡單首先內核空間比較寶貴,其次內核里面不能分配太大且每次傳出的結構體最大不能超過`1024`個,而最終這些記憶體由于無法得到更好的釋... ......

    uj5u.com 2023-05-19 14:42:56 more
  • Linux網路編程:socket & pthread_create()多執行緒 實作clients/s

    一、問題引入 Linux網路編程:socket & fork()多行程 實作clients/server通信 隨筆介紹了通過fork()多行程實作了服務器與多客戶端通信。但除了多行程能實作之外,多執行緒也是一種實作方式。 重要的是,多行程和多執行緒是涉及作業系統層次。隨筆不僅要利用pthread_cre ......

    uj5u.com 2023-05-19 14:28:36 more
  • Windows10安裝Jmeter(圖文教程)

    Apache JMeter是Apache組織開發的基于Java的壓力測驗工具。用于對軟體做壓力測驗,它最初被設計用于Web應用測驗,但后來擴展到其他測驗領域。 它可以用于測驗靜態和動態資源,例如靜態檔案、Java 小服務程式、CGI 腳本、Java 物件、資料庫、FTP 服務器, 等等。JMeter ......

    uj5u.com 2023-05-19 14:23:03 more