获取std :: vector的迭代器索引的最有效方法是什么?


438

我正在遍历向量,并且需要迭代器当前指向的索引。可以通过两种方式完成此操作:

  • it - vec.begin()
  • std::distance(vec.begin(), it)

这些方法的优缺点是什么?

Answers:


557

it - vec.begin()正是由于Naveen给出的相反原因,我更喜欢:因此,如果将向量更改为列表,它将不会编译。如果您在每次迭代中都这样做,则很容易最终将O(n)算法转换为O(n ^ 2)算法。

如果您在迭代过程中没有在容器中跳来跳去,另一种选择是将索引保留为第二个循环计数器。

注意:it是容器迭代器的通用名称std::container_type::iterator it;


3
同意 我想说负号是最好的,但是保留第二个循环计数器比使用std :: distance更好,因为该功能可能很慢。
史蒂文·苏迪特

28
到底是it什么?
Steinfeld 2014年

32
@Steinfeld是一个迭代器。std::container_type::iterator it;
马特·蒙森

2
添加第二个循环计数器是一个显而易见的解决方案,令我很尴尬,我没想到。
Mordred

3
@Swapnil,因为std::list它不提供按元素位置直接访问元素的权限,因此,如果您不能执行操作list[5],则不应该执行操作list.begin() + 5
何塞托马斯腌肠

135

我希望这样std::distance(vec.begin(), it)做,因为它将允许我在不更改任何代码的情况下更改容器。例如,如果您决定使用std::list而不std::vector提供随机访问迭代器,则您的代码仍会编译。由于std :: distance根据迭代器特征选择了最佳方法,因此您也不会有任何性能下降。


50
当您使用没有随机访问迭代器的容器时,最好不要计算这样的距离,因为它效率低下
Eli Bendersky 2010年

6
@Eli:我同意这一点,但是在非常特殊的情况下,如果确实需要它,那么该代码仍然可以工作。
纳文

9
我认为如果容器发生更改,无论如何都应该更改代码-将std :: list变量命名vec为坏消息。如果将代码重新编写为通用代码,并以容器类型作为模板参数,那么我们可以(并且应该)讨论处理非随机访问迭代器;-)
Steve Jessop 2010年

1
以及某些容器的专业化。
ScaryAardvark'2

19
@SteveJessop:命名一个向量vec也是一个坏消息。
谭河

74

正如UncleBens和Naveen所表明的,两者都有充分的理由。哪一个“更好”取决于您想要的行为:您想保证恒定时间的行为,还是希望在必要时回落到线性时间?

it - vec.begin()需要花费固定的时间,但是operator -仅在随机访问迭代器上定义,因此,例如,代码根本不会使用列表迭代器进行编译。

std::distance(vec.begin(), it) 适用于所有迭代器类型,但仅在随机访问迭代器上使用时才是恒定时间操作。

谁都不是“更好”的人。使用一种可以满足您需求的工具。


1
过去我对此一直犯规。在两个std :: map迭代器上使用std :: distance并期望它为O(N)。
ScaryAardvark'2

6
@ScaryAardvark:您不是要指望它是O(1)吗?
jalf

12

我喜欢这个:it - vec.begin(),因为对我来说它清楚地表明“距离起点”。对于迭代器,我们习惯于以算术的方式进行思考,因此-符号是此处最清晰的指示符。


19
从字面上看,使用减法查找距离比使用“ distance
特拉维斯·高克尔

4
@Travis,对我来说是。这是一个品味和习惯的问题。我们说的it++不是这样的std::increment(it),不是吗?难道这还算不上清楚吗?
Eli Bendersky 2010年

3
++随着我们如何增加迭代器,运算符被定义为STL序列的一部分。 std::distance计算第一个和最后一个元素之间的元素数。-操作员工作的事实仅仅是一个巧合。
特拉维斯·高克尔

3
@MSalters:但是,我们使用++ :-)
Eli Bendersky 2010年

10

如果您已经将算法限制/硬编码为仅使用std::vector::iteratorstd::vector::iterator,则最终使用哪种方法都没有关系。您的算法已经具体化,超过了选择另一种算法可能会有所不同的地步。他们俩做的完全一样。这只是个人喜好问题。我个人会使用显式减法。

另一方面,如果您希望在算法中保留更高的通用性,即允许将来某天将其应用于其他迭代器类型,则最佳方法取决于您的意图。这取决于您希望在此使用的迭代器类型有多严格。

  • 如果使用显式减法,则算法将被限制为一类较窄的迭代器:随机访问迭代器。(这是您现在从中得到的std::vector

  • 如果您使用distance,则您的算法将支持更多种类的迭代器:输入迭代器。

当然,distance通常情况下,对于非随机访问迭代器进行计算是一种低效的操作(而对于随机访问迭代器,其计算与减法一样有效)。由您自己决定算法是否对非随机访问迭代器有意义。如果最终导致效率损失严重到使您的算法完全无用的程度,那么您最好坚持减法运算,从而避免效率低下的使用并迫使用户为其他迭代器类型寻求替代解决方案。如果非随机访问迭代器的效率仍在可用范围内,则应使用distance并记录该算法在随机访问迭代器中效果更好的事实。



3

我仅将-变体用于其中std::vector-含义很清楚,并且操作的简单性(不只是指针减法)由语法表示(distance另一方面,听起来像pythagoras一读,不是吗?)。正如UncleBen所指出的,-如果vector被意外更改为,它也可以作为静态断言list

我也认为这是更常见的-尽管没有数字可以证明。主参数:it - vec.begin()源代码更短-更少的打字工作,更少的空间消耗。很明显,对您问题的正确答案归结为个人喜好,这可能是有效的论点。


0

这是一个示例,用于查找10个“全部”事件以及索引。认为这会有所帮助。

void _find_all_test()
{
    vector<int> ints;
    int val;
    while(cin >> val) ints.push_back(val);

    vector<int>::iterator it;
    it = ints.begin();
    int count = ints.size();
    do
    {
        it = find(it,ints.end(), 10);//assuming 10 as search element
        cout << *it << " found at index " << count -(ints.end() - it) << endl;
    }while(++it != ints.end()); 
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.