在 C++ 中,容器中的迭代器类似于指向数组的指针(我假设您熟悉指针)。迭代器有不同的风格,但归根结底,它们只是引用容器内元素的一种方式(通过取消引用运算符* 和->)并遍历容器中的元素。
重要的部分不是实现,而是概念。您不需要知道如何实现列表或向量的迭代器(或在许多情况下它们有何不同),只需要知道它提供了哪些操作。进入不同容器的迭代器将有不同的实现(对于列表,它将遵循节点中的一些 next 指针,对于映射,它将遵循平衡树的 right 子或 parent 指针。事实上,迭代器进入根据编译标志或模式,同一个容器可以以不同的方式实现(某些编译器对任何给定的容器确实有不止一个实现)。但是,重要的部分是你真的不在乎它们是怎样的,只是他们允许你做什么。
作为一个简单的例子,在 g++ STL 实现中std::vector 包含三个指针,类似于:
//...
class vector {
T * _b; // beginning of allocated memory
T * _e; // one past the last inserted element
T * _c; // one past the end of the allocated memory
//...
}
这样size() = (_e - _b) / sizeof(T) 和capacity = (_c - _b) / sizeof(T)。通过这个向量实现,您可以使用原始指针作为迭代器:
//...
class vector {
public:
typedef T* iterator;
T* begin() { return _b; }
T* end() { return _e; }
//...
}
但您也可以构建更复杂(更慢但更安全)的迭代器实现,例如检查迭代器,如果迭代器已失效,则会触发断言(此代码过于简化,仅用于示例目的):
template <typename T>
class checked_iterator {
public:
checked_iterator( std::vector<T> & v, std::size_t e )
: _check_begin(v._b), _vector(v), _pos( v._b + e )
{}
T& operator*() {
assert( _check_begin == _vector._b && "Dereferencing an invalidated iterator");
return *_pos;
}
// ...
private:
T * _pos;
T const * const _check_begin;
std::vector<T>& _vector;
};
此迭代器实现将检测到对无效迭代器的解引用(仅在整个向量重定位的情况下,但通过存储更多数据它可以进行全面检查),并将在仍在开发中时中止不正确程序的执行。从用户的角度来看,它将是一个普通的 RandomAccessIterator(它应该是,这是向量迭代器的要求),但在幕后它将提供一种机制来识别其他难以检测的错误。
这是 VS 编译器中的方法:在调试模式下(并且取决于编译器标志),它将使用慢速安全迭代器,这将有助于检测通过无效迭代器的访问(对于向量,只要元素是添加到容器中)。同时,更改编译器标志可以获得纯原始指针实现,这对于生产系统来说会快得多,但调试无效的迭代器使用会困难得多。
在 Java 和 C# 中,它们实际上是实现几个简单操作(在 Java 中 hasNext()、next() 和 remove())的对象,这些操作允许对整个容器进行横向隐藏,从而隐藏容器的实现方式。它们非常相似,目的是封装用户代码对特定容器执行的操作。
一个重要的区别是,在这两种情况下,您都可以使用它们来迭代整个容器,但在 c++ 中它们是可比较的,您可以将任意两个迭代器迭代到同一个容器中。例如,在包含您的城市电话簿的地图中,您可以使用操作来获取以 ac 开头的名字的迭代器,并使用另一个搜索来获取名称以 'd' 开头的第一个元素(假设名称排序)并且您可以将任何 STL(或您自己的)算法与这两个迭代器一起使用,以仅对那部分人执行操作。