Anda di halaman 1dari 141

Introduction to C, ObjectOriented Programming, and C++

Copyright c Dr. W. J. Phillips January 5, 2008

Contents
1 Introduction 1.1 History of C, C++ and Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 What is Object Oriented Programming? . . . . . . . . . . . . . . . . . . . . . . . . . 2 C and C++ Dierences 2.1 Simple Input and Output . . . . . . . . 2.2 Bool, String and Reference Data Types 2.2.1 The Bool Data type . . . . . . . 2.2.2 Cstrings . . . . . . . . . . . . . . 2.2.3 The String Data Type . . . . . . 2.2.4 The Reference Data Type . . . . 2.3 Simple File Handling . . . . . . . . . . . 2.4 Functions . . . . . . . . . . . . . . . . . 2.4.1 Pass by reference . . . . . . . . . 2.4.2 Function Overloading . . . . . . 2.4.3 Default Arguments . . . . . . . . 2.4.4 Return by Reference . . . . . . . 2.5 Dynamic Allocation . . . . . . . . . . . 3 3 3 5 5 7 7 8 9 10 10 12 12 14 15 15 17 21 21 23 25 31 31 34 38 41 43 45 47 49 51

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

. . . . . . . . . . . . .

3 Structures 3.1 Point Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Complex Numbers Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Operators and Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Classes 4.1 A First Example . . . . . . . . . . . . . . . . 4.2 Modeling Physical Objects with Classes . . . 4.3 Constructors . . . . . . . . . . . . . . . . . . 4.4 Object Pointers . . . . . . . . . . . . . . . . . 4.5 The this Pointer . . . . . . . . . . . . . . . . 4.6 The Destructor . . . . . . . . . . . . . . . . . 4.7 The Copy Constructor . . . . . . . . . . . . . 4.8 Operators and Operator Overloading . . . . . 4.9 Member Operators verus External Operators 1

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

C/C++ Programming 4.10 4.11 4.12 4.13 4.14 4.15 4.16 The Assignment Operator, Operator = . . . . . . . Composition: Objects as Members of Classes . . . Classes with Dynamically Allocated Data Members Static Data Members and Methods . . . . . . . . . Const Data Members and Methods . . . . . . . . . A Vector Class . . . . . . . . . . . . . . . . . . . . A Matrix Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2 54 56 61 64 65 67 70 78 78 81 88

5 Containers 5.1 Stacks and Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Dynamic Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Templates, Generic Programming 6.1 Template Classes . . . . . . . . . 6.2 Template Functions . . . . . . . 6.3 Namespaces . . . . . . . . . . . . 6.4 The Standard Template Library . and . . . . . . . . . . . . the STL . . . . . . . . . . . . . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

92 . 92 . 95 . 97 . 100 104 104 104 108 111 115 118 120 123 127 127 130 131 132 134 135 139 141

7 Inheritance 7.1 Derived Classes . . . . . . . . . . . . . . . . . . 7.2 A First Example . . . . . . . . . . . . . . . . . 7.3 Base and Derived Constructors, Destructor and 7.4 Virtual Functions and Polymorphism . . . . . . 7.5 Multiple Inheritance . . . . . . . . . . . . . . . 7.6 Virtual Base Classes . . . . . . . . . . . . . . . 7.7 Virtual Destructors . . . . . . . . . . . . . . . . 7.8 Run Time Type Identication . . . . . . . . . . 8 Exceptions 8.1 Traditional Error Handling Versus Exceptions 8.2 Exception Basics . . . . . . . . . . . . . . . . 8.2.1 Stack Unwinding . . . . . . . . . . . . 8.2.2 Exception Specication . . . . . . . . 8.3 User Dened Exception Types . . . . . . . . 8.4 Standard Exception Types . . . . . . . . . . . 8.5 The bad alloc Exception . . . . . . . . . . . . 9 Resources

. . . . . . . . . . . . . . Operator = . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

C/C++ Programming

1
1.1

Introduction
History of C, C++ and Java

C was developed at AT&T around 1972 by Kernighan and Ritchie. They needed a small quick language to support UNIX. In the 1980s the ANSI began to standardize C. ANSI C was released in 1989 and revised in 1999. The 1999 revision is called C99. In the early 1980s, Bjarne Stroustroup, also at AT&T, developed C with classes adding object oriented programming techniques to C. This language became C++. The name arose from the the ++ operation in C. If x is an integer variable then x++ is the next number after x and in analogy, C++ is the next language after C. C++ is backwards compatible with C and as such is regarded as a mid-level language having a combination of both high-level and low-level language features. C++ supports procedural programming, data abstraction, object-oriented programming, and generic programming. C++ was standardized in 1998 (C++98) and revised in 2003 (C++03). There is a new version of the standard (known informally as C++0x) being developed. In 1990, a group at SUN microsystems developed Java. In some sense Java is what C++ might have been if it was not required to be backward compatible with C. Java syntax borrows heavily from C and C++ but it eliminates certain low-level constructs such as pointers and has a very simple memory model where every object is allocated on the heap and all variables of object types are references. Memory management is handled through integrated automatic garbage collection.

1.2

What is Object Oriented Programming?

Objectoriented programming (OOP) is a programming paradigm that uses objects and their interactions to design applications and computer programs. This is in contrast to procedural programming in which the emphasis is on the procedures (functions). All OOP languages, including C++, share three common traits: encapsulation, and inheritance and polymorphism Encapsulation Encapsulation is the combining of code and the data it manipulates together into a new entity called a class. Each class adds a new data type to the language. An object is then a variable of this new data type. Within a class, code, data or both may be private or public. Private code or data can only be accessed by another part of the class. This hiding of information inside an class can lead to a high degree of security and decrease the frequency of bugs. For example, suppose that we are writing a program to mimic an address book. Lets assume that we want to store a persons name, address and phone number. We could represent an entry in the address book in any language with 3 variables. In C++ we would dene a class (i.e. a new data type) consisting of the 3 variables together with functions to read and write the variables. A database entry is then a variable (object) of this new type. Inheritance Inheritance is the process by which one class can acquire the properties of another. That is, a class can inherit a general set of properties to which it can add those features that are specic only to itself. This is an important process as it allows classes to support the concept of hierarchical classication. Large amounts of information can be made manageable by hierarchical classication. Inheritance also gives rise to code re-use. That is the code which was written for a class still works for those classes which inherit from it. As an example of code re-use, suppose that in our address program we want to record a work telephone number in addition to the telephone number previously stored. We can then dene a new class which inherits all the properties of the original but adds a work telephone number. All programs written for the original class will still work with the new class. Polymorphism Polymorphism (from the Greek, meaning many forms) is the technique of allowing one name to

C/C++ Programming

be used for two or more related but technically dierent purposes. This has the eect of reducing complexity by allowing one name to express a general class of actions. Virtually all programming languages contain limited polymorphism as it relates to arithmetic operators. That is, we use the same operator symbol + when we say z = x + y when x, y and z are integers, oating point, double precision etc. We can illustrate polymorphism in the example of the address program in which we have the original class whos objects represent at database entry and the new class which adds a work telephone number. The original class has functions to read and write its variables. We would then use the same function names to read and write the variables of the new class.

C/C++ Programming

C and C++ Dierences

C is a subset of C++. The nonobjectoriented aspects of C++ are the same as those in C but C++ has extensions in the following areas. C++ has a completely dierent input and output system. C uses the int data type to indicate true and false. C++ introduces the bool data type for this purpose. C uses character arrays for strings while C++ has a string data type. C++ has reference data types which is less powerful but safer than the pointer type. There are numerous extensions in the handling of functions: pass by reference using reference variables as arguments; function overloading; that is, multiple functions with the same name but dierent argument lists; default arguments. C++ has a dierent system for dynamic allocation and deallocation of memory. These C++ extensions are best introduced through a series of examples.

2.1

Simple Input and Output

In C++ there are operators (functions) provided to read information from the keyboard and to write information to the screen. These operators are illustrated by the following programs. In any computer language the rst program a student should write is one which prints a simple message. This exercises the students ability to compile and execute a program. Sample Program: hello.cpp // File hello.cpp // The standard first program as proposed by K+R #include <iostream> using namespace std; int main() { cout << "Hello World" << endl; return 0; } Notes: 1. // begins a comment which ends at the end of a line. Multiline comments begin with /* and end with */. We can nest mixed comment types to allow the commenting out of large sections of a program for debugging purposes. 2. The new IO system is declared in the header le iostream. 3. Programs consist of entities with unique names. C++ uses namespaces to organize these names. The line using namespace std; states that the program will be accessing entities whose names are part of the namespace called std.

C/C++ Programming

4. cout is dened in iostream and is called standard output. The c at the beginning signies console. 5. << is an called the putto operator. Its acts by sending its right operand to its left operand. In general this looks like: (place to send output) << (data to be sent) Recall that an (binary) operator is really a function with two arguments. We write the arguments of the function on either side of the operator. If we want to use function notation we can write: operator << (cout, "Hello World\n"); 6. In C++ Hello World is called a cstring as opposed to the string data type. 7. The symbol endl is dened in iostream as a carriage return or new line character. 8. The series of outputs can be concatenated. The operator acts from left to right so that in: cout << "Hello World" << endl; rst the cstring Hello World is sent to the stream cout: cout << "Hello World"; and this operator (function) returns the stream cout which is then used as the stream for the next putto cout << endl; 9. I adhere to the indentation scheme shown in this example. That is, each block of code delimited by { } is indented 3 spaces. Here is another program using both input and output. Sample Program: greet.cpp // File greet.cpp // Displays a users initials #include <iostream> using namespace std; int main() { char c1, c2, c3; // Get the three initials cout << "Enter your three initials: "; cin >> c1 >> c2 >> c3; // Display the result cout << "You entered :"; cout << c1 << c2 << c3 << endl; return 0; } /* The program output is: Enter your three initials: wjp You entered : wjp */ Notes:

C/C++ Programming 1. cin is the standard input stream which is the keyboard

2. The getfrom operator , >>, reads from its left operand and stores the result in its right operand. As for the putto operator the getfrom operator is actually a function which can be called as: operator >> (cin, c1); With character input the getfrom operator reads and discards white space characters until it nds a non-white space character. Here is another example. Sample Program: celcius.cpp // File celcius.cpp // Converts temperature in Fahrenheit to Celcius #include <iostream> using namespace std; int main() { float factor = 5.0/9.0; // conversion factor float offset = 32.0; // offset between the two scales float celcius, fahrenheit; // Read in the temperature in Fahrenheit cout << "Enter the temperature in Fahrenheit: "; cin >> fahrenheit; // Do the conversion celcius = factor*(fahrenheit - offset); // Display the result cout << "The temperature in Celcius is: " << celcius << endl; return 0; } /* the program output is: Enter the temperature in Fahrenheit: 70 The temperature in Celcius is: 21.111113 */

2.2
2.2.1

Bool, String and Reference Data Types


The Bool Data type

C++ adds the bool data type to the usual ones in C (later added to C99) This data type has only two values false and true. Sample Program: booltest.cpp // File: booltest.cpp // Illustrates the bool data type #include <iostream> using namespace std;

C/C++ Programming

int main() { bool test; int x, y; cout << "Enter two integers: "; cin >> x >> y; test = (x <= y); if (test == true) cout << x << " <= " << y << endl; else cout << x << " > " << y << endl; return 0; } 2.2.2 Cstrings

C++ has a string data type. In C, a string is not a data type. It is a one-dimensional array of type char. All string handling makes the assumption that the end of the string is marked by the null character, \0. Because a string is an array, string processing is a special case of array processing. The presence of the terminating null character gives string processing algorithms a dierent avour than simple array processing. The header le cstring contains the same functions as the C header le tt string.h Three useful functions are: // returns the length of the string s aside from the terminating null int strlen(const char* s); /* copies the string pointed to by src (including the null) to the string given by dest */ void strcpy(char* dest, const char* src); /* appends the strring src to the string dest overwriting the null at the end of dest void strcat(char* dest, const char* src); The const modier on the arguments means that these functions cannot modify the contents of that string. Sample Program: cstrings.cpp // File: cstrings.cpp // Program to test string handling #include <iostream> #include <cstring> using namespace std; int main() { char s[] = "My book";

C/C++ Programming

char t[10] = " is blue"; char r[100] =""; int i; int c; cout << strlen(s) << endl; // will print 7 cout << strlen(t) << endl; // will print 8 cout << strlen(r) << endl; // will print 0 for(i=0; i<= 9; i++) { c = t[i]; cout << c << " "; } cout << endl; // will print 32 105 115 32 98 108 117 101 0 0 strcpy(r,s); /* Copies string s to string r, stopping after the terminating null character has been copied */ cout << r << endl; // will print My book strcat(r,t); /* appends a copy of t to the end of r. The length of the resulting string is strlen(r) + strlen(s). */ cout << r << endl; // will print My book is blue return 0; } 2.2.3 The String Data Type

C++ has a string data type which can be used provided we include the header le string. There are many string operators which give similar behaviour to the cstring functions. Sample Program: strings.cpp /* File: strings.cpp This program illustrates the use of strings and string operators */ #include <iostream> #include <string> using namespace std; int main() { string result; //empty string string str1("My book"); // string created from a literal string str2; // empty string string str3; char str[] = " is black"; // a cstring str2 = str; // string assignment from cstring // string concatenation

result = str1 + str2;

cout << "result = " << result << endl; str1 += str2;

C/C++ Programming

10

cout << "str1 = " << str1 << endl; cout << "Enter two strings\n"; cin >> str1 >> str2; cout << str1 + str2 + "\n"; return 0; } Note that when reading strings the get from operator skips over any initial white space, reads non-white characters into the string and stops reading when whitespace is encountered. 2.2.4 The Reference Data Type

A reference variable is an alias for another variable. Reference variables are declared with an ampersand, &, following the data type. Reference variables must be initialized to refer to some preexisting variable at the time of declaration.. For example: float x = 10.0; float& y = x; // becomes an alias for x cout << y << endl; // will print 10.0 y = 20.0; // causes x to become 20.0 cout << x << endl; // will print 20.0

Reference variables are like pointers except that a reference variables alias variable cannot be changed. For example, float x = 10.0; float& y = x; // y is a reference to x float* p = &x; // p points to x float z = 20; p = &z; // p now points to z rather than x; y = z; // does not make y refer to z The power of reference variable will become apparent when they are used as function arguments and function returns.

2.3

Simple File Handling

The i/o operators >>, and << can be used with les as well as cin and cout. The following program illustrates how to do simple le handling. Sample Program: ftest.cpp // File ftest.cpp // Illustrates file i/o. #include <iostream> #include <fstream> // file version of iostream using namespace std;

C/C++ Programming

11

int main() { // declares and opens an input file stream using the // variable name infile and the disk file ftest.in ifstream infile ("ftest.in"); // declares and opens an output file stream using the // variable name outfile and the disk file ftest.out ofstream outfile ("ftest.out"); char c; int x; infile >> c; infile >> x; outfile << "c = " << c << endl; outfile << "x = " << x << endl; return 0; } /* ftest.in looks like: f 100 ftest.out looks like: c = f x = 100 */ Notes: 1. We can separate the declaration and opening of a le as follows: ifstream in; in.open("myinput.in"); 2. We can close a le with the close() function. We can reuse the le variable as we wish. For example: ifstream in; in.open("myinput.in"); . . in.close(); . . in.open("myother.in"); We can check for end of le with the function eof() as in the following example. Sample Program: eof.cpp // File eof.cpp // Shows how to read until end of file #include <iostream> #include <fstream> // file version of iostream

C/C++ Programming

12

using namespace std; int main() { ifstream fin ("testeof.in"); float x; int i = 0; while(1) { fin >> x; if( fin.eof() ) break; i++; cout << i << " values read\n"; } return 0; } The get-from operator returns the stream from which it is reading. If the end of le is reached during the read the stream will resolve (as a bool) to false so that we can rewrite the program as follows. Sample Program: eof1.cpp // File eof1.cpp // another version of read until end of file #include <iostream> #include <fstream> // file version of iostream using namespace std; int main() { ifstream fin ("testeof.in"); float x; int i = 0; while(fin >> x) { i++; cout << i << " values read\n"; } return 0; }

2.4

Functions

C++ diers from C in the handling of functions. 2.4.1 Pass by reference

In C, function arguments are passed by value to the function. If we want a function to modify a variable we pass the address of the variable as a function argument. In C++, we can do this another way by declaring that the argument is a reference variable. Here is an example. Sample Program: refval.cpp /* File: refval.cpp This program illustrates pass by reference, pass by value and pass by reference using pointers */

C/C++ Programming

13

#include <iostream> using namespace std; // Prototypes void swap(float& a, float& b); void noswap(float a, float b); void pswap(float* p, float* q); int main() { float x = 10.0, y = 20.0; noswap(x, y); cout << " x = " << x << " y = " << y <<endl; // no need to reset the values of x and y to their original values // x = 10.0; y = 20.0; swap(x, y); cout << " x = " << x << " y = " << y <<endl; // reset x and y to their original values x = 10.0; y = 20.0; pswap(&x, &y); cout << " x = " << x << " y = " << y <<endl; return 0; } /* In the function noswap the values of the variables a and b are interchanged but these are only copies of the variables in main so that the function does not actually swap the variables in main. */ void noswap(float a, float b) { float temp = a; a = b; b = temp; } /* In the function swap the & in an argument indicates that the variable is a reference, that is, inside the function the variable is actually the same variable as in main. */ void swap(float& a, float& b) { float temp = a; a = b; b = temp; } /* In the function pswap the variables are pointers to the variables in main so that when these variables are dereferenced the values of the variables in main are interchanged. */ void pswap(float *p, float *q) { float temp = *p; *p = *q; *q = temp; // swaps a and b // does not actually swap a and b // swaps *p and *q

C/C++ Programming

14

} /* x x x */

the output is: = 10 y = 20 = 20 y = 10 = 20 y = 10

2.4.2

Function Overloading

In C++ (but not in C) we can use the same name for dierent functions as long as the arguments lists dier. Sample Program: overload.cpp // File: overload.cpp // Illustrates function overloading #include <iostream> using namespace std; // prototypes void repeat_char(); void repeat_char(char ch); void repeat_char(char ch, int n); int main() { repeat_char(); repeat_char(=); repeat_char(+, 50); return 0; } // displays 45 asterisks void repeat_char() { int i; for(i=1; i<=45; i++) cout << *; cout << endl; return; } // displays 45 copies of ch void repeat_char(char ch) { int i; for(i=1; i<=45; i++) cout << ch; cout << endl; return; }

C/C++ Programming

15

// displays n copies of ch void repeat_char(char ch, int n) { int i; for(i=1; i<=n; i++) cout << ch; cout << endl; return; } 2.4.3 Default Arguments

In C++ (but not C) we can use default arguments to functions. That is, the last few arguments of a function can be given default values. Sample Program: defarg.cpp // File: defarg.cpp // Illustrates default function arguments #include <iostream> using namespace std; // prototypes void repeat_char(char ch=*, int n=45); int main() { repeat_char(); repeat_char(=); repeat_char(+, 50); return 0; } // displays n copies of ch with default n=45 and ch = * void repeat_char(char ch, int n) { int i; for(i=1; i<=n; i++) cout << ch; cout << endl; } 2.4.4 Return by Reference

Functions can return reference variables as in the following example. Sample Program: refvar.cpp // File: refvar.cpp // Illustrates reference variables #include <iostream> using namespace std;

C/C++ Programming

16

float& pick(float * a, int i); // returns a[i] int main() { float x = 10.0; float z = 15.0; float& y = x; // y is a reference to x; // a reference variable must be initialized when declared float a[5]; cout << y << endl; y = 20.0; // causes x to become 20.0 cout << x << endl; pick(a,3) = 12.0; cout << a[3] << endl; return 0; } float& pick(float * a, int i) { return a[i]; } Note that the main point here is that a function call which returns a reference can appear on the left side of an equation. This is not allowed for usual functions like sine, cosine, etc. One thing to watch out for is the dangling reference as illustrated in the following program. Sample Program: dangle.cpp // File: dangle.cpp // Illustrates dangling references in a function call #include <iostream> using namespace std; float& dangle(void); int main() { cout << dangle(); return 0; } float& dangle(void) { float temp = 10.0; return temp; }

