Classes Representing Non-Trivial Objects

advertisement
Classes
Representing Non-Trivial Objects
Problem
Write a program that reads a temperature
(either Fahrenheit or Celsius), and displays
that same temperature in both scales.
Preliminary Analysis
An object must be directly representable using
a single type.
A temperature has two attributes:
– its magnitude (a double), and
– its scale (a character).
When an object cannot be directly represented by
any of the available types, build a class!
Building Classes
Begin by defining variables to store the attributes
of the object being represented (a temperature)
in a class header file (e.g., Temperature.h):
double myMagnitude;
char myScale;
Pretending that we are the temperature object,
we begin the name of each attribute with the
prefix my, to reinforce this internal perspective.
Building Classes
We then wrap these variables in a class declaration:
class Temperature
{
public:
private:
double myMagnitude;
char myScale;
};
When declared within a class declaration, such variables are
called class data members.
Information Hiding
Classes have a public section and a private section.
Items declared in the public section are accessible to
users of the class; items declared in the private
section are inaccessible to users of the class.
Data members should go in the private section, to
prevent programmers from writing programs that
access data members directly.
Why?
Software must often be updated.
A program that uses a class should still work after
the class has been updated.
Updating a class may require that its data members
be replaced by others.
If a program accesses class data members directly,
then that program “breaks” if they are replaced.
This increases software maintenance costs.
A New Type
A programmer can now write:
#include “Temperature.h” // class Temperature
...
Temperature aTemp;
and object aTemp can be visualized as follows:
aTemp
myMagnitude
myScale
The data members myMagnitude and
myScale within aTemp are uninitialized.
Operations and Messages
Operations on a class object are usually
implemented as class function members.
A call to a function member:
Object.Function()
can be thought of as the caller sending the
object Object a message named Function.
The definition of a function member details what
Object does in response to the message.
Example: Output
Suppose we want to be able to display the value
of a Temperature object by sending it the
Print() message:
aTemp.Print(cout);
Using the internal perspective (where I am the
Temperature object receiving the message):
Specification:
Receive: out, an ostream.
Output: myMagnitude and myScale, via out.
Passback: out, containing the new values.
Function Member Definitions
In Temperature.cpp:
void Temperature::Print(ostream & out) const
{
out << myMagnitude << myScale;
}
The function returns nothing, so its return-type is void.
The full name Temperature::Print() tells the
compiler this is a Temperature function member.
The caller passes and changes an ostream argument, so
we need a reference parameter to store this argument.
Function members that change no data members should
be defined as const function members.
Function Member Prototypes
class Temperature
{
public:
void Print(ostream & out) const;
private:
double myMagnitude;
char myScale;
};
By declaring the prototype within the class, we tell the
compiler that class Temperature has a function
member named Print(), and that Temperature
objects should “understand” the Print() message.
Most function prototypes go in the class public section.
Problem
At present, a Temperature declaration:
Temperature aTemp;
leaves aTemp’s data members of uninitialized.
To auto-initialize them to a default value (e.g., 0C),
we can use a special function called a constructor.
Constructors
A constructor function is a class function member whose
task is to initialize the class data members.
Because they just initialize data members, they don’t
return anything, and so their specification is often
given as a postcondition (a boolean expression that is
true when the function terminates).
Default Constructor Specification:
Postcondition: myMagnitude == 0.0 && myScale == ‘C’.
Constructor Definition
Temperature::Temperature()
{
myMagnitude = 0.0;
myScale = ‘C’;
}
Since it returns nothing, a constructor has no return type
(not even void).
The name of a constructor is always the name of the
class (in this case Temperature()).
As a function member of class Temperature, its full
name is Temperature::Temperature().
Constructor Prototype
class Temperature
{
public:
Temperature();
void Print(ostream & out) const;
private:
double myMagnitude;
char myScale;
};
Since they specify the first thing a user of the class needs
to know (i.e., how to define class objects), constructor
prototypes are usually the first function members listed
in the public section of the class.
Object Definitions
A programmer can now write:
Temperature aTemp;
and object aTemp can be visualized as follows:
aTemp
myMagnitude
myScale
0.0
C
The class constructor is automatically called
whenever a class object is defined.
Testing
To test this much, we can write:
// ... documentation
// ... other #includes
#include “Temperature.h”
int main()
{
Temperature aTemp;
aTemp.Print(cout); // displays 0C
}
Problem 2
At present, we can only initialize a Temperature to a
default value:
Temperature aTemp;
We have no means of initializing a Temperature to
any other value.
To initialize a Temperature to a particular value
(e.g., 98.6F), we can overload the constructor
with a second definition.
Constructor 2
To overload the constructor, we just provide a second
definition (and prototype) that differs from all other
constructors in at least one parameter.
To initialize the data members of our class, this second
constructor must receive the initial values via its
parameters.
Constructor 2 Specification:
Receive: magnitude, a double; scale, a char.
Precondition: scale == ‘F’ || scale == ‘C’.
Postcondition: myMagnitude == magnitude &&
myScale == scale.
Constructor 2 Definition
Temperature::Temperature(double magnitude,
char scale)
{
assert(scale == ‘F’ || scale == ‘C’);
myMagnitude = magnitude;
myScale = scale;
}
As before, we place this (second) constructor definition in
the implementation file -- Temperature.cpp.
Constructor 2 Prototype
class Temperature
{
public:
Temperature();
Temperature(double magnitude, char scale);
void Print(ostream & out) const;
private:
double myMagnitude;
char myScale;
};
The same name can be used to define different functions,
provided the signature (the list of the parameter types)
of each function is different.
Object Definitions
A programmer can now write:
Temperature temp1, temp2(98.6, ‘F’);
and temp1 and temp2 are defined as follows:
temp1
myMagnitude
myScale
0.0
C
temp2
myMagnitude
myScale
98.6
F
The compiler uses the number of arguments in
a declaration to decide which constructor to
use in initializing an object.
Testing
To test this much, we can write:
// ... documentation
// ... other #includes
#include “Temperature.h”
int main()
{
Temperature temp1, temp2(98.6, ‘F’);
temp1.Print(cout);
cout << endl;
temp2.Print(cout);
}
// displays 0C
// displays 98.6F
Summary
When no existing type is appropriate to represent an
object, the C++ class allows a type to be created.
Classes have data members (private), and function
members (public).
In its definition, a function member’s name must be
preceded by the name of the class and the scope
operator (::).
Class constructor functions allow class objects to be
initialized to default, or to specified values.
Download