The insertion and extraction operators

The insertion operator << is the one we usually use for output, as in:

     cout << "This is output" << endl;

It gets its name from the idea of inserting data into the output stream.

The extraction operator >> is the one we usually use for input, as in:

     cin >> X;

It gets its name from the idea of extracting data from the input stream.

Frequently, when we invent our own classes, we want to redefine these operators to work with them, so we can have the convenience of code like this:

     MyClass MyVar;
          ...
     cout << MyVar;

As with any operator, we have questions to answer:

For the moment, we will focus on the insertion operator. Notice that it has two arguments: on the left, an instance of the ofstream class, and on the right, an instance of our own class. Therefore, if it is a method, it must belong to the ofstream class. Since we do not have the ability to add anything to the ofstream class (which someone else already invented), we will have to make it a friend of our own class.

What does the operator return? To figure this out, recall that we often chain together the use of this operator, as in:

     cout << A << B << C << D;

This is evaluated from left to right:

     (((cout << A) << B) << C) << D;

As the left argument must be an ofstream instance, the operator must be returning an ofstream. We want all these values printed on the same line or in the same file, so we must in fact always be dealing with the same ofstream, and the operator must be returning a reference to its left argument each time.

Obviously, the operator is making some change to the ofstream, as it is writing output, but there is no reason why it should change its other argument, so we can make the right argument a constant reference.

The prototype statement will therefore look like this:

     friend ostream & operator <<(ostream &, const MyClass &);

and the code will look something like this:

     ostream & operator <<(ostream & Os, const MyClass & MyVar)
     {
      ...code to print the data from one data member at a time...
      return Os;
     }

The same ideas apply to the extraction operator, except that of course it will change the value of its right argument, whcih therefore cannot be constant. It will have a prototype statement like this:

     friend istream & operator >>(istream &, MyClass &);

and the code will look something like this:

     istream & operator >>(istream & Is, MyClass & MyVar)
     {
      ....code to read data into one data member at a time...
      return Is;
     }


Example using the Rational class

We can add the prototypes for these operators to the Rational class:

     class Rational
     {
      private:
       int Num;                        //Numerator of a fraction
       int Den;                        //Denominator of a fraction 
      public:
       ...lots of methods...
       friend ostream & operator <<(ostream &, const Rational &);
       friend istream & operator >>(istream &, Rational &);
     };

An example of the code is:

     ofstream & operator <<(ofstream & Os, const Rational & MyVar)
     {
      cout << MyVar.Num << "/" << MyVar.Den;
      return Os;
     }

Here the operator can use the private data members Num and Den because it has been declared as a friend.


Comment

If the class involved has access methods for all its data members, we might be able to define these operators without making them friends of the class. This is probably more workable for the insertion operator than for the extraction operator. (Figure out why.)