This is an example of how an iterator can be added to the Single_list class.
The Single_list class has been stripped down to only the bare essential
functions to insert objects into the singly linked list.
In Project 1, it was possible to access a pointer to the first and last nodes in the
linked list. These have been made private to prevent users from accessing these.
Instead, the functions iterator begin() and iterator end() have
been included.
Because a singly linked list can only be intelligently traversed from the first
node to the last, auto-increment was implemented, but not auto-decrement. Therefore,
if itr is any iterator returned by the singly linked list, you may
use ++itr and itr++. The first returns an iterator to the updated
location while the latter returns an iterator referencing the node originally pointed
to by itr before it was incremented.
The iterator find( Type const & ) const function returns an iterator
pointing to the node containing the argument if that value is found; otherwise, it
returns the iterator Single_list<Type>::end().
#include <iostream>
#include "Single_list.h"
using namespace std;
using namespace ece250;
int main() {
Single_list<int> ls;
// Push the values 0, 1, 4, 9, 16, ..., 81 onto the singly linked list
for ( int i = 0; i < 10; ++i ) {
ls.push_front( i*i );
}
// Step through the linked list using the iterator and print the values
for ( Single_list<int>::iterator itr = ls.begin(); itr != ls.end(); ++itr ) {
cout << *itr << " ";
}
cout << endl;
// Go through the values from 0 to 101 and determine whether or not the values
// are in the singly linked list.
// - Print a 1 if it is found, and 0 otherwise:
for ( int i = 0; i < 102; ++i ) {
cout << ( ls.find( i ) != ls.end() );
}
cout << endl;
// Step through the singly linked list and double each value
for ( Single_list<int>::iterator itr = ls.begin(); itr != ls.end(); ++itr ) {
*itr = 2*(*itr);
}
// Step through the linked list using the iterator and print the values
for ( Single_list<int>::iterator itr = ls.begin(); itr != ls.end(); ++itr ) {
cout << *itr << " ";
}
cout << endl;
return 0;
}
The output of this program is
81 64 49 36 25 16 9 4 1 0
110010000100000010000000010000000000100000000000010000000000000010000000000000000100000000000000000000
162 128 98 72 50 32 18 8 2 0
The Class
The structure of the class is straight-forward: the iterator stores the address of a node. The
constructor is private, so only functions within either the Single_list class or the
Single_list::iterator class can call the constructor.
The public member functions include:
- Accessing the value, *itr,
- Move to the next node, ++itr and itr++,
- Determine if two iterators are referencing the same node, itr1 == itr2, and
- Determine if two iterators are referencing different nodes, itr1 != itr2.
class iterator {
private:
iterator( Single_node<Type> * );
Single_node<Type> *current;
public:
Type &operator*();
iterator operator++();
iterator operator++( int );
bool operator==( iterator const & ) const;
bool operator!=( iterator const & ) const;
friend class Single_list;
};
Internally, the operators simply compare or manipulate the addresses stored in the pointers.
The Significance?
Why is this any different from a function Single_node<Type> *head() const?
The user does not have access to the implementation—if you wanted to change the internal
structure of the data structure, for example, replacing it by a linked list of arrays of values,
as opposed to just linked lists of nodes storing a single value, as long as all the member functions
of the iterator class were appropriately updated, the user would have no idea that the
underlying implementation changed. This is not possible if the structure was exposed
to the user.