简体   繁体   English

end() 迭代器的算术运算

[英]Arithmetic on end() iterator

Let A be a std::vector<double> ,让 A 成为std::vector<double>

Is this well-defined?这是明确定义的吗?

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

Is the end iterator only good for equalities and inequalities checks? end迭代器仅适用于等式和不等式检查吗? Or I can perform some pointer arithmetic as long as I remain in the container?或者只要我留在容器中,我就可以执行一些指针运算?

On my platform this code works.在我的平台上,此代码有效。 I'm wondering if this is portable.我想知道这是否是便携式的。

It is perfectly valid as vector::iterator is a random access iterator.它完全有效,因为vector::iterator是一个随机访问迭代器。 You can perform arithmetic operations on it and it is not platform dependent.您可以对其执行算术运算,并且它不依赖于平台。

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'
}

But A.end() refers to theoretical past-the-end element, so it does not point to an element and thus shall not be dereferenced.但是A.end()指的是理论上的过去结束元素,因此它不指向元素,因此不应取消引用。 So best practice is to use reverse iterator instead of decrementing end iterator.所以最佳实践是使用反向迭代器而不是递减结束迭代器。

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

These two loops do the same thing, second is just understandable, cleaner way to do it.这两个循环做同样的事情,第二个是可以理解的,更简洁的方法。

It's almost safe if you are mindful of some exceptional cases:如果您注意一些特殊情况,这几乎是安全的:

A.end() gives you an iterator denoting the position just beyond the end of the std::vector . A.end()为您提供一个迭代器,表示刚好超出std::vector末尾的位置。 You should not attempt to dereference it.应该试图取消对它的引用。

If the vector has zero elements then A.end() - 1 is not well-defined.如果向量具有零个元素,则A.end() - 1定义明确。 In all other cases it is and you can indeed perform pointer arithmetic so long as you are in the container bounds.在所有其他情况下,只要您在容器边界内,您确实可以执行指针运算。 Note that the standard guarantees that the std::vector data are contiguous and packed in exactly the same way as an C++ array of the contains type.请注意,标准保证std::vector数据是连续的,并且以与 contains 类型的 C++ 数组完全相同的方式打包。 The only exception is std::vector<bool> which behaves differently due to a standards-specified tight packing specialisation.唯一的例外是std::vector<bool>由于标准指定的紧包装专业化而表现不同。 (Note well that sizeof(bool) is not guaranteed to have a particular value by the standard). (请注意,标准保证sizeof(bool)具有特定值)。

If I were you I'd use A.rbegin() to access the rightmost element and check the return value before proceeding and stick to the iterator formulation.如果我是你,我会使用A.rbegin()访问最右边的元素并在继续之前检查返回值并坚持迭代器公式。 It's all too easy to forget the std::vector<bool> specialisation.忘记std::vector<bool>专业化太容易了。

I realize this question is a bit old but I was directed here regarding end() - 1 , and I found the existing answers and comments to be informative and reasonable, but unconvincing in lack of citations, and also I wasn't sure if they were specific to vector .我意识到这个问题有点老了,但我被引导到这里关于end() - 1 ,我发现现有的答案和评论信息丰富且合理,但由于缺乏引文而无法令人信服,而且我不确定它们是否特定于vector So I dug up as much concrete information as I could to convince myself that the answers here were correct.所以我尽可能多地挖掘具体信息,让自己相信这里的答案是正确的。

This post represents my research to confirm the answers, and is basically my notes, but I tried to make it as coherent as possible and I thought it might be useful.这篇文章代表了我为确认答案所做的研究,基本上是我的笔记,但我试图让它尽可能连贯,我认为它可能会有用。 If anything here is off, corrections would be greatly appreciated.如果有什么这里是关闭的,更正,将不胜感激。


Restating The Answers重述答案

The TL;DR here is yes, the answers here are correct, not just for vector , but in the more general case as well:这里的TL;DR是的,这里的答案是正确的,不仅对于vector ,而且在更一般的情况下也是如此:

If the container's iterator types satisfy BidirectionalIterator (and therefore provide decrement operations), then the following will always be valid, for any container type, where e is initialized to the return value of container.end() :如果容器的迭代器类型满足BidirectionalIterator (并因此提供递减操作),那么以下对于任何容器类型总是有效的,其中e被初始化为container.end()的返回值:

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

If the iterators also satisfy RandomAccessIterator , then these more general statements are valid:如果迭代器也满足RandomAccessIterator ,那么这些更一般的语句是有效的:

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

And so, the vector example in the OP is not only fine, as the other answers also state, but it's well-defined and guaranteed to be fine for any container type.因此,OP 中的vector示例不仅很好,正如其他答案所述,而且它定义良好,并且保证适用于任何容器类型。


Reasoning推理

So now here is the bit that I felt was missing.所以现在这是我觉得缺失的一点。 First, from the Container requirements:一、从Container要求:

expression表达 return type返回类型 semantics语义 conditions状况 complexity复杂
a.end() (const_)iterator (const_) 迭代器 iterator to one past the last element of a迭代器到a的最后一个元素之后 Constant持续的

