【问题标题】:Can I increment an iterator by just adding a number?我可以通过添加一个数字来增加一个迭代器吗?
【发布时间】:2010-11-05 04:59:22
【问题描述】:

我可以使用迭代器进行正常计算,即通过添加一个数字来增加它吗?

例如,如果我想删除元素vec[3],我可以这样做吗:

std::vector<int> vec;
for(int i = 0; i < 5; ++i){
      vec.push_back(i);
}
vec.erase(vec.begin() + 3); // removes vec[3] element

它适用于我 (g++),但我不确定它是否保证有效。

【问题讨论】:

    标签: c++ iterator


    【解决方案1】:

    数字运算只能使用随机访问迭代器,例如 std::vector 和 std::deque 中的迭代器。

        std::vector<int>list={1,2,3,4,5,6,7,8};
        auto last_v=*(list.end()-1);
        auto third_last_v=*(list.end()-3);
        std::cout<<"Last & 3rd last entry for vector:"<<last_v<<","<<third_last_v<<std::endl;
    

    将输出 8 和 6,但是对于具有双向迭代器的 std::map、std::multimap、std::set、std::multiset

        std::map<int,std::string> map_={{1,"one"},{2,"two"},{3,"three"}};
        auto last_mp=*(map_.end()-1);
        auto third_last_mp=*(map_.end()-3);
        std::cout<<"Last & 3rd last entry for map:("<<last_mp.first<<","<<last_mp.second<<") and ("<<third_last_mp.first<<","<<third_last_mp.second<<")"<<std::endl;
    

    将导致 error: no match for ‘operator-’ (operand types are ‘std::map, int&gt;::iterator {aka std::_Rb_tree_iterator, int&gt; &gt;}’ and ‘int’)

    对于双向迭代器 std::next()、std::prev 或 std::advance() 有效

        std::map<int,std::string> map_={{1,"one"},{2,"two"},{3,"three"}};
        auto last_mp=*std::prev(map_.end(),1);
        auto third_last_mp=*std::prev(map_.end(),3);
        std::cout<<"Last & 3rd last entry for map:("<<last_mp.first<<","<<last_mp.second<<") and ("<<third_last_mp.first<<","<<third_last_mp.second<<")"<<std::endl;
    

    将输出 (3,three) 和 (1,one)。在类似的注释中,std::unordered_map 有一个前向迭代器,所以这里使用 std::next() 是有意义的。

    【讨论】:

      【解决方案2】:

      一个微妙的点是operator+ 需要一个Distance;即有符号整数。如果您将迭代器增加一个无符号数,您可能会失去精度并遇到意外。例如在 64 位系统上,

      std::size_t n = (1 << 64) - 2;
      std::vector<double> vec(1 << 64);
      std::vector<double> slice(vec.begin() + n, vec.end());
      

      导致实现定义的行为。使用g++clang,您可以要求编译器使用不属于规范-Wall-Wextra 的警告标志-Wsign-conversion 警告您此类不希望的转换。

      解决方法是直接处理指针

      std::vector<double> slice(vec.data() + n, vec.data() + vec.size());
      

      它不漂亮但正确。在某些情况下,您需要手动构造迭代器,例如

      std::vector<double>::iterator fromHere{vec.data() + n};
      vec.erase(fromHere, vec.end());
      

      【讨论】:

      • 是否有关于可用于 std::vector::iterator 的构造函数的文档?
      • 此处的示例涉及将n 的值从无符号(size_t) 类型隐式转换为有符号(Distance) 类型。当n 的值太大而无法由Distance 数据类型表示时,这会导致实现定义 行为。 它永远不会导致未定义的行为
      • 我知道差异是微妙的。在stackoverflow.com/questions/2397984/… 上,我找到了对An example of undefined behavior is the behavior on integer overflow. 标准的引用。我以为它会属于这一类。如果没有,@ghd 是否可以提供 C++ 标准中的声明,说明它是实现定义的?
      • 在上面的示例代码sn-p中,整数溢出的可能性在哪里?如果无符号值不能表示为有符号值,则无符号到有符号的转换不被视为溢出。根据 C++17 工作草案标准(N4659)7.8如果目标类型是有符号的,如果它可以在目标类型中表示,则值不变;否则,该值是实现定义的。 只有overflow in signed arithmetic 会导致未定义的行为。
      • 更新了使用 implementation-defined 的答案,感谢@ghd 相关说明是isocpp.github.io/CppCoreGuidelines/…
      【解决方案3】:

      如果迭代器是随机访问迭代器,它就可以工作,向量的迭代器是(参见reference)。 STL 函数std::advance 可用于推进通用迭代器,但由于它不返回迭代器,因此我倾向于使用 +(如果可用),因为它看起来更简洁。

      C++11 笔记

      现在有std::nextstd::prev,它们确实返回迭代器,所以如果你在模板领域工作,你可以使用它们来推进通用迭代器并且仍然有干净的代码。

      【讨论】:

      • 正确;添加了一些文档链接,列出了哪些函数应该可用于哪些类型的迭代器。
      • 不,它没有。 + 运算符的意思是“一步一步,向前跳这么远”,这是列表迭代器无法做到的。前向非随机访问迭代器(如列表迭代器)仅支持增量 (++) 运算符一次推进一个元素。正如 Todd 所说,您可以使用重复调用 ++ 运算符的 std::advance 来简洁地表达将非随机迭代器向前移动多个步骤的想法。
      • 好吧,老实说,它不能保证适用于像 list 这样的双向迭代器,但我相信它也不能保证不能工作;一个实现可以添加它。
      • @TylerMcHenry:我相信std::advance 利用+ 进行随机访问迭代器(即它并不总是重复调用++)。 std::advance documentation 似乎同意。
      • 值得指出的是,在某些情况下,将 + 与 std::list 迭代器一起使用时的编译错误是优于 std::advance 的好处。如果您在向量中推进任意数量的位置(不仅仅是这里的一个位置)并且需要在恒定时间内完成,那么有人将容器更改为列表,这可能是您想要捕获的错误。最好在编译时捕获它,而不是稍后发现性能不佳时(也许在生产中!)。此外,使用 + 而不是 std::advance 记录了它是一个恒定时间操作的期望。
      【解决方案4】:

      它适用于随机访问迭代器。一般来说,您可能想查看更通用的std::advance。请务必了解使用此函数模板的性能影响。

      【讨论】:

        猜你喜欢
        • 2019-03-18
        • 2021-05-23
        • 2013-01-27
        • 1970-01-01
        • 1970-01-01
        • 2016-05-01
        • 2011-01-31
        • 2015-05-03
        • 1970-01-01
        相关资源
        最近更新 更多