Introduction to iterator pattern
Let's first introduce the meaning of Iterator pattern. The core function of Iterator pattern Iterator is to provide a specific method to access each element in a container sequentially, which will not expose the internal design details of the container (the underlying data structure of the container), but also allow external code to access all elements in the collection transparently.
Firstly, a UML class diagram of iterator pattern is given, and the design is as follows:
Therefore, an iterator should basically provide the following interface operations. The code is as follows:
#include<iostream> using namespace std; //Abstract base class for iterators template<typename _Ty> class Iterator { public: //Is there another element virtual bool hasNext() = 0; //Returns the next element virtual _Ty& next() = 0; };
An abstract base class of iterators is defined above
The hasNext interface indicates whether there is another element inside the aggregate object (i.e. container). The next interface returns the value reference of the next element. Through such an iterator instance, you can traverse and access the internal elements of the container without knowing the specific implementation inside the container. It is completely transparent to users.
Of course, different containers have different internal data structures, so the iterator traverses them in different ways. Therefore, the iterator is generally designed as a nested class of the container and directly implements the logical process of iterator iteration inside the container. Please see the following code example.
Iterator pattern code example
See the following code example:
#include<iostream> using namespace std; //Abstract base class for iterators template<typename _Ty> class Iterator { public: //Is there another element virtual bool hasNext() = 0; //Returns the next element virtual _Ty& next() = 0; }; //Definition of container class template<typename _Ty> class CArray { public: //Only the operation of adding elements to the container is provided template<typename _Ty> void push(_Ty&& val) { _vec.push_back(std::forward<_Ty>(val)); } //Implementation of the current container iterator class ConcreteIterator : public Iterator<_Ty> { public: //Iterator constructor ConcreteIterator(CArray<_Ty>& arr) :_array(arr) { _cur = 0; } //Determine if there is a next element bool hasNext() { int size = _array._vec.size(); return _cur < size; } //Returns the value of the next element _Ty& next() { _Ty& val = _array._vec[_cur]; _cur++; return val; } private: CArray<_Ty>& _array;//Gets a reference to the container that the current iterator int _cur;//The element subscript of the current iterator }; //Returns the function interface of the container's iterator Iterator<_Ty>* createIterator() { return new ConcreteIterator(*this); } private: vector<_Ty> _vec;//Container for storing elements };
Here, as the implementation of a container, the bottom layer directly uses the vector container as the storage structure of elements. It internally implements an Iterator, ConcreteIterator, which inherits from the Iterator, rewrites the hasNext and next methods, as a specific Iterator for traversing the CArray container, and provides a createIterator function, The Iterator specially returns the container object. The specific traversal code is as follows:
int main() { //Define container objects CArray<int> array; //Add right value parameter array.push(20); array.push(56); array.push(3); array.push(21); //Add lvalue parameter int data = 89; array.push(89); //Iterate through the elements inside the access container object and print Iterator<int>* it = array.createIterator(); while (it->hasNext()) { int val = it->next(); cout << val << " "; } cout << endl; return 0; }
In the main function above, the code that the iterator traverses to access the container is general code. No matter how the underlying data structure of the container changes, the way the iterator traverses the container will not change. The underlying difference details are completely encapsulated in the iterator's hasNext and next functions, and the internal operation is completely transparent to the user.
Anyone familiar with the internal iterator design of C++ STL container knows that the design of STL container iterator is essentially the same as the general iterator mode above. There are still some differences in implementation details. Let's make a comparison.
Design of container iterator for C++ STL
The design analogy between the container iterator inside C++ STL and the general iterator pattern above:
1. Iterators are designed as nested types of containers, because different containers need to implement their own iterators for specific data traversal due to their different underlying data structures.
2. The C++STL container uniformly provides the begin() and end() methods to return the iterator of the starting element of the container and the iterator of the subsequent position of the end element, which is similar to the createIterator method provided by the curray container above.
3. The iterator of C++STL judges whether the iteration of the container element is completed by comparing it with the end() of the container, which is similar to the hasNext method of the general iterator above.
4. The iterator of C++STL encapsulates the details of data traversal inside the container in the operator + + operator overload function of the iterator, and provides the operator * () operator overload function of the iterator to access the value of the element traversed by the iterator, which is similar to the function of the next method of the general iterator above.
The following demonstrates the standard use of a C++STL container iterator. The code example is as follows:
#include <iostream> #include <vector> #include <ctime> using namespace std; int main() { //Define the vector container object and add 20 random integers vector<int> vec; srand(time(nullptr)); for (int i = 0; i < 20; ++i) { vec.push_back(rand() % 100 + 1); } //All elements of the container are accessed through the iterator of the vector container vector<int>::iterator it = vec.begin(); for (; it != vec.end(); ++it) { cout << *it << " "; } cout << endl; //C++11 provides a new container traversal syntax foreach, which is implemented by iterators for (int val : vec) { cout << val << " "; } cout << endl; return 0; }