This says "one past the last element".这表示“超过最后一个元素”。 However, does this mean end() is decrementable?但是,这是否意味着end()是可递减的? We need to be sure.我们需要确定。 The items below are significant here and I've numbered them for reference:下面的项目在这里很重要,我已经给它们编号以供参考:

  1. Container : The " end() returns one past the end of a " requirement mentioned above.容器:在“ end()返回一个过去端”上面提到的要求。
  2. RandomAccessIterator : i - n , defined in terms of -= , no constraints given. RandomAccessIterator : i - n ,根据-=定义,没有给出约束。
  3. RandomAccessIterator : r -= n , defined in terms of += , no constraints given. RandomAccessIterator : r -= n ,根据+=定义,没有给出约束。
  4. RandomAccessIterator : r += n , defined in terms of --r for n < 0 , no constraints given. RandomAccessIterator : r += n ,根据--r定义n < 0 ,没有给出约束。
  5. BidirectionalIterator : --a 双向--a :-- --a
    • Precondition: a is decrementable → there exists b such that a == ++b .前提条件: a是可递减的 → 存在b使得a == ++b
    • Postcondition: a is dereferenceable.后置条件: a是可解引用的。
    • Postcondition: --(++a) == a后置条件: --(++a) == a
    • Postcondition: if --a == --b then a == b后置条件:如果--a == --b那么a == b
    • Postcondition: a and --a are the same iterator instance.后置条件: a--a是同一个迭代器实例。
  6. BidirectionalIterator : Notes: "A bidirectional iterator does not have to be dereferenceable to be decrementable (in particular, the end iterator is not dereferenceable but is decrementable)". BidirectionalIterator :注释: “双向迭代器不必可解引用才能自减(特别是,结束迭代器不可解引用,但可自减)”。
  7. Container : States that size() is semantically equivalent to std::distance(begin(), end()) Container :声明size()在语义上等同于std::distance(begin(), end())
  8. distance : Returns the number of increments to get from first to last . distance :返回从firstlast的增量数

Breaking this down:打破这个:

The precondition for (5) states that for --a to work, a must be decrementable , and goes on to define that an iterator a is decrementable if there exists a b such that ++ b == a . (5) 的先决条件指出,要使--a起作用, a必须是可递减的,并且继续定义迭代器a是可递减的,如果存在b使得++ b == a

(1)'s "one past the end" language seems to imply that if b is an iterator to the last element in the container, then ++ b == end() . (1) 的“one over the end”语言似乎暗示如果b是容器中最后一个元素的迭代器,则++ b == end() More convincingly, though, (7) shows that std::distance(begin(), end()) must work, and (8) therefore implies that the iterator returned by begin() must be able to be repeatedly incremented until it equals end() , which means that for a non-empty container, at some point there must exist a b such that ++ b == end() .不过,更令人信服的是,(7) 表明std::distance(begin(), end())必须工作,因此 (8) 意味着begin()返回的迭代器必须能够重复递增,直到它等于end() ,这意味着对于非空容器,在某些时候必须存在b使得++ b == end()

Combining these two, then, shows that end() is always decrementable if !empty() , because there is always a b such that ++ b == end() (otherwise distance(begin(), end()) — and therefore size() — would not meet its semantic requirements), which is the definition of decrementability.将这两者结合起来,表明如果!empty()end()总是可递减的,因为总是有b使得++ b == end() (否则distance(begin(), end()) — 和因此size() — 不符合其语义要求),这是可递减性的定义。 Also note that (6) explicitly states that a decrementable iterator need not be dereferenceable , and has a note about the decrementability of the end iterator.还要注意,(6) 明确指出可递减迭代器不需要是可解引用的,并且有关于结束迭代器的递减性的注释。

Furthermore, since end() is decrementable when !empty() , then (where e is initialized to the return value of container.end() ):此外,由于end()!empty()时可递减,那么(其中e被初始化为container.end()的返回值):

  • -- e is valid, from (5). -- e是有效的,来自 (5)。
  • e += n for n <= 0 is valid, from (4). e += n对于n <= 0是有效的,来自 (4)。
  • e -= n for n >= 0 is valid, from (3). e -= n对于n >= 0是有效的,来自 (3)。
  • e - n for n >= 0 is valid, from (2). e - n对于n >= 0是有效的,来自 (2)。
  • Since += , -= , and - (for sign of n indicated above) are all semantically defined in terms of repeatedly applying -- , this constrains n to be within the container's size, since begin() is not decrementable (by definition of decrementability) and eventually the iterator must hit begin() .由于+=-=- (对于上面指出的n 的符号)都是根据重复应用--语义定义的,这将n限制在容器的大小范围内,因为begin()不可递减(根据定义递减性),最终迭代器必须点击begin()

Therefore the - 1 in the OP is valid (from (2)) as long as there is at least 1 element before the iterator it's being applied to.因此,只要在应用它的迭代器之前至少有 1 个元素,OP 中的- 1就是有效的(来自(2))。

Decrementability vs. dereferenceability : Note that there is a difference. Decrementability vs. dereferenceability :请注意,这是有区别的。 (6) points out that the concepts are separate. (6) 指出概念是分开的。 Decrementability implies that --i is valid, dereferenceability implies that *i and i-> are valid.可递减性意味着--i是有效的,可解引用意味着*ii->是有效的。 In the OP's vector example, while end() is decrementable, it is not dereferenceable ( vector::end() explicitly states this).在 OP 的vector示例中,虽然end()是可递减的,但它不可解引用( vector::end()明确说明了这一点)。


Code代码

Oh, yeah, also I wrote a test program just as a sanity check:哦,是的,我还编写了一个测试程序作为完整性检查:

#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
}

Should run without tripping any assertions.应该在不触发任何断言的情况下运行。


I hope that was helpful.我希望那是有帮助的。 Pretty much all of that was me working to convince myself that end() - 1 was indeed valid几乎所有这些都是我努力说服自己end() - 1确实有效

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

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