Introduction To Object-Oriented Programming Using C++
Introduction To Object-Oriented Programming Using C++
Object-Oriented Programming
Using C++
Peter Muller
[email protected]
Globewide Network Academy (GNA)
www.gnacademy.org/
v
Chapter 1
Introduction
This tutorial is a collection of lectures to be held in the on-line course Intro-
duction to Object-Oriented Programming Using C++. In this course, object-
orientation is introduced as a new programming concept which should help you
in developing high quality software. Object-orientation is also introduced as a
concept which makes developing of projects easier. However, this is not a course
for learning the C++ programming language. If you are interested in learning
the language itself, you might want to go through other tutorials, such as C++:
Annotations1 by Frank Brokken and Karel Kubat. In this tutorial only those
language concepts that are needed to present coding examples are introduced.
And what makes object-orientation such a hot topic? To be honest, not
everything that is sold under the term of object-orientation is really new. For
example, there are programs written in procedural languages like Pascal or C
which use object-oriented concepts. But there exist a few important features
which these languages won't handle or won't handle very well, respectively.
Some people will say that object-orientation is \modern". When reading
announcements of new products everything seems to be \object-oriented". \Ob-
jects" are everywhere. In this tutorial we will try to outline characteristics of
object-orientation to allow you to judge those object-oriented products.
The tutorial is organized as follows. Chapter 2 presents a brief overview of
procedural programming to refresh your knowledge in that area. Abstract data
types are introduced in chapter 3 as a fundamental concept of object-orientation.
After that we can start to dene general terms and beginning to view the world
as consisting of objects (chapter 4). Subsequent chapters present fundamental
object-oriented concepts (chapters 5 and 6). Chapters 7 through 9 introduce
C++ as an example of an object-oriented programming language which is in
wide-spread use. Finally chapter 10 demonstrates how to apply object-oriented
programming to a real example.
1 http://www.icce.rug.nl/docs/cpp.html
1
2 CHAPTER 1. INTRODUCTION
Chapter 2
A Survey of Programming
Techniques
Peter Muller
Globewide Network Academy (GNA)
[email protected]
This chapter is a short survey of programming techniques. We use a simple
example to illustrate the particular properties and to point out their main ideas
and problems.
Roughly speaking, we can distinguish the following learning curve of someone
who learns program:
Unstructured programming,
procedural programming,
modular programming and
object-oriented programming.
This chapter is organized as follows. Sections 2.1 to 2.3 brie
y describe the rst
three programming techniques. Subsequently, we present a simple example of
how modular programming can be used to implement a singly linked list module
(section 2.4). Using this we state a few problems with this kind of technique in
section 2.5. Finally, section 2.6 describes the fourth programming technique.
main program
data
main program
data
To sum up: Now we have a single program which is devided into small
pieces called procedures. To enable usage of general procedures or groups of
procedures also in other programs, they must be separately available. For that
reason, modular programming allows grouping of procedures into modules.
main program
data
module 1 module 2
data +data1 data +data2
MODULE Singly-Linked-List-1
BOOL list_initialize();
BOOL list_append(ANY data);
2.4. AN EXAMPLE WITH DATA STRUCTURES 7
BOOL list_delete();
list_end();
ANY list_getFirst();
ANY list_getNext();
BOOL list_isEmpty();
END Singly-Linked-List-1
Interface denitions just describe what is available and not how it is made
available. You hide the informationof the implementationin the implementation
le. This is a fundamental principle in software engineering, so let's repeat it:
You hide information of the actual implementation (information hiding). This
enables you to change the implementation, for example to use a faster but more
memory consuming algorithm for storing elements without the need to change
other modules of your program: The calls to provided procedures remain the
same.
The idea of this interface is as follows: Before using the list one have to
call list initialize() to initialize variables local to the module. The following two
procedures implement the mentioned access methods append and delete. The
append procedure needs a more detailed discussion. Function list append() takes
one argument data of arbitrary type. This is necessary since you wish to use
your list in several dierent environments, hence, the type of the data elements
to be stored in the list is not known beforehand. Consequently, you have to use
a special type ANY which allows to assign data of any type to it2. The third
procedure list end() needs to be called when the program terminates to enable
the module to clean up its internally used variables. For example you might
want to release allocated memory.
With the next two procedures list getFirst() and list getNext() a simple
mechanism to traverse through the list is oered. Traversing can be done using
the following loop:
ANY data;
Now you have a list module which allows you to use a list with any type
of data elements. But what, if you need more than one list in one of your
programs?
2 Not all real languages provide such a type. In C this can be emulated with pointers.
8 CHAPTER 2. A SURVEY OF PROGRAMMING TECHNIQUES
2.4.2 Handling Multiple Lists
You decide to redesign your list module to be able to manage more than one list.
You therefore create a new interface description which now includes a denition
for a list handle. This handle is used in every provided procedure to uniquely
identify the list in question. Your interface denition le of your new list module
looks like this:
/*
* A list module for more than one list.
*/
MODULE Singly-Linked-List-2
list_handle_t list_create();
list_destroy(list_handle_t this);
BOOL list_append(list_handle_t this, ANY data);
ANY list_getFirst(list_handle_t this);
ANY list_getNext(list_handle_t this);
BOOL list_isEmpty(list_handle_t this);
END Singly-Linked-List-2;
You use DECLARE TYPE to introduce a new type list handle t which repre-
sents your list handle. We do not specify, how this handle is actually represented
or even implemented. You also hide the implementation details of this type in
your implementation le. Note the dierence to the previous version where you
just hide functions or procedures, respectively. Now you also hide information
for an user dened data type called list handle t.
You use list create() to obtain a handle to a new thus empty list. Every
other procedure now contains the special parameter this which just identies
the list in question. All procedures now operate on this handle rather than a
module global list.
Now you might say, that you can create list objects. Each such object can be
uniquely identied by its handle and only those methods are applicable which
are dened to operate on this handle.
list_destroy(myList);
END
Let's compare the list with other data types, for example an integer. Inte-
gers are declared within a particular scope (for example within a procedure).
Once you've dened them, you can use them. Once you leave the scope (for
example the procedure where the integer was dened) the integer is lost. It
is automatically created and destroyed. Some compilers even initialize newly
created integers to a specic value, typically 0 (zero).
Where is the dierence to list \objects"? The lifetime of a list is also dened
by its scope, hence, it must be created once the scope is entered and destroyed
once it is left. On creation time a list should be initialized to be empty. Therefore
we would like to be able to dene a list similar to the denition of an integer.
A code frame for this would look like this:
PROCEDURE foo() BEGIN
list_handle_t myList; /* List is created and initialized */
The advantage is, that now the compiler takes care of calling initialization
and termination procedures as appropriate. For example, this ensures that the
list is correctly deleted, returning resources to the program.
2.5.2 Decoupled Data and Operations
Decoupling of data and operations leads usually to a structure based on the
operations rather than the data: Modules group common operations (such as
those list ...() operations) together. You then use these operations by providing
explicitly the data to them on which they should operate. The resulting module
10 CHAPTER 2. A SURVEY OF PROGRAMMING TECHNIQUES
structure is therefore oriented on the operations rather than the actual data.
One could say that the dened operations specify the data to be used.
In object-orientation, structure is organized by the data. You choose the data
representations which best t your requirements. Consequently, your programs
get structured by the data rather than operations. Thus, it is exactly the
other way around: Data species valid operations. Now modules group data
representations together.
2.5.3 Missing Type Safety
In our list example we have to use the special type ANY to allow the list to
carry any data we like. This implies, that the compiler cannot guarantee for
type safety. Consider the following example which the compiler cannot check
for correctness:
PROCEDURE foo() BEGIN
SomeDataType data1;
SomeOtherType data2;
list_handle_t myList;
...
list_destroy(myList);
END
The corresponding list routines should then automatically return the correct
data types. The compiler should be able to check for type consistency.
2.5.4 Strategies and Representation
The list example implies operations to traverse through the list. Typically
a cursor is used for that purpose which points to the current element. This
2.6. OBJECT-ORIENTED PROGRAMMING 11
implies a traversing strategy which denes the order in which the elements of
the data structure are to be visited.
For a simple data structure like the singly linked list one can think of only
one traversing strategy. Starting with the leftmost element one successively
visits the right neighbours until one reaches the last element. However, more
complex data structures such as trees can be traversed using dierent strategies.
Even worse, sometimes traversing strategies depend on the particular context
in which a data structure is used. Consequently, it makes sense to separate the
actual representation or shape of the data structure from its traversing strategy.
We will investigate this in more detail in chapter 10.
What we have shown with the traversing strategy applies to other strategies
as well. For example insertion might be done such that an order over the
elements is achieved or not.
object1
data object4
data
object3
data
object2
data
2.7 Excercises
1. The list examples include the special type ANY to allow a list to carry
data of any type. Suppose you want to write a module for a specialized
list of integers which provides type checking. All you have is the interface
denition of module Singly-Linked-List-2.
(a) How does the interface denition for a module Integer-List look like?
(b) Discuss the problems which are introduced with using type ANY for
list elements in module Singly-Linked-List-2.
(c) What are possible solutions to these problems?
2. What are the main conceptual dierences between object-oriented pro-
gramming and the other programming techniques?
3. If you are familiar with a modular programminglanguage try to implement
module Singly-Linked-List-2. Subsequently, implement a list of integers
and a list of integer lists with help of this module.
Chapter 3
Abstract Data Types
Peter Muller
Globewide Network Academy (GNA)
[email protected]
Some authors describe object-oriented programming as programming ab-
stract data types and their relationships. Within this section we introduce
abstract data types as a basic concept for object-orientation and we explore
concepts used in the list example of the last section in more detail.
Problem
Abstraction
Model
name,
size,
date of birth,
shape,
social number,
room number,
hair colour,
hobbies.
Certainly not all of these properties are necessary to solve the administration
problem. Only some of them are problem specic. Consequently you create a
model of an employee for the problem. This model only implies properties
which are needed to fulll the requirements of the administration, for instance
name, date of birth and social number. These properties are called the data
of the (employee) model. Now you have described real persons with help of an
abstract employee.
Of course, the pure description is not enough. There must be some op-
erations dened with which the administration is able to handle the abstract
employees. For example, there must be an operation which allows to create a
new employee once a new person enters the institution. Consequently, you have
to identify the operations which should be able to be performed on an abstract
employee. You also decide to allow access to the employees' data only with
associated operations. This allows you to ensure that data elements are always
in a proper state. For example you are able to check if a provided date is valid.
To sum up, abstraction is the structuring of a nebulous problem into well-
dened entities by dening their data and operations. Consequently, these en-
tities combine data and operations. They are not decoupled from each other.
3.2. PROPERTIES OF ABSTRACT DATA TYPES 15
3.2 Properties of Abstract Data Types
The example of the previous section shows, that with abstraction you create
a well-dened entity which can be properly handled. These entities dene the
data structure of a set of items. For example, each administered employee has
a name, date of birth and social number.
The data structure can only be accessed with dened operations. This set of
operations is called interface and is exported by the entity. An entity with the
properties just described is called an abstract data type (ADT).
Figure 3.2 shows an ADT which consists of an abstract data structure and
operations. Only the operations are viewable from the outside and dene the
interface.
abstract data type
operations interface
The angle brackets now enclose the data type of which a variant of the
generic ADT List should be created. listOfApples oers the same interface as
any other list, but operates on instances of type Apple.
3.4 Notation
As ADTs provide an abstract view to describe properties of sets of entities,
their use is independent from a particular programming language. We therefore
introduce a notation here which is adopted from [3]. Each ADT description
consists of two parts:
Data: This part describes the structure of the data used in the ADT in
an informal way.
Operations: This part describes valid operations for this ADT, hence,
it describes its interface. We use the special operation constructor to
18 CHAPTER 3. ABSTRACT DATA TYPES
describe the actions which are to be performed once an entity of this
ADT is created and destructor to describe the actions which are to be
performed once an entity is destroyed. For each operation the provided
arguments as well as preconditions and postconditions are given.
As an example the description of the ADT Integer is presented. Let k be an
integer expression:
ADT Integer is
Data
A sequence of digits optionally prexed by a plus or minus sign. We
refer to this signed whole number as N.
Operations
constructor Creates a new integer.
add(k) Creates a new integer which is the sum of N and k.
Consequently, the postcondition of this operation is sum = N+k.
Don't confuse this with assign statements as used in program-
ming languages! It is rather a mathematical equation which
yields \true" for each value sum, N and k after add has been
performed.
sub(k) Similar to add, this operation creates a new integer of the
dierence of both integer values. Therefore the postcondition for
this operation is sum = N-k.
set(k) Set N to k. The postcondition for this operation is N = k.
...
end
The description above is a specication for the ADT Integer. Please notice,
that we use words for names of operations such as \add". We could use the
more intuitive \+" sign instead, but this may lead to some confusion: You
must distinguish the operation \+" from the mathematical use of \+" in the
postcondition. The name of the operation is just syntax whereas the semantics
is described by the associated pre- and postconditions. However, it is always a
good idea to combine both to make reading of ADT specications easier.
Real programming languages are free to choose an arbitrary implementation
for an ADT. For example, they might implement the operation add with the
inx operator \+" leading to a more intuitive look for addition of integers.
3.6 Excercises
1. ADT Integer.
(a) Why are there no preconditions for operations add and sub?
(b) Obviously, the ADT description of Integer is incomplete. Add meth-
ods mul, div and any other one. Describe their impacts by specifying
pre- and postconditions.
2. Design an ADT Fraction which describes properties of fractions.
(a) What data structures can be used? What are its elements?
(b) What does the interface look like?
(c) Name a few axioms and preconditions.
3. Describe in your own words properties of abstract data types.
4. Why is it necessary to include axioms and preconditions to the denition
of an abstract data type?
5. Describe in your own words the relationship between
instance and abstract data type,
generic abstract data type and corresponding abstract data type,
instances of a generic abstract data type.
20 CHAPTER 3. ABSTRACT DATA TYPES
Chapter 4
Object-Oriented Concepts
Peter Muller
Globewide Network Academy (GNA)
[email protected]
The previous sections already introduce some \object-oriented" concepts.
However, they were applied in an procedural environment or in a verbal manner.
In this section we investigate these concepts in more detail and give them names
as used in existing object-oriented programming languages.
i = 1; /* Assign 1 to integer i */
j = 2; /* Assign 2 to integer j */
k = i + j; /* Assign the sum of i and j to k */
Let's play with the above code fragment and outline the relationship to the
ADT Integer. The rst line denes three instances i, j and k of type Integer.
Consequently, for each instance the special operation constructor should be
called. In our example, this is internally done by the compiler. The compiler
reserves memory to hold the value of an integer and \binds" the corresponding
21
22 CHAPTER 4. OBJECT-ORIENTED CONCEPTS
name to it. If you refer to i you actually refer to this memory area which
was \constructed" by the denition of i. Optionally, compilers might choose to
initialize the memory, for example, they might set it to 0 (zero).
The next line
i = 1;
sets the value of i to be 1. Therefore we can describe this line with help of the
ADT notation as follows:
Perform operation set with argument 1 on the Integer instance i. This is written
as follows: i.set(1).
We now have a representation at two levels. The rst level is the ADT level
where we express everything what is done to an instance of this ADT by the
invocation of dened operations. At this level, pre- and postconditions are used
to describe what actually happens. In the following example, these conditions
are enclosed in curly brackets.
f Precondition: = where 2 Integer g
i n n
i.set(1)
f Postcondition: = 1 gi
Don't forget that we currently talk about the ADT level! Consequently, the
conditions are mathematical conditions.
The second level is the implementation level, where an actual representation
is chosen for the operation. In C the equal sign \=" implements the set()
operation. However, in Pascal the following representation was chosen:
i := 1;
Obviously, \+" was chosen to implement the add operation. We could read the
part \i + j" as \add the value of j to the value of i", thus at the ADT level this
results in
f Precondition: Let = i n1 and =j n2 with n1 ; n2 2 Integer g
i.add(j)
f Postcondition: = i n1 and = j n2 g
The postcondition ensures that i and j do not change their values. Please recall
the specication of add. It says that a new Integer is created of which the
4.2. CLASS 23
value is the sum. Consequently, we must provide a mechanism to access this
new instance. We do this with the set operation applied on instance k:
f Precondition: Let = where 2 Integer g
k n n
k.set(i.add(j))
f Postcondition: = + g
k i j
4.2 Class
A class is an actual representation of an ADT. It therefore provides implemen-
tation details for the used data structure and operations. We play with the
ADT Integer and design our own class for it:
class Integer {
attributes:
int i
methods:
setValue(int n)
Integer addValue(Integer j)
}
4.4 Message
A running program is a pool of objects where objects are created, destroyed
and interacting. This interacting is based on messages which are sent from one
object to another asking the recipient to apply a method on itself. To give you
an understanding of this communication, let's come back to the class Integer
presented in section 4.2. In our pseudo programming language we could create
new objects and invoke methods on them. For example, we could use
Integer i; /* Define a new integer object */
i.setValue(1); /* Set its value to 1 */
to express the fact, that the integer object i should set its value to 1. This
is the message \Apply method setValue with argument 1 on yourself." sent to
object i. We notate the sending of a message with \.". This notation is also
4.5. SUMMARY 25
used in C++; other object-oriented languages might use other notations, for
example \- ".
>
4.5 Summary
To view a program as a collection of interacting objects is a fundamental prin-
ciple in object-oriented programming. Objects in this collection react upon
receipt of messages, changing their state according to invocation of methods
which might cause other messages sent to other objects. This is illustrated in
Figure 4.1.
Program
object 3 object 1
object 4
object 2
4.6 Excercises
1. Class.
(a) What distinguishes a class from an ADT?
(b) Design a class for the ADT Complex. What representations do you
choose for the ADT operations? Why?
2. Interacting objects. Have a look to your tasks of your day life. Choose
one which does not involve too many steps (for example, watching TV,
cooking a meal, etc.). Describe this task in procedural and object-oriented
form. Try to begin viewing the world to consist of objects.
3. Object view. Regarding the last excercise, what problems do you en-
counter?
4. Messages.
(a) Why do we talk about \messages" rather than \procedure calls"?
(b) Name a few messages which make sense in the Internet environment.
(You must therefore identify objects.)
(c) Why makes the term \message" more sense in the environment of
the last excercise, than the term \procedure call"?
Chapter 5
More Object-Oriented
Concepts
Peter Muller
Globewide Network Academy (GNA)
[email protected]
Whereas the previous lecture introduces the fundamental concepts of object-
oriented programming, this lecture presents more details about the object-
oriented idea. This section is mainly adopted from [2]1.
5.1 Relationships
In excercise 3.6.5 you already investigate relationships between abstract data
types and instances and describe them in your own words. Let's go in more
detail here.
A-Kind-Of relationship
Consider you have to write a drawing program. This program would allow
drawing of various objects such as points, circles, rectangles, triangles and many
more. For each object you provide a class denition. For example, the point
class just denes a point by its coordinates:
class Point {
attributes:
int x, y
methods:
1 This book is only available in German. However, since this is one of the best books about
object-oriented programming I know of, I decided to cite it here.
27
28 CHAPTER 5. MORE OBJECT-ORIENTED CONCEPTS
setX(int newX)
getX()
setY(int newY)
getY()
}
You continue dening classes of your drawing program with a class to describe
circles. A circle denes a center point and a radius:
class Circle {
attributes:
int x, y,
radius
methods:
setX(int newX)
getX()
setY(int newY)
getY()
setRadius(newRadius)
getRadius()
}
Comparing both class denitions we can observe the following:
Both classes have two data elements x and y. In the class Point these
elements describe the position of the point, in the case of class Circle they
describe the circle's center. Thus, x and y have the same meaning in both
classes: They describe the position of their associated object by dening
a point.
Both classes oer the same set of methods to get and set the value of the
two data elements x and y.
Class Circle \adds" a new data element radius and corresponding access
methods.
Knowing the properties of class Point we can describe a circle as a point plus
a radius and methods to access it. Thus, a circle is \a-kind-of" point. However,
a circle is somewhat more \specialized". We illustrate this graphically as shown
in Figure 5.1.
a-kind-of
Circle Point
Part-Of relationship
You sometimes need to be able to build objects by combining them out of
others. You already know this from procedural programming, where you have
the structure or record construct to put data of various types together.
Let's come back to our drawing program. You already have created several
classes for the available gures. Now you decide that you want to have a special
gure which represents your own logo which consists of a circle and a triangle.
(Let's assume, that you already have dened a class Triangle.) Thus, your logo
consists of two parts or the circle and triangle are part-of your logo:
class Logo {
attributes:
Circle circle
Triangle triangle
methods:
set(Point where)
}
5.2 Inheritance
With inheritance we are able to make use of the a-kind-of and is-a relationship.
As described there, classes which are a-kind-of another class share properties of
the latter. In our point and circle example, we can dene a circle which inherits
from point:
class Circle inherits from Point {
attributes:
int radius
methods:
setRadius(int newRadius)
getRadius()
}
Class Circle inherits all data elements and methods from point. There is no
need to dene them twice: We just use already existing and well-known data
and method denitions.
On the object level we are now able to use a circle just as we would use a
point, because a circle is-a point. For example, we can dene a circle object and
set its center point coordinates:
Circle acircle
acircle.setX(1) /* Inherited from Point */
acircle.setY(2)
acircle.setRadius(3) /* Added by Circle */
\Is-a" also implies, that we can use a circle everywhere where a point is expected.
For example, you can write a function or method, say move(), which should move
a point in x direction:
move(Point apoint, int deltax) {
apoint.setX(apoint.getX() + deltax)
}
5.2. INHERITANCE 31
As a circle inherits from a point, you can use this function with a circle argument
to move its center point and, hence, the whole circle:
Circle acircle
...
move(acircle, 10) /* Move circle by moving */
/* its center point */
Point
inherit-from
Circle
Point String
DrawableString
Figure 5.6: Derive a drawable string which inherits properties of Point and
String.
In our pseudo language we write this by simply separating the multiple super-
classes by comma:
class DrawableString inherits from Point, String {
attributes:
/* All inherited from superclasses */
methods:
5.3. MULTIPLE INHERITANCE 33
/* All inherited from superclasses */
}
We can use objects of class DrawableString like both points and strings. Because
a drawablestring is-a point we can move them around
DrawableString dstring
...
move(dstring, 10)
...
tance. This may introduce naming con
icts in A if at least two of its super-
classes dene properties with the same name.
The above denition introduce naming con
icts which occur if more than one
superclass of a subclass use the same name for either attributes or methods. For
an example, let's assume, that class String denes a method setX() which sets
te string to a sequence of \X" characters3 . The question arises, what should be
inherited by DrawableString? The Point, String version or none of them?
These con
icts can be solved in at least two ways:
The order in which the superclasses are provided dene which property
will be accessible by the con
ict causing name. Others will be \hidden".
The subclass must resolve the con
ict by providing a property with the
name and by dening how to use the ones from its superclasses.
The rst solution is not very convenient as it introduces implizit consequences
depending on the order in which classes inherit from each other. For the sec-
ond case, subclasses must explicitly redene properties which are involved in a
naming con
ict.
A special type of naming con
ict is introduced if a class D multiply inherits
from superclasses B and C which themselves are derived from one superclass A.
This leads to an inheritance graph as shown in Figure 5.7.
The question arises what properties class D actually inherits from its super-
classes B and C. Some existing programming languages solve this special inher-
itance graph by deriving D with
3 Don't argue whether such a method makes really sense or not. It is just introduced for
illustrating purposes.
34 CHAPTER 5. MORE OBJECT-ORIENTED CONCEPTS
B C
methods:
print()
}
We introduce the new keyword abstract here. It is used to express the fact that
derived classes must \redene" the properties to fulll the desired functionality.
Thus from the abstract class' point of view, the properties are only specied but
not fully dened. The full denition including the semantics of the properties
must be provided by derived classes.
Now, every class in our drawing program example inherits properties from
the general drawable object class. Therefore, class Point changes to:
class Point inherits from DrawableObject {
attributes:
int x, y
methods:
setX(int newX)
getX()
setY(int newY)
getY()
print() /* Redefine for Point */
}
We are now able to force every drawable object to have a method called print
which should provide functionality to draw the object within the drawing area.
The superclass of all drawable objects, class DrawableObject, does not provide
any functionality for drawing itself. It is not intended to create objects from
it. This class rather species properties which must be dened by every derived
class. We refer to this special type of classes as abstract classes:
Denition 5.4.1 (Abstract Class) A class A is called abstract class if it
is only used as a superclass for other classes. Class A only species properties.
It is not used to create objects. Derived classes must dene the properties of A.
Abstract classes allow us to structure our inheritance graph. However, we actu-
ally don't want to create objects from them: we only want to express common
characteristics of a set of classes.
36 CHAPTER 5. MORE OBJECT-ORIENTED CONCEPTS
5.5 Excercises
1. Inheritance. Consider the drawing program example again.
(a) Dene class Rectangle by inheriting from class Point. The point
should indicate the upper left corner of the rectangle. What are your
class attributes? What additional methods do you introduce?
(b) All current examples are based on a two-dimensional view. You now
want to introduce 3D objects such as spheres, cubes or cuboids. De-
sign a class Sphere by using a class 3D-Point. Specify the role of the
point in a sphere. What relationship do you use between class Point
and 3D-Point?
(c) What functionality does move() provide for 3D objects? Be as precise
as you can.
(d) Draw the inheritance graph including the following classes Draw-
ableObject, Point, Circle, Rectangle, 3D-Point and Sphere.
(e) Have a look at the inheritance graph of Figure 5.8.
Point
Circle
Sphere
methods:
setZ(int newZ)
getZ()
}
A A
B C
What naming con
icts can occur? Try to dene cases by playing with
simple example classes.
38 CHAPTER 5. MORE OBJECT-ORIENTED CONCEPTS
Chapter 6
Even More Object-Oriented
Concepts
Peter Muller
Globewide Network Academy (GNA)
[email protected]
We continue with our tour through the world of object-oriented concepts by
presenting a short introduction to static versus dynamic binding. With this, we
can introduce polymorphism as a mechanism which let objects gure out what
to do at runtime. But rst, here is a brief overview about generic types.
39
40 CHAPTER 6. EVEN MORE OBJECT-ORIENTED CONCEPTS
... /* Data structure needed to implement */
/* the list */
methods:
append(T element)
T getFirst()
T getNext()
bool more()
}
The above template class List looks like any other class denition. However,
the rst line declares List to be a template for various types. The identier T
is used as a placeholder for an actual type. For example, append() takes one
element as an argument. The type of this element will be the data type with
which an actual list object is created. For example, we can declare a list object
for apples1 :
List for Apple appleList
Apple anApple,
anotherApple
appleList.append(anotherApple)
appleList.append(anApple)
The rst line declares appleList to be a list for apples. At this time, the
compiler uses the template denition, substitutes every occurrence of T with
Apple and creates an actual class denition for it. This leads to a class denition
similar to the one that follows:
class List {
attributes:
... /* Data structure needed to implement */
/* the list */
methods:
append(Apple element)
Apple getFirst()
Apple getNext()
bool more()
}
This is not exactly, what the compiler generates. The compiler must ensure
that we can create multiple lists for dierent types at any time. For example,
if we need another list for, say pears, we can write:
List for Apple appleList
List for Pear pearList
...
1 Of course, there must be a denition for the type Apple.
6.2. STATIC AND DYNAMIC BINDING 41
In both cases the compiler generates an actual class denition. The reason
why both do not con
ict by their name is that the compiler generates unique
names. However, since this is not viewable to us, we don't go in more detail
here. In any case, if you declare just another list of apples, the compiler can
gure out if there already is an actual class denition and use it or if it has to
be created. Thus,
List for Apple aList
List for Apple anotherList
will create the actual class denition for aList and will reuse it for anoth-
erList. Consequently, both are of the same type. We summarize this in the
following denition:
Denition 6.1.1 (Template Class) If a class A is parameterized with a data
type B, A is called template class. Once an object of A is created, B is replaced
by an actual data type. This allows the denition of an actual class based
on the template specied for A and the actual data type.
We are able to dene template classes with more than one parameter. For ex-
ample, directories are collections of objects where each object can be referenced
by a key. Of course, a directory should be able to store any type of object. But
there are also various possibilities for keys. For instance, they might be strings
or numbers. Consequently, we would dene a template class Directory which is
based on two type parameters, one for the key and one for the stored objects.
The type of i is known as soon as its value is set. In this case, i is of type
integer since we have assigned a whole number to it. Thus, because the content
of i is a whole number, the type of i is integer.
Denition 6.2.2 (Dynamic Binding) If the type T of a variable with name
N is implicitly associated by its content, we say, that N is dynamically bound
to T. The association process is called dynamic binding.
Both bindings dier in the time when the type is bound to the variable. Consider
the following example which is only possible with dynamic binding:
if somecondition() == TRUE then
n := 123
else
n := 'abc'
endif
6.3 Polymorphism
Polymorphism allows an entity (for example, variable, function or object) to
take a variety of representations. Therefore we have to distinguish dierent
types of polymorphism which will be outlined here.
The rst type is similar to the concept of dynamic binding. Here, the type
of a variable depends on its content. Thus, its type depends on the content at
a specic time:
v := 123 /* v is integer */
... /* use v as integer */
v := 'abc' /* v "switches" to string */
... /* use v as string */
6.3. POLYMORPHISM 43
Denition 6.3.1 (Polymorphism (1)) The concept of dynamic binding al-
lows a variable to take dierent types dependent on the content at a particular
time. This ability of a variable is called polymorphism.
Another type of polymorphism can be dened for functions. For example,
suppose you want to dene a function isNull() which returns TRUE if its argu-
ment is 0 (zero) and FALSE otherwise. For integer numbers this is easy:
boolean isNull(int i) {
if (i == 0) then
return TRUE
else
return FALSE
endif
}
However, if we want to check this for real numbers, we should use another
comparison due to the precision problem:
boolean isNull(real r) {
if (r < 0.01 and r > -0.99) then
return TRUE
else
return FALSE
endif
}
In both cases we want the function to have the name isNull. In program-
ming languages without polymorphism for functions we cannot declare these
two functions: The name isNull would be doubly dened. However, if the lan-
guage would take the parameters of the function into account it would work.
Thus, functions (or methods) are uniquely identied by:
the name of the function (or method) and
the types of its parameter list.
Since the parameter list of both isNull functions dier, the compiler is able
to gure out the correct function call by using the actual types of the arguments:
var i : integer
var r : real
i = 0
r = 0.0
...
We would like to use this function with objects of classes derived from Draw-
ableObject:
Circle acircle
Point apoint
Rectangle arectangle
The actual method should be dened by the content of the object o of func-
tion display(). Since this is somewhat complicated, here is a more abstract
example:
class Base {
attributes:
methods:
virtual foo()
bar()
}
methods:
virtual foo()
bar()
}
demo(Base o) {
o.foo()
o.bar()
}
Base abase
Derived aderived
demo(abase)
demo(aderived)
In this example we dene two classes Base and Derive. Each class denes
two methods foo() and bar(). The rst method is dened as virtual. This
means that if this method is invoked its denition should be evaluated by the
content of the object.
We then dene a function demo() which takes a Base object as its argument.
Consequently, we can use this function with objects of class Derived as the is-a
relation holds. We call this function with a Base object and a Derived object,
respectively.
Suppose, that foo() and bar() are dened to just print out their name and
the class in which they are dened. Then the output is as follows:
foo() of Base called.
bar() of Base called.
foo() of Derived called.
bar() of Base called.
Why is this so? Let's see what happens. The rst call to demo() uses a Base
object. Thus, the function's argument is \lled" with an object of class Base.
When it is time to invoke method foo() it's actual functionality is chosen based
on the current content of the corresponding object o. This time, it is a Base
object. Consequently, foo() as dened in class Base is called.
The call to bar() is not subject to this content resolution. It is not marked
as virtual. Consequently, bar() is called in the scope of class Base.
The second call to demo() takes a Derived object as its argument. Thus, the
argument o is lled with a Derived object. However, o itself just represents the
Base part of the provided object aderived.
Now, the call to foo() is evaluated by examining the content of o, hence, it
is called within the scope of Derived. On the other hand, bar() is still evaluated
within the scope of Base.
46 CHAPTER 6. EVEN MORE OBJECT-ORIENTED CONCEPTS
Denition 6.3.3 (Polymorphism (3)) Objects of superclasses can be lled
with objects of their subclasses. Operators and methods of subclasses can be
dened to be evaluated in two contextes:
1. Based on object type, leading to an evaluation within the scope of the
superclass.
2. Based on object content, leading to an evaluation within the scope of the
contained subclass.
The second type is called polymorphism.
Chapter 7
Introduction to C++
Peter Muller
Globewide Network Academy (GNA)
[email protected]
This section is the rst part of the introduction to C++. Here we focus on
C from which C++ was adopted. C++ extends the C programming language
with strong typing, some features and { most importantly { object-oriented
concepts.
47
48 CHAPTER 7. INTRODUCTION TO C++
Type Description Size Domain
char Signed charac- 1 -128..127
ter/byte. Char-
acters are en-
closed in single
quotes.
double Double preci- 8 ca. 10,308..10308
sion number
int Signed integer 4 ,231..231 , 1
oat Floating point 4 ca. 10,38..1038
number
long (int) Signed long 4 ,231..231 , 1
integer
long long (int) Signed very 8 ,263..263 , 1
long integer
short (int) Short integer 2 ,215..215 , 1
unsigned char Unsigned 1 0..255
character/byte
unsigned (int) Unsigned 4 0..232 , 1
integer
unsigned long (int) Unsigned long 4 0..232 , 1
integer
unsigned long long (int) Unsigned very 8 0..264 , 1
long integer
unsigned short (int) Unsigned short 2 0..216 , 1
integer
Table 7.1: Built-in types.
With struct you can combine several dierent types together. In other lan-
guages this is sometimes called a record:
struct date_s {
int day, month, year;
} aDate;
The above denition of aDate is also the declaration of a structure called date s.
We can dene other variables of this type by referencing the sturcture by name:
struct date_s anotherDate;
We do not have to name structures. If we omit the name, we just cannot reuse
it. However, if we name a structure, we can just declare it without dening a
variable:
7.1. THE C PROGRAMMING LANGUAGE 49
struct time_s {
int hour, minute, second;
};
We are able to use this structure as shown for anotherDate. This is very similar
to a type denition known in other languages where a type is declared prior to
the denition of a variable of this type.
Variables must be dened prior to their use. These denitions must occur
before any statement, thus they form the topmost part within a statement block.
7.1.2 Statements
C denes all usual
ow control statements. Statements are terminated by a
semicolon \;". We can group multiple statements into blocks by enclosing them
in curly brackets. Within each block, we can dene new variables:
{
int i; /* Define a global i */
i = 1; /* Assign i the value 0 */
{ /* Begin new block */
int i; /* Define a local i */
i = 2; /* Set its value to 2 */
} /* Close block */
/* Here i is again 1 from the outer block */
}
{
int ix, sum;
sum = 0;
for (ix = 0; ix < 10; ix = ix + 1)
sum = sum + 1;
}
50 CHAPTER 7. INTRODUCTION TO C++
Statement Description
break; Leave current block. Also used to leave
case statement in switch.
continue; Only used in loops to continue with next
loop immediately.
do Execute stmt as long as expr is TRUE.
stmt
while (expr);
[ ] [
for ( expr ; expr ; ] [expr]) This is an abbreviation for a while loop
stmt where the rst expr is the initialization,
the second expr is the condition and the
third expr is the step.
goto label; Jumps to position indicated by label.
The destination is label followed by
colon \:".
if (expr) stmt else [ stmt] IF-THEN-ELSE in C notation
[
return expr ; ] Return from function. If function re-
turns void return should be used with-
out additional argument. Otherwise the
value of expr is returned.
switch (expr) f After evaluation of expr its value is com-
case const-expr: stmts pared with the case clauses. Execution
case const-expr: stmts continues at the one that matches. BE-
... WARE: You must use break to leave
[default: stmts] the switch if you don't want execution
g of following case clauses! If no case
clause matches and default clause ex-
ists, its statements are executed.
while (expr) stmt Repeat stmt as long as expr is TRUE.
Table 7.2: Statements.
What happens? The rst assignment assigns kx the value of its righthand side.
This is the value of the assignment to jx. But this is the value of the assignment
to ix. The value of this latter is 12 which is returned to jx which is returned to
kx. Thus we have expressed
ix = 12;
jx = 12;
kx = 12;
in one line.
Truth in C is dened as follows. The value 0 (zero) stands for FALSE. Any
other value is TRUE. For example, the standard function strcmp() takes to
strings as argument and returns -1 if the rst is lower than the second, 0 if they
are equal and 1 if the rst is greater than the second one. To compare if two
strings str1 and str2 are equal you often see the following if construct:
if (!strcmp(str1, str2)) {
/* str1 == str2 */
}
else {
/* str1 != str2 */
}
The exclamation mark indicates the boolean NOT. Thus the expression evalu-
ates to TRUE only if strcmp() returns 0.
Expressions are combined of both terms and operators. The rst could be
constansts, variables or expressions. From the latter, C oers all operators
known from other languages. However, it oers some operators which could be
viewed as abbreviations to combinations of other operators. Table 7.3 lists avail-
able operators. The second column shows their priority where smaller numbers
indicate higher priority and same numbers, same priority. The last column lists
the order of evaluation.
Most of these operators are already known to you. However, some need some
more description. First of all notice that the binary boolean operators &, ^ and
j are of lower priority than the equality operators == and !=. Consequently, if
you want to check for bit patterns as in
if ((pattern & MASK) == MASK) {
...
}
a = a + 1;
b = a;
Thus, the preincrement operator rst increments its associated variable and
then returns the new value, whereas the postincrement operator rst returns
the value and then increments its variable. The same rules apply to the pre-
and postdecrement operator ,,.
Function calls, nested assignments and the increment/decrement operators
cause side eects when they are applied. This may introduce compiler dependen-
cies as the evaluation order in some situations is compiler dependent. Consider
the following example which demonstrates this:
a[i] = i++;
The question is, whether the old or new value of i is used as the subscript into
the array a depends on the order the compiler uses to evaluate the assignment.
The conditional operator ?: is an abbreviation for a commonly used if
statement. For example to assign max the maximum of a and b we can use the
following if statement:
if (a > b)
max = a;
else
max = b;
The next unusual operator is the operator assignment. We are often using
assignments of the following form
expr1 = (expr1) op (expr2)
for example
i = i * (j + 1);
In these assignments the lefthand value also appears on the right side. Using
informal speech we could express this as \set the value of i to the current value
of i multiplied by the sum of the value of j and 1". Using a more natural way,
we would rather say \Multiply i with the sum of the value of j and 1". C allows
us to abbreviate these types of assignments to
54 CHAPTER 7. INTRODUCTION TO C++
i *= j + 1;
We can do that with almost all binary operators. Note, that the above op-
erator assignment really implements the long form although \j + 1" is not in
parenthesis.
The last unusal operator is the comma operator ,. It is best explained by
an example:
i = 0;
j = (i += 1, i += 2, i + 3);
This operator takes its arguments and evaluates them from left to right and
returns the value of the rightmost expression. Thus, in the above example, the
operator rst evaluates \i += 1" which, as a side eect, increments the value
of i. Then the next expression \i += 2" is evaluated which adds 2 to i leading
to a value of 3. The third expression is evaluated and its value returned as the
operator's result. Thus, j is assigned 6.
The comma operator introduces a particular pitfall when using n-
dimensional arrays with n > 1. A frequent error is to use a comma separated
list of indices to try to access an element:
int matrix[10][5]; // 2-dim matrix
int i;
...
i = matrix[1,2]; // WON'T WORK!!
i = matrix[1][2]; // OK
What actually happens in the rst case is, that the comma separated list is
interpreted as the comma operator. Consequently, the result is 2 which leads
to an assignment of the address to the third ve elements of the matrix!
Some of you might wonder, what C does with values which are not used.
For example in the assignment example above, we have three lines which each
return 12. The answer is, that C ignores values which are not used. This leads
to some strange things. For example, you could write something like this:
ix = 1;
4711;
jx = 2;
But let's forget about these strange things. Let's come back to something more
useful. Let's talk about functions.
7.1.4 Functions
As C is a procedural language it allows the denition of functions. Procedures
are \simulated" by functions returning \no value". This value is a special type
called void.
7.1. THE C PROGRAMMING LANGUAGE 55
Functions are declared similar to variables, but they enclose their arguments
in parenthesis (even if there are no arguments, the parenthesis must be speci-
ed):
int sum(int to); /* Declaration of function sum with one */
/* argument */
int bar(); /* Declaration of function bar with no */
/* argument */
void foo(int ix, int jx);
/* Declaration of function foo with two */
/* arguments */
You access the content of a pointer by dereferencing it using again the asterisk:
*strp = 'a'; /* A single character */
As in other languages, you must provide some space for the value to which
the pointer points. A pointer to characters can be used to point to a sequence
of characters: the string. Strings in C are terminated by a special character
NUL (0 or as char 'n0'). Thus, you can have strings of any length. Strings are
enclosed in double quotes:
strp = "hello";
56 CHAPTER 7. INTRODUCTION TO C++
In this case, the compiler automatically adds the terminating NUL character.
Now, strp points to a sequence of 6 characters. The rst character is `h', the
second `e' and so forth. We can access these characters by an index in strp:
strp[0] /* h */
strp[1] /* e */
strp[2] /* l */
strp[3] /* l */
strp[4] /* o */
strp[5] /* \0 */
The rst character also equals \*strp" which can be written as \*(strp + 0)".
This leads to something called pointer arithmetic and which is one of the pow-
erful features of C. Thus, we have the following equations:
*strp == *(strp + 0) == strp[0]
*(strp + 1) == strp[1]
*(strp + 2) == strp[2]
...
Note that these equations are true for any data type. The addition is not
oriented to bytes, it is oriented to the size of the corresponding pointer type!
The strp pointer can be set to other locations. Its destination may vary.
In contrast to that, arrays are x pointers. They point to a predened area of
memory which is specied in brackets:
char str[6];
because this would mean, to change the pointer to point to 'h'. We must copy
the string into the provided memory area. We therefore use a function called
strcpy() which is part of the standard C library.
strcpy(str, "hallo"); /* Ok */
Note however, that we can use str in any case where a pointer to a character is
expected, because it is a (xed) pointer.
int
main() {
puts("Hello, world!");
return 0;
} /* main */
The rst line looks something strange. Its explanation requires some informa-
tion about how C (and C++) programs are handled by the compiler. The
compilation step is roughly divided into two steps. The rst step is called \pre-
processing" and is used to prepare raw C code. In this case this step takes the
rst line as an argument to include a le called stdio.h into the source. The an-
gle brackets just indicate, that the le is to be searched in the standard search
path congured for your compiler. The le itself provides some declarations
and denitions for standard input/output. For example, it declares a function
called put(). The preprocessing step also deletes the comments.
In the second step the generated raw C code is compiled to an executable.
Each executable must dene a function called main(). It is this function which
is called once the program is started. This function returns an integer which is
returned as the program's exit status.
Function main() can take arguments which represent the command line pa-
rameters. We just introduce them here but do not explain them any further:
#include <stdio.h>
int
main(int argc, char *argv[]) {
int ix;
for (ix = 0; ix < argc; ix++)
printf("My %d. argument is %s\n", ix, argv[ix]);
return 0;
} /* main */
The rst argument argc just returns the number of arguments given on the
command line. The second argument argv is an array of strings. (Recall that
strings are represented by pointers to characters. Thus, argv is an array of
pointers to characters.)
We also suggest, that you get yourself a good book about C (or to nd one of
the on-line tutorials). We try to explain everything we introduce in the next
sections. However, it is no fault to have some reference at hand.
Chapter 8
From C To C++
Peter Muller
Globewide Network Academy (GNA)
[email protected]
This section presents extensions to the C language which were introduced
by C++ [6]. It also deals with object-oriented concepts and their realization.
59
60 CHAPTER 8. FROM C TO C++
ix = 1; /* also rx == 1 */
rx = 2; /* also ix == 2 */
References can be used as function arguments and return values. This allows
to pass parameters as reference or to return a \handle" to a calculated variable
or object.
The table 8.1 is adopted from [1] and provides you with an overview of
possible declarations. It is not complete in that it shows not every possible
combination and some of them have not been introduced here, because we are
not going to use them. However, these are the ones which you will probably use
very often.
Declaration name is ... Example
type name; type int count;
type name[]; (open) array of type int count[];
type name[n]; array with n elements of type int count[3];
type (name[0], name[1], ...,
name[n-1])
type *name; pointer to type int *count;
type *name[]; (open) array of pointers to type int *count;
type *(name[]); (open) array of pointers to type int *(count);
type (*name)[]; pointer to (open) array of type int (*count)[];
type &name; reference to type int &count;
type name(); function returning type int count();
type *name(); function returning pointer to type int *count();
type *(name()); function returning pointer to type int *(count());
type (*name)(); pointer to function returning type int (*count)();
type &name(); function returning reference to type int &count();
In C and C++ you can use the modier const to declare particular aspects
of a variable (or object) to be constant. The next table 8.2 lists possible combi-
nations and describe their meaning. Subsequently, some examples are presented
which demonstrate the use of const.
Now let's investigate some examples of contant variables and how to use
them. Consider the following declarations (again from [1]):
int i; // just an ordinary integer
int *ip; // uninitialized pointer to
// integer
int * const cp = &i; // constant pointer to integer
const int ci = 7; // constant integer
const int *cip; // pointer to constant integer
8.1. BASIC EXTENSIONS 61
Declaration name is ...
const type name= value; constant type
type * const name= value; constant pointer to type
const type *name = value; (variable) pointer to constant
type
const type * const name = value; constant pointer to constant
type
Table 8.2: Constant declaration expresssions.
When used with references some peculiarities must be considered. See the fol-
lowing example program:
#include <stdio.h>
int main() {
const int ci = 1;
const int &cr = ci;
int &r = ci; // create temporary integer for reference
// cr = 7; // cannot assign value to constant reference
r = 3; // change value of temporary integer
print("ci == %d, r == %d\n", ci, r);
return 0;
}
62 CHAPTER 8. FROM C TO C++
When compiled with GNU g++, the compiler issues the following warning:
conversion from `const int' to `int &' discards const
What actually happens is, that the compiler automatically creates a temporay
integer variable with value of ci to which reference r is initialized. Consequently,
when changing r the value of the temporary integer is changed. This temporary
variable lives as long as reference r.
Reference cr is dened as read-only (constant reference). This disables its
use on the left side of assignments. You may want to remove the comment
in front of the particular line to check out the resulting error message of your
compiler.
8.1.2 Functions
C++ allows function overloading as dened in section 6.3. For example, we can
dene two dierent functions max(), one which returns the maximum of two
integers and one which returns the maximum of two strings:
#include <stdio.h>
int main() {
printf("max(19, 69) = %d\n", max(19, 69));
printf("max(abc, def) = %s\n", max("abc", "def"));
return 0;
}
The above example program denes these two functions which dier in their
parameter list, hence, they dene two dierent functions. The rst printf() call
in function main() issues a call to the rst version of max(), because it takes
two integers as its argument. Similarly, the second printf() call leads to a call
of the second version of max().
References can be used to provide a function with an alias of an actual
function call argument. This enables to change the value of the function call
argument as it is known from other languages with call-by-reference parameters:
void foo(int byValue, int &byReference) {
8.2. FIRST OBJECT-ORIENTED EXTENSIONS 63
byValue = 42;
byReference = 42;
}
void bar() {
int ix, jx;
ix = jx = 1;
foo(ix, jx);
/* ix == 1, jx == 42 */
}
Point apoint;
This declares a class Point and denes an object apoint. You can think of a class
denition as a structure denition with functions (or \methods"). Additionally,
you can specify the access rights in more detail. For example, x and y are
private, because elements of classes are private as default. Consequently, we
explicitly must \switch" the access rights to declare the following to be public.
We do that by using the keyword public followed by a colon: Every element
following this keyword are now accessible from outside of the class.
We can switch back to private access rights by starting a private section with
private:. This is possible as often as needed:
class Foo {
64 CHAPTER 8. FROM C TO C++
// private as default ...
public:
// what follows is public until ...
private:
// ... here, where we switch back to private ...
public:
// ... and back to public.
};
This is exactly what C++ does with struct. Structures are handled like classes.
Whereas elements of classes (dened with class) are private by default, ele-
ments of structures (dened with struct) are public. However, we can also use
private: to switch to a private section in structures.
Let's come back to our class Point. Its interface starts with the public section
where we dene four methods. Two for each coordinate to set and get its value.
The set methods are only declared. Their actual functionality is still to be
dened. The get methods have a function body: They are dened within the
class or, in other words, they are inlined methods.
This type of method denition is useful for small and simple bodies. It also
improve performance, because bodies of inlined methods are \copied" into the
code wherever a call to such a method takes place.
On the contrary, calls to the set methods would result in a \real" function
call. We dene these methods outside of the class declaration. This makes
it necessary, to indicate to which class a method denition belongs to. For
example, another class might just dene a method setX() which is quite dierent
from that of Point. We must be able to dene the scope of the denition; we
therefore use the scope operator \::":
void Point::setX(const int val) {
_x = val;
}
apoint.setX(1); // Initialization
apoint.setY(1);
//
// x is needed from here, hence, we define it here and
// initialize it to the x-coordinate of apoint
//
int x = apoint.getX();
The question arises about how the methods \know" from which object they are
invoked. This is done by implicitly passing a pointer to the invoking object
to the method. We can access this pointer within the methods as this. The
denitions of methods setX() and setY() make use of class members x and
y, respectively. If invoked by an object, these members are \automatically"
mapped to the correct object. We could use this to illustrate what actually
happens:
void Point::setX(const int val) {
this->_x = val; // Use this to reference invoking
// object
}
Here we explicitly use the pointer this to explicitly dereference the invoking
object. Fortunately, the compiler automatically \inserts" these dereferences for
class members, hence, we really can use the rst denitions of setX() and setY().
However, it sometimes make sense to know that there is a pointer this available
which indicates the invoking object.
Currently, we need to call the set methods to initialize a point object1 .
However, we would like to initialize the point when we dene it. We therefore
use special methods called constructors.
8.2.2 Constructors
Constructors are methods which are used to initialize an object at its denition
time. We extend our class Point such that it initializes a point to coordinates
(0, 0):
1 In the following we will drop the word \object" and will speak of \the point".
66 CHAPTER 8. FROM C TO C++
class Point {
int _x, _y;
public:
Point() {
_x = _y = 0;
}
Constructors have the same name of the class (thus they are identied to be
constructors). They have no return value. As other methods, they can take
arguments. For example, we may want to initialize a point to other coordi-
nates than (0, 0). We therefore dene a second constructor taking two integer
arguments within the class:
class Point {
int _x, _y;
public:
Point() {
_x = _y = 0;
}
Point(const int x, const int y) {
_x = x;
_y = y;
}
public:
Point() {
_x = _y = 0;
}
Point(const int x, const int y) {
_x = x;
_y = y;
}
Point(const Point &from) {
_x = from._x;
_y = from._y;
}
With help of constructors we have fullled one of our requirements of imple-
mentation of abstract data types: Initialization at denition time. We still need
a mechanism which automatically \destroys" an object when it gets invalid (for
example, because of leaving its scope). Therefore, classes can dene destructors.
68 CHAPTER 8. FROM C TO C++
8.2.3 Destructors
Consider a class List. Elements of the list are dynamically appended and re-
moved. The constructor helps us in creating an initial empty list. However,
when we leave the scope of the denition of a list object, we must ensure that
the allocated memory is released. We therefore dene a special method called
destructor which is called once for each object at its destruction time:
void foo() {
List alist; // List::List() initializes to
// empty list.
... // add/remove elements
} // Destructor call!
Destruction of objects take place when the object leaves its scope of denition
or is explicitly destroyed. The latter happens, when we dynamically allocate an
object and release it when it is no longer needed.
Destructors are declared similar to constructors. Thus, they also use the
name prexed by a tilde (~) of the dening class:
class Point {
int _x, _y;
public:
Point() {
_x = _y = 0;
}
Point(const int x, const int y) {
_x = xval;
_y = yval;
}
Point(const Point &from) {
_x = from._x;
_y = from._y;
}
9.1 Inheritance
In our pseudo language, we formulate inheritance with \inherits from". In C++
these words are replaced by a colon. As an example let's design a class for 3D
points. Of course we want to reuse our already existing class Point. We start
designing our class as follows:
class Point3D : public Point {
int _z;
public:
Point3D() {
setX(0);
setY(0);
_z = 0;
}
Point3D(const int x, const int y, const int z) {
setX(x);
setY(y);
_z = z;
}
~Point3D() { /* Nothing to do */ }
69
70 CHAPTER 9. MORE ON C++
The leftmost column lists possible access rights for elements of classes. It
also includes a third type protected. This type is used for elements which
should be directly usable in subclasses but which should not be accessible from
the outside. Thus, one could say elements of this type are between private and
public elements in that they can be used within the class hierarchy rooted by
the corresponding class.
The second and third column show the resulting access right of the elements
of a superclass when the subclass is privately and publically derived, respectively.
9.1.2 Construction
When we create an instance of class Point3D its constructor is called. Since
Point3D is derived from Point the constructor of class Point is also called.
However, this constructor is called before the body of the constructor of class
Point3D is executed. In general, prior to the execution of the particular con-
structor body, constructors of every superclass are called to initialize their part
of the created object.
When we create an object with
Point3D point(1, 2, 3);
9.1. INHERITANCE 71
the second constructor of Point3D is invoked. Prior to the execution of the
constructor body, the constructor Point() is invoked, to initialize the point part
of object point. Fortunately, we have dened a constructor which takes no
arguments. This constructor initializes the 2D coordinates x and y to 0 (zero).
As Point3D is only derived from Point there are no other constructor calls
and the body of Point3D(const int, const int, const int) is executed. Here we
invoke methods setX() and setY() to explicitly override the 2D coordinates.
Subsequently, the value of the third coordinate z is set.
This is very unsatisfactory as we have dened a constructor Point() which
takes two arguments to initialize its coordinates to them. Thus we must only
be able to tell, that instead of using the default constructor Point() the param-
terized Point(const int, const int) should be used. We can do that by specifying
the desired constructors after a single colon just before the body of constructor
Point3D():
class Point3D : public Point {
...
public:
Point3D() { ... }
Point3D(
const int x,
const int y,
const int z) : Point(x, y) {
_z = z;
}
...
};
public:
Compound(const int partParameter) : part(partParameter) {
...
}
...
};
This dynamic initialization can also be used with built-in data types. For ex-
ample, the constructors of class Point could be written as:
72 CHAPTER 9. MORE ON C++
Point() : _x(0), _y(0) {}
Point(const int x, const int y) : _x(x), _y(y) {}
You should use this initialization method as often as possible, because it allows
the compiler to create variables and objects correctly initialized instead of cre-
ating them with a default value and to use an additional assignment (or other
mechanism) to set its value.
9.1.3 Destruction
If an object is destroyed, for example by leaving its denition scope, the de-
structor of the corresponding class is invoked. If this class is derived from other
classes their destructors are also called, leading to a recursive call chain.
9.1.4 Multiple Inheritance
C++ allows a class to be derived from more than one superclass, as was already
brie
y mentioned in previous sections. You can easily derive from more than
one class by specifying the superclasses in a comma separated list:
class DrawableString : public Point, public DrawableObject {
...
public:
DrawableString(...) :
Point(...),
DrawableObject(...) {
...
}
~DrawableString() { ... }
...
};
We will not use this type of inheritance in the remainder of this tutorial. There-
fore we will not go into further detail here.
9.2 Polymorphism
In our pseudo language we are able to declare methods of classes to be virtual,
to force their evaluation to be based on object content rather than object type.
We can also use this in C++:
class DrawableObject {
public:
virtual void print();
};
9.2. POLYMORPHISM 73
Class DrawableObject denes a method print() which is virtual. We can derive
from this class other classes:
class Point : public DrawableObject {
...
public:
...
void print() { ... }
};
Again, print() is a virtual method, because it inherits this property from Draw-
ableObject. The function display() which is able to display any kind of drawable
object, can then be dened as:
void display(const DrawableObject &obj) {
// prepare anything necessary
obj.print();
}
When using virtual methods some compilers complain if the corresponding class
destructor is not declared virtual as well. This is necessary when using pointers
to (virtual) subclasses when it is time to destroy them. As the pointer is declared
as superclass normally its destructor would be called. If the destructor is virtual,
the destructor of the actual referenced object is called (and then, recursively,
all destructors of its superclasses). Here is an example adopted from [1]:
class Colour {
public:
virtual ~Colour();
};
The various destructor calls only happen, because of the use of virtual destruc-
tors. If we would have not declared them virtual, each delete would have only
called ~Colour() (because palette[i] is of type pointer to Colour).
This class denition would force every derived class from which objects should
be created to dene a method print(). These method declarations are also called
pure methods.
Pure methods must also be declared virtual, because we only want to
use objects from derived classes. Classes which dene pure methods are called
abstract classes.
public:
Complex() : _real(0.0), _imag(0.0) {}
Complex(const double real, const double imag) :
_real(real), _imag(imag) {}
We would then be able to use complex numbers and to \calculate" with them:
Complex a(1.0, 2.0), b(3.5, 1.2), c;
c = a.add(b);
Here we assign c the sum of a and b. Although absolutely correct, it does not
provide a convenient way of expression. What we would rather like to use is
the well-known \+" to express addition of two complex numbers. Fortunately,
C++ allows us to overload almost all of its operators for newly created types.
For example, we could dene a \+" operator for our class Complex:
class Complex {
...
public:
...
...
};
Thus, the binary operator + only needs one argument. The rst argument is
implicitly provided by the invoking object (in this case a).
However, an operator call can also be interpreted as a usual function call,
as in
c = operator +(a, b);
public:
...
In this case we must dene access methods for the real and imaginary parts be-
cause the operator is dened outside of the class's scope. However, the operator
is so closely related to the class, that it would make sense to allow the operator
to access the private members. This can be done by declaring it to be a friend
of class Complex.
9.5 Friends
We can dene functions or classes to be friends of a class to allow them direct
access to its private data members. For example, in the previous section we
would like to have the function for operator + to have access to the private data
members real and imag of class Complex. Therefore we declare operator + to
be a friend of class Complex:
class Complex {
9.6. HOW TO WRITE A PROGRAM 77
...
public:
...
You should not use friends very often because they break the data hiding prin-
ciple in its fundamentals. If you have to use friends very often it is always a
sign that it is time to restructure your inheritance graph.
.cc
.o
linker libraries
a.out
gcc example.cc
1 This is due to the fact that C++ supports function polymorphism. Therefore the name
mangling must take function parameters into account.
2 This also creates an intermediary preprocessed raw C++ le. A typical sux is .i.
3 This has nothing to do with objects in the object-oriented sense.
4 For example, standard functions such as printf() are provided this way.
9.7. EXCERCISES 79
9.6.2 A Note about Style
Header les are used to describe the interface of implementation les. Conse-
quently, they are included in each implementation le which uses the interface of
the particular implementation le. As mentioned in previous sections this inclu-
sion is achieved by a copy of the content of the header le at each preprocessor
#include statement, leading to a \huge" raw C++ le.
To avoid the inclusion of multiple copies caused by mutual dependencies we
use conditional coding. The preprocessor also denes conditional statements to
check for various aspects of its processing. For example, we can check if a macro
is already dened:
#ifndef MACRO
#define MACRO /* define MACRO */
...
#endif
The lines between #ifndef and #endif are only included, if MACRO is not already
dened. We can use this mechanism to prevent multiple copies:
/*
** Example for a header file which `checks' if it is
** already included. Assume, the name of the header file
** is `myheader.h'
*/
#ifndef __MYHEADER_H
#define __MYHEADER_H
/*
** Interface declarations go here
*/
#endif /* __MYHEADER_H */
MYHEADER H is a unique name for each header le. You might want to follow
the convention of using the name of the le prexed with two underbars. The
rst time the le is included, MYHEADER H is not dened, thus every line is
included and processed. The rst line just denes a macro called MYHEADER H.
If accidentally the le should be included a second time (while processing the
same input le), MYHEADER H is dened, thus everything leading up to the
#endif is skipped.
9.7 Excercises
1. Polymorphism. Explain why
80 CHAPTER 9. MORE ON C++
void display(const DrawableObject obj);
In the rst line we introduce the keyword template which starts every template
declaration. The arguments of a template are enclosed in angle brackets.
Each argument species a placeholder in the following class denition. In our
example, we want class List to be dened for various data types. One could say,
that we want to dene a class of lists2. In this case the class of lists is dened
by the type of objects they contain. We use the name T for the placeholder.
We now use T at any place where normally the type of the actual objects are
1 C++ also allows the denition of function templates. However, as we do not use them,
we will not explain them any further.
2 Do not mix up this use of \class" with the \class denition" used before. Here we mean
with \class" a set of class denitions which share some common properties, or a \class of
classes".
81
82 CHAPTER 10. THE LIST { A CASE STUDY
expected. For example, each list provides a method to append an element to it.
We can now dene this method as shown above with use of T.
An actual list denition must now specify the type of the list. If we stick to
the class expression used before, we have to create a class instance. From this
class instance we can then create \real" object instances:
List<int> integerList;
Here we create a class instance of a List which takes integers as its data elements.
We specify the type enclosed in angle brackets. The compiler now applies the
provided argument \int" and automatically generates a class denition where
the placeholder T is replaced by int, for example, it generates the following
method declaration for append():
void append(const int data);
Templates can take more than one argument to provide more placeholders. For
example, to declare a dictionary class which provides access to its data elements
by a key, one can think of the following declaration:
template <class K, class T>
class Dictionary {
...
public:
...
K getKey(const T from);
T getData(const K key);
...
};
Here we use two placeholders to be able to use dictionaries for various key and
data types.
Template arguments can also be used to generate parameterized class deni-
tions. For example, a stack might be implemented by an array of data elements.
The size of the array could be specied dynamically:
template <class T, int size>
class Stack {
T _store[size];
public:
...
};
Stack<int,128> mystack;
public:
Node(Node *right = NULL) : _right(right) {}
Node(const Node &val) : _right(val._right) {}
A look to the rst version of method right() contains a const just before the
method body. When used in this position, const declares the method to be
constant regarding the elements of the invoking object. Consequently, you are
only allowed to use this mechanism in method declarations or denitions, re-
spectively.
This type of const modier is also used to check for overloading. Thus,
class Foo {
...
int foo() const;
int foo();
};
declare two dierent methods. The former is used in constant contexts whereas
the second is used in variable contexts.
86 CHAPTER 10. THE LIST { A CASE STUDY
Although template class Node implements a simple node it seems to dene
plenty of functionality. We do this, because it is good practice to oer at least
the following functionality for each dened data type:
Copy Constructor. The copy constructor is needed to allow denition of
objects which are initialized from already existing ones.
operator =. Each object should know how to assign other objects (of
the same type) to itself. In our example class, this is simply the pointer
assignment.
operator ==. Each object should know how to compare itself with another
object.
The unequality operator \!=" is implemented by using the denition of the
equality operator. Recall, that this points to the invoking object, thus,
Node a, b;
...
if (a != b) ...
would result in a call to operator !=() with this set to the address of a. We
dereference this using the standard dereference operator \*". Now, *this is an
object of class Node which is compared to another object using operator ==().
Consequently, the denition of operator ==() of class Node is used. Using the
standard boolean NOT operator \!" we negate the result and obtain the truth
value of operator !=().
The above methods should be available for each class you dene. This en-
sures that you can use your objects as you would use any other objects, for
example integers. If some of these methods make no sense for whatever reason,
you should declare them in a private section of the class to explicitly mark them
as not for public use. Otherwise the C++ compiler would substitute standard
operators.
Obviously, real applications require the nodes to carry data. As mentioned
above, this means to specialize the nodes. Data can be of any type, hence, we
are using the template construct.
template <class T>
class DataNode : public Node {
T _data;
public:
DataNode(const T data, DataNode *right = NULL) :
Node(right), _data(data) {}
DataNode(const DataNode &val) :
Node(val), _data(val._data) {}
The above template DataNode simply specializes class Node to carry data of any
type. It adds functionality to access its data element and also oers the same set
of standard functionality: Copy Constructor, operator =() and operator ==().
Note, how we reuse functionality already dened by class Node.
public:
List() : _head(NULL), _tail(NULL) {}
List(const List &val) : _head(NULL), _tail(NULL) {
*this = val;
}
virtual ~List() { flush(); }
virtual void flush();
The constructors initialize the list's elements head and tail to NULL which is
the NUL pointer in C and C++. You should know how to implement the
other methods from your programming experience. Here we only present the
implementation of method putInFront():
is called.
Also notice how we use placeholder T. If we would create a class instance
of class template List, say, List int this would also cause creation of a class
< >
The last line of the class template declaration declares class template List-
Iterator to be a friend of List. We want to separately dene the list's iterator.
However, it is closely related, thus, we allow it to be a friend.
90 CHAPTER 10. THE LIST { A CASE STUDY
10.5 Iterator Implementation
In section 10.2 we have introduced the concept of iterators to traverse through
a data structure. Iterators must implement three properties:
Current element.
Successor function.
Termination condition.
Generally speaking, the iterator successively returns data associated with the
current element. Obviously, there will be a method, say, current() which imple-
ments this functionality. The return type of this method depends on the type of
data stored in the particular data structure. For example, when iterating over
List int the return type should be int.
< >
elements.
The termination condition is implemented by a method, say, terminate(),
which returns TRUE if (and only if) all data elements of the associated data
structure have been visited. As long as succ() can nd an element not yet
visited, this method returns FALSE.
Again we want to specify an abstract iterator class which denes properties
of every iterator. The thoughts above lead to the following declaration:
template <class Data, class Element>
class Iterator {
protected:
Element _start,
_current;
public:
Iterator(const Element start) :
_start(start), _current(start) {}
Iterator(const Iterator &val) :
_start(val._start), _current(val._current) {}
virtual ~Iterator() {}
Again we use the template mechanism to allow the use of the iterator for any
data structure which stores data of type Data and which uses structural elements
of type Element. Each iterator \knows" a starting (structural) element and the
current element. We make both accessible from derived classes because derived
iterators need access to them to implement the following iterator properties.
You should already understand how the constructors operate and why we force
the destructor to be virtual.
Subsequently we specify three methods which should implement the three
properties of an iterator. We also add a method rewind() which simply sets
the current element to the start element. However, complex data structures
(for example hash tables) might require more sophisticated rewind algorithms.
For that reason we also specify this method to be virtual, allowing derived
iterators to redene it for their associated data structure.
The last step in the iterator implementation process is the declaration of
the list iterator. This iterator is highly related to our class template List, for
example, it is clear that the structural elements are class templates DataNode.
The only \open" type is the one for the data. Once again, we use the template
mechanism to provide list iterators for the dierent list types:
template <class T>
class ListIterator : public Iterator<T, DataNode<T> *> {
public:
ListIterator(const List<T> &list) :
Iterator<T, DataNode<T> *>(list._head) {}
ListIterator(const ListIterator &val) :
Iterator<T, DataNode<T> *>(val) {}
The class template ListIterator is derived from Iterator. The type of data is, of
course, the type for which the list iterator is declared, hence, we insert place-
holder T for the iterator's data type Data. The iteration process is achieved
with help of the structural elements of type DataNode. Obviously the starting
element is the head of the list head which is of type DataNode T *. We< >
it has access to the list's private members. We use this to initialize the iterator
to point to the head of the list.
We omit the destructor because we do not have any additional data members
for the list iterator. Consequently, we do nothing special for it. However, the
destructor of class template Iterator is called. Recall that we have to dene this
destructor to force derived classes to also have a virtual one.
The next methods just dene the required three properties. Now that we
have structural elements dened as DataNode T * we use them as follows:
< >
the current element is the data carried by the current structural element,
the successor function is to set the current structural element to its right
neighbour and
the termination condition is to check the current structural element if it
is the NULL pointer. Note that this can happen only in two cases:
1. The list is empty. In this case the current element is already NULL
because the list's head head is NULL.
2. The current element reached the last element. In this case the previ-
ous successor function call set the current element to the right neigh-
bour of the last element which is NULL.
10.6. EXAMPLE USAGE 93
We have also included an overloaded postincrement operator \++". To dis-
tinguish this operator from the preincrement operator, it takes an additional
(anonymous) integer argument. As we only use this argument to declare a cor-
rect operator prototype and because we do not use the value of the argument,
we omit the name of the argument.
The last method is the overloaded assignment operator for list iterators. Sim-
ilar to previous assignment operators, we just reuse already dened assignments
of superclasses; Iterator T ::operator =() in this case.
< >
The other methods and operators, namely rewind(), operator ==() and op-
erator !=() are all inherited from class template Iterator.
ListIterator<int> iter(list);
while (!iter.terminate()) {
printf("%d ", iter.current());
iter.succ();
}
puts("");
return 0;
}
As we have dened a postincrement operator for the list iterator, the loop can
also be written as:
while (!iter.terminate())
print("%d ", iter++);
10.7 Discussion
10.7.1 Separation of Shape and Access Strategies
The presented example focusses on an object-oriented view. In real applications
singly linked lists might oer more functionality. For example, insertion of new
data items should be no problem due to the use of pointers:
94 CHAPTER 10. THE LIST { A CASE STUDY
1. Take the successor pointer of the new element and set it to the element
which should become its right neighbour,
2. Take the successor pointer of the element after which the new element
should be inserted and set it to the new element.
Two simple operations. However, the problem is to designate the element after
which the new element should be inserted. Again, a mechanism is needed which
traverse through the list. This time, however, traversion stops at a particular
element: It is the element where the list (or the data structure) is modied.
Similar to the existence of dierent traversing strategies, one can think of
dierent modication strategies. For example, to create a sorted list, where
elements are sorted in ascending order, use an ascending modier.
These modiers must have access to the list structural elements, and thus,
they would be declared as friends as well. This would lead to the necessity that
every modier must be a friend of its data structure. But who can guarantee,
that no modier is forgotten?
A solution is, that modication strategies are not implemented by \external"
classes as iterators are. Instead, they are implemented by inheritance. If a
sorted list is needed, it is a specialization of the general list. This sorted list
would add a method, say insert(), which inserts a new element according to the
modication strategy.
To make this possible, the presented list template must be changed. Because
now, derived classes must have access to the head and tail node to implement
these strategies. Consequently, head and tail should be protected.
10.7.2 Iterators
The presented iterator implementation assumes, that the data structure is not
changed during the use of an iterator. Consider the following example to illus-
trate this:
List<int> ilist;
int ix;
ListIterator<int> iter(ilist);
while (!iter.terminate()) {
printf("%d ", iter.current());
iter.succ();
}
printf("\n");
ilist.putInFront(0);
10.8. EXCERCISES 95
iter.rewind();
while (!iter.terminate()) {
printf("%d ", iter.current());
iter.succ();
}
printf("\n");
instead of
1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
This is due to the fact, that our list iterator only stores pointers to the
list structural elements. Thus, the start element start is initially set to point
to the location where the list's head node head points to. This simply leads
to two dierent pointers referencing the same location. Consequently, when
changing one pointer as it is done by invoking putInFront() the other pointer is
not aected.
For that reason, when rewinding the iterator after putInFront() the cur-
rent element is set to the start element which was set at the time the iterator
constructor was called. Now, the start element actually references the second
element of the list.
10.8 Excercises
1. Similar to the denition of the postincrement operator in class template
ListIterator, one could dene a preincrement operator as:
T &operator ++() {
succ();
return _current->data();
}
3 http://sf.www.lysator.liu.se/c/chistory.ps
97
98 BIBLIOGRAPHY
Appendix A
Solutions to the Excercises
This section presents example solutions to the excercises of the previous lectures.
int_list_handle_t int_list_create();
BOOL int_list_append(int_list_handle_t this,
int data);
INTEGER int_list_getFirst(int_list_handle_t this);
INTEGER int_list_getNext(int_list_handle_t this);
BOOL int_list_isEmpty(int_list_handle_t this);
END Integer-List;
Data was initialized by a call to list getFirst(). The integer list pro-
cedure int list getFirst() returns an integer, consequently, there is
99
100 APPENDIX A. SOLUTIONS TO THE EXCERCISES
no such thing like an \invalid integer" which we could use for loop
termination checking.
2. Dierences between object-oriented programming and other techniques.
In object-oriented programming objects exchange messages with each
other. In the other programming techniques, data is exchanged between
procedures under control of a main program. Objects of the same kind but
each with its own state can coexist. This contrasts the modular approach
where each module only has one global state.
The operation mul does not require any precondition. That's similar
to add and sub. The postcondition is of course res = N*k. The next
operation div requires k to be not 0 (zero). Consequently, we dene
the following precondition: 6= 0. The last operation abs returns
k
2. ADT Fraction.
(a) A simple fraction consists of numerator and denominator. Both are
integer numbers. This is similar to the complex number example
presented in the section. We could choose at least two data structures
to hold the values: an array or a record.
(b) Interface layout. Remember that the interface is just the set of oper-
ations viewable to the outside world. We could describe an interface
of a fraction in a verbal manner. Consequently, we need operations:
A.2. ABSTRACT DATA TYPES 101
to get the value of nominator/denominator,
to set the value of nominator/denominator,
to add a fraction returning the sum,
to subtract a fraction returning the dierence,
...
(c) Here are some axioms and preconditions for each fraction which also
hold for the ADT:
The denominator must not equal 0 (zero), otherwise the value of
the fraction is not dened.
If the nominator is 0 (zero) the value of the fraction is 0 for any
value of the denominator.
Each whole number can be represented by a fraction of which
the nominator is the number and the denominator is 1.
3. ADTs dene properties of a set of instances. They provide an abstract
view to these properties by providing a set of operations which can be
applied on the instances. It is this set of operations, the interface, which
denes properties of the instances. The use of an ADT is restricted by
axioms and preconditions. Both dene conditions and properties of an
environment in which instances of the ADT can be used.
4. We need to state axioms and to dene preconditions to ensure the correct
use of instances of ADTs. For example, if we do not declare 0 to be
the neutral element of the addition of integers, there could be an ADT
Integer which do something weird when adding 0 to N. This is not what
is expected from an integer. Thus, axioms and preconditions provide a
means to ensure that ADTs \function" as we wish them to.
5. Description of relationships.
(a) An instance is an actual representative of an ADT. It is thus an
\example" of it. Where the ADT declare to use a \signed whole
number" as its data structure, an instance actually holds a value,
say, \-5".
(b) Generic ADTs dene the same properties of their corresponding
ADT. However, they are dedicated to another particular type. For
example, the ADT List denes properties of lists. Thus, we might
have an operation append(elem) which appends a new element elem
to the list. We do not say of what type elem actually is, just that it
will be the last element of the list after this operation. If we now use
a generic ADT List the type of this element is known: it's provided
by the generic parameter.
(c) Instances of the same generic ADT could be viewed as \siblings".
They would be \cousins" of instances of another generic ADT if both
generic ADTs share the same ADT.
102 APPENDIX A. SOLUTIONS TO THE EXCERCISES
A.3 Object-Oriented Concepts
1. Class.
(a) A class is the actual implementation of an ADT. For example, an
ADT for integers might include an operation set to set the value of
its instance. This operation is implemented dierently in languages
such as C or Pascal. In C the equal sign \=" denes the set operation
for integers, whereas in Pascal the character string \:=" is used.
Consequently, classes implement operations by providing methods.
Similarly, the data structure of the ADT is implemented by attributes
of the class.
(b) Class Complex
class Complex {
attributes:
Real real,
imaginary
methods:
:=(Complex c) /* Set value to the one of c */
Real realPart()
Real imaginaryPart()
Complex +(Complex c)
Complex -(Complex c)
Complex /(Complex c)
Complex *(Complex c)
}
We choose the well-known operator symbols \+" for addition, \-" for
subtraction, \/" for division and \*" for multiplication to implement
the corresponding operations of the ADT Complex. Thus, objects of
class Complex can be used like:
Complex c1, c2, c3
c3 := c1 + c2
You may notice, that we could write the addition statement as fol-
lows:
c3 := c1.+(c2)
You may want to replace the \+" with \add" to come to a repre-
sentation which we have used so far. However, you should be able
to understand that \+" is nothing more than a dierent name for
\add".
2. Interacting objects.
3. Object view.
A.4. MORE OBJECT-ORIENTED CONCEPTS 103
4. Messages.
(a) Objects are autonomous entities which only provide a well-dened
interface. We'd like to talk of objects as if they are active entities.
For example, objects \are responsible" for themselves, \they" might
deny invocation of a method, etc.. This distinguishes an object from
a module, which is passive. Therefore, we don't speak of procedure
calls. We speak of messages with which we \ask" an object to invoke
one of its methods.
(b) The Internet provides several objects. Two of the most well known
ones are \client" and \server". For example, you use an FTP client
(object) to access data stored on an FTP server (object). Thus, you
could view this as if the client \sends a message" to the server asking
for providing data stored there.
(c) In the client/server environment we really have two remotely acting
entities: the client and server process. Typically, these two entities
exchange data in form of Internet messages.
methods:
setWidth(int newWidth)
getWidth()
setHeight(int newHeight)
getHeight()
}
methods:
setRadius(int newRadius)
getRadius()
}
This is similar to the circle class for 2D space. Now, 3D-Point is just
a Point with an additional dimension:
class 3D-Point inherits from Point {
attributes:
int _z;
methods:
setZ(int newZ);
getZ();
}
Consequently, 3D-Point and Point are related with a is-a relationship.
(c) Functionality of move(). move() as dened in the section allows 3D
objects to move on the X-axis, thus only in one dimension. It does
this, by modifying only the 2D part of 3D objects. This 2D part
is dened by the Point class inherited directly or indirectly by 3D
objects.
(d) Inheritance graph (see Figure A.1).
DrawableObject
Point
Sphere
Here we use the equation operator ,,==\ to compare both data items.
As these items can be of any type, they especially can be objects of user
dened classes. The question is: How is ,,equality\ dened for those new
types? Consequently, to allow remove() to work properly, the list should
only be used for types which dene the comparison operator (namely,
,,==\ and ,,!=\) properly. Otherwise, default comparisons are used, which
might lead to strange results.
3. Class CountedList. A counted list is a list, which keeps track of the num-
ber of elements in it. Thus, when a data item is added, the number is
incremented by one, when an item is deleted it is decremented by one.
Again, we do not give the complete implementation, we rather show one
method (append()) and how it is altered:
class CountedList : public List {
int _count; // The number of elements
...
public:
...
virtual void append(const T data) {
_count++; // Increment it and ...
List::append(data); // ... use list append
}
...
}
Not every method can be implemented this way. In some methods, one
must check whether count needs to be altered or not. However, the main
idea is, that each list method is just expanded (or specialized) for the
counted list.
4. Iterator problem. To solve the iterator problem one could think of a
solution, where the iterator stores a reference to its corresponding list. At
A.6. THE LIST { A CASE STUDY 107
iterator creation time, this reference is then initialized to reference the
provided list. The iterator methods must then be modied to use this
reference instead of the pointer start.