繁体   English   中英

end() 迭代器的算术运算

[英]Arithmetic on end() iterator

让 A 成为std::vector<double>

这是明确定义的吗?

if(!A.empty())
    std::vector<double>::iterator myBack = A.end() - 1;

end迭代器仅适用于等式和不等式检查吗? 或者只要我留在容器中,我就可以执行一些指针运算?

在我的平台上,此代码有效。 我想知道这是否是便携式的。

它完全有效,因为vector::iterator是一个随机访问迭代器。 您可以对其执行算术运算,并且它不依赖于平台。

std::vector<double>::iterator it = A.end();
while (it != A.begin()){
    --it; //this will skip A.end() and loop will break after processing A.front()
    //do something with 'it'
}

但是A.end()指的是理论上的过去结束元素,因此它不指向元素,因此不应取消引用。 所以最佳实践是使用反向迭代器而不是递减结束迭代器。

for(std::vector<double>::reverse_iterator it = A.rbegin(); it != A.rend(); ++it) {
    //do something with 'it'
}

这两个循环做同样的事情,第二个是可以理解的,更简洁的方法。

如果您注意一些特殊情况,这几乎是安全的:

A.end()为您提供一个迭代器,表示刚好超出std::vector末尾的位置。 应该试图取消对它的引用。

如果向量具有零个元素,则A.end() - 1定义明确。 在所有其他情况下,只要您在容器边界内,您确实可以执行指针运算。 请注意,标准保证std::vector数据是连续的,并且以与 contains 类型的 C++ 数组完全相同的方式打包。 唯一的例外是std::vector<bool>由于标准指定的紧包装专业化而表现不同。 (请注意,标准保证sizeof(bool)具有特定值)。

如果我是你,我会使用A.rbegin()访问最右边的元素并在继续之前检查返回值并坚持迭代器公式。 忘记std::vector<bool>专业化太容易了。

我意识到这个问题有点老了,但我被引导到这里关于end() - 1 ,我发现现有的答案和评论信息丰富且合理,但由于缺乏引文而无法令人信服,而且我不确定它们是否特定于vector 所以我尽可能多地挖掘具体信息,让自己相信这里的答案是正确的。

这篇文章代表了我为确认答案所做的研究,基本上是我的笔记,但我试图让它尽可能连贯,我认为它可能会有用。 如果有什么这里是关闭的,更正,将不胜感激。


重述答案

这里的TL;DR是的,这里的答案是正确的,不仅对于vector ,而且在更一般的情况下也是如此:

如果容器的迭代器类型满足BidirectionalIterator (并因此提供递减操作),那么以下对于任何容器类型总是有效的,其中e被初始化为container.end()的返回值:

  • 如果!container.empty()那么--e是有效的。
  • 如果!container.empty()++(--e) == container.end()为真。

如果迭代器也满足RandomAccessIterator ,那么这些更一般的语句是有效的:

  • e - ne -= n表示[ 0, container.size() ] 中的任何整数n
  • e + ne += n对于[ - container.size() , 0 ] 中的任何整数n

因此,OP 中的vector示例不仅很好,正如其他答案所述,而且它定义良好,并且保证适用于任何容器类型。


推理

所以现在这是我觉得缺失的一点。 一、从Container要求:

表达 返回类型 语义 状况 复杂
a.end() (const_) 迭代器 迭代器到a的最后一个元素之后 持续的

这表示“超过最后一个元素”。 但是,这是否意味着end()是可递减的? 我们需要确定。 下面的项目在这里很重要,我已经给它们编号以供参考:

  1. 容器:在“ end()返回一个过去端”上面提到的要求。
  2. RandomAccessIterator : i - n ,根据-=定义,没有给出约束。
  3. RandomAccessIterator : r -= n ,根据+=定义,没有给出约束。
  4. RandomAccessIterator : r += n ,根据--r定义n < 0 ,没有给出约束。
  5. 双向--a :-- --a
    • 前提条件: a是可递减的 → 存在b使得a == ++b
    • 后置条件: a是可解引用的。
    • 后置条件: --(++a) == a
    • 后置条件:如果--a == --b那么a == b
    • 后置条件: a--a是同一个迭代器实例。
  6. BidirectionalIterator :注释: “双向迭代器不必可解引用才能自减(特别是,结束迭代器不可解引用,但可自减)”。
  7. Container :声明size()在语义上等同于std::distance(begin(), end())
  8. distance :返回从firstlast的增量数

打破这个:

(5) 的先决条件指出,要使--a起作用, a必须是可递减的,并且继续定义迭代器a是可递减的,如果存在b使得++ b == a

