• No results found

Abstract Data Types and C++ Classes

2.5 OPERATOR OVERLOADING

if (t1.flow( ) <= t2.flow( )) else

}

By the way, the C++ return statement uses the copy constructor to copy the function’s return value to a temporary location before returning the value to the calling program.

Self-Test Exercises for Section 2.4

22. Add default arguments to your throttle constructor from Self-Test Exer-cise 12 on page 51. Once you have done this, are the other two construc-tors still needed?

23. Which of these function calls could change the value of a point p:

cout << rotations_needed(p);

rotate_to_upper_right(p);

24. What is the difference between a formal parameter and an argument?

25. Suppose a function has a parameter named x, and the body of the func-tion changes the value of x. When should x be a value parameter? When should it be a reference parameter? With this function, could x ever be a const reference parameter?

26. Suppose the data type of a parameter is a class. The parameter cannot be modified inside the function. What kind of parameter is most efficient and secure for this purpose?

2.5 OPERATOR OVERLOADING

A binary function is a function with two arguments. Often, when you design a new class, there are binary functions to manipulate objects in the class. Some-times the new binary functions are naturally described using symbols such as ==

and+, which are symbols that C++ already uses to describe its own operations on numbers and other data types. For example, we might want to test whether two points are equal, and it seems natural to write this code:

throttle

return t1;

return t2;

Operator Overloading 75

point p1, p2;

if ( )

cout << "Those points are equal." << endl;

Unfortunately, the == operator cannot be used with a new class—unless you define a binary function that tells exactly what == means. In fact, C++ lets you define the meaning of many operators for a new class. Defining a new meaning for an operator is called overloading the operator. We’ll look at several com-mon overloading examples.

Overloading Binary Comparison Operators

The == operator that “compares for equality” can be overloaded for any new class by defining a function with a rather peculiar name. The name of the new function is operator ==, as shown in this example:

bool (const point& p1, const point& p2) // Postcondition: The value returned is true if p1 and p2 // are identical; otherwise false is returned.

{

return

(p1.get_x( ) == p2.get_x( ))

&&

(p1.get_y( ) == p2.get_y( ));

}

In order for this function to return true, both parts of the && expression must be true—in other words, both x and y coordinates of p1 must be equal to the corre-sponding coordinate in p2.

operator ==

Apart from the peculiar name operator ==, the function is just like any other function. It returns a boolean value that can be used as a true-or-false value, such as in an if-statement:

if ( )...

The overloaded operator is used in a program just like any other use of ==, by putting the first argument before == and the second argument after ==.

common usages of == are still available When you overload an operator, the common usages of that operator are still

available. For example, we can still use == to test the equality of two integers or two doubles. In fact, in the body of our operator ==, we do use the ordinary ==

to compare the doubles p1.get_x( ) and p2.get_x( ). This is fine. For each use of==, the compiler determines the data type of the objects being compared and uses the appropriate comparison function.

Once you have overloaded one operator, you can sometimes use the overloaded operator to make an easy implementation of another operator. For

p1 == p2

operator ==

p1 == p2

example, suppose we have defined operator == for the point class. Then we can quickly overload != to be the “not equal” operator:

operator != bool operator !=(const point& p1, const point& p2) // Postcondition: The value returned is true if p1 and p2 // are not identical; otherwise false is returned.

{

return ;

}

The expression deserves some examination. The == operator that we use is the overloaded == that we just defined for points. It returns true if the two points are equal, and false otherwise. We take the result of (p1 == p2) and reverse it with the usual not operator, “!”. So, if (p1 == p2) is true, then

!(p1 == p2) is false, and the != function returns false. On the other hand, if

(p1 == p2) is false, then !(p1 == p2) is true, and the != function returns true.

Theoperator == and operator != functions can also be defined as member functions rather than existing on their own. In this case, the p1 in an expression (p1 == p2) is the object that actually activates the member function, and p2 is an argument. In fact, p2 will be the only argument if we implement == as a member

other binary comparison operators

Overloading Binary Arithmetic Operators

In addition to the comparison operators, most of the other binary operators of C++ also can be overloaded for a new class. For example, the operators +,-,*, and/, which we normally think of as arithmetic operators, can all be overloaded for a new class. As a natural example, physicists often use points as objects that can be added by adding their x and y coordinates. If we could add two of our points, then we might write this program:

!(p1 == p2)

!(p1 == p2)

function (since the object that activates a member function is never actually listed in the parameter list). The choice between member function and non-member function is partly an issue of programming style. We prefer the use of the nonmember function since a nonmember function places the two argu-ments (p1 and p2) on equal footing. There really is no reason to say that p1 activates the operator any more than p2 does. (Later, you will find that non-member functions also provide more flexibility for classes with a feature called conversions.)

Figure 2.15 shows the six binary operators from C++ that are often overloaded as binary comparison operators for new classes.

FIGURE 2.15

Binary Operators That Are Often Overloaded As Comparison Functions

== !=

< >

<= >=

Operator Overloading 77

point speed1(5, 7);

point speed2(1, 2);

point total;

cout << total.get_x( ) << endl;

cout << total.get_y( ) << endl;

operator + In fact, we can define the meaning of + for points by overloading the + operator.

The overloaded operator has two parameters, which are the two points being added. And the function returns the sum of these two points, as shown here:

point (const point& p1, const point& p2) // Postcondition: The sum of p1 and p2 is returned.

