Conversions and Friends

Let’s bring addition to the Stonewt class. As mentioned in the discussion of the Time class, you can use either a member function or a friend function to overload addition. (To simplify matters, assume that no conversion functions of the operator double() form are defined.) You can implement addition with the following member function:

Stonewt Stonewt::operator+(const Stonewt & st) const
{
    double pds = pounds + st.pounds;
    Stonewt sum(pds);
    return sum;
}

Or you can implement addition as a friend function this way:

Stonewt operator+(const Stonewt & st1, const Stonewt & st2)
{
    double pds = st1.pounds + st2.pounds;
    Stonewt sum(pds);
    return sum;
}

Remember, you can provide the method definition or the friend definition but not both. Either form lets you do the following:

Stonewt jennySt(9, 12);
Stonewt bennySt(12, 8);
Stonewt total;
total = jennySt + bennySt;

Also given the Stonewt(double) constructor, each form lets you do the following:

Stonewt jennySt(9, 12);
double kennyD = 176.0;
Stonewt total;
total = jennySt + kennyD;

But only the friend function lets you do this:

Stonewt jennySt(9, 12);
double pennyD = 146.0;
Stonewt total;
total = pennyD + jennySt;

To see why, you can translate each addition into the corresponding function calls. First,

total = jennySt + bennySt;

becomes

total = jennySt.operator+(bennySt);   // member function

or else

total = operator+(jennySt, bennySt);  // friend function

In either case, the actual argument types match the formal arguments. Also the member function is invoked, as required, by a Stonewt object.

Next,

total = jennySt + kennyD;

becomes

total = jennySt.operator+(kennyD);   // member function

or else

total = operator+(jennySt, kennyD);  // friend function

Again, the member function is invoked, as required, by a Stonewt object. This time, in each case, one argument (kennyD) is type double, which invokes the Stonewt(double) constructor to convert the argument to a Stonewt object.

By the way, having an operator double() member function defined would create confusion at this point because that would create another option for interpretation. Instead of converting kennyD to double and performing Stonewt addition, the compiler could convert jennySt to double and perform double addition. Having too many conversion functions creates ambiguities.

Finally,

total = pennyD + jennySt;

becomes

total = operator+(pennyD, jennySt);  // friend function

Here, both arguments are type double, which invokes the Stonewt(double) constructor to convert them to Stonewt objects.

However, the member function version wouldn’t be able to add jennySt to pennyD. Converting the addition syntax to a function call would look like this:

total = pennyD.operator+(jennySt);   // not meaningful

But this is meaningless because only a class object can invoke a member function. C++ does not attempt to convert pennyD to a Stonewt object. Conversion takes place for member function arguments, not for member function invokers.

The lesson here is that defining addition as a friend makes it easier for a program to accommodate automatic type conversions. The reason is that both operands become function arguments, so function prototyping comes into play for both operands.

Choices in Implementing Addition

Given that you want to add double quantities to Stonewt quantities, you have a couple choices. The first, as you just saw, is to define the following as a friend function and have the Stonewt(double) constructor handle conversions of type double arguments to type Stonewt arguments:

operator+(const Stonewt &, const Stonewt &)

The second choice is to further overload the addition operator with functions that explicitly use one type double argument:

Stonewt operator+(double x);  // member function
friend Stonewt operator+(double x, Stonewt & s);

That way, the following statement exactly matches the operator+(double x) member function:

total = jennySt + kennyD; // Stonewt + double

And the following statement exactly matches the operator+(double x, Stonewt & s) friend function:

total = pennyD + jennySt; // double + Stonewt

Earlier, we did something similar for Vector multiplication.

Each choice has advantages. The first choice (relying on implicit conversions) results in a shorter program because you define fewer functions. That also implies less work for you and fewer chances to mess up. The disadvantage is the added overhead in time and memory needed to invoke the conversion constructor whenever a conversion is needed. The second choice (additional functions explicitly matching the types), however, is the mirror image. It makes for a longer program and more work on your part, but it runs a bit faster.

