Sei sulla pagina 1di 141

ENGI 3891: Advanced Programming

Table of contents:
1. Forward 2. Introduction 3. Variables 4. Constants 5. Functions 6. The string Class 7. Arrays 8. Pointers 9. More On Pointers 10. Dynamic Memory Allocation 11. Structures 12. Introduction to Classes 13. The UML 14. Classes 15. Running Examples 16. Constructors 17. Copy Constructors 18. Inheritance 19. Ad Hoc Polymorphism 20. Parametric Polymorphism 21. Virtual Functions 22. Testing 23. Extra notes on virtual functions

Forward Acknowledgement
The author would like to acknowledge and thank the Office Of Learning Technology of the Canadian Employment Insurance Commission for the generous support provided for this project.

Forward
These notes represent a significant milestone culminating five years of work by Theo Norvell and myself. Little did we know when we started out how long the road would be. I had been teaching C++ since the late eighties and had been growing more and more dissatisfied with the tools at my disposal. A solution to the problem had been slowly taking shape in my mind but I didn't have the expertise to tackle it alone. One day, over coffee, I sketched out my ideas to Theo, and, Glory Be!, he got interested. He threw in a few ideas of his own and before we were properly aware of it, the Teaching Machine was born. The fundamental proposition was quite simple. It was a matter of observation that beginning programmers often throw code at problems, using what worked last week, or for a friend, or for a different problem. For many of them programming too often seems like magic and there's more a sense of rather desparate invocation than of crafting. We refer to this as superstitious coding. There's sometimes some grains of truth in the code but there's an awful lot of mumbo-jumbo, too. To a skilled programmer, every line of code is meaningful. If pressed most would probably say that a piece of code is just a set of instructions to an underlying machine. But when one is using a higher level language like C++ or Java, what does that machine really look like? We believe it looks like an amalgam of the compiler and the microprocessor. The Teaching Machine represents an abstraction of how we think experienced programmers regard computers. Thus we represent memory quite physically as an array of bytes, much as the microprocessor does, but organize them into stores, an altogether higher level concept. Again, our model for mathematical computation is something we call the expression engine which is a combination of an ALU (which fetches data from memory and executes) and an parser (which takes apart C expressions).

Somewhere along the way WebWriter++ was added to make it easy for instructors to build the Teaching Machine into web pages. The next step seemed inevitable. Moving an entire course into HTML so that the TM could be integrated totally into the notes. This site is the result. Although it was designed to be used in lectures, student feedback indicates it also functions pretty well as a standalone site. I can conceive a number of things that would improve its performance in that direction, most notably a set of videos demonstrating the TMexamples. I am contemplating their creation even as this is written. And once again, I would like to thank The Office of Learning Technology for their kind support.

Lihong Zhang

Office: EN-3031

Office Hours: Thursday, 15:30-17:30 (to be confirmed) Tel: 864-4863 email: lzhang@mun.ca

Marking Scheme
Assignments: 12% Mid-Term: 30% Final: 58%

Click here to download a copy of the current Teaching Machine jar file. (The jar file is the java executable. In order to run it you must have the Java run time system installed on your machine. Instructions for getting it arehere.) Midterm Exam : Wednesday, Oct. 16, 3:00-3:50 pm (tentatively) Text: Frank L. Friedman and Elliot B. Koffman, Problem Solving, Abstraction and Design Using C++, fifth edition, Addison Wesley, 2006. ISBN: 0-32145005-1 (same text as was used in ENGI 1020). ( Note: The textbook is not mandatory. It is specified only as a backup. The lecture notes are the primary resource and are available on the web in a form that may readily be printed.) Software: Eclipse with the C++ development tools (CDT). The software installation guide can be found here.

Assignments
Weekly, due Fridays at 8:55 am. Posted, on the web, approximately a week before the due date. If not otherwise informed, submitted over the network using the WebSubmit procedure developed by ECS. The submit directory for each assignment will be closed at 8:55 am sharp. One programming problem each week. Unless otherwise specified, compiled and linked with a grading program which will test it automatically.

