我熟悉C ++ STL迭代器的用法,例如
for(map<pair<int,int>>::iterator it=m.begin(); it!=m.end(); ++it)
int a = it->first;
int b = it->second;
但我不知道其中的内在细节。有人可以向我解释一下吗?无论是C ++,Java,C#还是Python。
答案 0 :(得分:7)
在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)
。使用vector的这个实现,您可以使用原始指针作为迭代器:
//...
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(或您自己的)算法与这两个迭代器一起仅对该子集执行操作。
答案 1 :(得分:2)
迭代器只是一个抽象概念,定义它的解引用它,使用*运算符将给出与迭代器关联的序列中的某个元素,并且递增它会给你一个与下一个元素关联的迭代器序列。这意味着具体迭代器由序列定义,而不是单独定义的类,即为什么需要使用类型map<pair<int,int> >::iterator
而不仅仅是iterator
。每个STL序列的迭代器类型都有它自己的实现,++运算符被重载以提供指向序列中下一个元素的迭代器。
这意味着一个指向字符数组的简单指针也是一个迭代器,因为取消引用指针将为您提供与迭代器(指针)相关联的对象,并且递增它将为您提供与下一个相关联的新迭代器(指针)。序列中的元素。
双向链表的部分示例(注意:未经测试,只是写了这个,可能需要添加一些朋友条款和内容):
class doubly_linked_list {
class node {
node* prev;
node* next;
int data;
node(const int data, node* prev, node* next) : data(data), prev(prev), next(next) {}
};
static const node nil; // = node(0, 0, 0)
node* head;
node* tail;
public:
class iterator {
node* current;
iterator(node* n) : current(n) {}
public:
int& operator*() const {
return current->obj;
}
iterator& operator++() {
current = current->next;
return *this;
}
iterator& operator--() {
current = current->prev;
return *this;
}
};
double_linked_list() : head(nil), tail(nil) {}
iterator begin() const {
return iterator(head);
}
iterator end() const {
return iterator(tail);
}
};
并说明不同的数据结构将如何使用不同的迭代器作为矢量示例(就像未经测试一样)
class vector {
int* data;
size_t len;
public:
typedef int* iterator;
vector(const size_t len) : len(len) {
data = new int[len];
}
int& operator[](int i) const {
return data[i];
}
iterator begin() const {
return data;
}
iterator end() const {
return data + len;
}
};
答案 2 :(得分:1)
答案 3 :(得分:1)
使用迭代器的一点是你不需要知道“内部细节”。
然而,理解它背后的基本原理总是有用的,并且在你提到的所有语言中都是一样的:你有一个功能(或语言特性)需要对一系列对象做一些事情。然后该函数可以获取迭代器对象,并执行类似的操作(使用Python):
def DoSomething(start_iter, end_iter):
while (start_iter != end_iter):
calculate(start_iter.get_data())
start_iter = start_iter.next()
原则在所有情况下都是相同的。泛型函数接受迭代器并如上所述使用它们:获取与迭代器关联的数据,对其执行任何操作,递增迭代器,并在迭代器到达结束时退出。
例如,在C ++中,STL :: vector的迭代器非常接近于只是一个简单的整数,并且在底层迭代完成它,就像迭代纯数组一样。
答案 4 :(得分:1)
检查wiki链接是否为Iterator模式。这种模式的目的是提供对聚合对象元素的访问,而不暴露内部细节。
这是非常的解释(以及其他答案和链接提及)。例如,在Java中,AbstractList的迭代器在内部类中实现,该内部类的实例是为迭代List而创建的。 检查code此处。
答案 5 :(得分:1)
来自MSDN的C#中的example好。
有一个标准接口,IEnumerable。如果您的类是可枚举的,那么您只需实现此接口的GetEnumerator方法。
但是你可能知道,不同的类可能有不同的枚举方法,对于一个数组,你只需将指针移动1,对于一棵树,你需要一个树遍历方法。但总而言之,枚举器具有相同的接口,例如C#中IEnumerator中的方法。
除了实现IEnumerable的类之外,您仍然需要实现一个实现IEnumerator的类,特别是MoveNext
,Reset
。 GetEnumerator方法返回枚举器类的实例。
答案 6 :(得分:0)
除此之外:您发布的代码无效 - 您缺少大括号 - C#,C ++和Java都没有神奇的空白(而且代码看起来不像Python)。
此外,>>
将被解释为右移操作员(感谢Prasoon Saurav)。我想你想要这个:
for(map<pair<int,int> >::iterator it=m.begin(); it!=m.end(); ++it) { // <- here
int a = it->first;
int b = it->second;
} // <- and here