If your program makes intensive use of adding double values to Stonewt objects, it may pay to overload addition to handle such cases directly. If the program uses such addition only occasionally, it’s simpler to rely on automatic conversions, or if you want to be more careful, on explicit conversions.

Summary

This chapter covers many important aspects of defining and using classes. Some of the material in this chapter may seem vague to you until your own experiences enrich your understanding.

Normally, the only way you can access private class members is by using a class method. C++ alleviates that restriction with friend functions. To make a function a friend function, you declare the function in the class declaration and preface the declaration with the keyword friend.

C++ extends overloading to operators by letting you define special operator functions that describe how particular operators relate to a particular class. An operator function can be a class member function or a friend function. (A few operators can only be class member functions.) C++ lets you invoke an operator function either by calling the function or by using the overloaded operator with its usual syntax. An operator function for the operator op has this form:

operatorop(argument-list)

argument-list represents operands for the operator. If the operator function is a class member function, then the first operand is the invoking object and isn’t part of argument-list. For example, this chapter overloaded addition by defining an operator+() member function for the Vector class. If up, right, and result are three vectors, you can use either of the following statements to invoke vector addition:

result = up.operator+(right);
result = up + right;

For the second version, the fact that the operands up and right are type Vector tells C++ to use the Vector definition of addition.

When an operator function is a member function, the first operand is the object invoking the function. In the preceding statements, for example, the up object is the invoking object. If you want to define an operator function so that the first operand is not a class object, you must use a friend function. Then you can pass the operands to the function definition in whichever order you want.

One of the most common tasks for operator overloading is defining the << operator so that it can be used in conjunction with the cout object to display an object’s contents. To allow an ostream object to be the first operand, you define the operator function as a friend. To allow the redefined operator to be concatenated with itself, you make the return type ostream &. Here’s a general form that satisfies those requirements:

ostream & operator<<(ostream & os, const c_name & obj)
{
    os << ... ;  // display object contents
    return os;
}

If, however, the class has methods that return values for the data members you want to display, you can use those methods instead of direct access in operator<<(). In that case, the function needn’t (and shouldn’t) be a friend.

C++ lets you establish conversions to and from class types. First, any class constructor that takes a single argument acts as a conversion function, converting values of the argument type to the class type. C++ invokes the constructor automatically if you assign a value of the argument type to an object. For example, suppose you have a String class with a constructor that takes a char * value as its sole argument. Then, if bean is a String object, you can use the following statement:

bean = "pinto";   // converts type char * to type String

If, however, you precede the constructor declaration with the keyword explicit, the constructor can be used only for explicit conversions:

bean = String("pinto");   // converts type char * to type String explicitly

To convert from a class to another type, you must define a conversion function and provide instruction about how to make the conversion. A conversion function must be a member function. If it is to convert to type typeName, it should have the following prototype:

operator typeName();

Note that it must have no declared return type, must have no arguments, and must (despite having no declared return type) return the converted value. For example, a function to convert type Vector to type double would have this function form:

Vector::operator double()
{
    ...
    return a_double_value;
}

Experience has shown that often it is better not to rely on such implicit conversion functions.

As you might have noticed, classes require much more care and attention to detail than do simple C-style structures. In return, they do much more for you.

Chapter Review

1. Use a member function to overload the multiplication operator for the Stonewt class; have the operator multiply the data members by a type double value. Note that this will require carryover for the stone–pound representation. That is, twice 10 stone 8 pounds is 21 stone 2 pounds.

2. What are the differences between a friend function and a member function?

3. Does a nonmember function have to be a friend to access a class’s members?

4. Use a friend function to overload the multiplication operator for the Stonewt class; have the operator multiply the double value by the Stone value.

5. Which operators cannot be overloaded?

6. What restriction applies to overloading the following operators? =, (), [], and ->

7. Define a conversion function for the Vector class that converts a Vector object to a type double value that represents the vector’s magnitude.

Programming Exercises

1. Modify Listing 11.15 so that it writes the successive locations of the random walker into a file. Label each position with the step number. Also have the program write the initial conditions (target distance and step size) and the summarized results to the file. The file contents might look like this:

