cs213 Objectorientedprogrammingi
cs213 Objectorientedprogrammingi
An Introduction to
Object Oriented Programming
using C++
CS213: Object Oriented Programming I
By
2023
Modern University for Technology & Information
Faculty of Computers and Artificial intelligence
Quality Assurance Unit
ﺘﻠتزم �ﻠ�ﺔ اﻟحﺎﺴ�ﺎت واﻟذ�ﺎء اﻻﺼطنﺎﻋﻲ ﺒتﻘد�م ﺨدﻤﺎت ﺘﻌﻠ�م�ﺔ أﻛﺎد�م�ﺔ ﺘؤدي إﻟﻰ ﺨر�ﺞ ﻤتمیز ﻗﺎدر ﻋﻠﻰ
اﻟمنﺎﻓسﺔ ﻤحﻠ�ﺎً و�ﻗﻠ�م�ﺎً وﻋﺎﻟم�ﺎً وﻋﻠﻰ �ﻔﺎءة ﻋﺎﻟ�ﺔ ﻓﻲ ﻤجﺎﻻت اﻟحﺎﺴ�ﺎت وﺘكنوﻟوﺠ�ﺎ اﻟمﻌﻠوﻤﺎت و�تمتﻊ �مﻬﺎرات
وأﺨﻼق�ﺎت ﻤﻬن�ﺔ رف�ﻌﺔ وﻗﺎدر ﻋﻠﻰ اﻟتﻌﻠم اﻟذاﺘﻲ ﻟمواﻛ�ﺔ اﻟتطورات اﻟسر�ﻌﺔ ﻓﻲ ﻤجﺎل ﺘخصصﻪ وﺘﻘد�م ﺘﻌﻠ�م
ﻤستمر وﺨدﻤﺎت ﺘدر�ب�ﺔ و�ﺴتشﺎر�ﺔ ﻟتحﻘیق اﻟتنم�ﺔ اﻟﻘوﻤ�ﺔ واﻻرﺘﻘﺎء �ﺎﻟبیئﺔ اﻟمحﻠ�ﺔ و�ﺠراء اﻟ�حوث اﻟﻌﻠم�ﺔ ﻓﻲ
ﻤختﻠﻒ ﻤجﺎﻻت ﻋﻠوم اﻟحﺎﺴب وﺘكنوﻟوﺠ�ﺎ اﻟمﻌﻠوﻤﺎت.
Modern University for Technology & Information
Faculty of Computers and Information
Course Specification
CS213: Object Oriented Programming I
A- Affiliation:
Relevant Program: Computer Science / Information System
Department offering the program: Computer Science Department /
Information System Department.
Department offering the course: Computer Science Department
B-Basic Information
C-Professional Information
Introduction to computer programs and C++ . Revision of elementary
programming in C++: control structures, loops, methods and arrays. Object –
Oriented Programming: Object and Classes, Strings and Text I/O, and
Inheritance & Polymorphism.
B-Intellectual Skills:
On successful completion of the course, the students should be able to:
b1) Realize the notions of objects and classes. (B1, B2)
b2) Model objects as in the real world using programming languages.(B3,
B17, B18)
2
Modern University for Technology & Information
Faculty of Computers and Information
3- Contents:
Tutorial /
Topic Lecture
Practical
Introduction to OOP 6 4
OOP Basic Concepts 6 4
C++ Introduction 6 4
C++ Classes and Objects 6 4
C++ Member Functions 6 4
Control Statements – Part 1 6 4
Control Statements – Part 2 6 4
Input and Output Statements 3 2
Total 45 30
4-Teaching and Learning and Assessment methods:
Learning
Teaching Methods Assessment Method
Methods
Course IL O'S
Researching and
Problem solving
Practical Exam
&Experiments
Written Exam
Modeling and
Assignments
Term papers
Presentations and
Laboratory
Simulation
Discussions and
Tutorials
Quizzes
Reports
Lecture
seminars
Movies
a1 √ √ √ √ √ √ √ √ √
Intellectu Knowledg
a2 √ √ √ √ √
e
a3 √ √ √ √ √ √
b1 √ √ √ √ √
b2 √ √ √ √ √ √
al
b3 √ √ √ √ √
c1 √ √ √ √
Applie
c2 √ √ √ √ √
d
c3 √ √ √ √ √
d1 √ √ √
Gener
d2 √ √ √
al
d3 √ √
3
Modern University for Technology & Information
Faculty of Computers and Information
6- List of references
6.1 - Course notes:
Available as Power Point slides and handed to the students on first
day of classes.
6.2 – Required Books:
• C++ programming language. 3rd edition. BjarneStroustrup.
4
Table of Contents
Chapter 1 C++ Basics ..............................................................................................1
1.1. A Brief History of C++ .................................................................................2
1.2. Your First C++ Program ...............................................................................3
1.3. Comments ......................................................................................................4
1.4. Variables ........................................................................................................5
1.4.1. Fundamental Variable Types ..................................................................6
1.4.2. Defining a Variable .................................................................................6
1.4.3. Constants .................................................................................................7
1.4.4. Keywords ................................................................................................8
1.5. Console Input/Output ....................................................................................8
1.5.1. Output Using cout ...................................................................................9
1.5.2. Input Using cin ......................................................................................12
1.6. Operations and Expressions ........................................................................13
1.7. Control Structures........................................................................................19
1.7.1. Boolean Expressions .............................................................................19
1.7.2. The If Statement ....................................................................................20
1.7.3. The Switch Statement ...........................................................................22
1.7.4. The Conditional Operator .....................................................................24
1.7.5. The While Loop ....................................................................................25
1.7.6. The Do..While Loop .............................................................................25
1.7.7. The For Loop.........................................................................................26
1.7.8. The continue Statement .........................................................................28
1.7.9. The break Statement..............................................................................29
1.8. Arrays ..........................................................................................................30
1.8.1. Multidimensional Arrays ......................................................................31
1.9. Pointers ........................................................................................................33
1.10. Functions ..................................................................................................34
Exercises ...............................................................................................................38
Chapter 2 Classes and objects ..............................................................................39
i
2.1. Classes and Structures .................................................................................40
2.2. Stack Implementation using Structures .......................................................43
2.3. Classes as arguments and as Return Values ................................................45
2.3.1. Addresses as Arguments and as Return Values ....................................45
2.3.2. Entire Structures as Arguments and Return Values..............................47
2.3.3. Dynamic data structures ........................................................................48
2.3.4. References as Argument and Return Values ........................................48
2.4. Unions..........................................................................................................49
2.5. Bit Fields .....................................................................................................52
2.6. Member Functions and Encapsulation ........................................................52
2.7. Member Access Control ..............................................................................55
2.7.1. Using :: for functions that return pointers .............................................57
2.7.2. Implementation of the Stack Example Using the Member Function Concepts .....58
2.8. Constructors and Destructors ......................................................................59
2.9. Operator Overloading and Friend Functions...............................................63
2.9.1. Operator Overloading ...........................................................................63
2.9.2. Friend Functions ...................................................................................66
2.9.3. Operators as member functions ............................................................67
2.9.4. Overloading applied to unary operators ................................................69
2.10. Type Conversion for Classes ...................................................................70
2.11. Copying a Class Object ............................................................................73
2.11.1. Copy Constructors .............................................................................74
2.11.2. Copying by Assignment Operators ....................................................75
2.11.3. Copying Demonstrated by an Example .............................................75
2.11.4. Conversion from class types to other types .......................................79
Exercises ...............................................................................................................81
Chapter 3 Object-Oriented Programming .........................................................82
3.1. Introduction .................................................................................................83
3.2. Interface, Implementation, and Application Files .......................................83
3.3. Derived Classes and Inheritance .................................................................84
3.4. Virtual Functions and Late Binding ............................................................89
3.5. Static Class Members ..................................................................................96
Exercises ...............................................................................................................98
ii
Chapter 1
C++ Basics
This chapter introduces the basic elements of a C++ program. We will use simple
Examples to show the structure of C++ programs and the way they are compiled.
Elementary concepts such as constants, variables, and their storage in memory will
also be discussed.
C++ evolved from C, which evolved from two previous programming languages,
BCPL and B. BCPL was developed in 1967 by Martin Richards as a language for
writing operating-systems software and compilers for operating systems. C++, an
extension of C, was developed by Bjarne Stroustrup in the early 1980s at Bell
Laboratories. C++ provides a number of features that "spruce up" the C language,
but more importantly, it provides capabilities for object-oriented programming.
Objects have characteristics, also called properties or attributes, such as age, fast,
spacious, black, or wet. They also have capabilities, also called operations or
functions, such as purchase, accelerate, fly, purr, or bubble. It is the job of object-
oriented programming to represent these objects in the programming language.
2
1.2. Your First C++ Program
1. #include <iostream>
2. using namespace std;
3. int main()
4. {
5. cout << "Welcome to C++!\n";
6. return 0;
7. }
When This C++ program run, simply outputs the message Welcome to C++! to the
screen.
Line 1: This line uses the preprocessor directive #include to include the
contents of the header file iostream in the program. Iostream is a
standard C++ header file and contains definitions for input and output.
Line 2: using namespace std; instructing C++ compiler to use the standard C++
library. If you don't give this instruction, then you will have to use std::
followed by the function each time you use a standard C++ function.
Line 3: This line defines a function called main. A function may have zero or
more parameters; these always appear after the function name, between
a pair of brackets. A function may also have a return type; this always
appears before the function name. The return type for main is int (i.e.,
an integer number). All C++ programs must have exactly one main
function. Program execution always begins from main.
Line 4: This open brace marks the beginning of the main function.
Line 5: This line is a statement. A statement is a computation step, which may
produce a value. The end of a statement is always marked with a
semicolon (;). This statement causes the string " Welcome to C++!\n"
to be sent to the cout output stream. A string is any sequence of
characters enclosed in double-quotes. The last character in this string
(\n) is a newline character.
Line 6: return 0; indicate that program ended successfully.
Line 7: This brace marks the end of the main function.
3
1.3. Comments
return 0;
}
Comments should be used to enhance (not to hinder) the readability of a program.
The following two points, in particular, should be noted:
• A comment should be easier to read and understand than the code which it
tries to explain. A confusing or unnecessarily-complex comment is worse than
no comment at all.
• Over-use of comments can lead to even less readability. A program which
contains so much comment that you can hardly see the code can by no means
be considered readable.
• Use of descriptive names for variables and other entities in a program, and
proper indentation of the code can reduce the need for using comments.
The best guideline for how to use comments is to simply apply common sense.
4
1.4. Variables
A variable is a symbolic name for a memory location in which data can be stored
and subsequently recalled. Variables are used for holding data values so that they
can be utilized in various computations in a program. Every variable in a C++
program must be declared before it is used. When you declare a variable you are
telling the compiler what kind of data you will be storing in the variable. For
example,
int myVariable;
myVariable = 42;
All variables have two important attributes:
• A type, which is, established when the variable is defined (e.g., integer, real,
character). Once defined, the type of a C++ variable cannot be changed.
• A value, which can be changed by assigning a new value to the variable. The
kind of values a variable can assume depends on its type. For example, an
integer variable can only take integer values (e.g., 2, 100, and 12).
4
2
5
1.4.1. Fundamental Variable Types
Memory
Type Size Range Precision
Used
Integers
char 1 byte +27 – 1 to - 27 N/A
unsinged char 1 byte 0 to +2 – 1
8
N/A
short (also called short int) 2 bytes +2 – 1 to - 2
15 15
N/A
unsigned short 2 bytes 0 to +2 – 1
16
N/A
int 4 bytes +2 – 1 to - 2
31 31
N/A
unsigned int 4 bytes 0 to +2 – 1
32
N/A
long (also called long int) 4 bytes +2 – 1 to - 2
31 31
N/A
unsigned long 4 bytes 0 to +2 – 1
32
N/A
Floating Point
float 4 bytes Approx. 10–38 to 1038 7 digits
double 8 bytes Approx. 10 to 10
–308 308
15 digits
Boolean
bool 1 byte true, false N/A
The values listed here are only sample values to give you a general idea of how
the types differ. The values for any of these entries may be different on your
system. Precision refers to the number of meaningful digits, including digits in
front of the decimal point.
You create or define a variable by stating its type, followed by one or more
spaces, followed by the variable name and a semicolon. The variable name can
be virtually any combination of letters, but it cannot contain spaces. Legal
variable names include x, J23qrsnf, and myAge. Good variable names tell you what
the variables are for; using good names makes it easier to understand the flow of
your program. The following statement defines an integer variable called myAge:
int myAge;
6
C++ is case sensitive. In other words, uppercase and lowercase letters are
considered to be different. A variable named age is different from Age, which is
different from AGE.
You can create more than one variable of the same type in one statement by
writing the type and then the variable names, separated by commas. For example:
unsigned int myAge, myWeight;
long int area, width, length;
As you can see, myAge and myWeight are each declared as unsigned integer variables.
The second line declares three individual long variables named area, width, and
length. The type (long) is assigned to all the variables, so you cannot mix types in
one definition statement.
You assign a value to a variable by using the assignment operator (=). Thus,
you would assign 5 to width by writing
unsigned short width;
width = 5;
You can combine the steps of creating a variable and assigning a value to it. For
example, you can combine these two steps for the width variable by writing:
unsigned short width = 5;
1.4.3. Constants
Preceding a variable definition by the keyword const makes that variable read-
only (i.e., a symbolic constant). A constant must be initialized to some value
when it is defined. For example:
const int maxSize = 128;
const double pi = 3.141592654;
7
1.4.4. Keywords
Some words are reserved by C++, and you cannot use them as variable names.
These keywords have special meaning to the C++ compiler. Keywords include
if, while, for, and main. A list of keywords defined by C++ is presented in the
following table.
Simple console input is done with the objects cin and cout, all of which are defined
in the library iostream. In order to use this library, your program should contain the
following near the start of the file containing your code:
#include <iostream>
using namespace std;
8
1.5.1. Output Using cout
The values of variables as well as strings of text may be output to the screen
using cout. Any combination of variables and strings can be output. For example,
consider the following code:
cout << "Hello world.\n" << "Welcome to C++.\n";
This statement outputs two strings, one per line. Using cout, you can output any
number of items, each either a string, a variable, or a more complicated
expression. Simply insert a << before each thing to be output. As another example,
consider the following:
cout << numberOfGames << " games played.";
This statement tells the computer to output two items: the value of the variable
numberOfGames and the quoted string " games played.". Notice that you do not
need a separate copy of the object cout for each item output. You can simply list
all the items to be output, preceding each item to be output with the arrow
symbols <<. The previous single cout statement is equivalent to the following two
cout statements:
cout << numberOfGames;
cout << " games played.";
The two < symbols should be typed without any space between them. The arrow
notation << is often called the insertion operator. The entire cout statement ends
with a semicolon.
Notice the spaces inside the quotes in our examples. The computer does not insert
any extra space before or after the items output by a cout statement, which is why
the quoted strings in the examples often start or end with a blank. The blanks
keep the various strings and numbers from running together. If all you need is a
space and there is no quoted string where you want to insert the space, then use
a string that contains only a space, as in the following:
cout << firstNumber << " " << secondNumber;
9
Escape Sequences
If you want to put a backslash, \ , or a quote symbol, " , into a string constant, you
must escape the ability of the " to terminate a string constant by using \" , or the
ability of the \ to escape, by using \\ . The \\ tells the compiler you mean a real
backslash, \ , not an escape sequence; the \" tells it you mean a real quote, not the
end of a string constant.
You should not use any escape sequences other than those provided by the C++
standard. These C++ control characters are listed in the following table.
Sequence Meaning
\n New line
Carriage return (Positions the cursor at the start
\r of the current line. You are not likely to use this
very much.)
(Horizontal) Tab (Advances the cursor to the
\t
next tab stop.)
\a Alert (Sounds the alert noise, typically a bell.)
Backslash (Allows you to place a backslash in
\\
a quoted expression.)
Single quote (Mostly used to place a single
\'
quote inside single quotes.)
Double quote (Mostly used to place a double
\"
quote inside a quoted string.)
\v Vertical tab
\b Backspace
\f Form feed
\? Question mark
10
New Lines in Output
If you wish to insert a blank line in the output, you can output the newline
character \n by itself:
cout << "\n";
Another way to output a blank line is to use endl, which means essentially the
same thing as "\n". So you can also output a blank line as follows:
cout << endl;
Although "\n" and endl mean the same thing, they are used slightly differently; \n
must always be inside quotes, and endl should not be placed in quotes. A good
rule for deciding whether to use \n or endl is the following: If you can include the
\n at the end of a string, then use \n, as in the following:
cout << "Fuel efficiency is " << mpg << " miles per gallon\n";
On the other hand, if the \n would appear by itself as the short string "\n", then
use endl instead:
cout << "You entered " << number << endl;
When the computer outputs a value of type double, the format may not be
what you would like. For example, the following simple cout statement can
produce any of a wide range of outputs:
cout << "The price is $" << price << endl;
11
or it might be
The price is $78.5
It is extremely unlikely that the output will be the following, however, even
though this is the format that makes the most sense:
The price is $78.50
To ensure that the output is in the form you want, your program should contain
some sort of instructions that tell the computer how to output the numbers.
There is an instruction that you can insert in your program to cause numbers that
contain a decimal point, such as numbers of type double, to be output in everyday
notation with the exact number of digits after the decimal point that you specify.
If you want two digits after the decimal point, use the following instruction:
cout.precision(2);
You use cin for input more or less the same way you use cout for output. The
syntax is similar, except that cin is used in place of cout and the arrows point in
the opposite direction. For example, in the following code, the variable
numberOfLanguages was filled by the following cin statement:
cin >> numberOfLanguages;
You can list more than one variable in a single cin statement, as illustrated by the
following:
cout << "Enter the width\n" << "followed by the height.\n";
cin >> width >> height;
When a program reaches a cin statement, it waits for input to be entered from the
keyboard. It sets the first variable equal to the first value typed at the keyboard,
the second variable equal to the second value typed, and so forth. However, the
program does not read the input until the user presses the Return key. This allows
the user to backspace and correct mistakes when entering a line of input.
The following program illustrates reading two variables of type int in the same
program along with a simple calculation. Numbers in the input must be separated
by one or more spaces or by a line break. These delimiting characters are called
whitespace. When you use cin statements, the computer will skip over any number
of blanks or line breaks until it finds the next input value. Thus, it does not matter
12
whether input numbers are separated by one space or several spaces or even a
line break. This same behavior holds when you are reading data into a string.
#include <iostream>
#include <string>
using namespace std;
int main( )
{
int actualAge;
int humanAge;
cout << "How many years old is your dog?" << endl;
cin >> actualAge;
humanAge = actualAge * 7;
cout << “dog with age of “ << actualAge << “is approximately "
<< "equivalent to a " << humanAge << " year old human." << endl;
return 0;
}
As in most other languages, C++ allows you to form expressions using variables,
constants, and the following operators:
Arithmetic Operators
+ Addition
- Subtraction
/ Division
* Multiplication
% Remainder (Modulus)
Bitwise Operators
~ Bitwise Negation (unary) – flip 0 to 1 and 1 to 0
& Bitwise And
| Bitwise Or
^ Bitwise Exclusive Or
>> Right Shift by right hand side (RHS)
<< Left Shift by RHS
13
Relational Operators
== Equal
!= Not Equal
> Greater Than
< Less Than
>= Greater or Equal
<= Less or Equal
Logical Operator
! Boolean NOT (unary)
&& Boolean AND
|| Boolean OR
Bitwise Operations
14
/* Bitwise operations - Optimized Output */
#include <iostream>
#include <iomanip>
using namespace std;
int main ( )
{
unsigned int a , b , c ;
a = 0x0ff0 ;
b = 0xff00 ;
c = a << 4 ;
cout << hex;
cout << setw(4) << setfill('0') << a << " << "
<< setw(4) << setfill('0') << 4 << " = "
<< setw(4) << setfill('0') << c << endl;
c = a >> 4 ;
cout << setw(4) << setfill('0') << a << " >> "
<< setw(4) << setfill('0') << 4 << " = "
<< setw(4) << setfill('0') << c << endl;
c=a&b;
cout << setw(4) << setfill('0') << a << " & "
<< setw(4) << setfill('0') << 4 << " = "
<< setw(4) << setfill('0') << c << endl;
c=a|b;
cout << setw(4) << setfill('0') << a << " | "
<< setw(4) << setfill('0') << 4 << " = "
<< setw(4) << setfill('0') << c << endl;
c=a^b;
cout << setw(4) << setfill('0') << a << " ^ "
<< setw(4) << setfill('0') << 4 << " = "
<< setw(4) << setfill('0') << c << endl;
c = ~a ;
cout << "~" << setw(4) << setfill('0') << a << " = "
<< setw(4) << setfill('0') << c << endl;
return 0;
}
Mixing Types
All the arithmetic operators can be used with numbers of type int, numbers of
type double, and even with one number of each type. However, the type of the
value produced and the exact value of the result depend on the types of the
numbers being combined. If both operands (that is, both numbers) are of type int,
15
then the result of combining them with an arithmetic operator is of type int. If one
or both of the operands are of type double, then the result is of type double. For
example, if the variables baseAmount and increase are of type int, then the number
produced by the following expression is of type int:
baseAmount + increase
However, if one or both of the two variables are of type double, then the result is
of type double.
More generally, you can combine any of the arithmetic types in expressions. If
all the types are integer types, the result will be the integer type. If at least one of
the sub expressions is of a floating-point type, the result will be a floating-point
type.
C++ tries its best to make the type of an expression either int or double, but if the
value produced by the expression is not of one of these types because of the
value’s size, a suitable different integer or floating-point type will be produced.
The ++ in the name of the C++ language comes from the increment operator,
++ the increment operator adds 1 to the value of a variable. The decrement
operator, -- , subtracts 1 from the value of a variable. They are usually used with
variables of type int, but they can be used with any numeric type. If n is a variable
of a numeric type, then n++ increases the value of n by 1 and n-- decreases the
value of n by 1. So n++ and n-- (when followed by a semicolon) are executable
statements. For example, the statements
int n = 1, m = 7;
n++
cout << "The value of n is changed to " << n << "\n";
m- -;
cout << "The value of m is changed to " << m << "\n";
The expression n++ evaluates to the value of the variable n, and then the value of
the variable n is incremented by 1. If you reverse the order and place the ++ in
front of the variable, the order of these two actions is reversed. The expression
16
++n first increments the value of the variable n and then returns this increased
value of n. For example, consider the following code:
int n = 2;
int valueProduced = 2*(++n);
cout << valueProduced << "\n";
cout << n << "\n";
This code is the same as the previous piece of code except that the ++ is before
the variable, so this code will produce the following output:
6
3
Notice that the two increment operators in n++ and ++n have the same effect on a
variable n: They both increase the value of n by 1. But the two expressions
evaluate to different values.
Remember, if the ++ is before the variable, the incrementing is done before the
value is returned; if the ++ is after the variable, the incrementing is done after the
value is returned.
17
Precedence rules
is evaluated by first doing the multiplication and then the addition. It is usually
best to include the parentheses, even if the intended order of operations is the one
dictated by the precedence rules.
++ postfix increment operator (placed after the variable) Highest precedence
-- postfix decrement operator (placed after the variable) (done first)
++ prefix increment operator (placed before the variable)
-- prefix decrement operator (placed before the variable)
! not
- unary minus
+ unary plus
& address of
~ complement
* multiply
/ divide
% remainder (modulo)
+ addition
- subtraction
<< insertion operator (output), bitwise left shift
>> extraction operator (input), bitwise right shift
< less than <= less than or equal
> greater than >= greater than or equal
== equal
!= not equal
& bitwise and
^ bitwise exclusive or
| bitwise or
&& and
|| or
= assignment
+= add and assign -= subtract and assign
*= multiply and assign %= modulo and assign
<<= << and assign >>= >> and assign
Lowest precedence
&= & and assign ^= ^ and assign
(done last)
|= | and assign /= division and assign
18
1.7. Control Structures
Notice that some of the operators are spelled with two symbols, for example, ==
, != , <= , or >= . Be sure to notice that you use a double equal == for the equal sign
and that you use the two symbols != for not equal.
You can combine two comparisons using the && operator. For example, the
following expression is true provided x is greater than 2 and x is less than 7:
(2 < x) && (x < 7)
When two comparisons are connected using an &&, the entire expression is true,
provided both of the comparisons are true; otherwise, the entire expression is false.
You can also combine two comparisons using the || operator. For example, the
following is true provided y is less than 0 or y is greater than 12:
(y < 0)||(y < 12)
When two comparisons are connected using a ||, the entire expression is true
provided that one or both of the comparisons are true; otherwise, the entire
expression is false.
You can negate any Boolean expression using the ! operator. If you want to
negate a Boolean expression, place the expression in parentheses and place the !
operator in front of it. For example: !(x < y) means “ x is not less than y .”
Truth Tables
19
individual values of true or false are then combined according to the rules in the
tables
Exp ! (Exp)
false true
true false
The if statement provides a way of expressing this, the general form of which is:
if (<expression>) <statement> // simple form with no {}'s or else clause
For example, when dividing two values, we may want to check that the
denominator is nonzero:
if (count != 0)
average = sum / count;
20
Example:
if (balance > 0) {
interest = balance * creditRate;
balance += interest;
}
21
1.7.3. The Switch Statement
First expression (called the switch tag) is evaluated, and the outcome is compared
to each of the numeric constants (called case labels), in the order they appear,
until a match is found. The statements following the matching case are then
executed.
Each case may be followed by zero or more statements (not just one statement).
Execution continues until either a break statement is encountered or all
intervening statements until the end of the switch statement are executed.
The final default case is optional and is exercised if none of the earlier cases
provide a match.
For example, suppose we have parsed a binary arithmetic operation into its three
components and stored these in variables operator, operand1, and operand2.
The following switch statement performs the operation and stored the result in
result.
22
switch (operator) {
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
result = operand1 / operand2;
break;
default:
cout << "unknown operator: " << ch << '\n';
break;
}
23
Because case 'x' has no break statement, when this case is satisfied, execution
proceeds to the statements of the next case and the multiplication is performed.
It should be obvious that any switch statement can also be written as multiple if-
else statements. The above statement, for example, may be written as:
if (operator == '+')
result = operand1 + operand2;
else if (operator == '-')
result = operand1 - operand2;
else if (operator == 'x' || operator == '*')
result = operand1 * operand2;
else if (operator == '/')
result = operand1 / operand2;
else
cout << "unknown operator: " << ch << '\n';
The classic example of the ternary operator is to return the smaller of two
variables. Every once in a while, the following form is just what you needed.
Instead of...
if (x < y) {
min = x;
}
else {
min = y;
}
24
1.7.5. The While Loop
For example, suppose we wish to calculate the sum of all numbers from 1 to some
integer denoted by n. This can be expressed as:
i = 1;
sum = 0;
while (i <= n)
sum += i++;
For n set to 5, the following table provides a trace of the loop by listing the values
of the variables involved and the loop condition.
The do loop is similar to the while statement, except that its body is executed
first and then the loop condition is examined. The general form of the do
statement is:
25
do {
<statements>
} while (<expression>)
The do loop is less frequently used than the while loop. It is useful for situations
where we need the loop body to be executed at least once, regardless of the loop
condition.
For example, suppose we wish to repeatedly read a value and print its square, and
stop when the value is zero. This can be expressed as the following loop:
do {
cin >> n;
cout << n * n << '\n';
} while (n != 0);
The for loop is similar to the while loop, but has two additional components:
an expression which is evaluated only once before everything else, and an
expression which is evaluated once at the end of each iteration. The general form
of the for statement is:
for (<expression1>; <expression2>; <expression3>) {
<statements>;
}
26
The most common use of for loops is for situations where a variable is
incremented or decremented with every iteration of the loop. The following for
loop, for example, calculates the sum of all integers from 1 to n.
sum = 0;
for (i = 1; i <= n; ++i)
sum += i;
C++ allows the first expression in a for loop to be a variable definition. In the
above loop, for example, i can be defined inside the loop itself:
for (int i = 1; i <= n; ++i)
sum += i;
Contrary to what may appear, the scope for i is not the body of the loop, but the
loop itself. Scope-wise, the above is equivalent to:
int i;
for (i = 1; i <= n; ++i)
sum += i;
Any of the three expressions in a for loop may be empty. For example, removing
the first and the third expression gives us something identical to a while loop:
for (; i != 0;) // is equivalent to: while (i != 0)
something;
Removing all the expressions gives us an infinite loop. This loop's condition is
assumed to be always true:
for ( ; ; ) // infinite loop
something;
For loops with multiple loop variables are not unusual. In such cases, the comma
operator is used to separate their expressions:
for (i = 0, j = 0; i + j < n; ++i, ++j)
something;
Because loops are statements, they can appear inside other loops. In other words,
loops can be nested. For example,
for (int i = 1; i <= 3; ++i)
for (int j = 1; j <= 3; ++j)
cout << '(' << i << ',' << j << ")\n";
27
Produces the product of the set {1, 2, 3} with itself, giving the output:
(1,1)
(1,2)
(1,3)
(2,1)
(2,2)
(2,3)
(3,1)
(3,2)
(3,3)
The continue statement terminates the current iteration of a loop and instead
jumps to the next iteration. It applies to the loop immediately enclosing the
continue statement. It is an error to use the continue statement outside a loop.
In while and do loops, the next iteration starts from the loop condition. In a for
loop, the next iteration starts from the loop’s third expression.
For example, a loop which repeatedly reads in a number, processes it but ignores
negative numbers, and terminates when the number is zero, may be expressed as:
do {
cin >> num;
if (num < 0)
continue;
if (num >= 0) {
// process num here...
}
} while (num != 0);
28
A variant of this loop which reads in a number exactly n times (rather than until
the number is zero) may be expressed as:
for (i = 0; i < n; ++i) {
cin >> num;
if (num < 0)
continue; // causes a jump to: ++i
When the continue statement appears inside nested loops, it applies to the loop
immediately enclosing it, and not to the outer loops. For example, in the
following set of nested loops, the continue applies to the for loop, and not the while
loop:
while (more) {
for (i = 0; i < n; ++i) {
cin >> num;
if (num < 0) continue; // causes a jump to: ++i
// process num here...
}
}
A break statement may appear inside a loop (while, do, or for) or a switch
statement. It causes a jump out of these constructs, and hence terminates them.
Like the continue statement, a break statement only applies to the loop or switch
immediately enclosing it.
It is an error to use the break statement outside a loop or a switch. For example,
suppose we wish to read in a user password, but would like to allow the user a
limited number of attempts:
for (i = 0; i < attempts; ++i) {
cout << "Please enter your password: ";
cin >> password;
if (Verify(password)) // check password for correctness
break; // drop out of the loop
29
Here we have assumed that there is a function called Verify which checks a
password and returns true if it is correct, and false otherwise.
1.8. Arrays
An array variable is defined by specifying its dimension and the type of its
elements. For example, an array representing 5 scores (each being an integer) may
be defined as:
int scores[5];
The individual elements of the array are accessed by indexing the array. The first
array element always has the index 0. Therefore, scores[0] and scores[4] denote,
respectively, the first and last element of scores. Each of scores elements can be
treated as an integer variable.
Scores (Values) 13 52 75 80 42
Index 0 1 2 3 4
30
When a complete initializer is used, the array dimension becomes redundant,
because the number of elements is implicit in the initializer. The first definition of
nums can therefore be equivalently written as:
int nums[] = {5, 10, 15}; // no dimension needed
Processing of an array usually involves a loop which goes through the array element
by element. The following program illustrates how to calculate the average of an
array of integers.
#include <iostream>
using namespace std;
int main()
{
const int size = 3;
int nums[size] = { 5, 10, 15 };
double average = 0;
An array may have more than one dimension (i.e., two, three, or higher). The
organization of the array in memory is still the same (a contiguous sequence of
elements), but the programmer’s perceived organization of the elements is
different.
31
For example, suppose we wish to represent the average seasonal temperature for
a three major Egypt cities
… 26 34 22 17 24 32 19 13 28 38 25 20 …
1st row 2nd row 3rd row
32
uses nested loops instead of a single loop. The following program illustrates how
to find the highest temperature in seasonTemp.
#include <iostream>
using namespace std;
int main()
{
const int rows = 3;
const int columns = 4;
int seasonTemp[rows][columns] = {
{26, 34, 22, 17},
{24, 32, 19, 13},
{28, 38, 25, 20}
};
int highest = 0;
1.9. Pointers
33
The symbol & is the address operator; it takes a variable as argument and returns
the memory address of that variable. The effect of the above assignment is that the
address of num is assigned to ptr1. Therefore, we say that ptr1 points to num.
prt1 num
1.10. Functions
• The function parameters (also called its signature). This is a set of zero
or more typed identifiers used for passing values to and from the function.
• The function return type. This specifies the type of value the function
returns.
A function which returns nothing should have the return type void.
The body of a function contains the computational steps (statements) that comprise
the function.
Using a function involves ‘calling’ it. A function call consists of the function name
followed by the call operator brackets ‘()’, inside which zero or more comma-
separated arguments appear. The number of arguments should match the number
34
of function parameters. Each argument is an expression whose type should match
the type of the corresponding parameter in the function interface.
When a function call is executed, the arguments are first evaluated and their resulting
values are assigned to the corresponding parameters. The function body is then
executed. Finally, the function return value (if any) is passed to the caller.
Since a call to a function whose return type is non-void yields a return value, the call
is an expression and may be used in other expressions. By contrast, a call to a
function whose return type is void is a statement.
The following code shows the definition of a simple function which raises an integer
to the power of another, positive integer.
int Power (int base, unsigned int exponent)
{
int result = 1;
return result;
}
The following code shows how this function is called. The effect of this call is that
first the argument values 2 and 8 are, respectively, assigned to the parameters base
and exponent, and then the function body is evaluated.
#include <iostream>
using namespace std;
35
#include <iostream>
using namespace std;
return result;
}
At the beginning, the understanding of the different methods for function calls
is extremely important. There are three methods for function calls:
1) Call by Value
2) Call by Address
3) Call Reference
Call by Value
36
Will not have the desired effect. Although, inside swap0, the values of x and y are
exchanged, the arguments i and j are used only once namely to give the parameters
x and y their initial values. After exchanging these, the new values of x and y are
not passed back to i and j even if i, j are named x & y in the calling module.
Call by Address
In this method, the calling module sends the address of the variables to the
function parameters as follows:
void swap1(int *x, int *y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
}
Then, with address of the two int variables i and j, the call:
swap1( &i , &j) ;
Will have the desired effect. Inside swap1, the pointer values of x and y are used
to exchange, the arguments i and j of the calling module.
Call by Reference
Note the use of the ampersand &. When used for parameters as above, it indicates
that not only the values of the arguments but also their addresses are passed to
the function. The variables x and y are no longer local variables but rather
alternative notations for arguments, such as i, j in the call:
swap2 ( i , j) ;
37
Exercises
1 Consider the following function:
int secret(int m, int n) {
int temp = 0;
for (int i = 1; i < abs(n); i++)
temp = temp + i * m;
return temp;
}
a. What is the output of the following C++ statements?
i. cout << secret(3, 6) << endl;
ii. cout << secret(5, -4) << endl;
b. What does the function secret do?
2 Write a C++ program that declares an array alpha of 50 components of type
double. Initialize the array so that the first 25 components are equal to the
square of the index variable, and the last 25 components are equal to three
times the index variable. Output the array so that 10 elements per line are
printed.
3 A company hired 10 temporary workers who are paid hourly and you are
given a data file that contains the last name of the employees, the number of
hours each employee worked in a week, and the hourly pay rate of each
employee. You are asked to write a program that computes each employee’s
weekly pay and the average salary of all the workers. The program then
outputs the weekly pay of each employee, the average weekly pay, and the
names of all the employees whose pay is greater than or equal to the average
pay. If the number of hours worked in a week is more than 40 hours, then
the pay rate for the hours over 40 is 1.5 times the regular hourly rate. Use
two parallel arrays: a one-dimensional array to store the names of all the
employees, and a two-dimensional array of 10 rows and 3 columns to store
the number of hours an employee worked in a week, the hourly pay rate, and
the weekly pay. Your program must contain at least the following
functions—a function to read the data from the file into the arrays, a
function to determine the weekly pay, a function to output the names of all
the employees whose pay is greater than or equal to the average weekly pay,
and a function to output each employee’s data.
38
Chapter 2
Classes and objects
39
The C++ language as an example for an Object Oriented Programming, will be
discussed. Advantages and disadvantages of the Object Oriented Programming, will
also be demonstrated. The discussion in this note focuses on the basic features in
C++ language (Classes and Objects).
Instead of using only individual variables of the simple types you know, we can
group some variables together into a class, which in some other languages is called
a record. A class is a generalization of a structure which is a language concept that,
in a more restricted form, is also available in C. Compared with C structures, classes
have two important extensions:
1. Classes (and C++ structures) can have functions as their members. These
functions operate on the data members; this combination of functions and data
in classes is called Encapsulation.
Although the first five sections of this chapter are mainly about structures, they form
the basis of our discussion of the class concept; we will not use anything specific for
classes until section 1.8.
Classes in C++ can be regarded as a means of extending the language we are using.
They enable us to define our own types, along with operators that can be applied to
object of these types.
As far as data is concerned, classes and structures are somewhat similar to arrays,
but their elements, normally called members, are not identified by subscripts but by
member names.
As an example suppose we have objects for each of which we want to store its code
number, its name, its weight, and its length. We can define a structure type for these
objects as follows:
struct article
{
int code;
char name [20];
float weight, length;
};
40
A structure is a class with only public member. As we will see Section 2.5, classes
can also have members that are not public. The above fragment is equivalent to,
class article
{
public:
int code;
char name [20];
float weight, length;
};
Since every structure is a class, anything can be done with a structure can also be
done with a class (but the reverse is not true). As long as all members are public, as
in class article, we normally use the keyword struct, rather than class, in this note.
Consequently, in our discussion the term structure often occurs where we could have
the more general word class instead. So remember, when dealing with structure we
are in fact discussing classes.
In the above two fragments, only struct, class and public are keywords; the other names,
article, code, name, weight, and length are chosen by ourselves. This structure
declaration does not declare any variables. Yet it is useful, because it enables us to
use the type article, in the same way as standard types, such as int, float, and so on.
For example, we can now declare (and, at the same time, ‘define’) the two structure
variables s and t:
article s, t;
In C++ the keyword struct need not to be not used in this line, while in C we would
have to write,
struct article s, t;
The short form in C++, without struct, is more similar to declarations that we already
know, such as,
float x, y;
We could have combined the declaration of type article and that of the variables s and
t by writing
struct article
{
int code;
char name [20];
float weight, length;
} s, t;
41
In this case we should regard the whole part of the form
struct article {…};
As the type, comparable with the keyword float, for example. If we use this
combined declaration, we can omit the name article, although that would deprive us
of a shorthand notation for this new type. Remember that in large programs we often
need to use the same class type several times; in particular, the type name article
may occur in several functions, while the variables s and t are local to one of these
functions. It is then very convenient to separate the (global) declaration of this
structure type from the (local) variable declarations. If the program consists of
several modules and some class type is to be used in more than one, it is highly
recommended to defined that type in a header file and to use a # include line for this
file in all modules in which that type is used.
After declaring the structure variables s and t in one of the above ways, we can use
them as follows:
s.code =123;
strcpy (s.name, “pencil”);
s.weight = 12.3;
s.length = 150.7;
t = s;
This program fragment assigns values to all members of s; then these are all copied
to t. The latter is remarkable: recall that we cannot use a single assignment to copy
an entire array. We can copy a structure, even if this has an array among its members,
as is the case here.
The above fragment shows that structure members are written with a dot between
the structure-variable name and the member tag. These members are in fact normal
variables. We can use them in the same way as other variables. For example, reading
the code and name members can be done by writing
cin >> s.code >> s.name;
If we want to initialize array table, we can do this, for example, as follows:
article table [2] = {
{123, “pencil”, 12.3, 150.7},
{246, “pen”, 20.6, 147.0}
};
42
As with arrays, we can omit the innermost braces and write:
article table [2] = { 123, “pencil”, 12.3, 150.7,
246, “pen”, 20.6, 147.0 };
The former way of initializing is more logical and should therefore be preferred to
the latter. It also enables us to omit trailing values for some array elements; for
example, the weight and the length members of both table [0] and table [1] are given
the initial value 0 if we write:
article table [2] = {
{123, “pencil”},
{246, “pen”}
};
struct stack
{
char s[max_len];
int top;
};
43
Boolean empty (stack *stk)
{
return(stk -> top == 0);
}
main()
{
stack s1;
static char str [40]={“My name is Ahmed”};
int i=0;
reset(&s1);
while (str[i]) // push into stack
if (!full(&s1))
push(str[i++] , &s1) ;
44
2.3. Classes as arguments and as Return Values
With
p -> length
45
As we have seen, the use of a structure address (&s) as an argument, which acts
as the initial value of the corresponding pointer parameter (p), is very efficient. It
also has the advantage that we can modify the structure (s) the address of which
is given as an argument.
We can also use addresses of structures as return values. Suppose that we want
to write (and use) a function, newcopy, to create a new structure, the type article
and to fill it with the contents of a structure, the address of which is given as an
argument. The address of the created structure is the value to be returned by
newcopy. We can do this as follows:
#include <stdlib.h>
article *pobject, s, …;
…
pobject = newcopy (&s);
(*pobject).code = 30;
46
obj a short while ago may already be in use for other purposes, although its start
address is still available! (This is similar to entering a house the key of which was
given to us some time ago by the previous owner who no longer lives there.) One
way of avoiding any possibility of making such mistakes is by copying the entire
structure instead of only its address, as we will discuss now.
struct article
{
int code;
char name [20];
float weight, length;
};
article largerobj (article x)
{
x.code++;
x.weight *=2;
x.length *=2;
return x;
}
main ()
{
article s = {246, “pen”, 20.6 , 147.0}, t;
t = largerobj (s);
cout << t.code << ‘ ‘ << t.name << ‘ ‘ << t.weight << ‘ ‘ << t.length << endl;
}
47
In the function largerobj, parameter x is a copy of argument s. In the return
statement, x is again copied. In contrast to our previous newcopy, which returned
the address of a local variable, largerobj is correct because it returns a copy of a
‘local variable’. Ad we know, parameter x is in fact a local variable; the return
statement would also be correct if x were a real local variable, declared in function
largerobj in the normal way.
Since structure members can have any type, there can be a pointer member p
pointing to another structure that is of the same type as the one of which p is a
member:
struct element {
int num ;
element * p;
};
With three given article variables s, t, and u we can write, for example:
u = heavier (s, t);
heavier (s, t). weight = 0;
After the selected object has been saved by coping it into u, its weight member is
set to zero.
48
2.4. Unions
With structures, all members are in memory at the same time, so the amount of
memory used by a structure is at least the sum of the amounts of memory used by
its members. In contrast to this, there is another special case of the class concept,
called union. The notation of unions is similar to that of structures. However, union
member overlay each other. The amount of memory a union takes is only as large as
that of its largest member, which implies that there is only one member actually
present at a time. For example, after writing:
union intflo {
int i;
float x;
} u;
We can use u.i and u.x in the same way as if the above keyword union were replaced
with struct. However, the members i and x share memory space, so by executing
u.i = 123;
u.x = 98.7;
The second statement destroys the value just assigned to u.i. The above declaration
declares not only the variable u but also the type intflo. For example, we can now
declare
intflo v;
Unions can be useful if we want to store only one of their members. In that case,
they are more economical with memory space, especially if they are array elements.
However, it is the programmer’s responsibility to remember which of the members
have been used. One way of realizing this is to use a structure with two members: a
‘flag’ and a union, the flag being a code for the current member choice in the union.
For example, we can write:
struct {char flag; union {int I; float x;} num;} a[1000];
…
if { … } { a[i].flag = ‘I’; a[i].num.i =123 ;}
else{ a[i].flag = ‘F’; a[i].num.x = 98.7; }
…
if (a[k].flag == ‘I’)
cout << “Integer value; “ << a[k].num.i; else
if (a[k].flag == ‘F’)
cout << “Float value: “ << a[k].num.x; else
cout << “unknown flag”;
49
Example: program using union to make array which includes different record
sizes one for an employee and another for a student
#include <iostream>
using namespace std;
struct Student
{
int code;
char name[80];
float grade1;
float grade2;
int flag;
}; // struct student
struct Employee
{
int id;
char name[80];
float salary;
int flag;
}; // struct employee
struct Department {
int flag;
union {
Student s;
Employee e;
} person; // End of Union
};
int main()
{
int f;
const int length = 10;
Department d[length];
cin >> f;
d[i].flag = f;
switch (f)
{
50
case 0:
cout << "Please enter Student Code: ";
cin >> d[i].person.s.code;
cout << "Please enter Student Name: ";
cin >> d[i].person.s.name;
cout << "Please enter Student First Grade: ";
cin >> d[i].person.s.grade1;
cout << "Please enter Student Second Grade: ";
cin >> d[i].person.s.grade2;
break;
case 1:
cout << "Please enter Employee ID: ";
cin >> d[i].person.e.id;
cout << "Please enter Employee Name: ";
cin >> d[i].person.e.name;
cout << "Please enter Employee Salary: ";
cin >> d[i].person.e.salary;
break;
default:
cout << "Error, Invalid!" << endl;
cout << "Press any key to exit" << endl;
cin.get();
return 1;
break;
} // end of the switch
} // end of for
51
cout << "ID: " << d[i].person.e.id << endl;
cout << "Name: " << d[i].person.e.name << endl;
cout << "Salary: " << d[i].person.e.salary << endl;
cout << "-----------------------" << endl << endl;
break;
} // end of switch
} // end of for
return 0;
} // end of main function
Normally, the smallest unit of memory used for variables is one byte. However,
it is possible for structures to have members that are smaller than one byte. As their
sizes are expressed in bits, they are called bit fields. In the following example we
have a structure s, with bit fields b4, b1, b2, consisting of 4, 1, and 2 bits, respectively.
Besides, there is a char member ch:
struct example
{
unsigned b4:4, b1:1, b2:2;
char ch;
} s;
Bit fields are similar to other structure members, with one exception: since several
of them may be located in the same byte, we cannot uniquely identify them by their
addresses, and we must therefore not apply the ‘address of’ operator & to them.
We can write
s.b4 = 7;
But we have to note that the value of s.b4 must not be preceded by the unary operator
&.
This section is about an important C++ language aspect that is not available in C.
Although we will again use a structure in our example, it applies to classes in general.
When declaring structures types, we usually also define functions that manipulate
variables of these types. In complex programs, we often work with many structure
types and with several functions for each of these. As this might easily lead to
confusion, there is a need for a language facility to group things together in an
52
orderly way. The C++ language offers this facility. In order to make clear that certain
functions belong to the structures of some type, we make those functions members
of these structures. Since member functions are declared (or even defined) inside
classes, the technical term for this idea is encapsulation .for example, in program
VEC1 the
Functions setvec and printvec are defined inside the structure type vector:
// VEC1: A structure in which two functions are defined.
#include <iostream>
using namespace std;
main( )
{
struct vector
{
float xx,yy;
void setvec( float x, float y) {xx=x; yy=y; }
void printvec ( ) { cout <<xx << ‘ ‘ << yy<< end1; }
};
vector u, v;
u.setvec(1.0, 2.0) ;
u.printvec( );
v.setvec(3.0, 4.0);
v.pritvec( );
}
Notice the four function calls in this program. In
u.setvec(1.0, 2.0);
The variable name u and the dot at the beginning indicate that function setvec is to
be applied to the variable u. The notations u.setvec (for the function member setvec)
has the same form as u.xx (for the data member xx).
As a result of the calls u.printvec( ) and v.printvic( ), program VEC1 produces the
following output:
1 2
3 4
53
In program VEC1, the functions setvec and printvec have been defined inside the
structure type. These function definitions are therefore dealt with as if we had used
the inline keyword. Instead, we could only have declared them there and defined
them outside the structure, as program VEC2:
/ / VEC2: A structure in which two functions are declared.
// These functions are defined outside the structure.
#include <iostream>
using namespace std;
struct vector
{
float xx, yy;
void setvec ( float x, float y );
void printvec ( );
};
main ( )
{
vector u, v;
u.setvec (1.0, 2.0); u.printvec( );
v.setvec (3.0, 4.0); v.printvec( );
}
54
The name this is a C ++ keyword. In a member function, it is always available as a
pointer to the object specified in the call to that function. For example, if printvec is
called as
u.printvec
Then, during the execution of this function, this is a pointer to u, so we can write this
-> xx to denote the structure member u.xx. Although such use of this would be
superfluous in program VEC2, this keyword is useful in other cases, as we will see in
section 2.9.
You may have noticed that in program VEC1 type vector is local to function main,
whereas it is global in program VEC2. In the latter program we cannot make type
vector local to function main, because we need notation vector : : outside this function.
Another point to be noted is that although notations such as
u.xx
u.printvec( )
Look very similar, there is an important distinction in the implementation of data
members and functions: each of the objects u and v has its own data member xx, but
there is only one function printvec, shared by these two objects.
In programs VEC1 and VEC2, we could have used the structure members xx and yy
in the main functions, if we had wanted to do so. We did not, because access to these
members was delegated to the member functions setvec and printvec. This is a
situation, which frequently occurs. Especially in more complicated applications of
structure types, we feel a need to make a distinction in the access control of members
(applying to both data and functions):
1. Some members, such as the functions setvec and printvec in Section 2.6, should
be public. They belong to the interface; the ‘user” needs only to know what
can be done with them.
When we use the keyword struct, as we have done so far, all members are implicitly
public. Instead of struct, we can use the keyword class, together with the keywords
55
public and private to indicate which of the above two cases applies. (If we omit these
last two keywords, we have no access to any class member at all, because class
members are private by default.) Apart from this distinction in access control, which
applies to classes, not to structures, structures and classes are similar. In a class
declaration, we insert public: and private:, including the colons, as demonstrated by
program VEC3, which has been derived from program VEC2 in Section 2.6.
/ / VEC3: A class with private members xx and yy.
#include <iostream>
using namespace std;
class vector {
private:
float xx, yy;
public:
void setvec(float x, float y) ;
void printvec( ) ;
};
main( ) {
vector u, v;
u.setvec(1.0, 2.0) ;
u.printvec();
v.setvec(3.0, 4.0) ;
v.printvec();
}
void vector : :setvec(float x, float y) {
xx = x; yy = y;
}
void vector : : printvec() {
cout << xx << ‘ ‘ << yy << end1:
}
Since class members are by default private, we could have omitted the keyword
private in VEC3. It is used here to make the program more readable. However, we
could not have omitted it if the order of the class members had been different, as in
class vector {
public :
void setvec( float X, float y ) ;
void printvec( ) ;
private :
float xx, yy;
};
56
The only essential difference between the programs VEC2 and VEC3 is that the member
xx and yy are public in VEC2 while they are private in VEC3. For example, using u.xx in
the main function is possible in VEC2, not in VEC3. If in VEC3 we replaced private with
public (which would make the second occurrence of public superfluous), the class
vector in VEC3 would be equivalent to the structure vector in VEC2. In general, the form
struct name { … }
is equivalent to
class name { public: … }
Except for making a distinction between public and private members, everything that
can be done with classes can also be done with structures, that is, as long as we are
programming in C++. In C, we have structures (no classes) but these cannot contain
function members as they can in C++. Thus, C++ structures offer more facilities than
C structures. At the same time, C++ classes are generalizations of C++ structures.
From now on we will discuss classes, bearing in mind that all facilities for classes
also apply to structures as long as there are only public members.
In the definition of a member function outside its class, the scope resolution
operator :: is immediately followed by the function name. For a function that
returns a pointer, this implies that the asterisk is to be written in front of the class
name preceding this operator, as in
Char *classname::myalloc( )
{
…
}
Char* classname::myalloc ( )
Char * classname::myalloc ( )
Char*classname::myalloc ( )
It would not be correct if the asterisk were written between :: and myalloc.
57
2.7.2. Implementation of the Stack Example Using the Member Function
Concepts
Example 1 declares and defines the member function inside the structure
struct stack
{
//data representation
char s[max_len];
int top ;
// Declaration
stack data ;
stack *ptr_data = &data;
//Manipulation
data.reset( );
Example 2 declares the member function inside the structure and defines it
outside the structure
struct stack
{
//data representation
char s[max_len];
int top ;
// operations represented as member functions
void reset ( ) { top = 0 ;}
void push ( char c) ; // function prototype
…
};
58
void stack::push (char c) // function definition
{
top++ ;
s[top] = c;
}
Remarks:
59
r 3 Variable Length Sequence
0 1 2
0 1 2 3 4
The above figure shows two objects, r and s, of such a class type, along with two
sequences of three and five integers, respectively. Now suppose we have declared
class type row with only the two members ptr and len, mentioned above. Then the
declaration on the second line of the function:
void tworows ( )
{
row r, s;
…
}
will create the two objects r and s, excluding the integer sequences. In order to
allocate memory space for these sequences, we could insert:
r.ptr = new int [3]; r.len = 3;
s.ptr = new int [5]; s.len = 5;
In this function. When the function is left, the memory space is automatically
released for r and s, but not for the two integer sequences pointed to. It would
therefore be necessary to insert
delete r.ptr;
delete s.ptr;
At the end of function tworows. All this is not particularly convenient, and it is a
potential source of programming errors. It would be much better if we could arrange
for all required memory space to be automatically allocated and released. We can do
60
this by including both a constructor and destructor in the class row, as is done in
program CONSTR:
/ / CONSTR: Demonstration of a constructor and destructor.
#include <iostream>
using namespace std;
class row
{
private:
int *ptr, len;
public:
row(int n=3) {
ptr = new int [n]; len = n;
for (int I=0; I<n; I++) ptr [I] = 10 * I;
}
~row ( ) {delete ptr;}
void printrow (char *str);
};
void row::printrow (char *str)
{
cout <<str;
for (int I=0; I<len; I++) cout << ptr [I] << ‘ ‘;
cout << end1;
}
void tworows ( )
{
row r, s(5);
r.printrow (“r: “);
s.printrow(“s: “);
}
main ( )
{
tworows();
}
Class row has not only the two data members ptr and len but also the function
members row( ), ~row( ) and printrow( ). Since the first of these is named after the class
row, it is the constructor of this class. Analogously, the name of the destructor is
formed by writing the tilde-symbol ~, followed by the class name. When defining
constructors and destructors, we do not write void, int, or any other type at its
beginning. Constructors and destructors must not contain return-statements. For the
61
objects r and s of the class row, the constructor row( ) is implicitly called when they
are created, the destructor ~row( ) when they cease to exist. In function tworows( ),
both the constructor is called when r and s are created in their declarations, and the
destructor on return from tworows to main.
Thanks to these implicit calls, memory for the integer sequences is automatically
allocated and released in the same way as it is for the (smaller) objects r and s.
In this example, the constructor row has a default argument 3, which applies to r,
since no argument is given for it in the declaration
row r, s(5);
For s, however, the argument 5 is given. As a result of this declaration, the
constructor row( ) is executed twice, first with the default argument 3 and then with
argument 5. In the above declaration, it is not allowed to replace r with r( ).
You may wonder what would happen if we omitted the destructor ~row( ) in program
CONSTR. Apparently, the program would run correctly. However, if function tworows
were called a great many times, we might run out of memory, since without the
destructor the memory for the integer sequences, allocated by the constructor, would
not be released until program termination.
In our above discussion, we have considered r and s to be limited to the two data
members ptr and len of class row. Now that the integer sequences pointed to (as
illustrated in the previous figure) are automatically created and destroyed, we would
rather consider these to be part of r and s. This point of view makes it easier to think
in term of abstract data types: we simply regard objects of type row of integers,
without bothering about implementation details.
Remark: Constructors and dynamically created objects
In program CONSTR, the constructor row( ) was invoked when the objects r and s were
created by the declaration
row r, s(5);
Object can also be created by dynamic memory allocation. In our example, we could
write
row *p;
Which would not immediately lead to a call to the constructor row( ). After all, this
declaration causes memory to be allocated only for the pointer p, not for any objects
that p may point to later. We can dynamically allocate memory in two ways, either
62
by using the C++ operator new or by calling the (older) function malloc. There is,
however, an important distinction between these two methods. If we write
p = new row;
The constructor row( ) is called; this is not the case if we call malloc instead, writing
p = (row *)malloc(sizeof(row));
Analogously, the destructor ~row( ) is called by delete p, not by free(p). [Recall that
delete is the counterpart of new, and free( ) that of malloc()].
New delete
+ - * / % ^ & | -
! = > < += -= *= /= %=
^= &= |= << >> >>= <<= |== !=
<= >= && || ++ -- ‘ ->* ->
() []
63
To discuss operator overloading, we will again use type vector. With two given
vectors
u = (xu , yu )
v = (xv , yv )
We can compute their sum
s = (xs , ys )
as follows:
xs = xu + xv
ys = yu + yv
y
s=u+v
1 2 3 4 x
Sum of two vectors
The previous figure illustrates this for u = (3, 1), v = (1, 2), which gives s = (4,
3) as the sum vector.
We will now overload the addition operator + for vectors, so that in our program
we can write
s = u + v;
64
In which s, u, and v are vectors. Program OPERATOR shows how this can be done:
/ / OPERATOR: An operator function for vector addition.
#include <iostream>
using namespace std;
class vector
{
private:
float xx, yy;
public:
vector (float x=0, float y=0) {xx = x; yy = y;}
void printvec( );
void getvec (float &x, float &y) {x =xx; y = yy; }
};
void vector::printvec( )
{
cout << xx << ‘ ‘ << yy << end1;
}
vector operator+(vector &a, vector &b)
{
float xa, ya, xb, yb,;
a.getvec (xa, ya); b.getvec(xb, yb);
return vector (xa + xb, ya + yb);
}
main( )
{
vector u(3, 1), v(1, 2), s;
s = u + v; / / The sum of two vectors is computed here!
s.printvec( ); / / Output: 4 3
}
As this program shows, we can define a plus operator of our own by means of a
function whose name consists of the keyword operator followed by +. Other
operators can be defined analogously. Note that the first line
vector operator+(vector &a, vector &b)
of a function sum, which we could have defined instead. In that case we would
have written
s = sum(u, v);
65
Instead of the more convenient notation
s = u + v;
In spite of the notation with two operands and an operator, operator+ is a real
function. This is best illustrated by comparing the above statement with the
following one, which is also valid and equivalent to it:
s = operator + (u, v);
Unlike ordinary functions, operator functions can have only one or two
parameters; at least one of these must be a class. Here we have used reference
parameters (by writing vector &a instead of vector a) for the sake of efficiency:
in this way, no new copies of the arguments u and v are made.
Program OPERATOR also defines and uses the function getvec, which did not occur
in Section 2.6. We use this in the operator+ function to fetch the individual
components xx and yy of its parameters a and b. After all, we could not have
written a.xx and a.yy in this operator function because, in class vector, the members
xx and yy are private. You may argue that it would be preferable if, in some way
or other, the function operator+ would have direct access to these members
(without making these public). There are two ways to achieve this, and we will
discuss these successively
Note also the expression
Vector (xa + xb, ya + yb)
For a given class, we can define friend functions, which have access to the
private members of the class. It is necessary for friend functions to be declared
inside the class. At the beginning of such declarations, we use the keyword friend.
Although friend functions are declared in a class, they are not member functions.
In program FRIEND, the operator+ function is a friend function of class vector:
66
/ / FRIEND: The ‘friend’ keyword applied to an operator function.
#include <iostream>
using namespace std;
class vector
{
private:
float xx, yy;
public:
vector (float x=0, float y=0) {xx = x; yy = y;}
void printvec( );
friend vector operator+(vector &a, vector &b);
};
void vector::printvec( )
{
cout << xx << ‘ ‘ << yy << end1;
}
vector operator+(vector &a, vector &b)
{
return vector (a.xx + b.xx, a.yy + b.yy);
}
main( )
{
vector u(3, 1), v(1, 2), s;
s = u + v; / / The sum of two vectors is computed here
s.printvec( ); / / Output: 4 3
}
Thanks to the ‘friend declaration’ of the operator+ function, the private members
xx and yy are now directly accessible in this function, as the above modified
version of this function illustrates.
In a class A, we can specify that all member functions of a class B are to be friend
functions by declaring class B as a friend of A. We then write:
friend class B;
Our goal, having direct access to private members of class vector, can also be
obtained by including the operator+ function as a member of this class. However,
this method required a notation that at first sight looks odd: although we are
defining a binary operator, the operator function must have only one parameter,
namely one that corresponds to the second operand. There is also a more serious
67
argument in favor of friend functions for operators in general, as will be clear at
the end of Section 2.11. The following program shows how we can use a member
function for our vector addition:
/ / OPMEMBER: An operator function as a class member.
class vector {
private:
float xx, yy;
public:
vector (float x =0, float y=0) {xx = x; yy = y;}
void prinvec( );
vector operator+(vector &b);
};
void vector::printvec( ) {
cout << xx << ‘ ‘ << yy << end1;
}
vector vector::operator+(vector &b) {
return vector (xx + b.xx, yy + b.yy);
}
main( ) {
vector u(3, 1), v(1, 2), s;
s = u + v; / / The sum of two vectors is computed here !
s.printvec( ); / / Output: 4 3
}
Note the following statement in the operator+ function:
return vector (xx + b.xx, yy + b.yy);
The unqualified xx and yy refer to the first operand, that is, to the vector variable
u. The operator definition shows only the parameter b, which corresponds to the
second operand, b. The first operand is implicit because the statement
s = u + v;
is to be regard as an abbreviated form of
s = u.operator+(v);
This is in accordance with normal member functions (not necessarily operators).
Since u is written here before the dot to indicate that the function operator+ is one
of its members, it would not make sense if u was also given as an argument. This
explains why a member function that defines a binary operator has only one
parameter.
Another possibly confusing point in this program is the double occurrence of the
word vector in the first line.
vector vector::operator+(vector &b)
68
of the operator function. The first word vector on this line is the type to be
returned by this function. Then vector:: follows to indicate that the function is a
member of class vector. We would have omitted this second occurrence of vector
and this double colon if we had defined the function inside the class declaration.
Besides these aesthetic advantages of defining operator+ as a friend function, there
is a more practical reason for it, which is related to type conversions of the first
operand, as we will see almost at the end of Section 2.11.
So far, we have discussed how to define operators that have two operands. We
can also apply the principle of operator overloading to unary operands. For
example, let us define the minus sign as the unary operator of vectors, so that we
will be able to write
vector u, v;
…
v = -u;
Once we have defined both a unary minus and a binary plus operator, we can use
these to define a binary minus operator, since we have
a – b = a + (-b)
69
void vector:: printvec( ) {
cout << xx << ‘ ‘ << yy << end1;
}
vector vector::operator +(vector &b) / / Binary plus
{
float xa, ya, ab, yb;
return vector (xx + b.xx, yy + b.yy);
}
vector vector::operator-( ) / / Unary minus
{
return vector (-xx, -yy);
}
vector vector::operator-(vector &b) / / Binary minus
{
return *this + -b;
}
main( ) {
vector u(3, 1), v(1, 2), sum, neg, diff;
sum = u +v;
sum.printvec( ); / / Output: 4 3
neg = -sum;
neg.printvec( ); / / Output: -4 –3
diff = u – v;
diff.printvec( ); / / Output: 2 –1
}
This program also shows how the C ++ keyword this can be used. Recall that this
is a pointer to the current object, as discussed in Section 2.6. We use it here in
the function that defines the binary minus operator. Since *this is the object
pointed to by this, it denotes the object u, used in the call
u.operator-(v)
In the main function, this call actually occurs in the more convenient form
u–v
70
As function call. On the other hand, it is also the typical C++ notation for a cast,
comparable, with, for example,
float (5)
After all, vector, like float, is a type. As its notation suggests, vector (5) (with class
vector defined as in Section 2.9) indeed means both a cast and a function call. Since
the vector constructor is defined as in
class vectopr {
private:
float xx, yy;
public:
vector (float x=0, float y=0) {xx = x; yy =y;}
…
}
This function can be called with zero, one, or two arguments. By writing vector(5), a
vector object with xx =5 and yy =0 is created, and this object is the result of the cast.
In general, we can define type conversions (written as casts) for class types simply
by defining appropriate constructors. The beauty of this is that type conversions for
class types can be defined any way we like, and that their use can be as simple as
those for standard types. For example, compare these two lines:
float x; x = float (5) ;
vector v; v = vector (5);
This program fragment can be simplified by initializing the variables x and v in their
declarations. If we combine the declarations and the assignments in a very
straightforward way, we obtain
float x = float (5);
vector v = vector (5);
Again, note the similarity between these two lines. Although correct, they are not in
their most convenient forms. We would rather replace them with the following two
lines, which also have the same form:
float x = 5;
vector v = 5;
This is indeed allowed. Incidentally, this declaration of v can also be written as
vector v (5);
Which is similar to
vector v (1, 2);
71
As we have already used in several demonstration. This is possible because our
constructor for type vector has two parameters (for which arguments may or may not
be supplied). There is no such constructor for type float, so that
float x (5); /* Incorrect */
would be invalid. Here is another very simple but instructive program fragment:
x = 5;
v = 5;
The assignment v = 5 is regarded as an abbreviated form of
v = vector(5)
We now see that constructors are also useful to define implicit type conversions (in
which no cast is used). Such conversions also occur in contexts other than
assignments. Some examples of implicit conversion are
1. The expression v + 1, which is shorthand for v + vector (1).
2. The call f(1), where f is a function that takes a vector as its argument.
3. The statement return 1; occurring in a function that returns a vector.
So much for invoking the constructor vector with one argument . Since not only its
second but also its first parameter is given the default value 0 , it follows that the
declaration.
vector v;
creates a vector with both private members xx and yy initialized to 0. As for the
remaining case, in which we use arguments, recall that we have already seen
examples such as
vector u(3, 1), v(1, 2);
and
return vector(-xx, -yy);
If we want to add the constant vector (7, 3) to a given vector v, we must write
v + vector(7, 3)
It would be incorrect if we tried to achieve the same effect by writing
v + (7, 3)
Because here the comma would act as the comma-operator. As a result, that line
would be interpreted as
v+3
72
which is valid and in turn equivalent to
v + vector(3, 0)
Beware of a similar pitfall in declarations with initializations:
vector v=5 ; is equivalent to vector v(5);
vector v=vector (7, 3) ; is equivalent to vector v(7, 3);
vector v=(7, 3) is not equivalent to vector v(7, 3);
(a)
(b)
73
A shallow copy and its original object share the area pointed to, while a deep copy
has such an area of its own. The distinction between these two copying methods is
particularly important if, as usual, the memory area in question is located by a
constructor and deleted by a destructor. What we need is deep copies. If shallow
copies were made, applying the destructor to both the original object and the copy
would result in deleting the shared area twice, which is an illegal and a dangerous
thing to do.
Copying an object occurs more frequently than you may expect. For example, in
mytype x(123), y=x;
The object x is copied into object y. Also, in a call to the following function f,
the argument is copied into the parameter w:
mytype f (mytype w)
{
mytype x;
…
return x;
}
The argument would not be copied. In this example, copying occurs once again
in the return-statement.
This constructor has the task to specify how copying is to be done. Note that the
class name mytype is used here both as a function name and as a parameter name.
Remember also that a copy constructor always has a reference parameter, as the
ampersand indicates. How copy constructors can be defined will be demonstrated
shortly in a complete program.
74
2.11.2. Copying by Assignment Operators
It goes without saying that copying also takes place in assignments, such as in
the second line of
mytype x(123), y;
y = x;
However, this example is essentially different from the one in our discussion of
copy constructors: there the equal sign was used in a declaration while it occurs
here in an assignment. Although initialization and assignment look very much
the same, they are distinct language constructs. A special measure, other than a
copy constructor, is required to prevent shallow copying taking place in
assignments. We must define a special assignment operator, as declared in
mytype operator=(mytype &v);
to specify how a deep copy is to be made in assignments. The assignment
operator must be a member function; it cannot be a friend function.
Let us clarify these two important aspects (copy constructor and assignment)
by means of the following example. We want to define and use a special string
type, which we will call class cstring. Objects of this type can contain strings of
any length, and the +operator can be used to concatenate such strings. For
example, our cstring class enables us to write
cstring s=”ABC”, t=”DEF”, u;
u = s + t + “GH”; / / u = “ABCDEFGH”
So far, we have paid much attention to conversions from standard types to class
types. It is sometimes also desirable to enable the opposite conversions, that is,
from class type to more elementary types. For example, our cstring objects are
very much like normal string but since such objects are not pointers to characters,
some special measure is required to use them as such, as, with the above
declaration of x, is done in
cout <<x;
75
or
char a[80];
strcpy(a, x);
We will discuss the method to convert class types to other types in more detail
shortly. Let us first have a look at a complete program in which all this is realized:
// CSTRING: This program demonstrates how to make deep //copies .
# include <iostream>
# include <string . h>
using namespace std;
class cstring
{
private :
char *p;
public:
cstring(int n=0) ; // First constructor .
cstring(char *str) ; // Second constructor .
cstring(cstring &x); // Third constructor .
//(‘copy constructor’)
// to make deep copies .
~cstring ( ) {delete p;} // Destructor .
cstring operator= (cstring &x) ; // Assignment operator .
// Concatenation
friend cstring operator + (cstring &x, cstring &y);
// Conversion from cstring to char* . .
operator char* ( ) {return p;}
};
76
cstring: :cstring (cstring &x) // Third constructor
{
p + new char [strlen (x.p)+1]; // (copy constructor)
strcpy (p, x.p); // makes a deep copy.
}
Like default assignment operations (which we cannot use because they make
shallow copies), our cstring assignments can be used within expressions, as (x = x
+ “!”) on the fourth line from the bottom demonstrates. The output of this program
is
Hello, world !
Message: Hello, world! How are you?
77
The second constructor, which takes a pointer to a character as its argument,
enables us to write strings such as “How are you?” in positions that actually
require objects of type cstring. Recall that we discussed such implicit type
conversions by means of constructors in Section 2.10.
We must not omit the two components that make deep copies, namely the copy
constructor and assignment operator function. If we did, the compiler would not
detect it, but the program may crash during execution. This is because in that case
shallow copies would be made instead of deep ones. As a result, the destructor
(present in the header file) would try to release the same memory area more than
once. It is instructive to check how often our constructors and destructors are
called. If everything is as it should be, the destructor is called exactly as many
times as all constructors together are called. We can obtain accurate and detailed
information about this by introducing a global count variable for each constructor
and a similar variable for the destructor. These variables are initialized to zero
and increased by one in each constructor and in the destructor. Note, however,
that we also need another program modification to realize this. This is because of
destructor calls at the very end of the function display our count variables before
all destructor calls have taken place. We can solve this problem by renaming our
function main and calling it in a new one, as in
// CSTRING1 : Modified version to count all calls to
// the constructors and to the destructor.
78
Except for very simple programs, it is not easy to predict the frequencies counted
in this way. Fortunately, we need not do this. In an experiment with our cstring
program, the following counting results were found:
Constructor 1: 10
Constructor 2: 5
Constructor 3: 12
Destructor: 27
If we had omitted the third constructor, that is, the copy constructor, not bothering
about deep or shallow copies, there would have been only 10 + 5 = 15 constructor
calls and as many as 27 destructor calls, which would have been seriously wrong.
Constructor 3 is called by the declarations of variables t and s1 and by the
execution of return statements: six for the operator+ function and four for the
operator= function. Note that in the main function the plus operator occurs six
times and assignment operator four times. This explains that the copy constructor
is called 1+ 6 + 4 = 12 times.
Our + operator for string concatenation is a friend function. You may wonder
why not use a member function instead. The answer is that our choice enables us
to use this operator with a normal string (instead of cstring object) as its first
operand, as in
y = “Message: “ + …
79
operand, so defining the + operator as a member function would have deprived
us of the implicit conversion that is applied here.
As for the assignment operator, the C++ language requires operator = functions
to be member functions; they cannot be defined as friend functions.
At the beginning of this section, we restricted our discussion to classes that have
no other classes as their members. If classes do have other classes as their
members, the default copying method is not exactly “bitwise”, but what is called
member-wise. Suppose that class C has a member class M and that an object of
class C is to be copied. Obviously, copying this object implies copying its M
member. If C has no user-defined copy the M member. All member of C that are
not classes are copied bitwise.
80
Exercises
1. Write a program that reads the names and the ages of ten people and store
these data in structures that are elements of an array. Print the average age of
these people. Also, print a table of ten lines, with on each line the given data
of a person, along with the (positive or negative) deviation of his or her age
from the average age. Store only the first 30 characters (followed by the null
character) of names that are longer than 30 characters.
2. The same as Exercise 1, but full names are to be stored. Each structure must
not include a name itself but a pointer. This points to an area of memory which
is allocated by means of new and in which that name is stored. You may,
however, assume each name to be no longer than 100 characters, so that you
can initially read it into a buffer of a fixed size.
3. Define a class verylongint, with a pointer as one of its members. This pointer
points to a memory area in which very long integers are stored digit by digit.
Besides functions such as constructors and a destructor, which are not directly
called by the ‘user’, there must be the operators +, *, and =, as well as a function
print to display such long integers, in such a way that we can use this class,
for example, as follows:
verylongint x = 30000, y = 20000, y1 =y,
Z = “123456789012345”, result;
result = x * x * x * x + y * y* y * y1 +z;
result . print( );
As this example illustrates, string quotes are required for integers that do not
fit into type long int. For integers below this limit, string quotes may be written
or omitted, as the user likes. The output to be produced by this program
fragment is
970 123 456 789 012 345
81
Chapter 3
Object-Oriented
Programming
82
3.1. Introduction
So far, we have used only one program file for the definition, the implementation,
and the application of a class. This was very reasonable for our rather small
programs, intended only to demonstrate some basic facts about classes. However, in
practice it is usually to be preferred that programs dealing with classes should consist
of at least three files:
1. A header file that declares the class type. Except for inline member functions
(defined inside the class), functions are only declared, not defined, in this file.
This file is the interface between the implementation and the application (see
2 and 3).
2. A file that contains both an include line for the interface and the definitions of
the functions just mentioned. This file is called the implementation of the class
in question.
3. A file that also contains an include line for the interface, followed by other
functions, including the main function. This file is called an application of the
class.
83
Operations, it must be possible to use these for all kinds of applications. This aspect
of reusability is an important factor in efficient software construction. The class
concept encourages us to write reusable code in the form of implementation and
interface files.
Splitting up programs into modules is called modular programming. In large
programs, with hundreds of function and several class types, the application will
normally consist of several files, and there will normally be both an implementation
and an interface file for each class or for each set of classes that are closely related.
Instead of presenting a large software project in this Note, we prefer dealing with
rather small programs, but we will now split these up in the same way as we would
do for large ones. Although this approach may not really be advantageous for these
rather small and simple programs, you can easily imagine its usefulness in practical
program development.
If we have declared a certain class type, and we want a new one, consisting of all
members of the old class and some additional members besides, the new class can
be regarded as being derived from the old one. We say that a derived class inherits
the members of its base class. For example, suppose we have the class geom_obj for
geometrical objects, declared as follows:
#include <iostream>
using namespace std;
class geom_obj
{
protected:
float xc, yc;
public:
geom_obj ( float x=0, float y=0){xc=x; yc=y; }
void printcenter ( ) { cout << xc << “ “ << yc << endl; }
};
As for the new keyword protected, we will discuss this shortly. We can do only very
little with this class type, nor does it give much information about what kind of
objects are to be stored in it. We now want to use two types of very specific
geometrical objects, namely circles and squares. As usual, a circle is characterized
by its center and its radius. As for the square, let us represent it by its center and one
84
of its four vertices. We can now benefit from the class type geom_obj, which we have
already declared, by declaring the derived classes circle and square as follows:
const float PI=3.14159265;
class circle: public geom_obj
{
private:
float radius;
public:
circle (float x_c, float y_c, float r) : geom_obj (x_c, y_c)
{
radius = r;
}
float area ( ) {return PI * radius * radius; }
};
class square: public geom_obj
{
private:
float x1, y1;
public:
square (float x_c, float y_c, float x, float y) : geom_obj (x_c, y_c)
{
x1 = x; y1 = y;
}
float area ( )
{
float a=x1-xc, b=y1-yc;
return 2 * (a * a + b * b); // see Figure 1
}
};
In each of these two class declarations, the first line shows the name of the base class
geom_obj. The two derived classes circle and square are to be regarded as extensions
of their base class geom_obj. The keyword public as used in, for example,
class square: public geom_obj
Specifies that all public members of the class geom_obj are also regarded as public
members of the derived class square. For example, we can write:
square s(3, 3.5, 4.37, 3.85);
s.printcenter( );
Although printcenter does not occur directly in the declaration of class square, it is
nevertheless one of its member function because class square has been derived from
class geom_obj. Another point to be noted id the use of xc and yc in the area member
85
function of class square. These are protected member of the base class geom_obj, as
the keyword protected indicates. Protected members are similar to private ones. Their
only difference is that a derived class has access to the protected members, not to the
private members, of its base class.
The derived classes circle and square can be regarded as extensions of their base
class geom_obj. When an object of, for example, type square is created, as is done
by the above declaration of the variable s, the constructor of the base class
geom_obj is called first; then that of square is called. Conversely, when an object
of a derived class is destroyed, destructors, if any, are called in the reverse order:
first that of the derived class and then that of the base class.
This part is not obligatory. If we omit it, the constructor of geom_obj is implicitly
called with its default argument values 0. Instead, in our example the value of x_c
and y_c, as supplied to the derived class, are passed to the constructor of the base
class geom_obj, and stored as the member xc and yc of this class.
Let us now use the preceding class declarations of this section:
main ( )
{
circle c(2, 2.5, 2);
square s(3, 3.5, 4.37, 3.85);
cout << “center of circle:; c.printcenter( );
cout << “center of square:; s.printcenter( );
cout << “Area of circle: “<< c.area( ) << end1;
cout << “Area of square: “<< s.area ( ) <<end1;
}
86
The resulting program (compiled as one source file) gives the following output:
Center of circle: 2 2.5
Center of square: 3 3.5
Area of circle: 12.566371
Area of square: 3.998799
Figure 1 shows the circle and the square in question. It also shows the meaning of
the variables a and b in the area member function of class square.
4 b
2 a
1
x
1 2 3 4 5
Figure 1 Circle and Square
In the example we have been discussing, the relationship between the base class
and its derived classes is illustrated by Figure 2.
geom_obj
Circle Square
Figure 2 Base Class and Two Derived Classes
The situation can be more complicated. First, derived classes can act as classes
for other (derived) classes, so that we can build trees of classes, as shown in
Figure 3. Here class A1, although derived from class A, is the base class of A11
and A12. Second, base and derived classes need not form a tree, as we will see
now.
87
A
A1 A2
A1 A2
Figure 3 Tree of Classes
A B
A1 A2 AB B1 B2 B3
Figure 4 Multiple Inheritance
Multiple Inheritance
So far, it was taken for granted that each derived class should have only one
base class. In the first version of C++, this was indeed the case. However, in C++
Release 2 the concept of multiple inheritance was introduced. This means that it
is possible for a class to be derived from more than one base class. For example,
the situation can be as shown in Figure 4, where the derived class AB has both
class A and class B as its base classes. If we want the public members of A and B
to be public members of AB, the class declaration of AB is written as follows:
class AB: public A, public B
{
…
}
If AB has a constructor with parameters, we can pass these to each base class as,
for example, in
AB(int n=0, float x-0, char ch=’A’) : A(n, ch), B(n, x)
{
…
}
The creation of an object of class AB causes the three constructors for A, B, and
AB, in that order, to be called.
88
3.4. Virtual Functions and Late Binding
89
The question now arises whether or not, without using a cast, we can make p point
to a ctype1 or a ctype2 object. This is indeed the case, so we can write p = new ctype1,
and p = new ctype2.
The three class types ctype, and ctype1, and ctype2 have member functions with the
same name (f). Because of the keyword virtual in the base class ctype, function f is
what we call a virtual function. This means that during the execution of the call
p->f()
The decision is made which of the three functions (ctype::f, ctype1::f, or ctype2::f) is to
be called. This decision is based on the type of the object pointed to by p. Since the
correct function is not chosen at compile time but at execution time, the term late
binding (or dynamic binding) is used for the relationship between the pointer p and
the function in question. If we had omitted the keyword virtual in the declaration of
ctype, the function of ctype::f would have been taken, even if p pointed to a ctype1 or
ctype2 object. This is not unreasonable, since, as we know, a derived type is an
extension of its base type (s), so all members of a base class also belong to its derived
classes. Without the keyword virtual, the decision which function is to be taken is
already made at compile time, so in that case we speak of early (or static) binding.
As for the implementation, there will be an extra (anonymous) member in the
class in case of late binding (that is, if there is a keyword virtual), so that it can be
determined at run time which of the member functions is to be taken. This is shown
by program LATE, in which the classes early and late are identical but for the keyword
virtual:
/* LATE: This program shows that with late binding there is an anonymous class member
to tell at run time which class actually applies. */
#include <iostream>
using namespace std;
90
This program does not do any practical work but is nonetheless interesting in several
respects. First, it shows that, as long as there are only public members, the things we
are discussing for classes also apply to structures. In most cases, you may replace
the word class in our discussion with the word structure. Second, its output (with
Turbo C++) reveals that late binding requires an anonymous member, which in our
example takes 4-2=2 bytes:
Class sizes in bytes, found by sizeof:
early: 2
late: 4
early1: 2
late1: 4
Third, this output also shows that member functions are not really stored in the class
objects themselves: since the int member i of class early already takes two bytes,
which is the size of that whole class, it follows that function f, although a member of
the class, is not really stored in objects of this class. (If it were, much memory space
would be wasted in programs with a great number of objects of the same class type.)
You can also see from this output that an object of a derived class type also
accommodates the data members of its base class type(s). For example, the two bytes
of an early1 object are needed to store the integer i of its base class early. In other
words, our derived classes early1 and late1 take as much room as their base classes
early and late, respectively, because they have no data members other than those
inherited from their base classes.
We now turn to a simple but complete program with pointers to base and derived
classes, to see a virtual function (named print) in action:
//VIRTUAL: A virtual function in action.
#include <iostream>
using namespace std;
class animal
{
public:
virtual void print ( ) {cout << “Unknown animal type.\n”;}
protected:
int nlegs;
};
91
class fish: public animal
{
public:
fish(int n) {nlegs = n;}
void print ( )
{
cout << “A fish has “ << nlegs << “ legs.\n”;
}
};
class bird: public animal
{
public:
bird(int n) {nlegs = n;}
void print ( )
{
cout << “A bird has “ << nlegs << “ legs.\n”;
}
};
class mammal: public animal
{
public:
mammal(int n) {nlegs = n;}
void print ( )
{
cout << “A mammal has “ << nlegs << “ legs.\n”;
}
};
main ( )
{
animal *p[4];
p[0]=new fish (0);
p[1]=new bird (2);
p[2]=new mammal (4);
p[3]=new animal;
92
p[i]->print ( );
Which of the four functions fish::print, bird::print, mammal::print, and animal::print
applies. Thanks to the keyword virtual; in the base class animal, this choice is made at
run time. Before the for-statement is executed, the new operator is called four times
to create the objects in question. They accommodate an anonymous member for the
actual class type, to enable late binding. The output of program VIRTUAL is:
A fish has 0 legs.
A bird has 2 legs.
A mammal has 4 legs.
Unknown animal type.
Instead of these four lines, the text of the fourth line would have been printed four
times if we had used early binding, that is, if we had omitted the keyword virtual.
As mentioned at the beginning of this chapter, the use of derived classes and virtual
functions is often called object-oriented programming. Another popular term in
connection with programs such as VIRTUAL is polymorphism. It refers to the fact that
objects of different (derived) types, such as fish, bird, and mammal, are accessed in the
same way, as is done in the above call p[i] -> print( ).
As for terminology, it might be mentioned that sometimes member functions are
called methods and calling member functions is referred to as sending messages.
This terminology is particularly popular with users of OOP languages other than
C++.
An interesting point about derived classes is that they are not specified in their
base class, nor does the base class ‘know’ how many derived classes there are,
Consequently, derived classes can be added later, without altering either the base
class or already existing derived classes. Recall that in realistic C++ software
projects, there are interface, implementation, and application files. Now suppose
we want to program a new application of some class and we find interface and
implementation files which cover almost all we want, but in which some facilities
are lacking or different from what we need. With a well-designed class hierarchy
of base and derived classes, it will then be possible to write another
implementation file for a new derived class, tailored to our application, and to
use this file in addition to the already existing interface and implementation files,
without modifying or recompiling these. This idea can make software more
93
reusable than it was with older programming methods. Since programming in this
way is limited to what is new, the terms programming by difference and
incremental programming are sometimes used for it.
In program VIRTUAL, the function print is defined not only in the derived classes
fish, bird, and mammal, but also in the base class animal. If we had not felt a need
for this (and we had been interested only in the first three of the four output lines),
it would still have been necessary to write this function (with the keyword virtual)
in the base class, but then we could have written:
virtual void print ( ) {}
However, doing this has far-reaching consequences. Let us assume that we really
write this in the base class animal of program VIRTUAL. Then this base class is said
to be an abstract class. An abstract class type cannot be used to create objects of
this type. It can only be used for the declaration of derived classes. Such a
program modification would therefore make this line invalid:
p[3] = new animal;
(If we delete this line, we should also replace 4 with 3 in the for-statement that
follows it and in the declaration of pointer array p) With these program
modifications, it would also be incorrect to write:
94
animal A;
which would be correct in the original version of program VIRTUAL. Note the
difference between this incorrect declaration of A and
animal *p[4];
Using the abstract class type animal, we cannot declare the variable A since that
would create an object of type animal. However, we can declare the pointer array
p, provided that we use its elements only as pointers to objects of the derived
types fish, bird, and mammal. Pointers to objects that have the abstract class type
animal are out of the question simply because there are no such objects.
A1 A2
A12
The curious point about this is that each object of class A12 will have two sub-
objects of class A. After all, class A12 indirectly inherits the members of class A
twice. We can suppress this duplication of indirectly inherited members by using
the keyword virtual in declarations of A1 and A2 as follows:
class A { … };
class A1: virtual public A { … };
class A2: virtual public A { … };
class A12: virtual public A1, public A2 { … };
95
Thanks to the keyword virtual on the second and third lines, A is a virtual base
class, and class A12 has only one sub-object of class A. It would have had two if
we had omitted the virtual keyword.
For the sake of completeness, it must be mentioned that a derived class cannot
directly inherit the members of a base class more than once:
class A { … };
class A12 : public A, public A { … }; // Illegal
Normally, a data member of a class type is stored in every object of that type. As we
know, this is not the case with a function member, of which there is only one for
each class type. However, data members behave like function members in this regard
if we use the keyword static for them. In other words, a static class member belongs
to its class type rather than to the individual objects of that type. As usual with
normal static variables, static class members are implicitly initialized with 0. Program
STATMENT shows how static members can be used. The constructor of person adds 1
to the static class member count each time it is called. In this way, we count how
many objects of type person there are:
/* STATEMENT: Using a static class member to count how many
times the constructor person ( ) is called.
*/
#include <iostream>
#include <string.h>
using namespace std;
class person
{
private:
char name[20];
public:
static int count; // Initialized to zero.
Person (char *str)
{
strcpy(name, str);
count++;
}
};
96
main ( )
{
person A(“Mary”), B(“Peter”), C(“Charles”), *p;
p = new person (“Kate”);
A.print( );
B.print( );
C.print( );
p->print( );
Since the static class member count is associated with the class type person, not with
objects of that type, we write person::count, whereas we would have written, for
example, A.count if count had been a non-static member and if we had been interested
in the copy of it for object A.
The output of this program is as follows:
Mary
Peter
Charles
Kate
There are 4 persons.
Note that using the word static here is analogous to using it for local variables. In
both cases, there is only one copy of the object. As for a non-static local variable in
a function, there is a new copy of it each time the function is called, so in case of
recursion there will be several copies. If the local variable is static there is only one
copy, even in case of recursion. Things are similar with a static member of a class
type: there is only one copy of that member, no matter how many variables of that
type there are.
97
Exercises
a. Define the class bankAccount to store a bank customer’s account number and
balance. Suppose that account number is of type int, and balance is of type
double. Your class should, at least, provide the following operations: set the
account number, retrieve the account number, retrieve the balance, deposit
and withdraw money, and print account information. Add appropriate
constructors.
b. Every bank offers a checking account. Derive the class checkingAccount from
the class bankAccount (designed in part (a)). This class inherits members to
store the account number and the balance from the base class. A customer
with a checking account typically receives interest, maintains a minimum
balance, and pays service charges if the balance falls below the minimum
balance. Add member variables to store this additional information. In
addition to the operations inherited from the base class, this class should
provide the following operations: set interest rate, retrieve interest rate, set
minimum balance, retrieve minimum balance, set service charges, retrieve
service charges, post interest, verify if the balance is less than the minimum
balance, write a check, withdraw (override the method of the base class), and
print account information. Add appropriate constructors.
c. Every bank offers a savings account. Derive the class savingsAccount from
the class bankAccount (designed in part (a)). This class inherits members to
store the account number and the balance from the base class. A customer
with a savings account typically receives interest, makes deposits, and
withdraws money. In addition to the operations inherited from the base class,
this class should provide the following operations: set interest rate, retrieve
interest rate, post interest, withdraw (override the method of the base class),
and print account information. Add appropriate constructors.
d. Write a program to test your classes designed in parts (b) and (c).
98
99