Data Structures Dr Ahmed Rafat Abas Computer Science Dept, Faculty of Computer and Information, Zagazig University arabas@zu.edu.eg http://www.arsaliem.faculty.zu.edu.eg/
Stacks and Queues Chapter 8 (Cont.)
Example: Show the effect of the use of virtual functions in a base class when other classes are derived from it. (Q1 Chap. 6) Consider the class definitions: class BaseClass { protected: int Num1; public: BaseClass () : Num1(0) {} void TestFunc() { Num1 += 10;} };
class DerClass : public BaseClass { public: void TestFunc() { Num1 += 20; } }; After the code: BaseClass Object1; Object1.TestFunc (); What is the value of Object1.Num1? 10 function call answered by parent class.
After the code: DerClass Object2; Object2.TestFunc (); What is the value of Object2.Num1? 20 function call answered by child class.
After the code: BaseClass *Object3 = new BaseClass; Object3->TestFunc (); What is the value of Object3.Num1? 10 function call answered by parent class.
After the code: BaseClass *Object4 = new DerClass; Object4->TestFunc (); What is the value of Object4.Num1? 10 function call answered by parent class.
The declaration of TestFunc () in BaseClass is now replaced by the line: virtual void TestFunc () { Num1 += 10; } After the code: BaseClass *Object5 = new DerClass; Object5->TestFunc (); What is the value of Object5.Num1? 20 function call answered by child class.
Note: defining an object on a derived class makes no problem when function overloading occurs. However, defining a pointer to the base class and assigning to it an address to the derived class requires the overloaded functions in the base class to be virtual in order to run the overloading functions in the derived class.
Example: show the order of execution of constructor and destructor functions of classes that contain member objects and are related by inheritance. class base { protected: float a; public: base(float s=0); ~base(); }; // base.h
class base2 { protected: float aa; public: base2(float s=0); ~base2(); }; // base.h class derived:public base { private: float b; base obj1; public: derived(float f=0,float g=0); ~derived(); };
class derived2:public derived { // base.h private: float c; base2 obj2; public: derived2(float f=0,float g=0, float h=0); ~derived2(); };
base base2 derived base derived2 base2
// base.cpp #include "base.h" #include <iostream.h> base::base(float d):a(d) { cout<<"base class constructor"<<'\n'<<"a= "<<a<<endl; return; } base::~base() { cout<<"base class destructor"<<'\n'<<"a= "<<a<<endl; return; } base2::base2(float d):aa(d) { cout<<"base2 class constructor"<<'\n'<<"aa= "<<aa<<endl; return; }
// base.cpp base2::~base2() { cout<<"base2 class destructor"<<'\n'<<"aa= "<<aa<<endl; return; } derived::derived(float d,float e):base(d),b(e) { cout<<"derived class constructor"<<'\n'<<"b= "<<b<<endl; return; } derived::~derived() { cout<<"derived class destructor"<<'\n'<<"b= "<<b<<endl; return; }
// base.cpp derived2::derived2(float d,float e,float k):derived(d,e),c(k) { cout<<"derived2 class constructor"<<'\n'<<"c= "<<c<<endl; return; } derived2::~derived2() { cout<<"derived2 class destructor"<<'\n'<<"c= "<<c<<endl; return; }
// main.cpp #include "base.h" #include<iostream.h> showorder(); main() { showorder(); return 0; } // main.cpp showorder() { derived2 dcc(2,4,6); cout<<endl; cout<<" "<<endl<<endl; return 0; }
Note: The order of execution of constructor functions of classes with member objects and related by inheritance is: Initialization list of the derived class Initialization list of the base class Member objects of the base class Constructor of the base class Member objects of the derived class Constructor of the derived class
8.3 Queues Head Pointer elm1 elm2 elm3 elm4 elm5 elm6 Tail Pointer
8.3.1 Implementing a queue Queues can also be implemented using arrays or lists. For the present, the implementation of a queue using arrays is considered. Define a class containing an array for storing the queue elements, and two pointers: one pointing to the head of the queue, and the other to the first empty space following the tail.
Adding an item to the queue: check whether the tail pointer points to a valid location, then add the item to the queue and increment tail pointer by 1. Removing an item from the queue: check whether the queue is empty and, if not, retrieve the item referred to by the head pointer, Increment the head pointer by 1.
If several items are removed from the queue, then there will be empty spaces at the beginning of the array. To make use of these spaces in adding data items to the queue, all the stored data items in the queue should be shifted toward the top of the array so that the head pointer of the queue returns to the beginning of the array.
However, the shifting process is costly in terms of computer time, especially if the number of items stored in the array is large. In order to overcome this problem, a queue can be implemented using a circular array, in which the entire array can be used for storing queue elements without requiring any shifting for the data stored. A circular array with QSIZE elements is shown in the following figure.
QSIZE-1 0 1 A circular array.
Circular array is stored in memory as a linear block of QSIZE elements. The circular diagram is just a convenient way of representing the data structure. Head and tail pointers are used to indicate the location of the head and the location just after the tail where the next item should be added to the queue, respectively. An empty queue is denoted by the condition head == tail, as shown in the following figure.
head tail QSIZE-1 0 1 An empty queue.
To identify when the queue is full, it is necessary to keep at least one free space in the array, and therefore a queue becomes full when the tail pointer points to the location immediately prior to the head of the queue, as shown in the following figure.
QSIZE-1 0 1 tail head A full queue.
The algorithms required for dealing with a queue represented by a circular array Creating an empty queue: set head = tail = 0. Testing if a queue is empty: is head == tail? Testing if a queue is full: is (tail + 1) % QSIZE == head? Adding an item to a queue: if queue is not full, add item at location tail and set tail = (tail + 1) % QSIZE. Removing an item from a queue: if queue is not empty, remove item from location head and set head = (head + 1) % QSIZE. The use of the % operator, which is the modulus operator in C++, ensures that head and tail wrap around the end of the array.
8.3.2 A queue in C++ The C++ code for the algorithms that implement a queue using a circular array is given below. The following queue is used for storing integers. // intqueue.h #ifndef INTQUEUE_H #define INTQUEUE_H #include <iostream.h> typedef int BOOL; enum{false, TRUE};
class intqueue { protected: int QSize; int *Element; // Size of the Element array // For storing queue contents // Locate head and (tail+1)%qsize int Head, Tail; public: BOOL Empty() const; BOOL Full() const; intqueue(int queuesize = 10); virtual ~intqueue(); virtual BOOL Remove(int& TopElem); virtual BOOL Add(const int& NewElem); } ; #endif
The following are the definitions of these functions. //intqueue.cpp. #include "intqueue.h" BOOL intqueue::empty() const { return Head == Tail? TRUE: FALSE; } BOOL intqueue::full() const { return (Tail + 1) % QSize == Head? TRUE: FALSE; }
intqueue::intqueue(int queuesize) :QSize(queuesize), Element(new int[queuesize]), Head(0), Tail(0) { } intqueue::~intqueue() { delete [] Element; } BOOL intqueue::remove(int& TopElem) { if (!Empty()) { TopElem = Element[Head]; Head = (Head + 1) % QSize; return TRUE; } else { cout «"Queue empty: Remove failed.\n"; return FALSE; } }
BOOL intqueue::add(const int& NewElem) { if (! Full ()) { Element [Tail] = NewElem; Tail = (Tail + 1) % QSize; return TRUE; } else { cout «"Queue full: Add failed.\n"; return FALSE; } }
In order to test this class and provide a simple application of inheritance, a derived class testqueue which inherits intqueue and adds a few specialized functions useful for testing the queue is defined. One of the added functions is Print (), which traverses the queue starting at Head and stepping through the queue until it reaches the item just before Tail. Another function added is Menu (), which prints a menu of options for testing the queue. All these functions are used in a main () function.
The testqueue class is defined in the header file testque.h as follows. #ifndef TESTQ_H #define TESTQ_H #include "intqueue.h" class testqueue : public intqueue { public: testqueue(int queuesize = 10); void Print() const; int Menu() const; } ; #endif
The new functions are defined in the file testque.cpp as follows. //testque.cpp #include "testque.h" testqueue::testqueue(int queuesize) : intqueue(queuesize) { } void testqueue::print() const { if (Empty () ) cout «"Queue is empty.\n"; else for (int marker = Head; marker!= Tail; marker = (marker + 1) % QSize) cout «marker «' ' «Element [marker] «endl; }
int testqueue::menu() const { int Choice; cout «"--------------------------\n"; cout «"Select from:\n"; cout «"1. Add integer to queue\n"; cout «"2. Remove item from queue\n"; cout «"3. Print queue contents.\n"; cout «"0. Quit.\n"; cout «"Your choice: "; cin» Choice; return Choice; }
The main () function is defined in the file queue.cpp as follows. //main.cpp #include "testque.h" int main () { testqueue Queue(5); int Choice, Item; while (Choice = Queue.Menu()) switch (Choice) { case 1: cout «"Enter item to add: "; cin» Item; Queue.Add ( Item) ; break;
case 2: if (Queue.Remove(Item)) cout «"Item" «Item «" removed.\n"; break; case 3: cout «"Contents of queue:\n"; Queue.Print(); break; } return 0; }