Target Distance: 100, Step Size: 20
0: (x,y) = (0, 0)
1: (x,y) = (-11.4715, 16.383)
2: (x,y) = (-8.68807, -3.42232)
...
26: (x,y) = (42.2919, -78.2594)
27: (x,y) = (58.6749, -89.7309)
After 27 steps, the subject has the following location:
(x,y) = (58.6749, -89.7309)
 or
(m,a) = (107.212, -56.8194)
Average outward distance per step = 3.97081

2. Modify the Vector class header and implementation files (Listings 11.13 and 11.14) so that the magnitude and angle are no longer stored as data components. Instead, they should be calculated on demand when the magval() and angval() methods are called. You should leave the public interface unchanged (the same public methods with the same arguments) but alter the private section, including some of the private method and the method implementations. Test the modified version with Listing 11.15, which should be left unchanged because the public interface of the Vector class is unchanged.

3. Modify Listing 11.15 so that instead of reporting the results of a single trial for a particular target/step combination, it reports the highest, lowest, and average number of steps for N trials, where N is an integer entered by the user.

4. Rewrite the final Time class example (Listings 11.10, 11.11, and 11.12) so that all the overloaded operators are implemented using friend functions.

5. Rewrite the Stonewt class (Listings 11.16 and 11.17) so that it has a state member that governs whether the object is interpreted in stone form, integer pounds form, or floating-point pounds form. Overload the << operator to replace the show_stn() and show_lbs() methods. Overload the addition, subtraction, and multiplication operators so that one can add, subtract, and multiply Stonewt values. Test your class with a short program that uses all the class methods and friends.

6. Rewrite the Stonewt class (Listings 11.16 and 11.17) so that it overloads all six relational operators. The operators should compare the pounds members and return a type bool value. Write a program that declares an array of six Stonewt objects and initializes the first three objects in the array declaration. Then it should use a loop to read in values used to set the remaining three array elements. Then it should report the smallest element, the largest element, and how many elements are greater or equal to 11 stone. (The simplest approach is to create a Stonewt object initialized to 11 stone and to compare the other objects with that object.)

7. A complex number has two parts: a real part and an imaginary part. One way to write an imaginary number is this: (3.0, 4.0). Here 3.0 is the real part and 4.0 is the imaginary part. Suppose a = (A,Bi) and c = (C,Di). Here are some complex operations:

• Addition: a + c = (A + C, (B + D)i)

• Subtraction: a - c = (A - C, (B - D)i)

• Multiplication: a × c = (A × C - B×D, (A×D + B×C)i)

• Multiplication: (x a real number): x × c = (x×C,x×Di)

• Conjugation: ~a = (A, - Bi)

Define a complex class so that the following program can use it with correct results:

#include <iostream>
using namespace std;
#include "complex0.h"  // to avoid confusion with complex.h
int main()
{
    complex a(3.0, 4.0);   // initialize to (3,4i)
    complex c;
    cout << "Enter a complex number (q to quit): ";
    while (cin >> c)
    {
        cout << "c is " << c << ' ';
        cout << "complex conjugate is " << ~c << ' ';
        cout << "a is " << a << ' ";
        cout << "a + c is " << a + c << ' ';
        cout << "a - c is " << a - c << ' ';
        cout << "a * c is " << a * c << ' ';
        cout << "2 * c is " << 2 * c << ' ';
        cout << "Enter a complex number (q to quit): ";
    }
    cout << "Done! ";
    return 0;
}

Note that you have to overload the << and >> operators. Standard C++ already has complex support—rather more extensive than in this example—in a complex header file, so use complex0.h to avoid conflicts. Use const whenever warranted.

Here is a sample run of the program:

Enter a complex number (q to quit):
real: 10
imaginary: 12
c is (10,12i)
complex conjugate is (10,-12i)
a is (3,4i)
a + c is (13,16i)
a - c is (-7,-8i)
a * c is (-18,76i)
2 * c is (20,24i)
Enter a complex number (q to quit):
real: q
Done!

Note that cin >> c, through overloading, now prompts for real and imaginary parts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.58.50.156