วิธีการใช้ตัววนซ้ำที่กำหนดเองและ const_iterators อย่างถูกต้องได้อย่างไร


240

ฉันมีคลาสคอนเทนเนอร์แบบกำหนดเองซึ่งฉันต้องการเขียนiteratorและconst_iteratorคลาส

ฉันไม่เคยทำสิ่งนี้มาก่อนและไม่พบวิธีการที่เหมาะสม แนวทางในการสร้างตัววนซ้ำคืออะไรและฉันควรระวังอะไรบ้าง

ฉันต้องการหลีกเลี่ยงการทำซ้ำรหัส (ฉันรู้สึกว่าconst_iteratorและiteratorแบ่งปันหลายสิ่งหนึ่งควร subclass อื่น ๆ )

ข้อควรทราบ: ฉันค่อนข้างมั่นใจว่า Boost มีบางอย่างในการทำให้ง่ายขึ้น แต่ฉันไม่สามารถใช้ที่นี่ได้ด้วยเหตุผลโง่ ๆ หลายประการ



รูปแบบของ Iterator ของ GoF ได้รับการพิจารณาหรือไม่?
DumbCoder

3
@DumbCoder: ใน C ++ มักเป็นที่ต้องการให้มีตัววนซ้ำที่เข้ากันได้กับ STL เพราะมันจะทำงานได้ดีกับคอนเทนเนอร์และอัลกอริธึมที่มีอยู่ทั้งหมดที่จัดเตรียมโดย STL แม้ว่าแนวคิดจะคล้ายกัน แต่มีความแตกต่างบางอย่างกับรูปแบบที่เสนอโดย GoF
Björn Pollex

ฉันได้โพสต์ตัวอย่างของตัววนซ้ำแบบกำหนดเองที่นี่
Valdemar_Rudolfovich

1
ความซับซ้อนของคำตอบเหล่านี้ชี้ให้เห็นว่าภาษาซีพลัสพลัสนั้นเป็นภาษาที่ไม่คู่ควรกับสิ่งอื่นใดนอกจากการมอบหมายการบ้านให้กับอันเดอร์กราวด์ที่เพิ่มขึ้นหรือคำตอบนั้นซับซ้อนเกินไปและผิด ต้องมีวิธีที่ง่ายกว่าใน Cpp หรือไม่ เช่นเดียวกับ CMake และ Automake ก่อนที่มันจะสัมพันธ์กันดิบ C ต้มจากต้นแบบไพ ธ อนดูเหมือนจะง่ายกว่านี้มาก
คริสโตเฟอร์

คำตอบ:


157
  • เลือกประเภทของตัววนซ้ำที่เหมาะสมกับคอนเทนเนอร์ของคุณ: อินพุตเอาต์พุตไปข้างหน้า ฯลฯ
  • ใช้คลาสตัววนซ้ำพื้นฐานจากไลบรารีมาตรฐาน ตัวอย่างเช่นstd::iteratorด้วยrandom_access_iterator_tagคลาสฐานเหล่านี้กำหนดนิยามประเภททั้งหมดที่ STL ต้องการและทำงานอื่น
  • เพื่อหลีกเลี่ยงการทำซ้ำรหัสคลาสตัววนซ้ำควรเป็นคลาสเทมเพลตและเป็นพารามิเตอร์แบบ "ประเภทค่า", "ประเภทตัวชี้", "ประเภทอ้างอิง" หรือทั้งหมด (ขึ้นอยู่กับการใช้งาน) ตัวอย่างเช่น:

    // iterator class is parametrized by pointer type
    template <typename PointerType> class MyIterator {
        // iterator class definition goes here
    };
    
    typedef MyIterator<int*> iterator_type;
    typedef MyIterator<const int*> const_iterator_type;

    ประกาศiterator_typeและconst_iterator_typeคำจำกัดความประเภท: เป็นประเภทสำหรับตัววนซ้ำไม่ใช่ const และ const

ดูเพิ่มเติม: การอ้างอิงไลบรารีมาตรฐาน

