STL迭代器原理

参考C++ STL迭代器原理和实现

迭代器

STL(Standard Template Library)中提供了许多容器,包括vector、list、map、set等,少部分容器(vector)可以通过下标来访问其中的元素,而大部分的容器都不可以这么做,为了能够以一种统一的方式来访问STL的所有容器,STL提供了迭代器访问的方式,它的实现方式是在每个容器类中内嵌了一个专属的iterator类,在iterator类中实现了对该容器的元素的遍历和访问。

通过在所有容器类中实现iterator,就可以很方便地实现一些通用算法,比如find查找函数,前两个参数填起始和结束位置的迭代器,第三个参数填需要查找的值,就可以适用于各种容器(因为迭代器提供了统一的访问方式)。

在每个容器类中实现迭代器时,需要对一些基本操作如*、->、++、==、!=、=等进行重载,使其具有了遍历复杂数据结构的能力,其遍历机制取决于所遍历的容器,所有迭代器的使用和指针的使用非常相似(迭代器很好地诠释了接口与实现分离的意义)。通过begin,end函数获取容器的头部和尾部迭代器,end迭代器不包含在容器之内,当begin和end返回的迭代器相同时表示容器为空。

实现迭代器

这里以List为例子,展示了如何在容器中内嵌和实现list_iterator类。

my_list.h

#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

上面主要包含三个部分:node结点定义、my_list类和list_iterator类,前两个都非常简单易懂,下面直接分析list_iterator相关的实现内容。

1、首先是指针ptr,iterator就是通过这个指针来遍历容器的。

2、重载操作符:

  • *:通过*iter可以访问对应的元素
  • ->:当迭代器指向的是一个对象时,可以通过iter->a这样的方式来访问这个对象里的a变量。
  • ++:重载前置++,返回的是++后的迭代器
  • ++(int):重载后置++,返回的是++前的迭代器
  • ==:判断两个迭代器所持有的指针是否指向容器的同一个元素
  • !=:判断两个迭代器所持有的指针是否指向容器的不同元素

3、begin函数中构造一个指针指向第一个元素的迭代器并且返回,end函数中构造一个指针指向最后一个元素的迭代器并且返回。

test.cpp

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

主要看iterator的用法,可见声明iter时,用的是命名空间my_list<student>里的iterator,所以对iter使用相关操作符时,执行的都是my_list.h中重载过的操作。

迭代器失效情况

使用迭代器时遇到的一个最常见的问题就是迭代器失效,两种经典的情况:

  • 往vector中插入了大量的元素导致vector扩容(在别的地方新建一块两倍的内存,并且释放原本的内存),然后还操作此前的旧的迭代器,导致crash。
  • 在for循环中erase了当前迭代器指向的元素,随后iter++时,由于要执行ptr->next操作需要访问该元素节点,而该元素已经被释放了,导致crash。

上面的情况中,无一例外都是因为迭代器中的指针ptr已经变成野指针,迭代器失效,但是仍然对迭代器进行操作而导致的错误。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值