C++ STL全面解析:六大核心组件之一----迭代器(STL进阶学习)

 

目录

迭代器(iterator)

迭代器的分类

迭代器的使用

创建迭代器

迭代器的基本操作

使用迭代器遍历容器

迭代器的有效性和安全性

迭代器的类型

迭代器与算法

迭代器设计思维

迭代器的本质

文件 mylist-iter.h

文件 mylist-iter-test.cpp

迭代器相应类型


C++标准模板库(STL)是一组高效的数据结构和算法的集合,广泛应用于C++程序设计中。STL由六大核心组件组成,分别是:

  1. 容器(Containers):各种数据结构,如vector,list,deque等等。
  2. 迭代器(Iterators):扮演容器域算法之间的胶合剂,是所谓的”泛型指针“
  3. 算法(Algorithms):各种常用算法,例如sort,search,copy,erase等等。
  4. 函数对象(Function Objects):又名为仿函数,行为类似函数,可作为算法的某种策略。
  5. 适配器(Adapters):一种用来修饰容器或者仿函数或迭代器接口的东西,例如:stack和queue。
  6. 分配器(Allocators):负责空间配置域管理,从实现角度来看他是一个管理空间的模板类。

        STL六大组件的交互关系,容器通过分配器获取数据存储空间,算法则通过迭代器去存取容器的内容,(这也是为什么说迭代器是类似于一种胶合剂的角色),仿函数则可以协助算法完成不同的策略变化,适配器可以修饰或者套接仿函数。

迭代器(iterator)

迭代器可以被视为指向容器中元素的一个“指针”。它可以指向容器中的任意位置,并且提供了基本的指针操作,如递增(++)、递减(--)、解引用(*)等。迭代器作为我们日常编程中的常客之一,其重要性不言而喻。

注:以下内容部分参考自《STL源码剖析》。

迭代器的分类

迭代器可以根据功能的不同分为以下几类:

  1. 输入迭代器(Input Iterator):只能读取数据,支持前向移动(++)和解引用(*)操作。
  2. 输出迭代器(Output Iterator):只能写入数据,支持前向移动(++)和赋值(operator *)操作。
  3. 前向迭代器(Forward Iterator):结合了输入迭代器和输出迭代器的功能,支持前向移动(++)和解引用(*)操作。
  4. 双向迭代器(Bidirectional Iterator):除了前向迭代器的功能外,还支持反向移动(--)操作。
  5. 随机访问迭代器(Random Access Iterator):除了双向迭代器的功能外,还支持通过索引快速访问元素(operator[])。

迭代器的使用

创建迭代器

大多数标准容器(如 std::vector, std::list, std::map 等)提供了 begin()end() 成员函数来获取迭代器:

std::vector<int> vec = {1, 2, 3, 4, 5};
auto it_begin = vec.begin();  // 指向容器的第一个元素
auto it_end = vec.end();      // 指向容器的最后一个元素之后的位置
迭代器的基本操作

迭代器提供了以下基本操作:

  • 解引用(dereference):*it 获取迭代器指向的元素。
  • 递增(increment):++it 将迭代器移动到下一个元素。
  • 递减(decrement):--it 将迭代器移动到前一个元素。
  • 比较it1 == it2 或 it1 != it2 比较两个迭代器是否指向同一位置。
使用迭代器遍历容器

遍历容器中的元素通常使用迭代器来实现:

std::vector<int> vec = {1, 2, 3, 4, 5};

// 使用迭代器遍历
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << std::endl;
}

// 使用范围 for 循环(C++11 及以上版本)
for (const auto& element : vec) {
    std::cout << element << std::endl;
}

迭代器的有效性和安全性

迭代器的有效性是指迭代器是否仍然指向容器中的有效元素。如果容器发生改变(如插入或删除元素),迭代器可能会变得无效。例如:

std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();

// 插入一个元素会导致迭代器失效
vec.insert(it, 0);

// 此时 it 可能不再有效

为了保证迭代器的安全性,通常需要在操作容器之前检查迭代器的有效性,并在容器改变后重新获取迭代器。

迭代器的类型

C++ 标准库中的容器提供了特定类型的迭代器,可以通过类型别名方便地访问:

std::vector<int>::iterator it;  // 随机访问迭代器
std::list<int>::iterator it;    // 双向迭代器
std::map<int, int>::iterator it;// 双向迭代器
std::set<int>::iterator it;     // 双向迭代器

迭代器与算法

C++ 标准库中的算法(如 std::sort, std::find, std::reverse 等)通常接受迭代器作为参数,允许在不同类型的容器上使用相同的算法:

std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};
std::sort(vec.begin(), vec.end());  // 排序

int value = 2;
auto it = std::find(vec.begin(), vec.end(), value);  // 查找

if (it != vec.end()) {
    std::cout << "Found " << value << " at position " << std::distance(vec.begin(), it) << td::endl;
}

