我意识到这个问题有点老了,但我被指示到这里是关于 end() - 1 的,我发现现有的答案和 cmets 信息丰富且合理,但由于缺乏引用而无法令人信服,而且我不确定是否它们特定于vector。所以我挖掘了尽可能多的具体信息来说服自己这里的答案是正确的。
这篇文章代表我为确认答案所做的研究,基本上是我的笔记,但我试图使其尽可能连贯,我认为它可能有用。如果此处有任何问题,请非常感谢进行更正。
重申答案
这里的TL;DR是肯定的,这里的答案是正确的,不仅适用于vector,在更一般的情况下也是如此:
如果容器的迭代器类型满足BidirectionalIterator(并因此提供递减操作),那么对于任何容器类型,以下将始终有效,其中e被初始化为返回值container.end():
- 如果
!container.empty() 则--e 有效。
- 如果
!container.empty() 则++(--e) == container.end() 为真。
如果迭代器也满足RandomAccessIterator,那么这些更一般的陈述是有效的:
-
e - n 和 e -= n 用于 [ 0, container.size() ] 中的任意整数 n
-
e + n 和 e += n 用于 [ - container.size() , 0 ] 中的任何整数 n
因此,OP 中的 vector 示例不仅很好,正如其他答案所指出的那样,而且它定义明确,保证适用于任何容器类型。
推理
所以现在这是我觉得缺少的一点。一、从Container的要求:
| expression |
return type |
semantics |
conditions |
complexity |
a.end() |
(const_)iterator |
iterator to one past the last element of a
|
|
Constant |
这表示“过去最后一个元素”。然而,这是否意味着end() 可以递减?我们需要确定。以下项目在这里很重要,我已将它们编号以供参考:
-
Container:“
end() 在 a 结束后返回一个”要求。
-
RandomAccessIterator:
i - n,根据-=定义,没有给出限制。
-
RandomAccessIterator:
r -= n,根据+=定义,没有给出限制。
-
RandomAccessIterator:
r += n,根据--r 定义n < 0,没有给出限制。
-
BidirectionalIterator:
--a
- 前提条件:a 是可递减的 → 存在 b 使得
a == ++b。
- 后置条件:a 是可取消引用的。
- 后置条件:
--(++a) == a
- 后置条件:如果
--a == --b 那么a == b
- 后置条件:
a 和 --a 是同一个迭代器实例。
-
BidirectionalIterator:注意:“双向迭代器不一定非要可取消引用才能递减(特别是,结束迭代器不可取消引用但可递减)”。
-
Container:声明
size() 在语义上等同于std::distance(begin(), end())
-
distance:返回从first 到last 的增量数。
分解:
(5) 的前提条件是--a 要工作,a 必须是可递减的,并继续定义迭代器 a 如果存在 b 使得 ++ b == a 是可递减的。
(1) 的“最后一个”语言似乎暗示如果 b 是容器中最后一个元素的迭代器,那么 ++ b == end() .然而,更令人信服的是,(7) 表明std::distance(begin(), end()) 必须工作,因此(8) 意味着begin() 返回的迭代器必须能够重复递增直到等于end(),这意味着对于非-空容器,在某些时候必须存在一个 b 使得++ b == end()。
然后,将这两者结合起来表明,如果!empty(),end() 总是可递减的,因为总是有一个 b 使得++ b == end()(否则distance(begin(), end()) - 因此@987654381 @——不符合其语义要求),这是可递减性的定义。另请注意,(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))。
可递减性与可取消引用性:请注意,这是有区别的。 (6) 指出概念是分开的。可递减性意味着--i 是有效的,可取消引用性意味着*i 和i-> 是有效的。在 OP 的 vector 示例中,虽然 end() 可递减,但不可取消引用(vector::end() 明确说明了这一点)。
代码
哦,是的,我还写了a test program 只是为了检查一下:
#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 确实有效