C++ Course Outline

Part I
(1)  What is C++?
(2)  What is C++ in relation to C?
(3)  The simple new features of C++:
     I/O
     Comments
     Variables
     Constants
     Managing heap objects
     Referencing objects
(4)  Designing classes
(5)  A Student class
(6)  Working with objects
(7)  C++ Student class implementation (STUDENT.H)
(8)  Student class functionality (STUDENT.CPP)
(9)  Code to test the Student class (STEST.CPP)
(10) Syntax when coding classes
(11) Hiding data
(12) Revised Student code to support encapsulation
(13) Summary of OO terminology
(14) A C implementation of a C++ class
(15) C Student class implementation (CSTUDENT.H)
(16) CSTUDENT.C
(17) Code to test the C Student class (STEST.C)
(18) Programming operators
(19) Operator member function syntaxx
(20) Defining an operator
(21) Employee stock code (ESTOCK.H,ESTOCK.CPP,ESTEST.CPP)
(22) Overloading functions/operators
(23) Default parameters
(24) Protecting data
(25) Selected exercises
Part II
(26) Copy constructor
(27) When to supply a copy constructor
(28) Assignment operator
(29) Using the this pointer
(30) Destructor
(31) Static class members
(32) Revised EmployeeStock code (ESTOCK.H)
(33) Revised ESTOCK.CPP
(34) Test code (ESTEST.CPP)
(35) Friend functions

(36) FRIENDEX.CPP
(37) Friend classes
(38) Providing streamable support
(39) Programming the display/input functions
(40) Revised personn class (FRIENDEX2.CPP)
(41) File input/output using streams
(42) Writing class file i/o support
(43) Inline functions
(44) Making an out­line function inline
(45) Selected exercises

Part III
(46) Deriving classes
(47) General derivation syntax
(48) Deriving UniversityStudent from Student
(49) Revised Student class (STUDENT2.H)
(50) STUDENT2.CPP
(51) UniversityStudent definition (USTUDENT.H)
(52) USTUDENT.CPP
(53) UTEST.CPP
(54) Dynamic binding
(55) Virtual functions
(56) A virtual display() function
(57) Revised STUDENT2.H
(58) Revised USTUDENT.H
(59) Revised USTUDENT.CPP
(60) Test code (DBIND.CPP)
(61) A musical instrument derivation hierarchy
(62) Multiple inheritance
(63) Memory map of multiple inheritance
(64) Inheritance versus embedding
(65) Virtual base classes
(66) Pointers to member functions
(67) Assertions
(68) Exceptions
(69) Sample exception code (EXCEPT.CPP)
(70) When to code exceptions
(71) Selected exercises
Part IV
(72) C++ data structures
(73) An abstract base class
(74) An abstract Object definition
(75) C++ linked list code (OBJECT.H, LL.H)
(76) LL.CPP
(77) LLTEST.CPP
(78) Templates
(79) Template header file definition
(80) A template array definition (ARRAY.H)
(81) ARRAYTST.CPP
(82) Multiple template types
(83) Templates in general...
(84) A linked list template implementation
(85) LinkedList template definition (LLT.H)
(86) LLTTEST.CPP
(87) Selected exercises

What is C++?

C++ is an extension of the C language providing object­oriented support to programmers, 
namely:

. Encapsulation   (Data hiding/protection)
. Overloading     (Multiple function instances)
. Inheritance     (Reusability)
. Polymorphism    (Run­time type resolution, dynamic binding)
. Templates       (Type parameterizable classes)

The above techniques provide programmers with increased power with the minimum of syntax, 
hence validating C++'s popularity in the world of object­oriented technology.

As a result, C++ is widely replacing C as a worldwide industry standard.

What is C++ in relation to C?
 
C is actually a subset of C++, meaning that all C programs will run under a C++ compiler, but 
not vice­verse.

     ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
    |                                    |
    |    ­­­­­­­­­­­­­­­                 |
    |   |      C        |     C++        |
    |   | printf()      |      cout      |
    |   | malloc()      |      new       |
    |   | main()        |      virtual   |
    |   | static        |      class     |
    |   | #define       |      public    |
    |   | ...           |      inline    |
    |    ­­­­­­­­­­­­­­­       const     |
    |                          ...       |
    |                                    |
     ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

All C++ programs should be suffixed with .CPP.
The compiler does not recognize C++ keywords in a .C file.

The simple new features of C++

(1) I/O

Stream classes have been created to replace the standard C file streams.  The objects cin, cout, 
and cerr are the equivalents of the C stdin, stdout, stderr file streams. 

#include <iostream.h>
void main()
{
int g;
float f;
cout << "Enter variables g,f ";
cin >> g >> f;
cout << "You entered " << g << "," << f << endl;
}

Output

Enter variables g,f  54 78.9              
You entered 54,78.9  

(2) Comments

An additional commenting method has been added to the language to support quick commenting 
of lines.

The rule is the compiler ignores any text after the sequence // up until the next carriage return.

// A program to print hello twice
/* The C printf() is called and the cout
   object simulates the result */  
#include <iostream.h>
#include <stdio.h>
void main()
{
printf("Hello! C functions supported\n");
cout << "Hello! the C++ way" << endl;
// cout << "This line of code is commented out\n";
}

(3) Variables

Recall that C restricts variables to being declared at the beginning of a function.
In C++, variables can be declared anywhere within the enclosing braces {..} of a function.
This gives scope for grouping variables near the code they are used in for better program 
readability. 

#include <iostream.h>
void main()
{
cout << "My program is executing" << endl; 
int x = 6; //!! we can declare x flush to the
                    //    for loop where it is used
for (int i = 7; i + x > 5; i­­) 
cout << "[" << i << "]" << endl;
}

(4) Constants

Recall that C provides macro substitution of constants or computed constants at compile time via 
the #define directive.

#define G 9.8
...
f = m * G;  /* C compiler subs in 9.8 for G */

Note however that no type is allotted for G.

C++ provides the const keyword to typedefine a constant:
const <type> <variable name> = <value>;

A physical cell is allocated in memory of size depending on <type> and const guarantees that no 
one can change the value of <variable name>.
The compiler can perform type­checking can be done on the variable as it is passed into 
functions.

 
#include <iostream.h>
const int   MASS = 10;
const float G    = 9.8;
float force(int mass,float acceleration)
{
return (mass*acceleration); }
void main()
{

cout << "Force is " << force(MASS,G) << " newtons " << endl;
}

 
(5) Managing heap objects

The keywords new and delete replace the C malloc() and free() calls.

The syntax is:

<Object pointer> = new <Object type>
...
delete <Object pointer>;

void main()
{
int* heapArrayOfInts = new int[55];
heapArrayOfInts[0] = 56;
heapArrayOfInts[1] = 52;
//...
delete heapArrayOfInts;
}

(6) Referencing objects

One complaint C programmers had was that the pass by reference pointer syntax was ugly and 
prone to compile errors.
For example, if we wanted to write a C swap function, we would have to code:

void swap(int *a,int *b)
{
int temp = *a;
*a = *b;               /* Too many *'s! */
*b = temp;
}

C++ provides an easier mechanism for modifying arguments via the reference operator (&):

void swap(int &a,int &b)  // Pass a,b   by reference
{
int temp = a;
a = b;              // We are modifying the calling
b = temp;           // parameters, not local copies
}

Functionally, both code segments are the same; behind the scenes, C++ translates the above logic 
into the equivalent C swap code.  But syntactically we can refer to the reference (or pointer to) a 
and b without all the *'s.
Note that referencing and pointer syntax are interchangeable:

int A,*a=&A;
 // A is an integer, a points to A
int &aReference = *a;   // Creates aReference to A

aReference = 9;         // Changes A to 9
*a = 10;                // Changes A to 10

Designing classes

The power of C++ resides in its ability to create classes.  A class is a definition of data with 
operations on that data.  The definition rings somewhat similar to that of a data structure; in 
fact they are one and the same.

    
   

  

 ­­­­­­­­­­­­­­­­­­­­­­­­­
| CLASS                   |
| <has>  DATA             |
| <has>  FUNCTIONALITY    |
 ­­­­­­­­­­­­­­­­­­­­­­­­­

When we create data structures in C we must carefully package the unit via a .H header file 
describing the data and a corresponding .C file describing the operations.

We can simulate this packaging much the same way but with greater ease, effect and power using 
the C++ class keyword.

A Student class

Consider a simple implementation of a data structure, or class, that supports data on students. 
Suppose a student "has a" name and two marks.  We can represent this pictorially by a class data 
diagram:
    
 ­­­­­­­­­­
 
|Student   |
      ­­­­­­­­­­
|­­> name  : string         
|­­> mark1 : integer        "­­>" denotes "has a" condition
|­­> mark2 : integer

We can extend this definition by adding on the things that we would like to do with a student. 
We want to initialize a student, edit a student, and calculate their average:

    
 ­­­­­­­­­­
 
|Student   |
      ­­­­­­­­­­
name  : string       <=|
mark1 : integer        |  DATA PART
mark2 : integer      <=|
init()               <=|
edit()                 |  FUNCTIONAL PART
average()            <=|

Having the complete class diagram, we are ready to translate the sketch into C++ code.

Working with objects

Many objects of various classes can be sitting in memory and we can talk to them by sending 
messages to them via function calls or by reading/writing to their data members. 

We always think of the object first, meaning that the object must exist (in memory) before we can 
access it.  This makes sense.  That is why syntactically, C++ demands the name of the object 
first.  Without knowing the name of the object, we can't perform any request.
Once we have the object, then we can suffix the object name specifically with the request, being 
the data we want to grab or the message we want to send. 

To access an object method, we code:

<object name>.<function name>(<argument list>)

To access a data member of an object, we could code

<object name>.<data member name>

If object name is a pointer to an object, we use the ­> operator:

<object name>­>[request]
 

C++ Student class implementation (STUDENT.H)

#ifndef STUDENTH
#define STUDENTH
const int MAX_STUDENT_NAME_CHARS = 40;
class Student
{
public:
char name[MAX_STUDENT_NAME_CHARS];    /* DATA MEMBERS     */
int mark1,mark2;
Student();                            /* FUNCTION MEMBERS */
Student(char*,int,int);
void edit(char *,int,int);
void display();
int average();
};
#endif        

Student class functionality (STUDENT.CPP)

#include "student.h"
#include <string.h>
#include <iostream.h>
Student::Student()   /* DEFAULT CONSTRUCTOR */
{
name[0] = NULL;
mark1   = 0;
mark2   = 0;
}
Student::Student(char *_name,int _mark1,int _mark2) /* SPECIFIC CONSTRUCTOR */
{
edit(_name,_mark1,_mark2);
}
void Student::edit(char *_name,int _mark1,int _mark2) /* MUTATOR */
{
strcpy(name,_name);
mark1   = _mark1;
mark2   = _mark2;
}
void Student::display()                               
{
cout << name << "," << mark1 << "," << mark2 << endl;
}
int Student::average()
{
return ((mark1+mark2)/2);
}

Code to test the student class (STEST.CPP)

#include "student.h"
#include <iostream.h>
void main()
{
Student s1("Martha",67,78);   // (Method1) create a local object instance,
   //      deallocated automatically
s1.display();
cout << s1.name << "'s average is " << s1.average() << endl;
Student *s2 = new Student;    // (Method2) create a heap object instance
s2­>edit("Joey",78,55);
s2­>display();
cout << s2­>name << "'s average is " << s2­>average() << endl;
delete s2;                   // free the heap object
}
Output
Martha,67,78          
Martha's average is 72
Joey,78,55            
Joey's average is 66  

Memory map

Stack:
            ­­­­­­­­­­­
      s1   | Martha    |
           | 67        |
           | 78        |
            ­­­­­­­­­­­

Heap:
            ­­­­­­­­­­­

      s2­> | Joey      |
           | 78        |
           | 55        |
            ­­­­­­­­­­­

Syntax when coding classes

The header file (.H) contains the class definition: a description of the data members and 
prototypes of the member functions.
The actual details of the functions are described in a corresponding (.C) file.
To bind a member function to a particular class, the scope operator :: is used.  The syntax is:

<return type> <Class name>::<function name>(<argument list>)
{
...(function body)
}

Without a :: class scope specifier, we are back into writing C code.  The following describes a 
floating global C function:

void display()
{
cout << name << "," << mark1 << "," << mark2 << endl;
}

Not to mention that the compiler won't recognize the symbols name, mark1, and mark2 because 
they are not declared.  They are only recognized within the class as class members:

void Student::display()
{
     
// This works becase name, mark1, mark2 are declared
     
// as members of the Student class
cout << name << "," << mark1 << "," << mark2 << endl;
}

    