(1) 的“one over the end”语言似乎暗示如果b是容器中最后一个元素的迭代器,则++ b == end() 不过,更令人信服的是,(7) 表明std::distance(begin(), end())必须工作,因此 (8) 意味着begin()返回的迭代器必须能够重复递增,直到它等于end() ,这意味着对于非空容器,在某些时候必须存在b使得++ b == end()

将这两者结合起来,表明如果!empty()end()总是可递减的,因为总是有b使得++ b == end() (否则distance(begin(), end()) — 和因此size() — 不符合其语义要求),这是可递减性的定义。 还要注意,(6) 明确指出可递减迭代器不需要是可解引用的,并且有关于结束迭代器的递减性的注释。

此外,由于end()!empty()时可递减,那么(其中e被初始化为container.end()的返回值):

  • -- e是有效的,来自 (5)。
  • e += n对于n <= 0是有效的,来自 (4)。
  • e -= n对于n >= 0是有效的,来自 (3)。
  • e - n对于n >= 0是有效的,来自 (2)。
  • 由于+=-=- (对于上面指出的n 的符号)都是根据重复应用--语义定义的,这将n限制在容器的大小范围内,因为begin()不可递减(根据定义递减性),最终迭代器必须点击begin()

因此,只要在应用它的迭代器之前至少有 1 个元素,OP 中的- 1就是有效的(来自(2))。

Decrementability vs. dereferenceability :请注意,这是有区别的。 (6) 指出概念是分开的。 可递减性意味着--i是有效的,可解引用意味着*ii->是有效的。 在 OP 的vector示例中,虽然end()是可递减的,但它不可解引用( vector::end()明确说明了这一点)。


代码

哦,是的,我还编写了一个测试程序作为完整性检查:

#include <boost/core/demangle.hpp>
#include <version>
#if __has_include(<array>) && (__cplusplus >= 201103L)
#  include <array>
#  define HAVE_ARRAY 1
#endif
#include <vector>
#include <deque>
#include <list>
#include <set> // + multiset
#include <map> // + multimap
#if __has_include(<span>) && (__cpp_lib_span >= 202002L)
#  include <span>
#  define HAVE_SPAN 1
#endif
#include <typeinfo>
#include <cassert>
#include <cstdio>

#if (__cpp_constexpr < 200704L)
#  define constexpr
#endif

using namespace std;

constexpr const int MAGIC = 42;

int extract (const int &v) {
    return v;
}

int extract (const pair<int,int> &v) {
    assert(v.first == v.second);
    return v.first;
}

template <typename C> struct decrementable_end_tester {
    C container;
    decrementable_end_tester ();
    void test () {
        printf("%s...\n", boost::core::demangle(typeid(C).name()).c_str());
        assert(!container.empty());
        {
            typename C::iterator b = container.begin();
            typename C::iterator e = container.end();
            assert(b == --e);
            assert(extract(*e) == MAGIC);
            assert(container.end() == ++e);
        }
        {
            typename C::iterator b = container.begin();
            typename C::iterator e = container.end(); 
            assert(e == ++b);
            assert(container.begin() == --b);
            assert(extract(*b) == MAGIC);
        }
    }
};

// i thought templating that would make initialization simpler but i'm not really
// that great with templates so i dunno if i got the most out of it:

template <typename C> decrementable_end_tester<C>::decrementable_end_tester () {
    container.insert(container.end(), MAGIC);
}

#if HAVE_ARRAY
template <> decrementable_end_tester<array<int,1> >::decrementable_end_tester () {
    container[0] = MAGIC;
}
#endif

#if HAVE_SPAN
static int span_buffer = ~MAGIC;
template <> decrementable_end_tester<span<int,1> >::decrementable_end_tester () 
    : container(&span_buffer, 1)
{
    container[0] = MAGIC;
}
#endif

template <> decrementable_end_tester<map<int,int> >::decrementable_end_tester () {
    container.insert(make_pair(MAGIC, MAGIC));
}

template <> decrementable_end_tester<multimap<int,int> >::decrementable_end_tester () {
    container.insert(make_pair(MAGIC, MAGIC));
}

int main () {
    // forward_list, unordered_([multi](set|map)) don't use bidirectional iterators.
#if HAVE_ARRAY
    decrementable_end_tester<array<int,1> >().test();
#endif
    decrementable_end_tester<vector<int> >().test();
    decrementable_end_tester<deque<int> >().test();
    decrementable_end_tester<list<int> >().test();
    decrementable_end_tester<set<int> >().test();
    decrementable_end_tester<multiset<int> >().test();
    decrementable_end_tester<map<int,int> >().test();
    decrementable_end_tester<multimap<int,int> >().test();
#if HAVE_SPAN
    decrementable_end_tester<span<int,1> >().test();
#endif
}

应该在不触发任何断言的情况下运行。


我希望那是有帮助的。 几乎所有这些都是我努力说服自己end() - 1确实有效

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM