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