Hiding data

As it stands, there are some flaws with the Student class.  Note that the data members are 
declared as public, meaning that users of the class can directly access them and possibly write to 
them maliciously:

Student s1;
strcpy(s1.name,"....80 characters");
// crashes! because s1.name has only space for 40 chars

We should design the class to protect the integrity of the data members.  We can do this by 
declaring our data members private, thus hiding or encapsulating the core data from users, and 
by providing better internal error checking in our edit() mutator.
Of course we will have to supply accessor functions to allow read access to name, mark1 and 
mark2.

const char* Student::getName() { return name; }

We can protype the getName() accessor with const to force the compiler to prevent the class user 
from inadvertantly changing the contents of the name array.
Our getMark1() and getMark2() accessors return merely a copy of the data, so const protection is 
unnecessary:

int Student::getMark1() { return mark1; }
int Student::getMark2() { return mark2; }

 

Revised Student code to support encapsulation

STUDENT.H
class Student
{
private:
char name[MAX_STUDENT_NAME_CHARS];    /* DATA MEMBERS     */
int mark1,mark2;
public:
Student();                            /* FUNCTION MEMBERS */
Student(char*,int,int);
void edit(char *,int,int);
void display();
int average();
const char* getName();
int getMark1();
int getMark2();
};

STUDENT.CPP

#define min(a,b) (a < b ? a : b)
...
void Student::edit(char *_name,int _mark1,int _mark2) /* MUTATOR */
{
strncpy(name,_name,
min(strlen(_name)+1,MAX_STUDENT_NAME_CHARS));
mark1   = _mark1;
mark2   = _mark2;
}
const char* Student::getName() { return name; }
int Student::getMark1() { return mark1; }
int Student::getMark2() { return mark2; }

STEST.CPP

void main()
{
Student s1("Martha",67,78);   // (Method1) create a local object,
   //    deallocated automatically
s1.display();
//
strcpy(s1.name,"Joeee");      // Gives error: cannot access private member
//
strcpy(s1.getName(),"joeey"); // Gives error: cannot convert const char* to char*
cout << s1.getName() << "'s average is " << s1.average() << endl;
Student *s2 = new Student;    // (Method2) create a heap Student object
s2­>edit("Joey",78,55);
s2­>display();
cout << s2­>getName() << "'s average is " << s2­>average() << endl;
delete s2;                   // free the heap object
}

Summary of OO terminology

Class definition:
A definition or template of a data set and operations that work particularly on that data set.
Object instance:
An object is any physical instance or manifestation of a class that is sitting in memory.  A String 
is an object.  A Console is an object, even an integer can be considered on object.
Class member:
Any entity of the class, a piece of data or a function.  A class can contain both data members and 
function members.
Encapsulation:
The ability of a class to "hide" its internal details from the outside world.

Constructor:
A special function that initializes an object of a particular class to a certain state. 

Accessor:
A special function that accesses a piece of data from an object for read­access only and returns it 
to a caller from the outside world.

Mutator:
A special function called by a caller from the outside world that sets an objects internal piece(s) 
of data to particular value(s).

Method:
Any class member function.

A C implementation of a C++ class

Recall that in C to do the same job we had define a structure of the data involved then write 
floating C functions to do the work.  We even had to be careful to choose the names so they 
wouldn't conflict with other possible common names in other data structures.
A C++ class looks strikingly similar to a C struct with some functions embedding.
In fact we could simulate the student class version in C.  In fact, this is what kind of code the 
compiler ultimately generates, not in C, but in native code.
Some earlier C++ compilers were in fact merely C translaters.  They took C++ code, generated 
C code, and invoked a C compiler to get the final native .EXE.
A true C++ compiler translates C++ code directly into native machine code.

C Student class implementation (CSTUDENT.H)

#ifndef CSTUDENTH
#define CSTUDENTH
           
#define MAX_STUDENT_NAME_CHARS 40
typedef struct student
{                                
/* Data members */
char name[MAX_STUDENT_NAME_CHARS];
int mark1,mark2;
    
    /* Function members */
    
void (*init1)(struct student*);
void (*init2)(struct student*,char *,int,int);  
void (*edit)(struct student*,char *,int,int);
void (*display)(struct student*);
int  (*average)(struct student*);
} Student;
    
/* Global student instance initializer */
extern void StudentInstance(Student *s);
#endif                   

CSTUDENT.C

#include "cstudent.h"
#include <stdio.h>
#include <string.h>
void cstudent_init1(Student *s)   /* DEFAULT CONSTRUCTOR */
{
s­>name[0] = 0;
s­>mark1   = 0;
s­>mark2   = 0;
}
void cstudent_edit(Student *s,char *_name,int _mark1,int _mark2)   /* MUTATOR */
{
strcpy(s­>name,_name);
s­>mark1   = _mark1;
s­>mark2   = _mark2; 
}
void cstudent_init2(Student *s,char *_name,int _mark1,int _mark2) /* SPECIFIC CONSTRUCTOR */
{       
cstudent_edit(s,_name,_mark1,_mark2);
}
void cstudent_display(Student *s)
{
printf("%s,%d,%d\n",s­>name,s­>mark1,s­>mark2);
}
int cstudent_average(Student *s)
{
return ((s­>mark1+s­>mark2)/2);
}
void StudentInstance(Student *s)
{
/* initialize function pointers for object <s> */
s­>init1   = cstudent_init1;
s­>init2   = cstudent_init2;
s­>edit    = cstudent_edit;
s­>display = cstudent_display;
s­>average = cstudent_average;
}

Code to test the C Student class (STEST.C) 

#include "cstudent.h"
#include <stdio.h>   
void main()
{
Student S1,*s1=&S1;
StudentInstance(s1);           /* Set up <S1> for operations */
(*S1.init1)(s1);     
  
/* Invoke Default constructor */
(*S1.edit)(s1,"Joe",56,78);    /* S1.edit()                  */
(*S1.display)(s1);
  
/* S1.display()               */
printf("%s's average is %d\n",
S1.name,(*S1.average)(s1)); /* S1.average()              */
/* Note: in C, it is not possible to protect
S1.name from possible outside corruption */
}

Output

Joe,56,78          
Joe's average is 67

Programming operators

C++ provides the opportunity to create operators on classes.
These work in the same way as member functions, yet are more powerful syntactically to users of 
your class.

The following operators are programmable:

+ ­ * / % ^ & | ~ ! , = < > <= >= ++ ­­ << >> == !=
&& || += ­= /= %= ^= &= |= *= <<= >>= [] () ­> ­>*
new delete 

Operator member function syntax

The syntax for coding a member function is:

// .H file
class <Class name>
{
...
<return type> operator<operatorName>(<arg list>); // prototype
...
};

// .CPP file
<return type> <Class name>::operator<operatorName>(<arg list>)
{
... function body
}
// TEST.CPP for invoking the operator
...
<Class name> a,b;
result = a <operatorName> b;   // same as   a.funcName(b);

Defining an operator

Suppose a company records the number of shares each employee has in a database with the 
following records data:

EmployeeStock
|­> departmentID
|­> employeeID
|­> numberOfShares

They may decide to get statistics on employees from various departments, namely, what is the 
total number of shares that employees in department X have?

We could define how to add two employee stocks together: by defining the + (add) operator on 
two objects of EmployeeStock to return an Employee Stock object containing the sum of 
numberOfShares.

ESTOCK.H

#ifndef EMPLOYEESTOCKH
#define EMPLOYEESTOCKH
class EmployeeStock
{
public:
EmployeeStock();
EmployeeStock(int,int,int);   
EmployeeStock operator+(EmployeeStock&);
int EmployeeStock::getNShares();
private:
int deptID,employeeID,nShares;
};
#endif

ESTOCK.CPP

#include "estock.h"
// Constructors can initialize data members via an : initializer list
EmployeeStock::EmployeeStock() : deptID(0),employeeID(0),nShares(0)
{}
 
EmployeeStock::EmployeeStock(int _deptID,int _employeeID,int _nShares)
: deptID(_deptID),employeeID(_employeeID),nShares(_nShares)
{}
EmployeeStock EmployeeStock::operator+(EmployeeStock &es)
{
EmployeeStock result;
result.nShares = nShares + es.nShares;
return result;
}
int EmployeeStock::getNShares() { return nShares; }

ESTEST.CPP

#include "estock.h"
#include <iostream.h>
void main()

// Simulate two employees from department 54
//  with 100 and 10 shares respectively
EmployeeStock e1(54,12,100),e2(54,11,10),result;
result = e1 + e2; // invoke operator+() to 
cout << "Dept 54's total stock is " << result.getNShares() << endl;
}

Output
Dept 54's total stock is 110

Overloading functions/operators

Sometimes we may want to code multiple instances of a function.  C++ allows functions to have 
several forms.  We have already seen this with constructors.  Rarely do we have only one way to 
construct an object.  Sometimes we may wish to set none of the data, sometimes portions, 
sometimes all.
Let's define how to compare EmployeeStock, say by comparing their nShares fields.
We can overload the < operator to handle two types of comparisons via two operator<() 
functions:

EmployeeStock a(..),b(..)
if (a < b) // if a's nShares is less than b's
...
if (b < 60)
// if b's nShares is less than 60 
...

int EmployeeStock::operator<(const EmployeeStock &e)  // const promises that
{                                                     // the function will not
return (nShares < e.nShares);                 // change the parameter e
}
int EmployeeStock::operator<(int _nShares)
{
     return (nShares < _nShares);
}

Note that the above two functions vary in signature, namely in their argument list part.  One 
takes an EmployeeStock object the other an integer.  As long as the compiler has no trouble 
distinguishing between function instances, there are no resolution problems.  Two identical 
signatures result in a "multiply defined" compiler error.
Note that return types do not count as part of the change in signature, so void f(int a); and int  
f(int a); are not allowed.

Default parameters

Often, especially in long parameter lists, it is nice to provide default arguments, to give the class 
user extra convenience.
Consider an Inventory Record with four fields:

InventoryRecord
|­> description  : string
|­> # of units   : integer
|­> distributor  : string
|­> targetMarket : integer

In the header file, we can define default arguments for the distributor and targetMarket fields 
because let's say 90% of the time they will be "ACE Limited" and CANADA respectively.

enum {CANADA,USA,EUROPE,WORLDWIDE}; 
class InventoryRecord
{   
private:
char *description;
int nUnits;
    
char *distributor;
    
int targetMarket;
public:
InventoryRecord(char *_description,int _nUnits,
char *_distributor = "ACE Limited",
int _targetMarket = CANADA);
};
...
InventoryRecord::InventoryRecord(char *_description,int _nUnits,
char *_distributor,int _targetMarket)
: description(_description),nUnits(_nUnits),
distributor(_distributor),targetMarket(_targetMarket)
{}
...
void main()

{
InventoryRecord ir1("Skates",400);
// defaults <distributor>,<targetMarket> fields
InventoryRecord ir2("Skiis",110,"Lowdry Inc.");  // defaults <distributor>
InventoryRecord ir3("Skiis",110,"Venture Enterprises",EUROPE); // overrides defaults
...
}

Note the order of default parameters is important.  If we wish to let the distributor default, then 
also we must let the targetMarket default.  The following is illegal:

InventoryRecord ir4("Gloves",25,,WORLDWIDE);

Protecting data 

We can protect internal data from class users by declaring a class's data members as private, but 
that does not protect the data from accidental misuse by the class members themselves.
The compiler can catch unwanted writes to data members if the class designer tags a const 
keyword after the prototype:

class EmployeeStock
{
private:
int deptID; ...
...
int getNShares() const;  // function promises not to modify <deptId> or other members
};
int EmployeeStock::getNShares() const
{
deptID = 6;
// illegal access, compiler error
return nShares;
}

When creating a member function we should decide whether a function changes internal 
members or not.  If it doesn't then supply the const keyword.
This is good practice for three reasons:
(1) To catch first­round implementation errors
(2) For increased readability to class users scanning the .H file
(3) To make upgraders of your class (or meddlers) think twice about altering a member function 
to modify data members when the original definition was read­only.  There must have been some 
reason.

Part I Exercises

(1) Why is it better to make class data members private instead of public?

(2) Design a vector class to store x,y components as real numbers.  Define the operators +,­,
==,<,>,<=,>= and * as the dot product of two vectors.  Recall the dot product definition

v1 = (x1,y1), v2 = (x2,y2)
v1 * v2 = (x1*x2,y1*y2)

Create a function to return the unit vector perpendicular to a vector.  

Vector& normal();

Provide a mainline to test your class.  Document your class.

(3) Revise the student class to support the following definition:

Student
|­> name
|­> phone#
|­> list of marks

