【问题标题】:C++: Proper way to iterate over STL containersC++:迭代 STL 容器的正确方法
【发布时间】:2011-06-22 22:44:23
【问题描述】:

在我的游戏引擎项目中,我广泛使用了 STL,主要是 std::stringstd::vector 类。

在许多情况下,我必须遍历它们。目前,我的做法是:

for( unsigned int i = 0; i < theContainer.size(); i ++ )
{

}
  • 我做对了吗?
  • 如果不是,为什么,我应该怎么做?

  • size() 是否真的在每个循环周期中执行此实现?性能损失可以忽略不计吗?

【问题讨论】:

  • 你应该使用size_t而不是unsigned int
  • @Maxpm - 或者,更好的是,::std::vector&lt;Foo&gt;::size_type
  • begin() 和 end() 保证复杂度为 O(1)。虽然 size 在一般容器上只能保证 O(n) (尽管字符串和向量可能对通用容器有额外的保证)。

标签: c++ stl iterator


【解决方案1】:

C++11 有一个新的容器感知 for 循环语法,如果您的编译器支持新标准,则可以使用该语法。

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main() 
{
    vector<string> vs;
    vs.push_back("One");
    vs.push_back("Two");
    vs.push_back("Three");

    for (const auto &s : vs)
    {
        cout << s << endl;
    }

    return 0;
}

【讨论】:

  • C++11 也允许你写vector&lt;string&gt; vs = { "One", "Two", "Three" };
【解决方案2】:

您可能想查看标准算法。

例如

vector<mylass> myvec;

// some code where you add elements to your vector

for_each(myvec.begin(), myvec.end(), do_something_with_a_vector_element);

do_something_with_a_vector_element 是一个函数,可以执行循环中的操作

例如

void 
do_something_with_a_vector_element(const myclass& element)
{
 // I use my element here
}

有很多标准算法 - 请参阅 http://www.cplusplus.com/reference/algorithm/ - 所以大多数东西都受支持

【讨论】:

  • for_each 需要一个函数或函子才能应用。使用 C++0x lambda 会更好。
  • +1 for_each 是正确的用法,正如 David 所说,只有在使用 C++0x 和 lambdas 时才会变得更甜。
  • 通常建议使用仿函数对象而不是函数指针,因为前者更有可能被编译器内联。 (还有其他好处。)
  • @ephemient 谢谢!我不知道仿函数比标准算法中的函数更好。 (你必须交给我可以阅读更多内容的地方)
【解决方案3】:

STL 容器支持迭代器

vector<int> v;
for (vector<int>::iterator it = v.begin(); it!=v.end(); ++it) {
    cout << *it << endl;
}

size() 每次迭代都会重新计算。

【讨论】:

  • v.end() 也更新了,虽然通常是优化的。在遍历迭代器的同时修改容器是一件棘手的事情,这取决于对容器执行的确切操作。例如在容器上调用erase会使迭代器无效,并且需要使用erase返回的迭代器。以下是更多信息:bytes.com/topic/c/answers/…
  • end() 和 size() 也是可优化的,因此它们几乎没有工作。
  • @Martin 同意了。在 C++ 中使用迭代器更多地被视为实现可通用代码(在其他容器中交换)的“最佳实践”。当人们想要触摸每个元素和/或当容器的索引(例如树)不是很明显时,也很有帮助。
  • 通常,迭代器的意义在于它们使您能够使用标准库算法,而不是自己编写循环。这似乎只是答案的一半。
【解决方案4】:
  • 对于随机访问容器,没有错。
  • 但您可以使用迭代器。

    for (string::const_iterator it = theContainer.begin();
         it != theContainer.end(); ++it) {
        // do something with *it
    }
    
  • 1234563但不要依赖它。

【讨论】:

  • 这有点误导。对sizeend 的调用将总是被优化掉。变量查找不会。所以我们不再需要处理调用的开销,但我们仍然需要查找内存。
【解决方案5】:

通常,在容器上“迭代”的正确方法是使用“迭代器”。类似的东西

string myStr = "hello";
for(string::iterator i = myStr.begin(); i != myStr.end(); ++i){
    cout << "Current character: " << *i << endl;
}

当然,如果你不打算修改每个元素,最好使用string::const_iterator