Hand graded by a TA for style: 2 out of 10 pts. (TA's will be looking for mnemonic names, clean structure and good commenting).

Laboratory
There is a laboratory on Wednesdays from 10:00-11:50 in EN-3000/3029. There is no separate lab assignment as such. We will use this period to explore your weekly programming assignments. It is not anticipated that you will be able to complete your assignment during the lab period. Students are expected to do their own assignments. Group work is not permitted.

Building a C++ Library


Advanced books recommended for students who have finished the course and now find themselves with a C++ programming job! The C++ Programming Language, 3rd Edition, Bjarne Stroustrup, Addison-Wesley. ISBN 0-2-1-88954-4. The bible for C++ programmers by the man who created the language. When in doubt always check what Stroustrup has to say! Effective C++, Scott Meyers, Addison-Wesley. ISBN 0-201-92488-9 & More Effective C++, Scott Meyers, Addison-Wesley. ISBN 0-201-63371X. Excellent discussions of some nitty-gritty, but nonetheless, often quite deep, C++ programming details. The (Draft) Standard C++ Library, P.J.Plauger, Prentice-Hall. ISBN 0-13117003-1. Documentation and discussion of the (almost) standard library, by one of the best respected authors in the business. STL Tutorial and Reference Guide, David R. Musser & Atul Saini, Addison-Wesley. ISBN 0-201-63398-1. STL is the standard Template Library, a very advanced and powerful emerging standard for implementing containers.

3891 Course Outline


Review: commands and operators, control structures; functions and variables: scope, lifetime, storage class, pass-by-value, declaration vs. definition, privacy; arrays & string; library objects.

Pointers: notation, equivalence of pointer notation to array notation, passing by pointer, pass-by-reference. Structures: declaration, definition and usage, pointers to structures, structure assignment, abstract data types. Classes: data and function hiding, member functions, access control, objects, declaration versus instantiation, static members, inline functions. Object Creation: constructors and destructors, default constructors, overloading, default arguments, copy constructors, shallow vs. deep copying. Ad Hoc Polymorphism: conversions, binary operator overloading, unary operator overloading, friend functions, assignment and subscript overloading. Inheritance: code reuse, derived and base classes, the protected access control, code reuse, virtual functions, abstract base classes, multiple inheritance. Parametric Polymorphism: function templates, signature matching, class templates, inheritance. Although not formally taught, examples and assignments will emphasize and explore such fundamental data structures as arrays, stacks, and queues.

Objectives
At the end of this course you should 1. Have a fairly deep understanding of the processes underlying programming in general and object-oriented programming in particular. 2. Be able to read & understand well-written programs in C & C++. 3. Be able to code in C++ at the class level. 4. Have a working familiarity of Abstract Data Types, and basic Data Structures.

First Program
introDemo2.cpp
/* The mandatory "Hello world!" program */

/* In C & C++ input and output (i/o) are not part of the language. Instead they are included in the standard library of functions. The next line of code tells the precompiler to include the iostream part of the library */ #include <iostream> /* There are now thousands of names in the libraries so they need to be qualified. Just like there are lots of Johns, Marys & Michaels in the world. This is like saying "use lastName Bruce-Lockhart". Then Michael always refers to Michael Bruce-Lockhart*/

using namespace std; int main() { cout << "Hello, world!\n"; return 0; }

The standard "Hello world!" program. 5 lines of code, including An instruction to the preprocessor An instruction to the compiler to use the std namespace A main function A function call to insert the string into the standard output stream. Every piece of code has a specific meaning!

Superstitious vs Informed Coding

Variables
Attributes of Variables
1. Name
What the programmer uses to refer to the variable. Associated with the name are the following sub-attributes:

Scope
The range of the progam over which the name of the variable is known

Visibility
Whether a variable name is known at a particular point in a program. Subtly different from scope because of occlusion

2. Type
The category to which a piece of computer data is assigned. All data of a particular type will conform to a set of properties determined by that type, for example the amount of storage required to hold a piece of data or a range of acceptable values. Types may be categorized as

Built-in Scalar
single-valued. e.g. int, double, char

Compound
multiple-valued, either array or structure

User-Defined
In C++ we use classes to build user defined or Abstract Data Types (referred to as ADT's)

3. Value
the actual piece of data currently stored in the variable

4. Location
A physical place in memory where data is stored. There are sub attributes, vis

Reference
The way the location is referred to

Amount
The quantity of the storage used at the location for the data

Storage Class
Where the storage gets allocated.

5. Lifetime
How long a piece of data exists, generally 1. temporary 2. permanent (or static)

The Fundamental Attributes


These are the top-level (numbered) attributes in the preceding list. They are: name, type, value, location, and lifetime. They are illustrated in the following example:

Variables1.cpp
// A do nothing program which illustrates various attributes of variables // copyright(c) 2002 by Michael Bruce-Lockhart #include <iostream> using namespace std; int factorial(int c); double x; // These are external variables int count = 3; int main(){ int myCount; x = 3.14159; myCount = count++; int fact = factorial(myCount); cout << "The factorial of " << myCount << " is " << fact << endl; return 0; } int factorial(int c){ if (c < 0) return -1; int fact = 1; for (int i = 2; i <= c; i++ ) fact *=i; return fact; }

Scope
Scope is a sub-attribute of name. It is the range of the program over which the name is known. The scope of a variable is controlled by where it is declared. Scope always starts at the point of declaration. Variables declared inside of functions (internal variables) In C: must be declared at the beginning of the function and extend to the end of the function. In C++: may be declared anywhere in a function and are known until the end of the smallest block enclosing the declaration. Variables2.cpp
int main () { int q; for(int i=0;i<10;i++) { // Not legal in C q++; //This is silly cout << i << q;

int p = 12; // Also useless, just illustrative } cout << p; // The compiler will complain! return 0; }

The scope of q is throughout main The scope of i the for statement only (includes block). The scope of p is exactly 1 line! Variables declared outside all functions (external variables) Scope extends from point of declaration to the end of the file.

Visibility
Refers to where in a program a name (of a variable) is available to the code. Generally, variables in scope are visible. Unless two variables with the same name are both in scope In which case only the variable with the innermost scope is visible. Variables3.cpp
int x1, a[10]; int main() { int i, x2; //...code for main } sub1() { int s1, s2; //...code for sub1 } int locl1, locl2; sub2() { int s21, s22, x1; //...code for sub2 } sub3() { int s31, s32; //...code for sub3 }

As an exercise, try filling in the following table, putting checkmarks in each column where a variable is visible. Variables x1 (outer) a x1 (inner) i, x2 s1, s2 locl1, locl2 s21, s22 s31, s32 main sub1 sub2 sub3

Lifetime
The length of time a variable lasts. Permanent variables last the lifetime of the program Temporary variables last only for the duration of a function.

Storage Class
Characterizes the type of storage where the variable is allocated. Closely related to lifetime. Automatic: variables that are stored on the stack and are temporary by definition. This is the default for internal variables. Static: variables that are stored in long term memory (the data store). Static is the usual term but means many other things in C++ so we will refer to these as allocations in the data store.

Variables4.cpp
// A variation on our do nothing program which illustrates how the stack works // copyright(c) 2002 by Michael Bruce-Lockhart #include <iostream> using namespace std; int factorial(int c); double x; // These are external variables int count = 3; int main(){ int myCount; x = 3.14159; myCount = count++; int fact = factorial(myCount); cout << "The factorial of " << myCount << " is " << fact << endl; return 0; } int factorial(int c){ if (c < 0) return -1; if (c < 2) return 1; return c*factorial(c-1); }

All external variables are put in the data store. Internal variables can be put in by preceding the declaration with static.

Basic Rules
1. Internal (declared inside a function) variables are automatic unless declared static. 2. External variables are always static. 3. Static variables are permanent 4. Automatic variables are temporary 5. Internal variables should normally be used

Constants
User-Defined Types
In C & C++ you can create your own types typedef int number;

This declares number to be a new type, the same as int. Then, I can use type number in declarations, as

int i,j,k; number n; Later on, if I find all numbers really should have been long, it is easily changed. typedef long number;

Changes all instances of number throughout the program. Scoping rules the same as for external variables.

Enumerated variables
Any variable can be declared as enumerated. enum{BLACK,WHITE,RED,GREEN,YELLOW,BLUE} c1; enum{SOLID,DOTTED,DASHED,INVISIBLE} line1,line2; declares c1 as a variable of a distinct integral type with named constants -BLACK(0), WHITE(1)...BLUE(5) Of limited utility as it stands. We often want to create an enumerated type enum daysOfTheWeek { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }; then, later in the code (within scope of daysOfTheWeek). daysOfTheWeek workDays; Alternatively, both can be done at once enum colour {BLACK,WHITE,RED,GREEN}c1,c2; daysOfTheWeek & colour are now special integral types, each with its own constants. We've also declared two variables of type colour.

enumerated constants are assigned consecutive values, starting from 0. These may be overridden enum linestyle {SOLID=1,DOTTED,DASHED,INVISIBLE=0}; (DOTTED & DASHED are 2 & 3 respectively).

Enumerations really are constants.


Note that the compiler will complain about the following: enum day {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}; enum weekendDay {SATURDAY,SUNDAY}; SATURDAY and SUNDAY are redefined (within the same scope). Making sure the constants have the same value won't help enum day {SATURDAY, SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY,FRIDAY}; enum weekendDay {SATURDAY,SUNDAY}; is still illegal It also sees this as legal day d = 8; in essence, an implicit typecast has been carried out. It is an error because only the constants 0-6 are defined in the declaration but it is legal! (Good compilers will issue a warning). Both these problems drive Pascal programmers crazy!

The const keyword


constants are declared in C++ as follows: const const const const int GEORGE = 0; int MARY = 1; int JOE = 0; char initial = 'M';

enum may be used anonymously. For instance enum {GEORGE,MARY,JOE=0};

is equivalent to the first three declarations. You will often see the following in old code (usually in header files): #define GEORGE 0 #define MARY 1 #define JOE 2

This is not the same and we won't use it because compiler does no type checking on defines. const keyword is used with variable names to tell the compiler the value is not to be changed (however it is to be type checked).

Functions

Functions consist of a declaration and a definition (or implementation). Functions can be called from anywhere in the file after their declaration. The compiler will allow a function to be called even if it hasn't been implemented (defined).

functions1.cpp
double function1(double arg1, double arg2); int main(){ double x=3., y= 17.5; double w, z; w = function1(x,y); z = function1(y,w); return 0; } double function1(double arg1, double arg2) { if (arg1 > arg2) return arg1*arg2; else return -arg2; } // Definition // Call // Another call // Declaration

Function declarations are always external. Function names have the same properties as external variable names. Functions don't have to return a value in which case the return type should be void.

The declaration is a template specifying 1. the return type 2. the name of the function 3. the number and type of the arguments to be passed (via a formal parameter list)

functions2.cpp
double function1(double arg1, double arg2); int main(){ double x=3., y= 17.5; double w, z; w = function1(x,y); z = function1(y,x); return 0; } double function1(double a1, double a2){ if (a1 > a2) return a1*a2; else return -a2; }

The names in the formal parameter lists of the declaration and definition do not have to be the same. 1. Compiler matches lists, in order of argument. 2. Types and number of arguments must be the same. 3. Names are not even required in the declaration

functions3.cpp
double function1(double, double); int main(){ double x=3., y= 17.5; double w; w = function1(x,y); // More code here } double function1(double a1, double a2){ //code for function1 }

Argument Passing
Arguments in C are passed by value. This was done to avoid side effects. Pass-by-value means the function cannot succesfully change one of its arguments

functions4.cpp
//Consequences of Pass-by-value #include <iostream> using namespace std; void intSwap(int x1, int x2); int main(){ int a = 2; int b = 3; intSwap(a,b); cout << "a is " << a << " and b is " << b << endl; return 0; } // A totally useless function!!! void intSwap(int x1, int x2){ int temp; temp = x1; x1 = x2; x2 = temp; }

Arguments in C++ can also be passed by reference. Pass-by-reference allows arguments to be modified

functions5.cpp
//Passing by reference #include <iostream> using namespace std; void intSwap(int& x1, int& x2); int main(){ int a = 2; int b = 3; intSwap(a,b); cout << "a is " << a << " and b is " << b << endl; return 0; } // A useful function!!! void intSwap(int& x1, int& x2){ int temp; temp = x1; x1 = x2; x2 = temp; }

Notice that the call to the two different swap functions is exactly the same. You cannot tell from a call whether a function is pass-by-value or pass-by-reference. You must look at the declaration to find out.

The string Class


Although they look like variables, strings as you learned them last term were actually objects of class string. In some ways they look just like variables. For example here is the declaration and initialization of some string objects.

stringClass.cpp
string first, last, name; // declare a few strings string greetings = "Hello"; // declare and initialize

However, as objects of class string they also have operations available which can be invoked using the member-of operator "." .

stringClass.cpp
cout << "Here are the characters in our greeting: " << endl; for (int i = 0; i < greetings.length(); i++) cout << greetings.at(i) << ' '; cout << endl;

Here, the length() function of the string class returns the length of the string that is used to invoke it, in this case greetings. The at(i) function returns the character at the i'th position where i must be between 0 and greetings.length()-1 See table 7.3 on page 193 of your text for the definition of some other string class functions. Assignment may also be used with string objects.

stringClass.cpp
string first, last, name; // declare a few strings first = "Michael"; // Assignment cout << "And now its length is "<< first.length() << endl; last = "Bruce-Lockhart"; name = first + ' ' + last; cout << name << endl;

Assignment is subtly different from initialization in that the length of first when it is declared is 0 (because it was not initialized and so is set to a null string). After the assignment, it becomes 7.

The + operator may also be used to concatenate strings. Technically what has been done is to overload the assignment operator and the addition operator for string objects. Operator overloading will be covered in detail towards the end of the course. To be consistent, the += operator is also used to add something to the end of an existing string.

stringClass.cpp
greetings += " world!"; cout << greetings << endl;

Arrays
arrays1.cpp
int a,c[10]; double x[22],y[22][10]; /* y[row][col] */

Dimensionality of the array must be declared so that compiler knows how much storage to set aside. Multidimensional arrays are declared as arrays of arrays (in row-column order). Beware of declaring large arrays as temporary variables - temporary storage is extremely limited. Declare as external or as static internal.

arrays2.cpp
void myFunction1(int x, int y) { double myArray1[1000]; // Dangerous! Just asked for 8000 bytes // ...etc // from stack } double myArray2[1000]; // Safe. All external variables are static void myFunction2(int x, int y) { // ...etc } void myFunction3(int x, int y) { static double myArray3[1000]; // ... etc } // Safe. Is in static store.

What is the difference between these two declarations? Which is preferred?

Use the external form except to explicitly share variables

arrays3.cpp
const int n = 25; int grades[n]; // Grades array shared between these routines

bool changeGrade(int mark, int i){ if ( (i < 0) || (i >= n) ) return false; grades[i] = mark; return true; } double classAverage() { int i, sum; for (i=0, sum = 0; i<n;i++) sum += grades[i]; return sum / n; }

Initialization of Arrays

arrays4.cpp
int a[5] = {0,3,2,4,1}; int m[3][4] = { {19, 27, 1, 6}, { 1, 0, 14, 17}, {-4, 3, 21, 0} };

(as usual compiler pays no attention to the white space)

Element References
arrays5.cpp
a[0] // Element zero of the array a[i] // Element i (the i'th element) a[3] // Element 3 m[2][3] // The element at row 2, column 3.

arrays6.cpp

/************* Duplicating an array *******************/ int a[5] = {0,3,17,51,-6}; int b[5]; b = a; /* This is ILLEGAL!!! Instead */ for (int i = 0;i<5;i++) b[i] = a[i];

In fact, we will see later on that it is possible to develop code in C++ which will allow arrays to be copied by the statement b = a where b and a are both arrays (even if they are a different size!). Passing arrays by value is problematic 1. Large amounts of data may have to be copied - slow! 2. The stack (temporary data store) may get over-run. 3. Preclude indefinite limits on array size to avoid (2). Arrays are passed by reference. In function prototypes why is n passed in? void dummy(int grades[], int n) is used to declare grades as an array of integers

C Style Strings
In C a string is an array of characters terminated by a null character ( '\0'). For example char stringArray[15] = "Hi!\n";

declares stringArray to be a an array of 15 characters. initializes it to the constant string "Hi\n" which is permanently embedded in the code.

How many characters do you think are in the constant string? In other words, how small could I make stringArray and still be able to hold all of "Hi!\n"? A function to return the length of a string

arrays7.cpp
long strlen(char str[]){ long i=0; while (str[i++]) ; return (i - 1); }

Passing Multidimensional Arrays


Unhappily, this isn't possible void matrixComp(double x[][]); because the compiler has no idea what x[i][j] means. There are two alternatives (a) void matrixComp (double x[][6]); i.e. declaring all dimensions except (possibly) the first one. Fine if you know the dimensionality beforehand e.g. computer graphics. (b) void matrixComp (double oneDimArray[], int cols); Treating the array as a one dimensional array and passing in the dimensionality yourself.

arrays8.cpp
double sumOf(double xArray[],int r, int c) { double sum; for (int i = 0;i<r;i++) for (int j=0;j<c;j++) sum += xArray[i*c+j]; return sum; }

In other words, multidimensional arrays are stored as follows: x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] x[10] x[11]

so x[1][3] is at x[1*4+3] = x[7]

Pointers
C++ Passes by Value
Recall the default for C/C++ is to pass scalars by value. The function

../review/functions4.cpp
// A totally useless function!!! void intSwap(int x1, int x2){ int temp; temp = x1; x1 = x2; x2 = temp; }

is perfectly legal, but will not have the intended effect.


x1 and x2 are true variablescan be used on the left hand side of an expression-but they are temporary copies of the original variables! Only the values of the original, external arguments are available to the function.

Note that y = sin(x) and y = sin(4) are equivalent. In the first case, the current value of the variable x is passed, in the second, the value 4 is passed directly.

Pass-by-Reference
In 1020, you learned that the solution to this problem is to pass parameters by reference

../review/functions5.cpp
// A useful function!!! void intSwap(int& x1, int& x2){ int temp; temp = x1; x1 = x2; x2 = temp; }

The type of parameters x1 and x2 is reference-to-integer. Note that intSwap(int& x, int& y)

intSwap(int & x, int & y) intSwap(int &x, int &y) are all acceptable but the first is preferred. Call to intSwap looks exactly the same as before

pointers1.cpp
int a=2,b=3; intSwap(a,b); cout << a << ", " << b;

Results in 3, 2 Referencing is done automatically. It is sometimes called passing by variable.

Pointers
C++ has a third, passing mechanism, similar to (but more primitive than) references. First we introduce two new operators & unary operator: references (gives the address of) an object. p = &c assigns the address of c to the variable p. p references or points to c. * indirection or dereferencing operator: Applied to a pointer, it accesses the object pointed to.

pointers2.cpp
int x = 1, y = 2; int* ip; // ip is a pointer-to-int ip = &x; // set the value of ip to the address-of-x y = *ip; // y is now 1 *ip = 0; // x is now 0 ++*ip; // increment x - unary ops right to left (*ip)++; // increment it again *++ip; // increments the pointer, then derefs it

Note the last usage is potentially erroneous. Incrementing the pointer means you have no idea where it is pointing (what it addresses) unless it is pointing into a well defined structure (such as an array). The declaration int* ip;

sets storage aside for a pointer to type integer. ip is not an integer --- it is a pointer to an integer no storage space is made for an integer (because there is no integer variable).

Now redefine intSwap to use pointers.

pointersIntSwap.cpp
void intSwap(int* px,int* py){ int hold; hold = *px; *px = *py; *py = hold; }

pointersIntSwapTM.cpp
Both calls to intSwap are legal and both are the same. The case is just like sin(x) vs sin(4). In either case C passes the value, which happens to be an address. Note difference between swap code at end of main and in intSwap function. What confuses neophyte C users is the difference between the function prototype intSwap(int* px,int* py) and the function callintSwap(&x,&y); but they are perfectly logical.

The prototype specifies that the values to be passed in will point at integers (ie, that they will be addresses). The call passes the addresses of the integers.

A second point of confusion is that to build functions like intSwap that operate on input variables, you have to operate indirectly, whereas if the code were not buried in a function, you could operate directly. The difference has been highlighted by carefully using different names for the pointer variables. This is something you would do well to copy.

Even Pointers are Passed by Value


A function to position a pointer at the end of string (C++ programmers would often say move the pointer to the end of the string)

pntrToEndBad.cpp
void toEnd(char* pString){ while(*pString) pString++; }

This example doesn't work because pointers are also passed by value! What is needed instead is

pntrToEndGood.cpp
void toEnd(char*& pString){ while(*pString) pString++; }

This says that pString is a pointer to char (char*) and it is passed by reference!

Pointer Arithmetic
Pointers allow us to work with computer addresses without ever knowing what the addresses are. This independence of actual addresses is important because

It allows code and data to be relocated when it is first loaded into the computer. It will allow data to be allocated dynamically at run time (for example, by using the new operator).

Pointer arithmetic supports address independence by allowing us to work with pointers relative to some base address. For example, another way to move a pointer to the end of a string would be to write

pointers3.cpp
void endStr(char*& beg) { beg += strlen(beg); }

See more examples at end of next section.

Pointer - Array Notational Equivalence


int a[10]; // An array of 10 integers int* pa, x;1 // A pointer to an integer and an integer pa = &a[0]; // pa points to element 0 pa = a; // equivalent to the previous line x = *pa; // contents of a[0] to x x = *(pa+1); // contents of a[1] to x

Whenever pa points to a then pa+i is the address of a[i] and *(pa+i) is the contents of a[i]. The expression pa+i is an example of pointer arithmetic. pa is a pointer variable whose value is an address. pa+i is an expression whose value is the address held in pa plus i times the size in bytes of the object pa points at. Pointers work in terms of base units. Thus
1

Note that, as with references, the declarations


int* p1; int * p1; int *p1;

are all equivalent. We use the first one because it highlights that the type of p1 is pointer-to-int. Unhappily, it leads to confusion in the following case:
int* p1,i;

which appears to declare both p1 and i as pointers-to-int. In fact, it is actually declaring p1 as a pointer-to-int and i as an int. For this reason, some people prefer the third form
int *p1, *p2;int *p3, j, k;

which makes it clear that p1 and p2 and p3 are pointers to int while j and k are int. We will stick with the first notation and insist that pointers be declared one at a time on their own lines, thus
int* p1; int* p2; int* p3; int j,k;

pointers4.cpp
#include <iostream> using namespace std; const int ASIZE = 7; int main(){ double array[ASIZE]; // Also recall that array is a synonym for &array[0] double* pBeg = &array[0]; double* pEnd = array + ASIZE - 1; while (pBeg <= pEnd) cout << *pBeg++; return 0; }

creates an array of doubles and a pair of pointers which are intialized to the first and last elements of the array respectively pEnd is initialised to array + ASIZE - 1. array is the same as &array[0] and is the address of the 0'th element of the array. ASIZE - 1 is an offset between the initial address of array[0] and the final address of array[SIZE - 1]. It actually represents (ASIZE-1) x the number of bytes required to hold a double

Strings Revisited
Although strings are arrays of characters, pointer notation is almost always used in connection with them. We will use string examples a lot. Students sometimes find this obsessive. (a) You MUST know both notations (both on exams) (b) We usually use pointer notation (the hardest one!) So what's the big deal? Are strings really that important? Well, Yes. But that's not why we study them. They are the fastest route into the heart of C. If you understand string routines, you have a big part of C. String length revisited

strLenArray.cpp
long strlen(char str[]){// Array notation long i=0; while (str[i]) i++; return i; }

strLenPointer.cpp
long strlen(char* p){// Pointer notation long i=0; while (*(p+i)) i++; return i; }

Note that all of these work in a function call strlen("Hello world"); /* string constant - Pointer is passed!*/ strlen(array); strlen(ptr); Note, finally, that although pointer notation and array notation can be regarded as equivalent, pointers are not arrays! The following two declarations do very different things: int myArray[20]; int* p = myArray;

More On Pointers
more1.cpp
+1 if first string > second string (comes later in a dictionary), -1 if first string < second string */ int cmpStr(const char* pS1, const char* pS2){ while (*pS1 ==*pS2 && *pS1) {pS1++; pS2++;} if (*pS1==*pS2) return 0 ; else if (*pS1>*pS2) return 1 ; else return -1 ; }

Comment at beginning to explain what function does. Needed as it is not selfexplanatory. const in front of pointers pS1 & pS2 says they are read only pointers. Check loop runs as long as characters are equal and we haven't run out of string 1. (What about string 2 ??) Notice, we didn't even need a temporary variable.

Constant Pointers
Compiler will not allow programmer to write through pS1 and pS2. But it can't stop the following

more2.cpp
int cmpStr(const char* pS1, const char* pS2){ char* pS3 = pS1; // copy string1 while (*pS1 ==*pS2 && *pS1) {pS1++; pS2++;} while (*pS3) *pS3++ = ' '; // overwrite string1!!!! return (*pS1==*pS2) ? 0 : (*pS1>*pS2) ? 1 : -1 ; }

To quote one author, This is evil, pure and simple! The coder has maliciously broken the contract implicit in the declaration.

Errors, Pernicious and Egregious (Pointers from Hell)

1. The Blindfolded Shooting Problem blindfolded.cpp


void strCpy(char* pDest, const char* pSource){ while (*pSource) { *pDest = *pSource; pDest++; pSource++; } *pDest = '\0'; }

Implicit in the declaration of strCpy is that the calling code will provide a pointer to somewhere to put the copy. salutation points to a constant string (put automatically by the compiler in the constant store) buffer is actually a pointer and it points nowhere!

2. The But its only one bullet problem! oneBullet.cpp


void charSwap(char* pLeft,char* pRight) { char* pTemp; *pTemp = *pLeft; *pLeft = *pRight; *pRight = *pTemp; }

3. The Dangling Pointer Problem dangling.cpp

/* A function to report a single error line */ char* errorReport(int e) { char buffer[81]; // A place to put the string char* pBuff = buffer; // A pointer to the string strCpy(buffer,"Error #"); // Start with "Error " moveToEnd(pBuff); // Do some other stuff here we don't need to worry about return buffer; }

Dynamic Memory Allocation


So far, we have two types of memory allocation 1. Static memory has permanent lifetime and is allocated from the long-term memory store. 2. Temporary memory exists only for the duration of a function and is allocated on the stack (scratch pad). Both types of allocation are done by the compiler. Request is made when the program is written. However, there is a need for memory allocation to be made at run time. e.g. 1. Computer graphics 2. Strings

Static Memory
Allocated when code is initialized (Main started up) Lifetime is permanent. Addresses are all known-pointers aren't required.

The Stack
Allocation is controlled by scope data created when function called Temporary lifetime Stack builds downwards-only a single pointer required

The Heap
Allocation is on the flywhenever the program requests it. Allocation is randommemory is allocated wherever a large enough chunk is available. Lifetime is still temporary, but completely under program control. A pointer is required for every chunk of memory granted.

allocated the next

As more memory is requested, it will be available space

When memory is relinquished, it leaves a hole

Now when Memory is allocated, it may not be able to use the hole because it is too big

Eventually, as memory is allocated and released, the heap becomes fragmented. Lots of hidden management problems requires a Heap Manager.

How does heap manager know memory is allocated or free? How does heap manager know the size of a chunk? What does it do about fragmentation?

C++ provides two operators for run-time memory management. newallocates memory deletereleases memory Syntax is new(nothrow) object or

new(nothrow) object [size] where the second syntax is for declaring an array of objects. e.g. int* p; p = new(nothrow) int [20]; allocates an array of 20 integers. new returns a pointer to the chunk allocated on the heap. If no chunk large enough is available, it returns a null pointer. The nothrow that appears in parentheses after the new operator is technically called an allocator. Its function is to prevent the new operator from throwing an exception (which we haven't studied) if no memory is available. To release the memory back to the heap the syntax is delete pointer; or delete [] pointer; // for arrays (Be sure the pointer you delete still points into the heap)

Typical Allocation of 3 Kinds of Memory

nothrow and the ANSI C++ Standard


There are many places in a program where there is a potential for error at runtime. For example

incorrect entry of data by a user

division by 0 or of a very large number by a very small one such that overflow occurs being unable to supply memory when requested.

Programs can't ignore these problems. They have to deal with them. One solution is to throw an exception when a problem occurs. It is then up to the programmer to detect the exception (which is called catching it) and do something about it. The standard defines two ways of dealing with a failure of new to allocate the memory. 1. Throw an exception. 2. return null. It is compiler-dependent as to which technique is the default. We will use null returning in this course. Unhappily, this can be done in two ways

int* p; p = new int [20];


as well as

#include new using namespace std; int* p; p = new(nothrow) int [20];


The top case invokes the default model of our particular compiler. Unhappily it is compiler dependent and will work differently under different compilers. The second case will work with any compiler.

Structures
Arrays are compound data in which all the data in the package is homogeneous that is, of the same type. Also possible to bundle up data of different types in a single package. This type of compound variable is known as a structure. In C++, the template for the structure is generally defined before any variables of the structure are created. Effectively, programmers get to create a new type of their own.

account.cpp
struct Account{ int number; double balance; bool overdraftAllowed; }; bool transaction(Account& anAccount, double amount){ bool allowed; if (amount < 0) { // then a debit if (anAccount.balance + amount < 0 && anAccount.overdraftAllowed) { anAccount.balance = anAccount.balance + amount; allowed = true; } else allowed = false; // not enough money } else { // deposit anAccount.balance = anAccount.balance + amount; allowed = true; } return allowed; }

In this example, the programmer has created a new date type called Account by declaring Account as a structure (the keyword is actually struct). account bundles up three pieces of data by enclosing them in a block surrounded by curly brackets. The data in the block are declared just like normal variables. They are not normal variables, however. The only variables in the program are johnsAccount and myAccount, two separate bundles of data of type account, each containing their own three values (plus the bool allowed in the transaction function). What appear to be variables inside the block structure are actually known as fields. When the transaction function gets called, note we have elected to pass anAccount in by reference. While this is done automatically for arrays (so the ampersand is not required), structures may be passed-by-value.

Which of course means that a copy would have to be made. That might or might not be expensive depending on the structure. As the programmer, you have to know.

Declaration of Structures
structure2.cpp
struct complex { double re; double im; };

There are variations permitted on this declaration technique, but we will concentrate on this one. Now we can declare variables of same structure struct complex z; complex z; /* C Declaration */ // C++ Declaration

in C, we can also use a typedef typedef struct { double re,im; }complex; complex x,y,z; Here we have defined the type complex as being an un-named structure. Note that, in C, future references to the tag complex must also include the word struct struct complex z So the explicit typedef declaration is preferred. In both C & C++, typedef can be used for other things. e.g. typedef int number; Here we've defined a new type, number, which is equivalent to an int. Later on, we could quickly change every number in the program to a long or a double just by changing the typdef.

Using Structured Variables


structure3.cpp
struct complex { double re, im; }; /* This example has been adapted to work with TM which does not yet support initialization of structures.*/ complex compAdd(complex x,complex y); int main(){ // TM doesn't support this initialization of structures // complex x = {0.0,0.0}, y = {1.0,1.0},z; complex x, y, z; x.re = x.im = 0.; y.re = 1.0; y.im = -1.0; z = compAdd(x,y); cout << "z is (" << z.re << " + j" << z.im << ")\n"; return 0; } complex compAdd(complex a1, complex a2) { complex z; z.re = a1.re + a2.re; z.im = a1.im + a2.im; return z; }

Complex Structures
a student record might contain a name, a mun no., and a set of courses and grades organized by term. This can quickly get complex! student record --> name (a string) mun id (a 7 digit string) a list of term records --> date (a date string) term (an integer in [1,8]) average (a real on [0.0,100.0] a list of marks --> course no. (a course id string) grade (integer [0, 100]) Note how things are embedded We've got arrays inside our structure & structures inside the arrays!

Here's how it is done in C++

structure4.cpp

struct mark { char course[5]; int grade; }; struct term { char startDate[11]; int termno; double avg; mark marks[6]; }; struct student { char name[20]; int id; term terms[10]; }; student mary,john; strCpy(mary.name,"Mary Mathias"); strCpy(john.terms[3].marks[2].course,"3891"); john.terms[3].marks[2].grade=65; strCpy(mary.terms[3].startDate,"09.08.1992");

Introduction to Classes
Introduction
You may have heard it said that C++ is an Object Oriented Language. Classes are the means by which we create the objects that are so central to the language. But what is a class? And what is an object? As you learn more and more about OOPS ( Object Oriented Programming Style) you will hear a number of different definitions of classes and their relationship to objects. At bottom, however, the fundamental ideas are these: A Class is a category. An Object is a particular instance of a class. For example

Category Person Bank Account Building Window

Instance me, you, John, Mary, mom, my doctor, the weather man my account, your account, IBM's account General Hospital, my house, 1412 Main St., Wal-Mart this window, Word window, C++ window, debug window

Categorization is fundamental to our understanding of the world. A category is a generalization, an observation that, at some level, there is a sameness about a group of things. For example,all bank accounts 1. can have money deposited into them 2. or withdrawn from them 3. will carry a balance (summarizing the total past history of our transactions to and from the account). Yet each account is different. Your account might have a thousand dollars in it while mine might be overdrawn. You certainly wouldn't want the bank to mix them up! Thus we say that 1. the behaviour of all accounts is the same (if you deposit money the balance increases)

2. but their individual states are different (your account currently has more money in it than mine). Similarity of behaviour is what defines a category or class Individuality of state defines an instance or object.

Modeling
While classes and objects represent the central mechanism of OOPS programming, its central objective is really modeling. That might seem strange and way too passive for something like, say, controlling a nuclear reactor. But think about it. You don't want just anybody controlling a nuclear reactor. Homer Simpson notwithstanding, control room operators have to be highly trained. They have to, at least at some level, have an understanding of the process they are controlling, or disaster ensues. That understanding does not have to be absolute. The physicist who conceives of a new type of reactor, The engineer who designs it and The operator who controls it, all work at a different level of understanding. Each of them carries around in their head a view of the reactor that, while it is particular, is nonetheless necessary for them to do their jobs. In other words, each of them carries a model of the reactor in their heads. If we want to build software to control a reactor we will also have to build into it some model of how the reactor works. In the OOPS methodology, classes are the mechanism we use to build models. If we were building a reactor control program we would probably uses classes to represent the reactor, the cooling rods, the piping, and a whole host of other things that I don't know about because I know very little about reactors. I lack, what is known as domain expertise. Without domain expertise it is impossible to build good programs. So let's switch our attention to things we do all know about.

Modeling is Specific
Modeling is not an absolute. For example, consider a car. You could probably list off all kinds of attributes of cars. Here are some examples: Car attributes: wheels (four), steering wheel, engine, transmission, serial no., make, model, year, upholstery, condition. What others can you think of? If I were to write a program that involved cars, these could all be useful things to know. But notice, their usefulness depends upon the type of program I'm writing. For example, if I'm writing a program to track parking permits (say at a large company or university) then I will want to know information such the colour, make, model, year and license number of each car. But I won't care about the fact that it has a spare tire or what its odometer setting is. On the other hand, if we're building a sort of Janes Guide to Automobiles, we'll care about model and make and engine specifications but we won't see a red 1938 Bugatti as being any different from a green one. A program to manage the inventory of a used car dealership would need different information again. Now we definitely want the odometer setting! So each of these programs would undoubtedly have a class Car. But the classes would be quite different. We're making different models.

The UML
The UML is the Unified Modeling Language, a language that OOPS designers use to design object-oriented systems. We'll hardly touch on it here, but some of its basic constructs are useful even at this level. In particular, we'll introduce you to

Class Diagrams
The first Class diagram is the simplest possible. It shows a single class, representing the way a used car dealership program might view a car. A class is represented by a box divided into three compartments The top compartment contains the name of the class. By convention, we will capitalize the first letter. i.e. the class for a car is called Car. The middle compartment contains attributes which are type-name pairs. For example, model, which is a String. The bottom compartment contains the operations which can be carried out on objects of the class. For example, any particular car in the inventory can be sold by calling its sell() operation.

Although we are showing only one class in this system ( a real one would undoubtedly contain many more), there would be many objects. Every automobile in a dealer's inventory would constitute a separate object, with it's own values for its attributes. Thus, while there might be three blue 1998 Cavalier coupes in the system, each would likely have a different odometer reading and perhaps different purchase and sticker prices. We say that the set of all values for the attributes of an object constitute the current state of that object.

Parking Control
The class diagram shown for a parking control system is a little more complex. Here we are showing three classes (again, real systems would have many more) and defining some relationships, called associations, between them.

Discuss 1. attributes 2. methods 3. associations 4. multiplicity

Classes

C++ is an Object-Oriented Language. Classes are the means by which we create objects. Classes are central to the true OOPS. 1. many programmers who claim to program in C++ don't. 2. true C++ programming is programming classes OOPS represents a genuine paradigm shift.

Because classes are so important you will hear many views of what they are:Classes are object factories Classes are the means of encapsulating data and functions Classes are categories Classes are types For example

classes1.cpp
class Complex { double re,im; public: double getReal(); // accessors double getImag(); void set(double r, double i); // mutator };

declares a class Complex that encapsulates both the real variables re and im as well as three function declarationstwo accessors: getReal() and getImag() which read data and one modifier: set(.) which changes data

Access Control

Members of a class (both data and functions) may be public or private default for classes is private Access may be switched back & forth-e.g.

classes2.cpp
class PubPriv{ int a,b; double f1(); public: int c; double f2(int a1); private: int e; int f3(double a1,double a2); }; // a,b and function f1 are private // by default

However, this is not very good style, particularly for small classes. Without a very good over-riding reason, stick to one private section and one public in each class. Normally, data is private, functions are public.

classes3.cpp
class WellStyled { public: bool setData(int a, int b); double whatIsX(); private: int v1, v2; double x; bool why; }

This is the embodiment of encapsulation. Data is hidden. Only the class functions are allowed access to it.

Definition of Class Functions


1. Short functions may be declared in line.

classes4.cpp
class Complex { public: double getReal(){return re;} double getImag(){return im;} void set(double r, double i){re = r; im = i;} private: double re,im; };

2. Alternatively, functions may be defined in the normal way except that they need a scope resolution operator. In such a case we first declare the class

classes5.cpp
class Complex { public: double getReal(); double getImag(); void set(double r, double i); private: double re,im; };

And then we implement it (usually in a separate file)

classes5.cpp
double Complex::getReal(){ return re; } double Complex::getImag(){ return im; } void Complex::set(double r, double i){ re = r; im = i; }

Note that functions defined outside the class operate normally, whereas inline functions are really more like macros. A call doesn't actually generate a call. InsteadThe code for the function is written in at every point it is used. Efficient for small functions since function calls take a lot of time.

Using Classes
Given the declaration above

classes5.cpp
int main() { Complex x,y; // Declares objects of class Complex x.set(1.,1.); y.set(-1.5,3.); double z = sqrt( x.getReal()*x.getReal() + x.getImag()*x.getImag() );

/* use real member function attached to objects of class Complex */ cout << x.getReal() << " + j" << x.getImag(); return 0; }

Scope rules work just as for variables-e.g.

classes7.cpp
Complex x; int main() { Complex y; // etc. .... } int f1(){ Complex x,y; // ... }

x is a global object f1 and main both have their own objects y f1 has its own object x

However, C++ allows f1 to access global x as follows:

classes8.cpp
int f1(){ Complex x,y; double z; z = ::x.getReal(); //... }

This use of the scope resolution operator :: to access globals works in C++ for variables as well as objects

Shouldn't X Have Access to it's Own Data?


Consider the line cout << x.getReal() << " + j" << x.getImag(); x is an object of class Complex. Surely it should know about it's own data. Why can't I write cout << x.re << " + j" << x.im; x does know about re and imbut it's not going to let you know about it. If it did, you could write x.re = 3.2; When you declare an object of class Complex, it's like buying a hard disk for your computer. You get the object, but it's sealed up no tampering! The only way to get data from your hard disk is through the interface. The only way to get data from an object of class Complex is via the public functions provided. In fact the class declaration plus the set of public functions is often known as the interface of the class.

classes9.cpp
/* The interface for class Complex */ class Complex { public: double getReal(); double getImag(); // ... };

Why Encapsulate?
Consider a case where a complex no. is not changed often but both kinds of co-ordinates are requested frequently.

classes10.cpp
#include <math.h> class Complex { public: void set(double real,double imag); double getReal(); double getImag(); double getMag(); double getAngle(); private: double re,im; //rectangular co-ordinates double mag, ph; // polar co-ordinates }; int main(){ Complex x,y,z; x.set(1.0,0.0); y.set(1.0,-1.0); z.set(3.2, 1.0); } void Complex::set(double real, double imag){ re = real; im = imag; mag = sqrt(re*re + im*im); ph = atan2(re, im); } double double double double Complex::getReal(){return re;} Complex::getImag(){return im;} Complex::getMag(){return mag;} Complex::getAngle(){return ph;}

Designer has opted to carry the overhead of remembering (instead of recalculating) magnitude and phase. Why is the user not allowed to tamper with the data? What does the code for set have to do? Designer can later change design of private part without affecting how the public part works.

Making a Class Out of Queue


classes11.cpp
enum status {UNDERFLOW,EMPTY,PENDING,FULL,OVERFLOW}; class Queue { public: void putItem(int); int getItem(void); status getState(void); private: int q[100]; // The queue array itself int* pHead; int* pTail; status current; };

We haven't shown the code for the functions, yet, because it is quite straightforward. Notice an immediate difference between the C module approach and the C++ approach. I can declare more than 1 queue in C++. Once the class is declared, I can declare as many Objects of the class as I like. Queue q1, q2, q3; // 3 queues of 100 integers.

Queue Class Problems & Issues


1. How do we initialize pHead and pTail pointers? 2. Why should the queue alway be 100 elements long? 3. Why should a queue just be for integers? Isn't a queue of printer jobs managed exactly the same way?

Running Examples
We are going to create three classes that we will improve as we go along. An array class, a string class and an Account class. Array and string will have to wait until the next topic.

The Account Class


Similar to what we did in the example of structure.

account1.cpp

/* The interface for a class to model bank accounts */ class Account { public: // Accessor functions double getBalance(); // Mutator functions void initialize(); // call once when account created void deposit(double amount); void withdraw(double amount); void changeOD(bool allowed); void setRate(double iRate); void setMonthly(double charge); void setItem(double charge); void monthEnd(); // The data that we must track for each account object private: double balance; double minBalance; bool overDraftAllowed; double interestRate; double itemCharge; double monthCharge; }; #include <iostream> using namespace std; int main(){ Account mine, yours; mine.initialize(); mine.changeOD(true); mine.deposit(100); mine.withdraw(200); mine.deposit(1500); mine.monthEnd(); cout << "my Balance is " << mine.getBalance() << endl;

yours.initialize(); yours.deposit(100); yours.withdraw(200); yours.deposit(1500); yours.monthEnd(); cout << "your Balance is " << yours.getBalance() << endl; return 0; } double Account::getBalance(){return balance;} // This function must be called ONCE when an ccount object is // first created void Account::initialize(){ balance = 0; minBalance = 0; overDraftAllowed = false; itemCharge = .25; monthCharge = 2.00; interestRate = .0175; } // assertion: amount is non-negative void Account::deposit(double amount) { if (amount >= 0) balance = balance + amount; } // assertion: amount is non-negative void Account::withdraw(double amount) { if (amount < 0) return; //Assertion failure if (!overDraftAllowed && balance - amount - itemCharge < 0) return; // overdraft not allowed balance = balance - amount - itemCharge; if (balance < minBalance) minBalance = balance; } void Account::changeOD(bool allowed){ overDraftAllowed = allowed; } void Account::setRate(double iRate){ interestRate = iRate; } void Account::setMonthly(double charge){ monthCharge = charge; } void Account::setItem(double charge){ itemCharge = charge; } void Account::monthEnd(){ withdraw(monthCharge - itemCharge); if (minBalance < 0) withdraw(interestRate*(-minBalance)); minBalance = balance; }

In C, we are able to bundle data and functions together into structures. Thus you built a model of an account that had data for the account and some functions that manipulated it. Now, in C++, our unit of modularization is the class. The account class lets us declare as many accounts (account objects) as we want. Each account has its own data. (Objects have their own state) The functions are used to manipulate the data of any of the account objects. (Classes are characterised by commonality of behaviour)

Constructors
Creating Objects
Automatic Storage: Requires that objects declared internally within a scope block must be created and destroyed each time the block is entered and left. That creation and destruction is effected by functions, called respectively, constructors & destructors.

Constructors

Have the same name as the Class May take arguments of any type except of their own class. Never have a return type, not even void.

constructors1.cpp
class Complex { public: double real() const; double imag() const; Complex(double r=0.,double i=0.); private: double re,im; };

Complex is the constructor Note the use of default arguments. Complex may be created with zero, one or two arguments.

Using the constructor

constructors2.cpp
int main(){ Complex x; Complex y(3.4); Complex z(1.0,-1.0);

// Complex zero // Complex real // Full Complex

cout << "The real part of z is "; cout << z.real(); return 0; }

x gets intialized to (0. + j0.)

y gets intialized to (3.4 + j0.) z gets intialized to (1.0 - j1.0) Take care to avoid ambiguity

constructors3.cpp
class Complex { public: double real() const; double imag() const; Complex(double r,double i=0.); private: double re,im; };

This is legal, but the constructor takes one or two arguments. Thus Complex x(); Complex y(3.4),z(1.,-1.); the declaration of x is now illegal.

Default Constructors
Note that we have been able to make plain declarations like Complex z before constructors were ever mentioned. What changed? 1. In the absence of a user defined constructor the compiler will create a default constructor of the form classname () i.e., the default constructor takes no arguments. 2. Once a programmer-defined constructor is created for a class, no default constructor will be created by the compiler even if the class has not been equipped with a constructor for no arguments. 3. The no argument constructor (often, confusingly, referred to as the default constructor whether created by compiler or programmer) is needed to create arrays of objects and new objects from the heap.

Final Thought
Note that the programmer provides the code for the constructor(s) and the compiler takes care of calling them.

Destructors
Are always called by the class name preceded by a ~ e.g. ~Window() is the destructor for a class Window Never have a return type or any arguments. Called automatically when an object goes out of scope

Running Examples
Now that we have contructors we can create reasonable versions of all our running examples. First we improve the Account class. Here is the declaration of the original class

../classes/account1.cpp

/* The interface for a class to model bank accounts */ class Account { public: // Accessor functions double getBalance(); // Mutator functions void initialize(); // call once when account created void deposit(double amount); void withdraw(double amount); void changeOD(bool allowed); void setRate(double iRate); void setMonthly(double charge); void setItem(double charge); void monthEnd(); // The data that we must track for each account object private: double balance; double minBalance; bool overDraftAllowed; double interestRate; double itemCharge; double monthCharge; };

And here is how we had to use it. Recall that before we actually could do anything with an Account object, we had to remember to initialize it.

../classes/account1.cpp

int main(){ Account mine, yours; mine.initialize(); mine.changeOD(true); mine.deposit(100); mine.withdraw(200); mine.deposit(1500); mine.monthEnd(); cout << "my Balance is " << mine.getBalance() << endl; yours.initialize(); yours.deposit(100); yours.withdraw(200); yours.deposit(1500); yours.monthEnd(); cout << "your Balance is " << yours.getBalance() << endl; return 0; }

Now we replace our initialize() function with a constructor Unlike the initialize() function the constructor is called automatically when we declare an account object

account2.cpp

/* The interface for a class to model bank accounts */ class Account { public: // Constructors Account(); Account(bool od); // Accessor functions double getBalance(); // Mutator functions void deposit(double amount); void withdraw(double amount); void changeOD(bool allowed); void setRate(double iRate); void setMonthly(double charge); void setItem(double charge); void monthEnd(); // The data that we must track for each account object private: double balance; double minBalance; bool overDraftAllowed; double interestRate; double itemCharge;

double monthCharge; };

One small problem with this is that if you examine the implementation code for the two constructors it is virtually identical

account2.cpp
Account::Account(){ balance = 0; minBalance = 0; overDraftAllowed = false; itemCharge = .25; monthCharge = 2.00; interestRate = .0175; } Account::Account(bool od){ balance = 0; minBalance = 0; overDraftAllowed = od; itemCharge = .25; monthCharge = 2.00; interestRate = .0175; }

This is a maintenance problem. It would be better to make it common. We re-introduce initialize() but as a private function

account3.cpp
class Account {

/* The interface for a class to model bank accounts */ public: // Constructors Account(); Account(bool od); // Accessor functions double getBalance() const; double getMinBalance() const; // Mutator functions void deposit(double amount); void withdraw(double amount); void changeOD(bool allowed); void setRate(double iRate); void setMonthly(double charge); void setItem(double charge); void monthEnd();

// The data that we must track for each account object private: double balance; double minBalance; bool overDraftAllowed; double interestRate; double itemCharge; double monthCharge; // a private initializer void initialize(bool od); };

initialize can only be called by the implementation code of the other functions in the class

account3.cpp
Account::Account(){ initialize(false); } Account::Account(bool od){ initialize(od); } // Common private initializer function void Account::initialize(bool od){ balance = 0; minBalance = 0; overDraftAllowed = od; interestRate = .0175; itemCharge = .25; monthCharge = 2.00; }

The Array Class


C style arrays are awkward and error prone. Let's build a better Array class.

constructorsArray.cpp
class Array{ public: Array(int s); ~Array(); int getSize() const; double read(int i) const; void write(int i, double item); private: double* mpData; int mSize; };

So far, pretty simple minded Array class. However, Array objects now know their own size.

Array objects only have 2 pieces of data, the mpData pointer and mSize. The constructor requests an allocation of space from the heap for the actual array data. the const keyword placed after a member function declaration says that the function is readonly. It will not alter the state of any object to which it is applied.

constructorsArray.cpp
// Implementation Array::Array(int s){ if (s > 0 && (mpData = new double[s])) { mSize = s; for (int i=0; i < mSize; i++) mpData[i] = 0.; } else { mpData = 0; mSize = 0; } } Array::~Array(){ delete[] mpData; } // valid even if mpData is null

int Array::getSize() const { return mSize;} double Array::read(int i)const { if (i<0 || i >= mSize) return 0.0; // poor error detection return mpData[i]; } void Array::write(int i, double item){ if (i<0 || i >= mSize) return; mpData[i]=item; }

Implementation of the class. How could we get the index to run from 1 to mSize instead of 0 to mSize-1?

A String Class
Again, C style strings are pretty primitive so we will roll our own.

myString.cpp
class MyString{ public: MyString(const char* p); // Construct using a standard string MyString(); // "default" (no arguement) constructor ~MyString(); // standard destructor to deal with heap

// Accessor functions - used to read object data without changing it int length() const; char getChar(int i) const; // get char at location i void get(char* buff) const; // Get the string & put it into user buff // Mutator functions - used to change string objects void setChar(const int i, const char c); // Change char at i to c private: char* mPtr; int mLength; }; // pointer into the heap where the actual string will be // length of the string

Let's look at the implementation for the constructors

myString.cpp
MyString::MyString(const char* p){ mLength = strLen(p); if (mPtr = new char[mLength+1]) strCpy(mPtr,p); else mLength = 0; } MyString::MyString(){ mLength = 0; if(mPtr = new char[1]) *mPtr = '\0'; } MyString::~MyString(){ delete []mPtr; }

Now we have a string class with some power! By going to the heap, we can create a string of any size at run time. There are only two actual data members of each string object, taking up very few bytes. The constructors take care of finding the actual space for a string object.

Copy Constructors
It is often the case that we would like to make an exact copy of an object This is often called cloning the object Making a copy means basically requires 1. we construct a new object (the clone) 2. we set its initial state to the current state of the object being copied. For this purpose we use a special form of constructor called a Copy Constructor We have already seen that C++ often makes copies automatically (pass-by-value) Copy constructors are automatically invoked three places 1. Initialization 2. Passing arguments in to a function by value 3. Returning values from functions. The latter two are implicit calls, hidden from the unwary user.

Format
recognized by its unique signature

copycons1.cpp
class X { public: X(); // Null constructor X(int,int); // Two argument constructor X(X&); // Copy constructor private: int f; int g; };

The above is a declaration for class X, stripped to its bare essentials. The copy constructor has as argument a reference to an object of its own class

(Remember - a normal constructor may not take an object of its own class as argument.) Question: Why a reference to an object of its own class? Why not just an object of its own class?

Definition
Here are the definitions for class X as they might be found in a separately compiled file:

copycons1.cpp
// constructor implementations X::X(){ // default f = g = 0; } X::X(int a1, int a2){ // 2 arg f = a1; g = a2; } X::X(X& orig){ f = orig.f; g = orig.g; } // copy constructor

The null constructor (no arguments) sets the f and g members of the object to 0 The two argument constructor sets the f member to the first argument and the g member to the second one The copy constructor copies f and g from the corresponding f and g of the object passed in.

Using the Constructors


(at declaration)

copycons1.cpp
int main(){ X one; // one.f = one.g = 0 X two(1,2); // two.f = 1, two.g = 2 //X three = {3,4}; // three.f = 3, three.g = 4 X four(two); // four.f = 1, four.g = 2 // X five = three; // five.f = 3, five.g = 4 return 0; }

In declaring object four, we used an object of class X as argument.


o

The compiler calls the copy constructor as the only one allowed to have an object of its own class as argument.

In declaring object five we initialized it to be the same as object three.


o o

The compiler calls the copy constructor for this initialization . In order to utilize the = sign for assignment, the = operator must be formally overloaded (see later).

Implicit Copy Constructor Calls


Let's ammend our code to add a static count variable.

copycons2.cpp
class X { public: X(); // Null constructor X(int,int); // Two argument constructor ~X(); // destructor needed to correct count int howMany() const; private: int f; int g; static int count; //Keep track of how many objects there are };

In a class, a static variable means there is only one count for the entire class, instead of one for each object of the class. static variables are used for things such as object counting, as here Here is the implementation code for the class:

copycons2.cpp
// constructor implementations int X::count = 0; //defines the class integer count X::X(){ f = g = 0; count ++; } // default

X::X(int a1, int a2){ // 2 arg f = a1; g = a2; count++; } X::~X(){ count--; } // destructor

The static variable has to be defined (implemented) just like a function of the class. Here we set it to 0. Each constructor adds one to the count The destructor decrements the count.

Voila! Object counting.

copycons2.cpp
X foo(X anX); int main(){ X one; // one.f = one.g = 0 cout << one.howMany() << endl; X two(1,2); // two.f = 1, two.g = 2 cout << two.howMany() << endl; foo(two); // just returns two cout << two.howMany() << endl; return 0; }

In this case we've gotten rid of our copy constructor and let the compiler build one Notice, when we call our silly fuction foo, count gets fouled up. The problem is that the compiler doesn't know about our counting objects, so it doesn't increment count. However, there is no copy destructor. The normal destructor gets used.

Another Example
As another example of implicit calling, we put a (rather poorly designed) add function in the class Array.

copyConsArray.cpp
class Array{ public: Array(int s); int getSize() const; double read(int i) const; void write(int i, double item); Array add(Array other); // add other to this and return sum private: double* mpData; int mSize; };

When I write in my code

copyConsArray.cpp
int main(){ Array smallArray(10); Array otherArray(10);

for (int i = 0; i < smallArray.getSize(); i++) { smallArray.write(i,i*i); // An array of squares otherArray.write(i,i); } smallArray.add(otherArray); // But what do I do with the sum?

The call smallArray.add(otherArray) is a request to add the elements of otherArray to those of smallArray and return a sum array. The implementation code for add is relatively straightforward.

copyConsArray.cpp
Array Array::add(Array other){ if (other.mSize != mSize) return *this; Array sum(mSize); for (int i = 0; i < mSize; i++) sum.mpData[i] = mpData[i] + other.mpData[i]; return sum; }

How many times is copying invoked?


Because other is passed by value, it is actually a copy of the original object otherArray. Because sum is destructed at the end of the add function and only its value is returned, the return object is actually made by copying sum.

If the designer doesn't provide a copy constructor, the compiler creates one automatically. The compiler's copy constructor is of the form Array::Array(const Array& original){ mpData = original.mpData; mSize = original.mSize; } In other words, the default copy constructor just copies the member data fields In the Teaching Machine you can see this somewhat indirectly. Since there is no actual function to call, it doesn't step into a copy constructor function, per se. Instead, if you step through the expression engine at the calling point you will see the member fields being copied. There is no copy destructor. Instead the regular destructor is called.

Shallow vs Deep Copy


The problem with the default copy constructor in the Array case is that it creates no actual array.

Recall that the constructor goes to the heap to get the memory required to restore the array. In the next example, we mimic the compiler's behaviour by providing a copy constructor that is the same as the default constructor the compiler would provide if we didn't. Here is the declaration of Array

shallowCopy.cpp
class Array{ public: Array(int s); // regular constructor Array(Array& original); // Copy constructor ~Array(); // destructor - used for both int getSize() const; double read(int i) const; void write(int i, double item); private: double* mpData; int mSize; };

And here is the implementation of the copy constructor

shallowCopy.cpp
// Just does a straight copy of all member data fields Array::Array(Array& original){ mSize = original.mSize; mpData = original.mpData; }

Now let's check out what happens when we try to use it.

shallowCopy.cpp
int main(){ Array A(10); set(A,3); Array B(A); set(B,7);

// B is a clone of A

We've created an array A then set all it's elements to 3 Then we cloned A to create B and set all B's elements to 7 Look what happens when we output A Be sure to check it in the linked view.

The difficulty is that our objects of Array class has two kinds of storage. 1. The member data, which is compiler controlled. 2. The actual array storage (which is on the heap) which is constructor and destructor controlled. In building the default copy constructor, the compiler only knows about the first kind of memory, so that's all it clones. In copying the pointer into the heap exactly, both objects end up sharing the same heap space. It's not a true clone. It's more like a pair of siamese twins with a single heart or head. We call such a copy a shallow copy.

Deep Copy
In order to clone an array properly we need to give the clone it's own heap space. Then the clone is completely independent of the original. We call such a copy a deep copy. Here's a copy constructor for Array that does a deep copy.

deepCopy.cpp
// Implementation Array::Array(int s){ if (s > 0 && (mpData = new double[s])) { mSize = s; for (int i=0; i < mSize; i++) mpData[i] = 0.; } else { mpData = 0; mSize = 0; } } // A "good" copy constructor, that is one which // does a proper deep copy. Note contrast to constructor Array::Array(const Array& original){ if (mpData = new double[original.mSize]){ // request same amt. of space mSize = original.mSize; // success! we got the space for (int i = 0; i < mSize; i++) // Copy original's heap data *(mpData+i) = *(original.mpData+i); } else mSize = 0; // Oops! no space. Note the problem } Array::~Array(){ // Works for both kinds of constructor

delete[] mpData; }

// valid even if mpData is null

Note both the similarities to and differences from the regular constructor. The copy constructor 1. requests new storage from the heap (same size as the original) 2. copies the data from the original's heap area to the clone's heap area. In running the example below make sure to look at both the linked view as well as the heap and stack.

deepCopy.cpp
// A function to set every element of an array to value void set(Array array, double value); int main(){ Array A(10); set(A,3); Array B(A); set(B,7);

// B is a clone of A

Do you see some unexpected behaviour? The set function, which worked fine before does not seem to be working. Here is the corrected version. What's the difference? Why did the "incorrect" version work before, but not now?

deepCopyFixed.cpp
// A function to set every element of an array to value void set(Array& array, double value); int main(){ Array A(10); set(A,3); Array B(A); set(B,7);

// B is a clone of A

Scroll down for the answer.

The incorrect version of set passes in an Array object by value. This means that a copy of the object gets made and the value gets set in that copy. This worked in the case of a shallow copy because the copy and the original Array shared the same data in the heap. So when that data area got set in the copy, it was set in the original as well. Once we created a true deep copy, the data area was set in the copy but not in the original and the set data was lost once the set function was ended. Passing the Array object to set by reference solves the problem.

Running Classes
We need to ammend our running classes to take copy constructors into account. We've already taken care of the Array class. The Account class doesn't need a copy constructor because it just uses standard memory so the compiler (default) version works fine. This leaves the MyString class. Like Array, it gets it's actual storage from the heap. Actually, there's a simple test for the need to build your own copy constructor. Did you need a destructor? If so, you virtually always require a copy constructor. Array and MyString needed a destructor to clean up the heap and our X class required one when we added a static Object count (to decrement the count whenever an object was destroyed). All of them need copy constructors. Account didn't need a destructor and it doesn't need a copy constructor. The defaults work fine. So, without more ado, here is the declaration for a MyString class with a proper copy constructor.

myStringCopyCon.cpp
class MyString{ public: MyString(char* p); // Construct using a standard string MyString(); // "default" (no arguement) constructor MyString(MyString& orig); // Copy constructor ~MyString(); // standard destructor to deal with heap

// Accessor functions - used to read object data without changing it int length() ; char getChar(int i) ; // get char at location i void get(char* buff) ; // Get the string & put it into user buff bool compare(MyString& other); // true if equal // Mutator functions - used to change string objects void setChar(int i, char c); // Change char at i to c void changeTo(char* newString); // Change the whole string void changeTo(MyString newString); // Function overload private: char* mPtr; int mLength; }; // pointer into the heap where the actual string will be // length of the string

Another feature has been added as well. The function changeTo( ) allows a whole string to be changed (even if the new string is a different length) Note there are two different versions of changeTo void changeTo(const char* newString); void changeTo(const MyString newString); The first one lets us provide a conventional C string The second version lets us use a MyString object to effect the change. We can overload conventional functions as well as constructors.

Implementation code
Here are the constructor implementations

myStringCopyCon.cpp
MyString::MyString(){ mLength = 0; if(mPtr = new char[1]) *mPtr = '\0'; } MyString::MyString( char* p){ mLength = strLen(p); if (mPtr = new char[mLength+1]) strCpy(mPtr,p); else mLength = 0; } MyString::MyString( MyString& original){ if (mPtr = new char[original.mLength + 1]){ // Request heap space mLength = original.mLength; // Success strCpy(mPtr, original.mPtr); // Copy actual string }

else mLength = 0; }

// Consistent with constructor

Notice again how similar the constructor and the copy constructor are. That is not a surprise since they fundamentally have to do the same thing build a valid MyString object. However there are subtle differencesthese stem from the fact that the source of the information required to build the object is different. Finally, although we could have used array notation (after all, conventional C strings are arrays of characters) pointer notation has been used, just to keep you in touch with it. Notice that mPtr is never actually moved (pointer + offset notation is used). mPtr marks the beginning of our heap storage and we never move it because our MyString objects could be destructed at any time. Here is the destructor

myStringCopyCon.cpp
MyString::~MyString(){ delete []mPtr; }

When delete is called on mPtr, we want it pointing at the beginning of our storage or we will confuse the heap manager.

A Compare Function
We also added a function to allow us to compare two MyString objects (If you invoke the TM here, just step into the compare function for now)

myStringCopyCon.cpp
bool MyString::compare(MyString& other){ if (mLength != other.mLength) return false; for (int i = 0; i < mLength; i++){ if (*(mPtr+i) != *(other.mPtr+i)) return false; // exit as soon as a difference } return true; // everything the same if arrive here }

This one only tells us if the objects are the same or not (later on we'll tackle the problem of which one is "greater") Notice we do a cheap test first. If the strings have different lengths they can't be the same.

Conventional Function Overloads


C++ allows any function to be overloaded. We already saw it for constructors. In a future chapter, we'll study overloads more formally. In the meantime, here is the implementation of the first version of changeTo

myStringCopyCon.cpp
void MyString::changeTo(char* newString){ long l = strLen(newString); if (l != mLength){ // if diff, re-allocate memory char* pTemp = new char[l+1]; if (!pTemp) return; //punt!! delete [] mPtr; // Release old storage mPtr = pTemp; // hook-up to new } strCpy(mPtr, newString); }

It takes a conventional string as argument. Since that string might be of a different length than our existing string we have to take care of that. The code looks similar to the copy constructor (look back and see). There is one crucial difference We have to deal with the fact that our object owns a pre-existing string. If we're going to create new space on the heap (because the length is different) we must make sure we release the old storage properly! Notice how careful we are. We first create a temporary pointer, pTemp, and ask the heap manager for space (by invoking new) Only if we succeed do we release the old space and reset mPtr to the new space. Here's the version of changeTo overloaded to accept a MyString object as the source of its new string.

myStringCopyCon.cpp
// Overloaded version of changeTo to allow the string to // be changed to equal another MyString object void MyString::changeTo(MyString newString){ changeTo(newString.mPtr); // Just convert to conventional call }

Notice how simple it is. All it does is call the first version, passing it the internal mPtr of the source object. Why did we bother? In our test code we have the following:

myStringCopyCon.cpp
MyString password("bfltpx"); password.changeTo("btfplk"); password.changeTo(unlucky); // Hah! Get that one. //Oops! I spelled it wrong // Heck with it, I'll never remember it!

When we wanted to use the unlucky MyString object to change our password why not just give it the pointer directly? password.changeTo(unlucky.mPtr); // Heck with it, I'll never remember it!

The answer, of course is that we can't. mPtr is private! We're not allowed to manipulate it from code outside the class code. But the class code can. So we provide a simple overload that passes the pointer in for us.

Embedding Classes into Classes


We have been using variables as object attributes. We can also use other objects. In the User class below (which represents a user in a computer system) we are using MyString objects for mName,mUname and mPassword.

user.cpp
// A class of users for network management class User{ public: User(char* n, char* u, char* p); // Accessors MyString name(); int allocation(); // return mName // Disk space allowed

MyString password(); // return value MyString uname() ; bool confirm(const MyString& uName, const MyString& pass); // Mutators bool setPassword(const MyString& p1, const MyString& p2); void setAllocation(int a); private: MyString mName; // Notice attributes that are OBJECTS! MyString mUname; MyString mPassword; int mAllocation; // Memory allocation, in MBytes };

Notice there is only a constructor. We haven't supplied either a destructor or a copy constructor. (Remember, if we need one, we need the other. Conversely, if we don't need one, we don't the other.) But do we need them? First let's focus on the constructor. We introduce a new formatthe initialization list.

user.cpp
User::User(char* n, char* u, char* p) : mName(n), mUname(u),mPassword(p){ }

the list is in the form : mName(n), mUname(u),mPassword(p) and it precedes the body of the constructor (which is empty in this case although it doesn't have to be) Remember, the compiler allocates space for the object's attributes before it calls the constructor That gives us a problem because the attributes are objects. Allocating space for them means calling their constructors! C++ gets around the problem by setting up the initializer list. It's meaning is simple. The arguments passed into the User constructor, n, u, and p (which are pointers to conventional C strings) are passed straight to the mName, mUname and mPassword constructors. Since they are all MyString objects, it's actually three calls to the MyString constructor, applied to the three different objects. Why don't we just step into the User constructor once or twice and watch this work.

user.cpp
User mpbl("Michael Bruce-Lockhart", "mpbl", "bftplx"); User theo("Theo Norvell", "theo", "charl1eB");

Automated Copy Construction


Why don't we need a copy constructor? All the attribute objects need space from the heap. The answer is that it is the MyString class which have to go to the heap and MyString does have a copy constructor. We don't need one for User, because User objects just have plain, non-static attributes. The compiler knows how to take care of them. Step in and see.

user.cpp
User mpblClone(mpbl);

Constructing Anonymous Objects


Finally, consider setting the password in User. The function bool setPassword(const MyString& p1, const MyString& p2); requires a pair of MyString objects as arguements. But what if we don't have any handy? What if we want to use simple C string constants? We could declare a couple of MyString objects from the strings, then use them, like this. MyString pass1("unlucky"); MyString pass2(pass1); mpblClone.setPassword(pass1, pass2); but it's inelegant. pass1 and pass2 will be hanging around long after we need them. Instead of naming a couple of objects we can construct anonymous (unnamed) objects on the fly, like this.

user.cpp
//mpblClone.setPassword(MyString("unlucky"), MyString("unlucky")); MyString noLuck("unlucky"); mpblClone.setPassword(noLuck, noLuck);

Now they are built just at the point we need them (the call to setPassword) and disappear like smoke when we're done with them.

Summary
Copy constructors are used to create new objects by cloning an existing object Their signature is the same as a constructors except they take a reference to an object of the same class as their argument. Pass-by-value (either when entering a function or returning a value) both involve copying and therefore call the copy constructor. If you don't provide a copy constructor, the compiler will build one for you (it will simply copy the attributes of the original object). If a constructor allocates extra memory on the heap and the copy constructor only copies the original object's attributes, it is called a shallow copy. Copying both the extra heap space as well as the original attributes is known as making a deep copy. If you need a destructor, you need a copy constructor (and vice versa). A static class variable means there is only a single copy of the variable for the whole class (objects don't get their own copy). Static variables are used for class maintenance (like object counting). The presence of static variables or the need to go to the heap for extra space are strong indicators that a destructor and copy constructor are required. Any function in C++ can be overloaded. Classes may use objects of other classes as attributes. It is possible to create anonymous objects on the fly.

Inheritance
Inheritance is one of the most powerful tools in the Object Oriented Programming Style (OOPS) It is one (of a number) of techniques for making code reusable It allows one to extend a piece of existing code by specialising it. It models a very particular kind of relationship between classes known as polymorphism. This is often known as the "is-a" relationship. Consider two of the classes we have been working with recently. Account and User Both are representative of a kind of generic category. Consider the table below.

General Class Specialty Classes

Account Savings Account Checking Account Line of Credit

User Undergraduate Graduate Faculty

Savings and checking accounts and lines of credit are all special kinds of accounts. They all share common characteristics, such as a balance and an ability to put money in or take it out. But they all have special characteristics as well. Lines of credit normally have negative balances and large overdraft limits, while savings accounts pay interest. Similarly, different kinds of computer accounts have different policies applied to them. For example, in our institition, undergraduates pay for printing while faculty don't. Graduate students fall in between, getting an allocation large enough to do a thesis, but paying if they exceed the allocation. This kind of organization scheme is quite general. Remember that, at bottom, classes represent categories. Human categorization of knowledge is not confined to single categories. Rather we use sub categories and more sub categories, organizing knowledge into hierarchies. Thus human beings are hominids which are primates which are mammals That is humans are a kind of hominid which in turn is a kind of primate which is a kind of mammal. All mammals have warm blood.

All primates have large brains and opposable thumbs. As mammals they also have warm blood. (In the OOPS world we would say they inherit the characteristic of warm bloodedness.) All hominids walk on two legs. (They also have opposable thumbs and large brains, because they are primates, and warm blood because primates are mammals.) Humans have a soft palate. (They also walk on two legs because but you get the picture) In OOPS, a sub-category is called a sub-class. And objects of sub-classes inherit all the attributes and methods of their super class(es). Before turning our attention to Users and Accounts let's look at a very simple minded system.

simpleInherit.cpp
class A { public: void aFunc() ; protected: int x1; private: int x2; } ; class B : public A { public: void bFunc() ; protected: int y1; private: int y2; } ; class C : public B { public: void cFunc() ; private: int z ; } ;

This system forms a hierarchy with three generations. You can think of A as the grandparent, B as the parent and C as the child. We say that bObj is a B (an object of class B). But it is also an A. Likewise, cObj is a C as well as a B as well as an A. Thus an object of class C has five attributes (its own z plus y1 and y2 from B and x1 and x2 from A) And it has three methods (its own cFunc() plus bFunc() from B and aFunc() from A). We have also introduced a new access modifier, protected. Protected members of a class are accessable to sub-classes of the class but not to the outside.

Here is the implementation code for the three methods declared in the classes A, B and C.

simpleInherit.cpp
void A::aFunc() { x2 = 53; } void B::bFunc() { x1 = 7; y2 = 9; aFunc(); } void C::cFunc() { x1 = 13; y1 = 72; z = 99; bFunc(); }

the code in the implementation for cFunc()


can set z directly (because it belongs to C) can set y1 and x1 directly (because, although they belong to classes B & A respectively, they are protected and thus available to C objects) must go through the public interface function bFunc() to set x2 and y2 as they are private.

Access Modifiers
Let's look at our simple hierarchial class declarations one more time

simpleInherit.cpp
class A { public: void aFunc() ; protected: int x1; private: int x2; } ; class B : public A { public: void bFunc() ; protected: int y1; private: int y2; } ; class C : public B { public: void cFunc() ;

private: int z ; } ;

Note that the syntax class B : public A means that class B is publicly derived from A It is also possible to derive a class privately from another. To see what that means consider the following table:

Access in Base Class public private protected public private protected

Access Modifier public public public private private private

Access in Derived Class public can't access protected private can't access private

Notice that the derived class cannot loosen the security defined by its parent. It can only tighten it. Thus public members stay public if publically derived but become private if privately derived. Private members of the parent class are inaccessable by children. Protected mebers of the parent class stay protected if publicly derived but become private if privately derived.

access.cpp
class A { int i,j; protected: int k; public: int george(); }; class B: public A { int l; public: int m; int john(); };

class C: private B { int n; public: int alice(); };

What variables may john(), george() & alice() access? What access is permitted for the objects of classes A, B & C?

Extending Class Account


Let's apply these ideas to extending class Account to Savings Accounts and Checking Accounts. Here is the Accounts class as we left it.

../constructors/account3.cpp
class Account {

/* The interface for a class to model bank accounts */ public: // Constructors Account(); Account(bool od); // Accessor functions double getBalance() const; double getMinBalance() const; // Mutator functions void deposit(double amount); void withdraw(double amount); void changeOD(bool allowed); void setRate(double iRate); void setMonthly(double charge); void setItem(double charge); void monthEnd(); // The data that we must track for each account object private: double balance; double minBalance; bool overDraftAllowed; double interestRate; double itemCharge; double monthCharge; // a private initializer void initialize(bool od); };

When you start to think in terms of a hierarchy of classes, it is best to rethink them from scratch.

First, what is common to all accounts? That's what should go into the base class.

accountInherit.cpp
// The base class - distills what is common to all accounts class Account { public: // Constructors Account(); // Accessor functions double getBalance() const; // Mutator functions void credit(double amount); void debit(double amount); // The data that we must track for each account object protected: double balance; };

The base class is quite stripped down. It just has a balance and four functions. Moreover, the names of deposit and withdraw have been changed to the more generic credit and debit (which are better terms for charge accounts, like VISA). These functions are implemented in a very generic way

accountInherit.cpp
Account::Account(){ balance = 0; } double Account::getBalance() const {return balance;} // Generic approaches to mutators. Not right for all accounts // Assertion: amount is non-negative void Account::credit(double amount) { if (amount >= 0) balance = balance + amount; } // Assertion: amount is non-negative void Account::debit(double amount) { if (amount > 0 && amount <= balance ) balance = balance - amount; }

Now let's look at extending Account to checking accounts.

accountInherit.cpp
class Checking: public Account{ /* Checking is derived from Account Checking accounts are accounts that permit overdrafts and charge interest on negative minimum monthly balances */ public: Checking(bool od); double getMinBalance() const; void setODPolicy(bool allowed); void monthEnd(); void debit(double amount); // Over-ride default version in account // Set rates for all checking accounts static void setRate(double iRate); static void setMonthly(double charge); static void setItem(double charge); protected: bool odAllowed; double minBalance; // All checking accounts will use the same rates static double interestRate; static double monthlyCharge; static double itemCharge; };

Checking is publicly derived from class Account. It adds the notion of minimum balance as well as various charges. It over-rides the debit function because the generic one can't do the job. But not the credit function because the generic one in Account works just fine. Notice, however, all the charges are static. This means that there is one set of charges for all Checking account objects. The member functions to set charges are also static. What does this mean? After all there's only one actual member function for a whole class anyway. Static functions are called with respect to the whole class not a particular object of the class. That is to invoke setRate for the Checking class I would write Checking::setRate(.02); The real benefit of this is that we don't have to create any Checking objects before invoking the function.

With regular member functions, you must have an object before you can call the function. Here's the extension to savings accounts

accountInherit.cpp
class Savings: public Account{ /* Savings is derived from Account Savings accounts are accounts that permit no overdrafts but pay interest on positive minimum monthly balances */ public: Savings(); double getMinBalance() const; void debit(double amount); // generic function override void monthEnd(); static void setRate(double iRate); static void setMonthly(double charge); static void setItem(double charge); protected: double static static static };

minBalance; double interestRate; double monthlyCharge; double itemCharge;

It's quite similar but the policies will be different. For example there is no bool odAllowed attribute because no savings accounts are ever allowed to go into overdraft. Here is the implementation code for Checking objects.

accountInherit.cpp
double Checking::interestRate = .0175; double Checking::monthlyCharge = 2.00; double Checking::itemCharge = 0.0; // overdrafts

void Checking::setRate(double iRate){interestRate = iRate;} void Checking::setMonthly(double charge){monthlyCharge = charge;} void Checking::setItem(double charge){itemCharge = charge;} Checking::Checking(bool od){ // Account constructor called 1st odAllowed = od; minBalance = 0.; } double Checking::getMinBalance() const {return minBalance;}

void Checking::setODPolicy(bool od){ odAllowed = od; } void Checking::debit(double amount){ if (amount < 0) return; // assertion failure if (!odAllowed && balance - amount - itemCharge < 0 ) return; // overdraft not allowed balance = balance - amount - itemCharge; if (balance < minBalance) minBalance = balance; } void Checking::monthEnd(){ // over-rides Account version debit(monthlyCharge-itemCharge); if (minBalance < 0) balance = balance + interestRate*minBalance; minBalance = balance; }

Note that the debit function is much more complex than the one in Account since it has to take both charges and overdrafts into account. Finally here is some code that utilizes the various kind of accounts.

accountInherit.cpp
Account mine, yours; // "Generic" accounts

// With static functions we can set rates for all savings accounts // without actually having declared any Savings Object yet Savings::setItem(.50); Savings::setMonthly(1.50); Savings::setRate(.002); // Now we declare some Savings Accounts Savings mySavings, yourSavings; Checking myChecking(true), yourChecking(false); mine.credit(200); mine.debit(100); mine.credit(1500); cout << "my Balance is " << mine.getBalance() << endl; mySavings.credit(200); mySavings.debit(100); mySavings.credit(1500); mySavings.monthEnd(); cout << "my Savings Balance is " << mySavings.getBalance() << endl; // How come we didn't get any interest?? mySavings.debit(100); mySavings.credit(2000); mySavings.monthEnd(); cout << "my Savings Balance is " << mySavings.getBalance() << endl; myChecking.credit(100); myChecking.debit(200); myChecking.credit(1500); myChecking.monthEnd();

cout << "my Checking Balance is " << myChecking.getBalance() << endl; yours.credit(100); yours.debit(200); yours.credit(1500); cout << "your Balance is " << yours.getBalance() << endl; // Let's try using the transaction function with different Accounts transaction(mine, 200.); transaction(mine, -100.); transaction(mySavings, 200.); transaction(mySavings, -100.); return 0; }

Pay particular attention to the constructor calling sequences (by stepping into them in the TM)

Extending Class User


Our User class created a very simple model of computer users. Now let's extend it to deal with different kinds of usersfaculty & studentsby first grouping what is common about the two in a single User class Then derive the two new classes from this base class. First we have to change the base class a little

userInherit.cpp
// A class of users for network management class User{ public: User(char* n, char* u, char* p); // Accessors MyString name() const; int allocation() const; // Disk space allowed MyString password() const; MyString uname() const; bool confirm(const MyString& uName, const MyString& pass) const; // Mutators bool setPassword(const MyString& p1,const MyString& p2); void setAllocation(int a); protected: MyString mName; // Notice attributes that are OBJECTS! MyString mUname; MyString mPassword; int mAllocation; // Memory allocation, in MBytes };

The only change is to use the new access control protected where we had private before. This allows the class to be extended, giving member functions of derived classes direct access to this data.

Specialization to Student userInherit.cpp


class Student: public User{ public: Student(char* n, char* id, char* u, char* p); // Accessors MyString id() const; // Mutators void buy(long amount); bool approvePrint(int pages); protected: MyString mId; long mPrintPennies; };

Students have id numbers and they have to pay for printing so two attributes have been added mId records their id mPrintPennies keeps track of how much money they have in their print accounts a number of operations have been included to handle these extra attributes.

Constructor Chaining
Recall that a Student object is a User object so that constructing a Student object also implies constructing the User part of it. The constructors are chained. when a Student object is constructed its User part must be constructed first. That is, declaring a Student object 1. invokes the Student constructor which in turn 2. invokes the User constructor which, when it has finished 3. returns to the Student constructor which then does its initialization As you can see, the Student constructor takes four arguments. Three of them, however, are needed by the User constructor. How do they get passed along? One again the answer is to employ an initializer list

userInherit.cpp

User::User(char* n, char* u, char* p) : mName(n), mUname(u),mPassword(p),mAllocation(0){ } Student::Student(char* n, char* id, char* u, char* p) : User(n,u,p),mId(id){ // Initialization - data to User constructor mAllocation = 5; mPrintPennies = 0; }

Here we show the User constructor, which as was seen previously, uses an initializer list to pass arguments on to the various MyString constructors. Displayed right after it for comparison is the Student constructor. It uses the initializer list to pass the three arguments needed by the User constructor As well as to pass the id string to the MyString constructor to build the mId object Finally, in the body of the constructor, the allocation and number of print pennies are initialized.

Constructor Chaining for Class User

(The arguments are actually passed as pointers. We show strings pointed at for clarity)

Specialization to Faculty userInherit.cpp


class Faculty: public User{ public: Faculty(char* n, char* u, char* p, char* today); // accessor functions long pages() const; MyString lastCleared() const; // mutator functions bool approvePrint(int pages); // increases page count void clear(char* d); // clears count & notes date protected: long mPages; // Pages used since MyString mCleared; // last date cleared };

Faculty members get free printing but a record of total pages is kept. It can be cleared from time to time so the record is actually the number of pages printed since the last clearing date. This requires the addition of attributes mPages which tracks pages mCleared which is a MyString object which records the last time pages were cleared plus new functions for manipulation. The constructor issues are similar to those for class Student. Here is a little bit of driver code to try it out.

userInherit.cpp
int main(){ Faculty mpbl("Michael Bruce-Lockhart", "mpbl", "bftplx", "11/11/2002"); Student cullam("Cullam Bruce-Lockhart", "2001001762", "cullam", "oBiewan"); // Faculty mpblClone(mpbl); mpbl.setAllocation(100); cullam.setAllocation(10); cullam.buy(1000); // $10.00 printing credit return 0; }

Use the Teaching Machine to step through the constructor chains when objects of class Student and Faculty get declared.

Again, watch how automated the process is. We take care of implementing the code correctly. The compiler worries about which constructors have to be called when.

Copy Constructor Chaining


Copy constructors for base classes are chained just like regular constructors. For example, suppose that we needed an explicit copy constructor for both User and Student (we don't). Then the implementation of the Student copy constructor would go as follows:

Student::Student(const Student& original) : User(original) { // Extra code for student copy construction goes here }
Again, the idea is to reutilize the copy constructor code for the User object, then just add whatever extra is needed to copy construct a Student object. If the copy constructor code for Student is to be written from scratch, simply omit the initilizer invocation of User.

Ad Hoc Polymorphism
Polymorphism: n. 1. Biology. The occurrence of different forms, stages or colour types in individual organisms. 2. Chemistry. Chrystallization of a compound in at least two different forms. To which we add3. OOPS. The ability to give different meanings to the same message. For example a + b could result in addition when applied to integers but concatenation when applied to strings. How the message is to be interpreted depends upon the type of the data it is being applied to. ad hoc: Latin. With respect to this (particular thing); for a specific purpose, case or situation.

The Signature Matching Algorithm


The argument type list of a function forms its signature.

The signature does not include the return type Order of arguments does matter.

overload1.cpp

/* A pair of declarations with identical signature. The compiler will complain! */ int f1(int i); // signature is int double f1(int a); // signature is int // Order of arguments does matter. // double // int,double // double,int

void f2(double a); void f2(int a,double b); void f2(double a,int b);

Overloaded Function Selection Algorithm

1. Use an exact match if found 2. Try standard type promotions. 3. Try standard type conversions. 4. Try user-defined conversions

5. Use a match to ellipsis if found (we won't even talk about this one)

overload1.cpp
f2(3.14159); // exact match to double f2(6); // promotes and uses double f2(3.7,9); // matches double,int f2(1,13.1); // matches int,double long i = 3; f2(i,4.1); // converts and uses int,double f2(3,4); // ambiguous, no match f2(1.7,-3.2); // ambiguous, no match f2(3,4,5); // no match for three arguments/*#HB*/

Example: Overloading the Swap Function


Here we declare a whole bunch of different versions of the swap function

overloadSwap.cpp
// The declarations void swap(char& left, char& right); void swap(int& left, int& right); void swap(long& left, long& right); void swap(float& left, float& right); void swap(double& left, double& right);

Here are a couple of the implementations Look how similar they are!

overloadSwap.cpp
void swap(char& left, char& right){ char temp; temp = left; left = right; right = temp; } void swap(int& left, int& right) { int temp; temp = left; left = right; right = temp; }

Finally, here are some calls. Which one gets called?

overloadSwap.cpp
int i1 = 3; int i2 = 5; char c1 = 'a'; char c2 = 'z'; float f1 = 3.14; float f2 = 0.; double d1 = 3.14; double d2 = 0.; swap(i1,i2); swap(c1,c2); swap(d1,d2); swap(d1,i2); swap(f1,f2);

//

//ILLEGAL! Compiler will complain

You might consider what would happen to the last call if we hadn't defined a version of swap for the rarely used float type.

Operator Overloading
In order to overload operators you must be allowed to write your own operator functions. the names of such functions always consist of the word 'operator' immediately followed by the actual operator, for example operator+ operatoroperator& operator[]

All C++ operators except the member operator ., the member object selector operator .* (not covered yet), the conditional operator ? :, the sizeof operator and the scope resolution operator :: may be overloaded. Some operators are unary (take one operand): operator++ Some operators are binary (take two operands): operator&& Some operators can be unary or binary, depending upon the context: operator-, operator* we consider binary & unary operators separately

Binary Operators
We'll start by overloading the + and - operators for class Complex. As with any function there are three contexts in which a function can appear. Declaration, definition (implementation) and call. These three contexts are sufficiently important that member functions are sometimes given distinct names depending upon the context, as follows:

operation: the name given to a member function in a class declaration (or interface). Operation suggests what we want done method: the name fiven to a member function when it is being implemented. Method suggests how we do it. message: the name being given to a member function when it is being called. Message suggests we are telling the object to do it. Unfortunately, not all authors observe these distinctions. Sometimes the names get used interchangeably. Here is class Complex with a couple of operator operations declared.

complex1.cpp
class Complex { public: // These three should be replaced by // Complex(double r = 0., double i = 0.); TM unsupported Complex(double r, double i); Complex(double r); Complex(); Complex operator+(const Complex& rhs); Complex operator-(const Complex& rhs); private: double re,im; };

The declaration Complex operator+(Complex& rhs) says that we want a plus operation that will add rhs to the calling object and return a sum object Of course, we infer that the object returned is the sum, but it makes sense given the name. Use comments to clarify and define. The operators only take one argument because, as member functions, there is an implicit this argument in the form of the object attached to the method when it is invoked. Now let's look at the implementation of the operator methods.

complex1.cpp
Complex Complex::operator+(const Complex& rhs){ return Complex(re+rhs.re,im+rhs.im); } Complex Complex::operator-(const Complex& rhs){ return Complex(re-rhs.re,im-rhs.im); }

The implicit argument is taken to be the left operand of the operator and the explicit argument, the right operand. Notice we are required to return a Complex object. Since that is returning a value it would normally invoke the copy constructor. This is supressed here, however, because we are constructing (anonymously) the new sum (or difference) object right in the return statement. The interesting issue is how do the calls work? (How do we send the add and subtract messages?)

complex1.cpp
int main(){ Complex x(-1., -1.); Complex y(2.2); Complex z; z = x + y; z = x - y; return 0; }

The crucial statements are z = x + y; and z = x - y; They are conventional add and subtract operations. What does that have to do with our operations? The compiler automatically maps the expression x + y x.operator+(y) and x - y x.operator(y) That is, the general form

leftOperand rightOperand
is mapped by the compiler to

leftOperand.operator(rightOperand)
Where

is any binary C++ operator for which overloading is permitted.

Step into the code above with the TM and watch what happens when you step through the expression and reach the plus sign.

friend functions
Unfortunately, the mapping above has a fairly serious limitation. Sometimes, you want to do operations between types. For example, it makes sense to scale a Complex number by a real no. This we could do as follows: Complex operator*(double rhs) Thus in a set of statements like Complex x(2., -3.4); Complex z; double a; z = x*a; The term x*a would get mapped to x.operator*(a) as expected. Expectation is the problem, though. A user of your class, not as familiar with the intricacies of overloading as all of you are, might reasonably expect to be able to write z = a*x; However the standard mapping would produce a.operator*(x) which doesn't work at all. Not only is that form not declared, but it can't be declared. a is a double, not an object, so we can't define operations or methods for it. Enter friend functions It is possible to declare a function to be a friend of a class.

complexFriend.cpp
friend friend friend friend friend friend Complex Complex Complex Complex Complex Complex operator+(const Complex& lhs, const Complex& rhs); operator-(const Complex& lhs, const Complex& rhs); operator+(double lhs, const Complex& rhs); operator+(const Complex& lhs,double rhs); operator-(double lhs, const Complex& rhs); operator-(const Complex& lhs,double rhs);

friend functions are not members of a class but they do have access to the private members of a class (just as normal member functions do) a function which is to be a friend of the class must be declared as such within the class. binary friend functions require two arguments because, as a non-member function of the class, there is no implicit argument. Here is the implementation of the functions

complexFriend.cpp
Complex operator+(const Complex& lhs, const Complex& rhs){ return Complex(lhs.re+rhs.re, lhs.im+rhs.im); } Complex operator-(const Complex& lhs, const Complex& rhs){ return Complex(lhs.re-rhs.re, lhs.im-rhs.im); } Complex operator+(double lhs, const Complex& rhs){ return Complex(lhs + rhs.re, rhs.im); } Complex operator+(const Complex& lhs, double rhs){ return Complex(lhs.re + rhs, lhs.im); } Complex operator-(double lhs, const Complex& rhs){ return Complex(lhs - rhs.re, rhs.im); } Complex operator-(const Complex& lhs, double rhs){ return Complex(lhs.re - rhs, lhs.im); }

Because they are not members of the class, friend functions are implemented just like normal functions. Complex:: is not in the header Notice that the moment we start mixing types, we generate a lot of possible operations.

Why two forms?


The first form is purer. The operator is a member of the class & there is no need of friends. The second form is more flexible, vis

Explicit arguments permit mixed operations. Operators can be declared friends of more than one class (again supporting mixed ops)

Iterator Overload
Remember our Array class?

../constructors/copyConsArray.cpp
class Array{ public: Array(int s); int getSize() const; double read(int i) const; void write(int i, double item); Array add(Array other); // add other to this and return sum private: double* mpData; int mSize; };

The read and write functions are clumsy. Wouldn't it be nice to use standard [] array notation with it?

array.cpp
class Array{ public: Array(int s); // regular constructor Array(const Array& original); // Copy constructor ~Array(); // destructor - used for both int getSize() const; // operator overloads double& operator[](int i); // This replaces read AND write! Array operator+(const Array& rhs); private: double* mpData; int mSize; };

We've replaced the add function with an overload of operator+. We've declared an overload of operator[] (known as the iterator). It replaces both read and write. First consider the overload of the operator+

array.cpp
Array Array::operator+(const Array& rhs){ if (rhs.mSize != mSize) return Array(0); Array sum(mSize); for (int i = 0; i < sum.mSize; i++) sum.mpData[i] = mpData[i] + rhs.mpData[i]; return sum; }

Note the design Returns a valuei.e. a temporary object Only works if a and implicit object are the same size. Note the implementation (Code) Returns a null Array if there is a size mismatch (i.e., something sensible must be done with errors) sum.size is used as the bound in the for loop in case there was not enough space in the heap to create the needed temporary object. Utilizes the existing code of the copy constructor to do half the work! On to the iterator overload!

array.cpp
// Iterator overload double dummy; // a static dummy variable double& Array::operator[](int i) { // error if outside array. return harmless dummy location if (i<0 || i>(mSize-1)) return dummy; return mpData[i]; // Note array notation used with pointers }

If i is an illegal index, an error should be thrown. Because of TM limitations, a dummy variable is returned, fulfilling the contract and doing no further harm If it's legal the i'th element of the Array is returned. Because it's returned as a reference, it can be used to write to Thus, the code below is all legal.

array.cpp
int main(){ Array smallArray(10); Array otherArray(10); for (int i = 0; i < smallArray.getSize(); i++) { smallArray[i] = i*i; // An array of squares otherArray[i] = i; } for (int j = 0; j < smallArray.getSize(); j++) cout << smallArray[j]<< " "; return 0; }

When writing to smallArray, all that is done is smallArray[i] = i*i; in other words, put i*i into location i of smallArray. When reading it, cout << smallArray[j]<< " "; or read the ith location of smallArray and output it. Notice the special mapping The compiler maps smallArray[i] smallArray.operator[](i) The i goes between the square brackets in the standard expression but gets mapped to the parenthesis in the operator[] function. You were told this stuff was ad hoc!

I/O of User Defined Data Types


A relatively fully overloaded version of class Complex

complexFull.cpp
class Complex { public: // These three should be replaced by // Complex(double r = 0., double i = 0.); TM unsupported Complex(double r, double i); Complex(double r); Complex(); double real() const; double imag() const; Complex operator+(const Complex& rhs); Complex operator-(const Complex& rhs); Complex operator*(const Complex& rhs); Complex operator/(const Complex& rhs); Complex& operator=(const Complex& rhs); bool operator==(const Complex& rhs); bool operator!=(const Complex& rhs); friend ostream& operator<<(ostream& s,const Complex& c); private: double re,im; };

You haven't seen all the overloads before but most of them are fairly straightforward.

Not so the insertion and extraction operators. Nevertheless, we just show them here with no comment or explanation. When I want to use them I just look up an example like this and use it as a template.

complexFull.cpp

/* Overloading the insertion operator for class complex */ ostream& operator<<(ostream& s,const Complex& c) { s <<'('; if (c.real()) s << c.real(); if (c.imag()) if (c.imag() > 0) s << " + " << c.imag() << "j"; else s << " - " << -c.imag() << "j"; if ( !(c.imag()) && !(c.real()) ) s << "0"; s << ')'; return s; // Allows concatenation of stream op }

Assignment Overload
For objects of class Complex we would think nothing of writing the following:

assignFrags.cpp
Complex A(-3.1,4.2); Complex B(1.35,11.1); Complex D; D = A + B;

A and B are initialized to specific values but we don't bother for D, because we're going to set D to the sum. It happens in our design that D will be initialized for us by the no-argument constructor to {0.,0.} but even if it weren't, it would make no difference since we're going to reset it at once. It would be nice to be able to assign arrays in the same way.

assignFrags.cpp
Array A(10); Array B(10); Array D; load(A); load(B); D = A + B; // input data to A // input data to B

Whoops! The meaning is different. A and B are arrays of 10 elements. A set function is being called to initialise them somehow. We don't bother with D because we're going to set D to the sum of A & B But this means D will have to change size Enter the Assignment overload

arrayAssignment.cpp
Array& operator=(const Array& rhs); // Assignment

operator= is a binary operator A reference to an Array is returned to let the left side operand get written into. Remember D = sum gets mapped by the compiler to D.operator=(sum) Here is the implementation

arrayAssignment.cpp
Array& Array::operator=(const Array& rhs){ if (mSize != rhs.mSize) { // Oops, not the same size delete []mpData; // release old storage if (mpData = new double[rhs.mSize]) mSize = rhs.mSize; storage else mSize = 0; } for(int i=0;i<mSize;i++) // copy rhs data to lhs mpData[i] = rhs.mpData[i]; return (*this); // i.e. return the object itself } // This allows MULTIPLE assignment

// and get new

It's actually quite like the copy constructor.

arrayAssignment.cpp
Array::Array(const Array& original){ if (mpData = new double[original.mSize]){ // request same amt. of space mSize = original.mSize; // success! we got the space for (int i = 0; i < mSize; i++) // Copy original's heap data *(mpData+i) = *(original.mpData+i); } else mSize = 0; // Oops! no space. Note the problem }

Notice

DESIGN: Assignment of unequal Arrays is not an error. Instead the old destination Array is destroyed and a new one created the same length as the source. How assignment differs from initialization. There is a pre-existing Array while in initialization, you are constructing the Array for the first time

arrayAssignment.cpp
int main(){ Array A(10); Array B(10); for (int i = 0; i < A.getSize(); i++) { A[i] = i*i; // An array of squares B[i] = i; } Array C = A + B; Array D; D = A + B; for (int j = 0; j < D.getSize(); j++) cout << D[j]<< " "; return 0; }

Thus the line Array C = A + B is initialization (so the Copy Constructor gets called) while the lines Array D; D = A+B; constitute assignment, so the assignment overload is called.

The Big Three


Assignment overload constitutes the third member of what is sometimes known as the big three 1. The destructor 2. The copy constructor 3. The assignment operator overload If the designer/programmer of a class does not provide any or all of these, the compiler will provide a default version. (This is why assignment worked with Class Complex) If class designers find they have to write their own version of any one of the three of these, they should carefully consider writing all three!

(One exception would be automatic counting of objects. If the only reason you need a copy constructor & destructor is to properly handle the updating of a static variable, the assignment overload will not be required, since assignment does not change the no. of objects in existence, it merely reassigns the value of a pre-existing object.)

Type Conversion
Users can define their own type conversions Single argument constructors are inherently considered type conversions. Complex z(3.); // declares/constructs Complex {3. + j0} Array a(5); // declares/constructs 5 element Array The first example is clearly a type conversion. The second example doesn't look anything like a sensible type conversion. The two examples are semantically equivalent. The compiler cannot distinguish them & must either treat both or neither as type conversions. Consider the following version of class Complex which is overloaded for +, -, double() and <<

complexTypeConv.cpp
class Complex { public: // These three should be replaced by // Complex(double r = 0., double i = 0.); TM unsupported Complex(double r, double i); Complex(double r); Complex(); double real() const; double imag() const; Complex operator+(const Complex& rhs); Complex operator-(const Complex& rhs); operator double() const; friend ostream& operator<<(ostream& s,const Complex& c); private: double re,im; };

Now we define a bunch of overloads on an ordinary max function

complexTypeConv.cpp
Complex::operator double() const { return sqrt(re * re + im * im); }

int max(int a, int b) { return (a>b?a:b);} double max(double a, double b) { return (a>b?a:b);} Complex max(const Complex& a, const Complex& b) { return (a>b?a:b); }

First two are clear but why does the third work? We haven't overloaded the > operator for class Complex. We have provided an overload for the (double) typecast. In evaluating expressions the compiler invokes type conversions automatically and so casts a>b to (double)a>(double)b. This conversion does not apply to the second pair, so a Complex is returned.

Type Conversion Instead of Mixed Operator Overloading


Consider the following expressions for our class Complex above:

complexTypeConv2.cpp
int main(){ Complex x(-1., -1.); Complex y(2.2); Complex z; double a = 4.1, b = 7.2; int i = 3; z = x + y; z = a + y; z = i + y; return 0; }

(It has no assignmemt overload but, as one of the Big Three, the compiler builds a perfectly adequate one for us). The first assignment gets mapped by the compiler to z = x + y z.operator=(x.operator+(y)); The second case is quite tricky. 1. The compiler cannot find the explicit signature operator+(double,Complex&) 2. It can find no built-in promotion from Complex to double 3. It can find no built-in conversion from Complex to double 4. It can find a user defined conversion the Complex::double() and the constructor with one real argument (semantically equivalent to a conversion)

z = a + y z.operator=(Complex(a + double(y))) The third case is like the second except that the compiler has to convert i from int to double.

Caveats
Operator overloading is very powerful, very tempting & very difficult to get right. It is obviously very subtle (remember Complex max(Complex a,Complex b){return a>b?a:b;} as well as addition examples just previous). You can do nothing to alter operator precedence e.g. If the C++ exclusive OR operator, ^, is overloaded for exponentiation, a^b evaluates as expected but a^b+c does not because, in C++, + has higher precedence than ^.

Some Style Rules


The meaning of an overloaded operator should be natural, not clever. The exponentiation overload above violates this rule because it does not follow the rules of normal arithmetic. Overload operators consistentlyfor example, in most domains ab is equivalent to b a and a+-b is equivalent to a-b. A set of overloaded operators should be completeoverload unary + and most users would expect When defining operator= remember x=x. It is legal and can occur (inside loops, dealing with lots cases, for example) If overloading + and = don't forget +=

Parametric Polymorphism
Remember swap?

cswapfrag.cpp
void intSwap(int *a1,int *a2){ int temp = *a1; *a1 = *a2; *a2 = temp; }

This is the old C style. We had to create different swaps for different data types and give them different names (like dbleSwap(double *,double *), charSwap(char *, char *)). C++ improved this by allowing us to overload the swap function, so we could always use the same name. (The compiler then mangles the names to create different ones automatically.) But we still have to write separate functions for each (note switch to C++ pass-by-reference as well)

C++SwapOverload.cpp
void swap(int& a1,int& a2){ int temp = a1; a1 = a2; a2 = temp; } void swap(char& a1,char& a2){ char temp = a1; a1 = a2; a2 = temp; }

A Generic Swap
swapTemplate.cpp
template <class T>

/* Restrictions on T: 1. must be capable of being an l-value 2. must copy construct properly 3. must assign properly */ void mySwap(T& a1,T& a2) { T temp = a1; a1 = a2;

a2 = temp; }

Now the compiler writes all the variations for us!!

swapTemplate.cpp
int i=3,j=5; const int ONE =1; const int TWO = 2; char a='c',b='\n'; double pi=3.14159,x=1/3.; char *s1="Hello ", *s2="world!"; mySwap(i,j); // legal, integer version mySwap(pi,x); // legal, double version mySwap(a,b); // legal, character version mySwap(i,a); // illegal, no mixed version, can't convert references mySwap(s1,s2); // legal, only the pointers are swapped mySwap(ONE, TWO); // illegal, 1 & 2 can't be l-values

// //

If we really want to swap strings (instead of their pointers) we can write a special overload ( an ad hoc polymorphism) on top of the template overload (a parametric polymorphism ).

stringSwap.cpp
int mySwap(char* s1,char* s2) { // special string swap int l1=strlen(s1),l2=strlen(s2); l1 = (l1>l2)?l1:l2; // l1 is length of longest char* temp = new char[l1+1]; strcpy(temp,s1); // Now swap the actual strings strcpy(s1,s2); // Note, fails badly if location strcpy(s2,temp); // holding shorter string can't delete[] temp; // hold the longer one!!!! return 0; }

Given both kinds of polymorphism, the compiler looks first for a match to the ad hoc(special) definitions. i.e. special definition over-rides the template definition.

Template Arguments
Here's the lazy person's way to be able to equate arrays. You normally can't equate arrays in C++ without writing special code (mainly because the compiler can not be assured the arrays are the same mSize) However structure variables and objects can be equated and the privilege extends to an embedded array. Cute, eh! (Of course, the embedded arrays are always the same mSize because you have to declare the mSize of embedded arrays, so the compiler is happy).

arrayTemplate.cpp
/* Here we also get type checking over arrays of different types. */ #include <iostream> using namespace std; template <int n, class T> struct Array{ T a[n]; }; int main(){ Array<10,double> x,y; // x & y are arrays of 10 doubles for (int i=0;i<10;i++) x.a[i] = 1./(i+1); y = x; // Type safe. Compiler will flag problems for (int i=0;i<10;i++) cout << x.a[i] <<", "<< y.a[i] << "\n"; return 0; }

The declaration looks a little strange. The angle brackets flag the compiler which kind of object to create. This is because the normal declaration Array x; gives no clue as to whether x is an array of integers, doubles, etc.

Generalizing A Stack Class With A Template


The operation of container classes (e.g. stacks, queues, arrays) is largely independent of what is being contained. For queues, the first object placed in the queue is served regardless of the kind of object. Stacks are characterised by last-in-first-out behaviour, again independent of what is being pushed or popped. Here we focus on a Stack class. First we make its declaration a template.

stackTemplate.cpp
template <class type> class Stack{ public: Stack(int s=10); // Constructor & null constructor Stack(const Stack &s); // Copy constructor bool push(type d); type pop(); bool empty() const; ~Stack(); private:

type *mpTos, *mpStart; int mSize; };

Notice, we have only used 1 parameter here, the type. In the declaration, the only things parameterized are the push and pop operations and the declaration of the two pointers, mpTos and mpStart. These are the only things in the class declaration that deal with what is being held in, or moved on or off, the stack. Now we have to implement the class, creating templates for each function affected.

stackTemplate.cpp
template <class type> Stack<type>::Stack(int s){ if ((mSize=s) < 0 || !(mpStart = new type[mSize]) ){ mpStart = 0; mSize = 0; } mpTos = mpStart; } template <class type> Stack<type>::Stack(const Stack<type> &s){ if (mpStart=new type[s.mSize]) { mSize = s.mSize; int offset = s.mpTos-s.mpStart; for (int i=0;i<offset;i++) *(mpStart+i)=*(s.mpStart+i); mpTos = mpStart + offset; } else mSize = 0; } template <class type> bool Stack<type>::push(type d){ if (mpTos==mpStart+mSize) return false; // stack full *mpTos++=d; return true; } template <class type> type Stack<type>::pop(){ if (mpTos>mpStart)mpTos--; // Check something in stack return *mpTos; }

In definitions external to the class, Stack<type> always replaces Stack. Even the destructor and the empty function have to have templates created for them even though neither function has anything to do with what is on the stack.

stackTemplate.cpp
template <class type> bool Stack<type>::empty() const{return (mpTos==mpStart);} template <class type> Stack<type>::~Stack(){delete[]mpStart;}

The reason is that a stack of doubles is a different class than a stack of ints and so each will have its own destructor and empty function. Note that this parameterized stack handles ADTs as well as built in types. We'll define a struct complex after the template has been created.

stackTemplate.cpp
struct complex{ double re,im; complex(double r=0.,double i=0.){re=r;im=i;} }; ostream& operator<<(ostream& s, complex c);

Then we'll go ahead and use it with the template.

stackTemplate.cpp
int main(){ Stack<double> ds(5); Stack <int> is(4); Stack <complex> cs; int x = 0, i = 0, j = 0;

// complex declared after stack!

for(x=0; ds.push(x); x++) //Push until full ; for(i=0; is.push(i);i++) ; for (j=0; cs.push(complex(j,-j));j++) ; // Now empty each stack, outputting as we go cout <<"There were "<<x<<" items on the double stack\n"; while (!ds.empty()) cout << ds.pop() << " "; cout <<"\n\nThere were " << i << " items on the integer stack.\n"; while (!is.empty()) cout << is.pop() << " "; cout << "\n\nThere were " << j << " items on the complex stack.\n"; complex temp; while (!cs.empty()){ cout << cs.pop() << " } cout << "\n"; return 0; }

";

The compiler was quite happy to have complex declared after class Stack<type>. In essence the template tells the compiler how to build the correct Stack class when it is needed. The class is Stack<type> not Stack. That is Stack<int> is a different class from Stack<complex>.

Virtual Functions
Abstract Classes
Consider our User class with its hierarchy of sub-classes.

../inheritance/userInherit.cpp
// A class of users for network management class User{ public: User(char* n, char* u, char* p); // Accessors MyString name() const; int allocation() const; // Disk space allowed MyString password() const; MyString uname() const; bool confirm(const MyString& uName, const MyString& pass) const; // Mutators bool setPassword(const MyString& p1,const MyString& p2); void setAllocation(int a); protected: MyString mName; // Notice attributes that are OBJECTS! MyString mUname; MyString mPassword; int mAllocation; // Memory allocation, in MBytes };

Here is one of its derived classes:

../inheritance/userInherit.cpp
class Student: public User{ public: Student(char* n, char* id, char* u, char* p); // Accessors MyString id() const; // Mutators void buy(long amount); bool approvePrint(int pages); protected: MyString mId; long mPrintPennies; };

And here is another:

../inheritance/userInherit.cpp
class Faculty: public User{ public: Faculty(char* n, char* u, char* p, char* today); // accessor functions long pages() const; MyString lastCleared() const; // mutator functions bool approvePrint(int pages); // increases page count void clear(char* d); // clears count & notes date protected: long mPages; // Pages used since MyString mCleared; // last date cleared };

In writing code to handle printing we have to know that student accounts must be charged but faculty pages must simply be counted. Actually, we need to do something else for graduate students since they get a certain no. of pages, after which they must pay. It would be nicer if all print requests for users were invoked similarly! First we add a pure, virtual function to User

userVirtual.cpp
// A class of users for network management class User{ public: User(char* n, char* u, char* p); // Accessors MyString name() const; int allocation() const; // Disk space allowed MyString password() const; MyString uname() const; bool confirm(const MyString& uName, const MyString& pass) const; // Mutators bool setPassword(const MyString& p1,const MyString& p2); void setAllocation(int a); virtual bool printRequest(int pages) = 0; protected: MyString mName; // Notice attributes that are OBJECTS! MyString mUname; MyString mPassword; int mAllocation; // Memory allocation, in MBytes };
bool printRequest(int pages)=0

is declared as a virtual function in class User

This means that inheritors of class User (e.g. Student) can have the same function and that that will be the one used if a User object is actually a Student The = 0 means it is a pure virtual function (I don't like the notation either). This means it is not implemented in Class User Any class that has at least one pure virtual function is known as an Abstract class. 1. It is not possible to instantiate objects of abstract classes. That is there can be no User objects per se (but there can be Faculty or Student objects). 2. The reason this is so is that all user objects are supposed to be able to handle printRequest() calls, but pure User objects cant because we are not going to provide any code for them

Concrete Classes
Classes which either have no virtual functons or alternatively have available implementations for all virtual functions are called concrete classes. Another way to say this is that concrete classes contain no pure virtual functions . Concrete classes can instantiate objects. We want Student, Faculty and GradStudent to be concrete classes so we will implement the virtual printRequest function for all of them. First here are the new declarations for the three derived classes. The Student class:

userVirtual.cpp
class Student: public User{ public: Student(char* n, char* id, char* u, char* p); // Accessors MyString id() const; // Mutators void buy(long amount); virtual bool printRequest(int pages); protected: MyString mId; long mPrintPennies; };

The Faculty class:

userVirtual.cpp

class Faculty: public User{ public: Faculty(char* n, char* u, char* p, char* today); // accessor functions long pages() const; MyString lastCleared() const; // mutator functions virtual bool printRequest(int pages); void clear(char* d); // clears count & notes date protected: long mPages; // Pages used since MyString mCleared; // last date cleared };

The GradStudent class:

userVirtual.cpp
// A skeletal grad student class class GradStudent: public Student{ public: GradStudent(char* n, char* id, char* u, char* p, Faculty* pa); Faculty* getAdvisor() const; int freePages() const; // accessors

virtual bool printRequest(int pages); // Mutators void addToFreePages(int pages); protected: Faculty* mpAdvisor; int mFreePages; };

And here are the printRequest functions for each:

Student print requests are authorized only if there are enough printPennies in their
account. If there are enough, the account is debited by the cost of the job.

userVirtual.cpp
bool Student::printRequest(int pages){ assert(pages >=0); int cost = pages * PAGE_COST; if (cost > mPrintPennies) return false; mPrintPennies -= cost; return true; }

Faculty print requests are authorized as a matter of course but the mPages counter is
incremented to track printer usage.

userVirtual.cpp
bool Faculty::printRequest(int pages){ assert(pages>=0); mPages+=pages; return true; }

GradStudent print requests are more complex since grad students receive a free page
allowance to cover their thesis printing.

userVirtual.cpp
bool GradStudent::printRequest(int pages){ assert(pages >=0); int cost; if (pages <= mFreePages) { mFreePages -= pages; return true; } cost = (pages-mFreePages) * PAGE_COST; if (cost <= mPrintPennies) { mFreePages = 0; mPrintPennies -= cost; return true; } return false; }

Late Binding
One advantage of virtual functions is they don't even have to be known at compile time. The decision as to which version of printRequest(int) to call is made at run time!

void printCall (const User& user, PrintJob job){ if (user.printRequest(job.pages())) job.queueIt(user); else cout << Print job for << user.name() << refused.\n; }
We have promised to pass printCall a User object There are no User objects, per se. There can only be Student objects, Faculty objects or GradStudent objects. If, at run time, I call printCall with a user, it will find the right printRequest. If

User is a Student GradStudent Faculty

printCall will invoke Student::printRequest(int) GradStudent::printRequest(int) Faculty::PrintRequest(int)

This is done automatically, at run time. You can compile the file with printCall in it and then add new subclasses of users later. printCall will handle them properly without recompiling. If printRequest were not a virtual function, then the binding would be done by the linker. That is, when the program is assembled the linker would have to decide which printRequest function should be called inside of printCall. Since the only thing it can use is the declaration, User would have to have its own printRequest function and that is what would always be used regardless of the subtype. What the actual subtype is is only known at run time as printCall could be called many times during a program, with many different kinds of User objects. Late binding allows the decision as to whichprintRequest function is used to be delayed until an actual user object arrives, while the code is running.

Flamingos, Pigs and Elephants


This is taken from the 2003 final exam: Here are the declarations for some very arbitrary, poorly designed and completely non-intuitive classes.

flamingos.cpp
class Flamingo{ public: virtual void fly(); bool standOnLawn(); virtual void steal()=0; protected: bool isPink; private: int appendages; }; class Pig : public Flamingo{ public: virtual void steal (); }; class Elephant : public Flamingo{ public: virtual void fly(); bool standOnLawn();

virtual void steal(); };

Can pigs and elephants fly? State your reasoning.

Yes, because both inherit fly() operation from Flamingo.

Can implementation code for Elaphants and Pigs tell (i) if an Elephant or Pig is pink? (ii) how many appendages it has? State your reasoning.
Yes,isPink is protected and so available to implementation code of derived classes No because appendages is private and available only to implementation code for Flamingo.

(i) (ii)

Software for Lawn Ornament Hijacking, Inc. includes the following function

hijack.cpp
void jack(Flamingo& flamingo){ if (flamingo.standOnLawn()) flamingo.steal(); }

What version of standOnLawn() and steal() get called when someone tries to jack a pig? An elephant? (State your reasoning).

Pig.steal() & Elephant.steal() because they are virtual. Flamingo.standOnLawn() because it is not.

Virtual Destructors
If you are creating a class that you expect to be subclassed (i.e. that other classes will uses as a base class) You should very carefully consider creating a virtual destructor, even if your class does not itself need a destructor. The following example was found by Song Qing, to whom my thanks.

destructorCorruption.cpp
#include <iostream> class CBase{ public: CBase(){ data = new char[64];

} ~CBase(){ delete [] data; } char *data; }; class CFunction{ public: CFunction(){} ~CFunction(){} }; class CFunctionEx : public CFunction{ public: CFunctionEx(){} ~CFunctionEx(){} private: CBase m_cbase; }; int main(){ CFunction *pCFun = new CFunctionEx; delete pCFun; return 0; }

It looks a little strange since all the code has been inlined (which is often done for examples of particular iussues, like this). Can you figure out why it results in a heap corruption?
When we apply delete to pcFun we are causing the destructor to be invoked. Since the destructor is not virtual, the compiler figures out which destructor should be called. The only thing it has to go by is the declared type ofpcFun, which is a pointer to a CFunction object. So the CFunction destructor gets called and it doesn't know anything about what the CFunctionEx constructor did.

Testing
Testing Implementation Code

Test code is as important as implementation code. Writing it well constitutes a separate skill. Consider everything that could break the original code Special cases are particularly important.

Here is some of the test code used on your recent Container class assignment (adapted slightly for presentation in class). We'll focus on the Queue subclass. queueDemo.cpp
bool testQueue(){ const int SIZE = 5; Queue queue(SIZE); bool passed = !queue.inError(); queue.fetch(); // queue empty, can't fetch passed = queue.inError(); // rotate Queue a bit queue.add(0); queue.add(0); queue.add(0); queue.fetch(); queue.fetch(); queue.fetch(); passed = passed && !queue.inError(); if (passed) { int count = 0; while(queue.getStatus() < FULL) { queue.add(count*count-count); count++; passed = passed && !queue.inError(); } if (passed) { queue.add(100.0); // error passed = passed && queue.inError(); count = 0; while (queue.getStatus() > EMPTY) { passed = passed && (queue.fetch() == count*count - count) && !queue.inError(); count++; } passed = passed && count == SIZE; } } return passed; }

Concepts Reviewed
Constructor Chaining Step into the declaration of the queue object and watch how the Container constructor is utilised. Destructor Chaining Step into the destructor calls that are automatically generated when the testQueue function ends and queue goes out of scope. Note that although there is no destructor for the Queue class per se, Queue objects are also Container objects so the Container destructor is called. Passing Pointers by Reference Because objects of the queue class are circular, advancing either head or tail pointer may require wrapping the address back around. Hence we use an incPtr function. What would happen if we hadn't passed the pointers by reference? Pointer Arithmetic Watch the Expression Engine when the incPtr function does wrap the pointer for a nice example of pointer arithmetic.

Virtual Functions
Consider our User class with its children (sub-classes), Student & Faculty & even its grandchild (sub-sub-class)
class User{ public: User(const Name& n, const char* u, const char* p); // Accessors Name name()const; int allocation()const; // Disk space allowed MyString password()const; MyString uname() const; bool confirm(const MyString& uName, const MyString& pass); // Mutators bool setPassword(const MyString& p1, const MyString& p2); void setAllocation(int a); protected: Name mName; MyString mUname; MyString mPassword; int mAllocation; // Memory allocation, in MBytes }; class Student: public User{ public: enum Term{A,B,ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,WORK,GRAD}; Student(const Name& n,const MyString& id,const char* u,const char* p); MyString id() const; Term term() const; void set(Term t); void buy(long amount); bool debit(long amount); // accessors // mutators Print control

protected: // The data MyString mId; Term mTerm; // mTerm is a member VARIABLE of type TERM long mPrintPennies; };

mpbl 12/14/2013

Virtual Functions
class Faculty: public User{ public: enum Discipline{CIVIL,MECHANICAL,NAVAL,ELECTRICAL}; Faculty(const Name& n,const char* u,const char* p,Discipline d,Date today); long pages() const; Date cleared()const; void printed(long p); void clear(Date d); // accessor functions // Mutators Print control // clear page record, record date

protected: long mPages; // Number of pages printed since Date mCleared; // the last date cleared Discipline mDiscipline; }; // A skeletal graduate student class class GradStudent: public Student{ public: GradStudent(const Name& n, const MyString& id, const char* u, const char* p, Faculty* pa); Faculty advisor() const; protected: Faculty* mpAdvisor; }; // accessors

In writing code to handle printing we have to know that student accounts must be charged but faculty pages must simply be counted. Actually, we need to do something else for graduate students since they get a certain no. of pages, after which they must pay. It would be nicer if all print requests for users were invoked similarly!

VIRTUAL - 2

Virtual Functions

First we add a pure, virtual function to User


class User{ public: User(const Name& n, const char* u, const char* p); Name name()const; // Accessors int allocation()const; // Disk space allowed MyString password()const; MyString uname() const; bool confirm(const MyString& uName, const MyString& pass); void setAllocation(int a); // Mutators bool setPassword(const MyString& p1, const MyString& p2); virtual bool printRequest(int pages) = 0; protected: Makes the Makes the function virtual function pure Name mName; MyString mUname; MyString mPassword; int mAllocation; // Memory allocation, in MBytes };

bool printRequest(int pages) is declared as a virtual function in class User This means that inheritors of class User (e.g. Student) can have the same function and that that will be the one used if a User object is actually a Student It is also declared as a pure function (I dont like the notation either). This means it is not implemented in Class User Any class that has at least one pure virtual function is known as an Abstract class. 1. It is not possible to instantiate objects of abstract classes. That is there can be no User objects per se (but there can be Faculty or Student objects). The reason this is so is that all user objects are supposed to be able to handle printRequest() calls, but pure User objects cant because we are not going to provide any code for them

2.

VIRTUAL - 3

Virtual Functions

Concrete classes
class Student: public User{ public: enum Term{A,B,ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,WORK,GRAD}; Student(const Name& n,const MyString& id,const char* u,const char* p); MyString id() const; Term term() const; // Accessors

void set(Term t); // Mutators void buy(int amount); virtual bool printRequest(int pages); protected: MyString mId; Term mTerm; int mPrintPennies; }; // Now the data // mTerm is a member VARIABLE of type TERM

class Faculty: public User{ public: enum Discipline{CIVIL,MECHANICAL,NAVAL,ELECTRICAL}; Faculty(const Name& n, const char* u, const char* p,Discipline d,Date today); int pages() const; Date cleared()const; // accessors

virtual bool printRequest(int pages); // mutators void clear(Date d); // clear page record, record date protected: long mPages; // Number of pages printed since Date mCleared; // the last date cleared Discipline mDiscipline; };

VIRTUAL - 4

Virtual Functions
// A skeletal graduate student class class GradStudent: public Student{ public: GradStudent(const Name& n, const MyString& id, const char* u, const char* p, Faculty* pa); Faculty advisor() const; int freePages() const; // accessors // Mutators

virtual bool printRequest(int pages); void incFreePages(int pages); protected: Faculty* mpAdvisor; int mFreePages; };

Print request is now handled by each subClass differently


bool Student::printRequest(int pages){ assert(pages >=0); int cost = pages * PAGE_COST; if (cost > mPrintPennies) return false; mPrintPennies -= cost; return true; } bool Faculty::printRequest(int pages){ assert(pages>=0); mPages+=pages; return true; } bool GradStudent::printRequest(int pages){ assert(pages >=0); int cost; if (pages <= mFreePages) { mFreePages -= pages; return true; } cost = (pages-mFreePages) * PAGE_COST; if (cost <= mPrintPennies) { mFreePages = 0; mPrintPennies -= cost; return true; } return false; }

VIRTUAL - 5

Virtual Functions

Late Binding
One advantage of virtual functions is they don't even have to be known at compile time. The decision as to which version of printRequest(int) to call is made at run time!
void printCall (const User& user, PrintJob job){ if (user.printRequest(job.pages())) job.queueIt(user); else cout << Print job for << user.name() << refused.\n; }

We have promised to pass printCall a User There are no users, per se. There can only be Student objects, Faculty objects or GraduateStudent objects. If, at run time, I call printCall with a user, it will find the right printRequest. If User is a Student GraduateStudent Faculty printCall will invoke Student::printRequest(int) GraduateStudent::printRequest(int) Faculty::PrintRequest(int)

This is done automatically, at run time. You can compile the file with printCall in it and then add new subclasses of users later. PrintCall will handle them properly without recompiling

VIRTUAL - 6

Virtual Functions

An Alternative example
class Account { public: double balance() const; virtual void withdraw(double a); void deposit(double a); constructors, etc. protected: double mBalance; } class Chequing:Account{ public: virtual void withdraw(double a); void deposit(double a); constructors, etc. } class Savings:Account{ public: virtual void withdraw(double a); void deposit(double a); constructors, etc. protected: static double rate; }

Now, suppose I declare a function whose formal parameter is a reference to base class
void transaction(Account& pA, const double amount);

Now I call demo function and pass in a derived class object.


Savings myAccount; // myAccount is an object of the derived class transaction(myAccount, 100.) // Deposit $100 in my account

Is this legal? Sure it is. By definition, an object of class derived is also an object of the base class. However, consider the following within the body (definition) of transaction
void transaction(Account& a, const double amount) { if (amount < 0) // a withdrawal a.withdraw(-amount); // virtual call. Depends on a at run time else a.deposit(amount); // calls Account::withdraw() }

VIRTUAL - 7

Virtual Functions

Which functions will the following calls find? When will the decision be made?
Savings michaelsAccount; Account marysAccount; Checquing michaelsOther; transaction(michaelsAccount,100.); transaction(michaelsAccount,-50.); transaction(michaelsOther,50.); transaction(marysAccount,200.); transaction(michaelsOther,-20.); transaction(marysAccount,-40.);

All the positive amounts result in deposits (at run time). But at compile time, the compiler will have inserted a call to Account::deposit() into the code because deposit is not a virtual function and the class being input to transaction has been declared to be Account. This is all the compiler wants to know. All the negative amounts result in withdrawals(at run time). But the withdrawal call is to a virtual function so the compiler defers the decision as to which version to call & it is made at run time instead.
transaction(michaelsAccount,-50.); Savings::withdaw() transaction(michaelsOther,-20.); Checquing::withdraw() transaction(marysAccount,-40.); Account::withdraw()

VIRTUAL - 8

Virtual Functions

Virtual Classes
It is often the case that we don't ever want to instantiate an object of a base classfor example banks normally wouldnt allow just an Account to be opened, it must be a Savings or Checquing account.
class Account { public: double balance() const; virtual void withdraw(double a)=0.; void deposit(double a); constructors, etc. protected: double mBalance; } class Chequing:Account{ public: virtual void withdraw(double a); void deposit(double a); constructors, etc. } class Savings:Account{ public: virtual void withdraw(double a); void deposit(double a); constructors, etc. protected: static double rate; }

Now withdraw() is a pure virtual function in Account (I dont like the notation either) It is not implemented in Account Account is an abstract class. (Any class with at least one pure virtual function is an abstract class) It is not possible to instantiate objects of an abstract class (how could you withdraw from an Account object it has no withdraw code).

Although legal, the design is poor. Why do we have deposit in all three classes? If it must be overridden in Savings & Checquing why implement it in Account? Either make it virtual as well or

VIRTUAL - 9

Virtual Functions

class Account { public: double balance() const; virtual void withdraw(double a); void deposit(double a); constructors, etc. protected: double mBalance; } class Chequing:Account{ public: virtual void withdraw(double a); constructors, etc. }

class Savings:Account{ public: virtual void withdraw(double a); constructors, etc. protected: static double rate; }

Here, all deposits are handled the same way, so they are done in Account, while withdraw() is handled depending on the type of account.

VIRTUAL - 10

Potrebbero piacerti anche