Write functions average() to compute the average of <list of marks> and median() to return the 
"middle" mark in the list.
(4) Use the EmployeeStock definition to create a simulation of a company of employees holding 
shares.  Allow the user to type in various employees from different departments.  Your program 
stores EmployeeStock objects in an array.

Allow the user to enter a departmentID and the program tallies shares totals for employees of that 
department.
If you want to make your program more user­friendly, encode a lookup table of department 
names.  This way the user can type in a department name instead of an ID.
Use the + operator to create your total.

Copy constructor

The default copy scenario is a bitwise­copy.  All the members from one object are copied 
directly to the corresponding members of another object.
 
All classes in C++ support a default copy constructor.  No code is required.
A copy of an object can be created in three possible ways in C++.  Two are via a construction:

EmployeeStock e1,e2=e1,e3(e2);   // e2 is an exact copy of e1,
                                 // e3 is an exact copy of e2

The third is an indirect method when an object is passed into a function.  In the following 
example, e is not passed by reference which informs the compiler to create a local copy of the 
incoming object and name it e:

char* lookupEmployee(EmployeeStock e)
{
char *employeeName = employeeTable[e.id];
return (employeeName);
}
...
EmployeeStock es(45,13,12);
char *name = lookupEmployee(es);

Tip

It is better to pass e by reference to avoid the bitwise
copy operation and save time.

When to supply a copy constructor

There are times when we have to write code to override the default copying mechanism. 
Particularly This occurs when an object has a complex makeup.

Suppose we wanted to tag stock options onto an EmployeeStock.  We could add an options 
member acting as an array.  Over the lifetime of the object, options can be added.

Option
|­> dateOfIssue  : 
|­> optionType   : integer

EmployeeStock
|­> employeeId
|­> departmentId
|­> nShares
|­> options         : array of options
|­> nOptions        : total # of options
­­­­­­­­­­­­­­­­­­­
|­> addOption()

Suppose that heap Option objects are added to the array options by a call to addOption(). 
Resorting to the default bitwise copying method results in a copy from a source object 

SOURCE
 ­­­­­­­­­­­­­­
| departmentID : 55
| employeeID   : 32    0     1       2           MAX_OPTIONS
| nShares      : 14   ­­­­­­­­­­­­­­­­­­­­­­       ­­­­­
| options      :­­­> |     |      |       |   ... |     |
                 |   ­­­­­­­­­­­­­­­­­­­­­­       ­­­­­
                 |      |      |        |
                 |    ­­­­­­   ­­­­­­   ­­­­­­­
                 |   |05|97 | |06|97 | |12|97  |
                 |   |SINGLE| |DOUBLE| |TRIPLE |
                 |    ­­­­­­   ­­­­­­   ­­­­­­­
                 |
                  ­­­­   
COPY                  |
 ­­­­­­­­­­­­­        |
| departmentID : 55   |
| employeeID   : 32   |
| nShares      : 14   |
| options      : ­­>­­
 ­­­­­­­­­­­­­­

If we delete an option from copy, then we also delete an option from source.   Definitely not 
desirable.  COPY should have its own copy of the three pieces of option data.
  
To enact this, we must create our own copy routine that make copies of each of the elements in 
the options array.

Copy constructor syntax:

<Class Name>::<Class Name>(const <Class Name> & source) 
{
// code to copy source's members to this object
}

Assignment operator

Suppose we want to assign one object's data to another:

EmployeeStock e1(...),e2,e3;
e2 = e1;  // This does not call copy constructor!
e3 = (e2 = e1);   // chained assignment e2 <­ e1 then e3 <­ e2

Again, if a bitwise assignment of members is undesirable, we will have to overload the 
assignment operator to provide a well­defined assignment, code similar to our copy constructor 
code.

Assignment operator syntax:

<ClassName>& <Class Name>::<Class Name>(const <Class Name> & source) 
{
// code to assign source's members to this object
}

Using the this pointer

To write the chained assignment operator we have to return the result of the current assignment 
being done.  This requires use of the this pointer.
This refers to the object "being worked on" or the object bound to the operation:

          Object X begin worked on
this ­> ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
 | departmentID : 55                          |
 | employeeID   : 32                          |
 | nShares      : 14    ­­­­­­­­­       ­­­   |
 | options      : ­­>  |   |    | .... |   |  |
 |                      ­­­­­­­­­       ­­­   |
 |                      ||   ||               |
 |                      \/   \/               |
 |                      ...  ...              |
        ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

When the computer executes the statement a = b, the object bound to the operation is a, the 
operation is =, and the argument is b.

The completed assignment operator:

EmployeeStock& EmployeeStock::operator=(const EmployeeStock &e)
{
doCopy(e);     // Execute code to do the assignment
return *this;  // Because this is a pointer to the object
 
// being worked on, we have to dereference it
// to pass the actual object

}

Destructor

When an object goes out of scope, a destruction process occurs deallocating all class members, 
so the memory can be reused for other objects.
If the construction of class members is simple, like ints, floats, and char buffer types, we can rely 
on the compiler to free them up.  This is because the compiler knows the exact size of the data at 
compile time.
But if with the new command we allocate any embedded objects or structures over the lifetime of 
the object, we have to write a function responsible enough to deallocate or wrap up the extra 
allocation done.
Such a function is deemed a destructor.  As a general rule, if we have provided copy constructor 
and/or assignment operators, we must provide a destructor.  If we don't provide the destructor, we 
create a memory leak in the system and our program hogs memory that should be given up to the 
system, possibly resulting in an out of memory run­time error.

Destructor syntax:

<Class Name>::~<Class Name>() 
{
// code to wrap up this object
}

Static class members

Sometimes we want to have variables common to all objects of a particular class.  We can do this 
by prefixing them with the static keyword.
This is useful in our Option class when it comes time to display the type field.  Displaying 1, 2 or 
3 is inferior to outputting SINGLE, DOUBLE, or TRIPLE to the user.  We can create a lookup 
table of type descriptions to use in conjunction with display().
The point is, we want only one copy of the array to exist in memory, not one for each object 
instance.
Sure, we could create a static array of type descriptions in our .CPP class code file, but for 
readability sake, it is nice to embed the array within the Option class:

const int NTYPES = 3;
class Option
{
private:
...
static char* types[NTYPES];  // Only one copy of the
      // array exists for all
      // objects 
};

Initialize static members using the scope operator:

<type> <ClassName>::<variable name> = <value>;

char* Option::types[] = {"SINGLE","DOUBLE","TRIPLE"};

Revised EmployeeStock code
 
ESTOCK.H

#ifndef EMPLOYEESTOCKH
#define EMPLOYEESTOCKH
#define MAX_OPTIONS 10
enum {SINGLE,DOUBLE,TRIPLE};
class Option
{                                                            
public:
int issueYear,issueMonth,type;
static char *types[3]; // fixed type descriptions for all Option objects
Option();
Option(int _issueYear,int _issueMonth,int issueType); 
void display();
};
class EmployeeStock
{
public:
EmployeeStock();
EmployeeStock(const EmployeeStock &e);  // Copy constructor
EmployeeStock(int,int,int);   
EmployeeStock  operator+(const EmployeeStock&);
EmployeeStock& operator=(const EmployeeStock&);
void doCopy(const EmployeeStock&);
int EmployeeStock::getNShares();
~EmployeeStock();
void addOption(int,int,int);
Option* getOption(int index);
private:
int deptID,employeeID,nShares;
Option **options;  
int nOptions;
};
#endif

Revised ESTOCK.CPP

#include "estock.h"
#include <iostream.h>
// Option class code
Option::Option() : issueYear(0),issueMonth(0),type(0)
{}
Option::Option(int _issueMonth,int _issueYear,int _type)
: issueMonth(_issueMonth),issueYear(_issueYear),type(_type)
{}
char *Option::types[] = {"SINGLE","DOUBLE","TRIPLE"};
void Option::display()
{
cout << issueMonth << "," << issueYear << "," << types[type] << endl;
}                                                                      
// EmployeeStock class code
EmployeeStock::EmployeeStock() : deptID(0),employeeID(0),nShares(0),
options(0),nOptions(0)
{}
 
EmployeeStock::EmployeeStock(int _deptID,int _employeeID,int _nShares)
: deptID(_deptID),employeeID(_employeeID),nShares(_nShares),
options(0),nOptions(0)
{}
void EmployeeStock::addOption(int _issueMonth,int _issueYear,int _type)
{
if (!options)
options = new Option*[MAX_OPTIONS];
if (nOptions < MAX_OPTIONS)
options[nOptions++] = new Option(_issueMonth,_issueYear,_type);
}
Option* EmployeeStock::getOption(int index)
{
if ((index < 0) || (index >= MAX_OPTIONS))
return 0;
else

return options[index];
}                         
// Copy constructor
 
EmployeeStock::EmployeeStock(const EmployeeStock &e)
{
doCopy(e);
}

// Assignment operator
EmployeeStock& EmployeeStock::operator=(const EmployeeStock &e)
{
doCopy(e);
return *this;
}

void EmployeeStock::doCopy(const EmployeeStock &e)
{
if (!e.options)
options = 0;
else
{
options = new Option*[MAX_OPTIONS];
for (int i = 0; i < e.nOptions; i++)
{
options[i] = new Option(*e.options[i]);
}                                      
nOptions = e.nOptions;
}
}
// Destructor
EmployeeStock::~EmployeeStock()
{
if (options)
{
for (int i = 0; i < nOptions; i++)
delete options[i];
delete options;
}
}
EmployeeStock EmployeeStock::operator+(const EmployeeStock &es)
{
EmployeeStock result;
result.nShares = nShares + es.nShares;
return result;
}
int EmployeeStock::getNShares() { return nShares; }

Test code (ESTEST.CPP)

#include "estock.h"
#include <iostream.h>
void main()
{
EmployeeStock e22(56,32,900);
e22.addOption(5,97,SINGLE);
e22.addOption(6,97,DOUBLE);
e22.addOption(12,97,TRIPLE);
EmployeeStock e23(e22);
cout << "E23's 2nd option type is: " << endl;
e22.getOption(1)­>display();
EmployeeStock e24,e25;
e25 = (e24 = e23);
cout << "E24's 3rd option type is: " << endl;
e24.getOption(2)­>display();
cout << "E25's 1st option type is: " << endl;
e25.getOption(0)­>display();
}

Output

E23's 2nd option type is:    
6,97,DOUBLE                  
E24's 3rd option type is:    
12,97,TRIPLE                 
E25's 1st option type is:    
5,97,SINGLE                  

Friend functions

It is possible to allow functions outside of a class to have read/write access to a class's private 
variables.  The class designer must give authorization for such access.
Such functions are deemed friend functions.  The label "friend" is given with the meaning that 
such a function is friendly to a class, promising not to corrupt its internal variables.
Some may argue that such priviledges betray the object­oriented philosophy of encapsulation, but 
we will see that there is a special need for friend functions in at least one common case in the 
C++ language.
Let's study an example that allows a C function verifyAge() to modify the private variable 
inTheInClub of the Person class.
As you can see from the output only Gretel and Geronimo are part of the "in club" because they 
are between the ages of 25 and 35.

FRIENDEX.CPP

#include <iostream.h>
#include <string.h>
const int TRUE = 1,FALSE = 0;
class Person
{
private:
int age;
char name[80];
        int inTheInClub;
public:
Person(char*,int);
void display();
friend void verifyAge(Person&);  // The class designer gives permission
                                 // for the function verifyAge() to
                                 // read/write to the private variables
};
Person::Person(char *_name,int _age) : age(_age),inTheInClub(FALSE)
{
strcpy(name,_name);
}
void Person::display()
{
cout << name << "," << age << "," << inTheInClub << endl;
}
static void verifyAge(Person &p)
{
if ((p.age <= 35) && (p.age >= 25))
p.inTheInClub = TRUE;
else
p.inTheInClub = FALSE;
}
void main()
{
Person p1("Harry",56),p2("Gretel",34),p3("Vina",16),p4("Geronimo",29);
verifyAge(p1);         // decide who can be in the in­club
verifyAge(p2);
verifyAge(p3);
verifyAge(p4);

p1.display();         // display the results
p2.display();
p3.display();
p4.display();
}

Output

Harry,56,0       
Gretel,34,1      
Vina,16,0        
Geronimo,29,1    

Friend classes