C/C++ Programming

17

2.5

Dynamic Allocation

When dealing with static arrays In C it is traditional to use a #define rather than an explicit constant for the size of the array. So in C we might write: #define N 100 int main() { float data[N]; . . . In C++ is traditional to use a global constant for the array size. To make a variable global we declare it outside of main. To make the variable constant we use the modier const. So in C++ we might write: const int N = 100; int main() { float data[N]; . . . Both methods achieve the same purpose but the C++ version is preferred. Here is an example program in which we read a set of marks from a le and compute the average. Sample Program: markarrayp.cpp /* File: markarrayp.cpp This program illustrates the use of arrays as function arguments. A set of marks is read from a file using a function and the average computed using another function. */ #include <iostream> #include <fstream> using namespace std; const int N = 100; // maximum number of marks void readmarks(int& n, float* x); void average(float& xbar, int n, float* x); int main(void) { int n; float data[N]; float xbar;

// actual number of marks // array storing the marks // the average

/* read the marks into the array data[] note that n is passed as a reference to the function */ readmarks(n, data); /* compute the average note that xbar is passed as a reference to the function */

C/C++ Programming

18

average(xbar, n, data); cout << n << " marks were read\n"; cout << "The average is: " << xbar << "\n"; return 0; } void readmarks(int& n, float* x) { ifstream in("markarray.in"); n = 0; while(in >> x[n]) // read until end of file n++; } void average(float& xbar, int n, float* x) { float sum = 0.0; // sum of all marks int i = 0; // counter for a for loop for(i=0; i < n; i++) sum = sum + x[i]; xbar = sum/count; } In the program above the marks are stored in the array data which is declared by: const int N = 100; float data[N]; This is called static allocation of the array. The term static refers to the fact that the array size cannot be changed without recompiling the program. There is another form of array creation called dynamic allocation. In this form we allocate the array at run time rather than at compile time as is done with static allocation. C provides the function malloc to request memory dynamically. C++ provides a simpler approach using the operator called new. Instead of: float x[10]; We write: float* x; x = new float[10]; The new command is actually an operator which returns a pointer of the type stated. The entry in square brackets is the size of the array allocated. C provides the function free which allows us to release memory dynamically as the program is executing. C++ provides the operator called delete which serves the same purpose. For example, delete [] x;

C/C++ Programming

19

causes the previously allocated memory pointed to by x to be released. Note that the pointer x is still a valid variable and can be pointed somewhere else later. The syntax of new and delete is much simpler than that of malloc and free. Here is the marks program rewritten to use dynamic allocation. There is a signicant change to readmarks in that the value of the pointer to the array must be set within the function so that we must pass the pointer by reference. Sample Program: markarrayd.cpp /* File: markarrayd.cpp This program illustrates the use of dynamic allocation of arrays. A set of marks is read from a file using a function and the average computed using another function. */ #include <iostream> #include <fstream> using namespace std; void readmarks(int& n, float*& x); void average(float& xbar, int n, float* x); int main(void) { int n; float* data; float xbar;

// actual number of marks // pointer to the array storing the marks // the average

// allocate and read the marks into the array data readmarks(n, data); // compute the average average(xbar, n, data); // release the memory occupied by data cout << n << " marks were read\n"; cout << "The average is: " << xbar << "\n"; return 0; } void readmarks(int& n, float*& x) { ifstream fin("markarray.in"); float temp; int i; n = 0; // we first have to see how many marks there are while(fin >> temp) // read until end of file n++; /* Now we can allocate the array. The memory allocated exists for the duration of the program not just the function readmarks */ x = new float[n]; // need to rewind the file fin.close();

C/C++ Programming

20

fin.open("markarray.in"); // now we can use a for loop for(i = 0; i < n; i++) fin >> x[i]; } void average(float& xbar, int n, float* x) { float sum = 0.0; // sum of all marks int i = 0; // counter for a for loop for(i=0; i < n; i++) sum = sum + x[i]; xbar = sum/n; }

C/C++ Programming

21

Structures

A structure, called a struct in C and C++, forms a new data type consisting of one or more variables of possibly dierent data types. A structure is declared with the keyword struct followed by an identier and a list of declarations (called structure members) in braces. For example, to represent a point on a computer screen we might dene: struct { int int int }; point x; // horizontal position y; // vertical position c; // colour

The point structure has three members named x, y, and c. Note that the struct is merely a declaration describing the structures components. That is, we do not yet have any instances of points. After our declaration we can use the keyword point as if it is a data type like float or int or char. We can now declare variables of this new type. For example: struct point a, b; C++, but not C, permits dropping the keyword struct in this declaration. So, in C++ we would just say: point a, b; We now have two variables of type point and we can access their members x and y using the membership operator . as follows: a.x a.y a.c b.x b.y b.c = = = = = = 5; 6; 9; 4; 3; 8;

3.1

Point Example

Here is a program in which we have points as function arguments and function returns. Note that the struct variables are passed by reference. This is generally done to avoid making copies of structures which may occupy signicant amounts of memory. When we want to forbid a function from modifying a struct argument we use the keyword const before the argument. Sample Program: strpoint.cpp /* File: strpoint.cpp This program illustrates structures */ #include <iostream> #include <cmath> // needed for sqrt struct { int int int point x; // horizontal position y; // vertical position c; // colour

C/C++ Programming

22

}; // initializes a point void init_point(point& a, int h, int v, int clr); // shows the members of a point void show_point(const point& a); // computes the distance between two points float distance(const point& a, const point& b); // computes the sum of two points point sum(const point& a, const point& b); int main() { point a, b, c; float d; init_point(a, 5, 6, 8); init_point(b, 4, 4, 9); d = distance(a,b); cout << "The distance between the points "; show_point(a); cout << " and "; show_point(b); cout << " is " << d << "\n"; c = sum(a, b); cout << "The sum of the points "; show_point(a); cout << " and "; show_point(b); cout << " is "; show_point(c); cout << "\n"; return 0; } void init_point(point& a, int h, int v, int clr) { a.x = h; a.y = v; a.c = clr; } void show_point(const point& a) { cout << "(" << a.x <<", " << a.y << ", " << a.c << ")"; } float distance(const point& a, const point& b) { float result, dx, dy; dx = a.x - b.x; dy = a.y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point sum(const point& a, const point& b)

C/C++ Programming

23

{ point result; result.x = a.x + b.x; result.y = a.y + b.y; result.c = a.c + b.c; return result; } /* the output is: The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) */

3.2

Complex Numbers Example

We can use a structure to dene a data type representing complex numbers. Sample Program: complex.cpp /* File: complex.cpp Implementation of complex numbers as a structure */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; struct complex { float x; // real part float y; // imaginary part }; // initializes a complex number void init_complex(complex& z, float a, float b); // Real and Imaginary parts float Real(const complex& z); float Imag(const complex& z); // print a complex number void print(const complex& z); // computes the sum of two points complex add(const complex& u, const complex& v); // computes the product of two complex numbers complex mult(const complex& u, const complex& v); // computes the quotient of two complex numbers complex quotient(const complex& u, const complex& v); int main() { complex u, v, w; init_complex(u, 2.0, 3.0); init_complex(v, 1.0, 1.0);

C/C++ Programming

24

w = add(u,v); cout << "The sum of "; print(u); cout << " and "; print(v); cout << " is "; print(w); cout << "\n"; w = mult(u,v); cout << "The product of "; print(u); cout << " and "; print(v); cout << " is "; print(w); cout << "\n"; w = quotient(u,v); cout << "The quotient of "; print(u); cout << " and "; print(v); cout << " is "; print(w); cout << "\n"; return 0; } // initializes a complex number void init_complex(complex& z, float a, float b) { z.x = a; z.y = b; } // Real and Imaginary parts float Real(const complex& z) { return z.x; } float Imag(const complex& z) { return z.y; } // print a complex number void print(const complex& z) { cout << z.x; if (z.y < 0.0) cout << " - " << -z.y << "j"; else cout << " + " << z.y << "j"; } // computes the sum of two complex numbers complex add(const complex& u, const complex& v) { complex z; init_complex(z, u.x+v.x, u.y+v.y); return z; } // computes the product of two complex numbers

C/C++ Programming

25

complex mult(const complex& u, const complex& v) { complex z; init_complex(z, u.x*v.x - u.y*v.y, u.x*v.y + u.y*v.x); return z; } // computes the quotient of two complex numbers complex quotient(const complex& u, const complex& v) { complex z; float d = v.x*v.x + v.y*v.y; init_complex(z, (u.x*v.x + u.y*v.y)/d, (-u.x*v.y + u.y*v.x/d) ); return z; }

3.3

Operators and Operator Overloading

When we add two oats to get another oat (z = x + y) we are really calling a function of two variables (a binary function): float add(float x, float y) { return (x+y); } That is, the operator + is actually a function call where the left operand is the rst argument and the right operand is the second argument of the function. In C++ we can refer to the + operator as operator +. Similarly we have operator *, operator / etc. Since operators are really functions we can use function overloading to dene the various operators for our structures. This is called operator overloading. All existing operators can be overloaded but no new operators can be created. The order of precedence among the operators cannot be changed. That is, operator * has higher precedence than operator +. In our struct complex the add function could be replaced by operator +. complex operator + (const complex & u, const complex & v) Now when we have three complex variables u, v, and w in our main program we will invoke the operator as: w = u + v; This means the same as: w = operator + (u, v); Here is our complex number program rewritten to use operator overloading. Sample Program: complexo.cpp /* File: complexo.cpp Implementation of complex numbers as a structure with operator overloading*/ #include <iostream> #include <cmath> // needed for sqrt using namespace std;

C/C++ Programming

26

struct complex { float x; // real part float y; // imaginary part }; // initializes a complex number void init_complex(complex& z, float a, float b); // Real and Imaginary parts float Real(const complex& z); float Imag(const complex& z); // print a complex number void print(const complex& z); // computes the sum of two points complex operator + (const complex& u, const complex& v); // computes the product of two complex numbers complex operator *(const complex& u, const complex& v); // computes the quotient of two complex numbers complex operator /(const complex& u, const complex& v); int main() { complex u, v, w; init_complex(u, 2.0, 3.0); init_complex(v, 1.0, 1.0); w = u + v; cout << "The sum of "; print(u); cout << " and "; print(v); cout << " is "; print(w); cout << "\n"; w = u * v; cout << "The product of "; print(u); cout << " and "; print(v); cout << " is "; print(w); cout << "\n"; w = u / v; cout << "The quotient of "; print(u); cout << " and "; print(v); cout << " is "; print(w); cout << "\n"; return 0; } // initializes a complex number void init_complex(complex& z, float a, float b) { z.x = a; z.y = b; }

C/C++ Programming

27

// Real and Imaginary parts float Real(const complex& z) { return z.x; } float Imag(const complex& z) { return z.y; } // print a complex number void print(const complex& z) { cout << z.x; if (z.y < 0.0) cout << " - " << -z.y << "j"; else cout << " + " << z.y << "j"; } // computes the sum of two complex numbers complex operator + (const complex& u, const complex& v) { complex z; init_complex(z, u.x+v.x, u.y+v.y); return z; } // computes the product of two complex numbers complex operator * (const complex& u, const complex& v) { complex z; init_complex(z, u.x*v.x - u.y*v.y, u.x*v.y + u.y*v.x); return z; } // computes the quotient of two complex numbers complex operator /(const complex& u, const complex& v) { complex z; float d = v.x*v.x + v.y*v.y; init_complex(z, (u.x*v.x + u.y*v.y)/d, (-u.x*v.y + u.y*v.x/d) ); return z; } We can even overload the I/O operators. When we do so we make the operator return the stream by reference so that we can concatenate calls to the operator. For example, cout << x << y;

C/C++ Programming

28

The rst call is cout << x and it returns the stream so that the second call has the stream as the left operand. cout << x << y; |________| | cout << y; We always pass and return the streams by reference to avoid making copies of the stream. Here is a rewrite of the complex number program using I/O operator overloading. Sample Program: complexio.cpp /* File: complexo.cpp Implementation of complex numbers as a structure with operator overloading*/ #include <iostream> #include <cmath> // needed for sqrt using namespace std; struct complex { float x; // real part float y; // imaginary part }; // initializes a complex number void init_complex(complex& z, float a, float b); // Real and Imaginary parts float Real(const complex& z); float Imag(const complex& z); // operator put-to ostream& operator << (ostream& out, const complex& z); // operator get-from istream& operator >> (istream& in, complex& z); // computes the sum of two points complex operator + (const complex& u, const complex& v); // computes the product of two complex numbers complex operator *(const complex& u, const complex& v); // computes the quotient of two complex numbers complex operator /(const complex& u, const complex& v); int main() { complex u, v, w; cout << "Enter two complex numbers in the form x + yj\n"; cin >> u >> v; w = u + v; cout << "The sum of " << u << " and " << v << " is " << w << "\n"; w = u * v; cout << "The product of " << u << " and " << v << " is " << w << "\n";

C/C++ Programming

29

w = u / v; cout << "The quotient of " << u << " and " << v << " is " << w << "\n"; return 0; } // initializes a complex number void init_complex(complex& z, float a, float b) { z.x = a; z.y = b; } // Real and Imaginary parts float Real(const complex& z) { return z.x; } float Imag(const complex& z) { return z.y; } ostream& operator <<(ostream& out, const complex& z) { out << z.x; if (z.y < 0.0) out << " - " << -z.y << "j"; else out << " + " << z.y << "j"; return out; } istream& operator >>(istream& in, complex& z) { char sign, j; in >> z.x >> sign >> z.y >> j; if(sign == -) z.y = -z.y; return in; } // computes the sum of two complex numbers complex operator + (const complex& u, const complex& v) { complex z; init_complex(z, u.x+v.x, u.y+v.y); return z; } // computes the product of two complex numbers complex operator * (const complex& u, const complex& v) { complex z;

C/C++ Programming

30

init_complex(z, u.x*v.x - u.y*v.y, u.x*v.y + u.y*v.x); return z; } // computes the quotient of two complex numbers complex operator /(const complex& u, const complex& v) { complex z; float d = v.x*v.x + v.y*v.y; init_complex(z, (u.x*v.x + u.y*v.y)/d, (-u.x*v.y + u.y*v.x/d) ); return z; }

C/C++ Programming

31

Classes

A class in C++ is an extension of the idea of a struct in C. With a struct we bring together data of possibly dierent types to create a new datatype. With a class we also bring together the functions which manipulate the data members making them also members of the class. A class in C++ is a user dened datatype. An object is a variable of the type dened. The class description also has access modiers that allow data hiding. Access that is public is available to any part of the code. Access that is private is restricted principally to use by the class code itself. Allowing private and public visibility for members gives the programmer control over what parts of the data structure are modiable. The private parts are hidden from client code, and the public parts are available. It is then possible to change the implementation of the hidden parts without aecting the public interface. This hiding of data and implementation can lead to ease of maintainance of large projects.

4.1

A First Example

As a rst example of a class we can convert the struct point to a class. Example: a point class class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); void show(void); float distance(const point& b); point sum(const point& b); }; Notes: 1. The elements within a class are called the members of the class. 2. The member of the class which are functions are called member functions or methods and the other members are called data members. 3. The label private tells the compiler that the members which follow cannot be accessed from outside the class. 4. The label public tells the compiler that the members which follow can be accessed from outside the class. There is another access type called protected which well deal with later. 5. This is a class declaration only. We must eventually elaborate and dene each member function. These will be placed after main along with all other functions. 6. A constructor has the same name as the class; it must not have a return type or a return statement. A constructor may or may not have arguments. The constructors job is to initialize the object. The constructor for our class is dened following main by: point::point(int h, int v, int clr) { x = h; y = v; c = clr; } // // // // constructor for the class point show the members of point to cout distance to point b sum with point b

C/C++ Programming

32

7. The notation point::function name tells the compiler that the function referred to is a member of the class point. The :: is called the scope resolution operator. The scope of a variable is the block of code where the variable is valid. For example, when we write point::show() we are referring to the function show which belongs to the class point. Keep in mind that there may be many other classes containing a method called show. The scope resolution operator is a notation for keeping track of which member of which class we are working with. For the constructor the scope resolution operator notation looks a little unwieldy as the class name and the member name are the same. 8. It is useful to compare the constructor for class point to the function init point previously dened for struct point. void init_point(point& a, int h, int v, int clr) { a.x = h; a.y = v; a.c = clr; } These two functions have the same purpose: to initialize the object. There is one notable dierence: init point has the point itself as an argument passed by reference along with the three ints while the constructor has only the three ints. That is because the constructor is a member of the class so that there is an implicit object of which the function is a member. 9. The show member function of the class is coded as follows. void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } Note that again the analogous function for struct point has the point itself as a argument passed by reference. void show_point(const point& a) { cout << "(" << a.x <<", " << a.y << ", " << a.c << ")"; } The explanation of this dierence is the same: for the class point the function show is a member and so is acting on an implicit object. Lets look at the complete program. Sample Program: point.cpp /* File: point.cpp This program illustrates classes */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position

C/C++ Programming

33

int c; // colour public: point(int h, int v, int clr); void show(void); float distance(const point& b); point sum(const point& b); }; int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; d = a.distance(b); cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = a.sum(b); cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is " ; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::distance(const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::sum(const point& b) { int sumx = x + b.x; int sumy = y + b.y; int sumc = c + b.c; point result(sumx, sumy, sumc); // // // // constructor for the class point show the members of point to cout distance to point b sum with point b

C/C++ Programming

34

return result; } /* the output is: The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) */ Notes 1. The declaration: point a(5, 6, 8) causes a variable (object) of type point named a to be created. The constructor method is automatically executed by this declaration and is passed the arguments (5, 6, 8). This means that the data members x, y, and c of the object a are set to 5, 6, and 8 respectively. 2. When we have an object of type point we can refer to its members using the membership operator . (period). That is, the data members of a are: a.x, a.y, and a.c and the function members (methods) are: a.show(), a.distance(), and a.sum()). 3. In the main program we can invoke the distance function belonging to a with argument b by a.distance(b); 4. In the main program we are allowed to access the public members of point objects but we cannot directly refer to any private members. If we were to insert a line in main like: a.x = 14; the compiler would give us an error message like: point.cpp: In function int main(): point.cpp:27: member x is a private member of class point

4.2

Modeling Physical Objects with Classes

Physical objects are characterized by their behaviour and their state. The behaviour of a physical object is its response to stimuli. That is, behaviour is what an object can do. The state of a physical object is the information necessary to determine the object completely. The state of an object is not well dened in that the information needed to determine an object can be given in dierent but equivalent forms. For example, the state of a power supply could be equally well represented by an indicator needle, currents on circuits inside the supply or by the buttons pushed on the front panel. The representations are dierent even though the essential information is identical. The behaviour of a physical object either changes its state, depends on its state or both. When we use a C++ class to represent a physical object the member functions of the class represent the behaviour of the physical object and the data members of the class represent the state of the physical object.

C/C++ Programming

35

+--------------- -+ +-----------------+ | | | | | behaviour |<-------------------->| member functions| | | | | +-----------------+ +-----------------+ | | | | | state |<-------------------->| data members | | | | | +-----------------+ +-----------------+ physical object C++ class The designers of a C++ class need to know the details of both the data members (state) and the member functions (behaviour). The users of the class need only be concerned with the member functions (behaviour) of the class. For this reason we put data members in the private section of the class to hide them from the users. This is called information hiding. Here is an example in which we model a coke machine as a class. Sample Program: coke.cpp /* File: coke.cpp Program to illustrate how classes can simulate physical objects with internal deposits modeled as data members and behaviour modeled as member functions */ #include <iostream> #include <fstream> using namespace std; /* The following class represents a coke machine which 1. can accept inputs: q = deposit quarter d = deposit dollar r = press refund