แก้ไข: std::iteratorเลิกใช้แล้วตั้งแต่ C ++ 17 ดูการอภิปรายเกี่ยวกับที่นี่


8
@Potatoswatter: ยังไม่ได้ลงคะแนนในเรื่องนี้ แต่เฮ้random_access_iteratorไม่ได้อยู่ในมาตรฐานและคำตอบไม่ได้จัดการกับการแปลงที่ไม่แน่นอนเพื่อ const คุณอาจต้องการที่จะรับมรดกจากเช่นstd::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>แม้ว่า
Yakov Galka

2
ใช่ฉันไม่แน่ใจว่ามันทำงานอย่างไร ถ้าผมมีวิธีการที่RefType operator*() { ... }ผมเป็นหนึ่งในขั้นตอนที่ใกล้ - RefType operator*() const { ... }แต่มันไม่ได้ช่วยเพราะผมยังคงต้อง
Autumnsault



5
หากเลิกใช้แล้ววิธีการ "ทำใหม่" ที่เหมาะสมแทนคืออะไร?
SasQ

56

ฉันจะแสดงให้คุณเห็นว่าคุณสามารถกำหนดตัววนซ้ำสำหรับคอนเทนเนอร์แบบกำหนดเองของคุณได้อย่างง่ายดาย แต่ในกรณีที่ฉันสร้างไลบรารี่ c ++ 11 ที่อนุญาตให้คุณสร้างตัววนซ้ำแบบกำหนดเองด้วยพฤติกรรมแบบกำหนดเองสำหรับคอนเทนเนอร์ประเภทใด ๆ ไม่ต่อเนื่องกัน

คุณสามารถค้นหาได้ใน Github

นี่คือขั้นตอนง่าย ๆ ในการสร้างและใช้ตัววนซ้ำแบบกำหนดเอง:

  1. สร้างคลาส "custom iterator" ของคุณ
  2. กำหนด typedefs ในคลาส "container ที่กำหนดเอง" ของคุณ
    • เช่น typedef blRawIterator< Type > iterator;
    • เช่น typedef blRawIterator< const Type > const_iterator;
  3. กำหนดฟังก์ชั่น "เริ่มต้น" และ "สิ้นสุด"
    • เช่น iterator begin(){return iterator(&m_data[0]);};
    • เช่น const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. เราเสร็จแล้ว !!!

สุดท้ายกำหนดคลาสตัววนซ้ำแบบกำหนดเองของเรา:

หมายเหตุ: เมื่อกำหนดตัววนซ้ำที่กำหนดเองเราได้รับจากหมวดตัววนซ้ำมาตรฐานเพื่อให้อัลกอริทึม STL รู้ประเภทของตัววนซ้ำที่เราทำ

