代码之家  ›  专栏  ›  技术社区  ›  WilliamKF

测试迭代器是否指向最后一个项?

  •  30
  • WilliamKF  · 技术社区  · 14 年前

    我有一个stl迭代器,它由std::find()生成,希望测试它是否是最后一个元素。写这个的一种方法如下:

    mine *match = someValue;
    vector<mine *> Mine(someContent);
    vector<mine *>::iterator itr = std::find(Mine.begin(), Mine.end(), match);
    
    if (itr == --Mine.end()) {
      doSomething;
    }
    

    但在我看来,递减end()迭代器会带来麻烦,例如,如果向量没有元素,那么它将是未定义的。即使我知道它永远不会是空的,它仍然看起来很难看。我认为rbegin()可能是一种可行的方法,但不确定将前向迭代器与反向迭代器进行比较的最佳方法。

    9 回复  |  直到 14 年前
        1
  •  54
  •   GManNickG    14 年前

    这样做:

    // defined in boost/utility.hpp, by the way
    template <typename Iter>
    Iter next(Iter iter)
    {
        return ++iter;
    }
    
    // first check we aren't going to kill ourselves
    // then check if the iterator after itr is the end
    if ((itr != Mine.end()) && (next(itr) == Mine.end()))
    {
        // points at the last element
    }
    

    仅此而已。永远不要给你未定义的行为,在所有迭代器上工作,好的一天。

    为了好玩,把它包起来:

    template <typename Iter, typename Cont>
    bool is_last(Iter iter, const Cont& cont)
    {
        return (iter != cont.end()) && (next(iter) == cont.end())
    }
    

    给:

    if (is_last(itr, Mine))
    

    如果您对实用功能/好看的代码过敏,请执行以下操作:

    if ((itr != Mine.end()) && (itr + 1 == Mine.end()))
    

    但不能在非随机访问迭代器上进行。这一个用于双向迭代器:

    if ((itr != Mine.end()) && (itr == --Mine.end()))
    

    从那以后就安全了 end() > itr 第一次检查。

        2
  •  11
  •   Potatoswatter R. Martinho Fernandes    14 年前

    是的,递减(或递增)是不安全的。 end 如果矢量可能为空。用指针做同样的操作甚至有些不安全,尽管你可能会逃脱它。

    要真正安全,请使用已知安全有效的减法和值:

    if ( Mine.end() - itr == 1 )
    

    与所有前向迭代器兼容(例如 slist ,而不是随机访问迭代器 vector deque )、使用

    if ( std::distance( itr, Mine.end() ) == 1 )
    

    或者,如果你关心性能,但有双向迭代器(包括任何C++ 03容器)

    if ( itr != Mine.end() && itr == -- Mine.end() )
    

    或者只对前向迭代器和O(1)次进行分析,

    if ( itr != Mine.end() && ++ container::iterator( itr ) == Mine.end() )
    

    或者,如果您非常聪明,避免命名迭代器类,

    if ( itr != Mine.end() && ++ ( Mine.begin() = itr ) == Mine.end() )
    
        3
  •  5
  •   Mark B    14 年前

    为什么只有当项目是最后一个项目时才需要执行特殊行为?

    这个怎么样?该计划只是比较迭代器项的地址与容器中最后一个项的地址,并检查以确保该项实际上还没有结束(使 back 呼叫安全):

    if (itr != Mine.end() && &*itr == &Mine.back()) {
      doSomething;
    }
    
        4
  •  3
  •   TheUndeadFish    14 年前

    如果你这样做:

    if(itr != Mine.end() && itr == --Mine.end())
    

    应该没事。因为如果itr不在末尾,那么容器中必须至少有一个元素,因此当递减时,end必须产生一个值结果。

    但是如果你仍然不喜欢,有很多方法可以做同样的事情,就像所有其他答案所显示的那样。

    另一种选择是:

    if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)
    
        5
  •  3
  •   Community Egal    7 年前

    你首先需要一种方法 determine if an iterator is a reverse one ,那是 ingeniously shown here :

    #include <iterator>
    #include <type_traits>
    
    template<typename Iter>
    struct is_reverse_iterator : std::false_type { };
    
    template<typename Iter>
    struct is_reverse_iterator<std::reverse_iterator<Iter>>
    : std::integral_constant<bool, !is_reverse_iterator<Iter>::value>
    { };
    

    然后您可以有两种口味来执行测试

    template<bool isRev> // for normal iterators
    struct is_last_it
    {
        template<typename It, typename Cont>
        static bool apply(It it, Cont const &cont)
        { // you need to test with .end()
            return it != cont.end() && ++it == cont.end();
        }
    };
    
    template<> // for reverse iterators
    struct is_last_it<true>
    {
        template<typename It, typename Cont>
        static bool apply(It it, Cont const &cont)
        { // you need to test with .rend()
            return it != cont.rend() && ++it == cont.rend();
        }
    };
    

    以及单个接口函数

    template<typename It, typename Cont>
    bool is_last_iterator(It it, Cont const &cont)
    {
        return is_last_it<is_reverse_iterator<It>::value>::apply(it, cont);
    };
    

    然后对于每种类型的迭代器(反向/直线),都可以使用接口函数

    int main()
    {
        std::vector<int> v;
        v.push_back(1);
    
        auto it (v.begin()),  ite(v.end());   // normal iterators
        auto rit(v.rbegin()), rite(v.rend()); // reverse iterators
    
        std::cout << is_last_iterator(it, v) << std::endl;
        std::cout << is_last_iterator(ite, v) << std::endl;
        std::cout << is_last_iterator(rit, v) << std::endl;
        std::cout << is_last_iterator(rite, v) << std::endl;
    
        return 0;
    }
    

    注意一些实现(除了 std::begin() std::end() 这是很常见的,也包括 std::rbegin() std::rend() . 如果可能,使用此函数集而不是成员 .begin() 等。

        6
  •  2
  •   John Dibling    14 年前

    这是另一个潜在的解决方案:

    template<class Iterator, class Container> bool is_last(Iterator it, const Container& cont)
    {
        // REQUIREMENTS:
        // the iterator must be a valid iterator for `cont`
        if( it == cont.end() )
            return false;   // or throw if you prefer
        return (++it) == cont.end();
    }
    
        7
  •  1
  •   rmeador    14 年前

    这基本上与从单个链接列表中删除节点相同。您必须有两个迭代器,一个在一个节点之后,另一个在另一个节点之后,所以当“前进”迭代器到达要删除的节点(或任何操作;在您的情况下,所需的节点将是结尾)时,“跟随”迭代器指向之前的节点(在您的情况下,这将是最后一个节点)。

        8
  •  1
  •   Steven Sudit    14 年前

    一个更好的方法是复制迭代器,然后递增它。然后,您可以使用 end() . 如果您很小心,可以使用post增量来避免正式复制它。

      if (++vector<mine*>::iterator(itr) == Mine.end())
    

    如果ITR可能已经结束:

      if (itr == Mine.end() || ++vector<mine*>::iterator(itr) == Mine.end())
    

    或者,根据GMAN的答案,但更安全一点:

      if (Mine.Length() == 0 || itr == Mine.End() || &*itr == &Mine.back())
    

    我又修了最后一个,因为我错了 &* .

        9
  •  1
  •   Adrian Maire    7 年前

    尽量使这个答案简单和通用:

    if( itr!=Mine.end() && itr== --Mine.end())
    

    如果迭代器不是双向的,

    if( itr!=Min.end() && ++decltype(itr)(itr)==Mine.end())
    

    第二个创建一个ITR的临时副本,并对其进行增量,以针对结束迭代器进行测试。

    在这两种情况下,第一个测试都避免使用空容器来触发未定义的情况。