C++ goes a step further and allows whole classes to be friends of another class.  This means to 
say that if class B is a friend of A then all the member functions of B can access the private 
members of A.
The friend class relationship should only occur if there is by definition a strong link between 
classes A and B, that is that B has its fingers in A a great amount of time and that B's fingers are 
trustworthy.

        A                        B
       ­­­­­­­­­­­              ­­­­­­­­­­­
      | private:  |            | public:   |
      |   x,y,z  <==========   |  f1()     |
       ­­­­­­­­­­­     /\      |  f2()     |
                       ||      |  f3()     |
                       ||      |   ...     |
                       ||       ­­­­­­­­­­­
                   Strong link

The number of friend class declarations should be kept in check because it can weaken the 
definition of OO encapsulation.
This feature is great for testing or prototyping a system without having rigorous protection 
mechanisms and accessor/mutator methods in place.  Once the code has been hammer­tested 
with "loose" friend class relationship(s), it can be tightened by eliminating friend classes and 
shipped for protection.
For example, suppose we have one class A that is linked heavily to another class B.  Class A 
wishes to give permission to class B to access its private members but no one else!

class A
{
friend class B;   // A gives permission for B's member functions

                         // to access A's private variables 
private:           
int x,y,z;
...
};
class B
{
public:
void f1(A &a)
{
a.z = 56; // this is ok
}
...
};

Providing streamable support

The last program was a weak "real­life" example of the use of friend functions.  We could have 
just made verifyAge() a member function of the Person class and avoided the whole "friend" 
fiasco!  When do we really need friends?
Suppose we want to use the cout and cin objects to display Persons and input them from the 
keyboard.
It would be nice to code:

Person p;
cin >> p; // ask the user to input Person data
cout << p; // display person p's input data

In order for the cin and cout objects to do their job, they must have access to the private members 
of p.  The only one who can do this is the class designer of Person, and that is us.
Specifically, we must supply the functionality to do so by overloading the << and >> 
streamable operators for the cout and cin objects taking a Person parameter as input.  We must 
label such functions as friends.

Programming the display function

cin  is an instance of the class istream.
cout is an instance of the class ostream.
 
The syntax for the input and display functions is somewhat cryptic.  They can be better 
understood by understanding what is the bound object, the operation, and the input parameter in 
the usage of cin and cout to perform the i/o.

Bound object   operation     parameter
    cout         <<            p

        Return      Function     Bound    Input
argument     name        object   parameter
         ||          ||           ||         ||
         \/          \/           \/         \/