2. has an internal deposit = money currently deposited = 0, 25, 50, or 75 3. has another internal which records whether a coke was dispensed (1) or not (0) (cokes cost 1 dollar) 4. has another internal deposit which records the amount of refund = 0, 25, 50, or 75, given 5. all are triggered by the behaviour represented by the update function */ class coke_machine { public: coke_machine(); // constructor // updates the private data based on a users action void update(char input); // prints the private data to cout void printdata(void); private: int deposit; // money currently in machine

C/C++ Programming

36

int coke; int refund; }; int main() { coke_machine x; char input; x.printdata(); do { cout cout cout cout cout cout << << << << << <<

// 1 indicates coke was dispensed, 0 means no coke // amount refunded

"\n\n"; "Enter a command: " << "\n"; "q = deposit quarter" << "\n"; "d = deposit dollar" << "\n"; "r = refund" << "\n"; "e = end" << "\n\n";

cin >> input; if( (input == q) || (input == d) || (input == r) ) { cout << "input is " << input << "\n"; x.update(input); x.printdata(); } }while (input != e); return 0; } coke_machine::coke_machine() // constructor { deposit = 0; coke = 0; refund = 0; } // updates the private data based on a users action void coke_machine::update(char input) { /* need a lookup table; the entries in the table are the values of the private data: deposit, coke, refund action q d r ---------------------------------------------------------deposit = 0 25, 0, 0 0, 1, 0 0, 0, 0 deposit = 25 50, 0, 0 0, 1, 25 0, 0, 25 deposit = 50 75, 0, 0 0, 1, 50 0, 0, 50 deposit = 75 0, 1, 0 0, 1, 75 0, 0, 75 */ if (input == q) { if (deposit == 0) {

C/C++ Programming

37

deposit = 25; coke = 0; refund = 0; } else if (deposit == 25) { deposit = 50; coke = 0; refund = 0; } else if (deposit == 50) { deposit = 75; coke = 0; refund = 0; } else if (deposit == 75) { deposit = 0; coke = 1; refund = 0; } } if (input == d) { if (deposit == 0) { deposit = 0; coke = 1; refund = 0; } else if (deposit == { deposit = 0; coke = 1; refund = 25; } else if (deposit == { deposit = 0; coke = 1; refund = 50; } else if (deposit == { deposit = 0; coke = 1; refund = 75; } } if (input == r) { if (deposit == 0) { deposit = 0; coke = 0; refund = 0; } else if (deposit == {

25)

50)

75)

25)

C/C++ Programming

38

deposit = 0; coke = 0; refund = 25; } else if (deposit == 50) { deposit = 0; coke = 0; refund = 50; } else if (deposit == 75) { deposit = 0; coke = 0; refund = 75; } } } // prints the private data to the screen void coke_machine::printdata() { cout << "total in machine: " << deposit << "\n"; if (coke == 1) cout << "coke dispensed" << "\n"; else cout << "coke not dispensed" << "\n"; cout << "refund : " << refund << "\n"; cout << "-----------------------------" << "\n"; }

4.3

Constructors

A class can have multiple constructors. If we dont provide any constructors the compiler will supply a default constructor. In the case of the class point the default constructor will not initialize the three integer data members. As these members are private we cannot subsequently set them. This forces us to supply our own constructor for this class. There are cases where we can rely on the default constructor supplied by the compiler. In the point class we might want to have a constructor with no arguments in addition to the one we do have. We can do this as follows: class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(void); point(int h, int v, int clr); void show(void); float distance(const point& b); point sum(const point& b); }; point::point(void) { x = 0; y = 0; c = 0; } // // // // // constructor with no arguments constructor for the class point show the members of point to cout distance to point b sum with point b

C/C++ Programming In our main program we can now declare point objects without giving arguments. int main() { point a(5, 6, 8), b(4, 4, 9), c; . . . Another approach is to have a constructor with default arguments. class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h=0, int v=0, int clr=0); // constructor for the class point void show(void); // show the members of point to cout float distance(const point& b); // distance to point b point sum(const point& b); // sum with point b }; point::point(int h, int v, int clr) { x = h; y = v; c = clr; } We can now declare point giving any number of arguments. int main() { point a(5, 6, 8), b(4, 4), c(3), d; . .

39

There is an interesting outcome of having a constructor with a single argument. We can then regard this constructor as a conversion function from the type of the argument to the type dened by the class. Here is a program illustrating this idea. Sample Program: pointc.cpp /* File: pointc.cpp This program illustrates classes */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public:

C/C++ Programming

40

point(int h=0, int v=0, int clr=0);// constructor for the class point void show(void); // show the members of point to cout float distance(const point& b); // distance to point b point sum(const point& b); // sum with point b }; int main() { point a(5, 6, 8), b, c; float d; b = 2; /* the line above is the same as point temp(2); b = temp; */ d = a.distance(b); cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = a.sum(3); // 3 becomes the point (3, 0, 0 ) as above cout << "The sum of the points "; a.show(); cout << " and "; ((point) 3).show(); // cast 3 to a point cout << " is "; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::distance(const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::sum(const point& b) {

C/C++ Programming

41

int sumx = x + b.x; int sumy = y + b.y; int sumc = c + b.c; point result(sumx, sumy, sumc); return result; } /* the output is: The distance between the points (5, 6, 8) and (2, 0, 0) is 6.7082 The sum of the points (5, 6, 8) and (3, 0, 0) is (8, 6, 8) */

4.4

Object Pointers

As previously noted, the constructor is invoked when an object is created (or constructed). We usually think of this as occurring when we declare an object in our program but the constructor also runs when we create an object dynamically with the new operator. The syntax in this case is to place the arguments of the constructor after the class name in the new statement. For example, with the point class we may have the following code: int main() { point* ptr; ptr = new point(1, 2, 3); cout << "The point is "; (*ptr).show(); . . . There is an alternate notation when dealing with object pointers. In our example above we dereference the object pointer and then access a member. This take 4 keystrokes, (*object pointer).member. The alternate notation is object pointer->member. This saves 2 keystrokes and seems to look nicer. Here is our previous main program rewritten to use pointers. Sample Program: pointp.cpp /* File: pointp.cpp This program illustrates pointers to objects*/ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); void show(void); // constructor for the class point // show the members of point to cout

C/C++ Programming

42

float distance(const point& b); // distance to point b point sum(const point& b); // sum with point b }; int main() { point *p; p = new point(5, 6, 8);

// declare and allocate in two statements

point* q = new point(4, 4, 9); // declare and allocate in one statement point* r = new point(0, 0, 0); float d; d = p->distance(*q); cout << "The distance between the points "; p->show(); cout << " and "; q->show(); cout << " is " << d << "\n"; *r = p->sum(*q); cout << "The sum of the points "; p->show(); cout << " and "; q->show(); cout << " is " ; r->show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::distance(const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::sum(const point& b) { int sumx = x + b.x; int sumy = y + b.y; int sumc = c + b.c; point result(sumx, sumy, sumc);

C/C++ Programming

43

return result; } /* the output is: The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) */

4.5

The this Pointer

When we are writing the code for the member functions of a class we dont yet have any objects declared. In some cases we nd that we want to refer to the object which will exist when we write a main program. For example: class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); void show(void); float distance(const point& b); point sum(const point& b); point* where_am_I(void); }; When we try to write the code for the member where am I() we need a pointer to the object, but the object doesnt exist yet. C++ gets around this chicken and egg situation by providing a new keyword, this, which is a pointer to the implicit object. Here is the code for the function: point* point::where_am_I(void) { return this; } If we like we can make use of the pointer this as we code the class. Here are the other members rewritten using this. Sample Program: pointt.cpp /* File: pointt.cpp This program illustrates the pointer this*/ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: // // // // // constructor for the class point show the members of point to cout distance to point b sum with point b returns the pointer to the object

C/C++ Programming

44

point(int h, int v, int clr); void show(void); float distance(const point& b); point sum(const point& b); point* where_am_I(void); };

// // // // //

constructor for the class point show the members of point to cout distance to point b sum with point b returns the pointer to the object

int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; d = a.distance(b); cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = a.sum(b); cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is " ; c.show(); cout << "\n"; cout << "The point a is at " << (unsigned long) a.where_am_I() << "\n"; return 0; } point::point(int h, int v, int clr) { this->x = h; this->y = v; this->c = clr; } void point::show(void) { cout << "(" << this->x <<", " << this->y << ", " << this->c << ")"; } float point::distance(const point& b) { float result, dx, dy; dx = this->x - b.x; dy = this->y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::sum(const point& b) { int sumx = this->x + b.x; int sumy = this->y + b.y; int sumc = this->c + b.c; point result(sumx, sumy, sumc); return result;

C/C++ Programming

45

} point* point::where_am_I(void) { return this; } /* the output is: The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) The point a is at 3221223676 */

4.6

The Destructor

The opposite of a constructor is a destructor. The constructor executes when an object is created and the destructor executes when the object goes out of scope or is explicitly deleted. The constructor has the same name as the class and the destructor has the same name preceded by a tilde. For example, for the class point the destructor is called point(). If we dont provide a destructor the compiler will supply a default one. The default destructor will release the memory occupied by statically allocated members but does not release the memory of dynamically allocated members. The destructor plays a signicant role only when the object contains dynamically allocated data members. For our point class the default destructor releases the memory occupied by the three ints which make up an object. For illustration purposes we can write our own destructor for the class point so that it prints a small message as it executes. We can then see exactly when the destructor is invoked. We might as well modify the constructor so that it prints a small message too and then we can keep track of constructions and destructions in our program. Sample Program: pointd.cpp /* File: pointd.cpp This program illustrates the destructor */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); ~point(void); void show(void); float distance(const point& b); point sum(const point& b); }; int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; // // // // // constructor for the class point the destructor show the members of point to cout distance to point b sum with point b

C/C++ Programming

46

d = a.distance(b); cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = a.sum(b); cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is "; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; cout << "creating the point "; show(); cout << "\n"; } point::~point(void) { cout << "destroying the point "; show(); cout << "\n"; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::distance(const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::sum(const point& b) { int sumx = x + b.x; int sumy = y + b.y; int sumc = c + b.c; point result(sumx, sumy, sumc); return result; } /* the output is:

C/C++ Programming

47

creating the point (5, 6, 8) <--- construction of a creating the point (4, 4, 9) <--- construction of b creating the point (0, 0, 0) <--- construction of c The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 creating the point (9, 10, 17) <--- construction of result destroying the point (9, 10, 17) <--- destruction of result destroying the point (9, 10, 17) <--- destruction of copy of result The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) destroying the point (9, 10, 17) <--- destruction of c destroying the point (4, 4, 9) <--- destruction of b destroying the point (5, 6, 8) <--- destruction of a */ Note that there appears to be one more destruction than construction in our program. A copy of the object result is made by the compiler to give back to the main program. Our constructor is not called to make this copy. There is another constructor called the copy constructor which the compiler supplies since we havent supplied it ourselves.

4.7

The Copy Constructor

As noted in our previous program, when a copy of an existing object is to be made the copy constructor is called. If we dont provide a copy constructor for a class then the compiler supplys a default one. The default copy constructor creates a new object and assigns the values of the member variables in the new object to be the same as in the existing object. The copy constructor is passed the existing object by reference and is not suppose to change the existing object. This tells us that the argument is const. For illustration purposes we can write our own copy constructor for the class point so that it prints a small message as it executes. Here is a modied program for class point including our own copy constructor. Sample Program: pointcc.cpp /* File: pointcc.cpp This program illustrates the copy constructor */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); point(const point& a); ~point(void); void show(void); float distance(const point& b); point sum(const point& b); }; int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; // // // // // // constructor for the class point the copy constructor the destructor show the members of point to cout distance to point b sum with point b

C/C++ Programming

48

d = a.distance(b); cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = a.sum(b); cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is "; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; cout << "creating the point "; show(); cout << "\n"; } point::point(const point& a) { x = a.x; y = a.y; c = a.c; cout << "copying the point "; show(); cout << "\n"; } point::~point(void) { cout << "destroying the point "; show(); cout << "\n"; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::distance(const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::sum(const point& b) {

C/C++ Programming

49

int sumx = x + b.x; int sumy = y + b.y; int sumc = c + b.c; point result(sumx, sumy, sumc); return result; } /* the output is: creating the point (5, 6, 8) <--- construction of a creating the point (4, 4, 9) <--- construction of b creating the point (0, 0, 0) <--- construction of c The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 creating the point (9, 10, 17) <--- construction of result copying the point (9, 10, 17) <--- copy of result destroying the point (9, 10, 17) <--- destruction of result destroying the point (9, 10, 17) <--- destruction of copy of result The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) destroying the point (9, 10, 17) <--- destruction of c destroying the point (4, 4, 9) <--- destruction of b destroying the point (5, 6, 8) <--- destruction of a */

4.8

Operators and Operator Overloading

As for structures we can overload operators for classes. When we are dening an operator on classes we sometimes have the choice of making the operator a method of the class or an external function. In our class point the sum member could be replaced by operator +. Although operator + is usually a binary operator, when we make it a member of our class it will appear to be a function of one variable as the other variable is the current object. That is, the operator is declared as: point operator + (const point & b) Now when we have three point objects a, b, and c in our main program we will invoke the operator as: c = a + b; This means the same as: c = a.operator +(b); We might also make the distance member into an operator. It seems reasonable to overload operator - so that we can think of a - b as the distance between a and b. Sample Program: pointo.cpp /* File: pointo.cpp This program illustrates operator overloading */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public:

C/C++ Programming

50

point(int h, int v, int clr); // point(const point& a); // void show(void); // float operator - (const point& b); // point operator + (const point& b); // };

constructor for the class point the copy constructor show the members of point to cout distance to point b sum with point b

int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; d = a - b; cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = a + b; cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is "; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; } point::point(const point& a) { x = a.x; y = a.y; c = a.c; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::operator - (const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::operator + (const point& b) { int sumx = x + b.x; int sumy = y + b.y;

C/C++ Programming

51

int sumc = c + b.c; point result(sumx, sumy, sumc); return result; }

4.9

Member Operators verus External Operators

In most cases it is conceptually easier to program an operator as a external function rather than a member of a class. The only drawback with this is that the external function must somehow access the private members of the class. This is accomplished using the concept of a friend to the class. A friend of a class is a function (or another class) which is permitted full access to the private members of the class. For now, we will only consider friends which are functions. Friends of a class are declared by placing the keyword friend before the function declaration and placing the prototype inside the class declaration. This does not mean that the function is a member of the class. Here as a reworking of the class point with external operators. The default copy constructor and destructor are used. Sample Program: pointeo.cpp /* File: pointeo.cpp This program shows a complete implementation of the class point using external operators */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); void show(void); // constructor for the class point // show the members of point to cout

/* these are NOT members of the class, they are friends to the class */ friend float operator - (const point& a, const point& b); // distance friend point operator + (const point& a, const point& b); // sum }; int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; d = a - b; cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = (a + b);

C/C++ Programming

52

cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is "; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; } void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float operator - (const point& a, const point& b) { float result, dx, dy; dx = a.x - b.x; dy = a.y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point operator + (const point& a, const point& b) { int sumx = a.x + b.x; int sumy = a.y + b.y; int sumc = a.c + b.c; point result(sumx, sumy, sumc); return result; } If we overload operator << and operator >> then our objects can intermix with the builtin data types in a transparent fashion. We can modify the class point to program the I/O operators as follows: Sample Program: pointio.cpp /* File: pointio.cpp This program shows a complete implementation of the class point using external operators */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position

C/C++ Programming

53

int c; // colour public: point(int h, int v, int clr); // constructor for the class point

/* these are NOT members of the class, they are friends to the class */ friend float operator - (const point& a, const point& b); // distance friend point operator + (const point& a, const point& b); // sum friend istream& operator >> (istream& in, point& a); // operator >> friend ostream& operator << (ostream& out, const point& a);// operator << }; int main() { point a(0,0,0), b(0, 0, 0 ), c(0, 0, 0); float d; cout << "Enter the two points in the form (x,y,c)\n"; cin >> a >> b; d = a - b; cout << "The distance between the points " << a << " and "; cout << b << " is " << d << "\n"; c = (a + b); cout << "The sum of the points " << cout << b << " is " << c << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; } ostream& operator << (ostream& out, const point& a) { out << "(" << a.x <<", " << a.y << ", " << a.c << ")"; return out; } istream& operator >> (istream& in, point& a) { char junk; in >> junk >> a.x >> junk >> a.y >> junk >> a.c >> junk; return in; } float operator - (const point& a, const point& b) { float result, dx, dy; a << " and ";

C/C++ Programming

54

dx = a.x - b.x; dy = a.y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point operator + (const point& a, const point& b) { int sumx = a.x + b.x; int sumy = a.y + b.y; int sumc = a.c + b.c; point result(sumx, sumy, sumc); return result; }

4.10

The Assignment Operator, Operator =

Suppose that we have two objects a and b and we write a = b in our program. We are invoking the binary operator =. The compiler interprets this as a member function call: a.operator = (b) The compiler supplies a default operator = in case we dont dene one ourselves. The default operator = assigns each data members of the left hand side object the value of the corresponding data member of the right hand side object. This is usually what we want but may not be the best choice when the data members are pointers to arrays. Note that operator = cannot be external, it must be a member of the class. For illustration purposes we can write our operator = for the class point so that it prints a small message as it executes. Here is a modied program for class point including our own operator =. Sample Program: pointoe.cpp /* File: pointoe.cpp This program shows a complete implementation of the class point using operators */ #include <iostream> #include <cmath> // needed for sqrt using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); point(const point& a); ~point(void); point& operator = (const point& a); void show(void); float operator - (const point& b); // // // // constructor for the class point the copy constructor the destructor operator =

// show the members of point to cout // distance to point b

C/C++ Programming

55

point operator + (const point& b); };

// sum with point b

int main() { point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); float d; d = a - b; cout << "The distance between the points "; a.show(); cout << " and "; b.show(); cout << " is " << d << "\n"; c = (a + b); cout << "The sum of the points "; a.show(); cout << " and "; b.show(); cout << " is "; c.show(); cout << "\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; cout << "creating the point "; show(); cout << "\n"; } point::point(const point& a) { x = a.x; y = a.y; c = a.c; cout << "copying the point "; show(); cout << "\n"; } point::~point(void) { cout << "destroying the point "; show(); cout << "\n"; } point& point::operator = (const point& a) { x = a.x; y = a.y; c = a.c; cout << "assigning the point "; show(); cout << "\n"; return (*this); }

C/C++ Programming

56

void point::show(void) { cout << "(" << x <<", " << y << ", " << c << ")"; } float point::operator - (const point& b) { float result, dx, dy; dx = x - b.x; dy = y - b.y; result = sqrt(dx*dx + dy*dy); return result; } point point::operator + (const point& b) { int sumx = x + b.x; int sumy = y + b.y; int sumc = c + b.c; point result(sumx, sumy, sumc); return result; } /* The output is creating the point (5, 6, 8) creating the point (4, 4, 9) creating the point (0, 0, 0) The distance between the points (5, 6, 8) and (4, 4, 9) is 2.23607 creating the point (9, 10, 17) copying the point (9, 10, 17) destroying the point (9, 10, 17) assigning the point (9, 10, 17) destroying the point (9, 10, 17) The sum of the points (5, 6, 8) and (4, 4, 9) is (9, 10, 17) destroying the point (9, 10, 17) destroying the point (4, 4, 9) destroying the point (5, 6, 8) */

4.11

Composition: Objects as Members of Classes

