circle.h (The header file)
circle.cpp (The implementation file)
circle_driver.cpp (The driver file)
We'd like to improve on the circle class by adding some facility for I/O. For example, with the appropriate definition, we might be able to replace the following code from circle_driver.cpp
cout << c.name() << " is located at (" << x << "," << y << ")" << endl;
cout << "Its radius is: " << c.radius() << endl;
cout << "Its circumference is: " << c.circumference() << endl;
cout << endl;
with
c.insert(cout);
To accomplish this, we would add the following at the end of the circle.h file:
void insert (ostream & out) const;
and the following to the implementation file, circle.cpp:
void circle::insert (ostream & out) const
{
out << m_name << " is located at (" << m_x << ", " << m_y << ")" << endl;
out << "Its radius is: " << m_rad << endl;
out << "Its circumference is: " << circumference() << endl;
out << endl;
}
We thus arrive at the following improved versions:
Lab Exercise. It's tempting to try to overload the usual insertion operator << so that a statement like
cout << c;
would produce the same output. Suppose we try this by adding another member function. In the header file, circle.h, add
void operator<< (ostream & out) const;
and in the implementation file, add
void circle::operator<< (ostream & out) const
{
out << m_name << " is located at (" << m_x << ", " << m_y << ")" << endl;
out << "Its radius is: " << m_rad << endl;
out << "Its circumference is: " << circumference() << endl;
out << endl;
}
This would allow us to make the following invocation in circle_driver.cpp:
c << cout;
Unfortunately, this is backwards from the usual order. But, if you try it the other way, cout << c, you'll get an error message. Effectively, all member functions expect the object to be the first argument or parameter. We'll see if we can fix this below.
Last class, we embarked on the following exercise
Lab Exercise. Write a employee class with 4 data members, name, ssNum, age, and salary. Add member function raise() to the class. Its prototype looks like:
void raise (const double & x) //gives x % raise to employee
Write a driver program that incorporates the Employee class. If you get frustrated, you can check my solution to the challenge, employee.cpp and driver.cpp
Let's examine the solution.
//File: employee.cpp
#include <string>
using namespace std;
class employee
{
private:
string name;
string ssNum;
int age;
double
salary;
public:
//Constructors
employee()
{
name
= "";
ssNum
= "";
age =
0;
salary
= 0.0;
}
employee(string n, string s, int
a, double rate)
{
name
= n;
ssNum
= s;
age =
a;
salary
= rate;
}
//Accessors
string getName()
{
return
name;
}
string getSSNum()
{
return
ssNum;
}
int getAge()
{
return
age;
}
double getSalary()
{
return
salary;
}
//Mutators
void raise (const
double & x) //gives x % raise to employee
{
salary += salary *
x/100;
}
};
//File: driver.cpp
#include <iostream>
#include "employee.cpp"
using namespace std;
int main()
{
employee e("Bill", "123456789", 35, 15.0);
cout << "Name:\t\t\t" << e.getName() <<
endl;
cout << "Social Security #:\t" << e.getSSNum()
<< endl;
cout << "Age:\t\t\t" << e.getAge() << endl;
cout << "Salary:\t\t\t" << e.getSalary() <<
endl;
cout << endl;
e.raise (20.0);
cout << "Name:\t\t\t" << e.getName() <<
endl;
cout << "Social Security #:\t" << e.getSSNum()
<< endl;
cout << "Age:\t\t\t" << e.getAge() << endl;
cout << "Salary:\t\t\t" << e.getSalary() <<
endl;
}
Notice that in the driver program, we have serious redundancy. Having to repeat the output code each time is an imposition on the client program. It suggests adding this code as a member function in the employee class.
Lab Exercise. Add the following methods to the employee class and revise the driver program to test the new I/O facilitators.
//I/O Facilitators
void readEmployee(istream
& in)
void print (ostream & out)
const
Lab Exercise. Add I/O facilitators (overloaded operators) << and >> to the employee class and test them with an updated driver program.
This may be a good place to display the I/O hierarchy in the ANSI C++ standard:
![]() |
The above graph reflects the object oriented nature of C++. In particular, note that an ifstream object is itself an istream object. So, the fact that we have an istream parameter in the readEmployee() method suggests that the actual parameter could be either a file or the keyboard (cin).
Although the revised circle class is I/O enhanced, the access methods, insert() and extract(), are clumsy. It would be nice if we could input and output circle objects just as we do int or double objects; i.e.,
circle c1;
cout << c1 << endl;
circle c2;
cin >> c2;
...............
This requires overloading the insertion (<<) and extraction (>>) operators. Each is a binary operator and so, in a sense, is a function of two variables, an I/O stream object and a circle object in that order. All member functions are implicitly functions of one or more variables, with the class object implicitly the first parameter. So, we could add these overloaded operators to the class. However, as we saw above, this would mean awkward sytax like:
c1 << cout; //since c1 is implicitly the first parameter and cout the second
So, we'll add these as auxiliary (non-member) operators. Their description and definition will come after the class description and definition in circle.h and circle.cpp respectively.
//File: circle.h
#include <string>
#include <fstream>
using namespace std;
class circle
{
private:
double m_rad;
double m_x, m_y;
string m_name;
public:
//Constructors
circle ();
circle (double xcoord, double ycoord, double
radius, string name);
//Destructor
~circle ();
//Accessors
double circumference () const;
double radius () const;
string name () const;
void center (double
& x, double & y) const;
};
//File: circle.cpp IMPLEMENTATION file
#include <string>
#include "circle.h"
using namespace std;
const double PI = 3.14159;
circle::circle ()
{
m_x = 0.0;
m_y = 0.0;
m_rad = 1.0;
m_name = "Unit Circle";
}
circle::circle (double xcoord, double ycoord, double
radius, string name)
{
m_x = xcoord;
m_y = ycoord;
m_rad = radius;
m_name = name;
}
circle::~circle ()
{
}
double circle::circumference () const
{
return (m_rad * 2.0 * PI);
}
double circle::radius () const
{
return m_rad;
}
string circle::name () const
{
return m_name;
}
void circle::center (double & x, double
& y) const
{
x = m_x;
y = m_y;
}
ostream & operator << (ostream & out, const
circle & c)
{
double x, y;
c.center(x,y);
out << c.name() << " is located at (" << x
<< ", " << y << ")" << endl;
out << "Its radius is: " << c.radius() <<
endl;
out << "Its circumference is: " <<
c.circumference() << endl;
out << endl;
return out;
}
The driver file now becomes:
//File: circle_driver.cpp
#include "circle.h"
#include <iostream>
using namespace std;
int main()
{
circle c, d(1.0, 3.0, 5.0, "Test Circle");
cout << c << d;
return 0;
}
The example helps to make clear a central feature of C++, namely, classes allow the implementation of user-defined types. The distinction between interface (circle.h) and implementation (circle.cpp) as well as the access restrictions implied by the labels public and private, illustrate the ability of C++ to incorporate the software engineering ideals of encapsulation and information hiding.