为什么要使用string::iterator而不是索引?

49

可能是重复问题:
为什么要使用迭代器而不是数组索引?

string::iterator it;
for (it = str.begin(); it < str.end(); it++) 
    cout << *it;
cout << endl;

为什么不这样做:

for (int i = 0; i < str.size(); i++)
    cout << str[i];
cout << endl;

看起来,string::iterator也没有提供范围检查。为什么我们要使用 string::iterator 而不是索引呢?

谢谢。


8
除了其他答案之外,确保在循环中使用迭代器时养成使用前置递增的习惯。即使用++it而不是it++。前置递增不会创建不必要的临时变量。 - Jagannath
6
除了Jagannath的评论之外,在将迭代器与end()进行比较时,更倾向于使用operator!=()而不是operator<() - jason
2
@jcyang:后置递增的典型实现是 MyIterator operator++(int) { MyIterator temp(*this); ++*this; return temp; }。无论是否分配,这都会创建一个不必要的临时变量。我默认我们已经重载了前置递增。 - jason
2
@jcyang:最好使用++i。因为如果您稍后更改所使用的类型,那么您无需担心更改代码。无论循环变量的类型是什么,您现在都将始终拥有最有效的版本。 - Martin York
7
迭代器通常没有定义 < 运算符。你只是碰巧得到了 std::string 迭代器的该运算符。在测试是否已达到结尾时,应使用 != 运算符。请注意保持原文意思不变并使翻译易懂。 - Martin York
显示剩余4条评论
9个回答

37

索引只能用于支持随机访问 - 直接访问给定位置的容器。

迭代器提供了一种统一的方式来访问任何集合/数据结构。重构代码时的灵活性是巨大的。


20

迭代器是一种标准接口。通过使用迭代器,您可以在不同的容器上使用相同的算法。最终是否使用它们取决于可用性和可读性,由您自己决定。

例如,使用标准的转换算法将 std::string 转换为大写:

std::string str = "A String";
std::transform(str.begin(), str.end(), str.begin(), ::toupper);

这会导致str等于"A STRING"


那么特定的string::iterator呢?例如,有什么好处吗? - Jichao
@jcyang:只要使用迭代器,那么它就可以在任何需要迭代器的地方工作,而且字符串可以更改为字符数组、向量或其他容器,你的循环仍然可以正常工作。 - jalf
在上面的例子中,方法std::string::begin()返回一个std::string::iterator,与std::string::end()一样。好处是你可以使用std::transform算法。你不能用索引。 - Thomas Matthews
如何按UTF-8字符(而不是8位字符)进行迭代?是否有适用于std :: strings的UTF-8迭代器(我猜迭代器的类型应该是uint16_t或uint32_t)?如何迭代希腊字母字符串“\ u03b4 \ u03b8 \ u03c6”? - x4444

9

对于std::string,我建议您使用索引,因为它支持随机访问,并且更简单。唯一的原因是“推荐”使用迭代器是因为迭代器提供了一个标准接口来访问序列,这样如果您的序列更改为std::list,您的迭代代码将保持不变。


还要注意,迭代器可能会失效,而索引不会。 - musiphil

2
在你不知道你正在迭代哪个类(因为它是一个模板参数)的情况下,你应该使用迭代器,因为并不是每个提供迭代器的类也提供[](而且并不是每个提供[]的类都能在O(1)时间内工作)。因此,通过使用迭代器,你可以确保该函数将与尽可能多的类一起工作(但不包括C数组)。
在这种特定情况下,我认为除了个人偏好或者过早优化,没有理由更喜欢其中一个。

2

重复内容:

  1. 迭代器..为什么要使用它们?
  2. 为什么使用迭代器而不是数组索引?

话虽如此,这是一种通用性问题。使用STL迭代器可以比使用数组访问做更多的事情。此外,如果您需要重构代码,并将字符串更改为向量、列表或绳子,则根本不必重写代码。

最后还有迭代中的安全问题。如果您想在循环中访问下一个字符,则使用迭代器可以安全地执行此操作,但增加数组下标可能会在最后一个元素上出现段错误,因此需要进行另一个检查。


2
在循环中尝试访问(解引用)下一个字符,使用迭代器和数组下标都不比较安全!两者都需要检查以确保您没有到达字符串的末尾。 - j_random_hacker

1
此问题所述,size()方法不能保证为O(1)。

1
从那个讨论中,似乎不太清楚。有一条评论提到s.end() - s.begin()肯定具有常数复杂度,因此实现size()时要疯了才会选择更差的复杂度。 - UncleBens
2
s.size() 无法用类似 strlen() 的方法实现,因为 std::string 可能包含任何字符(包括 '\0')。此外,字符串必须跟踪它的结束位置(或者大小)。如果字符串不知道其长度并且需要在 O(N) 时间内找到结尾,你认为 s.end() 和迭代器会更好吗? - UncleBens
1
@UncleBens: 如果该字符串没有存储其大小(虽然可能会这样做),那么查找size()将是O(n)。使用迭代器begin()和end()查找大小也是如此(执行end() - begin()可能不是直接算术操作,因为字符串中没有要求连续的内存)。但第二个循环并没有计算大小,它只是不断递增迭代器,直到达到结尾。 - Martin York
@LokiAstari:basic_string 的存储应该是连续的。请参见[LWG问题#530](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#530)。 - musiphil
@LokiAstari:你在评论中说,“字符串中没有连续内存的要求”,这听起来像是一个断言(“没有要求”的断言)。我想补充一下信息,即事情可能有些模糊,但现在已经明确解决了(需要连续内存)。人们可能也会对标准中修复问题的过程感兴趣。这是否重复或愚蠢? - musiphil
显示剩余10条评论

0

两种方法都可以。

主要原因是为了保持一致性:您以相同的方式迭代集合或字符串的字符,通过请求迭代器并使其前进。

我认为不值得提及 ++it 的实现细节导致指针增量与 str [i] 涉及指针算术。范围检查也是实现细节。


0

迭代器更安全,提供更多的灵活性,正如其他人所说。此外,只有支持随机访问(即在给定位置直接访问元素)的容器才能使用索引。迭代器是一个更通用的概念。迭代器提供了对链表、文件和许多其他数据结构的高效遍历。它经常导致生成更有效率的代码。


4
迭代器不会更快。每次使用迭代器进行的测试都表明,它们如果有任何差别,只是稍微慢一些。 - anon
谢谢,我已经修改了我的帖子。 - Prasoon Saurav

-1
在C++中,你可以用许多不同的方式做许多事情。这是又一个例子。 在这种情况下,使用哪种方法没有区别。但一般来说,迭代器更快、更安全,并且在不同类型的容器之间提供更多的灵活性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接