C++ STL迭代器原理和實現

1. 迭代器簡介

爲了提升C++編程的效率,STL(Standard Template Library)中提供了許多容器,包括vector、list、map、set等。然而有些容器(vector)能夠經過下標索引的方式訪問容器裏面的數據,可是大部分的容器(list、map、set)不能使用這種方式訪問容器中的元素。爲了統一訪問不一樣容器時的訪問方式,STL爲每種容器在實現的時候設計了一個內嵌的iterator類,不一樣的容器有本身專屬的迭代器(專屬迭代器負責實現對應容器訪問元素的具體細節),使用迭代器來訪問容器中的數據。除此以外,經過迭代器能夠將容器和通用算法結合在一塊兒,只要給予算法不一樣的迭代器,就能夠對不一樣容器執行相同的操做,例如find查找函數(由於迭代器提供了統一的訪問方式,這是使用迭代器帶來的好處)。迭代器對一些基本操做如*、->、++、==、!=、=進行了重載,使其具備了遍歷複雜數據結構的能力,其遍歷機制取決於所遍歷的容器,全部迭代器的使用和指針的使用很是類似。經過begin,end函數獲取容器的頭部和尾部迭代器,end迭代器不包含在容器以內,當begin和end返回的迭代器相同時表示容器爲空。node

STL主要由 容器、迭代器、算法、函數對象、和內存分配器 五大部分構成。ios

2. 迭代器的實現原理

首先,看看STL中迭代器的實現思路:
STL中迭代器的實現思路
從上圖中能夠看出,STL經過類型別名的方式實現了對外統一;在不一樣的容器中類型別名的真實迭代器類型是不同的,並且真實迭代器類型對於++、--、*、->等基本操做的實現方式也是不一樣的。(PS:迭代器很好地詮釋了接口與實現分離的意義)c++

既然咱們已經知道了迭代器的實現思路,如今若是讓咱們本身設計一個list容器的簡單迭代器,應該如何實現呢?算法

  1. list類須要有操做迭代器的方法
    1. begin/end
    2. insert/erase/emplace
  2. list類有一個內部類list_iterator
    1. 有一個成員變量ptr指向list容器中的某個元素
    2. iterator負責重載++、--、*、->等基本操做
  3. list類定義內部類list_iterator的類型別名

以上就是實現一個list容器的簡單迭代器須要考慮的具體細節。編程

3. 迭代器的簡單實現

my_list.h(重要部分有註釋說明數據結構

//
// Created by wengle on 2020-03-14.
//

#ifndef CPP_PRIMER_MY_LIST_H
#define CPP_PRIMER_MY_LIST_H

#include <iostream>

template<typename T>
class node {
public:
    T value;
    node *next;
    node() : next(nullptr) {}
    node(T val, node *p = nullptr) : value(val), next(p) {}
};

template<typename T>
class my_list {
private:
    node<T> *head;
    node<T> *tail;
    int size;

private:
    class list_iterator {
    private:
        node<T> *ptr; //指向list容器中的某個元素的指針

    public:
        list_iterator(node<T> *p = nullptr) : ptr(p) {}
        
        //重載++、--、*、->等基本操做
        //返回引用,方便經過*it來修改對象
        T &operator*() const {
            return ptr->value;
        }

        node<T> *operator->() const {
            return ptr;
        }

        list_iterator &operator++() {
            ptr = ptr->next;
            return *this;
        }

        list_iterator operator++(int) {
            node<T> *tmp = ptr;
            // this 是指向list_iterator的常量指針,所以*this就是list_iterator對象,前置++已經被重載過
            ++(*this);
            return list_iterator(tmp);
        }

        bool operator==(const list_iterator &t) const {
            return t.ptr == this->ptr;
        }

        bool operator!=(const list_iterator &t) const {
            return t.ptr != this->ptr;
        }
    };

public:
    typedef list_iterator iterator; //類型別名
    my_list() {
        head = nullptr;
        tail = nullptr;
        size = 0;
    }

    //從鏈表尾部插入元素
    void push_back(const T &value) {
        if (head == nullptr) {
            head = new node<T>(value);
            tail = head;
        } else {
            tail->next = new node<T>(value);
            tail = tail->next;
        }
        size++;
    }

    //打印鏈表元素
    void print(std::ostream &os = std::cout) const {
        for (node<T> *ptr = head; ptr != tail->next; ptr = ptr->next)
            os << ptr->value << std::endl;
    }

public:
    //操做迭代器的方法
    //返回鏈表頭部指針
    iterator begin() const {
        return list_iterator(head);
    }

    //返回鏈表尾部指針
    iterator end() const {
        return list_iterator(tail->next);
    }

    //其它成員函數 insert/erase/emplace
};

#endif //CPP_PRIMER_MY_LIST_H

test.cpp函數

//
// Created by wengle on 2020-03-14.
//

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

struct student {
    std::string name;
    int age;

    student(std::string n, int a) : name(n), age(a) {}

    //重載輸出操做符
    friend std::ostream &operator<<(std::ostream &os, const student &stu) {
        os << stu.name << " " << stu.age;
        return os;
    }
};

int main() {
    my_list<student> l;
    l.push_back(student("bob", 1)); //臨時量做爲實參傳遞給push_back方法
    l.push_back(student("allen", 2));
    l.push_back(student("anna", 3));
    l.print();

    for (my_list<student>::iterator it = l.begin(); it != l.end(); it++) {
        std::cout << *it << std::endl;
        *it = student("wengle", 18);
    }
    return 0;
}

4. 迭代器失效

// inserting into a vector
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector (3,100);
  std::vector<int>::iterator it;

  it = myvector.begin();
  it = myvector.insert ( it , 200 );

  myvector.insert (it,200,300);
  //it = myvector.insert (it,200,300);
  myvector.insert (it,5,500); //當程序執行到這裏時,大機率會crash
  for (std::vector<int>::iterator it2=myvector.begin(); it2<myvector.end(); it2++)
    std::cout << ' ' << *it2;
  std::cout << '\n';

  return 0;
}

上面的代碼很好地展現了什麼是迭代器失效?迭代器失效會致使什麼樣的問題?
當執行完myvector.insert (it,200,300);這條語句後,實際上myvector已經申請了一塊新的內存空間來存放以前已保存的數據本次要插入的數據,因爲it迭代器內部的指針仍是指向舊內存空間的元素,一旦舊內存空間被釋放,當執行myvector.insert (it,5,500);時就會crash(PS:由於你經過iterator的指針正在操做一塊已經被釋放的內存,大多數狀況下都會crash)。迭代器失效就是指:迭代器內部的指針沒有及時更新,依然指向舊內存空間的元素。測試

insert源碼

上圖展現了STL源碼中vector容器insert方法的實現方式。當插入的元素個數超過當前容器的剩餘容量時,就會致使迭代器失效。這也是測試代碼中myvector.insert (it,200,300);插入200個元素的緣由,爲了模擬超過當前容器剩餘容量的場景,若是你的測試環境沒有crash,能夠將插入元素設置的更多一些。this

相關文章
相關標籤/搜索