迭代器设计思维

        首先我们要知道STL设计时的中心思想:在使用时将容器和算法分开来,彼此独立设计,但是我如果想让容器使用算法怎么使用?迭代器就是两者之间的粘合剂。这个不难理解,我们日常在编程中会用到大量的例子。例如我们创建了个vector,我们可能会用到sort等算法,我们通常的做法就是把vector的头尾迭代器发给find,然后即可查找。例图如下

        现在我们知道迭代器是容器和算法之间的粘合剂,但是表面看来迭代器貌似是依附于容器的一个指针对吧。但是它只能依附于容器之下吗?是否有一些独立的用途?

迭代器的本质

        迭代器本质上可以视为一种智能指针。虽然迭代器具备普通指针的基本操作,如 ++--,但迭代器内部封装了更多的细节和检查机制,使其更加安全和易于使用。智能指针则是一种用于包装原生指针的对象,它能够有效地管理内存,防止内存泄漏。智能指针的用法如下,和原生指针一摸一样。

        上面意思是,首先是new了一个处置为jjhou的string对象,然后用auto_ptr<string>类型的指针指向了这个对象。注意:auto_ptr括号内放的是“原生指针所指对象”的型别,而不是原生指针的型别。

        如果我们自己设计一个迭代器的话该如何设计呢?首先我们需要有一个迭代器的模板。

        有了模仿对象后,我们现在以List为例设计一个迭代器。假设list的结构如下:

                如果让这个list与算法结合到一起呢?没错,那就是设计一个迭代器让他们粘合在一起。当我们获取迭代器是传回来的应该是一个ListItem的一个对象,递增是则指向下一个ListItem对象,反之递减。为了让我们的迭代器去适合任何类型的节点,这里设计时我们需要用到class template。

文件 mylist-iter.h

#ifndef MYLIST_ITER_H
#define MYLIST_ITER_H

#include "mylist.h"

template<class Item>
struct ListIter {  // 这个迭代器特定只为链表服务,因为其独特的 operator++
    Item* ptr;  // 保持与容器之间的一个联系(保持对容器的引用)

    ListIter(Item* p = nullptr) : ptr(p) {}  // 默认构造函数

    // 不必实现 copy ctor,因为编译器提供的缺省行为已足够
    // 不必实现 operator=,因为编译器提供的缺省行为已足够

    Item& operator*() const { return *ptr; }
    Item* operator->() const { return ptr; }

    // 以下两个 operator++ 遵循标准做法,参见 [Meyers96] 条款 6
    // (1) 前缀递增操作
    ListIter& operator++() {
        ptr = ptr->next;
        return *this;
    }

    // (2) 后缀递增操作
    ListIter operator++(int) {
        ListIter tmp = *this;
        ++(*this);
        return tmp;
    }

    bool operator==(const ListIter& i) const {
        return ptr == i.ptr;
    }

    bool operator!=(const ListIter& i) const {
        return ptr != i.ptr;
    }
};

#endif // MYLIST_ITER_H

文件 mylist-iter-test.cpp

  此文件中我们把List和Find由ListIter粘合起来.

#include "mylist.h"
#include "mylist-iter.h"
#include <iostream>

void main() {
    List<int> mylist;

    // 插入元素
    for (int i = 0; i < 5; ++i) {
        mylist.insert_front(i);
        mylist.insert_end(i + 2);
    }

    // 输出列表
    std::cout << "List contents: ";
    mylist.display();
    std::cout << std::endl;

    // 创建迭代器
    ListIter<ListItem<int>> begin(mylist.front());
    ListIter<ListItem<int>> end(nullptr);  // 默认为 null
    ListIter<ListItem<int>> iter;

    // 查找元素
    iter = find(begin, end, 3);
    if (iter == end) {
        std::cout << "Not found" << std::endl;
    } else {
        std::cout << "Found: " << iter->value() << std::endl;
    }

    // 再次查找元素
    iter = find(begin, end, 7);
    if (iter == end) {
        std::cout << "Not found" << std::endl;
    } else {
        std::cout << "Found: " << iter->value() << std::endl;
    }
}

        从上方的一些实现细节我们可以看出,为了完成一个针对List而设计的迭代器,我们会暴露很多List实现细节。例如在制作begin和end时就暴露了ListItem。这在我们日常编程时可能没有什么太大的关系,但是在STL中这是一件非常不好的事,可能会影响一些封装性等。既然无可避免,那不如把List迭代器的开发工作交给List的创作人即可,这样实现细节可以最大性质的被封装起来。这也是为什么每种STL容器都有各自专属的迭代器。

迭代器相应类型

        在算法中使用迭代器时,很可能会用到其相应型别。什么是相应型别呢?迭代器所指之物的类型就是相应型别其一。假设算法中需要声明一个相应型别类型的变量那该怎么办?毕竟C++没有typeof()!

        解决办法是:利用function template的参数推导机制。

        我们以 func() 作为对外的接口,但实际的操作则委托给 func_impl()。由于 func_impl() 是一个函数模板,一旦被调用,编译器会自动进行模板参数推导,从而确定类型 T,从而顺利解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨笨小乌龟11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值