Вы находитесь на странице: 1из 22

Classes: Advanced Topics

iterators & friend classes

Tapestry pp. 614-620

Overview & Purpose


When we write a container class, such as LinkedList or linkstrinset classes, we normally
add all functionality that we think would be useful by the users of the class
For example, AddInOrder because people may want to use a linked list as an ordered list
or may be just an Add function, etc.

Similarly, traversing over a linked list is also needed. While traversing you perform some
task on each node (e.g. print the content)
Note that printing is often added to the class (e.g. LinkedList) because it is a fixed and
expected task.
However, someone may need to traverse the list for whatever other reasons they want
(traverse and print twice the number in each node; maybe silly, but it's an example)

Although it is a good idea to add common, expected and mostly used tasks (such as
printing the list) as a member function to the class, we need more generic member
function to traverse the linked list in the main program
be aware that we cannot access private data members of the linked list class as we do in
the implementation of member functions

This functionality is typically provided as an iterator class, associated with the


corresponding container class.
In this slide set, we will see how an iterator class may be written, for the LinkedList class.
These slides are also useful in strengthening/understanding of class member variables and
reference variables.
2

Goal
Often, we use what is called an iterator class to access a container class (e.g.
Linkedlist which contains the data)
e.g. In order to access the link list`s elements one by one from outside the class, what
can be done?
void Print (const LinkedList & list)
{
...
for ( ......... ; ................ ; .........)
cout << .........
cout << "size =" << list.length();
}

Iterators
In order to access a linkedlist`s nodes from outside the class, you need to do one of the
following:
1) Provide methods in the class to access individual elements but still this method may require
knowing or using some of class functions/structs
GetHead(), GetNext(), GetInfo() etc.

2) Write an associated iterator class:


- that can access the private members of the LinkedList class without giving
client code the right to access private data
- a bit more complex but preferable
- define as Friend class to the List class
void Print (const LinkedList & list)
{
LinkedListIterator iter (list);
for (iter.Init(); iter.hasMore(); iter.Next())
cout << iter.Current();
cout << "size =" << list.length();
}

Iterators & Friend Classes


We will show the iterator class concepts over two example cases.
First we will extend previously developed LinkedList class with integer container so
that it now includes a friend iterator class. Later we will go over the iterator class
of Tapestry's linkstringset class.
First of all, the container class for which we are writing an iterator class, needs to
declare the iterator class as friend.
The friend class is not limited to iterators only. Any two class declarations that
needs to be separate, but tightly coupled can be friends.

Friend Class
How to define a friend class (e.g. shapeFriend) to a class (e.g. shape):
class shape
{
public:
...
private:
...
friend class shapeFriend;
}
Friend declaration can be done anywhere in the class declaration (before
public and private, or under public or under private)
shape class grants friend status to shapeFriend (one way)
shapeFriend can acces private data and helper functions (defined in
private) of shape
but not vice versa

Linked List Class Declaration (.h file)


//Declaration of struct node comes here
class LinkedList
{
friend class LinkedListIterator;
private:
node * head;
int size;
public:
LinkedList ();
LinkedList (const LinkedList &);
~LinkedList ();
//destructor

//copy constructor

void printList() const;


void addToBeginning(int n);
void deleteList ();
int length() const; //returns # of elements in the list
const LinkedList & LinkedList::operator = (const LinkedList & rhs);
node * createClone () const;
};

Completing the Iterator Class


class LinkedListIterator
{
public:
//constructor comes here

How should the iterator class be defined,


so as to work as follows?
void Print(const LinkedList & list)
{
LinkedListIterator iter (list);

void Init()
{
...
}
bool HasMore() const
{
...
}

for (iter.Init(); iter.hasMore(); iter.Next())


cout << iter.Current();

//note the use of const

// . . .
}

int Current() const


{
...
}
void Next()
{
...
}
private:
//what do we need to have as data members?

};

Completing the Iterator Class


class LinkedListIterator
{
public:
//constructor comes here

How should the iterator class be defined,


so as to work as follows?
void Print(const LinkedList & list)
{
LinkedListIterator iter (list);

void Init()
{
...
}
bool HasMore() const
{
...
}

for (iter.Init(); iter.hasMore(); iter.Next())


cout << iter.Current();

//note the use of const

// . . .
}

int Current() const


{
...
}
void Next()
{
...
}
private:
//what do we need to have as data members?
//you need to access the lists head to begin
//you need to have a pointer over the list
};

