|
|||||
CS201
Introduction to Programming
Lecture
Handout
Introduction
to Programming
Lecture
No. 40
Reading
Material
Deitel
& Deitel - C++ How to
Program
Chapter
7
7.3,
7.4
Summary
·
Objects
as Class Members
·
Example
1
·
Example
2
·
Advantages
of Objects as Class Members
·
Structures
as Class Members
·
Classes
inside Classes
·
Tips
Objects as
Class Members
A class
is a user defined data type
and it can be used inside
other classes in the same
way
as native
data types are used.
Thus we can create classes
that contain objects of
other
classes
as data members.
When one
class contains objects of
other classes, it becomes
mandatory to understand
how
and in what sequence the
contained and containing objects
are constructed. An
important
point in construction of an object is that
the contained data members
of the
object
(regardless whether they are native or
user defined data types)
are constructed
before
the object itself. The order
of destruction of an object is reverse to
this
construction
order, where the containing object is
destroyed first before the
contained
objects.
To
elaborate the construction and
destruction orders of objects, we
take a class A
and
contain
its instance (object) in another
class B.
Page
511
CS201
Introduction to Programming
/* This
program illustrates the construction and
destruction orders of objects.
*/
#include
<iostream.h>
class
A
{
public:
A()
{
cout
<< "\n A Constructor
...";
}
~A()
{
cout
<< "\n A Destructor ...";
}
};
class
B
{
public:
B()
{
cout
<< "\n B Constructor
...";
}
~B()
{
cout
<< "\n B Destructor ...";
}
private:
A
a;
};
void
main(void)
{
B
b;
}
The
output of this code is as
follows:
A
Constructor ...
B Constructor
...
B Destructor
...
A Destructor
...
Page
512
CS201
Introduction to Programming
In the
code above, we have
contained an instance of the
class A
inside
class B. In
the
main
function,
we have only created an object of
the class B.
From
the output, we can see the
first line that the
contained object a's default
constructor
is called
before the default constructor of
the class B. At
destruction time, the
destructor
of the
class B
is
called first before A's.
Note that the contained
object `a'
of
class A
is
constructed
by calling the default constructor.
Hence, we have found one way
of
constructing
contained objects by means of default
constructors and then
setting the
values of
data members by calling
setter methods of the object.
But this is cumbersome
and
wasteful, we have a better
way provided by the language to
initialize contained
objects
at construction time using the
initializer
list.
Initializer list is used to
initialize the
contained
objects at the construction
time.
Example
1
Let's
take a class of PersonInfo
that
stores name, address
and
birthday
of a
person. This
PersonInfo
class
contains an instance of our
veteran Date
class to
store birthday
of
a
person.
class
PersonInfo
{
public:
//
public member functions...
private:
char
name[30];
char
address[60];
Date
birthday;
//
member object
};
This
declaration specifies a Date
object birthday
as a
private
data
member. Note that no
arguments
are specified in the declaration of
birthday.
However, this does not
mean that
the
default constructor is called when the
PersonInfo
object is
constructed but we
can
always
specify a member initializer to call a
parameterized constructor.
A colon is
placed after the parameter
list of the containing class's
constructor, followed
by the
name of the member and a
list of arguments as shown
below:
class
PersonInfo
{
public:
PersonInfo(
char * nm, char * addr, int
month, int day, int
year );
//
...
private:
//
...
};
Page
513
CS201
Introduction to Programming
PersonInfo::PersonInfo(
char * nm, char * addr, int
month, int day, int
year )
:
birthday( month, day, year ) // Member
initializer
{
strncpy(
name, nm, 30 );
strncpy(
address, addr, 60 );
}
Note
that there are five
parameters inside PersonInfo
constructor
including the three
parameters
month, day
and
year
parameters
to be passed to Date
class's
parameterized
constructor
as birthday(
month, day, year ). We are
using the initializer list,
therefore,
there is
no need to call setter
methods of the Date
class to
initialize the birthday
object.
Similarly,
multiple contained objects
can be initialized by using
comma separated
initializers.
The order of the execution of
initializers is the same as
the order of
declarations
of objects inside the outer
class. To confirm about the
order of execution, let
us have
another Date
object drvLicenseDate
declared
after birthday
object in
the
PersonInfo
class:
/* This
program illustrates the initializer
list, order of execution of constructor's
inside
the
list. */
#include
<iostream.h>
#include
<string.h>
class
Date
{
public:
Date(
);
Date(int month,
int day, int
year);
~Date (
);
private:
int
month, day, year;
};
Date::Date(
)
{
cout
<< "\n Date -- Default
constructor called ...";
month =
day = year = 0;
}
Date::Date(int month,
int day, int
year)
{
cout
<< "\n Date -- Constructor
with month=" <<
month
<< ", day= "
<< day << ", year= " <<
year << " called ...";
this->month
= month;
Page
514
CS201
Introduction to Programming
this->day =
day;
this->year =
year;
}
Date::~Date (
)
{
cout
<< "\n Date -- Destructor called
...";
}
class
PersonInfo
{
public:
// public
member functions...
PersonInfo(
char * nm, char * addr,
int month, int day,
int year,
int
licMonth, int licDay, int
licYear );
PersonInfo::~PersonInfo();
private:
char
name[30];
char
address[60];
// member
objects
Date
birthday;
Date
drvLicenseDate;
};
PersonInfo::PersonInfo(
char * nm, char * addr,
int month, int day, int
year,
int
licMonth, int licDay, int
licYear )
: drvLicenseDate(
licMonth, licDay, licYear),
birthday( month, day, year
)
// Above
line is initializer
list
{
cout
<< "\n PersonInfo -- Constructor called
...";
strncpy(
name, nm, 30 );
strncpy(
address, addr, 60 );
}
PersonInfo::~PersonInfo()
{
cout
<< "\n PersonInfo -- Destructor called
...";
}
main(void)
{
PersonInfo
pi("Abbas", "12-Y, DHS,
Lahore, Pakistan", 12, 12,
1972, 12, 10,
1992);
}
Page
515
CS201
Introduction to Programming
The
output of this program
is:
Date --
Constructor with month=12, day= 12, year=
1972 called ...
Date --
Constructor with month=12, day= 10, year=
1992 called ...
PersonInfo --
Constructor called ...
PersonInfo --
Destructor called ...
Date --
Destructor called ...
Date --
Destructor called ...
Because
birthday
is
declared before drvLicenseDate, it is
clear from the output that
the
constructor
for birthday
is
called first and then for
the drvLicenseDate
object,
although
drvLicenseDate
is
present before birthday
in
the initializer list.
Example
2
Let's
take another example to work
with the size of a matrix. We
declare a Column
class
first
then a Row
class.
Row
class
contains an instance of Column
class to
store the number
of columns
(number of elements) inside one
Row
instance.
Further, the Matrix
class
contains
an instance of Row
class.
See the code
below.
/*
Program to illustrate the
initialization lists, construction and
destruction sequences of
contained
and containing objects. */
#include
<iostream.h>
#include
<stdlib.h>
class
Column
{
private
:
int size
;
public
:
Column (
int size )
{
cout <<
"Column created" << endl <<
endl ;
this->size
= size ;
}
~Column (
)
{
cout <<
"Column destroyed " << endl
<< endl ;
}
void
showSize ( ) ;
void
setSize ( int ) ;
};
void
Column :: showSize ( )
Page
516
CS201
Introduction to Programming
{
cout
<< "Column size is : " << size <<
endl << endl ;
}
void
Column :: setSize ( int sz
)
{
size = sz
;
}
class
Row
{
private
:
int size
;
Column
col ;
public
:
Row (
int rowSize, int colSize ) :
col( colSize )
{
cout <<
"Row created" << endl <<
endl ;
this->size
= rowSize ;
}
~Row (
)
{
cout <<
"Row destroyed " << endl <<
endl ;
}
void
showSize ( ) ;
void
setSize ( int ) ;
};
void
Row :: showSize ( )
{
col.showSize
( ) ;
cout
<< "Row size is : " << size << endl
<< endl ;
}
void
Row :: setSize ( int sz
)
{
size = sz
;
}
class
Matrix
{
private
:
Row
row ;
public
:
Matrix (
int rowSize, int colSize ) :
row( rowSize, colSize
)
{
cout <<
"Matrix created" << endl <<
endl ;
Page
517
CS201
Introduction to Programming
}
~Matrix (
)
{
cout <<
"Matrix destroyed" << endl <<
endl ;
}
void
displayMatrixSize ( ) ;
};
void
Matrix :: displayMatrixSize ( )
{
row.showSize
( ) ;
}
void f(
)
{
Matrix
matrix(3, 4) ;
matrix.displayMatrixSize
( ) ;
}
int
main()
{
f(
);
system("PAUSE");
return
0;
}
The
output of the program is as
follows:
Column
created
Row
created
Matrix
created
Column
size is : 4
Row size
is : 3
Matrix
destroyed
Row
destroyed
Column
destroyed
Press
any key to continue . .
.
Page
518
CS201
Introduction to Programming
Notice
the construction sequence of objects. In
order to create a Matrix
object, a
Row
object is
created first and to create
a Row
object, a
Column
object is
created. So the
contained
object Column
is
constructed first of all,
then comes the Row
object
and finally
the
Matrix
object. At
destruction time, the very
first object to destroy is the
last object
constructed,
which is the Matrix
object.
The second object destroyed is
Row
object
and
then
the Column
object at
the end. See also
the use of initializer list
in the code, how
the
colSize
and
rowSize
arguments
are passed to the
constructors.
The
public
data
members of a contained object can
also be accessed from
outside of the
containing
class. For example, if row
object inside
Matrix
class is
declared as public
and
has a
public
variable
named size
then it
can be accessed using the
dot operator (".")
as:
int
main ( void )
{
Matrix
matrix ( 4, 5 ) ;
Matrix.row.size
= 8 ;
}
Advantages of
Objects as Class
Members
It is a
way of reusing the code
when we contain objects of
our already written classes
into
a new
class. For example, Date
class can be used as data
member of Student, Employee
or PersonInfo
class. In this approach, we
don't have to test our
previously written
classes
again
and again. We write a class,
test it once and add it
into our components library
to
use it
later.
It gives
clarity and better
management to the source
code of our programs when we
break
up problems
into smaller components. The smaller
components can be
managed
independently
from their contained objects
forming their own classes.
For example, in the
previous
example program, Matrix
was
subdivided into Row
and
Column
classes.
When we
declare an object as a constant data
member inside a class then that
constant
object is
initialized using the
initializer list. Therefore, a
class, whose object is
contained
as const
object, must
have a parameterized
constructor.
Structures
as Class Members
We have
already studied that
structures and classes are
very similar in C++ except
the
default
scope of members. The default
scope for members of
structures is public
whereas
the
default visibility for class
members is private.
Page
519
CS201
Introduction to Programming
Likewise,
objects of different classes
can act as data members,
structures and unions
can
also
act as data members of a
class. In fact, all the
discussion above for Class
Objects as
Class
Members applies
to this topic of Structure
Objects as Class Members.
#include
<iostream.h>
#include
<stdlib.h>
struct
VehicleParts
{
int
wheels;
int
seats;
VehicleParts()
{
cout
<< "\n VehicleParts - default
constructor";
}
VehicleParts(int
wheels, int seats)
{
this->wheels
= wheels;
this->seats
= seats;
cout
<< "\n VehicleParts - parameterized
constructor";
}
~VehicleParts()
{
cout
<< "\n VehicleParts - destructor" <<
endl;
}
};
class
Vehicle
{
private
:
VehicleParts
vehicleParts ;
public
:
Vehicle(
)
{
cout <<
"\n Vehicle - default constructor" <<
endl;
}
Vehicle(
int a, int b ) : vehicleParts( a, b
)
{
cout <<
"\n Vehicle - parameterized
constructor";
Page
520
CS201
Introduction to Programming
}
~Vehicle(
)
{
cout <<
"\n Vehicle - destructor";
}
void
setPartsNum ( int a, int b
)
{
vehicleParts.wheels =
a ;
vehicleParts.seats
= b ;
}
void
displayNumVehicleParts ( )
{
/* The
data members of the
structure are public,
therefore,
directly accessible from
outside. */
cout <<
"\n Number of wheels for
this vehicle are "
<<
vehicleParts.wheels;
cout <<
"\n Number of seats for
this vehicle are "
<<
vehicleParts.seats << endl;
}
};
void
f()
{
Vehicle
car( 4, 2 ) ;
car.displayNumVehicleParts(
) ;
}
void
main ( )
{
f();
system (
"PAUSE" ) ;
}
The
output of the program is:
VehicleParts -
parameterized constructor
Vehicle -
parameterized constructor
Number of
wheels for this vehicle are
4
Number of
seats for this vehicle are
2
Vehicle -
destructor
VehicleParts -
destructor
Press
any key to continue . . .
Page
521
CS201
Introduction to Programming
Classes
inside Classes
In C
language, structures can be
defined inside structures, Similarly in
C++, we can have
structures
or classes defined inside classes.
Classes defined within other
classes are called
nested
classes.
A nested
class is written exactly in
the same way as a normal
class. We write its
data
members,
member functions, constructors and
destructors but no memory is
allocated for
a nested
class unless an instance of it is
created. C++ allows multiple
levels of nesting.
Importantly,
we should be clear about the
visibility of the nested
class. If a class is
nested
inside
the public
section
of a class, it is visible outside
the outer (enclosed) class.
If it is
nested in
the private
section,
it is only visible to the
members of the outer class.
The outer
class
has no special privileges
with respect to the inner
class. So, the inner class
still has
full
control over the
accessibility of its members by
the outer class.
Interestingly, the
friend
operator
can be used to declare
enclosed class as a friend of inner
class to provide
access to
inner class's private
members.
This operator is used in the
same way as we use
it for
other classes that are
not nested. We can also
make the inner class to
access the
private
members
of enclosed class by declaring the inner
class as a friend
of
outer class.
The
reason of nesting classes
within other classes is
simply to keep associated
classes
together
for easier manipulation of
the objects.
/* This
program illustrates the nested
classes */
#include
<iostream.h>
#include
<stdlib.h>
class
Surround
{
public
:
class
FirstWithin
{
public:
FirstWithin
()
{
cout
<< "\n FirstWithin - default
constructor";
}
~FirstWithin()
{
cout
<< "\n FirstWithin -
destructor";
}
int
getVar() const
{
return
(variable);
}
private:
int
variable;
Page
522
CS201
Introduction to Programming
};
FirstWithin
myFirstWithin;
private:
class
SecondWithin
{
public:
SecondWithin()
{
cout
<< "\n SecondWithin - default
constructor";
}
~SecondWithin()
{
cout
<< "\n SecondWithin - destructor
";
}
int
getVar() const
{
return
(variable);
}
private:
int
variable;
};
// other
private members of Surround
};
void
f(void)
{
Surround::SecondWithin
a;
Surround::FirstWithin
b;
Surround
c;
c.myFirstWithin.getVar();
}
int
main()
{
f();
cout
<< endl << " ";
system("PAUSE");
return
0;
}
The
output of the program is as
follows:
Page
523
CS201
Introduction to Programming
SecondWithin
- default constructor
FirstWithin
- default constructor
FirstWithin
- default constructor
FirstWithin
- destructor
FirstWithin
- destructor
SecondWithin
- destructor
Press
any key to continue . . .
Notice
the access specifier ( :: ) usage in
function f()
to
access the members of inner
class.
The
class FirstWithin
is
visible both outside and
inside Surround. The
class FirstWithin
has
therefore global scope. The
constructor FirstWithin()
and
the member function
getVar()
of
the class FirstWithin
are
also globally visible. The
int
variable data
member
is only
visible for the members of
the class FirstWithin
as it
is declared private.
Neither
the
members of Surround
nor
the members of SecondWithin
can
access the variable of
the
class FirstWithin
directly.
The class SecondWithin
is
visible only inside Surround.
The
public
members
of the class SecondWithin
canalso
be used by the members of
the
class
FirstWithin, as
nested classes can be
considered members of their surrounding
class.
The
constructor SecondWithin()
and
the member function getVar()
of
the class
SecondWithin
can
also only be reached by the
members of Surround
(and by
the
members
of its nested classes). The
int
variable data
member of the class SecondWithin
is only
visible to the members of
the class SecondWithin. Neither
the members of
Surround
nor
the members of FirstWithin
can
access the variable of the
class
SecondWithin
directly.
The
nested classes can be
considered members of the
surrounding class, but the
members
of nested
classes are not members of
the surrounding class. So, a
member of the class
Surround
may
not access FirstWithin::getVar()
directly.
The nested classes are
only
available as type
names. They do not imply as
objects containment by the
surrounding
class. If
a member of the surrounding class
uses a (non-static) member of a nested
class
then a
pointer to a nested class object or a
nested class data member is
defined in the
surrounding
class. The pointer is further used by
the members of the
surrounding class to
access
members of the nested
class.
It is
important to know how do we define
Member functions of nested classes. They
may
be
defined as inline functions or they
can also be defined outside of their
surrounding
class.
Consider the constructor of
the class FirstWithin
in
the previous example.
The
constructor FirstWithin()
is
defined in the class FirstWithin, which
is, in turn,
defined
within the class Surround.
Consequently,
the class scopes of the
two classes must be used to define a
constructor
as
the
following:
Surround ::
FirstWithin :: FirstWithin ( )
{
variable
= 0 ;
}
The
classes FirstWithin
and
SecondWithin
are
both nested within Surround, and
can be
considered
members of the surrounding
class. Since members of a
class may directly
Page
524
CS201
Introduction to Programming
refer to
each other, members of the
class SecondWithin
can
refer to public
members
of
the
class FirstWithin
but
they cannot access private
members
of the FirstWithin
unless
SecondWithin
is
declared as a friend
of
FirstWithin.
See
the code snippet below, we
have used friend
operator
here extensively so that all
the
three
classes Surround, FirstWithin
and
SecondWithin
can
access private
members
of
each
other.
class
Surround
{
class
SecondWithin ;
public
:
class
FirstWithin
{
friend
class Surround ;
friend
class SecondWithin ;
public
:
int
getValue()
{
Surround ::
variable = SecondWithin :: variable
;
return
(variable);
}
private
:
static
int variable ;
};
friend
class FirstWithin ;
int
getValue ( )
{
FirstWithin
:: variable = SecondWithin :: variable
;
return
(variable) ;
}
private
:
class
SecondWithin
{
friend
class Surround ;
friend
class FirstWithin ;
public
:
int
getValue ( )
{
Surround::variable
= FirstWithin::variable;
return
(variable) ;
}
private:
static
int variable;
};
friend
class SecondWithin ;
static
int variable;
Page
525
CS201
Introduction to Programming
};
We can
also define structures inside classes in
the same manner as we defined
classes
within
classes. Again, all the
above discussion is valid
for structures inside classes
except
the
default scope of members in structures is
public
unless
explicitly declared otherwise.
Tips
·
A class
can contain instances of other
classes as its data
members.
·
It is a
way of reusing the code
when we contain objects of our
already written
classes
into a new class.
·
The inner
data members of the object
are constructed and then
the object itself.
The
order of destruction of an object is
reverse to this construction order,
where
the
outer object is destroyed first
before the inner data
members.
·
Initializer
list is used to initialize
the inner objects at the construction
time.
·
In C++,
we can have structures or
classes defined inside classes. Classes
defined
within
other classes are called
nested classes.
Page
526
Table of Contents:
|
|||||