A Class for a Fraction Type[1] [top]
#ifndef FRACTION_H
#define FRACTION_H
#include <iostream>
#include <string>
using namespace std;
class Fraction
{
int n; // numerator
int d; // denominator
public:
Fraction(); // n = 0, d = 1
Fraction(int nval, int dval);
Fraction operator+(const Fraction& f) const;
Fraction operator-(const Fraction& f) const;
Fraction operator*(const Fraction& f) const;
Fraction operator/(const Fraction& f) const;
int getNumerator() const;
int getDenominator() const;
void reduce();
};
#endif
What's going on with all those const qualifiers?
#include "fraction.h"
int main()
{
Fraction f1(1,4);
Fraction f2(1,2);
Fraction f;
f = f1 + f2;
cout << "Expecting 1/4 + 1/2 = 3/4" << endl;
cout << "1/4 + 1/2 = " << f.getNumerator() << "/" << f.getDenominator << endl;
return 0;
}
How could we make it easier to print Fraction's?
Some possibilities:
Non-Member print[4] [top]
void print(const Fraction& f)
{
cout << f.getNumerator() << "/" << f.getDenominator();
cout << endl; // Do we want this?
}
Return Type of operator<<[5] [top]
Consider the cout expression:
int main()
{
int x = 5;
int y = 3*x + 1;
cout << "y = " << y;
cout << endl;
}
There are two << operators. They evaluate from left to
right. So the expression evaluates as if it were parenthesized
like this:
(cout << "y = ") << y;
The value of parenthesized expression is the left operand of
the second << operator.
What is the value of the parenthesized
expression
(cout << "y = ")
Return Type of operator<< [6] [top]
The type of cout is ostream
But functions ordinarily return by value!
This means that a copy is made of the value being
returned.
This is a good thing if a local variable is being
returned. Why?
But generally, copies should not be made of cout and
cout will not be a local variable. It will be passed by
reference as the first parameter to
operator<<
So in order to not make a copy at the return, simply return
the same cout by reference! So the declaration of
operator<< should be
ostream& operator<<(ostream& os, const Fraction& f);
Non-member operator<<[7] [top]
In a test function we could write
int main()
{
Fraction f1(1,2);
Fraction f2(1,4);
cout << f1; // Same as operator<<(cout, f1);
cout << endl;
...
}
So for
cout << f1; // Same as operator<<(cout, f1);
cout will be passed by reference to the
parameter os
f1 will be passed by constant reference to the
parameter f in
ostream& operator<<(ostream& os, const Fraction& f);
Implementation of operator<<[8] [top]
The implementation of this function is almost like the
print function. If it were exactly like the body of the
print we would have:
ostream& operator<<(ostream& os, const Fraction& f)
{
cout << f.getNumerator() << "/" << f.getDenominator();
// But we need to return an ostream value!
}
But since cout is passed by
reference, os is a alias for cout
in this function. So a correct implementation is
ostream& operator<<(ostream& os, const Fraction& f)
{
os << f.getNumerator() << "/" f.getDenominator();
return os;
}
1 class Employee
2 {
3 string name;
4 char ssn[12];
5 double salary;
6 public:
7 Employee();
8 Employee(const char nm[], const char ss[], double sal);
9 string getName() const;
10 const char * getSSN() const;
11 double getSalary() const;
12 void setSalary(double sal);
13
14 };
What's going on with all the const qualifiers?
What about writing an overloaded operator<< for the
Employee class?
cout << e1; // same as cout.operator<<(e1) ???
Note: If the form were cout.operator<<(e1)
then the overloaded operator<< would have to be written as
a member of the class that cout belongs to - ostream!
Uh oh! The ostream class doesn't belong to us! That's why we
have to write a non-member operator<<
We can't define new members of the ostream class.
And we can't make operator<< be a member of the
Employee class.
So as for the Fraction class we can define a non-member overloaded
operator<<
ostream& operator<<(ostream& os, const Employee& e);
ostream& operator<<(ostream& os, const Employee& e);
The return type must be an ostream in order to write:
cout << e << endl;
because this will be equivalent to
operator<<(cout, e) << endl;
From this form it is clear that the type of the return value of
operator<< must be acceptable as the left operand
of <<.
That is, the return value must be of type ostream.
Since operator<< for Employee must be a non-member
function, it will not have direct access to the private data
members unless we make it a friend!
1 ostream& operator<<(ostream& os, const Employee& e)
2 {
3 os << "Name: " << e.getName() << endl;
4 os << "SSN: " << e.getSSN() << endl;
5 return os;
6 }
Overloading operator<<: 5[14] [top]
Using the public interface of Employee in writing
operator<< works, but there are more flexible choices.
- Write a public member of Employee - string
toString() then write the overloaded operator<<
like this:
ostream& operator<<(ostream& os, const Employee& e)
{
os << e.toString();
return os;
}
- The Employee class can declare the non-member function
operator<< to be a friend function.
Declaring a function to be a friend function of Employee
means the function can access all members including
private members of any Employee parameter passed to it.
The first choice has superior benefits for
opertor<< in case subclasses of Employee are created
later (such as a Manager subclass).
Do we need to provide an additional operator<< for
files?
That is, do we need both of these:
1. ostream& operator<<(ostream& os, const Employee& e);
2. ofstream& operator<<(ofstream& os, const Employee& e);
The ofstream class inherits from ostream.
This means that all the operations that are valid for
cout are valid for any ofstream.
An ofstream has some additional properties and operations,
but any code that uses an ostream will only use operations that
will also work if an ofstream is substituted for the ostream.
So an ofstream can be passed as the first parameter to
version 1 and version 2 is not needed.
Used in EmployeeApp.cpp:
#include "Employee.h"
int main()
{
ofstream ofs;
ofs.open("output.txt");
if ( !ofs.is_open() ) {
cout << "Unable to open output file 'output.txt'\n";
exit(1);
}
Employee e("Bob", "123-45-6789");
ofs << e << endl;
ofs.close();
return 0;
}
The overloaded operator<< for Employee is
declared in Employee.h and defined in Employee.cpp.
Defined in Employee.cpp
#include "Employee.h"
ostream operator<<(ostream& os, const Employee& e)
{
os << e.toString();
return os;
}
If a Manager class is created as a subclass of Employee, do
we need to write another operator<<?
That is, do we need both of these:
1. ostream operator<<(ostream& os, const Employee& e)
2. ostream operator<<(ostream& os, const Manager& m)
Answer: No, provided some conditions are met:
- We implement a public Employee virtual method that returns a string
representation of Employee; for example, a toString() method.
- We implement the operator<< using this
toString() method:
Defined in Employee.cpp
#include "Employee.h"
ostream operator<<(ostream& os, const Employee& e)
{
os << e.toString();
return os;
}
- We override toString() in the Manager subclass of Employee
to print a representation of a Manager instance.
Then a Manager instance can be passed to the
operator<< already written for Employee
and the e.toString() will use Manager's
implementation of toString().
What is needed to write a toString() method for a class?
Answer: We will usually want to provide a string
representation that includes all the data member values
of the instance.
So the more general problem is how to convert a collection of
different data type values to a single string!
There is a C style way and a C++ style way to convert a
collection of data values to a single string.
Each way uses the input/output facilities of the language, but
with the output going (ultimately) to a string rather
than to standard output or a file!
The C++ style will use the C++ string type and a new
type: stringstream.
The stringstream type is similar to ostream and istream
in that it allows both << and >> operators.
In fact stringstream inherits all the methods and
operators from istream and ostream.
The << operator allows the different types to be
"inserted" into the stringstream just as for ostream.
Just as for ostream, values inserted into a stringstream are
converted to a string!
However, the stringstream object has a string buffer where it stores
this string rather than sending the string to standard output.
There are three stringstream methods that manage the string buffer of a
stringstream:
-
void clear() - sets the buffer to the empty
string.
-
string str() - returns a copy of the string in the
buffer.
-
void str(string s) - sets the buffer's contents to
be s.
To use the stringstream type, a header file similar to
<ostream> and <fstream> is needed:
#include <ostream> // o for output stream
#include <istream> // i for input stream
#include <fstream> // f for file streams
#include <sstream> // s for string streams
Example. Convert name, ss, id, and amt to a single string.
string name = "Bob";
string ss = "123-45-6789";
int id = 175;
double amt = 55000.00;
stringstream ss;
ss.setf(ios::fixed);
ss.precision(2);
ss << "Name: " << name << endl
<< "SSN: " << ss << endl
<< "Id: " << id << endl
<< "Amount: << amt;
string result = ss.str();
Finally, consider the class Pair:
1 class Pair
2 {
3 int x;
4 int y;
5 public:
6 Pair() : x(0), y(0)
7 { }
8
9 Pair(int x, int y) : x(x), y(y)
10 { }
11
12 int getX() const;
13 int getY() const;
14 string toString() const;
15
16 friend ostream& operator<<(ostream& os, const Pair& pr);
17
18 };
We really don't need to make this operator<< be a
friend, since it will use the public method toString().
A friend function may be declared inside a class definition,
even though it is not a member function of the class.
If a non-member function is not a friend, it must be
declared outside the class.
If operator<< is not declared a friend in the
Pair class, then in the Pair.h file, it should be declared
outside the Pair class:
1 class Pair
2 {
3 int x;
4 int y;
5 public:
6 Pair() : x(0), y(0)
7 { }
8
9 Pair(int x, int y) : x(x), y(y)
10 { }
11
12 int getX() const;
13 int getY() const;
14 string toString() const;
15
16
17 };
18
19 ostream& operator<<(ostream& os, const Pair& pr);
The implementation of Pair's toString() method
1 string Pair::toString() const
2 {
3 stringstream ss;
4 ss << x << " " << y;
5 return ss.str();
6 }
The usual arithmetic operators can be overloaded for types
provided there is not already a definition of the operator for
that type.
So operator+ cannot be overloaded for the int type,
but can be overloaded for the example Pair type.
There are two choices for overloading an operator for a class
type:
- as a member function of the class
- as a non-member function.
In case of a non-member, the function can either be a
friend or not, depending on whether you need or want to
access private members of instances.
Suppose we want to overload operator+ for the Pair
class.
Pair p1(1,2);
Pair p2(5,5);
Pair p;
p = p1 + p2;
p should be the pair with x=6, y=7
If we make this operator a member function of Pair, then
p1 + p2 will be equivalent to calling the operator+
method on p1 with argument p2; (so this operator+ will have
only 1 parameter):
p = p1.operator+(p2);
Note that changing the order of the operands of +
should not change the result, but the roles of p1 and p2 change:
p = p2 + p1;
is equivalent to
p = p2.operator+(p1); // Ok p2 and p1 are both Pair's
1 Pair Pair::operator+(const Pair& other) const
2 {
3 int xval = x + other.x;
4 int yval = y + other.y;
5
6 return Pair(xval, yval);
7 }
Suppose we want to multiply each element of Pair by an
int:
Pair p1(3,5);
Pair p;
p = 2 * p1;
or
p = p1 * 2;
// p should now be the pair x=6, y=10
The overloaded operator* will have one operand of type
int and the other of type Pair.
Should this operator* be a member function or not?
It can't be a member function, because the expression
2*p1 would mean
p = 2 * p1;
would mean
p = 2.operator*(p1); // 2 is not of class type
So we would need to make operator* be a non-member
function. (It will have 2 parameters.)
We actually need two operator* functions, one for each
order of the two operands of *:
class Pair
{
int x;
int y;
public:
Pair();
Pair(int x, int y);
int getX() const;
int getY() const;
string toString() const;
Pair operator+(const Pair& b) const;
friend Pair operator*(int a, const Pair& p);
friend Pair operator*(const Pair& p, int a);
friend ostream& operator<<(ostream& os, const Pair& pr);
};
The implementaions (in Pair.cpp).
Note: The Pair:: class scope operator is not
used since these are not member functions of Pair:
Pair operator*(int a, const Pair& p)
{
return Pair( a * p.getX(), a * p.getY() );
}
Pair operator*(const Pair& p, int a)
{
return a * p;
}
The second version of operator* can be defined
directly, but in this example it is defined in terms of the
first version.