Private Data Members of the Iterator Class


class LinkedListIterator
{
public:
....
private:
const LinkedList &

myList; // to access the linked list

//reference variable will refer to the outside defined linked list,


//as opposed to making a local copy of it.
//it is const so that it can match const parameters (see Print function)
//and also so that we are sure not to change anything
node * myCurrent;
};

// to access the next node of the linked list

Constructor for the Iterator Class


Here the complication is how the iterator should be constructed, so that it can
access the linkedlists private data
Constructor and member function are declared and defined together
Not a rule though, you could have a separate implementation file
class LinkedListIterator
{
public:
LinkedListIterator (const LinkedList & list)
: myList(list), myCurrent(NULL)
{}
. . . //more functions will come
private:
const LinkedList & myList;
node *
};

myCurrent;

Reference is assigned to a
reference variable.
No copy constrcutor is invoked;
myList is another name for the
linked list (the parameter list) on
which the iterator will operate

Completing the Iterator Class


class LinkedListIterator
{
public:
LinkedListIterator(const LinkedList & list)
: myList(list), myCurrent(NULL)
{ }
Initialize the iterator so
that myCurrent shows
void Init()
the head of the list
{ ...
}
bool HasMore() const
{ ...
}
int Current() const
{ ...
}
void Next()
{ ...
}
private:
//see previous slides
};

How should the iterator class be defined,


so as to work as follows?
void Print(const LinkedList & list)
{
LinkedListIterator iter (list);
for (iter.Init(); iter.hasMore(); iter.Next())
cout << iter.Current();
// . . .

Are we at the end of


the list?

The info field of the


node that myCurrent
points
Advance to the next
node.

}
----------------struct node {
int info;
node *next;
...
};
class LinkedList
{
friend class LinkedListIterator;
private:

};

public:
...

node * head;
int size;
12

Completing the Iterator Class


class LinkedListIterator
{
public:
LinkedListIterator(const LinkedList & list)
: myList(list), myCurrent(NULL)
{ }
void Init()
{ myCurrent = myList.head;
// mycurrent points to first node
}
bool HasMore() const
{ return (myCurrent != NULL);
}
int Current() const
{ return myCurrent->info;
}
void Next()
{ myCurrent = myCurrent->next;
}
private:
//see previous slides
};

How should the iterator class be defined,


so as to work as follows?
void Print(const LinkedList & list)
{
LinkedListIterator iter (list);
for (iter.Init(); iter.hasMore(); iter.Next())
cout << iter.Current();
// . . .
}
---------------------------------struct node {
int info;
node *next;
...
};
class LinkedList
{
friend class LinkedListIterator;
private:

};

public:
...

node * head;
int size;
13

Where to Use the Iterator Class


//free function
void Print(const LinkedList & list)
{
LinkedListIterator iter (list);
for (iter.Init(); iter.hasMore(); iter.Next())
cout << iter.Current() << endl;
cout << "size = " << list.length() << endl;
}

int main()
{
LinkedList list1;
for (int k=0; k < 4; k++)
{
list1.addToBeginning(k+1);
}
4
Print(list1);
3
2
}
1
size = 4

LinkedListIterator(const LinkedList& list)


: myList(list), myCurrent(0)
{}

Constructor of the iterator class


is called.
iter.myList refers to list so that
iter object is bound to the
linked list object list
In general, an iterator object is
bound to a particular container
(e.g. linked list) when the
iterator is constructed!

See LinkedListIterator.h and .cpp,


and LinkedListIteratordemo.cpp
for details

Some Issues about the Iterator Class


Do we have another .h file for the iterator class?
We could have, but preferred to have both LinkedList and its iterator class
in the same .h file

What happens if you change head node of the list after binding to the
iterator (i.e. after creating the iterator object)?
Let us see the answer on the code

How can you change the value in a node using iterator?


Let us see the answer on the code

What about copy constructor and destructor for the iterator class?
We did not write and that was on purpose

We do not want deep copy here; the iterator object must not copy the
linked list
It operates on the existing list; thus the reference of the list is used.
Only shallow copy, thus default copy constructor is enough

Since we use the existing list in the iterator class, destructing the
iterator object is catastrophic.
Let's see the effect of having destructor on the code