是的,size() 每次都会被调用,而且它是 O(n),所以在很多情况下性能损失会很明显并且它是 O(1),但这是一个好习惯在循环之前计算大小而不是每次调用大小。

【讨论】:

  • 需要更多 const_iterator :)
  • 我不同意。 size() 的复杂度是恒定的。见cplusplus.com/reference/stl/vector/size
  • 嗯,它应该保持不变。只有在脑死亡的实现中它不会是恒定的,但技术上对容器的size() 成员函数没有复杂性要求(以及某些容器的一些实现,例如 libstdc++ 中的std::list,采用的优势)。 C++0x 增加了任何支持size() 的容器必须以恒定的时间复杂度这样做的要求。
  • 不要将 .size()strlen 混淆,这是 O(n) 并且会自动生成至少需要 O(n^2) 复杂度的循环。
  • 已编辑以包含您的想法。
【解决方案6】:

不,这不是正确的做法。对于::std::vector::std::string,它可以正常工作,但问题是如果您使用其他任何东西,它就不会那么好用。此外,它不是惯用的。

并且,回答您的其他问题... size 函数可能是内联的。这意味着它可能只是从::std::string::std::vector 的内部获取一个值。编译器会对此进行优化,并且在大多数情况下只获取一次。

但是,这是惯用的方式:

for (::std::vector<Foo>::iterator i = theContainer.begin();
     i != theContainer.end();
     ++i)
{
    Foo &cur_element = *i;
    // Do stuff
}

++i 非常重要。同样,对于迭代器基本上是指针的::std:vector::std::string,它并不那么重要。但对于更复杂的数据结构,它是。 i++ 必须复制并创建一个临时值,因为旧值需要保留。 ++i 没有这样的问题。养成始终使用++i 的习惯,除非您有令人信服的理由不这样做。

最后,theContainer.end() 通常也会被优化,不再存在。但是你可以通过这样做来迫使事情变得更好:

const ::std::vector<Foo>::iterator theEnd = theContainer.end();

for (::std::vector<Foo>::iterator i = theContainer.begin(); i != theEnd; ++i)
{
    Foo &cur_element = *i;
    // Do stuff
}

当然,C++0x 使用 for 循环的新语法大大简化了所有这些:

for (Foo &i: theContainer)
{
     // Do stuff with i
}

这些将适用于标准固定大小的数组以及任何定义 beginend 以返回类似迭代器的东西的类型。

【讨论】:

    【解决方案7】:

    原生 for 循环(尤其是基于索引的) - 它是 C 方式,而不是 C++ 方式。

    对循环使用 BOOST_FOREACH。

    比较,对于整数容器:

    typedef theContainer::const_iterator It;
    for( It it = theContainer.begin(); it != theContainer.end(); ++it ) {
        std::cout << *it << std::endl;
    }
    

    BOOST_FOREACH ( int i, theContainer ) {
        std::cout << i << std::endl;
    }
    

    但这不是完美的方法。如果你可以在没有循环的情况下完成工作 - 你必须在没有循环的情况下完成。例如,使用算法和 Boost.Phoenix:

    boost::range::for_each( theContainer, std::cout << arg1 << std::endl );
    

    我知道这些解决方案会在您的代码中带来额外的依赖关系,但 Boost 是现代 C++ 的“必备”。

    【讨论】:

      【解决方案8】:

      对于向量来说,你做得很好,尽管这并不能转化为其他容器的正确方式。

      更一般的方式是

      for(std::vector<foo>::const_iterator i = theContainer.begin(); i != theContainer.end; ++i)
      

      这比我真正喜欢的打字要多,但随着即将发布的标准中auto 的重新定义,它将变得更加合理。这适用于所有标准容器。请注意,您将个人foo 称为*i,如果需要其地址,请使用&amp;*i

      在您的循环中,.size() 每次都会执行。但是,所有标准容器的时间都是恒定的(标准,23.1/5),所以它不会减慢你的速度。另外:标准说“应该”具有恒定的复杂性,因此特别糟糕的实现可能使其不恒定。如果您使用了如此糟糕的实现,那么您还需要担心其他性能问题。

      【讨论】:

        猜你喜欢
        • 2010-10-17
        • 1970-01-01
        • 1970-01-01
        • 2013-04-16
        • 1970-01-01
        • 1970-01-01
        • 2011-12-25
        • 1970-01-01
        • 2021-12-20
        相关资源
        最近更新 更多