If we have an existing class and we create a new class which has an object of the existing class as a member then we say that the new class is formed by composition. We will see later that there is another way, called inheritance, to form a new class from an existing one. Our class point is meant to represent a point on a computer screen. If we want to represent a string at a position on a computer screen we might use the following class. class point { private: int x; // horizontal position int y; // vertical position int c; // colour public:

C/C++ Programming

57

point(int h, int v, int clr); void show(ostream& out); }; class message { private: string text; point where;

// constructor // show the members of point to out

public: message(const string& str, const point& p); // constructor void show(ostream& out); // show the members of message }; When an object is created, its constructor is called automatically. We pass arguments to the constructor by enclosing an argument list in round brackets after the object name. For example: point a(5, 6, 8), b(4, 4, 9), c(0, 0, 0); string str("Its hot"); message m(str, a); Member objects are constructed in the order in which they are declared in the class declaration. That is, for the message class, rst text then where are created as the constructor runs. The constructor for class point might be coded as follows. point::point(int h, int v, int clr) { x = h; y = v; c = clr; } If we try the same syntax for class message we will run into a problem. The following code will not compile. message::message(const string& str, const point& p) { text = str; where = p; } This works for the string member of message because the class string has a consructor with a void argument list. That is, rst an empty string, text, is created and then the operator = for the class string is used to assign text the value of str. The code does not work for the point member of message because there is not a point constructor with a void argument list. So, we cannot create an empty point and then assign it using operator = There is another notation for initializing data members. We put a colon after declaration of the constructor followed a list of member intializers with arguments to the constructors. For example: message::message(const string& str, const point& p) : text(str), where(p) { // nothing to do now } When using initializers it is recommeded that:

C/C++ Programming Avoid using the value of one member in the initialization of another member. Member intializers should appear in the same order in which the members are declared. A member intializer does not need to be provided for a data member in three cases: 1. The data member is a built in type.

58

2. The data member is a userdened type and the class has a constructor with a void argument. 3. The data member is a userdened type and the class does not have any constructors. In this case the default constructor is used. A member initializer must be provided in three cases: 1. There is a constructor but not one with a void argument. 2. The data member is a reference and therefore must be initialized. 3. The data member is declared const. A const variable is a variable that, once intialized, cannot be changed. Likewise, a const member cannot be changed once it is initialized. When a class is formed by composition the four methods: constructor, copy constructor, destructor and opertor = for the class must involve the same methods for object members. Here is the class point and class message with all four methods coded. Sample Program: message.cpp /* File: message.cpp When a class has members which are objects of another class then we say that the class is formed by composition. */ #include <iostream> #include <string> using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); point(const point& a); ~point(void); point& operator = (const point& a); void show(ostream& out); }; class message { private: string text; point where; public: message(const string& str, const point& p); message(const message& m); // // // // constructor for the class point the copy constructor the destructor operator =

// show the members of point

C/C++ Programming

59

~message(void); message& operator = (const message& m); void show(ostream& out); }; int main() { point p(1,2,3); string str("Hello"); cout << "\nCreate a static message\n"; message m(str, p); cout << "\n\nUse the copy constructor for message\n"; message n(m); cout << "\n\nCreate a dynamic message\n"; message* ptr = new message(str, p); cout << "\n\nUse operator = \n"; m = n; cout << "\n\nDelete the dynamic message\n"; delete ptr; cout << "\n\nStatic objects are destroyed as the program ends\n"; return 0; } point::point(int h, int v, int clr) { x = h; y = v; c = clr; cout << "creating the point "; show(cout); cout << "\n"; } point::point(const point& a) { x = a.x; y = a.y; c = a.c; cout << "copying the point "; show(cout); cout << "\n"; } point::~point(void) { cout << "destroying the point "; show(cout); cout << "\n"; } point& point::operator = (const point& a)

C/C++ Programming

60

{ x = a.x; y = a.y; c = a.c; cout << "assigning the point "; show(cout); cout << "\n"; return (*this); } void point::show(ostream& out) { out << "(" << x <<", " << y << ", " << c << ")"; } message::message(const string& str, const point& p) : text(str), where(p) { cout << "creating the message "; show(cout); } message::message(const message& m): text(m.text), where(m.where) { cout << "copying the message "; show(cout); } message& message::operator =(const message& m) { text = m.text; where = m.where; cout << "assigning the message "; show(cout); return *this; } message::~message(void) { cout << "destroying the message "; show(cout); cout << "\n"; } void message::show(ostream& out) { out << "[" << text << ", "; where.show(out); out << "]"; } /* the output is creating the point (1, 2, 3) Create a static message copying the point (1, 2, 3) creating the message [Hello, (1, 2, 3)] <---- construction of p <---- construction of m.where <---- construction of m

C/C++ Programming

61

Use the copy constructor for message copying the point (1, 2, 3) copying the message [Hello, (1, 2, 3)] Create a dynamic message copying the point (1, 2, 3) creating the message [Hello, (1, 2, 3)] Use operator = assigning the point (1, 2, 3) assigning the message [Hello, (1, 2, 3)]

<---- construction of n.where <---- construction of n <---- consruction of ptr->where <---- construction of *ptr <---- m.where.operator= <---- m.operator=

Delete the dynamic message destroying the message [Hello, (1, 2, 3)] <---- destruction of *ptr destroying the point (1, 2, 3) <---- destruction of ptr->where Static objects destroying the destroying the destroying the destroying the destroying the */ are destroyed as the program ends message [Hello, (1, 2, 3)] <---- destruction point (1, 2, 3) <---- destruction message [Hello, (1, 2, 3)] <---- destruction point (1, 2, 3) <---- destruction point (1, 2, 3) <---- destruction

of of of of of

n n.where m m.where p

Note that when a message is constructed rst the point constructor is called. When a message is destroyed the message destructor runs rst and calls the point destructor.

4.12

Classes with Dynamically Allocated Data Members

The compiler implicitly declares and denes a default constructor, copy constructor, operator= and the destructor when we do not declare them. The most common case in which we must implement these functions explicitly is when an object manages pointers to memory that the object controls. In this case, a copy constructor or operator= must not blindly copy a member that is a pointer as this will result in two objects pointing to the same memory. Instead, we should allocate a new pointer and copy the contents. In such cases we will nd that we must supply not only a constructor but also the copy constructor, operator= and the destructor. A useful guidline is that if we vwrite one of the copy constructor, operator= or the destructor then we must write all three. Here is a rewrite of the class message which uses dynamically allocated member object. Sample Program: messagedynamic.cpp /* File: messagedynamic.cpp A class with a dynamically allocated data member */ #include <iostream> #include <string> using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public:

C/C++ Programming

62

point(int h, int v, int clr):x(h),y(v),c(clr){ } void show(ostream& out){ out << "(" << x <<", " << y << ", " << c << ")"; } }; class message { private: string text; point* where; // this member is dynamic public: message(const string& str, const point& p); message(const message& m); ~message(void); message& operator = (const message& m); void show(ostream& out); }; int main() { point p(1,2,3); string str("Hello"); cout << "\nCreate a static message\n"; message m(str, p); cout << "\n\nUse the copy constructor for message\n"; message n(m); cout << "\n\nCreate a dynamic message\n"; message* ptr = new message(str, p); cout << "\n\nUse operator = \n"; m = n; cout << "\n\nDelete the dynamic message\n"; delete ptr; cout << "\n\nStatic objects are destroyed as the program ends\n"; return 0; } message::message(const string& str, const point& p) : text(str) { // dont just point to p, instead, create a new point which is a copy where = new point(p); cout << "Creating the message "; show(cout); cout << "\nold point is at " << (unsigned) &p; cout << "\nnew point is at " << (unsigned) where; } message::message(const message& m): text(m.text) { // the default copy constructor would say: where = m.where

C/C++ Programming

63

// instead, we create a new point which is a copy where = new point(*m.where); cout << "Copying the message "; show(cout); cout << "\nold point is at " << (unsigned) m.where; cout << "\nnew point is at " << (unsigned) where; } message& message::operator =(const message& m) { // the default operator= would say: text = m.text and where = m.where // instead, we create a new point which is a copy // watch out for the equation m = m if( this != &m) { text = m.text; delete where; // delete the old point first where = new point(*m.where); } cout << "Assigning the message "; show(cout); cout << "\nold point is at " << (unsigned) m.where; cout << "\nnew point is at " << (unsigned) where; return *this; } message::~message(void) { // the default destructor would not release the memory cout << "Destroying the message "; show(cout); cout << " point was at " << (unsigned) where; cout <<"\n"; delete where; } void message::show(ostream& out) { out << "[" << text << ", "; where->show(out); out << "]"; } /* The program output is: Create a static message Creating the message [Hello, (1, 2, 3)] old point is at 3221223152 new point is at 5243296 Use the copy constructor for message Copying the message [Hello, (1, 2, 3)] old point is at 5243296 new point is at 5243312 Create a dynamic message Creating the message [Hello, (1, 2, 3)] old point is at 3221223152

C/C++ Programming

64

new point is at 5243344 Use operator = Assigning the message [Hello, (1, 2, 3)] old point is at 5243312 new point is at 5243296 Delete the dynamic message Destroying the message [Hello, (1, 2, 3)] point was at 5243344 Static objects are destroyed as the program ends Destroying the message [Hello, (1, 2, 3)] point was at 5243312 Destroying the message [Hello, (1, 2, 3)] point was at 5243296 */

4.13

Static Data Members and Methods

The original usage of the keyword static in the C language was to allow a local variable in a function to retain its value between function calls. This could be used to avoid having global variables. In C++ the keyword static can be applied to members of a class. When we declare a data member of a class as static there is only one copy of that variable shared among all objects of the class. A static data member exists before any objects of the class are created. In essence, a static data member is a global variable which has its scope restricted to the class in which it is declared. We can access a static data member even before any objects of the type are created using the scope resolution operator prex. When we declare a static data member we are not dening the member. This denition must be done elsewhere. When we apply the keyword static to a method that method can only access other static members and methods. We can refer to the static method even before any objects are created using the scope resolution operator prex. For example, suppose that we want all points to have the same colour. We might code the classs point as follows: Sample Program: staticpoint.cpp /* File: staticpoint.cpp Illustrates static members and methods */ #include <iostream> using namespace std; class point { private: int x; // horizontal position int y; // vertical position static int c; // All points are the same colour public: point(int h, int v):x(h),y(v){ } void show(ostream& out) { out << "(" << x <<", " << y << ", " << c << ")"; } void move(int newx, int newy){ x = newx; y = newy;

C/C++ Programming

65

} static void setc(int clr){ c = clr; } }; int point::c = 0; // must allocate an int for the static member int main(void) { point p(1,2); point q(3,4); cout << "p = "; p.show(cout); cout << "\n"; cout << "q = "; q.show(cout); cout << "\n"; p.setc(1); cout << "p = "; p.show(cout); cout << "\n"; cout << "q = "; q.show(cout); cout << "\n"; point::setc(2); cout << "p = "; p.show(cout); cout << "\n"; cout << "q = "; q.show(cout); cout << "\n"; return 0; } /* The program output is: p = (1, 2, 0) q = (3, 4, 0) p = (1, 2, 1) q = (3, 4, 1) p = (1, 2, 2) q = (3, 4, 2) */

4.14

Const Data Members and Methods

We have seen that the keyword const can be used with variables to ensure that once they are intialized they cannot be changed. For example: const int N = 10; int main(void) { float x[N]; . . . } Function arguments can also be declared const. This ensures that the function cannot modify the argument. For example, class point { public: point(int h, int v, int clr): x(h), y(v), c(clr) {}

C/C++ Programming

66

void show(ostream& out){ out << "(" << x <<", " << y << ", " << c << ")"; } point add(const point& u){ point p(x + u.x, y + u.y, c + u.c); return u; } }; Objects can be declared const. Unless otherwise specied, the only methods that can be used on a const object are constructors and destructors. For example, the following code will not compile without the keyword const following the declaration of point::show. Sample Program: constpoint.cpp /* File: constpoint.cpp Illustrates const objects and const methods */ #include <iostream> using namespace std; class point { private: int x,y,c; public: point(int h, int v, int clr): x(h), y(v), c(clr) {} void show(ostream& out) const { out << "(" << x <<", " << y << ", " << c << ")"; } point add(const point& u){ point p(x + u.x, y + u.y, c + u.c); return u; } }; int main(void) { const point origin(0,0,0); cout << "origin = "; origin.show(cout); cout << "\n"; return 0; } Declaring a member function as const tells the compiler that the member function will not modify the objects data and will not invoke other member functions that are not const. The compiler will check to make sure that we really dont modify the data. We can call a const member function for either a const or a non-const object, but we cant call a non-const member function for a const object (because it could modify the object). The proper usage of const in our programs is really a mechanism to protect us from ourselves. A popular USENET joke goes: In C, you merely shoot yourself in the foot.

C/C++ Programming In C++, you accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency medical care is impossible, because you cant tell which are bitwise copies and which are just pointing at others and saying, Thats me, over there.

67

4.15

A Vector Class

Here is an example which implements a oat array class in which we program the basic 4 members: constructor, copy constructor, operator =, and the destructor. We also overload a number of operators thinking of the arrays as vectors. We use a header le to contain the declarations and a source code le with the implementation. Sample Program: vectoroat.h // File: vectorfloat.h // How to program an "array of floats" class with lots of operator overloading #include <iostream> #include <cstdio> // needed for the exit() function using namespace std; class vectorfloat { private: int size_; float* data_;

// number of elements // pointer to the array

public: vectorfloat(int n=0); // constructor ~vectorfloat(void); // destructor vectorfloat(const vectorfloat& x); // copy constructor vectorfloat& operator = (const vectorfloat& x); // assignment operator int size(void) const {return size_;} const float& operator [] (int i) const {return data_[i];} float& operator [] (int i) {return data_[i];} }; // vector operators istream& operator >> ostream& operator << vectorfloat operator vectorfloat operator /* Notes: 1. Subscript operators often come in pairs. See the C++ faq 18.12. http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-18.12 When the subscript operator is applied to an object that is non-const the compiler will call the non-const version. When the subscript operator is applied to a const object the compiler will call the const version. */ Sample Program: vectoroat.cpp // File: vectorfloat.cpp // Implementation of class vectorfloat #include "vectorfloat.h" ////////////////////// Implementation of vectorfloat ///////////////////////// (istream& in, vectorfloat& x); (ostream& out, const vectorfloat& x); + (const vectorfloat& x, const vectorfloat& y); - (const vectorfloat& x, const vectorfloat& y);

C/C++ Programming

68

vectorfloat::vectorfloat(int n) { int i; size_ = n; if (size_ > 0) { data_ = new float [size_]; for (i = 0; i < size_; i++) data_[i] = 0.0; } else data_ = NULL; } vectorfloat::~vectorfloat() { delete [] data_; } vectorfloat::vectorfloat(const vectorfloat& x) { size_ = x.size_; // copy the size of x data_ = new float[size_]; for(int i=0; i<size_; i++) data_[i] = x.data_[i]; } vectorfloat& vectorfloat::operator = (const vectorfloat& x) { // watch out for the equation x = x if( this != &x) { // must resize the array if size_ != x.size_ if (size_ != x.size_) { delete[] data_; // get rid of the current array size_ = x.size_; // copy the size of x data_ = new float[size_]; // create a new array to hold the copy of x } for(int i=0; i<size_; i++) // copy over the data_ from x data_[i] = x.data_[i]; } return *this; } /////////////////////////// vectorfloat operators ////////////////////////////// istream& operator >> (istream& in, vectorfloat& x) { for(int i =0; i < x.size(); i++) in >> x[i]; // create a new array to hold the copy of x // copy over the data_ from x

C/C++ Programming

69

return in; } ostream& operator<<(ostream& out, const vectorfloat& x) { for(int i=0; i < x.size(); i++) out << x[i] << " "; return out; } vectorfloat operator + (const vectorfloat& x, const vectorfloat& y) { vectorfloat c(x.size()); if (x.size() == y.size()) for(int i = 0; i < x.size(); i++) c[i] = x[i] + y[i]; else { cout << "Vectors must be of the same size to be added\n"; exit(1); } return c; } vectorfloat operator - (const vectorfloat& x, const vectorfloat& y) { vectorfloat c(x.size()); if (x.size() == y.size()) for(int i = 0; i < x.size(); i++) c[i] = x[i] - y[i]; else { cout << "Vectors must be of the same size to be subtracted\n"; exit(1); } return c; } Notes: 1. The default copy constructor would be: vectorfloat::vectorfloat(const vectorfloat& x) { size_ = x.size_; // copy the size of x data_ = x.data_; } That is, the object and object x would both be pointing to the same array. This would cause havoc if we used the current destructor to destroy one of these two objects releasing the memory occupied by the array.

C/C++ Programming

70

2. The default destructor would release the 8 bytes occupied by the int size and the pointer data . That is, the memory occupied by the array to which data is pointing would not be released. This is probably not what we would want. 3. operator = must be coded as a member function. 4. The rst thing to avoid when coding operator = is the equation x = x. We can avoid this situation by making sure that this != &x. 5. The code for operator = looks a lot like the code for the copy constructor. The dierence is that the copy constructor is creating a new object while operator = is working with an existing object. 6. As this is a pointer to the object, *this is the object. So, we can return *this by reference. The reason for returning the current object is that we may want to write a compound expression like: x = y = z. The operator = works from right to left. That is, rst we carry out the assignment y = z. This is a function call which returns the current object y. We then carry out x = y which again returns x. 7. The default operator = would behave similarly to the default copy constructor. That is, the pointers would be rearranged without releasing any memory. Again, this is probably not what we would want. 8. The operators << and >> cannot be made members of the class as the notation requires that the left operand be the object and the right operand be the argument. Binary operators are conceptually easier to code as external functions rather than member functions.

4.16

A Matrix Class

One of the attractive things about C++ is the ability to translate mathematical structures, operations and notations into classes keeping the same notation. Weve done this for the vector class and now we can carry this out for the matrix class. Sample Program: matrix.h /* File: matrix.h A matrix class with various matrix operators */ #include <iostream> #include <fstream> #include <cstdlib> // needed for the exit function using namespace std; class matrix { private: int rows_; int cols_; float * data_;

// number of rows // number of columns // pointer to matrix entries as one long array // // // // m by n matrix filled with s copy constructor destructor operator =

public: matrix(int m=1, int n=1, float s=0.0); matrix(const matrix& x); ~matrix(void); matrix& operator = (const matrix& x); int rows(void) const {return rows_;} int cols(void) const {return cols_;} const float& operator() (int i, int j)

const {return data_[i*cols_ + j];}

C/C++ Programming

71

float& operator() (int i, int j) {return data_[i*cols_ + j];} friend istream& operator >> (istream& in, matrix& x); }; ostream& operator << (ostream &out, const matrix &x); matrix operator + (const matrix &x, const matrix &y); matrix operator * (const matrix &x, const matrix &y); /* Notes: 1. The declaration matrix x; gives a 1 by 1 matrix with entry 0 2. The matrix is not represented as an array of arrays as you might expect. This linear representation makes for a faster implementation. 3. The reason for choosing float& operator()(int i, int j) rather than float*& operator[](int i) is spelled out in the C++ faq Lite: http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-13.11 The reason is partly connected to 2. 4. As for the vectorfloat class, there are two versions of the subscript operator. */ Sample Program: matrix.cpp /* File: matrix.cpp The implementation of matrix.h*/ #include "matrix.h" ///////////////////// Implementation of matrix /////////////////////// matrix::matrix(int m, int n, float s) { int i; rows_ = m; cols_ = n; data_ = new float[m*n]; for(i=0; i<m*n ; i++) data_[i] = s; } matrix::matrix(const matrix& x) { int i, j; rows_ = x.rows_; cols_ = x.cols_; data_ = new float[rows_ * cols_]; for(i=0; i< rows_ * cols_ ; i++) data_[i] = x.data_[i]; } matrix& matrix::operator = (const matrix& x) { int i, j;

