In most of the problems we have worked on in these lab exercises,
the data objects could be represented using types provided in C++. For
example, we might represent the radius of a circle with a
double; menu selections with a char or an
int; the radius of a circle with a double; and
an element of a Life grid with a short int.
However, there are many real-world problems with data objects
that cannot be directly represented using just a single predefined
C++ type. Here are two such problems:
Problem #1: Cartesian points. In mathematics courses, functions of a single variable can be pictured with two-dimensional graphs. Two-dimensional vectors used in a calculus or physics course and operations on them such as addition, subtraction, and scalar multiplication can also be pictured by two-dimensional plots. These graphs typically are plotted in a Cartesian coordinate system using x- and y-coordinates for points on the graph. The following figure illustrates plotting a point with coordinates (7,3), which is 7 units to the right along the x-axis and 3 units up along the y-axis:

Problem #2: Fractions. The second problem, and the one that we will be concerned with in this lab exercise, is to develop a fraction calculator, that is, a calculator in which the user enters fractions such as 1/4 and 2/3 and specifies some operation to be performed on them such as multiplication, for which the calculator will output the result as a (reduced) fraction: 1/6.
Building the fraction calculator would be quite easy if we had a
data type named Fraction that we could use to declare fractions,
input them, output them, and perform arithmetic operations such as
multiplication on them. And that is what you will be doing in this lab
exercise — building a Fraction class. The example used to
help you is building a Coordinate type for points in the plane.
Fraction.h,
Fraction.cpp, and
Fraction.txt
implements the Fraction class.gcd.h and gcd.cpp implement
Euclid's greatest-common-divisor algorithm (used by the Fraction class).fractionTester.cpp
is a program to test-drive the Fraction class.C++ does not provide a Temperature type or a
Fraction type or . . . types for a lot of other "real-world"
things. However, it does provide a class mechanism that we
can use to extend the language by creating a new type, including built-in
operations for that type. This new type will provide:
For example, for a Temperature class we might have
double to store the degrees
char to store the scale ('C' for Celsius, 'F' for
Fahrenheit, etc.)
Temperature object
Temperature object
These two kinds of members of a class are usually organized into two sections in a class declaration of the form
Such a declaration is (almost always) saved in a header file namedclassTypeName{ public: // -- Declarations of function members private: // -- Declarations of data members };
Typename.h. It creates a new data type
named TypeName.
Note that a class declaration has two sections: a
public section where function members that implement
the class operations are declared; and a private section
where the data members that implement the class attributes are declared.
The contents of the public section are accessible to a program that
#includes the file containing the class declaration and the
contents of the private section are not (unless some
public function member makes them accessible).
As far as the compiler is concerned, it doesn't matter whether the public section is first and the private section last or the order is reversed. Here, as in the textbook, we will put the public section first because it is the part of the class that is accessible in programs that use the class — the public interface to the class. The private section, whose contents are not accessible (except via public function members), is "buried" at the bottom.
Let's look now at our example of Cartesian coordinates of points in two dimensions. They consist of an x-coordinate and a y-coordinate. We can easily declare two variables to store these values,
double myX, myY;
and put these in the private section of an appropriately-named class, making them data members:
class Coordinate
{
public:
private:
double myX, myY;
};
Using the prefix "my" at the beginning of a name
for a data member name helps maintain an internal
perspective. We imagine ourselves as the object we're trying to
describe. For example, when deciding what the data members should be, I
could say, "I am a Cartesian coordinate, and I have an
x-coordinate and a y-coordinate." And because they
are my attributes, I label them as such. Although this may sound
rather inane and superfluous, it does at least help keep us from confusing
them with other objects that we'll be using.
The result is a new type, named Coordinate,
that can be used to declare new objects. For example,
Coordinate point1, point2, point3;
creates three Coordinate objects that we might picture
as

Each of the Coordinate objects point1,
point2, point3,
has two double components,
one named myX and the other named myY.
Every Coordinate object has its own copies of these
variables. The neat thing is that we only had to declare myX
and myY once in the class definition.
We can now expand our general pattern for a class definition:
where each class
TypeName
{
public:
private:
Type1 DataMemberList1;
Type2 DataMemberList2;
...
Typen DataMemberListn;
};Typei is any defined type and each
DataMemberListi is a list of data members of
type Typei.
Now let's apply a similar approach to the fraction problem. We first
need to identify the attributes of a Fraction object. Here
are a few fractions:
1/2Each fraction has the form:
4/3
4/16 16/4
number1 / number2where number1 is the numerator and number2, which must be nonzero, is the denominator. A fraction needs both a numerator and a denominator. But the slash (/) symbol serves only to separate them and could as well be some other character such as ] or \ or a host of other symbols; it isn't really an attribute of a fraction. Consequently, a fraction has just two attributes, a numerator and a denominator, both of which are integers and the denominator is nonzero.
Question #12.1: Edit the fileFraction.hand do the following:Look at the patterns and example above if you need help.
- Write a class declaration for a class named
Fraction.- In the appropriate place, define two
intdata members namedmyNumeratorandmyDenominator.
Given this definition of a new type Fraction,
we can make declarations like the following:
Fraction frac1, frac2, frac3;These declarations define three
Fraction objects with the
following forms:

Again, note that each object of type Fraction has its own
copy of each of the data members myNumerator
and myDenominator; that's why we prefixed the variable names
with "my" — to encourage an internal perspective.
Question #12.2: In the source programfractionTester.cpp, uncomment the declaration of theFractionobjectf:
Fraction f;(Do not uncomment anything else yet.) Compile, link, and execute your source program to test the syntax of what you have written. When it is correct, continue to the next part of the exercise.
In addition to data members, a class can also have function members. A function member is a function declared inside a class to provide an operation for objects of the class.
We've used several function members already — for example, a
string object has
find(), length(), and substr()
function members plus many others;
a vector object has many function members such as
front(), push_back(), and size().
But until now we've only used function members. Now we have to
define our own function members in our own classes.
Function members are prototyped within the class structure itself (in the
header file), and they are normally defined in the class
implementation file. However, very simple function members (i.e., those
that have at most 3 or 4 operations) are usually defined
"inline" in the header file, following the
class declaration, prefixed by the keyword
inline. (The reason for this is that the
code for inlined functions is inserted wherever they are called,
thus avoiding the extra overhead involved in function calls.)
One of the characteristics of a class is that its data members
are kept private, meaning that a
program using the class is not allowed direct access to them.
In the case of a Coordinate object, it probably
wouldn't be too bad if other programmers had direct access
to the myX and myY data members.
However, in the Fraction class, we must make sure that
myDenominator does not become zero.
If we hide myDenominator from the casual
programmer, we can write the function members of the Fraction
class so that myDenominator is never 0.
In practice, it is not worth the time and effort to try to figure out
which data members are safe to let everyone access directly.
Common practice is to make all data members private.
On the other hand, with few exceptions, we do want users of the
class to be able to access the operations of a class, because they provide
the interface to objects of that type. Thus, the
operations are declared in the public section
of the class. So wherever we have a Fraction object,
we will be able to access its public operations. (If there are some
"special" functions that perform operations such as encryption, we
could prevent access to them by putting them in a separate private section.)
As noted earlier, by convention, the public section of a class comes
first in a class declaration, so that a user of the class can quickly see
what operations it provides — this is why it is referred to as the
interface to the class. It is good style to have one public
section and one private section in a class; however, C++ does allow the
keywords public: and private: to appear an
arbitrary number of times in a class declaration.
We have seen that function members are called differently than normal
functions — by using the dot operator
(the "push-button" operator). For example, if two string
objects named greeting and closing are defined
as follows
string greeting = "hello",
closing = "goodbye";
then the output statement
cout << greeting.size() << ' ' << closing.size() << endl;will display
5 7, the sizes of the two strings.
From an object-oriented perspective, we can think of calling a function
member as sending a message to which the object responds.
When we send the size() message to greeting,
greeting responds with the number of characters it contains;
and if we send the same size() message to closing,
closing responds with the number of characters it contains.
This is part of the internal perspective for designing and writing a class.
When we use the string class, we're not responsible for its
operations. We just simply ask the string objects to carry
out various actions.
A useful analogy is that of a wristwatch that has a built-in calculator and push buttons to perform basic arithmetic operations. Calling a function member of an object is like pushing a button on this watch to send it a message to add two numbers that you enter. This is very different from the "ordinary" functions we considered in earlier lab exercises — with them, we would have to take our watch, wrap it up in a package and send it to a watch-processing facility who would know how to have the watch display the sum of our two numbers and communicate that back to us when then return our watch.
Fraction Class — Part ICoordinate
class and then having you do this for your Fraction class.
In this first part of this exercise, we will focus on adding some of the
usual function members to the Fraction class — output, input,
constructors, accessors — and one arithmetic operation —
multiplication. (Project 12.1 asks you to add others.) You will use
the program fractionTester.cpp to test your operations.
To help with debugging a class, it is often a good idea to begin with a function member that can be used to display class objects — an output function member — because we can use it to display the results produced by other operations.
From the perspective of our Coordinate class, we can specify
the task of such a function member, which we'll call display(), as
follows:
Specification:Note the use of internal perspective — e.g., "... I write my value." — used because we are defining a built-in operation.
Receive:out, anostreamto which I write my values.
Output:myXandmyY, as an ordered pair.
Passback:out, containing the output values.
Also note that this function member receives and passes back an ostream.
Rule: All streams are passed by reference — received and passed back.
The reason for this is that when a stream is received by a function as one of its parameters and the function changes this stream — e.g., removing something from it via input or adding to it via output — this same change must happen in the corresponding stream argument.
Function members must be prototyped within the class declaration, so we would write:
class Coordinate
{
public:
void display(ostream & out) const;
private:
double myX, myY;
};
No, the keyword const at the end of the prototype for
display() isn't a mistake. We've used it before with parameters
to keep them from being changed by a function and it plays a somewhat
similar role here.
Using an internal perspective is the easiest way to explain this:
"I am a Coordinate object, and when I am output by
display(), I cannot be changed." It's the const
that adds the "I cannot be changed." And this is surely what we
want because a Coordinate object that changes every time it
is displayed would be quite useless. Adding the const at the
end of the function heading instructs the compiler to protect
Coordinate objects from being changed in the definition of
the display() function. Any attempt to change myX
or myY in the definition of display() either
directly or by sending them to some other function that could change them
will be rejected by the compiler.
Also, note that we've put display() in the public section
of the Coordinate class. The reason for is that we want
it to be available to anyone who uses this class. Function members
that are only to be used internally in the class and not made available
to users can be placed in the private section.
This is a simple function member so we probably would want to inline it
to avoid the overhead of calls to it. Recall that inlining a function
makes its definition available to the compiler so that it can substitute
it for calls to that function. And to make this definition available to
the compiler, we put it in the header file Coordinate.h,
following the definition of the class.
However, we must also inform the compiler that display() is a
function member of the Coordinate class so it can bind this
function definition to its prototype. And this is done by attaching the
name of the class to the name of function with the
scope operator :: . That is, we define
display() as a function member of our
Coordinate class as follows:
inline void Coordinate::display(ostream & out) const
{
out << '(' << myX << ',' << myY << ')';
}
inline indicates that this is a simple function
that should be inlined. Note that it it is used only with the
the definiition, not the prorotype.void informs the compiler that this function returns
nothing.Coordinate:: informs the compiler that this function member
is a member of class Coordinate.display is the name of the function member.(ostream & out) is the parameter list of the function
member.const tells the compiler that this function member should
not modify any of the data members of class Coordinate.
Now try the following to check your understanding of this rather lengthy presentation.
Question #12.2: Ifpointis aCoordinateobject with x-coordinate 3 and y-coordinate 4, what output (if any) will be produced by the statementpoint.display(cout);
Question #12.3: Iforiginis aCoordinateobject with x-coordinate 0 and y-coordinate 0, what output (if any) will be produced by the statementpoint.display(cerr);
Using the preceding as a guideline:
Fraction Coding #1:
- Add a prototype and defininition of a similar
display()function member for yourFractionclass. Write the function member so that whenfis aFractionwhose numerator is 3 and whose denominator is 4, then a message:f.display(cout);will display3/4
- Prototype this function member in the public section of class
Fraction, and define it as aninlinefunction member following the declaration of classFractioninFraction.h.
- To check what you have done, uncomment the code in the first section of
fractionTester.cppbelow//1. Test display() function. You can either physically remove the comment delimiters/*and*/or "virtually" remove them by making each line begin with//. Then compile, link, and execute the program. Proceed to the next part of this lab exercise you are satisfied with the format of your output.
An output operation for a class is of little use unless we can build objects of that class. This is the task of a special function member called a constructor. It specifies what actions to take when a class object needs to be created. For example, when an object is declared as in
the compiler will call a constructor function to initialize the data members of that object —Coordinate point;
point in this example. Currently,
there would be "garbage" in this object's data members, but it would
seem preferable to have them initialized to some default values. And in
this example, the origin (0, 0) seems like a reasonable choice.
We can specify this as a postcondition (as part of a
specification):
Specification:
Postcondition:myX== 0 andmyY== 0.
A constructor does not return anything to its caller; it initializes the data members of an object when that object is declared. We specify this behavior as a boolean expression that will be true when the constructor terminates. Such an expression is called a postcondition because it is true after (i.e., "post") the constructor is executed.
Like other function members, a constructor's prototype must be within the class declaration. But a constructor has some unique requirements.
And because it is a member of a class, its prototype must appear within the class. So, for our
Rule: The name of a constructor is always the name of the class. It has no return type — not even void.
Coordinate class, the constructor's
name is Coordinate() and we prototype it in the public
section of class Coordinate:
class Coordinate
{
public:
Coordinate();
void display(ostream & out) const;
private:
double myX, myY;
};
Note that the const specifier is not used with the
constructor because, unlike our display() function member,
a constructor initializes and thus modifies the class' data members.
As a result, it cannot be specified as const.
Also, as with the display() function member, the
prototype should be placed in the public section of the class so that
users of this class can construct Coordinate objects.
To define an inline Coordinate constructor, we proceed as
with other function members and qualify their names with the class name,
which gives a rather unusual-looking definition in the header file,
placed after the end of the class declaration:
inline Coordinate::Coordinate()
{
myX = 0.0;
myY = 0.0;
}
The first Coordinate is the name of the class, informing the
compiler that this is a member of class Coordinate. The
second Coordinate is the name of the constructor.
Given this definition, when a Coordinate object is declared,
the compiler will call this constructor to initialize the new object,
setting it's myX and myY data members to zero.
The general pattern for the definition of a constructor is:
where the first ClassName::ClassName(ParameterList)
{
StatementList
}ClassName refers to the name of
the class, the second ClassName names the
constructor, ParameterList is a sequence
(possibly empty) of parameter declarations, and
StatementList is a sequence of statements
that initialize the data members of the class. As this pattern indicates,
constructors may have no parameters — for example, the above
constructor for the Coordinate class. Such a constructor
is called the class' default-value constructor. A
constructor that does have parameters is an
explicit-value constructor. We will illustrate this
in the next section.
Using this information:
Fraction Coding #2:
- Prototype and define a default-value constructor for your
Fractionclass, using the following specification:Specification:That is, the declaration
Postcondition:myNumerator== 0 andmyDenominator== 1.Fraction f;should initialize the data members offappropriately to represent the fraction 0/1.- Put the prototype in the public section of class
Fraction, and its definition, specified asinline, after the end of the class declaration inFraction.h.- To test what you have written, uncomment the code in
fractionTester.cppin the section labeled2. Test Constructor #1and then compile, link, and execute the program. Continue when it executes correctly.
In C++, a function may be defined more than once, provided that each definition differs from the others in the number or the types of its parameters. Defining the same function multiple times in this way is called overloading that function. Overloading works for ordinary functions as well as for function members, including constructors, of a class.
Suppose that we would like to be able to explicitly initialize the
x- and y-coordinates of a Coordinate object
to two values specified by the programmer creating the object.
We can specify this as follows:
Specification:
Receive:xandy, twodoublevalues.
Postcondition:myX==xandmyY==y.
We can overload the Coordinate constructor with a second
definition, an explicit-value constructor that has two
double parameters and uses them to initialize our data members:
inline Coordinate::Coordinate(double x, double y)
{
myX = x;
myY = y;
}
As usual for such simple functions, this constructor is defined inline after the class declaration in the header file and like all function members of a class, its prototype placed in the public section:
class Coordinate
{
public:
Coordinate();
Coordinate(double x, double y);
void display(ostream & out) const;
private:
double myX, myY;
};
We can now declare Coordinate objects and initialize them by
using this initial-value constructor; for example,
Coordinate point1,
point2(1.2, 3.4);
The default-value constructor initializes point1 because its
declaration has no arguments. Our initial-value constructor initializes
point2 because its declaration has two double
arguments and the second constructor has two double parameters.
After these declarations, we have these objects:

Fraction Coding #3:
- Define and prototype a second
Fractionconstructor that satisfies this specification:
Specification:
Return:myNumerator.
- Also write an accessor function member
getDenominator()that satisfies this specification:Specification:These are simple function members so define them
Return:myDenominator.inlineafter the class declaration in the header file.- To test these extractors, uncomment the code in the section of
fractionTester.cpplabeled4. Test Extractor Functions, and then compile, link, and execute the program. When everything is working correctly, proceed to the next part of this lab exercise.
Now that we are able to define Coordinate objects and
Fraction objects, it would be nice if we could input
them. For example, suppose we wanted to input a Coordinate
value as
(3,4)A user would enter this from the keyboard or perhaps a program would read it from a file.
We can specify the problem as follows:
Specification:
Receive:in, anistream.
Precondition:incontains aCoordinateof the form(x,y).
Input:(x,y)fromin.
Passback:inwith the input values extracted from it.
Postcondition:myX==xandmyY==y.
As with all function members, we prototype this function member in the
class, but this one is not const because it modifies
the data members::
class Coordinate
{
public:
Coordinate();
Coordinate(double x, double y);
double getX() const;
double getY() const;
void read(istream & in);
void display(ostream & out) const;
private:
double myX, myY;
};
We can define read() as a function member that satisfies the
specification, as follows:
void Coordinate::read(istream & in)
{
char ch; // for reading ( , and )
in >> ch // read '('
>> myX // read x-coordinate
>> ch // read ','
>> myY // read y-coordinate
>> ch; // read ')'
}
The ch variable is used to read in the punctuation characters
— parentheses and the comma — and then simply "throw them away"
because they're not essential within a Coordinate object —
every Coordinate object is written with this
punctuation. We only need them when inputting a coordinate and in output.
Of course, we could have required that the user enter only the coordinates, but it's more user-friendly to have the input be in the customary form. With this function member, we can use statements like
to read aCoordinate point; point.read(cin);
Coordinate of the form (x,y) from
cin.
Note that we didn't declare this function as inline. There are no
"hard and fast" rules for when to inline and when not. Inlining a function
that involves several operations can result in "code bloat." As a general
rule, functions that use more than a few — 3 or 4? —
should not be inlined.
Thus, given the length of this function member, we have opted not to
declare it as an inline function. In fact, some compilers may not allow it.
As a result, we put this definition in the implementation file
(without the keyword inline).
Using this information:
Fraction Coding #5:
- Prototype and define an input function member named
read()for classFraction. Your function member should satisfy this specification:Specification:
Receive:in, anistream.
Precondition:incontains a Fraction value of the formn/danddis not zero.
Input:n/d, fromin.
Passback:in, withFractionn/dextracted from it.
Postcondition:myNumerator==nandmyDenominator==d.Some differences from the
read()ofCoordinate:
- You can (and should) test the second half of the precondition (i.e.,
dis not zero).
- Watch the punctuation. A coordinate has three punctuation characters, but a fraction has just one. Don't blindly copy the input statement. Change the comments so that it's clear what's being read in by each input operator.
- Test your input function member by uncommenting the section labeled
5. Test InputinfractionTester.cppCompile, link, and execute the program.- Continue to the next part of this lab exercise when
read()is working correctly.
We have seen that function members like constructors can be overloaded. In
addition, C++ allows us to overload operators, such as the arithmetic
operators (+, -, *, /, and %). However to do so, we need to rethink the way expressions work.
Suppose that we want to permit two Coordinate objects to be
added together. In C++'s object-oriented world, an expression like
point1 + point2is thought of as sending the
+ message to point1,
with point2 as a message argument. We can specify the
problem from the perspective of the Coordinate receiving this
message:
Specification:
Receive:point2, aCoordinate.
Return:result, aCoordinate.
Postcondition:result.myX==myX+point2.myXandresult.myY==myY+point2.myY.
Again, the postcondition here suggests the code that we should write.
But how do we write a function for +? The answer comes
this property of C++:
Rule: For any overloadable operator Δ, we can use the notation operatorΔ
as the name of a function that overloads Δ with a new definition.
So, to overload + for Coordinate objects, we
define a function named operator+; and we can make it a
function member of Coordinate that will have the second
addend as a parameter.
class Coordinate
{
public:
Coordinate();
Coordinate(double x, double y);
double getX() const;
double getY() const;
void read(istream & in);
void display(ostream & out) const;
Coordinate operator+(const Coordinate & point2) const;
private:
double myX, myY;
};
According to our specification, this operation does not modify the
data members of the Coordinate that receives it, and so
is declared to be a const function. But neither does it
modify the parameter, so that is const as well. We don't
change either object. We pass the parameter by reference because
it's not a primitive type — recall that passing by reference is
faster than by value for non-primitive types because it does not
require making a copy of the argument.
One way to define this function member is as follows:
Coordinate Coordinate::operator+(const Coordinate & point2) const
{
Coordinate result(myX + point2.getX(), myY + point2.getY());
return result;
}
This definition uses our second constructor to construct and
initialize result with the appropriate values.
Once such a function member has been prototyped and defined as a member of
class Coordinate, we can write familiar looking expressions,
such as
point1 + point2to compute the sum of two
Coordinate objects point1
and point2. The C++ compiler treats such an expression as an
alternative notation for the function member call:
point1.operator+(point2)
It is useful to overload other arithmetic operators for
Fraction objects. We will describe here how to
do this for multiplication, and leave the others for the first project.
From the preceding discussion, it should be evident that
we need to overload operator* so that the expression
oldAmount * scaleFactor
in fractionTester.cpp can be used to multiply the two
Fraction objects oldAmount
and scaleFactor.
However, the math here is a bit more involved that in the past examples and tasks. We can get some insight into the problem by working some simple examples:
The specification for such an operation can be written as follows:
Specification:
Receive:rightOperand, aFractionoperand.
Return:result, aFraction, containing the product of the receiver of this message andrightOperand, simplified, if necessary.
We can construct result by taking the product of the
corresponding data members and then simplifying the resulting
Fraction.
First, let's implement multiplication and forget about simplifying the result:
Fraction Coding #6:
- Extend your
Fractionclass with a definition ofoperator*that can be used to multiply twoFractionobjects. When we add the code to simplifyresult, this function member will be reasonably complicated, so define it in the implementation file (and, of course, prototype it in the header file, in the class declaration).
- Test the correctness of what you have written by uncommenting the code in the section labeled
6. Test multiplicationinfractionTester.cpp.
- Compile, link, and execute the resulting program. When your multiplication operation yields correct (but unsimplified) results, continue on with the following part of the lab exercise.
The main deficiency of our implementation of operator* is its
inability to simplify fractions. That is, our
multiplication operation would be improved if class Fraction
had a simplify() operation so that fractions like:
2/6, 6/12, 12/4 could be simplified to 1/3, 1/2, and 3/1, respectively.
Such an operation is useful to keep fractional results as simple and
easy to read as possible. To provide this capability, we will implement
a Fraction function member named simplify().
In a function member like operator* which constructs its
answer in a Fraction named result, we can simplify
this answer by calling simplify() as follows:
result.simplify();
Phrased in message-passing terms, this call
says, "Hey, result! Simplify yourself!"
and result then
goes off and simplifies itself (e.g., changes itself from 12/4 to
3/1). "I don't expect anything back; I've told result to do
all the work and change itself."
That takes care of result in the multiplication operation,
but what about the definition of simplify()? We shift our
perspective to the Fraction that's been told to simplify
itself. There are a number of ways to simplify a fraction. One is with
the following algorithm:
gcd, the greatest common
divisor of myNumerator and myDenominator.myNumerator by myNumerator/gcd.myDenominator by myDenominator/gcd.Those "replace" steps are assignment statements: e.g., "Change my numerator to be my numerator (my old one) divided by the gcd." Read this statement carefully, and the code almost writes itself.
The specification for this function member is thus:
Specification:
Postcondition:myNumeratorandmyDenominatordo not share any common factors (i.e., the fraction is simplified).
Remember that simplify() doesn't need any extra information,
except the Fraction object that it's invoked on, so there are
no parameters. Also, the object receiving this message changes
itself, so it cannot be declared const. And there's no
need to return anything. So we end up with only a postcondition in
our specification.
The implementation file gcd.cpp contains a function greatestCommonDivisor() that implements Euclid's algorithm for
finding the greatest common divisor of two integers.
Fraction Coding #7:Using
greatestCommonDivisor()and the preceding algorithm:
- Define
simplify()as a function member of classFraction. Because this is a complicated operation, define it in the implementation file and only prototype it in the header file.- Compile, link, and execute
fractionTester.cppto check that you multiplication operator is now working correctly and producing simplified results.
<<
and Input Operator >> We have seen how arithmetic operators such as * can be
overloaded. We turn now to the problem of overloading the
output operator << and input operator >>.
While we have provided the capability to output a Fraction
value via a display() function member, doing so requires that we write
clumsy code like:
cout << "\nThe converted measurement is: " newAmount.display(cout); cout << endl;instead of elegant code like:
cout << "\nThe converted measurement is: " << newAmount << endl
Our display() function member does solve the problem, but its solution
doesn't fit in particularly well with the rest of the iostream library operations. It would be preferable to use
the usual output operator (<<) to display a
Fraction value.
Let's revisit our Coordinate class. We would like to be able
to write this:
cout << point << endl;And this should display the
Coordinate object named point on cout.
Well, << is an operator just like + or *, and
overloading those worked well, so maybe we could add a function member to class
ostream overloading operator<< with a new definition
to display a Coordinate value. Then the compiler could treat
an expression like
cout << pointas the call
cout.operator<<(point)However, this would require us to modify
ostream, a
predefined, standardized class. This is never a good idea,
since the resulting class will no longer be standardized. (It's also
very hard to do.)
We could define operator<< as a function member of Coordinate, but C++ would then require us to invoke the function member in this way:
point << cout;But that looks like
cout is being sent to point
— the exact opposite of what we want!
Instead, we can overload the output operator (<<) as a
normal function (i.e., not as a function member) that takes an ostream (e.g., cout) and a Coordinate (e.g., point) as its operands. That is, an expression
cout << pointwill be translated by the compiler as a normal function call
operator<<(cout, point)That's exactly what we want.
We define the following "ordinary" (i.e., non-member) function in the header file, following the class declaration:
inline ostream & operator<<(ostream & out, const Coordinate & coord)
{
coord.display(out);
return out;
}
There are several subtle points in this definition that need further explanation:
inline function. Since it is not a function member, defining it
in the header file serves as both prototype and definition for the
function. Inline function members must still be prototyped in the class
so that the compiler fully understands that they are members of the
class.ostream which is altered by the operation — the Coordinate
is inserted into the output stream. Since it's changed, the ostream is a non-const reference parameter.const.The leftmostcout <<value1<<value2<< ... <<valueN;
<< is applied first, and the value it returns
becomes the left operand to the second <<. Similarly, the
value returned by the second << becomes the left operand of
the third <<, and so on, down the chain. Consequently, our
function must return an ostream to its caller to make the
chaining work correctly. However, if we simply make the
return type ostream (instead of ostream &), the C++
function-return mechanism will make and return a copy of
parameter out which (as an alias for cout) would
return a copy of cout for use by the next operator in the
chain. As a result, the next value would get inserted into a copy
of cout, rather than cout itself, which would have
unpredictable results.
What we need is a way to tell the compiler to return the
actual object to which out refers, instead of a copy of
it. This is accomplished by defining the function with a reference
return type, ostream &. The compiler then does all the
work to return the actual object itself (not a copy), and so the
chaining will work the way we want.
Coordinate
is done by sending the Coordinate parameter coord
the display() message we defined in Part I of this exercise,
with out as its argument. If we hadn't written display(), we could still define this function using the accessor
function members getNumerator() and getDenominator().For our Fraction class, the specification of this operation is thus:
Specification:
Receive:out, anostream, and aFractionobjectaFraction.
Precondition:ostream outis open for output.
Output:aFraction, in the formn/d, viaout.
Passback :out, containingn/d.
Return:out, for chaining.
Using this information:
Fraction Coding #8:
- Overload the output operator for class
Fraction.- To test it, uncomment the section labeled
7. Test output operator <<infractionTester.cpp.- Compile, link, and execute the program. When it's working correctly, continue to the next section.
As a dual to output, all of the things we learned about the output operator also apply to the input operator, just with a slight change in direction (reading information in, instead of sending it out). The syntax is very similar.
Suppose we wanted to input a Coordinate, entered as
(3,4)We could define this operator in the header file:
inline istream & operator>>(istream & in, Coordinate & coord)
{
coord.read(in);
return in;
}
There are a few differences between the input and output operators:
operator<<; the input
operator is operator>>.istream
instead of an ostream.Coordinate reference, rather than a
const Coordinate reference. Because we want to change this Coordinate, it cannot be const.istream reference
instead of an ostream reference. Similar to the output
operator, this is for chaining: cin >> point1 >> point2;.Using this information:
Fraction Coding #9:
- overload the input operator
>>for classFraction, so that a user can enter3/4or (with spaces around the slash)
3 / 4to input the fraction 3/4.- To test your input operator, uncomment the section labeled
8. Test input operator >>infractionTester.cpp. Compile, link, and exeute the program and test whether your input operator works.- When everything in your
Fractionclass is correct, completeFraction.txt.
While it is not necessary for this particular problem and the code solution we have, there are certain situations where it is useful for a function that is not a member of a class to be able to access the private data members. By default, C++ will not allow any other function to access private data members.
But suppose we had not written the read() function member for class
Coordinate, and wanted to overload operator>> in
order to input Coordinate values. We'd probably put what
we do have for read() in the input operator:
istream & operator>>(istream & in, Coordinate & coord)
{
char ch;
in >> ch // consume (
>> coord.myX // read x-coordinate
>> ch // consume ,
>> coord.myY // read y-coordinate
>> ch; // consume )
}
Syntactically, this is 100% correct. However, semantically, the
compiler does not allow this and will generate compilation errors for
the accesses to coord's data members. As a non-function member,
this operator cannot access the private data members. However, we might
like to be able to grant this permission.
For such situations, C++ provides the friend mechanism. If a class names a
function as a friend with the keyword friend, then that
non-member function is permitted to access the private section of the
class. A function is declared as a friend by including a prototype
of the function preceded by the keyword friend in the class
definition. Thus, we would have to write this in our Coordinate class:
class Coordinate
{
public:
Coordinate();
Coordinate(double x, double y);
double getX() const;
double getY() const;
Coordinate operator+(const Coordinate & point2) const;
friend istream & operator>>(istream & in, Coordinate & coord);
private:
double myX, myY;
};
Placing the friend keyword before a function prototype in a
class thus has two effects:
As we have seen in this exercise, an object-oriented programmer can
usually find other ways to implement an operation without resorting
to the friend mechanism. In the object-oriented world,
solving a problem through the use of function members is generally preferred
to solving it through use of the friend mechanism. As a
result, friend functions tend to be used only when the
object-oriented alternatives are too inefficient for a given
situation. Nevertheless, they are a part of the C++ language, and
you should know the distinction between a function member and a friend
function of a class.
You could make a similar change to your Fraction class, even
just prototype the input and output operators as friend
functions, but it's not necessary.
Now that you have seen how to build a class, we need to expand our design methodology to incorporate classes:
Using this function memberology and the C++ class mechanism, we can now create a software model of any object! If you can imagine it, you can write a class for it.
The class thus provides the foundation for object-oriented programming, and mastering the use of classes is essential for anyone wishing to program in the object-oriented world.