Inheritance and virtual functions:
As a parameter, a class object can be passed either by value or by
reference.
Earlier chapters also said that the types of the actual and formal
parameters must match. However, in the case of classes, C++ allows the
user to pass an object of a derived class to a formal parameter of the base
class type.
class baseClass
public:
void print();
baseClass(int u = 0);
private:
int x;
};
class derivedClass: public baseClass
public:
void print();
derivedClass(int u = 0, int v = 0);
private:
int a;
};
void baseClass::print()
cout << "In baseClass x = " << x << endl;
baseClass::baseClass(int u)
{
x = u;
void derivedClass::print()
cout << "In derivedClass ***: ";
baseClass::print();
cout << "In derivedClass a = " << a << endl;
derivedClass::derivedClass(int u, int v)
: baseClass(u)
a = v;
Consider the following function in a user program (client code):
void callPrint(baseClass& p)
[Link]();
The function callPrint has a formal reference parameter p of type
baseClass. You
can call the function callPrint by using an object of either type baseClass
or type
derivedClass as a parameter. Moreover, the body of the function callPrint
calls the
member function print. Consider the following function main:
int main() //Line 1
{ //Line 2
baseClass one(5); //Line 3
derivedClass two(3, 15); //Line 4
[Link](); //Line 5
[Link](); //Line 6
cout << "*** Calling the function "
<< "callPrint ***" << endl; //Line 7
callPrint(one); //Line 8
callPrint(two); //Line 9
return 0; //Line 10
} //Line 11
Sample Run:
In baseClass x = 5
In derivedClass ***: In baseClass x = 3
In derivedClass a = 15
*** Calling the function callPrint ***
In baseClass x = 5
In baseClass x = 3
The output generated by the statements in Lines 8 and 9 shows only the
value of x, even though in these statements a different class object is
passed as a parameter. (Because in Line 9 object two is passed as a
parameter to the function callPrint, one would expect that the output
generated by the statement in Line 9 should be similar to the output in
the second and third lines of the output.)
What actually occurred is that for both statements (Lines 8 and 9), the
member function print of the class baseClass is executed. This is due to
the fact that the binding of the member function print, in the body of the
function callPrint, occurred at compile time. Because the formal parameter
p of the function callPrint is of type baseClass, for the statement [Link]();,
the compiler associates the function print of the class baseClass. More
specifically, in compile-time binding, the necessary code to call a specific
function is generated by the compiler. (Compile-time binding is also known
as static binding.)
We suppose to get the corresponding output when we pass objecttwo. C+
+ corrects this problem by providing the mechanism of virtual functions.
The binding of virtual functions occurs at program execution time, not at
compile time. This kind of binding is called run-time binding. More
formally, in run-time binding, the compiler does not generate the code to
call a specific function. Instead, it generates enough information to enable
the run-time system to generate the specific code for the appropriate
function call. Run-time binding is also known as dynamic binding.
class baseClass
public:
virtual void print(); //virtual function
baseClass(int u = 0);
private:
int x;
};
class derivedClass: public baseClass
public:
void print();
derivedClass(int u = 0, int v = 0);
private:
int a;
};
Note that we need to declare a virtual function only in the base class.
The definition of the member function print is the same as before. If we
execute the
previous program with these modifications, the output is as follows.
Sample Run:
In baseClass x = 5
In derivedClass ***: In baseClass x = 3
In derivedClass a = 15
*** Calling the function callPrint ***
In baseClass x = 5
In derivedClass ***: In baseClass x = 3
In derivedClass a = 15
#include <iostream> //Line 1
#include "derivedClass.h" //Line 2
using namespace std; //Line 3
void callPrint(baseClass *p); //Line 4
int main() //Line 5
{ //Line 6
baseClass *q; //Line 7
derivedClass *r; //Line 8
q = new baseClass(5); //Line 9
r = new derivedClass(3, 15); //Line 10
q->print(); //Line 11
r->print(); //Line 12
cout << "*** Calling the function "
<< "callPrint ***" << endl; //Line 13
callPrint(q); //Line 14
callPrint(r); //Line 15
return 0; //Line 16
} //Line 17
void callPrint(baseClass *p)
p->print();
}
Sample Run:
In baseClass x = 5
In derivedClass ***: In baseClass x = 3
In derivedClass a = 15
*** Calling the function callPrint ***
In baseClass x = 5
In derivedClass ***: In baseClass x = 3
In derivedClass a = 15
In case of passing value parameter:
However, if p is a value parameter, then this mechanism of passing a
derived class object as an actual parameter to p does not work, even if p
uses a virtual function. Recall that, if a formal parameter is a value
parameter, the value of the actual parameter is copied into the formal
parameter. Therefore, if a formal parameter is of a class type, the member
variables of the actual object are copied into the corresponding member
variables of theformal parameter. Suppose that we have the classes
defined above—that is, baseClass and derivedClass. Consider the
following function definition: void callPrint(baseClass p) //p is a value
parameter.
[Link]();
Further suppose that we have the following declaration:
derivedClass two;
The object two has two member variables, x and a. The member variable
x is inherited
from the base class. Consider the following function call:
callPrint(two);
In this statement, because the formal parameter p is a value parameter,
the member
variables of two are copied into the member variables of p. However,
because p is an
object of type baseClass, it has only one member variable. Consequently,
only the
member variable x of two will be copied into the member variable x of p.
Also, the
statement:
[Link]();
in the body of the function will result in executing the member function
print of the
class baseClass.
//*******************************************************
// This program illustrates how virtual functions and a
// pointer variable of base class as a formal parameter
// work.
//*******************************************************
#include <iostream> //Line 1
#include "derivedClass.h" //Line 2
using namespace std; //Line 3
void callPrint(baseClass p); //Line 4
int main() //Line 5
{ //Line 6
baseClass one(5); //Line 7
derivedClass two(3, 15); //Line 8
[Link](); //Line 9
[Link](); //Line 10
cout << "*** Calling the function "
<< "callPrint ***" << endl; //Line 11
callPrint(one); //Line 12
callPrint(two); //Line 13
return 0; //Line 14
} //Line 15
void callPrint(baseClass p) //p is a value parameter
[Link]();
Sample Run:
In baseClass x = 5
In derivedClass ***: In baseClass x = 3
In derivedClass a = 15
*** Calling the function callPrint ***
In baseClass x = 5
In baseClass x = 3
Note:
An object of the base class type cannot be passed to a formal
parameter of the derived class type.
Classes and Virtual Destructors
One thing recommended for classes with pointer member variables is that
these classes
should have the destructor. The destructor is automatically executed
when the class
object goes out of scope. Thus, if the object creates dynamic objects, the
destructor
can be designed to deallocate the storage for them. If a derived class
object is passed to a formal parameter of the base class type, the
destructor of the base class executes regardless of whether the derived
class object is passed by reference or by value. Logically, however, the
destructor of the derived class should be executed when the derived class
object goes out of scope. To correct this problem, the destructor of the
base class must be virtual. The virtual destructor of a base class
automatically makes the destructor of a derived class virtual. When a
derived class object is passed to a formal parameter of the base class
type, then when the object goes out of scope, the destructor of the
derived class executes. After executing the destructor of the derived class,
the destructor of the base class executes. Therefore, when the derived
class object is destroyed, the base class part (that is, the members
inherited from the base class) of the derived class object is also
destroyed.
Abstract Classes and Pure Virtual Functions
From the class shape you can derive other classes such as rectangle,
circle, ellipse, and
so on. Some of the things common to every shape are its center, using the
center to move
a shape to a different location, and drawing the shape. Among others, we
can include
these in the class shape. For example, you could have the definition of the
class
shape similar to the following:
class shape
public:
virtual void draw();
//Function to draw the shape.
virtual void move(double x, double y);
//Function to move the shape at the position (x, y).
.
};
Because the definitions of the functions draw and move are specific to a
particular shape,
each derived class can provide an appropriate definition of these
functions. Because we do not want to include the definitions of the
functions draw and move of the
class shape, we must convert these functions to pure virtual functions. In
this case,
the prototypes of these functions are:
virtual void draw() = 0;
virtual void move(double x, double y) = 0;
Once a class contains one or more pure virtual functions, then that class is
called an abstract
class. Thus, the abstract definition of the class shape is similar to the
following:
class shape
public:
virtual void draw() = 0;
//Function to draw the shape. Note that this is a
//pure virtual function.
virtual void move(double x, double y) = 0;
//Function to move the shape at the position (x, y).
//Note that this is a pure virtual function.
};
Because an abstract class is not a complete class, as it (or its
implementation file) does not contain the definitions of certain functions,
you cannot create objects of that class. Note that in addition to the pure
virtual functions, an abstract class can contain instance variables,
constructors, and functions that are not pure virtual. However, the
abstract class must provide the definitions of constructor and functions
that are not pure virtual.
Array-Based Lists
List: A collection of elements of the same type.
The length of a list is the number of elements in the list.
Following are some of the operations performed on a list:
1. Create the list. The list is initialized to an empty state.
2. Determine whether the list is empty.
3. Determine whether the list is full.
4. Find the size of the list.
5. Destroy, or clear, the list.
6. Determine whether an item is the same as a given list element.
7. Insert an item in the list at the specified location.
8. Remove an item from the list at the specified location.
9. Replace an item at the specified location with another item.
10. Retrieve an item from the list from the specified location.
11. Search the list for a given item.
To maintain and process the list in an array, we need the following three
variables:
• The array holding the list elements
• A variable to store the length of the list (that is, the number of list
elements currently in the array)
• A variable to store the size of the array (that is, the maximum number of
elements that can be stored in the array)
UML class diagram of the class arrayListType.
1, IsEmty
template <class elemType>
bool arrayListType<elemType>::isEmpty() const
return (length == 0);
template <class elemType>
bool arrayListType<elemType>::isFull() const
return (length == maxSize);
2. Listsize
template <class elemType>
int arrayListType<elemType>::listSize() const
return length;
3. maxListSize
template <class elemType>
int arrayListType<elemType>::maxListSize() const
return maxSize;
4. print
The member function print outputs the elements of the list. We assume
that the output
is sent to the standard output device.
template <class elemType>
void arrayListType<elemType>::print() const
for (int i = 0; i < length; i++)
cout << list[i] << " ";
cout << endl;
5. sItemAtEqual
template <class elemType>
bool arrayListType<elemType>::isItemAtEqual
(int location, const elemType& item) const
return(list[location] == item);
The body of function isItemAtEqual has only one statement, which is a
comparison
statement. It is easy to see that this function is of O(1).
6. InsertAt
The function insertAt inserts an item at a specific location in the list. The
item to beinserted and the insert location in the array are passed as
parameters to this function. To insert the item somewhere in the middle of
the list, we must first make room for the new item. That is, we need to
move certain elements right one array slot. Suppose that the data
member list of an arrayListType object is as shown in Figure. The number
of elements currently in the list is 6, so length is 6. Thus, after inserting a
new element, the length of the list is 7. If the item is to be inserted at, say
location 6, we can easily accomplish this by copying the item into list[6].
On the other hand, if the item is to be inserted at, say location 3, we first
need to move elements list[3], list[4], and list[5] one array slot right to
make room for the new item. Thus, we must first copy list[5] into list[6],
list[4] into list[5], and list[3] into list[4], in this order. Then we can copy
the new item into list[3].
template <class elemType>
void arrayListType<elemType>::insertAt
(int location, const elemType& insertItem)
if (location < 0 || location >= maxSize)
cerr << "The position of the item to be inserted "
<< "is out of range" << endl;
else
if (length >= maxSize) //list is full
cerr << "Cannot insert in a full list" << endl;
else
for (int i = length; i > location; i--)
list[i] = list[i - 1]; //move the elements down
list[location] = insertItem; //insert the item at the
//specified position
length++; //increment the length
} //end insertAt
7. insertEnd:
This function is called to indsert element at the end
template <class elemType>
void arrayListType<elemType>::insertEnd(const elemType& insertItem)
if (length >= maxSize) //the list is full
cerr << "Cannot insert in a full list" << endl;
else
list[length] = insertItem; //insert the item at the end
length++; //increment the length
} //end insertEnd
8. removeAt
This function is used to remove an item at a given location
template <class elemType>
void arrayListType<elemType>::removeAt(int location)
if (location < 0 || location >= length)
cerr << "The location of the item to be removed "
<< "is out of range" << endl;
else
for (int i = location; i < length - 1; i++)
list[i] = list[i+1];
length--;
} //end removeAt
9. retrieveAt
This function is used to retrieve an item at a given location
template <class elemType>
void arrayListType<elemType>::retrieveAt
(int location, elemType& retItem) const
if (location < 0 || location >= length)
cerr << "The location of the item to be retrieved is "
<< "out of range." << endl;
else
retItem = list[location];
} //end retrieveAt
10. replace
This function is used to replace an item with other item
template <class elemType>
void arrayListType<elemType>::replaceAt
(int location, const elemType& repItem)
if (location < 0 || location >= length)
cerr << "The location of the item to be replaced is "
<< "out of range." << endl;
else
list[location] = repItem;
} //end replaceAt
11. clear list
This function ius used to Clear the items in a list:
template <class elemType>
void arrayListType<elemType>::clearList()
length = 0;
} //end clearList