Iterator Class for LinkStringSet


LinkedStringSet is a Tapestry Class for a linked list with string container.
It works in set manner; that is, the insertion function first check if an
element exists in the list. If so,it does not add. If not, adds.
The implementation uses dummy node. The dummy node is the first node
of the list and it does not contain real data. The first real node is the
next of dummy node. The use of dummy node makes sure that the list
is never empty (head does not point to NULL)
list object
myFirst
mySize

dummy

NULL

The Iterator class of LinkStringSet is more or less similar to the other one
that we have seen, but there are some differences due to the design of
LinkStringSet. We will see them now.

LinkStringSet.h
class LinkStringSet
{
public:
// constructors
...
// accessors
int size() const;
...

// # elements in set

// mutators
...
friend class LinkStringSetIterator;
private:
struct Node
//node definition is embedded in class
{ string info;
Node * next;
...
};
...
Node * myFirst; //head of the list
int mySize;
//number of elements in the list
};

Constructor and Private Data Members for the Iterator Class


Having the node definition in the LinkStringSet class slightly changes the private data members as
compared to the case of other LinkedList class iterator.

class LinkStringSetIterator
{
public:
LinkStringSetIterator (const LinkStringSet & list)
: myList(list), myCurrent(NULL)
{}
private:
const LinkStringSet & myList;
Alternative 1 (more usable, if you will frequently refer to Node):

typedef LinkStringSet::Node Node;


Node *

myCurrent;

Alternative 2 (more clear):


LinkStringSet::Node *
};

myCurrent;

//define a new type name


//
based on an already defined type
//pointer to a linklist node

//refer to LinkList classs node type, rather


//
than defining a new type

Completing the Iterator Class


class LinkStringSetIterator
{
public:
LinkStringSetIterator(const LinkStringSet & list)
: myList(list), myCurrent(NULL)
{}
void Init()
{ myCurrent = myList.myFirst->next;
// mycurrent points to first real node
}
bool HasMore() const
{ return (myCurrent != NULL);
}
string Current() const
{ return myCurrent->info;
}

Class LinkStringSet {
private:
struct Node {
string info;
Node * next;
}
Node * myFirst;
...

//points to dummy header

public:
...
friend class LinkStringSetIterator;
};

void Next()
{ myCurrent = myCurrent->next;
}
private:
//see previous slide
};

19

Using the LinkStringSetIterator Class


Not different than the other Iterator class
void Print(const LinkStringSet & list)
{
LinkStringSetIterator it (list);
for (it.Init(); it.hasMore(); it.Next())
cout << it.Current();
cout << ``size =`` << list.Size();
}

int main()
{
LinkStringSet a;
a.insert("apple");
a.insert("cherry");
cout << "a : ";
Print(a);
//cherry apple 2
}

See linkedstringset.h and .cpp,


and linksetdemo.cpp
for details

Using the LinkStringSetIterator Class - 2


Updating the values stored in the nodes e.g.
append " seed" the end of the info field of
each node in the list
LinkStringSet c;
c.insert("watermelon");
c.insert("apricot");
LinkStringSetIterator itr(c);
for (itr.Init(); itr.HasMore(); itr.Next())
{
itr.Current().append(" seed");
}
cout << "after update\nc : ";
Print(c);
after update
c :
apricot
watermelon
What's output?
---------- size = 2

Why it did not work out?


string Current() const
{ return myCurrent->info;
}

Current returns a copy due to


return type string.
What's updated is this copy, not
the list's element.
How can we solve it? See next
slide for one solution.

See linkedstringset.h and .cpp,


and linksetdemo.cpp
for details

Using the LinkStringSetIterator Class - 3


Solution: Let the Current ()
function return a reference to
the current node's string info
string & Current() const
{ return myCurrent->info;
}
When the return value of Current()
is updated the current list
element is updated.

LinkStringSet c;
c.insert("watermellon");
c.insert("apricot");
LinkStringSetIterator itr(c);
for (itr.Init(); itr.HasMore(); itr.Next())
{
itr.Current().append(" seed");
}
cout << "after update\nc : ";
Print(c);
What's output?

See linkedstringset.h and .cpp,


and linksetdemo.cpp
for details

after update
c :
apricot seed
watermelon seed
---------- size = 2