【问题标题】:Range based for loop for linked list链表的基于范围的循环
【发布时间】:2019-03-07 16:04:23
【问题描述】:

我有一个在嵌套链表上运行的函数。函数如下:

void DoLiana(void) {

    PlotPointer plot;
    TreePointer tree;

        plot = FirstPlot;
        while (plot != nullptr) {
            tree = plot->FirstTree;
            while (tree != nullptr) {
                if (tree->isLiana) {
                    if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                    DoLianaAttachement(plot, tree);
                }
                tree = tree->next;
            }
            plot = plot->next;
        }    
}

因为这种类型的迭代在我的代码中多次发生,所以我正在寻找一种方法来使迭代更加紧凑和富有表现力。我读到在 C++11 中有基于范围的 for 循环迭代一个集合。这种结构是否适用于这种情况?还是有其他可能的方法来执行这些迭代?

【问题讨论】:

  • 基于范围的 for 循环主要是语法糖,需要你有一个开始/结束(更多细节here)。所以你只需要以某种方式提供所需的接口。
  • range-for 循环可以遍历任何提供一对迭代器的东西。所以,让你的班级支持迭代。或者,只需使用 std::liststd::slist 来满足您的链表需求 - 这些类已经提供了必要的脚手架。
  • 应该没问题。您需要为您的容器提供beginend 方法,这将返回定义了operator++()operator== 的对象。我认为一个简单的指针适合该法案。 @IgorTandetnik 我认为 begin/end 返回的类型甚至不需要是正式的迭代器。
  • 但是你可以立即在这里使用香草循环:for (PlotPointer plot = FirstPlot; plot != nullptr; plot=plot.next) { for (TreePointer tree = plot->FirstTree, tree!= nullptr; tree=tree->next) { ... }}
  • @luk32 我不确定 简单指针 是否符合要求:codereview.stackexchange.com/questions/74609/…

标签: c++ c++11 for-loop linked-list iterator


【解决方案1】:

要跟进Serge Ballesta's comment,您可以立即在此处使用香草for 循环,替换while 循环。所以你的示例代码会变成:

void DoLiana(void) {

    for (PlotPointer plot = FirstPlot; plot; plot = plot->next) { 
        for (TreePointer tree = plot->FirstTree; tree; tree = tree->next) {
            if (tree->isLiana && !tree->attachedTree && TestForLianaAttach(plot, tree)) {
                DoLianaAttachement(plot, tree);
            }
        }
    }
}

这会缩短代码,并增加局部性和可读性。如果这是一个优势,还可以保持与 C 的兼容性。