ในตัวอย่างนี้ฉันกำหนด iterator เข้าถึงแบบสุ่มและ iterator เข้าถึงแบบย้อนกลับแบบสุ่ม:

  1. //-------------------------------------------------------------------
    // Raw iterator with random access
    //-------------------------------------------------------------------
    template<typename blDataType>
    class blRawIterator
    {
    public:
    
        using iterator_category = std::random_access_iterator_tag;
        using value_type = blDataType;
        using difference_type = std::ptrdiff_t;
        using pointer = blDataType*;
        using reference = blDataType&;
    
    public:
    
        blRawIterator(blDataType* ptr = nullptr){m_ptr = ptr;}
        blRawIterator(const blRawIterator<blDataType>& rawIterator) = default;
        ~blRawIterator(){}
    
        blRawIterator<blDataType>&                  operator=(const blRawIterator<blDataType>& rawIterator) = default;
        blRawIterator<blDataType>&                  operator=(blDataType* ptr){m_ptr = ptr;return (*this);}
    
        operator                                    bool()const
        {
            if(m_ptr)
                return true;
            else
                return false;
        }
    
        bool                                        operator==(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr == rawIterator.getConstPtr());}
        bool                                        operator!=(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr != rawIterator.getConstPtr());}
    
        blRawIterator<blDataType>&                  operator+=(const difference_type& movement){m_ptr += movement;return (*this);}
        blRawIterator<blDataType>&                  operator-=(const difference_type& movement){m_ptr -= movement;return (*this);}
        blRawIterator<blDataType>&                  operator++(){++m_ptr;return (*this);}
        blRawIterator<blDataType>&                  operator--(){--m_ptr;return (*this);}
        blRawIterator<blDataType>                   operator++(int){auto temp(*this);++m_ptr;return temp;}
        blRawIterator<blDataType>                   operator--(int){auto temp(*this);--m_ptr;return temp;}
        blRawIterator<blDataType>                   operator+(const difference_type& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
        blRawIterator<blDataType>                   operator-(const difference_type& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
    
        difference_type                             operator-(const blRawIterator<blDataType>& rawIterator){return std::distance(rawIterator.getPtr(),this->getPtr());}
    
        blDataType&                                 operator*(){return *m_ptr;}
        const blDataType&                           operator*()const{return *m_ptr;}
        blDataType*                                 operator->(){return m_ptr;}
    
        blDataType*                                 getPtr()const{return m_ptr;}
        const blDataType*                           getConstPtr()const{return m_ptr;}
    
    protected:
    
        blDataType*                                 m_ptr;
    };
    //-------------------------------------------------------------------
  2. //-------------------------------------------------------------------
    // Raw reverse iterator with random access
    //-------------------------------------------------------------------
    template<typename blDataType>
    class blRawReverseIterator : public blRawIterator<blDataType>
    {
    public:
    
        blRawReverseIterator(blDataType* ptr = nullptr):blRawIterator<blDataType>(ptr){}
        blRawReverseIterator(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();}
        blRawReverseIterator(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
        ~blRawReverseIterator(){}
    
        blRawReverseIterator<blDataType>&           operator=(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
        blRawReverseIterator<blDataType>&           operator=(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();return (*this);}
        blRawReverseIterator<blDataType>&           operator=(blDataType* ptr){this->setPtr(ptr);return (*this);}
    
        blRawReverseIterator<blDataType>&           operator+=(const difference_type& movement){this->m_ptr -= movement;return (*this);}
        blRawReverseIterator<blDataType>&           operator-=(const difference_type& movement){this->m_ptr += movement;return (*this);}
        blRawReverseIterator<blDataType>&           operator++(){--this->m_ptr;return (*this);}
        blRawReverseIterator<blDataType>&           operator--(){++this->m_ptr;return (*this);}
        blRawReverseIterator<blDataType>            operator++(int){auto temp(*this);--this->m_ptr;return temp;}
        blRawReverseIterator<blDataType>            operator--(int){auto temp(*this);++this->m_ptr;return temp;}
        blRawReverseIterator<blDataType>            operator+(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr-=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
        blRawReverseIterator<blDataType>            operator-(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr+=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
    
        difference_type                             operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}
    
        blRawIterator<blDataType>                   base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
    };
    //-------------------------------------------------------------------

ตอนนี้อยู่ในคลาสคอนเทนเนอร์ที่กำหนดเองของคุณ:

template<typename blDataType>
class blCustomContainer
{
public: // The typedefs

    typedef blRawIterator<blDataType>              iterator;
    typedef blRawIterator<const blDataType>        const_iterator;

    typedef blRawReverseIterator<blDataType>       reverse_iterator;
    typedef blRawReverseIterator<const blDataType> const_reverse_iterator;

                            .
                            .
                            .

public:  // The begin/end functions

    iterator                                       begin(){return iterator(&m_data[0]);}
    iterator                                       end(){return iterator(&m_data[m_size]);}

    const_iterator                                 cbegin(){return const_iterator(&m_data[0]);}
    const_iterator                                 cend(){return const_iterator(&m_data[m_size]);}

    reverse_iterator                               rbegin(){return reverse_iterator(&m_data[m_size - 1]);}
    reverse_iterator                               rend(){return reverse_iterator(&m_data[-1]);}

    const_reverse_iterator                         crbegin(){return const_reverse_iterator(&m_data[m_size - 1]);}
    const_reverse_iterator                         crend(){return const_reverse_iterator(&m_data[-1]);}

                            .
                            .
                            .
    // This is the pointer to the
    // beginning of the data
    // This allows the container
    // to either "view" data owned
    // by other containers or to
    // own its own data
    // You would implement a "create"
    // method for owning the data
    // and a "wrap" method for viewing
    // data owned by other containers

    blDataType*                                    m_data;
};

ฉันคิดว่าตัวดำเนินการ + และตัวดำเนินการ - อาจมีการดำเนินการย้อนกลับ ดูเหมือนว่าตัวดำเนินการ + กำลังลบการเคลื่อนไหวจากตัวชี้ที่ไม่ได้เพิ่มและตัวดำเนินการ - กำลังเพิ่ม ดูเหมือนว่าจะย้อนกลับไปแล้ว
ชายหาด

สำหรับตัววนกลับแบบย้อนกลับตัวดำเนินการ + ควรย้อนกลับและตัวดำเนินการ - ควรไปข้างหน้า
Enzo

2
น่ากลัว คำตอบที่ยอมรับนั้นสูงเกินไป นี่มันเจ๋งมาก. ขอบคุณ Enzo
FernandoZ

คุณต้องแก้ไขคำตอบของคุณ สมมติว่า m_data ได้รับการจัดสรรด้วยองค์ประกอบ m_size ที่คุณได้รับพฤติกรรมที่ไม่ได้กำหนด: m_data[m_size]คือ UB m_data+m_sizeคุณก็สามารถแก้ไขได้โดยการแทนที่ด้วย สำหรับตัววนซ้ำแบบย้อนกลับทั้งคู่m_data[-1]และm_data-1ไม่ถูกต้อง (UB) ในการแก้ไข reverse_iterators คุณจะต้องใช้ "เคล็ดลับเคล็ดลับองค์ประกอบถัดไป"
Arnaud

ก่อนหน้านี้ฉันเพิ่งเพิ่มสมาชิกตัวชี้ไปยังคลาสคอนเทนเนอร์แบบกำหนดเองที่แสดงสิ่งที่ฉันหมายถึงได้ดียิ่งขึ้น
Enzo

24

พวกเขามักจะลืมไปว่าiteratorต้องเปลี่ยนเป็นconst_iteratorแต่ไม่ใช่วิธีอื่น ๆ นี่คือวิธีในการทำเช่นนั้น:

template<class T, class Tag = void>
class IntrusiveSlistIterator
   : public std::iterator<std::forward_iterator_tag, T>
{
    typedef SlistNode<Tag> Node;
    Node* node_;

public:
    IntrusiveSlistIterator(Node* node);

    T& operator*() const;
    T* operator->() const;

    IntrusiveSlistIterator& operator++();
    IntrusiveSlistIterator operator++(int);

    friend bool operator==(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
    friend bool operator!=(IntrusiveSlistIterator a, IntrusiveSlistIterator b);

    // one way conversion: iterator -> const_iterator
    operator IntrusiveSlistIterator<T const, Tag>() const;
};

ในหนังสือดังกล่าวข้างต้นวิธีการแปรรูปIntrusiveSlistIterator<T> IntrusiveSlistIterator<T const>ถ้าTมีอยู่แล้วconstแปลงนี้ไม่เคยได้รับใช้


ที่จริงแล้วคุณยังสามารถทำมันได้วิธีอื่น ๆ ด้วยการกำหนดตัวสร้างสำเนาที่เป็นแม่แบบก็จะไม่รวบรวมถ้าคุณพยายามที่จะโยนชนิดพื้นฐานจากการที่ไม่ใช่const const
Matthieu M.

คุณจะจบลงด้วยการที่ไม่ถูกต้องIntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() constหรือไม่?
Potatoswatter

อ่ามันใช้ได้ แต่ Comeau ให้คำเตือนและฉันสงสัยว่าคนอื่นจะทำเช่นกัน enable_ifอาจแก้ไขได้ แต่ ...
Potatoswatter

ฉันไม่ได้รำคาญกับ enable_if เพราะคอมไพเลอร์ปิดการใช้งานอยู่แล้วแม้ว่าคอมไพเลอร์บางคนจะให้คำเตือน (g ++ เป็นเด็กดีไม่ได้เตือน)
Maxim Egorushkin

1
@ Matatieu: ถ้ามีใครไปกับแม่แบบคอนสตรัคเมื่อแปลง const_iterator เป็น iterator คอมไพเลอร์ผลิตข้อผิดพลาดภายในตัวสร้างทำให้ผู้ใช้เกาหัวของเขาในความสับสนและ utter wtf ด้วยโอเปอเรเตอร์การแปลงที่ฉันโพสต์คอมไพเลอร์เพิ่งบอกว่าไม่มีการแปลงที่เหมาะสมจาก const_iterator เป็น iterator ซึ่ง IMO นั้นชัดเจนยิ่งขึ้น
Maxim Egorushkin

23

Boost มีสิ่งที่จะช่วย: ห้องสมุด Boost.Iterator

แม่นยำมากขึ้นหน้านี้: เพิ่ม :: iterator_adaptor

สิ่งที่น่าสนใจมากคือตัวอย่างการสอนที่แสดงการใช้งานที่สมบูรณ์ตั้งแต่เริ่มต้นสำหรับประเภทที่กำหนดเอง

template <class Value>
class node_iter
  : public boost::iterator_adaptor<
        node_iter<Value>                // Derived
      , Value*                          // Base
      , boost::use_default              // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
    >
{
 private:
    struct enabler {};  // a private type avoids misuse

 public:
    node_iter()
      : node_iter::iterator_adaptor_(0) {}

    explicit node_iter(Value* p)
      : node_iter::iterator_adaptor_(p) {}

    // iterator convertible to const_iterator, not vice-versa
    template <class OtherValue>
    node_iter(
        node_iter<OtherValue> const& other
      , typename boost::enable_if<
            boost::is_convertible<OtherValue*,Value*>
          , enabler
        >::type = enabler()
    )
      : node_iter::iterator_adaptor_(other.base()) {}

 private:
    friend class boost::iterator_core_access;
    void increment() { this->base_reference() = this->base()->next(); }
};

จุดหลักตามที่ได้รับการอ้างถึงแล้วคือการใช้งานเทมเพลตเดียวและtypedefมัน


คุณสามารถอธิบายความหมายของความคิดเห็นนี้ได้หรือไม่ // a private type avoids misuse
kevinarpe

@kevinarpe: enablerไม่เคยตั้งใจจะเป็นผู้ให้บริการโดยผู้เรียกดังนั้นฉันเดาว่าพวกเขาทำให้มันเป็นส่วนตัวเพื่อหลีกเลี่ยงคนพยายามที่จะผ่านมัน ฉันไม่คิดว่าปิดมือว่ามันอาจจะสร้างปัญหาใด ๆ enable_ifจริงผ่านมันตั้งแต่การป้องกันอยู่ใน
Matthieu M.

16

ฉันไม่รู้ว่า Boost มีสิ่งใดบ้างที่จะช่วยได้

รูปแบบที่ฉันต้องการนั้นง่าย: ใช้อาร์กิวเมนต์เท็มเพลตซึ่งเท่ากับvalue_type, const ที่ผ่านการรับรองหรือไม่ หากจำเป็นก็เป็นประเภทโหนด จากนั้นทุกอย่างตกลงไปในสถานที่

ก็อย่าลืม parameterize (แม่แบบ ize) operator==ทุกอย่างที่จะต้องรวมทั้งตัวสร้างสำเนาและ ส่วนใหญ่ความหมายของconstจะสร้างพฤติกรรมที่ถูกต้อง

template< class ValueType, class NodeType >
struct my_iterator
 : std::iterator< std::bidirectional_iterator_tag, T > {
    ValueType &operator*() { return cur->payload; }

    template< class VT2, class NT2 >
    friend bool operator==
        ( my_iterator const &lhs, my_iterator< VT2, NT2 > const &rhs );

    // etc.

private:
    NodeType *cur;

    friend class my_container;
    my_iterator( NodeType * ); // private constructor for begin, end
};

typedef my_iterator< T, my_node< T > > iterator;
typedef my_iterator< T const, my_node< T > const > const_iterator;

หมายเหตุ: ดูเหมือนว่าตัวแปลง iterator-> const_iterator ของคุณและด้านหลังเสีย
Maxim Egorushkin

@ Maxim: ใช่ฉันไม่พบตัวอย่างการใช้เทคนิคของฉัน: vP ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึงการแปลงถูกทำลายเนื่องจากฉันไม่ได้แสดงให้พวกเขาเห็น แต่อาจมีปัญหาในการเข้าถึงcurจากตัววนซ้ำของเครื่องหมายตรงข้าม วิธีแก้ปัญหาที่อยู่ในใจคือfriend my_container::const_iterator; friend my_container::iterator;แต่ฉันไม่คิดว่าเป็นวิธีที่ฉันเคยทำมาก่อน…อย่างไรก็ตามโครงร่างทั่วไปนี้ใช้งานได้
Potatoswatter

1
* ทำเช่นนั้นfriend classในทั้งสองกรณี
Potatoswatter

ถึงเวลาแล้ว แต่ฉันจำได้ตอนนี้ว่าการแปลงควรจะได้รับการบอกกล่าวล่วงหน้า (โดย SFINAE) เกี่ยวกับความเป็นรูปแบบที่ดีของการเริ่มต้นสมาชิกพื้นฐาน นี่เป็นไปตามรูปแบบ SCARY (แต่โพสต์นี้มีคำศัพท์นั้นมาก่อน)
Potatoswatter

13

มีคำตอบที่ดีมากมาย แต่ฉันสร้างส่วนหัวของแม่แบบที่ฉันใช้ค่อนข้างกระชับและใช้งานง่าย

ในการเพิ่มตัววนซ้ำไปยังคลาสของคุณจำเป็นต้องเขียนคลาสเล็ก ๆ เพื่อแสดงสถานะของตัววนซ้ำด้วยฟังก์ชันขนาดเล็ก 7 ฟังก์ชันโดยที่ 2 เป็นตัวเลือก:

#include <iostream>
#include <vector>
#include "iterator_tpl.h"

struct myClass {
  std::vector<float> vec;

  // Add some sane typedefs for STL compliance:
  STL_TYPEDEFS(float);

  struct it_state {
    int pos;
    inline void begin(const myClass* ref) { pos = 0; }
    inline void next(const myClass* ref) { ++pos; }
    inline void end(const myClass* ref) { pos = ref->vec.size(); }
    inline float& get(myClass* ref) { return ref->vec[pos]; }
    inline bool cmp(const it_state& s) const { return pos != s.pos; }

    // Optional to allow operator--() and reverse iterators:
    inline void prev(const myClass* ref) { --pos; }
    // Optional to allow `const_iterator`:
    inline const float& get(const myClass* ref) const { return ref->vec[pos]; }
  };
  // Declare typedef ... iterator;, begin() and end() functions:
  SETUP_ITERATORS(myClass, float&, it_state);
  // Declare typedef ... reverse_iterator;, rbegin() and rend() functions:
  SETUP_REVERSE_ITERATORS(myClass, float&, it_state);
};

จากนั้นคุณสามารถใช้งานได้ตามที่คุณคาดหวังจากตัววนซ้ำ STL:

int main() {
  myClass c1;
  c1.vec.push_back(1.0);
  c1.vec.push_back(2.0);
  c1.vec.push_back(3.0);

  std::cout << "iterator:" << std::endl;
  for (float& val : c1) {
    std::cout << val << " "; // 1.0 2.0 3.0
  }

  std::cout << "reverse iterator:" << std::endl;
  for (auto it = c1.rbegin(); it != c1.rend(); ++it) {
    std::cout << *it << " "; // 3.0 2.0 1.0
  }
}

ฉันหวังว่ามันจะช่วย


1
ไฟล์เทมเพลตนี้แก้ไขปัญหาตัววนซ้ำทั้งหมดของฉัน!
Perrykipkerrie
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.