C/C++ Programming

72

// watch out for x = x if (this != &x) { if (!((rows_ == x.rows_)&&(cols_ == x.cols_)) ) // must call reallocate { delete [] data_; rows_ = x.rows_; cols_ = x.cols_; data_ = new float[rows_ * cols_]; } for(i=0; i<rows_ * cols_; i++) data_[i] = x.data_[i]; } return *this; } matrix::~matrix(void) { delete [] data_; } ///////////////// matrix operations /////////////////////////////// istream& operator >> (istream &in, matrix &x) { int m, n, i, j; in >> m >> n; // check whether the sizes are compatible if(!( (m == x.rows_) && (n == x.cols_) ) ) { delete [] x.data_; x.data_ = new float[m*n]; x.rows_ = m; x.cols_ = n; } for(i=0; i < m*n; i++) in >> x.data_[i]; // read by rows return in; } ostream& operator << (ostream &out, const matrix &x) { int i, j; int m = x.rows(), n = x.cols(); for(i=0; i < m; i++) { for(j=0; j<n; j++) out << x(i,j) << \t; out << endl; } return out;

C/C++ Programming

73

} matrix operator + (const matrix &x, const matrix &y) { int i, j; int m = x.rows(), n = x.cols(); if(! ( (x.rows() == y.rows()) && (x.cols() == y.cols() ) ) ) { cout << "matrices incompatible\n"; exit(1); } matrix c(m,n); for(i=0; i < m; i++) for(j=0; j < n; j++) c(i,j) = x(i,j) + y(i,j); return c; } matrix operator * (const matrix &x, const matrix &y) { int i, j, k; int m = x.rows(), n = x.cols(), p = y.cols(); float sum; if( y.rows() != n ) { cout << "matrices incompatible\n"; exit(1); } matrix c(m,p); for(i=0; i < m; i++) for(j=0; j < p; j++) { sum = 0.0f; for(k=0; k<n; k++) sum = sum + x(i,k) * y(k,j); c(i,j) = sum; } return c; } Here is a driver program. To compile this, we make a project consisting of matrix.cpp and matrixdrv.cpp. Sample Program: matrixdrv.cpp /* File: matrixdrv.cpp driver program for the class matrix */ #include "matrix.h" int main() {

C/C++ Programming

74

matrix x, y, z; ifstream infile("matrixdrv.in"); ofstream outfile("matrixdrv.out"); // get the two matrices from the input file infile >> x >> y; // print matrices to the the output file outfile << "The first matrix is\n"; outfile << x; cout << x; outfile << "\n\nThe second matrix is\n"; outfile << y; cout << endl << y; // compute the sum and print it to the output file z = x + y; outfile << "\n\nThe sum matrix is\n"; outfile << z; // compute the matrix product and print it to the output file z = x*y; outfile << "\n\nThe product matrix is\n"; outfile << z; return 0; } /* The file matrixdrv.in consists of: 3 3 1 2 3 4 5 6 7 8 9 3 3 1 0 1 2 4 6 1 2 3 The file matrixcdrv.out consists of: The 1 2 4 5 7 8 The 1 0 2 4 1 2 first matrix is 3 6 9 second matrix is 1 6 3

C/C++ Programming

75

The sum matrix is 2 2 4 6 9 12 8 10 12 The product matrix is 8 14 22 20 32 52 32 50 82 */ A Speed Test You might wonder about the overhead involved in wrapping our matrices in the class structure with all its extra functions (constructor, destructor etc). To compare, we can use our matrix class to multiply two big matrices and then compare the speed of execution to the usual C code to carry out the same job. The following two programs execute in about 20 seconds on a 1.25GHz processor when compiled with optimization. This shows that there is no real overhead in using the matrix class. Sample Program: matspeed.cpp // File: matspeed.cpp // test the speed of the matrix class #include "matrix.h" const int N=800; int main() { ofstream fout("matspeed.out"); matrix a(N,N,0.5), b(N,N,0.5), c(N,N); int i,j; float sum = 0.0; c = a*b; // do something with c for(i=1; i<=N; i++) for(j=1; j<=N; j++) sum += c(i,j); fout << sum; return 0; } Sample Program: dynspeed.cpp // File: dynspeed.cpp // test static dynamic allocation of matrices #include <iostream> #include <fstream> using namespace std;

C/C++ Programming

76

float ** setup(int m, int n); // allocate the memory for an m by n matrix const int N=800; int main() { int i, j, k; float sum; float ** a, **b, **c; ofstream fout("dynspeedcpp.out"); a = setup(N,N); b = setup(N,N); c = setup(N,N); // initialize to all 0.5 for(i=0; i < N ;i++) for(j=0; j < N; j++) { a[i][j] = 0.5; b[i][j] = 0.5; } // multiply for(i=0; i < N ;i++) for(j=0; j < N; j++) { sum = 0.0; for(k=0; k< N; k++) sum += a[i][k]*b[k][j]; c[i][j] = sum; } // do something with c sum= 0.0; for(i=0; i < N ;i++) for(j=0; j < N; j++) sum += c[i][j]; fout << sum; return 0; } float ** setup(int m, int n) { float ** temp; int i; // allocate the array of pointers to the rows of the matrix temp = new float* [m]; // now allocate enough space to hold the entries temp[0] = new float[m*n]; // now do the rest for(i=1; i<m; i++) temp[i] = temp[i-1] +n;

C/C++ Programming

77

return temp; }

C/C++ Programming

78

Containers

A data structure is a systematic way of organizing and accessing data. In object oriented programming, a Container is a data structure whose purpose is to store and retrieve large numbers of objects. Generally containter classes are expected to implement methods to do the following: create a new empty container (constructor), report the number of objects it stores (size), delete all the objects in the container (clear), insert new objects into the container (store), remove objects from it (retrieve), provide access to the stored objects.

5.1

Stacks and Queues

A stack is a container in which the store and retrieve functions are called push and pop and the store-retrieve algorithm is the LIFO (last in rst out) rule. A queue is a container in which the store and retrieve functions are called put and get and the store-retrieve algorithm follows the FIFO (rst in rst out) rule. If we implement these structures as classes using arrays they suer from having a xed maximum storage size. Here are two examples using arrays to store integers. For simplicity we will use the default for the copy constructor, operator =, and destructor. Sample Program: stack.cpp
// File: stack.cpp // This program implements a simple stack of integers #include <iostream> #include <fstream> using namespace std; class stack { private: int max; int count; int *data;

// maximum size of the stack // number of items on the stack // pointer to the array holding the stack items

public: stack(int size); // constructor, size = max void push(int item); // push the integer item onto the stack int pop(void); // pop an item off the stack bool empty(void) const; bool full(void) const ; void show(ostream& out) const; }; int main() { stack mystack(5); ofstream fout ("stack.out"); char ch; int x; do { // print a little menu cout << "\n\np = push \n"; cout << "o = pop\n"; cout << "s = show\n"; // check whether the stack is empty // check whether the stack is full // show the data stored to out

C/C++ Programming

79

cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == p) { if( mystack.full() ) cout << "stack is full\n"; else { cout <<"\ndata to push :"; cin >> x; mystack.push(x); } } if(ch == o) { if( mystack.empty() ) cout << "stack is empty\n"; else cout << "\n\ndata popped : " << mystack.pop(); } if(ch == s) mystack.show(cout); if(ch == f) mystack.show(fout); }while(ch != q); return 0; } stack::stack(int size) { max = size; data = new int[max]; // allocate the array count = 0; // the stack has no entries yet } void stack::push(int item) { if(count < max) // just to be safe { data[count] = item; count++; } } int stack::pop(void) { if(count > 0) // just to be safe count--; return data[count]; } bool stack::full(void) const { return (count == max); } bool stack::empty(void) const { return (count == 0); } void stack::show(ostream& out) const { for(int i=0; i < count; i++) out << data[i] << ", "; out << endl; }

C/C++ Programming

80

Sample Program: queue.cpp


// File: queue.cpp // This program implements a simple queue of integers #include <iostream> #include <fstream> using namespace std; class queue { private: int max; int count; int *data;

// maximum size of queue // number of items in the queue // pointer to the array holding the queue items

public: queue(int size); // constructor, max = maximum queue size void put(int item); // put integer item into the queue int get(void); // get an item from the queue bool empty(void) const; bool full(void) const ; void show(ostream& out) const; }; int main() { queue myqueue(5); ofstream fout ("queue.out"); char ch; int x; do { // print a little menu cout << "\n\np = put \n"; cout << "g = get\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == p) { if( myqueue.full() ) cout << "queue is full\n"; else { cout <<"\ndata to put :"; cin >> x; myqueue.put(x); } } if(ch == g) { if( myqueue.empty() ) cout << "queue is empty\n"; else cout << "\n\ndata gotten : " << myqueue.get(); } if(ch == s) myqueue.show(cout); if(ch == f) myqueue.show(fout); }while(ch != q); return 0; } queue::queue(int size) { // check whether the queue is empty // check whether the queue is full // show the data stored to out

C/C++ Programming

81

max = size; data = new int[max]; // allocate the array count = 0; // the queue has no entries yet } void queue::put(int item) { if(count < max) // just to be safe { data[count] = item; count++; } } int queue::get(void) { int temp = data[0]; int i; if(count > 0) { // move all the entries down for(i=0; i < count-1; i++) data[i] = data[i+1]; count--; } return temp; } bool queue::full(void) const { return (count == max); } bool queue::empty(void) const { return (count == 0); } void queue::show(ostream& out) const { for(int i=0; i < count; i++) out << data[i] << ", "; out << endl; }

5.2

Dynamic Structures

The stack and queue as implemented have xed maximum sizes. Instead of allocating a xed array to store data it would be best if the data structure could grow and shrink as needed. To accomplish this we use a self-referential structure as in the following program. Sample Program: selfref.cpp
// File: selfref.cpp // Illustrates dynamic structures #include <iostream> using namespace std; class item { public: int data; // this is the data in a stack element item *next; // pointer to the next item on the stack }; /* +------+ | data | +------+ | next |---> +------+ */

C/C++ Programming

82

int main() { item x, y, z; x.data = 5; y.data = 3; z.data = 2; // put some data into the items

// connect the items x.next = &y; y.next = &z; // terminate the list of items z.next = NULL; /* The picture now looks like +------+ +------+ +------+ | 5 | | 3 | | 2 | +------+ +------+ +------+ | next |---->| next |---->| next |----> NULL +------+ +------+ +------+ x y z

/* We can do the same thing with pointers */ item *p, *q, *r; // make the items p = new item; p->data = 5; q = new item; q->data = 3; r = new item; r->data = 2; // connect the items p->next = q; q->next = r; r->next = NULL; // now display the data items item* ptr; for(ptr = p; ptr != NULL; ptr=ptr->next) cout << ptr->data << " --> "; return 0; }

With this type of self-referential structure we can rewrite the stack and queue programs. Sample Program: stacklnk.cpp
// File: stacklnk.cpp // This program implements a simple stack of integers using a linked list #include <iostream> #include <fstream> using namespace std; class item { friend class stack; private: int data; // this is the data in a stack element item *next; // pointer to the next item on the stack public:

C/C++ Programming

83

item(int x) {data = x; next = NULL;} }; class stack { private: item* top;

// pointer to the top of the stack // // // // // constructor of an empty stack push the integer x onto the stack pop an item off the stack check for empty stack show data stored to out

public: stack(void); void push(int x); int pop(void); bool empty(void) const; void show(ostream& out) const; }; /* A stack looks like

+------+ +------+ +------+ +------+ | data | | data | | data | | data | top ---> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ */ int main() { stack mystack; ofstream fout ("stacklnk.out"); char ch; int x; cout << "A dynamic stack of integers\n"; fout << "A dynamic stack of integers\n"; do { // print a little menu cout << "\n\np = push \n"; cout << "o = pop\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == p) { cout <<"\ndata to push :"; cin >> x; mystack.push(x); } if(ch == o) { if(mystack.empty()) cout << "stack is empty\n"; else cout << "\n\ndata popped :" << mystack.pop(); } if(ch == s) mystack.show(cout); if(ch == f) mystack.show(fout); }while(ch != q); return 0; } bool stack::empty(void) const { return (top == NULL); } /* the empty stack looks like:

C/C++ Programming

84

top----> NULL */ stack::stack(void) { top = NULL; // the stack is initially empty } /* the push function takes an existing stack

+------+ +------+ +------+ | 1st | | data | | data | top ---> +------+ +------+ +------+ | next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ ptr points to a new item +------+ | x | ptr ---->+------+ | next | +------+ then connect this new item into the list

+------+ +------+ +------+ +------+ | x | | 1st | | data | | data | top ---> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ */ void stack::push(int x) { item* ptr; ptr = new item(x); // make ptr point to a new item storing x

ptr->next = top; // connect *ptr into the list top = ptr; // make *ptr the new top of the list } /* The pop function takes an existing stack +------+ +------+ +------+ +------+ | 1st | | 2nd | | data | | data | top ---> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ and picks off the first data item to be returned +------+ | 1st | ptr ---->+------+ | next | +------+ then reassigns the top pointer to the second item +------+ +------+ +------+ | 2nd | | data | | data | top ---> +------+ +------+ +------+ | next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ */

C/C++ Programming

85

int stack::pop(void) { int temp = 0; // return 0 if stack is empty if( top != NULL) // ok to pop { temp = top->data; // hold onto this value until the end item* ptr = top; // remember the top of the stack top = top->next; delete ptr; } return temp; } void stack::show(ostream& out) const { item* ptr = top; // ptr will move down the list while(ptr != NULL) { out << ptr->data << ", "; // print the data ptr = ptr->next; // move one position down the list } out << endl; // go to a new line } // move the top to the next item in the list // release the memory used by the old top

Sample Program: queuelnk.cpp


// File: queuelnk.cpp // This program implements a simple queue of integers as a linked list #include <iostream> #include <fstream> using namespace std; class item { friend class queue; private: int data; // this is the data in a queue element item *next; // pointer to the next item in the queue public: item(int x) {data = x; next = NULL;} }; class queue { private: item* end;

// pointer to the end of the queue

public: queue(void); // constructor of an empty queue void put(int x); // put the integer x into the queue int get(void); // get the item at the front of the queue bool empty(void) const; // check for empty queue void show(ostream& out) const; // show data stored to out }; /* A queue looks like

+------+ +------+ +------+ +------+ | data | | data | | data | | data | end ---> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ */ int main() {

C/C++ Programming

86

queue myqueue; ofstream fout ("queuelnk.out"); char ch; int x; cout << "A dynamic queue of integers\n"; fout << "A dynamic queue of integers\n"; do { // print a little menu cout << "\n\np = put \n"; cout << "g = get\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == p) { cout <<"\ndata to put :"; cin >> x; myqueue.put(x); } if(ch == g) { if(myqueue.empty()) cout << "queue is empty\n"; else cout << "\n\ndata gotten : " << myqueue.get(); } if(ch == s) myqueue.show(cout); if(ch == f) myqueue.show(fout); }while(ch != q); return 0; } bool queue::empty(void) const { return (end == NULL); } queue::queue(void) { end = NULL; } /* the put function takes an existing queue

+------+ +------+ +------+ | last | | data | | data | end ---> +------+ +------+ +------+ | next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ ptr points to a new item +------+ | x | ptr ---->+------+ | next | +------+ then connect this new item into the list

C/C++ Programming

87

+------+ +------+ +------+ +------+ | x | | last | | data | | data | end ---> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ */ void queue::put(int x) { item* ptr; ptr = new item(x); // make ptr point to a new item storing x

ptr->next = end; // connect *ptr into the list end = ptr; // make *ptr the new end of the list } /* The get function takes an existing queue +------+ +------+ +------+ +------+ | data | | data | | data | | 1st | end ---> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ and picks off the first data item to be returned +------+ | 1st | ptr ---->+------+ | next | +------+ then reassigns the second to last pointer to NULL +------+ +------+ +------+ | data | | data | | data | end ---> +------+ +------+ +------+ | next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ */ int queue::get(void) { int temp = 0; // return 0 if queue is empty item* ptr = end; // start at the end of the queue item* q = end; // needed to mark the second to last item if( end != NULL) // ok to get { while(ptr->next != NULL) // move down the queue until the last item { q = ptr; ptr = ptr->next; } q->next = NULL; // cut off the last item from the queue temp = ptr->data; // remember the data of the last item if (ptr == end) end = NULL; delete ptr; } return temp; } void queue::show(ostream& out) const { item* ptr = end; // ptr will move along the queue // we are getting the last item in the queue // release the memory used by the last item

C/C++ Programming

88

while(ptr != NULL) { out << ptr->data << ", "; // print the data ptr = ptr->next; // move one position down the list } out << endl; // go to a new line }

5.3

Linked Lists

We have already been using the idea of a linked list with the stack and queue program. In general terms, a linked list is based on a self-referential structure like item in our previous programs. The linked list is class containing an item pointer, start, to the head of the list together with a consecutive number of items beginning at *start and terminating at NULL. +------+ +------+ +------+ +------+ | data | | data | | data | | data | start--> +------+ +------+ +------+ +------+ | next |---->| next |---->| next |----> ... | next |---->NULL +------+ +------+ +------+ +------+ There are a number of standard operations on a linked list. 1. Traverse the list. for(ptr = start; ptr != NULL; ptr=ptr->next) { // do something to the list item } 2. insert a new item at the head of the list. item* n = new item; // connect n n->next = start; start = n; 3. insert a new item at the end of the list. item* ptr; item* n = new item; // move to the end of the list for(ptr = start; ptr->next != NULL; ptr = ptr->next); // connect n ptr->next = n; n->next = NULL; 4. Insert a new item between between two consecutive items, *q and *p, in the list. item* n = new item; q->next = n; n->next = p;

C/C++ Programming 5. Delete the head of the list. item* temp = start; start = start->next; delete temp; 6. Delete the end of the list. item* q, p; // move to the end of the list for(p = start; p->next != NULL; p = p->next) q = p; q->next = NULL; delete p; 7. Delete the item *p in the middle of the list assuming q-next =p. q->next = p->next; delete p; Here is an example of an ordered linked list. Sample Program: llist.cpp
// File: llist.cpp // This program implements a sorted (ascending order) linked list of integers #include <iostream> #include <fstream> using namespace std; class item { friend class linked_list; private: int data; // this is the data in a list element item *next; // pointer to the next item in the list public: item(int x) {data = x; next = NULL;} }; class linked_list { private: item* start; // pointer to the item at the start of list

89

public: linked_list(void); // constructor of an empty list void add(int x); // add an item with data = x to the list bool del(int x); // delete the item with data = x from the list void show(ostream& out) const; // show data stored to out }; int main() { linked_list mylist; // start with a null list ofstream fout ("llist.out"); char ch; int x; do {

C/C++ Programming

90

// print a little menu cout << "\n\na = add\n"; cout << "d = delete\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == a) { cout <<"\ndata to add :"; cin >> x; mylist.add(x); } if(ch == d) { cout << "\n\ndata to delete :"; cin >> x; if(!mylist.del(x)) cout << "data not in the list\n"; } if(ch == s) mylist.show(cout); if(ch == f) mylist.show(fout); }while(ch != q); return 0; } linked_list::linked_list(void) { start = NULL; } void linked_list::add(int x) // allow duplicates { item* p = start; // p and q will move down the list item* q = start; // item* r; // first check to see if x goes first if ( (start == NULL) || (x <= start->data ) ) { // add the new item at the head of the list start = new item(x); start->next = p; } else // move down the list looking for the correct position { while (p != NULL) { if( (q->data < x)&& (x <= p->data) ) break; q = p; p = p->next; } if (p == NULL) // we must place x at the end { p = new item(x); q->next = p; p->next = NULL; // terminate the list } else // put x in between q and p { r = new item(x); q->next = r; r->next = p; } } }

C/C++ Programming

91

bool linked_list::del(int x) { bool found = false; item* p = start; // pointers will move down the list to find data=x item* q = start; item* temp; // first find item in the list while(p != NULL) { if (p->data == x) // found { found = true; if(p == start) start = p->next; // we are deleting the start of the list else q->next = p->next; // bypass *p temp = p; p = p->next; delete temp; } else { q = p; p = p->next; } } return found; } void linked_list::show(ostream& out) const { item* p = start; while(p != NULL) { out << p->data << ", "; p = p->next; } out << endl; } // move p along // release the memory occupied by *p

// remember p // move down the list

C/C++ Programming

92

Templates, Generic Programming and the STL

Just as a class is a kind of schematic for building objects, a template is a schematic for building functions and classes. Also called parametized types, templates provide specications for general purpose classes and functions that automatically mold themselves to new uses. The Standard Template Library (STL) is a recent addition to the language. It provides a library of standard classes and algorithms. So, for example, we dont have to write our own container classes when we need them we can use this standard library. The growth of the C++ language has lead to the introduction of a new naming convention called namespaces. These allow us to localize the names of identiers to avoid name collisions. The STL is part of the standard namespace.

6.1

Template Classes

Now that we have a stack class for integers we might want to have a stack of chars or a stack of oats. One way to achieve this is to create a new class in which we manually replace the occurrences of integer by the desired type. For example to create a stack of floats in the stacklnk program we can replace certain occurrences of the type int with the type float.
class stack private: int max; int count; int* data; // maximum size of the stack // number of items on the stack // pointer to the array holding the stack items

public: stack(int size=100); // constructor, max = maximum stack size void push( int item); // push the item onto the stack int pop(void); // pop an item off the stack void show(ostream& out); // display stack to out ; class fstack private: int max; int count; oat* data; // maximum size of the stack // number of items on the stack // pointer to the array holding the stack items

public: stack(int size=100); // constructor, max = maximum stack size void push( oat item); // push the item onto the stack oat pop(void); // pop an item off the stack void show(ostream& out); // display stack to out ;

Of course we would then have to edit all the denitions which go along with class stack changing various occurrences of int to oat and occurrences of stack to stackf. There is a mechanism, called templates, built into C++, which will allow the compiler to do all the work for us. When we use this mechanism with classes we get a class template. This is exactly what the name implies: a template for creating classes. The syntax looks like: template < template parameters > class declaration { . . }; Here is what the template declaration looks like for the stack class.

C/C++ Programming

93

template <class T> class stack { private: int max; // maximum size of the stack int top; // number of items on the stack T* data; // pointer to the array holding the stack items public: stack(int size=100); void push(T item); T pop(void); void show(ostream& out); }; // // // // constructor, max = maximum stack size push the item onto the stack pop an item off the stack display stack to out

Now when we want to use a stack of oats in a program we can declare the object by substituting oat for the parameter T as follows: int main() { stack<float> mystack(50); . . . } Here is an example which puts this all together. There is a hidden assumption which becomes apparent when we try to write the implementation of the template class. The assumption is that operator << and operator >> exist for the template parameter class T. This is ne for the simple data types like char, int, and float but may present a problem for other classes. Sample Program: stacktem.cpp
// File: stacktem.cpp // This program implements a simple template stack #include <iostream> #include <fstream> using namespace std; /* When we look at the class stack previously defined we see that with a few simple changes we could make a stack of floats, a stack of chars, or a stack of any predefined data type for which operator << and operator >> is defined. This leads the the idea of a template class . That is, a class whose definition depends on parameters which are other classes. To let the compiler know that we are using parameters we prefix the class declaration by the keyword template followed by a comma separated list of parameters enclosed in wedge brackets. */ template <class T> class stack { private: int max; int count; T *data;

// maximum size of the stack // number of items on the stack // pointer to the array holding the stack items

public: stack(int size); // constructor, size = max void push(T item); // push the integer item onto the stack T pop(void); // pop an item off the stack bool empty(void) const; bool full(void) const ; void show(ostream& out) const; }; // check whether the stack is empty // check whether the stack is full // show the data stored to out

C/C++ Programming

94

int main() { stack<float> mystack(5); ofstream fout ("stacktem.out"); char ch; float x; do { // print a little menu cout << "\n\np = push \n"; cout << "o = pop\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == p) { if( mystack.full() ) cout << "stack is full\n"; else { cout <<"\ndata to push :"; cin >> x; mystack.push(x); } } if(ch == o) { if( mystack.empty() ) cout << "stack is empty\n"; else cout << "\n\ndata popped : " << mystack.pop(); } if(ch == s) mystack.show(cout); if(ch == f) mystack.show(fout); }while(ch != q); return 0; } template <class T> stack<T>::stack(int size) { max = size; data = new T[max]; // allocate the array count = 0; // the stack has no entries yet } template <class T> void stack<T>::push(T item) { if(count < max) // just to be safe { data[count] = item; count++; } } template <class T> T stack<T>::pop(void) { if(count > 0) // just to be safe count--; return data[count]; }

C/C++ Programming

95

template <class T> bool stack<T>::full(void) const { return (count == max); } template <class T> bool stack<T>::empty(void) const { return (count == 0); } template <class T> void stack<T>::show(ostream& out) const { // note that type T must have opertor << defined for(int i=0; i < count; i++) out << data[i] << ", "; out << endl; }

Note that in some books you may see the template parameters call typename rather than class. This is just a matter of style.

6.2

Template Functions

We can also have template functions. That is, functions whose denition depends on parameters which are classes. To let the compiler know that we are using parameters we prex the function declaration by the keyword template followed by a comma separated list of parameters enclosed in wedge brackets. The prototype would look like: template <class T> return_type function_name(argument_list_depending_on_T); We can modify our main program in stacktem.cpp to use a template function. Sample Program: stackft.cpp
// File: stackft.cpp // This program implements a simple template stack and a template function #include <iostream> #include <fstream> using namespace std; template <class T> class stack { private: int max; int count; T *data;

// maximum size of the stack // number of items on the stack // pointer to the array holding the stack items

public: stack(int size); // constructor, size = max void push(T item); // push the integer item onto the stack T pop(void); // pop an item off the stack bool empty(void) const; bool full(void) const ; void show(ostream& out) const; }; /* We can also have template functions. That is, functions whose definition depends on parameters which are classes. To let the compiler know that we are using parameters we prefix the // check whether the stack is empty // check whether the stack is full // show the data stored to out

C/C++ Programming

96

function declaration by the keyword template followed by a comma separated list of parameters enclosed in wedge brackets. */ // Prototype for a template function template <class T> void menu(ostream& out, stack<T>& s); int main() { stack<float> fstack(10); // declares a stack of floats stack<int> istack(10); // declares a stack of ints ofstream fout ("stackft.out"); cout << "A stack of floats\n"; fout << "A stack of floats\n"; menu(fout, fstack); cout <<"\n\nA stack of integers\n"; fout <<"\n\nA stack of integers\n"; menu(fout, istack); return 0; } //////////////////////////// Implementation of menu ////////////////////// template <class T> void menu(ostream& out, stack<T>& s) { char ch; T x; do { // print a little menu cout << "\n\np = push \n"; cout << "o = pop\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == p) { if( s.full() ) cout << "stack is full\n"; else { cout <<"\ndata to push :"; cin >> x; s.push(x); } } if(ch == o) { if( s.empty() ) cout << "stack is empty\n"; else cout << "\n\ndata popped : " << s.pop(); } if(ch == s) s.show(cout); if(ch == f) s.show(out); }while(ch != q); } ///////////////////////// Implementation of stack ///////////////////////// template <class T> stack<T>::stack(int size) { max = size; data = new T[max]; // allocate the array count = 0; // the stack has no entries yet }

C/C++ Programming

97

template <class T> void stack<T>::push(T item) { if(count < max) // just to be safe { data[count] = item; count++; } } template <class T> T stack<T>::pop(void) { if(count > 0) // just to be safe count--; return data[count]; } template <class T> bool stack<T>::full(void) const { return (count == max); } template <class T> bool stack<T>::empty(void) const { return (count == 0); } template <class T> void stack<T>::show(ostream& out) const { // note that type T must have opertor << defined for(int i=0; i < count; i++) out << data[i] << ", "; out << endl; }

6.3

Namespaces

Namespaces are a relatively recent introduction to C++. Their purpose is to localize the names of identiers to avoid name collisions. In C++ there has been an explosion of variable, function and class names. Prior to the addition of namespaces, all of these names competed for slots in the global namespace and many conicts arose. The namespace keyword localizes the visibility of the names declared within it. The most noticeable beneciary of namespaces is the C++ standard library which now has its own namespace called std. Along with namespaces, ANSI C++ has introduced a new kind of header which does not specify lenames extensions. Instead they specify standard identiers which may be mapped to header les but need not be. For example we now say: #include <iostream> #include <fstream> #include <cstring> rather than #include <iostream.h> #include <fstream.h> #include <string.h>

C/C++ Programming

98

The old method of indicating le names has be deprecated (that is, not recommended) and all new programs should use the new naming convention. The syntax of the namespace declaration is: namespace name { // declarations } Anything dened within a namespace statement is within the scope of that namespace. For example: namespace MyNameSpace { int i, k; void myfunc( int j) { cout << j;} class myclass { public: void seti(int x) { i=x;} int geti () { return i;} } } Here i, k, myfunc() and the class myclass are part of the scope of MyNameSpace. Inside this namespace we can refer to the variable i directly. Outside this namespace we have to refer to i by with the scope resolution operator MyNameSpace::i. If we want to avoid prexing all variables and functions by the namespace we can employ the using keyword. using namespace MyNameSpace; brings all members of MyNameSpace into the current scope. Alternately we can say using MyNameSpace::i to bring the variable i into the current scope. You can have more that one namespace statement to allow the contents of the namespace to be split over several les. Here is an example of namespace usage Sample Program: namespacedemo.cpp
// File: namespacedemo.cpp #include <iostream> using std::cout; using std::endl; // define a namespace namespace firstNS { class demo { private: int i; public: demo(int x) { i = x; } void seti(int x) { i = x; }

C/C++ Programming

99

int geti() { return i; } }; char str[] = "Illustrating namespaces\n"; int counter; } // define another namespace namespace secondNS { int x, y; } int main() { // use scope resolution firstNS::demo ob(10); /* Once ob has can be used cout << "Value cout << endl; ob.seti(99); cout << "Value cout << endl; been declared, its member functions without namespace qualification. */ of ob is : " << ob.geti(); of ob is now : " << ob.geti();

// bring str into current scope using firstNS::str; cout << str; // bring all of firstNS into current scope using namespace firstNS; for(counter = 10; counter; counter--) cout << counter << " "; cout << endl; // use secondNS namespace secondNS::x = 10; secondNS::y = 20; cout << "x, y: " << secondNS:: x; cout << ", " << secondNS::y << endl; // bring another namespace into view using namespace secondNS; demo xob(x), yob(y); cout << "xob, yob: " << xob.geti() << ", "; cout << yob.geti() << endl; return 0; } /* The output is: Value of ob is : 10 Value of ob is now : 99 Illustrating namespaces 10 9 8 7 6 5 4 3 2 1 x, y: 10, 20 xob, yob: 10, 20 */

We can make all names in the std namespace visible by saying using namespace std; at the start of our program. Of course, this defeats the purpose of isolating the namespaces. After this, the compiler can see any std name even if not qualied by std::. There are two more acceptable alternatives: 1. Use a using--declaration, which brings in specic, selected names. This is the method used in namespacedemo.cpp.

C/C++ Programming

100

2. Prex all required names by std::. In the program namespacedemo.cpp we would used std::cout and std::endl rather than the specic using statements.

6.4

The Standard Template Library

The Standard Template Library (STL) is a recent addition to the language. It provides general purpose, templatized classes and functions that implement many popular and commonly used algorithms and data structures. For example it includes support for vectors, lists, queues, and stacks. The template syntax that describes the STL can seem quite intimidating but we can use the STL without delving too deeply into this syntax. At the core of the STL are three foundational items: containers, algorithms and iterators. These items work together to provide o the shelf solutions to variety of programming problems. Containers are objects that hold other objects. For example, the classes vector and and list are container classes. Algorithms act on containers. Some of these perform searching, sorting, etc. Iterators are object which behave like pointers. They give the ability to cycle through the elements of a container in much the same way that you can move through an array with a pointer. A good on line reference for the standard library is: http://www.dinkumware.com/manuals/default.aspx. A more consise reference is: http://www.cppreference.com. A collection of references for the STL can be found at http://www.medini.org/stl/stl.html. This last web site contains an interview with the creator of the STL. His opinion of OOP is quite interesting. Containers The fundamental purpose of a container is to store multiple objects in a single container object. The choice of container type to use in a given situation depends on the characteristics and behaviour required. Containers are divided into two categories: sequence and associative. A sequence container preserves the original order in which items were added to the container. An associate container keeps the items in ascending order to speed up searching. The standard sequence containers are: deque A deque (double ended queue) is a sequence container that supports fast insertions and deletions at the beginning and end. Inserting or deleting at other positions is slow but indexing to any item is fast (i.e. random access). Items are not stored contiguously in memory. The header is <deque>. list A list is a sequence container that supports rapid insertion or deletion at any position but does not support random access. Items are not stored contiguously in memeory. The header is <list>. vector A vector is a sequence container that is like an array except that it can grow and shrink as needed. Items can be rapidly added or removed only from the end. At other positions, insertion and deletion is slowwer. Items are stored contiguously in memory. The header is <vector>. The standard associative containers are map, multimap, set and multiset.

C/C++ Programming

101

Container Adapters The stack and queue classes are implemented using one of the standard sequence containers for storage. The default container for stack and queue is deque. The stack and queue classes have a restricted set of methods (push, pop etc). As the stack and queue are implemented using a container class they are called container adapters. Iterators An iterator is an abtraction of a pointer used for pointing into containers and other sequences. The ++ operator can be used with an iterator to move it along in the sequence. The * (dereference) operator can be applied to an iterator to extract the value that the iterator is pointing to. There are ve types of iterators: input, output, forward, bidirectional and random access. Iterators can be dangerous just like pointers. Here is an example using the vector class Sample Program: vectorstl.cpp
/* File: vectorstl.cpp This program illustrates the STL vector class */ #include <iostream> #include <vector> using namespace std; int main() { int i; vector<int> v; // create zero length vector vector<int>::iterator p, q; cout << "Size = " << v.size() << "\n"; // put some values in the vector for (i=0; i < 10; i++) v.push_back(i); cout << "Size now = " << v.size() << "\n"; // print out the vector cout << "current vector is:\n"; for (i=0; i < v.size(); i++) cout << v[i] << " "; cout << "\n"; // put some more data in for(i=0; i < 10; i++) v.push_back(i+10); cout << "Size now = " << v.size() << "\n"; // print out the vector cout << "current vector is:\n"; for (i=0; i < v.size(); i++) cout << v[i] << " "; cout << "\n"; // remove some entries p = v.begin(); q = v.end(); for (i=1; i<= 5; i++) { p++; q--; } v.erase(p, q); cout << "Size now = " << v.size() << "\n"; // print out the vector

C/C++ Programming

102

cout << "current vector is:\n"; for (i=0; i < v.size(); i++) cout << v[i] << " "; cout << "\n"; return 0; } /* The output is: Size = 0 Size now = 10 current vector is: 0 1 2 3 4 5 6 7 8 9 Size now = 20 current vector is: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Size now = 10 current vector is: 0 1 2 3 4 15 16 17 18 19 */

Here is another example using the deque class. Sample Program: dequestl.cpp
// File: dequestl.cpp #include <iostream> #include <fstream> #include <deque> using namespace std; template <class T> void show(ostream& out, deque<T>& d); int main() { deque<int> mydeque; ofstream fout ("dequestl.out"); char ch; int x; do { // print a little menu cout << "\n\n1 = push_front \n"; cout << "2 = push_back \n"; cout << "3 = pop_front\n"; cout << "4 = pop_back\n"; cout << "s = show\n"; cout << "f = print to file\n"; cout << "q = quit\n\n"; cin >> ch; if (ch == 1) { cout <<"\ndata to push_front:"; cin >> x; mydeque.push_front(x); } if (ch == 2) { cout <<"\ndata to push_back:"; cin >> x; mydeque.push_back(x); } if(ch == 3) { if(mydeque.empty()) cout << "deque is empty\n"; else { x = mydeque.front(); mydeque.pop_front();

C/C++ Programming

103

cout << "\n\ndata popped : " << x; } } if(ch == 4) { if(mydeque.empty()) cout << "deque is empty\n"; else { x = mydeque.back(); mydeque.pop_back(); cout << "\n\ndata popped : " << x; } } if(ch == s) show(cout, mydeque); if(ch == f) show(fout, mydeque); }while(ch != q); return 0; }

template <class T> void show(ostream& out, deque<T>& d) { typename deque<T>::iterator p; p = d.begin(); while(p != d.end()) { out << *p << ", "; // print the data p++; // move one position down the list } out << endl; // go to a new line }

C/C++ Programming

104

Inheritance

Inheritance is the powerful codereuse mechanism of deriving a new class from an old one. That is, the existing class can be added to or altered to create the derived class. Through inheritance, a hierarchy of related types that share code and interfaces can be created. C++ supports virtual member functions: functions declared in the base class and redened in the derived class. A class hierarchy arises all of whose objects may be pointed to by a base class pointer. By accessing a virtual function through this pointer, C++ selects the appropriate function at run time rather than at compile time. Each object knows how it is to be acted on. This is a form of polymorphism. Inheritance should be designed into software to maximize reuse adn to allow a natural modeling of the problem domain. With inheritance, the key elements of the OOP design methodology are: 1. Decide on an appropriate set of types. 2. Design in their relatedness and use inheritance to share code. 3. Use virtual functions to process related objects polymorphically.

7.1

Derived Classes

As noted before, inheritance is the process of creating a new class called, the derived class, from an existing class, called the base class. The base class is unchanged by the process. The derived class inherits all the members of the base class and usually adds new members and may override old members. class base { member A; member B; member C; } class derived : public base { member A; // a new version of member A member D; member E; } The derived class has members A, B, and C by but in addition has members D and E. The process of inheritance permits code reusability. That is, once the base class is dened, coded, and debugged it need not be touched again but can be adapted by inheritance to new and dierent situations. Reusing existing code saves time and money and increases reliability (new code is based on already debugged code). One result of inheritance is that class libraries can be created and distributed in binary form so that others can use these as base classes and derive new classes suitable for their applications.

7.2

A First Example

Suppose that we have written a counter class which includes an increment operator but not a decrement operator. class counter {

C/C++ Programming

105

protected: int count; public: counter(int init=0); int value(void); void operator ++ (void); };

// this is the value of the counter // constructor for the class counter // get the value of the counter // overloaded increment operator

Now, if we want to add a decrement operator to this class there are two ways to proceed. 1. Edit the counter class adding the decrement operator and the code to implement it then recompile the resulting new class. 2. Derive a new class with counter as the base class. The major problem with method 1 is that editing and recompiling we have the possibility of introducing bugs. In addition, all programs which use counter would have to be examined to make sure we havent disturbed anything. When we use method 2, the compiler ensures that we can use one of our new objects wherever we could previously use one of our original objects. That is, functions previously written for the counter class continue to work when we pass them objects of the derived type. Sample Program: counteri.cpp // File: counteri.cpp // Program to illustrate derived classes and inheritance /* Suppose that after we have written the class counter with operator ++ we then decide that we would like to have a more general class which can also count down with an operator -We can then derive an new class from the old one retaining all the properites of the old one but adding the new functionality that we want */ #include <iostream> using namespace std; class counter { protected: int count; public: counter(int init=0); int value(void); void operator ++ (void); }; class counteri : public counter // publicly derived from counter { public: counteri(int init=0); // constructor void operator -- (void); // overloaded decrement operator }; // Use call by reference to avoid a discussion of the copy constructors. int function1(counter& x); int function2(counteri& y);

// this is the value of the counter // constructor for the class counter // get the value of the counter // overloaded increment operator

C/C++ Programming

106

int main() { counter c1(5); // c1 is initialized to 5 counteri c2(6); // c2 is initialized to 6 int temp; cout << "c1 = " << c1.value() << endl; // shows 5 cout << "c2 = " << c2.value() << endl; // shows 6 ++c1; // increment a few times ++c2; ++c1; cout << "c1 = " << c1.value() << endl; // shows 7 cout << "c2 = " << c2.value() << endl; // shows 7 --c2; // deccrement //--c1; would give an error on compilation cout << "c1 = " << c1.value() << endl; // shows 7 cout << "c2 = " << c2.value() << endl; // shows 6 /* we can use a counteri object wherever a counter object is called for That is, a counteri "is-usable-as" a counter. Some text books make a big deal about this idea but our book just takes it as a matter of course. */ temp = function1(c2); cout << "function1 returns " << temp << endl; // But, a counter is not usable as a counteri. /* cout << function2(c1); will give an error because the compiler cant figure out how to convert a counter to a counteri */ temp = function2(c2); cout << "function2 returns: " << temp << endl; /* This is ok as function2 is expecting a counteri object as its argument */ cout << "Program ends\n"; return 0; } ////////////////// implementation of class counter ////////////////// counter::counter(int init) // constructor { cout << "Creating a counter object with value: "<< init <<endl; count = init; } void counter::operator ++ (void) { count++; return; } int counter::value(void) This means that

C/C++ Programming

107

{ return count; } ////////////////// implementation of class counteri ////////////////// counteri::counteri(int init) : counter(init) { /* We want to call the constructor of class counter to create a counter object first then proceed to enlarge this object to a counteri object. As we cannot directly call counter(init) as a C++ statement we need another mechanism. This is to place the call to the constructor prefixed by a colon just before the opening bracket {. */ cout << "Creating a counteri object with value: " << init << endl; } void counteri::operator -- (void) { count--; return; } int function1(counter& x) { return x.value(); } int function2(counteri& y) { return y.value(); } /* The program output is: Creating a counter object with value: 5 Creating a counter object with value: 6 Creating a counteri object with value: 6 c1 = 5 c2 = 6 c1 = 7 c2 = 7 c1 = 7 c2 = 6 function1 returns 6 function2 returns: 6 Program ends */ Notes: 1. The colon notation in the declaration of counteri tells the compiler that counteri is derived from counter. The use of the keyword public in the deriviation tells the compiler that the protected and public members of the base class retain their access priveledges in the derived class. 2. Members which are private in the base are not acessible in the derived class. Members which are protected in the base class are accessible in the derived class. Of course, public members are always accessible. 3. To code the counteri constructor, we want to call the constructor of class counter to create a counter object rst then proceed to enlarge this object to a counteri object. As we cannot

C/C++ Programming

108

directly call counter(init) as a C++ statement we need another mechanism. This is to place the call to the constructor prexed by a colon just before the opening bracket {. Of course, we could just set count = init but in other examples the member is private rather than protected and we cant proceed this way. 4. We can call function1 with a counteri object because each counteri object is a counter object. That is, a derived object may be used where a base object is called for. (Every elephant is a mammal). A pointer whose type is pointer to the base class can point to objects of the derived type. The situation would be dierent for protected or private derivation. In this respect, public derivation should be considered the normal form of inheritance. 5. We cannot call function2 with a counter object as this function is expecting an object of the derived type not the base type. (A mammal is not necessarily and elephant). 6. The program output shows that when an object of the derived type is constructed the rst step is to create an object of the base type and then this object is enlarged to become a derived object.

7.3

Base and Derived Constructors, Destructor and Operator =

To illustrate the interaction between the base and derived constructors, destructor and operator = we derive a class label from our previous class point. The new class adds a string member to the base class. We have kept the base class members private. This does not present a problem in coding the derived class as we can call the base class members using the scope resolution operator, ::. For example, when coding the show() member for the label class we can explicitely call point::show(out). Sample Program: label.cpp /* File: label.cpp This program illustrates derived classes */ #include <iostream> #include <cmath> // needed for sqrt #include <string> using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); point(const point& p); point& operator=(const point& p); ~point(void); void show(ostream& out); }; class label : public point { private: string text; // the label public: // // // // // constructor for the class point copy constructor operator = destructor show the members of point to out

C/C++ Programming

109

label(int h, int v, int clr, char* str); label(const label& u); label& operator=(const label& u); ~label(void); void show(ostream& out); }; int main() { label* p = new label(1,2,3, "my label"); label a(4,5,6, "another label"); label q(a); a = *p; delete p; return 0; }

// // // // //

constructor copy constructor operator = destructor show the members to out

///////////////////// implementation of class point ///////////////////// point::point(int h, int v, int clr) { x = h; y = v; c = clr; cout << "creating the point "; show(cout); cout << "\n"; } point::point(const point& a) { x = a.x; y = a.y; c = a.c; cout << "copying the point "; show(cout); cout << "\n"; } point& point::operator=(const point& a) { x = a.x; y = a.y; c = a.c; cout << "assigning the point "; show(cout); cout << "\n"; return *this; } point::~point(void) { cout << "destroying the point "; show(cout); cout << "\n"; } void point::show(ostream& out)

C/C++ Programming

110

{ out << "(" << x <<", " << y << ", " << c << ")"; } /////////////////// implementation of class label ///////////////////////// label::label(int h, int v, int clr, char* str) : point(h,v,clr) { // make a duplicate of the string str text = str; cout <<"creating the label "; show(cout); cout << "\n"; } label::label(const label& u) : point(u), text(u.text) { cout <<"copying the label "; show(cout); cout << "\n"; } label& label::operator=(const label& u) { if(this != &u) { point::operator=(u); // first equate the point part text = (u.text); } cout <<"assigning the label "; show(cout); cout << "\n"; return *this; } label::~label(void) { cout << "destroying the label "; show(cout); cout << "\n"; } void label::show(ostream& out) { point::show(out); out << "<" << text << ">"; } /* The program output is: creating the point (1, 2, 3) creating the label (1, 2, 3)<my label> creating the point (4, 5, 6) creating the label (4, 5, 6)<another label> copying the point (4, 5, 6) copying the label (4, 5, 6)<another label> assigning the point (1, 2, 3) assigning the label (1, 2, 3)<my label> destroying the label (1, 2, 3)<my label> destroying the point (1, 2, 3)

C/C++ Programming

111

destroying destroying destroying destroying */

the the the the

label point label point

(4, (4, (1, (1,

5, 5, 2, 2,

6)<another label> 6) 3)<my label> 3)

7.4

Virtual Functions and Polymorphism

The show() member of class label overrides the show() member in class point. As previously stated, a class point pointer can reference either a point object or a label object. The type of the object referenced may only be known at run time. Here is an example. The implementation of the classes is the same as before except that the messages printed on creation, copying etc. is left out. Also the destructor for class point is omitted. Sample Program: labelv.cpp /* File: labelv.cpp This program illustrates virtual member functions */ #include <iostream> #include <string> using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); point(const point& p); point& operator=(const point& p); virtual void show(ostream& out); }; class label : public point { private: string text; // the label public: label(int h, int v, int clr, char* str); label(const label& u); label& operator=(const label& u); ~label(void); void show(ostream& out); }; int main() { point* ptr; // this will point to an object only known at run time int h, v, clr; char str[30]; char answer; // // // // // constructor copy constructor operator = destructor show the members to out // // // // constructor for the class point copy constructor operator = show the members of point to out

C/C++ Programming

112

cout << "point or label? (p or l)"; cin >> answer; if(answer == p) { cout << "Enter three ints :"; cin >> h >> v >> clr; ptr = new point(h, v, clr); } if(answer == l) { cout << "Enter three ints and a string:"; cin >> h >> v >> clr >> str; ptr = new label(h, v, clr, str); } cout << "You entered: "; ptr->show(cout); cout << "\n"; return 0; } /* a typical run is: point or label? (p or l)l Enter three ints and a string:1 2 3 mylabel You entered: (1, 2, 3)<mylabel> */ Notes: 1. Notice that the show() member of the base class has been declared virtual. This keyword allows us to dynamically select, at run time, the version of show() based on the type of object. 2. A virtual member function looks just like any member function except for the keyword virtual in the base class. In a derived class, it can be overridden and the function prototype of the derived function must have a matching signature and return type. 3. The selection of which function dention to invoke for a virtual function is dynamic. In the typical case, a base class pointer can reference a base class object or a derived object. The member function selected will depend on the class of the object being referenced, not on the pointer type. If the base class member is not overridden in the derived class then the base class member is selected. 4. Once a member is declared virtual in the base class it is virtual in all derived classes even those farther along in the hierarchy. So, for example, if we wanted to derive a further class from label it would not be necessary to repeat the keyword virtual in the class label. Here is another example where some members are virtual and some are not. Sample Program: virtual1.cpp // File: virtual1.cpp // Illustrates virtual functions #include <iostream> using namespace std;

C/C++ Programming

113

// All member functions are inlined class base { public: void a(void) { cout << "base::a called\n"; } virtual void b(void) {cout << "base::b called\n";} virtual void c(void) {cout << "base::c called\n";} }; class derived : public base { public: void a(void) {cout << "derived::a called\n";} void b(void) {cout << "derived::b called\n";} }; int main() { derived y; base x1 = y; base* ptr = &y; base& x2 = y; // reference variable must be initialized cout << "\nCall functions in the object y \n"; y.a(); y.b(); y.c(); cout << "\nCall functions in the object x1 \n"; x1.a(); x1.b(); x1.c(); cout << "\nCall functions in the object *ptr \n"; ptr->a(); ptr->b(); ptr->c(); cout << "\nCall functions in the object x2 \n"; x2.a(); x2.b(); x2.c(); return 0; } /* The output is: Call functions in the object y derived::a called derived::b called base::c called Call functions in the object x1 base::a called base::b called base::c called Call functions in the object *ptr base::a called derived::b called base::c called Call functions in the object x2 base::a called derived::b called

C/C++ Programming

114

base::c called */ Note that even though ptr is a base pointer, the correct member functions are executed. Similarly, even though x2 is a base (reference) object, the correct member functions are executed. We can use a small variation on this example to illustrate code reuse. In the following example, think of the functions as old code which were written when only the base class existed. By passing references or pointers to the old functions we can make them act in new ways with the derived class. All this is accomplished without modifying the functions in any way. That is, we have code reuse. Sample Program: virtual2.cpp // File: virtual2.cpp // Illustrates virtual functions #include <iostream> using namespace std; // All member functions are inlined class base { public: void a(void) { cout << "base::a called\n"; } virtual void b(void) {cout << "base::b called\n";} virtual void c(void) {cout << "base::c called\n";} }; class derived : public base { public: void a(void) {cout << "derived::a called\n";} void b(void) {cout << "derived::b called\n";} }; void function1(base x) { cout << "\nCall functions in the base class\n"; x.a(); x.b(); x.c(); } void function2(base& x) { cout << "\nCall functions in the base class\n"; x.a(); x.b(); x.c(); } void function3(base* ptr) { cout << "\nCall functions in the base class\n"; ptr->a(); ptr->b(); ptr->c(); } int main() { derived y; cout << "\nCall functions in the derived class\n"; y.a(); y.b(); y.c();

C/C++ Programming

115

function1(y); function2(y); function3(&y); return 0; } /* The output is: Call functions in the derived class derived::a called derived::b called base::c called Call functions in the base class base::a called base::b called base::c called Call functions in the base class base::a called derived::b called base::c called Call functions in the base class base::a called derived::b called base::c called */

7.5

Multiple Inheritance

The employee database assignment question uses multiple inheritance. I have made a small extension to the classes and split the code across a number of les to give an example. Sample Program: employ project

employee

student

labourer

manager

scientist

vicepres

Note: when we draw class diagrams showing the inheritance hierarchy among a set of classes the arrows point from the derived class to the base class. Source Files: bases.cpp: consisting of the implementation of the base classes.

C/C++ Programming derived.cpp: consisting of the implementation of the derived classes. employ.cpp: consisting of the main program. Header Files: bases.h: consisting of the declarations of the base classes. derived.h: consisting of the declaration of the derived classes. // File: bases.h // Declarations and definitions for an employee database #include <iostream> #include <fstream> #include <string> using namespace std; class employee { private: string firstname; string lastname; int number; float salary;

116

// // // //

employee employee employee employee

first name last name number salary

public: virtual void get(istream& in); // get employee data from in virtual void show (ostream& out); // display employee data to out }; class student { private: string school; string degree; int gradyear;

// name of school or university // highest degree obtained // year of graduation // get educational info // display educational info

public: void get(istream& in); void show(ostream& out); };

/* Notes: 1. Note that we did not provide any constructors. This means that the compiler provides its own default constructors. 2. Note that the data members of the base class are private rather than protected. This is not a problem for derived classes as each base class provides a get and a show function. */ // File: derived.h // Declarations and definitions for an employee database #include "bases.h" class manager : public employee, public student { private: // management class

C/C++ Programming

117

string title;

// vice-president etc

public: void get(istream& in); void show(ostream& out); }; class scientist : public employee, public student // scientist class { private: int pubs; // number of publications public: void get(istream& in); void show(ostream& out); }; class labourer : public employee { private: string department; public: void get(istream& in); void show(ostream& out); }; class vicepres : public manager { private: int number_of_depts; // string depts[]; //list of departments public: void get(istream& in); void show(ostream& out); }; /* Note: We are using multiple inheritance for manager and scientists and vicepres. */ // File: employ.cpp // Build a database as an array of employee pointers #include "derived.h" const int MAX=100; // maximum database size int main() { string type; ofstream fout ("employ.out"); employee* database[MAX]; employee* ptr; int count = 0; int i; // labourer class

// employee type // array of employee pointers // pointer to an employee object // number of employees

C/C++ Programming

118

while(1) // loop until break occurs { cout << "\n\nType of employee: "; cin >> type; if(type == "labourer") ptr = new labourer; else if (type == "scientist") ptr = new scientist; else if (type == "manager") ptr = new manager; else if (type == "vicepres") ptr = new vicepres; else break; // we are done entering data ptr->get(cin); database[count] = ptr; count++; } // now write out the database to a file for(i=0; i<count;i++) { database[i]->show(fout); fout << "\n\n"; } return 0; }

7.6

Virtual Base Classes

If we were to derive the vicepres class also from scientist or labourer then the base class employee would occur in vicepres twice and the compiler would complain that the call to the employee member functions is ambiguous.

employee

student

labourer

manager

scientist

vicepres

In this case we would have to modify our declaration of the derived classes manager and scientist to insert the keyword virtual in their derivation declaration. We then call these virtual base classes. class manager : virtual public employee, virtual public student // management class

C/C++ Programming

119

{ private: char title[LEN]; // vice-president etc

public: void get(istream& in); void show(ostream& out); }; class scientist : virtual public employee, virtual public student { private: int pubs; // number of publications public: void get(istream& in); void show(ostream& out); }; class labourer : public employee { private: char department[LEN]; public: void get(istream& in); void show(ostream& out); }; class vicepres : public manager { private: int number_of_depts; public: void get(istream& in); void show(ostream& out); }; The iostream library contains a concrete example of multiple inheritance: the class iostream can be derived from either istream or ostream with each of these being derived from class ios. // labourer class // scientist class

C/C++ Programming

120

ios

ostream

istream

iostream

ofstream

fstream

ifstream

Some I/O Classes


Note that this class diagram explains why we can pass an ifstream object to a function which is expecting an istream object.

7.7

Virtual Destructors

We saw in the program label.cpp that when a derived object is created rst the base class constructor is called then the derived constructor is called. When a derived object is destroyed the derived class constructor is called rst and then the base class destructor. When we use base class pointers and point them to derived objects we nd that the derived class destructor is not called when it should be unless the base class destructor is declared virtual. Here is an example: Sample Program: lablevd.cpp /* File: labelvd.cpp This program illustrates the necessity of virtual destructors in the base class */ #include <iostream> #include <cmath> // needed for sqrt #include <string> using namespace std; class point { private: int x; // horizontal position int y; // vertical position int c; // colour public: point(int h, int v, int clr); point(const point& p); point& operator=(const point& p); ~point(void); virtual void show(ostream& out); // // // // // constructor for the class point copy constructor operator = destructor show the members of point to out

C/C++ Programming

121

}; class label : public point { private: string text; // the label public: label(int h, int v, int clr, char* str); label(const label& u); label& operator=(const label& u); ~label(void); void show(ostream& out); }; int main() { point* p; label* q = new label(4,5,6, "a label"); p = q; delete p; return 0; } ///////////////////// implementation of class point ///////////////////// point::point(int h, int v, int clr) { x = h; y = v; c = clr; cout << "creating the point "; show(cout); cout << "\n"; } point::point(const point& a) { x = a.x; y = a.y; c = a.c; cout << "copying the point "; show(cout); cout << "\n"; } point& point::operator=(const point& a) { x = a.x; y = a.y; c = a.c; cout << "assigning the point "; show(cout); cout << "\n"; return *this; } // // // // // constructor copy constructor operator = destructor show the members to out

C/C++ Programming

122

point::~point(void) { cout << "destroying the point "; show(cout); cout << "\n"; } void point::show(ostream& out) { out << "(" << x <<", " << y << ", " << c << ")"; } /////////////////// implementation of class label ///////////////////////// label::label(int h, int v, int clr, char* str) : point(h,v,clr), text(str) { cout <<"creating the label "; show(cout); cout << "\n"; } label::label(const label& u) : point(u), text(u.text) { cout <<"copying the label "; show(cout); cout << "\n"; } label& label::operator=(const label& u) { if(this != &u) { point::operator=(u); // first equate the point part text = u.text; } cout <<"assigning the label "; show(cout); cout << "\n"; return *this; } label::~label(void) { cout << "destroying the label "; show(cout); cout << "\n"; } void label::show(ostream& out) { point::show(out); out << "<" << text << ">"; } /* The program output is: creating the point (4, 5, 6) creating the label (4, 5, 6)<a label>

C/C++ Programming

123

destroying the point (4, 5, 6) After making point::~point() virtual the output becomes: creating the point (4, 5, 6) creating the label (4, 5, 6)<a label> destroying the label (4, 5, 6)<a label> destroying the point (4, 5, 6) */ So, we should always make our base class destructors virtual.

7.8

Run Time Type Identication

We have seen that if we have a derived class and a base class pointer then we can reference a derived object with the base class pointer. class base { // some stuff }; class derived public : base { // more stuff } int main() { base *p, *q; p = new base; q = new derived; return 0; } In some programs we cant be sure at compile time what type a pointer will be pointing to at run time. One of the features recently added to the language is the ability to do Run Time Type Identication (RTTI). To do this we use the typeid function which returns a reference to an object of type type info. The type info class includes such members as: bool operator== (const type_info& obj); bool operartor!= (const type_info& obj); const char* name(); The name function returns a pointer to the name of the type. The overloaded operators can be used to check if two types match or not. The name is suitable for collating. The names, encoded values, and collating order for types are all unspecied and may dier between program executions. To use typeid we must include <typeinfo>. Here is an example using various geometric geometric shapes. At the top of the hierarchy is an abstract base class called Shape and four derived classes Line, Square, Rectangle, and NullShape. The function generator() generates an object and returns a pointer to it. The actual object created is determined randomly using the random number function rand() in the C header le stdlib.h. The shapes of each object is displayed. There is no way to know at compile type what shapes will be generated.

C/C++ Programming

124

Note that the base class Shape contains a pure virtual function This is a virtual function which has no implementation. We signify such functions by placing an = 0 after the function declaration. Any class containing a pure virtual function is called an abstract class. No objects of this type can be instantiated (declared). Sample Program: shapefactory.cpp // File: shapefactory.cpp #include <iostream> #include <cstdlib> #include <typeinfo> using namespace std; class Shape // Shape is abstract { public: virtual void draw() = 0; // pure virtual function }; class Rectangle: public Shape { public: void draw() {cout << "*****\n* }; class Triangle: public Shape { public: void draw() {cout << "*\n* *\n* };

*\n*

*\n*****\n";}

*\n*****\n";}

class Line: public Shape { public: void draw() {cout << "******\n";} }; class NullShape: public Shape { public: void draw() { } }; // A factory for objects derived from Shape. Shape *generator() { switch(rand() % 4) { case 0: return case 1: return case 2: return case 3: return } new Line; new Rectangle; new Triangle; new NullShape;

C/C++ Programming

125

return NULL; } int main() { int i; Shape *p; srand(time(NULL)); // seed rand() for(i=0; i<10; i++) { p = generator(); // get next object cout << typeid(*p).name() << endl; // draw object only if it is not a NullShape if(typeid(*p) != typeid(NullShape)) p->draw(); } return 0; } /* A sample run gave: 4Line ****** 9NullShape 8Triangle * * * * * ***** 8Triangle * * * * * ***** 9Rectangle ***** * * * * ***** 4Line ****** 9Rectangle ***** * * * * ***** 9Rectangle ***** * * * * ***** 9Rectangle ***** * *

C/C++ Programming

126

* * ***** 8Triangle * * * * * ***** */

C/C++ Programming

127

Exceptions

Exception handling allows us to manage run-time errors in an orderly fashion. Using C++ exception handling, our programs can automatically invoke an error-handling routine when an error occurs. Error handling is not important when we are rst learning the language but it is a requirement of professional quality software.

8.1

Traditional Error Handling Versus Exceptions

The traditional approach to exception handling is awkward and unreliable. In this approach, if we detect a run-time error in a function we return an error code indicating the condition to the calling routine. The code that handles the error may be several layers up in the function call stack so that the error must be propagated through the layers. This process breaks down if any function in the chain is not able to handle the condition.
main() f1() f1(); f2() f2(); return error return error

Traditional Error Handling

In C++ exception handling, control can pass directly from the error reporting to the error handler.
main() try { f1(); } catch(x) { } f1() f2() f2();

throw x;

C++ Exception Handling

Not only is the C++ version of error handling easier to write and maintain, but it is also the only way to report an error from inside a constructor (as constructors do not return any values). Exception handling was not part of the original specication for C++. It evolved from 1984 to 1989 and is now part of the ANSI C++ standard. C++ exception handling is built on three keywords: try, catch, and throw. In most general terms, program statements that we want to monitor for exceptions are contained in a try block. If an exception (i.e. an error ) occurs within the try block an exception (an object) is thrown. The exception is caught and processed by the catch block which matches the objects type. More precisely, any statement that throws an exception should be executed from within a try block. Functions called from within a try block may also throw an exception. The exception is caught by a

C/C++ Programming

128

catch block which immediately follows the try block. The catch blocks are checked in order to nd one which matches the exception type. For example:
try { /* this is called a try block. When some error condtion is occurs an object characterizing the error is created and then thrown with the throw statement. */ throw obj; } catch(type1 arg) { // process the exception if the data type of obj matches type1 } catch(type2 arg) { // process the exception if the data type of obj matches type2 } catch(type3 arg) { // process the exception if the data type of obj matches type3 } . . . catch(typeN arg) { // process the exception if the data type of obj matches typeN } catch(...) { // catch-all case: process the exception if the exception in any case }

Here is a simple example showing traditional error handling followed by the analogous code using exceptions. Sample Program: Traditional.cpp
/* File: Traditional.cpp Illustrates traditional error handling If we detect an error in a function we return an error code indicating the condition to the calling routine. The code that handles the error may be several layers up in the function call stack so that the error must be propagated through the layers. This process breaks down if any function in the chain is not able to handle the condition. */ #include <iostream> #include <fstream> using namespace std; int function1(char* name); int function2(istream& in); int main() { int flag; flag = function1("test.in"); if(flag == 1) cout << "file open error occurred\n"; else if(flag == 2) cout << "file reading error occurred\n";

C/C++ Programming

129

return 0; } int function1(char* name) { int flag=0; ifstream fin(name); if(fin) flag = function2(fin); else flag = 1; return flag; } int function2(istream& in) { int a, b, c; int flag = 0; in >> a >> b; if(in) { c = a + b; cout << " sum = " << c << "\n"; } else flag = 2; return flag; }

Sample Program: Exception.cpp


/* File: Exception.cpp Illustrates exception handling. C++ exception handling is built on three keywords: try, catch, and throw. Program statements that we want to monitor for exceptions are contained in a "try block". If an exception (i.e. an error ) occurs within the try block an exception is "thrown". The exception is caught and processed using a "catch block". */ #include <iostream> #include <fstream> using namespace std; void function1(char* name); void function2(istream& in); int main() { try { function1("test.in"); } catch(int& flag) { if(flag == 1) cout << "file open error occurred\n"; else if(flag == 2) cout << "file reading error occurred\n"; } return 0; } void function1(char* name)

C/C++ Programming

130

{ ifstream fin(name); if(fin) function2(fin); else throw(1); } void function2(istream& in) { int a, b, c; in >> a >> b; if(in) { c = a + b; cout << " sum = " << c << "\n"; } else throw(2); }

8.2

Exception Basics

Here is a simple example illustrating the various points about try, catch, and throw. Sample Program: ExceptionBasic.cpp
// File: ExceptionBasic.cpp // A simple program to illustrate exceptions #include <iostream> #include <string> using namespace std; int main() { string datatype; cout << "Start\n"; do { try { // start a try block cout << "Enter a exception type: float, int, or char: "; cin >> datatype; if(datatype == "float") throw((float) 1); // use a cast as 1.0 is a double else if(datatype == "int") throw(1); else if(datatype == "char") throw(1); else throw "any other exception"; } catch(float& f) { cout << "Caught a float exception with value: " << f << "\n\n"; } catch(int& i) { cout << "Caught a int exception with value: " << i << "\n\n"; } catch(char& c) // type is char*

C/C++ Programming

131

{ cout << "Caught a char exception with value: " << c << "\n\n"; } catch(...) // default catch { cout << "Caught an exception\n\n"; } }while(datatype != "none"); return 0; } /* The output of a run of the program is Start Enter a exception type: float, int, or char: float Caught a float exception with value: 1 Enter a exception type: float, int, or char: int Caught a int exception with value: 1 Enter a exception type: float, int, or char: char Caught a char exception with value: 1 Enter a exception type: float, int, or char: none Caught an exception */

If an exception is thrown within the try block the catch blocks are checked in order to nd one which matches the objects type. The catch(...) will match any type. If it is to be present, it should go last. If there is no matching catch block and the default catch is not present the terminate function is called causing the program to abort. This can easily be shown in the above example by commenting out the catch(...) block. Exceptions are generally caught by reference for the following reason. Using inheritance, a family of types for error handling can be dened. A dervied object can be thrown and caught by a catch block matching the base type (by reference). This is a way of dealing generically with an entire family of related error conditions. The C++ standard provides several classes just for this purpose. 8.2.1 Stack Unwinding

When an exception is thrown but not caught in a particular scope then an attempt is made to catch the exception in the next outer try/catch block. If the nested try/catch blocks use function calls then we say that (function call) stack unwinding takes place. Here is an example. Sample Program: ExceptionUnwindStack.cpp
/* File: ExceptionUnwindStack.cpp Illustrates stack unwinding with nested try blocks. When an exception is thrown but not caught in a particular scope then an attempt is made to catch the exception in the next outer try/catch block. If the nested try/catch blocks use function calls we say that (function call) stack unwinding takes place. */ #include <iostream> #include <string> using namespace std; void void void void function1(void); function2(void); function3(void); function4(void);

int main()

C/C++ Programming

132

{ try { function1(); } catch(char const* p) { cout << "Caught an exception with value: " << p << "\n"; } return 0; } void function1(void) { try { function2(); } catch(float& f) { cout << "Caught a float exception with value: " << f << "\n"; } } void function2(void) { try { function3(); } catch(int & i) { cout << "Caught an int exception with value: " << i << "\n"; } } void function3(void) { try { function4(); } catch(char & c) { cout << "Caught an char exception with value: " << c << "\n"; } } void function4(void) { string datatype; cout << "Enter an exception type: float, int, or char: "; cin >> datatype; if(datatype == "float") throw (float) 1; else if(datatype == "int") throw 1; else if(datatype == "char") throw 1; else throw "any other exception"; } // type is char*

8.2.2

Exception Specication

An exception specication (also called a throw list) enumerates a list of exceptions that a function can throw. A function can throw only exceptions of the types indicated by the specication or exceptions of any type derived from these types.

C/C++ Programming

133

If the function throws an exception that does not belong to a specied type then function unexpected is called which terminates the program. A function with no exception speciction can throw exceptions of any type. The empty specication can also be listed as throw(); Here is an example. Sample Program: ExceptionSpecication.cpp
/* File: ExceptionSpecification.cpp Illustrates exception specifications */ #include <iostream> #include <string> using namespace std; void somefunction(string type) throw (float, int, char, char const*); int main() { string datatype; cout << "Start\n"; do { try { // start a try block cout << "Enter a exception type: float, int, or char: "; cin >> datatype; somefunction(datatype); } catch(float& f) { cout << "Caught a float exception with value: " << f << "\n\n"; } catch(int& i) { cout << "Caught a int exception with value: " << i << "\n\n"; } catch(char& c) { cout << "Caught a char exception with value: " << c << "\n\n"; } catch(...) // default catch { cout << "Caught an exception\n\n"; } }while(datatype != "none"); return 0; } void somefunction(string type) throw (float, int, char, char const*) { if(type == "float") throw (float) 1; else if(type == "int") throw 1; else if(type == "char") throw 1; else throw "any other exception"; } // type is char*

C/C++ Programming

134

8.3

User Dened Exception Types

Programs usually dont throw builtin datatypes like float, int and char. A program can dene and use its own exception types or it can use a predened exception type. The standard library has a number of exception types in the header les exception, new, and stdexcept. Here is an example in which an exception type is dened and used. Sample Program: DomainError.cpp
/* File: DomainError.cpp Illustrates the use of exceptions */ #include <iostream> #include <string> #include <cmath> using namespace std; class DomainError { private: string whatError; public: DomainError(const char* message){ whatError = message;} const char* what(void) { return whatError.c_str(); } }; float quotient(float numerator, float denominator); float squareroot(float number); float logarithm(float number); const float zero = 0.000001; int main(void) { int number1, number2; float number; float result; while(1) { cout << "Enter two numbers (0 0 to end): "; cin >> number1 >> number2; if(fabs(number1) < zero) break; try { result = quotient(number1, number2); cout << "The quotient is: " << result << "\n"; result = squareroot(number1); cout << "The square root of " << number1 << " is " << result << "\n"; result = logarithm(number2); cout << "The logarithm of " << number2 << " is " << result << "\n"; } catch (DomainError& d) { cout << d.what() << endl; } } return 0; } float quotient(float numerator, float denominator) { float result; if(fabs(denominator) < zero){ DomainError err("division by zero"); throw ( err ); }

C/C++ Programming

135

result = numerator / return result; }

denominator;

float squareroot(float number) { float result; if(number < -zero){ DomainError err("square root of a negative number"); throw ( err ); } result = sqrt(number); return result; } float logarithm(float number) { float result; if(number < -zero){ DomainError err("logarithm of a negative number"); throw ( err ); } result = log10(number); return result; }

8.4

Standard Exception Types

The header le exception denes the class exception as follows: class exception { public: exception() throw(); exception(const exception& right) throw(); exception& operator=(const exception& right) throw(); virtual ~exception() throw(); virtual const char *what() const throw(); }; The class serves as the base class for all exceptions thrown by certain expressions and by the Standard C++ library. The C string value returned by what() is left unspecied by the default constructor, but may be dened by the constructors for certain derived classes as an implementation-dened C string. The keyword throw() following each member function declaration shows that these methods do not throw any exceptions. The header le stdexcept derives several classes used for reporting exceptions. The classes logic error and runtime error are derived from the base class exception. class logic_error : public exception { public: logic_error(const string& message); }; The class serves as the base class for all exceptions thrown to report errors presumably detectable before the program executes, such as violations of logical preconditions. The value returned by what() is a copy of message.data(). class runtime_error : public exception {

C/C++ Programming

136

public: runtime_error(const string& message); }; The class serves as the base class for all exceptions thrown to report errors presumably detectable only when the program executes. The value returned by what() is a copy of message.data(). There are four classes derived from class logic error. These are: // Specific logic errors class domain_error : class invalid_argument : class length_error : class out_of_range : defined by the standard. public logic_error; public logic_error; public logic_error; public logic_error;

There are three classes derived from class runtime error. These are: // Specific runtime errors defined by the standard. class range_error : public runtime_error; class overflow_error : public runtime_error; class underflow_error : public runtime_error; We can modify our class DomainError to be an instance of class domain error as shown in the following program. Note that an object of type domain error is thrown in each case but the catch uses the base class exception. It is essential to catch by reference for this strategy to work. Sample Program: DomainErrorException.cpp
/* File: DomainErrorException.cpp Illustrates deriving using the standard exception classes */ #include #include #include #include #include <iostream> <string> <cmath> <exception> <stdexcept>

using namespace std; float quotient(float numerator, float denominator); float squareroot(float number); float logarithm(float number); const float zero = 0.000001; int main(void) { int number1, number2; float number; float result; while(1) { cout << "Enter two numbers (0 0 to end): "; cin >> number1 >> number2; if(fabs(number1) < zero) break; try { result = quotient(number1, number2); cout << "The quotient is: " << result << "\n"; result = squareroot(number1); cout << "The square root of " << number1 << " is " << result << "\n"; result = logarithm(number2); cout << "The logarithm of " << number2 << " is " << result << "\n"; }

C/C++ Programming

137

catch (exception& d) { cout << d.what() << endl; } } return 0; } float quotient(float numerator, float denominator) { float result; if(fabs(denominator) < zero){ domain_error err("division by zero"); throw ( err ); } result = numerator / return result; } float squareroot(float number) { float result; if(number < -zero){ domain_error err("square root of a negative number"); throw ( err ); } result = sqrt(number); return result; } float logarithm(float number) { float result; if(number < -zero){ domain_error err("logarithm of a negative number"); throw ( err ); } result = log10(number); return result; } denominator;

Here is another example in which exception handling is added to the stack class implemented as an array. Sample Program: ExceptionStack.cpp
/* File: ExceptionStack.cpp This program implements a simple stack of integers with bounds checking member functions using exceptions. */ #include <iostream> #include <fstream> #include <stdexcept> using namespace std; class stack { private: int *data; int max; int count;

// pointer to the array holding the stack items // maximum stack size // number of items on the stack // // // // constructor, n = maximum stack size push the integer item onto the stack pop an item off the stack display stack to out

public: stack(int n); void push(int item); int pop(void); void show(ostream& out); };

C/C++ Programming

138

// test functions void pop_a_lot(stack& s, int n); void push_a_lot(stack& s, int n); int main() { stack safe(5); int n; cout << "Enter the number of elements to push: "; cin >> n; cout << "Push onto the stack object\n"; try { push_a_lot(safe, n); } catch(exception& err) { cerr << "Error: bounds exceeded\n"; cerr << "Reason: " << err.what() << "\n"; } cout << "Enter the number of elements to pop: "; cin >> n; cout << "Pop from the stack object\n"; try { pop_a_lot(safe, n); } catch(exception& err) { cerr << "Error: bounds exceeded\n"; cerr << "Reason: " << err.what() << "\n"; } return 0; } ////////////////////// Implemenation of stack ///////////////////// stack::stack(int n) { max = n; data = new int[max]; // allocate the array count = 0; // the stack has no entries yet } void stack::push(int item) { if(count == max) { runtime_error err("Stack is full"); throw err; } else { data[count] = item; count++; } } int stack::pop(void) { if (count == 0) { runtime_error err("Stack is empty"); throw err; }

C/C++ Programming

139

else { count--; return data[count]; } } void stack::show(ostream& out) { for(int i=0; i<count; i++) out << data[i] << ", "; out << endl; } void pop_a_lot(stack& s, int n) { int i; for(i=1; i <= n; i++) cout << "Popping " << s.pop() << endl; } void push_a_lot(stack& s, int n) { int i; for(i=1; i <= n; i++) { s.push(i); cout << "Pushing " << i << endl; } }

8.5

The bad alloc Exception

The C++ standard denes a few other exception classes that are derived from exception but that are not derived from either logic error or runtime error. The most important of these exceptions is bad alloc. The library operator new throws bad alloc if it is unable to allocate the requested memory. Here is an example. Sample Program: BadAllocException.cpp
/* File: BadAllocException.cpp Illustrates the bad_alloc exception */ #include <iostream> #include <exception> #include <new> using namespace std; int main() { double* ptr; int size; cout << "Enter array size: "; cin >> size; try { ptr = new double[size]; cout << "Allocation successful\n"; } catch(bad_alloc& err ) { cout << "Memory allocation error\n"; cout << err.what() << "\n"; }

C/C++ Programming

140

cout << "program ends\n"; return 0; }

C/C++ Programming

141

Resources
1. GNU C++ for Linux, by Tom Swann, published by Que. Despite the title this is a great book for experienced programmers to learn C++. There is a very good treatment of the STL as well as an introduction to X windows programming. This is the book for anyone wanting to apply C++ in the UNIX environment. 2. Eective C++, 50 Specic Ways to Improve Your Programs and Designs, by Scott Meyers, publised by Addison Wesley. This is a must for the serious C++ programmer. 3. The C++ Programming Language by Bjarne Stroustrup, published by Addison Wesley Bjarne Stroustrup is the designer of C++ and has written a number of great books on C++. This book is divided into three parts: The rst part provides a tutorial introduction to C++. The second part presents a discussion of design and software development issues arising in connection with the use of C++ and the third part is a complete reference manual. One of the best way to learn a programming language is by writing small programs relevant to the item you study. There are exercises at the end of each chapter to test/apply what you learned in that particular chapter. This is really a great book but is probably not good for beginners. 4. C++ Primer by Stanley Lippman, published by Addison Wesley The C++ Primer is really is one of the best books C++ books but it assumes a lot of familiarity with programming concepts and a prociency in C and is probably not good for beginners. 5. C++ Programming for C Programmers, by Ira Pohl, published by Addison Weseley. This is a good book for intermediate programmers. It doesnt really require a C background. There is a very good appendix. 6. Teach yourself C++, third edition, by Herb Schildt, published by Osborne. This book is written for C programmers and is written to the new ANSI standard. This book is not for beginners. 7. The C++ primer by Mitchell Waite, published by the Waite Group, This is another excellent book for beginners. It does not assume familiarity with C. 8. The C Programming Language 2nd Edition, by B. Kernighan and D. Ritchie; Published by Prentice Hall The bible for C. 9. Practical C Programming by Steve Oualline, published by OReilly and Associates. This is a good C book.

10. The ANSI C++ standard is at http://www.cygnus.com/misc/wp 11. The online book C++ Annotations can be found at http://www.icce.rug.nl/docs/cplusplus 12. Stroustroups C++ web page is at http://www.research.att.com/ bs/C++.html 13. The Dinkum C++ library reference is at http://www.dinkumware.com/htm cpl/index.html 14. The CodeForge ntegrated developement environment can be found at http://www.codeforge.com/ 15. Thinking in C++, by Bruce Eckel can be found online at http://www.bruceeckel.com/ThinkingInCPP2e.html 16. The C++ FAQ (lite version) can be found at http://new-brunswick.net/workshop/c++/faq/ 17. A collection of sites of interest to C++ users is at http://webnz.com/robert/cpp site.html

Anda mungkin juga menyukai