返回不提供back()
成员函数(如std::set
)的容器中最后一个元素的最佳方法是什么?
由于end()
方法在容器结束后将迭代器返回到第一个元素,因此在取消引用迭代器之前抓取最后一个元素以递减迭代器的唯一方法是什么?
如:
std::set<int> set = {1,2,3,4,5};
int end = *(set.end());
int beforeEnd = *(--set.end());
std::cout << "set.end() -> " << end << std::endl;
std::cout << "--set.end() -> " << beforeEnd << std::endl;
但是,这些都返回:
set.end() -> 5
--set.end() -> 5
这是获取最后一个元素的正确方法吗,为什么它们返回相同的值?
int end = *(set.end());
正如πάντα ῥεῖ所评论的那样,具有未定义的行为。那是因为std::set::end
将迭代器返回到容器最后一个元素之后的元素。此元素充当占位符;尝试访问它会导致未定义的行为。 (https://en.cppreference.com/w/cpp/container/set/end,强调我的)
另一行:
int beforeEnd = *(--set.end());
它不能保证工作。例如,请参阅 https://en.cppreference.com/w/cpp/iterator/prev,强调我的:
尽管表达式
--c.end()
经常编译,但不能保证这样做:c.end()
是一个右值表达式,并且没有迭代器要求指定保证右值的递减有效。特别是,当迭代器作为指针实现时,--c.end()
不会编译,而std::prev(c.end())
会编译。
因此,由于无法编译的原因相同,它可能会失败:
int arr[4] = {1,2,3,4};
int *p = --(arr + 4); // --> error: expression is not assignable
您可以改为编写类似以下内容的内容。
std::set<int> set = {1,2,3,4,5};
if ( set.begin() != set.end() )
{
auto itLast = std::prev(set.end());
std::cout << "last -> " << *itLast << 'n';
}
是抓取最后一个元素以递减迭代器的唯一方法 在取消引用之前?
不,还有其他选择。
最简单的方法是使用反向迭代器(std::set::rbegin
或std::set::crbegin
),它为您提供过去std::set
结束迭代器的元素。
从 cppreference.comstd::set::rbegin and std::set::crbegin
将反向迭代器返回到反向容器的第一个元素。它对应于非反向容器的最后一个元素。如果容器为空,则返回的迭代器相等 到 rend()。
std::set<int> set = { 1,2,3,4,5 };
auto iter = set.rbegin();
const int beforeEndIter = *iter;
std::cout << "--set.end() -> " << beforeEndIter << 'n';
(更新)如果容器中的倒数第二个元素(即两个经过结束迭代器),请使用 std::next,原因与std::prev
的另一个答案中提到的相同。观看演示
std::set<int> set = {1, 2};
const bool hasElements = set.cbegin() != set.cend();
auto iter = set.rbegin();
if(hasElements && iter != set.rend()) std::cout << "--set.end() -> " << *iter << 'n';
if(hasElements && std::next(iter) != set.rend()) std::cout << "two past set.end() -> " << *std::next(iter) << 'n';
输出:
--set.end() -> 2
two past set.end() -> 1
第二个选项已在另一个答案中提到。
另一方面,取消引用结束迭代器是一种未定义的行为,您可能会期望任何结果。在您的情况下,您已经获得了容器的最后一个元素(即一个过去的结束迭代器)。