{

Overloading Output and Input Operators

The standard C++ data types can be written and read using the output operator

<< and the input operator >>. For example, we can read and write an integer:

int i;

cin >> i;

cout << i;

total = speed1 + speed2;

sets total to

As with the binary comparison operators, a binary arithmetic operator can also be defined as a member function rather than a function that stands on its own. The member function would have just one parameter, which is the right-hand argument in an expression such as (p1 + p2). The left-hand argu-ment is the object that activates the member func-tion. Our programming style prefers implementing the binary operators as nonmember functions.

Figure 2.16 shows the five binary operators from C++ that are most often overloaded to perform arith-metic operations.

reads the value of i from the standard input

writes the value of i to the standard output

No doubt you would like to do the same with your impressive new point class:

point p;

cin >> p;

cout << p;

You can provide input /output power to the point class by overloading the

<< and >> operators. We start by overloading the output operator, which has the mysterious prototype shown here:

ostream& operator <<(ostream& outs, const point& source);

Let’s demystify this problematic prototype. The function has two parameters:

outs (which is an ostream) and source (which is a point). We use the func-tion by listing the two arguments like this:

cout << p;

As shown, the data type of cout is ostream, which means “output stream.” The

ostream class is part of the iostream library facility. The facility also defines

cout (the console output device or “standard output”) and provides the ability for programmers to define other output streams (such as output streams con-nected to a disk file or a printer). In any case, our intention is for the << function to print the point named source to the ostream named outs. We can now write most of our postcondition:

ostream& operator <<(ostream& outs, const point& source);

// Postcondition: The x and y coordinates of have been // written to .

Theouts parameter is a reference parameter, meaning that the function can change the output stream (by writing to it), and the change will affect the actual argument (such as the standard output stream, cout). The source parameter is a const reference parameter, meaning that the function will not alter the point that it is writing.

One last mystery remains: The return type of the function is ostream&:

operator <<(ostream& outs, const point& source);

For the most part, this return type means that the function returns an ostream. In fact, the function returns the ostream that it has just written. There is addi-tional meaning of the & symbol (called a reference return type). But we won’t use that additional meaning until Chapter 6, so it is enough to know that the out-put and inout-put operators both require a reference return type.

reads the x and y coordinates of p from the standard input writes the x and y coordinates of p to the standard output

The first argument, cout, is an ostream.

The second argument, p, is a point.

source outs

ostream&

Operator Overloading 79

With this in mind, we can now write the complete postcondition:

ostream& operator <<(ostream& outs, const point& source);

// Postcondition: The x and y coordinates of have been // written to . The return value is the ostream .

The reason that the function returns an ostream is that C++ will then permit the “chaining” of output statements such as the following:

cout << "The points are " << p << " and " << q << endl;

This example calls five << functions, with each function changing the ostream and passing the result on to the next function call.

operator <<

The complete implementation of the point’s output operator is shown at the top of Figure 2.17. Most of the work is done in this statement:

outs << source.get_x( ) << " " << source.get_y( );

The statement uses the ordinary << operator to print the coordinates of the point, with a single blank character in between.

source

outs outs

Function Implementations

ostream& operator <<(ostream& outs, const point& source) // Postcondition: The x and y coordinates of source have been // written to outs. The return value is the ostream outs.

// Library facilities used: iostream {

outs << source.get_x( ) << " " << source.get_y( );

return outs;

}

istream& operator >>(istream& ins, point& target) // Postcondition: The x and y coordinates of target have been // read from ins. The return value is the istream ins.

// Library facilities used: iostream // Friend of: point class

{

ins >> target.x >> target.y;

return ins;

}

FIGURE 2.17 Output and Input Operations for the Point

This prints the point’s coordinates with a blank in between.

This function must be a friend function since it requires direct access to the private members of the point class.

www.cs.colorado.edu/~main/chapter2/newpoint.cxx W W W

The prototype for the point’s input function is similar to the output function, but it uses an istream (input stream) instead of an ostream, as shown here:

istream& operator >>(istream& ins, point& target);

// Postcondition: The x and y coordinates of have been // read from . The return value is the istream .

operator >> The implementation of the input function is shown at the bottom of Figure 2.17.

The key work is accomplished with the usual >> operator reading two double numbers in the statement shown here:

ins >> target.x >> target.y;

But hold on! The statement sends input directly to the private member variables

x and y of the point. Only member functions can access private member vari-ables, and the input function is not a point member function. There are two pos-sible solutions to the problem:

1. We could write new member functions to set a point’s coordinates and use these member functions within the input function’s implementation.

2. Because we are the implementor of the point class, and we are also writ-ing the input function ourselves, we can grant special permission for the input function to access the private members of the point class.

The second approach is called using a friend function, which we’ll explain now.

Friend Functions friend functions

can access private members

A friend function is a function that is not a member function, but that still has access to the private members of the objects of a class. To declare a friend function, the function’s prototype is placed in a class definition, preceded by the keywordfriend.

For example, to declare the point’s input function as a friend, we must insert the friend prototype in the class definition, as shown here:

class point {

public:

...

// FRIEND FUNCTIONS

private:

...

};

target

ins ins

The point class with a new friend

friend istream& operator >>(istream& ins, point& target);

Operator Overloading 81

Once the friend prototype has been placed in the class definition, the body of the function may access private members of its point parameter, as shown here:

istream& operator >>(istream& ins, point& target) // Postcondition: The x and y coordinates of target have been // read from ins. The return value is the istream ins.

// Library facilities used: iostream //

{

ins >> target. >> target. ; return ins;

}

Notice that a friend function is not a member function, so it is not activated by a particular object of a class. All of the information that the friend function manipulates must be present in its parameters. It would be illegal to simply writex or y in the body of our function; we must write target.x and target.y. In our case, the friend operator >> has one point parameter, and it is the private member variables of this parameter that the function may access.

friendship and information hiding Friendship may be provided to any function, not just to operator functions.

But friendship should be limited to functions that are written by the programmer who implements the class—after all, this programmer is the only one who really knows about the private members. In this way, information hiding about a new class remains intact.