【讨论】:

    【解决方案2】:

    是的,您可以为此定义适当的函数。

    因为 oyu 给出的细节很少。让我们做一些假设。

    struct Tree
    {
        bool  isLiana;
        void* attachedTree;
        Tree* next;
    };
    
    using TreePointer = Tree*;
    
    struct Plot
    {
        TreePointer FirstTree;
        Plot*       next;
    };
    
    using PlotPointer = Plot*;
    
    bool TestForLianaAttach(PlotPointer, TreePointer);
    void DoLianaAttachement(PlotPointer, TreePointer);
    
    PlotPointer FirstPlot;
    

    要使用指针,您需要为指针定义适当的 begin()end() 方法。

    NextIterator<Plot> begin(PlotPointer ptr)  {return make_NextIterator(ptr);}
    NextIterator<Plot> end(PlotPointer)        {return make_NextIterator<Plot>();}
    
    NextIterator<Tree> begin(TreePointer ptr)  {return make_NextIterator(ptr);}
    NextIterator<Tree> end(TreePointer)        {return make_NextIterator<Tree>();}
    

    基于范围的 for 查找可与您的类型一起使用的 begin()end() 函数。现在标准具有默认的std::begin()std::end(),它们在传递的对象上调用begin()end() 方法。但是您可以提供自己的(如上)为您的类型/指针做一个特殊情况。

    现在,由于您的指针使用p = p-&gt;next; 来推进,我们需要一个迭代器对象来完成这部分工作。在上面的代码中,我称之为NextIterator。比较容易定义。

    template<typename T>
    struct NextIterator
    {
        T* p;
    
        NextIterator():       p(nullptr) {}
        NextIterator(T* ptr): p(ptr)     {}
    
        NextIterator& operator++(){p = p->next;return *this;}
    
        T const& operator*() const  {return *p;}
        T&       operator*()        {return *p;}
        T const* operator->() const {return p;}
        T*       operator->()       {return p;}
    
        bool operator==(NextIterator const& rhs) const {return p == rhs.p;}
        bool operator!=(NextIterator const& rhs) const {return p != rhs.p;}
    };
    template<typename T>
    NextIterator<T> make_NextIterator(T* val)   {return NextIterator<T>(val);}
    template<typename T>
    NextIterator<T> make_NextIterator()         {return NextIterator<T>{};}
    

    现在我们可以使用基于范围的 for 重写您的循环。

    void DoLianaRange(void) {
    
            for(auto& plot: FirstPlot) {
                for(auto& tree: plot.FirstTree) {
                    if (tree.isLiana) {
                        if (tree.attachedTree == nullptr && TestForLianaAttach(&plot, &tree))
                        DoLianaAttachement(&plot, &tree);
                    }
                }
            }
    }
    

    原始版本进行比较。

    void DoLiana(void) {
    
        PlotPointer plot;
        TreePointer tree;
    
            plot = FirstPlot;
            while (plot != nullptr) {
                tree = plot->FirstTree;
                while (tree != nullptr) {
                    if (tree->isLiana) {
                        if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                        DoLianaAttachement(plot, tree);
                    }
                    tree = tree->next;
                }
                plot = plot->next;
            }
    }
    

    或者您可以简单地使用标准 for 循环!

    void DoLianaForLoop(void) {
    
            for (PlotPointer plot = FirstPlot; plot != nullptr; plot = plot->next) {
                for (TreePointer tree= plot->FirstTree; tree != nullptr; tree = tree->next) {
                    if (tree->isLiana) {
                        if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                        DoLianaAttachement(plot, tree);
                    }
                }
            }
    }
    

    代码都在一个地方(按照正确的编译顺序)。

    struct Tree
    {
        bool  isLiana;
        void* attachedTree;
        Tree* next;
    };
    
    using TreePointer = Tree*;
    
    struct Plot
    {
        TreePointer FirstTree;
        Plot*       next;
    };
    
    using PlotPointer = Plot*;
    
    template<typename T>
    struct NextIterator
    {
        T* p;
    
        NextIterator():       p(nullptr) {}
        NextIterator(T* ptr): p(ptr)     {}
    
        NextIterator& operator++(){p = p->next;return *this;}
    
        T const& operator*() const  {return *p;}
        T&       operator*()        {return *p;}
        T const* operator->() const {return p;}
        T*       operator->()       {return p;}
    
        bool operator==(NextIterator const& rhs) const {return p == rhs.p;}
        bool operator!=(NextIterator const& rhs) const {return p != rhs.p;}
    };
    
    template<typename T>
    NextIterator<T> make_NextIterator(T* val)   {return NextIterator<T>(val);}
    template<typename T>
    NextIterator<T> make_NextIterator()         {return NextIterator<T>{};}
    
    NextIterator<Plot> begin(PlotPointer ptr)  {return make_NextIterator(ptr);}
    NextIterator<Plot> end(PlotPointer)        {return make_NextIterator<Plot>();}
    
    NextIterator<Tree> begin(TreePointer ptr)  {return make_NextIterator(ptr);}
    NextIterator<Tree> end(TreePointer)        {return make_NextIterator<Tree>();}
    
    bool TestForLianaAttach(PlotPointer, TreePointer);
    void DoLianaAttachement(PlotPointer, TreePointer);
    
    PlotPointer FirstPlot;
    
    void DoLianaRange(void) {
    
            for(auto& plot: FirstPlot) {
                for(auto& tree: plot.FirstTree) {
                    if (tree.isLiana) {
                        if (tree.attachedTree == nullptr && TestForLianaAttach(&plot, &tree))
                        DoLianaAttachement(&plot, &tree);
                    }
                }
            }
    }
    void DoLiana(void) {
    
        PlotPointer plot;
        TreePointer tree;
    
            plot = FirstPlot;
            while (plot != nullptr) {
                tree = plot->FirstTree;
                while (tree != nullptr) {
                    if (tree->isLiana) {
                        if (tree->attachedTree == nullptr && TestForLianaAttach(plot, tree))
                        DoLianaAttachement(plot, tree);
                    }
                    tree = tree->next;
                }
                plot = plot->next;
            }
    }
    

    【讨论】:

    • NextIterator 应该是DefaultConstructible,这就像将`= nullptr` 放入当前构造函数的参数列表中一样简单。结束迭代器可以是{}
    • @Caleth:如果你想让NextIterator匹配迭代器Concept。基于范围的 for 不要求类型是迭代器。
    • 它也摆脱了丑陋的make_NextIterator(static_cast&lt;Tree*&gt;(nullptr))
    • 实际上,make_NextIterator 似乎麻烦多于其价值。 NextIterator&lt;Plot&gt; begin(PlotPointer ptr) {return ptr;} NextIterator&lt;Plot&gt; end(PlotPointer ) {return nullptr; }做得更好
    • @Caleth 但它符合一个很好的模式(从标准开始)。所以是的,最初编写代码有点麻烦,但它提供了心理线索并添加到预期的文档中。
    猜你喜欢
    • 2016-10-31
    • 1970-01-01
    • 1970-01-01
    • 2014-12-06
    • 2014-01-11
    • 2022-01-14
    • 2014-01-12
    • 2013-01-04
    • 1970-01-01
    相关资源
    最近更新 更多