friend ostream& operator<<(ostream &o,Person &p)
{
// Do the code to pipe to object o (cout) the details
// of Person p...
// Return o so that chained cout << p1 << p2 << ...
// can occur
...
return(o);

Programming the input function

Bound object   operation     parameter
    cin          >>            p

        Return      Function     Bound    Input
argument     name        object   parameter
         ||          ||           ||         ||
         \/          \/           \/         \/
friend istream& operator>>(istream &i,Person &p)
{
// Do the code to allow object i (cin) to input
// Person p's details from the keyboard
// Return i so that chained cin >> p1 >> p2 >> ...
// can occur
...
return(i);

Revised Person class code (FRIENDX2.CPP)

#include <iostream.h>
#include <string.h>
const int TRUE = 1,FALSE = 0;
class Person
{
private:
int age;
char name[80];
        int inTheInClub;
public:             
Person();
Person(char*,int);
friend ostream& operator<<(ostream&,Person&); // prototype for display function
friend istream& operator>>(istream&,Person&); // prototype for input   function
};
Person::Person() : age(0),inTheInClub(FALSE)
{
name[0] = 0;
}
Person::Person(char *_name,int _age) : age(_age),inTheInClub(FALSE)
{
strcpy(name,_name);
}
ostream& operator<<(ostream &o,Person &p)
{
o << p.name << "," << p.age << "," << p.inTheInClub;
return o;
}
istream& operator>>(istream &i,Person &p)
{
i >> p.name >> p.age;
return i;
}
void main()
{
Person p;

cout << "Enter Person p's <name> <age>: ";
cin >> p;
cout << p << endl;
}

Output

Enter Person p's <name> <age>: Craig 89   
Craig,89,0                               

File input/output using streams

Reading and writing to disk files works the same way as the standard streaming using cin and 
cout.
The ofstream class handles output to files, the ifstream class input from files.

Sample usage:

// Program to create an ascii file consisting of personal data
// and read it back in
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
const char *TEST_FILE = "C:\\TEST.DAT";
void main()
{
ofstream o;
o.open(TEST_FILE);
// Careful!, overwrites <TEST_FILE>
if (!o)
{                            
// Incorrect path, write protect or drive door open or
// other error
cout << "Can't open " << TEST_FILE << " for output" << endl;
exit(0);
}
       
o << "Joe " << 56 << endl; // pipe some data to output file <o>
o.close();                  
// Reread the same file
ifstream i;
i.open(TEST_FILE);
if (!i)
{                            

cout << "Can't open " << TEST_FILE << " for input" << endl;
exit(0);
}
       
char name[80];
int age;
i >> name >> age;
i.close();
cout << name << "," << age;
}

TEST.DAT

Joe 56

Writing class file i/o support

If we wanted to write specific functions, it makes sense to embed them (or make them part of) 
the class we are trying to stream, or pipe to/from a file.
Again we can create friend equivalents of operator<< and operator>> functions for the 
ofstream and ifstream classes.

Sample code:

#include <iostream.h>
#include <fstream.h>
...
class Person
{
public:             
...
 
  friend ofstream& operator<<(ofstream&,Person&); // prototype for file output function
  friend ifstream& operator>>(ifstream&,Person&); // prototype for file input   function
};
...
// File input/output operations
ifstream& operator>>(ifstream &i,Person &p)
{
i >> p.name >> p.age >> p.inTheInClub;
return i;
}
ofstream& operator<<(ofstream &o,Person &p)
{                        
// Important to write out spaces as delimiters not commas!
o << p.name << " " << p.age << " " << p.inTheInClub;
return o;

}

const char *TEST_FILE = "C:\\TEST.DAT";
void main()
{
// Write out some Persons to <TEST_FILE>
ofstream o;
o.open(TEST_FILE);
if (!o)
{                            
cout << "Can't open " << TEST_FILE << " for output" << endl;
exit(0);
}
       
Person p1("Janie",23),p2("Jeff",24),p3("Granny",21);
o << p1 << endl;
o << p2 << endl;
o << p3 << endl;
o.close();
// Read back all persons in <TEST_FILE> and display to terminal
Person tempPerson;
ifstream i;
i.open(TEST_FILE);
if (!i)
{                            
cout << "Can't open " << TEST_FILE << " for input" << endl;
exit(0);
}
i >> tempPerson;
while (!i.eof())
{
cout << tempPerson << endl;
i >> tempPerson;
}
       
i.close();
}

TEST.DAT

Janie 23 0  
Jeff 24 0   
Granny 21 0 

Inline functions

When we want to optimize our code for speed, we can make our member functions inline.  This 
means that whenever a function call is made, the compiler does not generate a native JSR 
instruction (jump to subroutine) and an expensive copy of arguments onto the stack.  These can 
be too time consuming for our tastes.  The actual function code is substituted or embedded inline.
We should not overuse the inline capability as it can make the executable large.  Remember that 
performance can really only be increased by increasing the speed of commonly used functions.
Deciding upon whether an function should be inline may require some time­analysis study of 
many functions to determine those (if any) that cumulatively eat­up an appreciable percentage of 
overall run time.  
Class accessors and mutators are generally coded inline as they are generally small one­liners 
that don't cost us anything in terms of executable size.

A function is automatically loaded inline if the codification appears inside the class header file:
 

class Person
{
public:
// automatic inline functions
int getInClubStatus() { return inTheInClub; }
const char* getName() { return name; }
int getAge()          { return age; }
}; 

Making an out-line function inline

We can also force out­line functions (those in the .CPP file) to be inline by prefixing the inline 
keyword in the function prototype.

// Another inline demonstration
// ..a function that converts last name, first name delimiters
//   from white space to underscore characters.
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
...
class Person
{
public:             
...
inline void checkForNameSpaces();
};
...
void Person::checkForNameSpaces()
{                                      
int len = strlen(name);
for (int i = 0; i < len; i++) 
if (isspace(name[i]))
name[i] = '_';
}
               
void main()    
{              
Person p("Jody Charon",40);
p.checkForNameSpaces(); // <== The above code is substitued here
cout << p << endl;
}

Output

Jody_Charon,40,0

Part II Exercises

(1) Design a ComplexNumber class.  Recall the definition and operations on complex numbers:
Let c be a complex number, then

 

  c = a + bi,  i = square root of(­1), a,b elements of R
If C1 and C2 are complex numbers,
  C1 = a1 + b1i, C2 = a2 + b2i then

     C1 + C2 = (a1 + a2) + (b1 + b2)i 
     C1 ­ C2 = (a1 ­ a2) + (b1 + b2)i
     C1 * C2 = (a1*a2 ­ b1*b2) + (a1*b2 + b1*a2)i
C1 / C2 = (a1*a2 + b1*b2)/d + (b1*a2 ­ a1*b2)i/d
                  where d = a22 + b22
. Provide default and full constructors for your class
. Overload the +,­,*,/ operators
. Allow chained operations like the following:
Complex c1(4.1,5.7),c2(2.0,­9.8),c3 = c1 + c1 + c2*2;
. Allow mixed operations of complex numbers with a real:
Complex c1(­4.0,5.6),c3 = c1+2.1 + c1*2.2 + c1/2.1 + c1­2.0
. Allow complex numbers to be piped to and from standard input
  and file streams
. Test your class

(2) If the class designer supplies a copy constructor is it necessary to provide an assignment 

operator?  When is it necessary to code a destructor?

(3) Design a Dstring class which provides dynamic string management.  A dynamic string can 
grow or shrink through the operations provided on the class.

 Dstring s1("abc"),s2("def),s3(s1),s4;
 s3 = s1 + s2      : concatenates s1 to s2 and returns a result
 s3 = s1.mid(4,6); : extracts 6 characters from s1 starting from the 5th character
 s3 = s1.left(4);  : extracts the leftmost 4 characters of s1
 s3 = s1.right(2); : extracts the rightmost 2 characters of s3
 cout << s1[2];    : extracts the 3rd character of sd
 s4 = s2 = s1      : assigns s4 = s1 and s3 = s2

 
 
. You will need to a code constructor(s), a copy constructor, an assignment operator, and a 
destructor.
. Allow complex numbers to be piped to/from standard input
  and file streams
. Test your class

(4) Design an Input class, one that allows "tokens" to be read from standard input or a file.  A 
token is a series of characters delimited by white space, (or a user­defined delimiter).
Here is some sample code which describes the usage of your class: 
// Usage 1
Input i1(",");   // define a keyboard input source that sees commas as delimiters 
Dstring firstName,lastName;
cout << "Enter your first name,last name" << endl;
i1 >> firstName >> lastName;
cout << lastName << "," << firstName << endl;
...
// Usage 2
const char *INPUT_FILE = "test.dat"; 
ifstream inputFile(INPUT_FILE);
if (!inputFile) exit(0);
Input i2(inputFile); // define a file input source that defaults to white space as delimiters

Dstring token;
i2 >> token;
while (!i2)
// write out each token in <inputFile> until the end of file
{
cout << token << ",";
i2 >> token;
}
...

(5) Users of the Student class wish you as its designer to
extend its capabilites.  Recording a series of marks and calculating the average mark is not 
enough details on a student's performance.  They wish to record a series of subject descriptions 
and corresponding grades for each subject
and have the class calculate the average internally.  As well,
the class should describe the final state of a student's
performance based on the overall average.  The possibilities are:
     0 ­  49  Failure
     50 ­ 55  Academic probation
     56 ­ 65  Pass
     66 ­ 75  Median
     76 ­ 89  Honours
     90 ­ 100 First class honours
Students may take up to ten courses each semester.  Study the
following theoretical output of a student's data.
  Martha jones  Age: 18  Overall avg: 82%  Bottom line: honours
       Physics   82
       English   77
       Geography 91
       Latin     82
Revise the Student class to support this functionality.  Decide upon how a Student object should 
be constructed.  Suggested:

Student::Student(name,age);
Student::addASubject(description,mark);

Deriving classes

Once we have designed a class, we can derive another class from it to create a composite or 
subclass, something similar but with additional data and/or functionality.  A composite describes 
an "IS A" relation with the source or base class.
For example, we already have a Student class defined.  A university student IS A student but with 
some extra fields: nYears, typeOfProgram (honours/general), fieldOfStudy (English, 
Mathematics,...), tuition, and lastYearsAverage: 

 ­­­­­­­­­­          ­­­­­­­­­­­­­­­­­­­
| Student  |        | UniversityStudent |
|  name    |  <­­­­ |  year             |
|  marks[] |        |  typeOfProgram    |
 ­­­­­­­­­­         |  fieldOfStudy     |
                    |  tuition          |
                    |  lastYearsAverage |
                     ­­­­­­­­­­­­­­­­­­­

A UniversityStudent IS A  Student.
A UniversityStudent HAS A year,typeOfProgram,fieldOfStudy,...

General derivation syntax:

The C++ syntax for creating a derived class is:

class CompositeClass :  <scope>  BaseClass
{ // ...definition };

where there are three <scope> possibilities : public, private, or protected :

public, (most commonly used), means public members of base class become public members of 
derived class and protected members of the base class becom protected members of the derived 
class.

The other two scopes are rarely used; the IS A condition is lost:

protected means public and protected members of base class become protected members of the 
derived class.
private means public and protected members of the base class become private members of the 
derived class.

* Class members declared as protected like private, remain hidden to outside users of the class 
with the exception of class designers who derive from the class.

Deriving UniversityStudent from Student

We can create a special UniversityStudent instance description of Student which maintains all the 
data and functionality at the Student level by coding:

class UniversityStudent : public Student
{
private:
<UniversityStudent fields>
public:
<universityStudent user functions>
};

How is the base class Student part initialized?

A/ Via UniversityStudent() constructors.  They can propogate fields down into the Student class 
by calling a Student constructor in the initializer list:

UniversityStudent(...) : Student(...),... {}

OR by simply omitting a Student(..) constructor and let the compiler call the default Student() 
constructor.  Of course this is only possible if a default Student() constructor exists:

UniversityStudent(...) : ... {}

Revised Student class (STUDENT2.H)

#ifndef STUDENT2H
#define STUDENT2H
#include <iostream.h>
#include <string.h>
const int MAX_STUDENT_NAME_CHARS = 40;
const int MAX_STUDENT_MARKS      = 20;
const int NPROGRAMS              = 2;
class Student
{
private:
char name[MAX_STUDENT_NAME_CHARS];
int marks[MAX_STUDENT_MARKS];
int nMarks;
public:
Student(); 
Student(char*,int*,int);
void edit(char *,int*,int);
int average();
const char* getName();
const int*  getMarks();
friend ostream& operator<<(ostream& o,Student&);
};
#endif

STUDENT2.CPP

#include "student2.h"
#include <string.h>
#include <iostream.h>
#define min(a,b) (a < b ? a : b)
Student::Student()
{
name[0] = NULL;
nMarks = 0;
}
Student::Student(char *_name,int *_marks,int _nMarks)
{
edit(_name,_marks,_nMarks);
}
void Student::edit(char *_name,int *_marks,int _nMarks)
{
strncpy(name,_name,min(strlen(_name)+1,MAX_STUDENT_NAME_CHARS));
for (int i = 0; i < _nMarks; i++)
marks[i] = _marks[i];
nMarks = _nMarks;
}
ostream& operator<<(ostream& o,Student& s)
{
o << "Name  " << s.name << endl; 
o << "Marks ";
for (int i = 0; i < s.nMarks; i++)
o << s.marks[i] << " ";
return o; 
}
const char* Student::getName()
{
return name;
}
const int* Student::getMarks()
{
return marks;
}

int Student::average()
{                       
long int total = 0;
for (int i = 0; i < nMarks; i++)
total += marks[i];
return (total/nMarks);
}

UniversityStudent definition (USTUDENT.H)

#ifndef USTUDENTH
#define USTUDENTH
#include "student2.h"
enum {FIRST_YEAR=1,SECOND_YEAR,THIRD_YEAR,FOURTH_YEAR};
enum {HONOURS,GENERAL};
#define MAX_DESC_CHARS                30
#define STANDARD_LAST_YEARS_WEIGHTING 0.3
class UniversityStudent : public Student
{
private:
int year;
int typeOfProgram;
char fieldOfStudy[MAX_DESC_CHARS];
int tuition;
int lastYearsAverage;
static char *programs[NPROGRAMS];
public:           
UniversityStudent();
UniversityStudent(char*,int*,int,
int,int,char*,int,int);
friend ostream& operator<<(ostream&,UniversityStudent&);
int average(float lastYearsWeight = STANDARD_LAST_YEARS_WEIGHTING);
};
#endif         

USTUDENT.CPP

#include "ustudent.h"
// automatically calls default constructor for Student
UniversityStudent::UniversityStudent()
: year(FIRST_YEAR),typeOfProgram(GENERAL),tuition(2500),lastYearsAverage(0)
{
strcpy(fieldOfStudy,"unknown");
}
                              
// In this constructor, we explicitly invoke the base class Student(..) constructor
UniversityStudent::UniversityStudent(char *_name,int *_marks,int _nMarks,
int _year,int _typeOfProgram,char *_fieldOfStudy,int _tuition,int _lastYearsAverage)
: Student(_name,_marks,_nMarks),
  year(_year),typeOfProgram(_typeOfProgram),tuition(_tuition),
  lastYearsAverage(_lastYearsAverage)
{
strcpy(fieldOfStudy,_fieldOfStudy);
}
                            
// Compute a weighted average of lastYearsAverage
// Use a default weighted average of <lastYearsWeight>
//  in favour of <lastYearsAverage>
int UniversityStudent::average(float lastYearsWeight)
{
int currentAverage = Student::average();
float overallAverage = (1­lastYearsWeight)*(float)currentAverage
+ lastYearsWeight*(float)lastYearsAverage;
int result = (int)overallAverage;
return (result); 
}
ostream& operator<<(ostream& o,UniversityStudent &u)
{
// To display the Student part of u, we must cast u to an object of type Student
// and invoke operator(ostream&,Student&) 
o << *(Student*)&u << endl;             
// Display <u>'s particulars
o << "Year " << u.year
<< "," << u.programs[u.typeOfProgram]

<< "," << u.fieldOfStudy
<< "," << "Tuition " << u.tuition
<< "," << "last years avg " << u.lastYearsAverage;
return o;
}
char* UniversityStudent::programs[] = {"Honours","General"};

UTEST.CPP

#include <iostream.h>
#include "ustudent.h"
void main()
{              
int kellysMarks[] = {56,78,65,67,77};
UniversityStudent s1("Kelly",kellysMarks,5,
2,HONOURS,"Accounting",2200,50);
cout << s1 << endl;
cout << s1.getName() << "'s weighted Average(default) =" << s1.average() << endl;
cout << s1.getName() << "'s weighted Average(20%,80%) =" << s1.average(0.2) << endl;
}

Output

Name  Kelly                                              
Marks 56 78 65 67 77                                     
Year 2,Honours,Accounting,Tuition 2200,last years avg 50 
Kelly's weighted Average(default) =62                    
Kelly's weighted Average(20%,80%) =64                    

Dynamic binding

If we have a bunch of Students and UniversityStudents in a list, how can we process them without 
having to decode the type?  This is a good question, for if we execute the following code, we 
don't get the results we would have hoped for!

#include <iostream.h>
#include "ustudent.h"
const int NSTUDENTS = 2;
void main()
{
int mannysMarks[] = {88,90,52,55};
Student s1("Manny",mannysMarks,4);

int verasMarks[] = {70,71,85,88,87,86};
UniversityStudent u1("Vera",verasMarks,6,
3,GENERAL,"Biology",1900,90);
Student *sa[] = {&s1,&u1};
for (int i = 0; i < NSTUDENTS; i++)
{         
Student *s = sa[i];
cout << *s << endl;
cout << s­>getName()
     << "'s weighted Average(default) =" << s­>average()
     << endl << endl;
}
}

Output

Name  Manny                           
Marks 88 90 52 55                     
Manny's weighted Average(default) =71 
Name  Vera                            

Marks 70 71 85 88 87 86               
Vera's weighted Average(default) =81  

Note that Vera's average was calculated the simple "Student" way without a weighted average, 
even though she IS A university student!

Virtual functions

There are several problems with the last example.  Firstly, Students are all cast implicitly to 
Student *s so that when we call a member function of s, (namely average and operator<<), the 
default mechanism is to call the function at the exact object type level of s which is obviously 
Student.
We can override the default behaviour by prefixing functions with the virtual keyword.  This 
tells the compiler to get the exact type of the object at run time.  Then it calls the function at this 
level if it exists.  If not, is searches one derived level down working towards the base class to find 
a matching function.  Such action is called dynamic binding.

                      ­­­­­­­­­­­­­­­­­­­­­­
                     |       Student        |
                     |    virtual average() |
                      ­­­­­­­­­­­­­­­­­­­­­­
                         /           \
                        /             \     
           ­­­­­­­­­­­­­­­­­­­­    ­­­­­­­­­­­­­­­­­­­­­­­
          |  UniversityStudent |  | NightSchoolStudent    |
          |      average()     |  |                       |
           ­­­­­­­­­­­­­­­­­­­­    ­­­­­­­­­­­­­­­­­­­­­­­
             /                 \
            /                   \
       ­­­­­­­­­­­­­­­­­­­­­    ­­­­­­­­­­­­­­­­­­­
      | GraduateStudent     |  |  PHDStudent       |
      |     average()       |  |     average()     | 
       ­­­­­­­­­­­­­­­­­­­­­    ­­­­­­­­­­­­­­­­­­­

In the above derivation chart, all classes have there own specific average() calculation except a 
NightSchoolStudent who shares the average() calculation with a regular Student.

Also note that for virtual binding to work, the signatures of the common function must be exactly 
the same as declared in the base class.  For this reason we must remove the default parameter 
lastYearsWeight in UniversityStudent::average() for the dynamic binding to work.

A virtual display() function

We can't rely on the operator<< function to display virtually any type of student because only 
member functions can be declared virtual, and friend ostream& operator<<(..) is not a member 
function.

We have to write a virtual display() function to get the right output.  friend ostream& and 
display() can share the same code, we just have two interfaces now:

      ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
     | Student                             |
     |   virtual void display(ostream&);   |
     |   friend ostream& operator<<(..);   |
     |   ...                               |
      ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

      ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
     | UniversityStudent                   |
     |   virtual void display(ostream&);   |
     |   friend ostream& operator<<(..);   |
     |   ...                               |
      ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

Revised STUDENT2.H

#ifndef STUDENT2H
#define STUDENT2H
#include <iostream.h>
#include <string.h>
const int MAX_STUDENT_NAME_CHARS = 40;
const int MAX_STUDENT_MARKS      = 20;
const int NPROGRAMS              = 2;
class Student
{
protected:  // We make this protected so that derived class UniversityStudent
                   // can access the private members of Student
char name[MAX_STUDENT_NAME_CHARS];
int marks[MAX_STUDENT_MARKS];
int nMarks;
public:
Student(); 
Student(char*,int*,int);   
void edit(char *,int*,int);
virtual int average();
virtual void display(ostream&);
const char* getName();
const int*  getMarks();
friend ostream& operator<<(ostream& o,Student&);
};
#endif        

STUDENT2.CPP

...
ostream& operator<<(ostream& o,Student& s)
{
s.display(o);  
return (o);
}

void Student::display(ostream& o)
{
o << "Name  " << name << endl; 
o << "Marks ";
for (int i = 0; i < nMarks; i++)
o << marks[i] << " ";
}
int Student::average()
{                       
long int total = 0;
for (int i = 0; i < nMarks; i++)
total += marks[i];
return (total/nMarks);
}

Revised USTUDENTH

#ifndef USTUDENTH
#define USTUDENTH
#include "student2.h"
...
class UniversityStudent : public Student
{
private:
int year;
int typeOfProgram;
char fieldOfStudy[MAX_DESC_CHARS];
int tuition;
int lastYearsAverage;
static char *programs[NPROGRAMS];
public:
void display(ostream&);           
UniversityStudent();
UniversityStudent(char*,int*,int,
int,int,char*,int,int);
friend ostream& operator<<(ostream&,UniversityStudent&);
int average();
};
#endif         

Revised USTUDENT.CPP

ostream& operator<<(ostream& o,UniversityStudent &u)
{           
u.display(o);
return o;
}
void UniversityStudent::display(ostream &o)
{
//
o << *(Student*)this << endl;  // This causes a stack overflow
// because via virtual, it re­invokes
// UniversityStudent::display(ostream&)             
                             // To avoid repeating the Student::display() code,
                             // we have to create yet another version
                                    // of Student::display, call it Student::displayCoreFields()
                            // with no virtual keyword and call that from here!
                                
o << "Name:" << getName() << endl;
o << "Marks:" << endl;
for (int i = 0; i < nMarks; i++)
o << marks[i] << " ";
o << endl;
o << "Year " << year
<< "," << programs[typeOfProgram]
<< "," << fieldOfStudy
<< "," << "Tuition " << tuition
<< "," << "last years avg " << lastYearsAverage;
}
int UniversityStudent::average()
{    
float lastYearsWeight = STANDARD_LAST_YEARS_WEIGHTING;   
int currentAverage = Student::average();
float overallAverage=
(1­lastYearsWeight)*(float)currentAverage
+ lastYearsWeight*(float)lastYearsAverage;
int result = (int)overallAverage;
return (result); 
}

Test code (DBIND.CPP)

#include <iostream.h>
#include "ustudent.h"
// Simulate processing a family of Student objects loaded into an array
                                 
const int NSTUDENTS = 2;
void main()
{
int mannysMarks[] = {88,90,52,55};
Student s1("Manny",mannysMarks,4);

int verasMarks[] = {70,71,85,88,87,86};
UniversityStudent u1("Vera",verasMarks,6,
3,GENERAL,"Biology",1900,90);
Student *sa[] = {&s1,&u1};
for (int i = 0; i < NSTUDENTS; i++)
{         
Student *s = sa[i];
cout << *s << endl;
cout << s­>getName() << "'s weighted Average(default) ="
<< s­>average() << endl << endl;
}
}

Output

Name  Manny                                            
Marks 88 90 52 55                                      
Manny's weighted Average(default) =71                  
                                                      
Name:Vera         
Marks:            
70 71 85 88 87 86 
Year 3,General,Biology,Tuition 1900,last years avg 90  
Vera's weighted Average(default) =83                   

                                                      

A musical instrument derivation hierachy

 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
 
|     Musical Instrument            |
 ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
/                    |             \      
Woodwind                 Brass           String 
 |       \               /    \           /  \  
 |        \             /      \         /    \
Clarinet  Saxsophone  Trumpet Tuba   Guitar   Violin
                                    /      \
                                   /        \
                                Electric  Acoustic
                                   |          |
                                 Ibanez      Samick

Multiple inheritance

In more complex programs, we may wish to derive a class from more than one counterpart.  An 
AND condition describes multiple inheritance.  

Theoretically:

     ­­­­   ­­­
    | X  | | Y |
     ­­­­   ­­­ 
       \    /         Z is an X and a Y
        \  /
       ­­­­­­
      |  Z   |
       ­­­­­­

Practically:

    ­­­­­­­­­     ­­­­­­­­­           ­­­­­­­­­­
   | Student |   | Person  |         |  Person  |
    ­­­­­­­­­     ­­­­­­­­­           ­­­­­­­­­­
        \          /           OR         |
         \        /                       |
      ­­­­­­­­­­­­­­­­­­­             ­­­­­­­­­­­
     | UniversityStudent |           | Student   |
      ­­­­­­­­­­­­­­­­­­­             ­­­­­­­­­­­
                                          |
                                          |
                                     ­­­­­­­­­­­­­­­­­­­
                                    | UniversityStudent |
                                     ­­­­­­­­­­­­­­­­­­­

In both cases,  UniversityStudent is a Student and a Person
Actually, the second derivation chart may be a more realistic model since we have more concise 
definition of Student as Student being a Person.
Regardless, they are both examples of multiple inheritance.

Memory map of multiple inheritance

To create an AND derived relation in C++, we simple append the extra class(es) after the colon 
operator separated by commas.  We could code the first model as:

class UniversityStudent : public Student,Person
{ ... };

Suppose a Person has a birthDate and a countryOfBirth.  Then creating an object u of type 
UniversityStudent yields something like the following in memory:

    u
            ­­­­­­­­­­­­­­­­­­­
"Person"   |  birthDate        |
 part      |  countryOfBirth   |
           | ­ ­ ­ ­ ­ ­ ­ ­ ­ |
           |  name             |
"Student"  |  marks[]          |
 part      |                   |
            ­­­­­­­­­­­­­­­­­­­

All data and functional members of Student and Person are available at the UniversityStudent 
level.  We can write:

UniversityStudent u(...);
cout << u.getName() << u.average() << u.getBirthdate() << endl;

* Note if we opt for this design, we may decide to move the name field from the Student class to 
the Person class since it makes more sense group­wise to say that every Person has a name, 
birthdate and countryOfBirth.

Inheritance versus embedding

Another way to achieve the same results is to embed instances of our derived classes inside the 
subclass. 

class UniversityStudent
{
public:
Student s;   // Here, we are saying that a UniversityStudent
Person  p;   // has Student and Person counterparts
...
};

This works but the syntax is clumsier.  Suppose we want to access the Student or Person 
components of u, a UniversityStudent object,

UniversityStudent u(..);
cout << u.s.getName() << u.p.getBirthdate() << endl;

IS A is a stronger relation than HAS A, so if we recognize an IS A relation, we should not kludge 
it with HAS A condition(s).
 

Virtual base classes

Sometimes we may wish to avoid loading in a .  In some multiple inheritance schemas we may 
accidentally include two copies of a base class.  Consider a chess piece organization which 
derives queen properties from both the bishop and rook properties:

Chess piece derivation chart

         ­­­­­­­­­­­­­­­­­­­­­­­­­­­
        |        ChessPiece         |
         ­­­­­­­­­­­­­­­­­­­­­­­­­­­
       /      |       |       |     \
      /       |       |       |      \
  Pawn      Bishop   Rook   Knight   King
               \     /
                \   /
                Queen

The Queen has two routes back to her base class ChessPiece which means that two copies of 
ChessPiece members would exist!  Really, we don't want this ­ we only want one instance of 
ChessPiece loaded.

The solution is to inherit a base class that has danger of being multiply included as virtual: 

class Bishop : public virtual ChessPiece {...};
class Rook   : public virtual ChessPiece {...};
class Queen  : public Rook,Bishop        {...};

 Queen(non­virtual Rook/Bishop)       Queen(virtual Rook/Bishop)
 ­­­­­­­­­­­­­­­­­­­­­­­­             ­­­­­­­­­­­­­­­­­­­­­­­­­­
|RookPart      ChessPiece|     ==>   | RookPart       ChessPiece|
|BishopPart    ChessPiece|           | BishopPart               |
 ­­­­­­­­­­­­­­­­­­­­­­­­             ­­­­­­­­­­­­­­­­­­­­­­­­­­

Pointers to member functions

Pointers to C++ member functions work the same way as C function pointers except that we can 
only access the function pointer through a specific object of that class.
Suppose we wanted to create a pointer to the UniversityStudent::display(..) function and invoke it 
indirectly via the pointer.  We can code:

UniversityStudent u1(...);
void (UniversityStudent::*pf)(ostream&) = u1.display;
(u1.*pf)(cout);

or we could initialize the pointer without having an actual object handle using the scope operator:

UniversityStudent u1(...);
void (UniversityStudent::*pf)(ostream&) = &UniversityStudent::display;
(u1.*pf)(cout);

To load an array of member functions...

First we must have a consistency in member function signature.  Suppose we have generic class 
member functions on class X: input(), edit(), and display(), all returning no argument and 
accepting no arguments.  Then we can create an array of pointers to the three member functions. 
A typedef simplifies the syntax:

const int NFUNCTIONS = 3;
enum {INPUT,EDIT,DISPLAY};
typedef void (X:*PVF)();

X x;
PFV fa[NFUNCTIONS] = {&X::input,&X::edit,&X::display};
(x.*fa[INPUT])(); // invoke x.input()
(x.*fa[EDIT])(); // invoke x.edit()
(x.*fa[DISPLAY])();
// invoke x.display()

Assertions

In building code for the first time or attempting to track down bugs, sometimes it is useful to 
insert assertion patches into a program.

If a particular condition isn't met which should have been, a program can unconditionally abort 
with a message describing a failed condition, a source file and a line number.

Suppose we should never expect that the name field of a record should be blank when processing 
a Record.  We can guarantee this statement with an assert clause, assert(<condition>), which 
says: if <condition> isn't met, then terminate.

#include <assert.h>
#include <string.h>
void processRecord(Record r)
{
assert (strlen(r.name) != 0);
...
}   

Output (on the error condition of a blank name field)

Assertion failed: strlen(r.name) != 0, file assert.h, line 6
  

Exceptions

C++ provides the ability for programmers to provide their own exception handling.  An 
exception, or program error, is said to be thrown by a function and caught by the calling code.
The three C++ keywords necessary to create and trap exceptions are throw, try and catch.  Here 
is an example:

try
{
// some dubious code
f();
}
catch (ExceptionType1 &e1) { // error handler for error type 1 }
...
catch (ExceptionTypeX &eX) { // error handler for error type x }

void f()
{
...
if (error condition of type 1)
throw ExceptionType1;
...
else if (error condition of type x)
throw ExceptionTypeX;
}

ExceptionType can be as simple as a string description of the problem to a specialized exception 
class.

Sample exception code (EXCEPT.CPP)

#include <iostream.h>
class DivideByZeroException
{
public:
DivideByZeroException() : description("Division by zero error") {}
friend ostream& operator<<(ostream &o,DivideByZeroException &e)
{
o << e.description;
return(o);
}
char *description;
};
// Function that throws two possible exceptions:
// (1) a simple char* exception and
// (2) a specialized DivideByZeroException
int f(int index,int denominator)
{
  if ((index >= 10) || (index < 0))
 throw "index out of range";
  else if (denominator == 0)
throw DivideByZeroException();
  return (index/denominator);
}
void main()
{
// Test const char* exception...
try
{
cout << f(9,3) << endl;
// succeeds
cout << f(10,2) << endl; // generates a const char* exception
}
catch (const char* s) { cout << s << endl; }
catch (DivideByZeroException &e)
{ cout << e << endl; }
// Test DivideByZeroException...

try
{
cout << f(9,0) << endl;
// generates as DivideByZeroException
cout << f(10,4) << endl; // never is executed
}
catch (const char* s) { cout << s << endl; }
catch (DivideByZeroException &e)
{ cout << e << endl; }
}

Output

3
index out of range
Division by zero error

When to use exceptions

(1) When it does not make sense for the function to handle its own error(s).

(2) To maintain uniform error processing in team projects.

(3) For widely­used code such as that found in libraries.  It is easier for a user to put a try block 
around a series of library calls rather than checking the return code of each function for possible 
error.

Tips:

Don't go overboard on exceptions.  You, or some other programmer, has to ultimately provide the 
error handling code to process them when they are thrown!
Decide when exception processing is overkill.  Perhaps a simple int error code suffices.

Part III Exercises

(1) What is the difference between an IS A relation and a HAS A relation?  How do we describe 
each relation syntactically in C++? 

(2) Various shapes can be said to derive from a generic Shape class.  Every Shape consists of a 
Color and a Centre, a Color being an integer, a Centre a Point.  Consider Circle as being a 
special instance of an Oval, Square a special instance of a Rectangle.  Simulate virtual draw() 
functions for all shapes in the derivation tree below.  Each draw function should display the 
geometry attributes of that shape.  For example, Square::draw() should display the length of a 
side as well as the core Shape information.

     Shape
         /     \       
        /       \
      Oval     Rectangle
       |          |
       |          |
     Circle     Square

(3) Create a N x M character Screen object on which Shapes can be drawn.  Revise your virtual 
draw() functions to display directly onto a Screen object, the syntax being:

Screen screen(80,50);
// An 80 x 50 character screen
Square square(Centre(40,25),SQUARE_LENGTH,GREEN);
square.draw(screen);
// writes '*' characters to screen
...
screen.display();
// outputs the screen matrix to standard output
ofstream of("Screen.out");
screen.display(of);
// outputs the screen matrix to file stream <of>

Generate an OffScreen exception if a draw() command writes outside the screen boundaries.  Test 
your exception trigger by catching it from your test mainline. 

(4a) A middle­man appliance retailer wishes to have a computer
system that will record and report on their inventory.  Analysts
have come up with the following inheritance diagram:
               Appliance
               |­>Name
               |­>numberOfUnits
               |­>Wholesale cost
               |­>Retail price
               |­>Distributor
               |­>Date purchased
                  /\            /\
                  ||            ||
     HouseholdAppliance      OfficeAppliance
      markup()                  markup()
Household appliances are items such as fridges, stoves, and
toaster ovens, etc.  Office appliances are items such as tables,
phones, and computers, etc.  
The calculation for retail price depends on the Appliance type
and when the appliance was purchased.  Office appliances have a
2% markup over and above the household appliance markup of 20%. 
The markup goes down a percentage for every month the appliance
has been sitting in stock, with a bottom limit of an 12%
markdown.
     
Construct the classes.   
Code default constructors and full constructors for your classes.
Populate an array with some hypothetical inventory.
Write a function which produces a report somewhat like:
Date: 1997 07 18
Item      # units  wholesale retail  markup purchaseDate overhead

Fridge      4       $ 400    $ 600    20%    1997 07 08   $1600
Computer    2       $1200    $1428    19%    1997 04 01   $2400
Desk        10      $ 300    $ 450    15%    1997 02 21   $3000
Total retail overhead    $ 9756
Total wholesale overhead $ 7000
(4b) Allow the user to edit the markup percentages and time factor markup depreciation ramp 
and rerun the report.
Suggestion: create a helper class which allows editing of the parameters pertinent to the 
application.
 

(5) The game of chess involves pieces of various types that have
various movement patterns.  The pieces are
{PAWN,KNIGHT,BISHOP,KING,ROOK, QUEEN}.  All these pieces can be
derived from an abstract class called Piece.  Each piece moves
differently and can be queried for its squares of movement by
calling the function getMoves().  Here is a sample board:
        ­­­­­­­­­­­­­­­­­­­­­­­­
     8 | BR BN   BK             |  W = white, B = Black
     7 | BP            BB BB    |  K = king, P = pawn, B = bishop
     6 |            BP          |  N = knight, R = Rook, Q= queen
     5 |    BP   BR       BP    |
     4 |                  WP BP |  For ex, WN at Position(B,3)
     3 |    WN   WR WP WP    WP |  has the following squares to
     2 |       WB               |  move to:
     1 |    WK       WQ         |  (A,1),(C,1),(D,4),(D,2),(C,5)
        ­­­­­­­­­­­­­­­­­­­­­­­­   (A,5)
         A  B  C  D  E  F  G  H
 
* Note that a queen's movement can be monitored by sending a
getMoves() message to a Bishop object and a Rook object of the
same color at the same square.
Class diagram:
     Piece          <­­ Rook
     |­name              Position[] getMoves()
     |­position     <­­ Bishop
     |­colour            Position[] getMoves()
                     ...
     Position
     |­row
     |­column
     Color: [WHITE,BLACK]
     Board

     |­8x8 array of pieces
Instantiate a board and populate it with the above sample setup.
Display the board, then let the user ask where any piece on the
board can be moved to:
Sample run:
Enter piece position => B3
White knight at B3 can move to {A1, C1, D4, D2, C5, A5} 
Enter piece position => H1
?? No piece at H1
Enter piece position => 

C++ Data structures

Having learned the basics of C++ we are ready to tackle building our own data structure.  Let's 
start by converting our C­linked list implementation to a C++ OO implementation.

The messy part of the C implementation of the linked list is the need to pass a pointer to a 
comparison function.  We can eliminate this ugliness in our C++ version by recommending 
objects to provide their own comparison feature.

Specifically a user can pass an object to add to the data structure, and the data structure code can 
ask this object to compare itself with another.

To do this though, we will have to create an object base or abstract base class from which 
addable objects to our data structures can derive from.  In so doing, we can maintain a family of 
addable objects and can rely on the virtual mechanism of the language to call the comparison 
function of the type of object the data structure is working on.

        DATA STRUCTURE             APPLICATION CODE
      ­­­­­­­­­­­­­­­­­            ­­­­­­­­­­­­­­­­­­­­
     |     . ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­> ObjectA        |
     |                 |          |       compare()    |
     |                 |          |                    |
     |     . ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­> ObjectB        |
     |                 |          |       compare()    |
     |                 |          |                    |
     |     ...         |          |      ...           |
     |                 |          |                    |
     |                 |          |                    |
     |     . ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­ ­> ObjectX        |
     |                 |          |       compare()    |
      ­­­­­­­­­­­­­­­­­            ­­­­­­­­­­­­­­­­­­­

An abstract base class

An abstract base class cannot be instantiated; it is as described, abstract, it exists only in theory.
The purpose of such a beast is to provide a template or shell description of objects that must 
follow a certain guideline.
In a nutshell, as designers of the linked list data structure, we are going to say:

Dear application designer:
 
If you wish to add objects to my data structure, you must derive from my abstract base  
class and I'm going to force you to code an comparison function otherwise your class won't  
compile.

In general, an abstract base class can define any number of data or functional members, but 
functional members that are not defined are labelled as null via an = 0 declaration:

class AbstractBaseClass
{
public:
void f1() = 0; // The deriver promises to define
};                           // f1() 
   

                            ­­­­­­­­­­­­­­­­­­­
                           |   X               |
                           |                   |
                           |   void f1() {...} |
                            ­­­­­­­­­­­­­­­­­­­

                             /
       ­ ­ ­ ­ ­ ­ ­ ­ ­    /
|AbstractBaseClass |
|                  |
       ­ ­ ­ ­ ­ ­ ­ ­ ­ 
                            \
                             \
                           ­­­­­­­­­­­­­­­­­­­­­
                          |   Y                 |
                          |                     |
                          |   void f1() {...}   |
                           ­­­­­­­­­­­­­­­­­­­­­

An abstract Object definition

class Object
{
public:
virtual int compare(const Object&) = 0;
};

Suppose a user wants to populate our data structure with InventoryRecords.  Then the user 
derives from Object and provides a compare function describing how to compare 
InventoryRecords. 

class InventoryRecord : public Object
{
public:
...
int compare(const Object& o) {...}
};

            

 ­ ­ ­ ­ ­ ­            ­­­­­­­­­­­­­­­­­­­­­­
| Object     |   <­­­  |  InventoryRecord     |
|            |         |    int compare(...)  |
 ­ ­ ­ ­ ­ ­            ­­­­­­­­­­­­­­­­­­­­­­

OBJECT.H

#ifndef OBJECTH
#define OBJECTH
class Object
{
public:
virtual int compare(const Object&) = 0;
};
#endif      

LL.H

#ifndef LLH
#define LLH
#include "std.h"
#include "object.h"
/* forward declare the LLNode class for the recursive definition that follows */
      
class LLNode;
/* LLNode storage class */
class LLNode
{
public:
LLNode(Object *_data,LLNode *_next)
{ data = _data; next = _next; }
LLNode()
{ data = 0; next = 0; }
         
Object *data;
LLNode *next;
};
class LinkedList
{
public:

LinkedList(int ownsElements = FALSE); 
~LinkedList();
void begin();
LLNode *next();
LLNode *cur();
void add(Object &o,LLNode *insertAfterNode=0);
LLNode *search(Object &o);
int remove(Object &o);
void setOwnership(int _ownsElements)
{ ownsElements = _ownsElements; }
int  getOwnership() { return (ownsElements); }
private:
LLNode *head;
LLNode *current;
int ownsElements;
LLNode* find(Object &data,LLNode **previousNode);
};
#endif     

LL.CPP

#include <stdlib.h>
#include "ll.h"
LinkedList::LinkedList(int _ownsElements)
{
head         = 0;
current      = head;
ownsElements = _ownsElements;
}
LinkedList::~LinkedList()
{                  
LLNode *node,*nextNode;
node = head;
while (node)
{
if (ownsElements)
delete node­>data;
 /* important to get next node before freeing <node>! */
nextNode = node­>next;
delete node;
node = nextNode;
}  
}
void LinkedList::begin()
{
current = 0;
}
LLNode* LinkedList::cur()
{
return(current);
}
LLNode* LinkedList::next()
{
/* CASE 1: get first element in list */
if (!current)
{
current = head;

return(current);
}
/* CASE 2: get subsequent elements in list */
current = current­>next;
return(current);
}

void LinkedList::add(Object &data,LLNode *insertAfterNode)
{
LLNode *newNode;
/* Create the new node */
newNode       = new LLNode;
newNode­>data = &data;
if (!insertAfterNode)
{
/* CASE 1: insert element at start of list */
newNode­>next = head;
head          = newNode;
}
else
{
/* CASE 2: adding after the first element */
newNode­>next         = insertAfterNode­>next;
insertAfterNode­>next = newNode;
}
/* Make the added node the current one */
current = newNode;
}
LLNode* LinkedList::find(Object &data,LLNode **previousNode)
{
LLNode *currentNode;
begin();
*previousNode = 0;
while (currentNode = next())
{
if (data.compare(*currentNode­>data) == EQUAL)
{
current = currentNode;
return(currentNode);
}
*previousNode = currentNode;
}
return(0);
}
LLNode* LinkedList::search(Object &data)
{
LLNode *previousNode;

return(find(data,&previousNode));
}

int LinkedList::remove(Object &data)
{
LLNode *nodeToDelete,*previousNode;
nodeToDelete = find(data,&previousNode);
if (!nodeToDelete)
return(FALSE);
if (!previousNode)
{
/* CASE 1: delete element at start of list, update the head pointer */
head = nodeToDelete­>next;
}
else
{
/* CASE 2: delete an element after the first element,
patch a previous pointer */
previousNode­>next = nodeToDelete­>next;
}
if (ownsElements)
delete nodeToDelete­>data;
delete nodeToDelete;
/* make the previous node current */
current = previousNode;
return(TRUE);
}

LLTEST.CPP

#include "ll.h"
#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>
class Data : public Object
{
public:
int a;
Data(int _a = 0) :a(_a) {}
int compare(const Object &o)
{
Data &data = *(Data*)&o;
if (data.a == a)
return(EQUAL);
else if (data.a < a)
return (LESS_THAN);
else
return (GREATER_THAN);
}
    
friend ostream& operator<<(ostream &o,Data &d)
{
o << d.a;
return (o);
}
};
void displayData(LinkedList &llist)
{
LLNode *node;
Data *element;
llist.begin();
while (node = llist.next())
{
element = (Data*)node­>data;
cout << *element << " ";
}
cout << endl;
}

#define NELEMENTS 6
void main()
{
int i,data[NELEMENTS] = {9,4,3,2,99,88};
LLNode *node;
// Turn ownership of elements on for <llist>
// This means <llist> performs garbage collection explicitly
// when items are removed from <list>
LinkedList llist;
llist.setOwnership(TRUE);   

 
 

for (i = 0; i < NELEMENTS; i++)
{                           
Data &d = *new Data(data[i]);
llist.add(d);
displayData(llist);
}
Data three(3),minusFour(­4),eightyEight(88);
Data& oneHundred = *new Data(100);
node = llist.search(three);
if (node)
cout << "Element < " << *(Data*)node­>data << " > found" << endl;
llist.add(oneHundred,node);
cout << "Element < " << oneHundred << " > added after < "
<< three << " >" << endl;
displayData(llist);
node = llist.search(minusFour);
if (!node)
cout << "Element ­4 not found" << endl;
if (llist.remove(eightyEight))
cout << "Element < " << eightyEight << " > removed" << endl;
displayData(llist);
if (llist.remove(three))
cout << "Element < " << three << " > removed" << endl;
displayData(llist);

}

Output

9                                  
4 9                                
3 4 9                              
2 3 4 9                            
99 2 3 4 9                         
88 99 2 3 4 9                      
Element < 3 > found                
Element < 100 > added after < 3 >  
88 99 2 3 100 4 9                  
Element ­4 not found               
Element < 88 > removed             
99 2 3 100 4 9                     
Element < 3 > removed              
99 2 100 4 9                       

Templates

C++ provides the ability to parameterize data types at compile time.  This is a useful if a class 
wishes to work on any user defined type without having to code internally a generic void* data 
type and perform cumbersome casts.
For example, suppose we wanted to create a class which would store an array of data of arbitrary 
type.  Normally we would have to code something like:

class Array
{
private:
int size;
void **arrayData;
public:
Array(int _size = 10) {...};
void* operator[](index) { return(arrayData[index]); }
...
};

We can replace void* with a type variable inside the angle brackets.

Inline template definition (template.cpp)

#include <iostream.h>
#include <stdlib.h>
template<class Type>
class Array
{
private:
int size;
Type *arrayData;
  public:
 Array(int _size = 10)
 {
size = _size;
arrayData = new Type[size];

 }
 Type& operator[](int index) { return(arrayData[index]); }
 int getSize() { return (size); }
 ~Array() { delete arrayData; }
};
void main()
{
Array<int> a(3);
a[0] = 67;
a[1] = 43;
a[2] = 102;
for (int i = 0; i < a.getSize(); i++)
cout << a[i] << " ";
cout << endl;
}

Template header file definition 

The complete template definition must appear in the header file.  This includes function 
definitions outside the class.  The reason being that the functions are generated at compile­time 
when a type is bound to the array via:

     Array<OfWhateverType> a(SIZE);

Only when the compiler has loaded a definition of the functions via #include array.h can it 
generate the functions.  If they are not there, no functions are generated and undefined symbol 
errors occur at link time.

The syntax for out­of­line template functions for class X with a parametrized type T is:

template<class T>
[returnType] X<T>::[functionName](parameters) {...}

ARRAY.H

#ifndef ARRAYH
#define ARRAYH
template<class Type>
class Array
{
private:
int size;
Type *arrayData;
  public:
 Array(int _size = 10);
 Type& operator[](int index);
 int getSize();
 ~Array();
};

template<class Type>
Array<Type>::Array(int _size)
{
size = _size;
arrayData = new Type[size];
}
template <class Type>
Type& Array<Type>::operator[](int index)
{
return(arrayData[index]);
}
}
template <class Type>
int Array<Type>::getSize()
{
return (size);
}
template <class Type>
Array<Type>::~Array()
{
delete arrayData;
}
#endif

ARRAYTST.CPP

#include <iostream.h>
#include <string.h>
#include "array.h"
class InventoryRecord
{
private:
int key;
int nUnits;
char description[10];
public:
InventoryRecord() {}
InventoryRecord(int _key,int _nUnits,char *_description)
: key(_key),nUnits(_nUnits)
{
strcpy(description,_description);
}
friend ostream& operator<<(ostream &o,InventoryRecord &r)
{
o << r.key << "," << r.nUnits << "," << r.description;
return o;
}
};
void main()
{
InventoryRecord inv1(1,10,"pail"),inv2(2,4,"shovel"),inv3(3,40,"trowel");
Array<InventoryRecord> a(3);
a[0] = inv1;
a[1] = inv2;
a[2] = inv3;
for (int i = 0; i < a.getSize(); i++)
cout << a[i] << endl;
}

Multiple template types

C++ does not restrict us to parametrizing a class upon a single type.  We can list as many class 
parameters as we want inside the angle brackets, separated by commas.

Here is an example of a class that builds itself upon three types A,B,C:

template <class A,class B,class C>
class X                          
{
private:
A a;
B b;
C c;
public:
...
};
X x<A,B,C>;  // Creates instance x based on the three types A,B,C

Templates in general...

Advantages:

. provides ability to create type­generic code
. provides compact clean syntax
. avoids the necessity to create an abstract base class
      from which the user must derive
. avoids the necessity to cast back from the void* type

Disadvantages:

. higher executable size overhead as the compiler generates code
  for each type occurrence

For example if we code:

Array<int> a(3);
Array<InventoryRecord> ir(10);

then we have two instances of all the Array functions, a copy for the int type and a copy for the 
InventoryRecord type.
 
At least with our void* implementation we had only one copy of the class functions for all 
possible user types.
  

Linked list template implementation

This is an interesting data structure to attempt because we have two classes to template, the 
LLNode class and the LinkedList class, both parameterized by a user <Type>.

template<class Type>
class LLNode {..};
template<class Type>
class LinkedList {...};

To minimize code changes required from the original C linked list version we will keep the 
LLNode members public access even though it is better to make them private.
Incidentally, we could still have LLNode private members and avoid the fiasco of creating the 
necessary public accessors and mutators getData(), setData(), getNext(), setNext() by making 
LinkedList a friend of LLNode:

template<class Type>
class LinkedList
{
friend class LinkedList<Type>;
...
};

Recall that member functions of friend classes have access to the private members of their 
friends.

LinkedList template (LLT.H)

#ifndef LLTH
#define LLTH
#include "std.h"
/* forward declare the LLNode class for the recursive definition */
template<class Type>
class LLNode;
/* LLNode storage class */
template<class Type>
class LLNode
{
public:
LLNode(Type *_data,LLNode *_next)
{ data = _data; next = _next; }
LLNode()
{ data = 0; next = 0; }
Type *data;
LLNode *next;
};
template<class Type>
class LinkedList
{
public:
LinkedList(int ownsElements = FALSE);
~LinkedList();
void begin();
LLNode<Type> *next();
LLNode<Type> *cur();
void add(Type &o,LLNode<Type> *insertAfterNode=0);
LLNode<Type> *search(Type &o);
int remove(Type &o);
void setOwnership(int _ownsElements)
{ ownsElements = _ownsElements; }
int  getOwnership() { return (ownsElements); }
private:
LLNode<Type> *head;
LLNode<Type> *current;
int ownsElements;

LLNode<Type>* find(Type &data,LLNode<Type> **previousNode);
};
template<class Type>
LinkedList<Type>::LinkedList(int _ownsElements)
{
head         = 0;
current      = head;
ownsElements = _ownsElements;
}

template<class Type>
LinkedList<Type>::~LinkedList()
{
LLNode<Type> *node,*nextNode;
node = head;
while (node)
{
if (ownsElements)
delete node­>data;
 /* important to get next node before freeing <node>! */
nextNode = node­>next;
delete node;
node = nextNode;
}
}
template<class Type>
void LinkedList<Type>::begin()
{
current = 0;   }
template<class Type>
LLNode<Type>* LinkedList<Type>::cur()
{
return(current); }
template<class Type>
LLNode<Type>* LinkedList<Type>::next()
{
/* CASE 1: get first element in list */
if (!current)
{
current = head;
return(current);
}
/* CASE 2: get subsequent elements in list */
current = current­>next;
return(current);
}
template<class Type>
void LinkedList<Type>::add(Type &data,LLNode<Type> *insertAfterNode)
{
LLNode<Type> *newNode;
/* Create the new node */
newNode       = new LLNode<Type>;

newNode­>data = &data;
if (!insertAfterNode)
{
/* CASE 1: insert element at start of list */
newNode­>next = head;
head          = newNode;
}
else
{
/* CASE 2: adding after the first element */
newNode­>next         = insertAfterNode­>next;
insertAfterNode­>next = newNode;
}
/* Make the added node the current one */
current = newNode;
}

template<class Type>
LLNode<Type>* LinkedList<Type>::find(Type &data,LLNode<Type> **previousNode)
{
LLNode<Type> *currentNode;
begin();
*previousNode = 0;
while (currentNode = next())
{
Type *nodeData = (Type*)currentNode­>data;
if (data.compare(*nodeData) == EQUAL)
{
current = currentNode;
return(currentNode);
}
*previousNode = currentNode;
}
return(0);
}
template<class Type>
LLNode<Type>* LinkedList<Type>::search(Type &data)
{
LLNode<Type> *previousNode;
return(find(data,&previousNode));
}
template<class Type>

int LinkedList<Type>::remove(Type &data)
{
LLNode<Type> *nodeToDelete,*previousNode;
nodeToDelete = find(data,&previousNode);
if (!nodeToDelete)
return(FALSE);
if (!previousNode)
{
/* CASE 1: delete element at start of list, update the head pointer */
head = nodeToDelete­>next;
}
else
{
/* CASE 2: delete an element after the first element,
patch a previous pointer */
previousNode­>next = nodeToDelete­>next;
}
if (ownsElements)
delete nodeToDelete­>data;
delete nodeToDelete;
/* make the previous node current */
current = previousNode;
return(TRUE);
}
#endif

Code to test the templated link list (LLTTEST.CPP)

#include "llt.h"
#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>
class Data
{
public:
int a;
Data(int _a = 0) :a(_a) {}
int compare(const Data &o)
{
Data &data = *(Data*)&o;
if (data.a == a)
return(EQUAL);
else if (data.a < a)
return (LESS_THAN);
else
return (GREATER_THAN);
}
friend ostream& operator<<(ostream &o,Data &d)
{
o << d.a;
return (o);
}
};
void displayData(LinkedList<Data> &llist)
{
LLNode<Data> *node;
Data *element;
llist.begin();
while (node = llist.next())
{
element = node­>data;
cout << *element << " ";
}
cout << endl;
}

#define NELEMENTS 6
void main()
{
int i,data[NELEMENTS] = {9,4,3,2,99,88};
LLNode<Data> *node;
// Turn ownership of elements on for <llist>
// This means <llist> performs garbage collection explicitly
// when items are removed from <list>
LinkedList<Data> llist;
llist.setOwnership(TRUE);
for (i = 0; i < NELEMENTS; i++)
{
Data &d = *new Data(data[i]);
llist.add(d);
displayData(llist);
}
Data three(3),minusFour(­4),eightyEight(88);
Data& oneHundred = *new Data(100);
node = llist.search(three);
if (node)
cout << "Element < " << *(Data*)node­>data << " > found" << endl;
llist.add(oneHundred,node);
cout << "Element < " << oneHundred << " > added after < "
<< three << " >" << endl;
displayData(llist);
node = llist.search(minusFour);
if (!node)
cout << "Element ­4 not found" << endl;
if (llist.remove(eightyEight))
cout << "Element < " << eightyEight << " > removed" << endl;
displayData(llist);
if (llist.remove(three))
cout << "Element < " << three << " > removed" << endl;
displayData(llist);
}

Part IV Exercises

(1a) Create a Stack class, that stores arbitrary objects on a stack.  Recall that a stack works on a 
LIFO basis (last in­first out).  If we push() the elements A, B, C on the stack and pop() them out 
one at a time, we get them back in the reverse order: C, B, A.
Here are the stack operations you should supply:
Stack();
void push(void*);
void* pop();
int isEmpty();
(b) Create a templated version of your class.
(c) Test your templated version by creating and populating a Stack of LotteryTickets from an 
input file.

Stack<LotteryTickets> s;

Lottery tickets have a number, a purchaseDate and purchasePlace.  Once your data structure is 
loaded, try various combinations of push() and pop() commands to test its functioning.

(2a) Create a priority queue class.  Recall that a queue operates on a FIFO, first­in first­out basis. 
A priority queue can insert elements of higher priority ahead of others.  For example, below we 
have 4 elements of priorities 10,8,3, and 1, 1 being the highest priority and specifically the next 
element to be popped off the queue.  If we add an element of priority 5 then it should be inserted 
at the appropriate place in the queue, namely between elements of priority 8 and 3:

        ­­­­­­        ­­­­­        ­­­­­         ­­­­­
tail­> |  10  | <­>  |  8  | <­>  |  3  | <­>   |  1  | <­ head
        ­­­­­­        ­­­­­        ­­­­­         ­­­­­

                              /\
                              ||
                            ­­­­­­
                           |  5   |
                            ­­­­­­
Implement your queue using a doubly­linked list as the underlying data structure.

(2b) Test your PriorityQueue by simulating jobs queued for printing.  The master scheduler 
assigns priorities to print jobs based on user administration groupid.  Managers and vice­
presidents have highest priority.  Regular employees have lowest priority.  Super­users can dictate 
their own priority to the master scheduler.  Assume lookup tables of userid to admin group and 
admin group to priority exists.

Here is a sample OO mainline.  You can fill in the details of what a PrintJob, AssociationTable 
and MasterScheduler should look like:

AssociationTable userLookup;
userLookup.add("Ned","superuser");
userLookup.add("Frank,"vice");
userLookup.add("Belinda","payroll");
...
PriorityQueue<PrintJob> printQueue;
MasterScheduler masterSheduler;
masterScheduler.assignUsers(userLookup);
PrintJob printJob1("monthlyReport.txt");
masterScheduler.addJob("Frank",printJob1
PrintJob printJob2("daemons.out");
masterScheduler.addJob("ned",printJob2,3);
PrintJob printjob3("doc.draft");
jobHandle lindasJobHandle = masterScheduler.addJob("linda");
...
masterScheduler.displayJobs();
masterScheduler.cancelJob(lindasJobHandle);
masterScheduler.displayJobs();
...

(3) Convert the following C data structure implementations over to C++ template­based 
implementations.

. DArray
. HashTable
. BinaryTree

(4a) Your Student class software from exercise 5 Part II, has gained popularity amongst many 
institutions.  Requests have been made for a Student manager package that will provide users to 
manage all the students in the institution.  Here is a list of the following requests:

Add a student
Delete a student
Search for a student
Edit a students marks
Display a students details
Display all students details

class StudentManager
{
     public:
 ... add(...)        {...}
       ... search(...)     {...}
       ... delete(...)     {...}
       ... edit(...)       {...}
       ... display(...)    {...}
       ... displayAll(...) {...}
     private:
 LinkedList<Student> sList;
// (or other data structure of your choice)
};   

Design your class on paper and walk through the logic with a
series of test cases, before attempting the code.  Write an
application that uses the StudentManager class to simulate the
tool described above.  Test your code with a list of at least ten
students.

(4b) A second request has been asked of your StudentManager
package, that is to provide a record of attendance for each
student.  It suffices to list only the student's absences:
Martha Jones ...
     ...
   4 Absences:
     02 22 97
     02 27 97
     03 02 97
     03 10 97

Suggested class design:

class Student
{
...
   private:
 AttendanceRecord ar;
};
class AttendanceRecord
{
     private:
 LinkedList<Absence> aList[];
};
class Absence
{
     ...
};

Sign up to vote on this title
UsefulNot useful