【问题标题】:Returning the last element of a container that has no back() method in C++?返回 C++ 中没有 back() 方法的容器的最后一个元素?
【发布时间】:2019-09-06 19:50:48
【问题描述】:

在不提供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());未定义的行为
  • 您不能在 any 容器中取消引用 end 迭代器

标签: c++ algorithm c++11 iterator containers


【解决方案1】:

这个

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() 是一个 rvalue 表达式,并且 没有迭代器要求指定递减右值保证工作。特别是,当迭代器被实现为指针时,--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';
}

【讨论】:

  • 如果end()返回一个指针,为什么--c.end()编译失败?递减指针是明确定义的行为。为什么指针是右值会改变这一点?
  • @RemyLebeau 递减指针yes,定义明确,但他们指的是expression
【解决方案2】:

是获取最后一个元素来递减迭代器的唯一方法 在取消引用之前?

不,还有其他选择。

最简单的方法是使用反向迭代器(std::set::rbeginstd::set::crbegin),它直接给你一个过去的元素 std::sets end 迭代器。

来自cppreference.comstd::set::rbegin and std::set::crbegin

返回一个反向迭代器到reversed的第一个元素 容器。它对应于非反转的最后一个元素 容器。如果容器为空,则返回的迭代器相等 撕裂()。

std::set<int> set = { 1,2,3,4,5 };

auto iter = set.rbegin();
const int beforeEndIter = *iter;
std::cout << "--set.end() -> " << beforeEndIter  << '\n';

(update) 如果是容器中倒数第二个元素(即 end 迭代器之后的两个元素),请使用 std::next,原因与本文中提到的相同std::prev 的其他答案。 See a demo

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

第二个选项已在另一个答案中提到。


另一方面,取消对结束迭代器的引用是一个undefined behavior,您可以在其中得到任何结果。在您的情况下,您已经获得了容器的最后一个元素(即一个过去的迭代器)。

【讨论】:

  • 问题用 C++11 标记,所以 auto 也可以
  • 谢谢,反向迭代器更有意义。非常感谢
  • “递减[ing] [end] 迭代器 取消引用之前”没有任何问题。 *--set.end(),如果它编译,是对最后一个元素的引用。正如在一个答案中所指出的,最好写成*std::prev(set.end())。也可以是auto iter = set.end(); std::cout &lt;&lt; *--iter &lt;&lt; '\n';
猜你喜欢
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-30
  • 1970-01-01
  • 2014-09-09
相关资源
最近更新 更多