CSC262 Jan20

slide version

single file version

Contents

  1. A Class for a Fraction Type
  2. Test Program
  3. Printing a Fraction
  4. Non-Member print
  5. Return Type of operator<<
  6. Return Type of operator<<
  7. Non-member operator<<
  8. Implementation of operator<<
  9. Employee Example Class
  10. Overloading operator <<: 1
  11. Overloading operator<<: 2
  12. Overloading operator<<: 3
  13. Overloading operator<<: 4
  14. Overloading operator<<: 5
  15. Overloaded operator<< with Files: 1
  16. Overloaded operator<< with Files: 2
  17. Overloaded operator<< with Subclasses
  18. Writing a toString() Method: 1
  19. Writing a toString() Method: 2
  20. Writing a toString() Method: 3
  21. Writing a toString() Method: 4
  22. Writing a toString() Method: 5
  23. Writing a toString() Method: 6
  24. Writing a toString() Method: 7
  25. Writing a toString() Method: 8
  26. Overloading Other Operators
  27. Overloading operator+ as Member: 1
  28. Overloading operator+ as Member: 2
  29. Overloading operator* for Pairs: 1
  30. Overloading operator* for Pairs: 2
  31. Overloading operator* for Pairs: 3
  32. Overloading operator* for Pairs: 4

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?

Test Program[2] [top]


#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;
}

Printing a Fraction[3] [top]

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;
}

Employee Example Class [9] [top]

    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?

Overloading operator <<: 1[10] [top]

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<<

Overloading operator<<: 2[11] [top]

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);	
      

Overloading operator<<: 3[12] [top]

 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.

Overloading operator<<: 4[13] [top]

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.

The first choice has superior benefits for opertor<< in case subclasses of Employee are created later (such as a Manager subclass).

Overloaded operator<< with Files: 1[15] [top]

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.

Overloaded operator<< with Files: 2[16] [top]

 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;
   }

      

Overloaded operator<< with Subclasses[17] [top]

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:

  1. We implement a public Employee virtual method that returns a string representation of Employee; for example, a toString() method.
  2. 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;
       }
    
          
  3. 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().

Writing a toString() Method: 1[18] [top]

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!

Writing a toString() Method: 2[19] [top]

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!

Writing a toString() Method: 3[20] [top]

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:

Writing a toString() Method: 4[21] [top]

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
      

Writing a toString() Method: 5[22] [top]

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();
      

Writing a toString() Method: 6[23] [top]

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.

Writing a toString() Method: 7[24] [top]

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);

Writing a toString() Method: 8[25] [top]

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	}
      

Overloading Other Operators[26] [top]

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:

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.

Overloading operator+ as Member: 1[27] [top]

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
      

Overloading operator+ as Member: 2[28] [top]

    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	}

Overloading operator* for Pairs: 1[29] [top]

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?

Overloading operator* for Pairs: 2[30] [top]

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.)

Overloading operator* for Pairs: 3[31] [top]

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);
};
	
      

Overloading operator* for Pairs: 4[32] [top]

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.