And here is step 6 ...
the template class List (with iterators) ...
Firstly the test program and then the .h file ...
(but see next page, at bottom, for this demo using the, there, added const_iterator)
// List_step6.cpp // // 2013-05-08 //
// Here we have upgraded our (int) class List to NOW be a template class
// so that it can NOW handle any type of data (NOT just int data)
// We have also added a nested iterator class so that we can traverse the list
// and access the elements in the list ... i.e. the data in any Node in the list
// i.e can read the (const) data ... or change (update) the data in a node
// Note: the data could be another data struct or class
// Also note, that HERE we have NOW moved our 'io' print functions
// to NOW be defined *outside* our class ...
// See the overloaded << operator in the template function def'n right below ...
// that uses the iterators (added here) ...
// for the class to traverse AND to access the elements in the class
// Note that the iterators behave also as a 'wrapper class' that 'wrap' a Node
// to permit access to a Node and the data in a Node ...
// as facilitating traversing the elements in the List ...
// (from the head element ... to the tail element)
#include "template_class_List_SLL.h"
#include <iostream>
#include <fstream> // re. ostream
using namespace std;
template < typename T >
ostream& operator << ( ostream& os, List < T > & lst )
{
typename List < T > ::iterator it;
for( it = lst.begin(); it != lst.end(); ++ it )
os << *it << ", ";
return os << "size now is " << lst.size() << endl;
}
int main()
{
List < int > myLst;
myLst.push_back( 10 );
myLst.push_back( 30 );
myLst.push_back( 20 );
myLst.push_back( 40 );
cout << "Showing myLst ...\n" << myLst;
List < int > myLst2 = myLst;
cout << "\ndeleting 20 ... \n";
myLst.erase( 20 );
cout << "Showing myLst ...\n" << myLst;
cout << "\ndeleting 40 ... \n";
myLst.erase( 40 );
cout << "Showing myLst ...\n" << myLst;
cout << "\nclear list ... \n";
myLst.clear();
cout << "Showing myLst ...\n" << myLst;
cout << "\nShowing myLst2 ...\n" << myLst2;
cout << "\nShowing myLst2 ...\n";
List< int >::iterator it;
for( it = myLst2.begin(); it != myLst2.end(); ++ it )
cout << *it << endl;
cout << "Press 'Enter' to continue/exit ... " << flush;
cin.get();
}
And the .h file ...
// template_class_List_SLL.h // // 2013-05-08 //
#ifndef TEMPLATE_CLASS_LIST_SLL_H
#define TEMPLATE_CLASS_LIST_SLL_H
template < typename T >
class List
{
// this section is private by default (for a class)
// we are putting this private Node definition at the top so that it is 'known'
// inside our 'nested' interator class that follows here ...
struct Node
{
T val;
Node* next;
// default ...and value passed in ...ctor(s)... (this code handles both)
// Note initialization list and how default values are handled here...
Node( const T& value = 0 ) : val( value ), next( 0 ) {}
} ;
Node* head;
Node* tail;
int len;
public:
// default ctor with initialization list ...
List() : head( 0 ), tail( 0 ), len( 0 ) {}
~List() { clear() ; }
List( const List& old ); // copy ctor ...
List& operator = ( const List& old ); // overloaded =
void push_back( const T& val );
void push_front( const T& val );
// erase FIRST node with this val (if it exists in list)
bool erase( const T& val );
void clear();
int size() const { return len; }
class iterator
{
friend class List< T >; // to give class List access to private ptr
public:
iterator& operator ++ () { ptr = ptr->next; return *this; }
iterator& operator ++ ( int ) { ptr = ptr->next; return *this; }
bool operator != (const iterator& ) const;
T operator * () const;
T& operator * ();
private:
Node* ptr;
};
iterator begin();
iterator end();
} ;
template < typename T >
List< T >::List( const List& old )
{
head = tail = 0;
len = 0;
Node* cur = old.head;
while( cur ) { push_back( cur->val ); cur = cur->next; }
}
template < typename T >
List< T >& List< T >::operator = ( const List& old )
{
if( this != &old )
{
clear();
//head = tail = 0;
//len = 0;
Node* cur = old.head;
while( cur ) { push_back( cur->val ); cur = cur->next; }
}
return *this;
}
template < typename T >
void List< T >::push_back( const T& val )
{
Node* newNode = new Node( val );
if( len ) // then newNode not first node
{
tail->next = newNode;
tail = newNode;
}
else head = tail = newNode;
++ len;
}
template < typename T >
void List< T >::push_front( const T& val )
{
Node* newNode = new Node( val );
newNode->next = head;
head = newNode; // can now update head ...
if( ++len == 1 ) tail = newNode;
}
// erase FIRST node with this val (if it exists in list)
template < typename T >
bool List< T >::erase( const T& val )
{
Node* cur = head; // get copy
Node* prev = 0;
while( cur )
{
if( cur->val == val ) break;
prev = cur;
cur = cur->next;
}
// test end conditions ...
if( cur ) // was found
{
if( prev ) // was not first node, so prev and cur exist
prev->next = cur->next; // skip over cur ...
else // was first node
head = head->next;
if( cur == tail ) tail = prev;
delete cur;
-- len;
return true; // was erased ...
}
// else cout << "\nNot found ...\n";
return false;
}
template < typename T >
void List< T >::clear()
{
Node* cur;
while( head )
{
cur = head;
head = head->next; // head set to NULL at end of loop //
delete cur;
}
tail = 0;
len = 0;
}
// Compares two iterators and returns true or false depending on whether
// or not ... they are referencing the same Node.
template < typename T >
bool List< T >::iterator::operator != (
const typename List< T >::iterator& it ) const
{
return ptr != it.ptr;
}
// Returns the value of the Node that the iterator is referencing.
template < typename T >
T List< T >::iterator::operator * () const
{
return ptr->val;
}
// Permits assigning a Node a new value, via an iterator.
template < typename T >
T& List< T >::iterator::operator * ()
{
return ptr->val;
}
template < typename T >
typename List< T >::iterator List< T >::begin()
{
typename List< T >::iterator it;
it.ptr = head;
return it;
}
template < typename T >
typename List< T >::iterator List< T >::end()
{
typename List< T >::iterator it;
it.ptr = 0;
return it;
}
#endif