Tải bản đầy đủ (.pdf) (40 trang)

Absolute C++ (phần 9) pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (500.93 KB, 40 trang )

320 Operator Overloading, Friends, and References

FRIEND FUNCTIONS
If your class has a full set of accessor and mutator functions, you can use the accessor
and mutator functions to define nonmember overloaded operators (as in Display 8.1 as
opposed to Display 8.2). However, although this may give you access to the private
member variables, it may not give you efficient access to them. Look again at the defi-
nition of the overloaded addition operator
+ given in Display 8.1. Rather than just
reading four member variables, it must incur the overhead of two invocations of
get-
Cents
and two invocations of getDollars. This adds a bit of inefficiency and also can
make the code harder to understand. The alternative of overloading + as a member gets
around this problem at the price of losing automatic type conversion of the first oper-
and. Overloading the + operator as a friend will allow us to both directly access mem-
ber variables and have automatic type conversion for all operands.
A friend function of a class is not a member function of the class, but it has access
to the private members of that class (to both private member variables and private
member functions) just as a member function does. To make a function a friend func-
tion, you must name it as a friend in the class definition. For example, in Display 8.3
we have rewritten the definition of the class
Money yet another time. This time we have
overloaded the operators as friends. You make an operator or function a friend of a class
by listing the operator or function declaration in the definition of the class and placing
the keyword
friend in front of the operator or function declaration.
A friend operator or friend function has its declaration listed in the class definition,
just as you would list the declaration of a member function, except that you precede the
declaration by the keyword
friend. However, a friend is not a member function; rather,


it really is an ordinary function with extraordinary access to the data members of the
class. The friend is defined exactly like the ordinary function it is. In particular, the oper-
ator definitions shown in Display 8.3 do not include the qualifier
Money:: in the func-
tion heading. Also, you do not use the keyword
friend in the function definition (only
in the function declaration). The friend operators in Display 8.3 are invoked just like
the nonfriend, nonmember operators in Display 8.1, and they have automatic type con-
version of all arguments just like the nonfriend, nonmember operators in Display 8.1.
The most common kinds of friend functions are overloaded operators. However,
any kind of function can be made a friend function.
A function (or overloaded operator) can be a friend of more than one class. To make
it a friend of multiple classes, just give the declaration of the friend function in each
class for which you want it to be a friend.
Many experts consider friend functions (and operators) to be in some sense not
“pure.” They feel that in the true spirit of object-oriented programming all operators
and functions should be member functions. On the other hand, overloading operators
as friends provides the pragmatic advantage of automatic type conversion in all argu-
ments, and since the operator declaration is inside the class definitions, it provides at
least a bit more encapsulation than nonmember, nonfriend operators. We have shown
you three ways to overload operators: as nonmember nonfriends, as members, and as
friends. You can decide for yourself which technique you prefer.
friend
function
08_CH08.fm Page 320 Wednesday, August 13, 2003 1:02 PM
Friend Functions and Automatic Type Conversion 321
Display 8.3 Overloading Operators as Friends
(part 1 of 2)
1 #include <iostream>
2 #include <cstdlib>

3 #include <cmath>
4 using namespace std;
5 //Class for amounts of money in U.S. currency
6 class Money
7 {
8 public:
9 Money( );
10 Money(double amount);
11 Money(int dollars, int cents);
12 Money(int dollars);
13 double getAmount( ) const;
14 int getDollars( ) const;
15 int getCents( ) const;
16 void input( ); //Reads the dollar sign as well as the amount number.
17 void output( ) const;
18 friend const Money operator +(const Money& amount1, const Money& amount2);
19 friend const Money operator -(const Money& amount1, const Money& amount2);
20 friend bool operator ==(const Money& amount1, const Money& amount2);
21 friend const Money operator -(const Money& amount);
22 private:
23 int dollars; //A negative amount is represented as negative dollars and
24 int cents; //negative cents. Negative $4.50 is represented as
-4 and -50.
25 int dollarsPart(double amount) const;
26 int centsPart(double amount) const;
27 int round(double number) const;
28 };
29 int main( )
30 {
31


<
If the main function is the same as in Display 8.1, then the screen dialogue
will be the same as shown in Display 8.1.
>
32 }
33
34 const Money operator +(const Money& amount1, const Money& amount2)
35 {
36 int allCents1 = amount1.cents + amount1.dollars*100;
37 int allCents2 = amount2.cents + amount2.dollars*100;
38 int sumAllCents = allCents1 + allCents2;
39 int absAllCents = abs(sumAllCents); //Money can be negative.
40 int finalDollars = absAllCents/100;
08_CH08.fm Page 321 Wednesday, August 13, 2003 1:02 PM
322 Operator Overloading, Friends, and References
Display 8.3 Overloading Operators as Friends
(part 2 of 2)
41 int finalCents = absAllCents%100;
42 if (sumAllCents < 0)
43 {
44 finalDollars = -finalDollars;
45 finalCents = -finalCents;
46 }
47 return Money(finalDollars, finalCents);
48 }
49 const Money operator -(const Money& amount1, const Money& amount2)
50

<

The complete definition is Self-Test Exercise 7.
>
51 bool operator ==(const Money& amount1, const Money& amount2)
52 {
53 return ((amount1.dollars == amount2.dollars)
54 && (amount1.cents == amount2.cents));
55 }
56 const Money operator -(const Money& amount)
57 {
58 return Money(-amount.dollars, -amount.cents);
59 }


<
Definitions of all other member functions are the same as in Display 8.1.
>
60
Note that friends have direct access to
member variables.
F
RIEND
F
UNCTIONS
A friend function of a class is an ordinary function except that it has access to the private members
of objects of that class. To make a function a friend of a class, you must list the function declaration
for the friend function in the class definition. The function declaration is preceded by the keyword
friend. The function declaration may be placed in either the private section or the public section,
but it will be a public function in either case, so it is clearer to list it in the public section.
S
YNTAX


OF

A
C
LASS
D
EFINITION

WITH
F
RIEND
F
UNCTIONS
class
Class_Name
{
public:
friend
Declaration_for_Friend_Function_1
friend
Declaration_for_Friend_Function_2
.
.
.
You need not list the
friend functions first.
You can intermix the
order of these
declarations.

08_CH08.fm Page 322 Wednesday, August 13, 2003 1:02 PM
Friend Functions and Automatic Type Conversion 323
Pitfall
C
OMPILERS

WITHOUT
F
RIENDS
On some C++ compilers friend functions simply do not work as they are supposed to work. Worst
of all, they may work sometimes and not work at other times. On these compilers friend functions
do not always have access to private members of the class as they are supposed to. Presumably,
this will be fixed in later releases of these compilers. In the meantime, you will have to work
around this problem. If you have one of these compilers for which friend functions do not work,
you must either use accessor functions to define nonmember functions and overloaded operators
or you must overload operators as members.

FRIEND CLASSES
A class can be a friend of another class in the same way that a function can be a friend
of a class. If the class
F is a friend of the class C, then every member function of the class
F is a friend of the class C. To make one class a friend of another, you must declare the
friend class as a friend within the other class.

Member_Function_Declarations
private:

Private_Member_Declarations
};
E

XAMPLE
class FuelTank
{
public:
friend void fillLowest(FuelTank& t1, FuelTank& t2);
//Fills the tank with the lowest fuel level, or t1 if a tie.
FuelTank(double theCapacity, double theLevel);
FuelTank(
);
void input(
);
void output(
) const;
private:
double capacity;//in liters
double level;
};
A friend function is
not
a member function. A friend function is defined and called the same way
as an ordinary function. You do not use the dot operator in a call to a friend function, and you do
not use a type qualifier in the definition of a friend function.
friend class
08_CH08.fm Page 323 Wednesday, August 13, 2003 1:02 PM
324 Operator Overloading, Friends, and References
Self-Test Exercises
When one class is a friend of another class, it is typical for the classes to reference
each other in their class definitions. This requires that you include a forward declara-
tion to the class defined second, as illustrated in the outline that follows this paragraph.
Note that the forward declaration is just the heading of the class definition followed by

a semicolon.
If you want the class
F to be a friend of the class C, the general outline of how you set
things up is as follows:
class F; //forward declaration
class C
{
public:

friend class F;

};
class F
{

Complete examples using friend classes are given in Chapter 17. We will not be
using friend classes until then.
6. What is the difference between a friend function for a class and a member function for the
class?
7. Complete the definition of the friend subtraction operator
- in Display 8.3.
8. Suppose you wish to overload the operator
< so that it applies to the type Money defined in
Display 8.3. What do you need to add to the definition of
Money given in Display 8.3?
References and More Overloaded Operators
Do not mistake the pointing finger for the moon.
Zen Saying
This section covers some specialized, but important, overloading topics, including
overloading the assignment operator and the

<<, >>, [], ++, and operators. Because
forward
declaration
8.3
08_CH08.fm Page 324 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 325
you need to understand returning a reference to correctly overload some of these oper-
ators, we also discuss this topic.

REFERENCES
A reference is the name of a storage location.
2
You can have a standalone reference, as
in the following:
int robert;
int& bob = robert;
This makes bob a reference to the storage location for the variable robert, which makes
bob an alias for the variable robert. Any change made to bob will also be made to rob-
ert
. Stated this way, it sounds like a standalone reference is just a way to make your
code confusing and get you in trouble. In most instances, a standalone reference is just
R
ULES

ON
O
VERLOADING
O
PERATORS
■ When overloading an operator, at least one parameter (one operand) of the

resulting overloaded operator must be of a class type.
■ Most operators can be overloaded as a member of the class, a friend of the class,
or a nonmember, nonfriend.
■ The following operators can only be overloaded as (nonstatic) members of the
class:
=, [], ->, and ( ).
■ You cannot create a new operator. All you can do is overload existing operators
such as
+, -, *, /, %, and so forth.
■ You cannot change the number of arguments that an operator takes. For exam-
ple, you cannot change
% from a binary to a unary operator when you overload %;
you cannot change
++ from a unary to a binary operator when you overload it.
■ You cannot change the precedence of an operator. An overloaded operator has
the same precedence as the ordinary version of the operator. For example,
x*y + z
always means
(x*y) + z, even if x, y, and z are objects and the operators + and *
have been overloaded for the appropriate classes.
■ The following operators cannot be overloaded: the dot operator (.), the scope
resolution operator (
::), sizeof, ?:, and the operator .*, which is not discussed
in this book.
■ An overloaded operator cannot have default arguments.
2
If you know about pointers, you will notice that a reference sounds like a pointer. A reference
is essentially, but not exactly, a constant pointer. There are differences between pointers and ref-
erences, and they are not completely interchangeable.
reference

08_CH08.fm Page 325 Wednesday, August 13, 2003 1:02 PM
326 Operator Overloading, Friends, and References
trouble, although there a few cases where it can be useful. We will not discuss standal-
one references any more, nor will we use them.
As you may suspect, references are used to implement the call-by-reference parame-
ter mechanism. So, the concept is not completely new to this chapter, although the
phrase a reference is new.
We are interested in references here because returning a reference will allow you to
overload certain operators in a more natural way. Returning a reference can be viewed
as something like returning a variable or, more precisely, an alias to a variable. The syn-
tactic details are simple. You add an
& to the return type. For example:
double& sampleFunction(double& variable);
Since a type like double& is a different type from double, you must use the & in both
the function declaration and the function definition. The return expression must be
something with a reference, such as a variable of the appropriate type. It cannot be an
expression, such as
X + 5. Although many compilers will let you do it (with unfortu-
nate results), you also should not return a local variable because you would be generat-
ing an alias to a variable and immediately destroying the variable. A trivial example of
the function definition is
double& sampleFunction(double& variable)
{
return variable;
}
Of course, this is a pretty useless, even troublesome, function, but it illustrates the con-
cept. For example, the following code will output
99 and then 42:
double m = 99;
cout << sampleFunction(m) << endl;

sampleFunction(m) = 42;
cout << m << endl;
We will only be returning a reference when defining certain kinds of overloaded
operators.
L-V
ALUES

AND
R-V
ALUES
The term l-value is used for something that can appear on the left-hand side of an assignment
operator. The term r-value is used for something that can appear on the right-hand side of an
assignment operator.
If you want the object returned by a function to be an l-value, it must be returned by reference.
08_CH08.fm Page 326 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 327
Pitfall
R
ETURNING

A
R
EFERENCE

TO
C
ERTAIN
M
EMBER
V

ARIABLES
When a member function returns a member variable and that member variable is of some class
type, then it should normally not be returned by reference. For example, consider
class A
{
public:
const SomeClass getMember( ) { return member; }

private:
SomeClass member;

};
where SomeClass is, of course, some class type. The function getMember should not return a
reference, but should instead return by
const value as we have done in the example.
The problem with returning a reference to a class type member variable is the same as what we
described for returning the member variable by non-const value in the programming tip section
of this chapter entitled RR
RR
ee
ee
tt
tt
uu
uu
rr
rr
nn
nn
ii

ii
nn
nn
gg
gg


MM
MM
ee
ee
mm
mm
bb
bb
ee
ee
rr
rr


VV
VV
aa
aa
rr
rr
ii
ii
aa

aa
bb
bb
ll
ll
ee
ee
ss
ss


oo
oo
ff
ff


aa
aa


CC
CC
ll
ll
aa
aa
ss
ss
ss

ss


TT
TT
yy
yy
pp
pp
ee
ee


When returning a member
variable which is itself of some class type it should normally be returned by
const value. (Every
such rule has rare exceptions.)

OVERLOADING
>>
AND
<<
The operators >> and << can be overloaded so that you can use them to input and out-
put objects of the classes you define. The details are not that different from what we
have already seen for other operators, but there are some new subtleties.
The insertion operator
<< that we used with cout is a binary operator very much
like
+ or For example, consider the following:
cout << "Hello out there.\n";

The operator is <<, the first operand is the predefined object cout (from the library
iostream), and the second operand is the string value "Hello out there.\n". The pre-
defined object
cout is of type ostream, and so when you overload <<, the parameter
that receives
cout will be of type ostream. You can change either of the two operands to
<<. When we cover file I/O in Chapter 12 you will see how to create an object of type
ostream that sends output to a file. (These file I/O objects as well as the objects cin and
cout are called streams, which is why the library name is ostream.) The overloading
that we create, with
cout in mind, will turn out to also work for file output without
any changes in the definition of the overloaded
<<.
In our previous definitions of the class
Money (Display 8.1 through Display 8.3) we
used the member function
output to output values of type Money. This is adequate, but
<< is an
operator
stream
overloading
08_CH08.fm Page 327 Wednesday, August 13, 2003 1:02 PM
328 Operator Overloading, Friends, and References
it would be nicer if we could simply use the insertion operator << to output values of
type
Money, as in the following:
Money amount(100);
cout << "I have " << amount << " in my purse.\n";
instead of having to use the member function output as shown below:
Money amount(100);

cout << "I have ";
amount.output( );
cout << " in my purse.\n";
One problem in overloading the operator << is deciding what value, if any, should
be returned when
<< is used in an expression like the following:
cout << amount
The two operands in the above expression are cout and amount, and evaluating the
expression should cause the value of
amount to be written to the screen. But if << is an
operator like
+ or -, then the above expression should also return some value. After all,
expressions with other operands, such as
n1 + n2, return values. But what does cout <<
amount
return? To obtain the answer to that question, we need to look at a more com-
plicated expression involving
<<.
Let’s consider the following expression, which involves evaluating a chain of expres-
sions using
<<:
cout << "I have " << amount << " in my purse.\n";
If you think of the operator << as being analogous to other operators, such as +, then
the above should be (and in fact is) equivalent to the following:
( (cout << "I have ") << amount ) << " in my purse.\n";
What value should << return to make sense of the above expression? The first thing
evaluated is the subexpression:
(cout << "I have ")
If things are to work out, then the above subexpression had better return cout so that
the computation can continue as follows:

( cout << amount ) << " in my purse.\n";
And if things are to continue to work out, (cout << amount) had better also return
cout so that the computation can continue as follows:
cout << " in my purse.\n";
chains of <<
08_CH08.fm Page 328 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 329
This is illustrated in Display 8.4. The operator << should return its first argument,
which is of type
ostream (the type of cout).
Thus, the declaration for the overloaded operator
<< (to use with the class Money)
should be as follows:
class Money
{
public:
. . .
friend ostream& operator <<(ostream& outs, const Money& amount);
<< returns
a stream
Display 8.4 << as an Operator
cout << "I have " << amount << " in my purse.\n";
means the same as
((cout << "I have ") << amount) << " in my purse.\n";
and is evaluated as follows:
First evaluate
(cout << "I have "), which returns cout:
((cout << "I have ") << amount) << " in my purse.\n";
(cout << amount) << " in my purse.\n";
Then evaluate (cout << amount), which returns cout:

(cout << amount) << " in my purse.\n";
cout << " in my purse.\n";
Then evaluate cout << " in my purse.\n", which returns cout:
cout << " in my purse.\n";
cout;
The string "I have" is output.
The string
"in my purse.\n" is output.
The value of
amount is output.
Since there are no more
<< operators, the process ends.
08_CH08.fm Page 329 Wednesday, August 13, 2003 1:02 PM
330 Operator Overloading, Friends, and References
Once we have overloaded the insertion (output) operator <<, we will no longer need
the member function
output and will delete output from our definition of the class
Money. The definition of the overloaded operator << is very similar to the member func-
tion
output. In outline form, the definition for the overloaded operator is as follows:
ostream& operator <<(ostream& outputStream, const Money& amount)
{
<This part is the same as the body of
Money::output which is given in Display 8.1 (except that

dollars is replaced with amount.dollars
and cents is replaced by amount.cents).>
return outputStream;
}
Note that the operator returns a reference.

The extraction operator
>> is overloaded in a way that is analogous to what we
described for the insertion operator
<<. However, with the extraction (input) operator
>>, the second argument will be the object that receives the input value, so the second
parameter must be an ordinary call-by-reference parameter. In outline form the defini-
tion for the overloaded extraction operator
>> is as follows:
istream& operator >>(istream& inputStream, Money& amount)
{
<This part is the same as the body of
Money::input, which is given in Display 8.1 (except that

dollars is replaced with amount.dollars
and cents is replaced by amount.cents).>
return inputStream;
}
The complete definitions of the overloaded operators << and >> are given in Display
8.5, where we have rewritten the class
Money yet again. This time we have rewritten the
class so that the operators
<< and >> are overloaded to allow us to use these operators
with values of type
Money.
Note that you cannot realistically overload
>> or << as member operators. If << and
>> are to work as we want, then the first operand (first argument) must be cout or cin
(or some file I/O stream). But if we want to overload the operators as members of, say,
the class
Money, then the first operand would have to be the calling object and so would

have to be of type
Money, and that will not allow you to define the operators so they
behave in the normal way for
>> and <<.
<< and >>
return
a reference
08_CH08.fm Page 330 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 331
Display 8.5 Overloading << and >>
(part 1 of 3)
1 #include <iostream>
2 #include <cstdlib>
3 #include <cmath>
4 using namespace std;
5 //Class for amounts of money in U.S. currency
6 class Money
7 {
8 public:
9 Money( );
10 Money(double amount);
11 Money(int theDollars, int theCents);
12 Money(int theDollars);
13 double getAmount( ) const;
14 int getDollars( ) const;
15 int getCents( ) const;
16 friend const Money operator +(const Money& amount1, const Money& amount2);
17 friend const Money operator -(const Money& amount1, const Money& amount2);
18 friend bool operator ==(const Money& amount1, const Money& amount2);
19 friend const Money operator -(const Money& amount);

20 friend ostream& operator <<(ostream& outputStream, const Money& amount);
21 friend istream& operator >>(istream& inputStream, Money& amount);
22 private:
23 int dollars; //A negative amount is represented as negative dollars and
24 int cents; //negative cents. Negative $4.50 is represented as -4 and -50.
25 int dollarsPart(double amount) const;
26 int centsPart(double amount) const;
27 int round(double number) const;
28 };
29 int main( )
30 {
31 Money yourAmount, myAmount(10, 9);
32 cout << "Enter an amount of money: ";
33 cin >> yourAmount;
34 cout << "Your amount is " << yourAmount << endl;
35 cout << "My amount is " << myAmount << endl;
36
37 if (yourAmount == myAmount)
38 cout << "We have the same amounts.\n";
39 else
40 cout << "One of us is richer.\n";
41 Money ourAmount = yourAmount + myAmount;
08_CH08.fm Page 331 Wednesday, August 13, 2003 1:02 PM
332 Operator Overloading, Friends, and References
Display 8.5 Overloading << and >>
(part 2 of 3)
42 cout << yourAmount << " + " << myAmount
43 << " equals " << ourAmount << endl;
44 Money diffAmount = yourAmount - myAmount;
45 cout << yourAmount << " - " << myAmount

46 << " equals " << diffAmount << endl;
47 return 0;
48 }


<
Definitions of other member functions are as in Display 8.1.
Definitions of other overloaded operators are as in Display 8.3.
>
49 ostream& operator <<(ostream& outputStream, const Money& amount)
50 {
51 int absDollars = abs(amount.dollars);
52 int absCents = abs(amount.cents);
53 if (amount.dollars < 0 || amount.cents < 0)
54 //accounts for dollars == 0 or cents == 0
55 outputStream << "$-";
56 else
57 outputStream << ’$’;
58 outputStream << absDollars;
59 if (absCents >= 10)
60 outputStream << ’.’ << absCents;
61 else
62 outputStream << ’.’ << ’0’ << absCents;
63 return outputStream;
64 }
65
66 //Uses iostream and cstdlib:
67 istream& operator >>(istream& inputStream, Money& amount)
68 {
69 char dollarSign;

70 inputStream >> dollarSign; //hopefully
71 if (dollarSign != ’$’)
72 {
73 cout << "No dollar sign in Money input.\n";
74 exit(1);
75 }
76 double amountAsDouble;
77 inputStream >> amountAsDouble;
78 amount.dollars = amount.dollarsPart(amountAsDouble);
Since << returns a
reference, you can chain
<< like this.
You can chain
>> in a
similar way.
In the
main function, cout is
plugged in for outputStream.
For an alternate input algorithm,
see Self-Test Exercise 3 in Chapter 7.
Returns a reference
In the
main function, cin is
plugged in for
inputStream.
Since this is not a member operator, you
need to specify a calling object for
member functions of
Money.
08_CH08.fm Page 332 Wednesday, August 13, 2003 1:02 PM

References and More Overloaded Operators 333
Self-Test Exercises
9. In Display 8.5, the definition of the overloaded operator << contains lines like the fol-
lowing:
outputStream << "$-";
Isn’t this circular? Aren’t we defining << in terms of <<?
10. Why can’t we overload
<< or >> as member operators?
11. Below is the definition for a class called
Percent. Objects of type Percent represent per-
centages such as 10% or 99%. Give the definitions of the overloaded operators
>> and <<
so that they can be used for input and output with objects of the class
Percent. Assume
that input always consists of an integer followed by the character
’%’, such as 25%. All per-
centages are whole numbers and are stored in the
int member variable named value. You
do not need to define the other overloaded operators and do not need to define the con-
structor. You only have to define the overloaded operators
>> and <<.
#include <iostream>
using namespace std;
class Percent
{
public:
friend bool operator ==(const Percent& first,
const Percent& second);
friend bool operator <(const Percent& first,
const Percent& second);

Percent( );
Display 8.5 Overloading << and >>
(part 3 of 3)
79 amount.cents = amount.centsPart(amountAsDouble);
80 return inputStream;
81 }
S
AMPLE
D
IALOGUE
\
Enter an amount of money: $123.45
Your amount is $123.45
My amount is $10.09.
One of us is richer.
$123.45 + $10.09 equals $133.54
$123.45 - $10.09 equals $113.36
Returns a reference
08_CH08.fm Page 333 Wednesday, August 13, 2003 1:02 PM
334 Operator Overloading, Friends, and References
friend istream& operator >>(istream& inputStream,
Percent& aPercent);
friend ostream& operator <<(ostream& outputStream,
const Percent& aPercent);
//There would normally also be other members and friends.
private:
int value;
};
O
VERLOADING


>>

AND

<<
The input and output operators >> and << can be overloaded just like any other operators. If you
want the operators to behave as expected for
cin, cout, and file I/O, then the value returned
should be of type
istream for input and ostream for output, and the value should be returned
by reference.
D
ECLARATIONS
class
Class_Name
{
. . .
public:
. . .
friend istream& operator >>(istream&
Parameter_1
,

Class_Name
&
Parameter_2
);
friend ostream& operator <<(ostream&
Parameter_3

,
const
Class_Name
&
Parameter_4
);
. . .
The operators do not need to be friends but cannot be members of the class being input or
output.
D
EFINITIONS
istream& operator >>(istream&
Parameter_1
,
Class_Name
& Parameter_2)
{
. . .
08_CH08.fm Page 334 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 335
Tip
W
HAT
M
ODE

OF
R
ETURNED
V

ALUE

TO
U
SE
A function can return a value of type T in four different ways:
By plain old value, as in the function declaration
T f( );
By constant value, as in the function declaration const T f( );
By reference, as in the function declaration T& f( );
By const reference, as in the function declaration const T& f( );
There is not unanimous agreement on which to use when. So, do not expect too much consistency
in usage. Even when an author or programmer has a clear policy, they seldom manage to follow it
without exception. Still, some points are clear.
If you are returning a simple type, like
int or char, there is no point in using a const when
returning by value or by reference. So programmers typically do not use a
const on the return
type when it is a simple type. If you want the simple value returned to be allowed as an l-value,
that is to be allowed on the left-hand side of an assignment statement, then return by reference;
otherwise return the simple type by plain old value. Class types are not so simple. The rest of this
discussion applies to returning an object of a class type.
The decision on whether or not to return by reference has to do with whether or not you want to be
able to use the returned object as an as an l-value. If you want to return something that can be
used as an l-value, that is, that can be used on the left-hand side of an assignment operator, you
must return by reference and so must use an ampere sign
& on the returned type.
Returning a local variable (or other short lived object) by reference, with or without a
const, can
produce problems and should be avoided.

For class types, the two returned type specifications const T and const T& are very similar. They
both mean that you cannot change the returned object by invoking some mutator function
directly on the returned object, as in
f( ).mutator( );
}
ostream& operator <<(ostream&
Parameter_3
,
const
Class_Name
&
Parameter_4
)
{
. . .
}
If you have enough accessor and mutator functions, you can overload >> and << as nonfriend
functions. However, it is natural and more efficient to define them as friends.
08_CH08.fm Page 335 Wednesday, August 13, 2003 1:02 PM
336 Operator Overloading, Friends, and References
The returned value can still be copied to another variable with an assignment operator and that
other variable can have the mutator function applied to it. If you cannot decide between the
const T& and const T, use const T (without the ampersand). A const T& is perhaps a bit more
efficient than a
const T.
3
However, the difference is not typically that important and most pro-
grammers use
const T rather than const T& as a retuned type specification. As noted earlier,
const T& can sometimes cause problems.

The following summary may be of help.
T is assumed to be a class type. Copy constructors are not
covered until Chapter 10, but we include details about them here for reference value. If you have
not yet read Chapter 10, simply ignore all references to copy constructors.
If a public member function returns a private class member variable, it should always have a
const on the returned type, as we explained in the pitfall section of this chapter entitled RR
RR
ee
ee
tt
tt
uu
uu
rr
rr
nn
nn


ii
ii
nn
nn
gg
gg


MM
MM
ee

ee
mm
mm
bb
bb
ee
ee
rr
rr


VV
VV
aa
aa
rr
rr
ii
ii
aa
aa
bb
bb
ll
ll
ee
ee
ss
ss



oo
oo
ff
ff


aa
aa


CC
CC
ll
ll
aa
aa
ss
ss
ss
ss


TT
TT
yy
yy
pp
pp
ee

ee


(One exception to this rule is that programmers normally
always return a value of type
string by ordinary value, not by const value. This is presumably
because the type
string is thought of as a simple type like int and char, even though string
is a class type.)
The following summary may be of help.
T is assumed to be a class type.
Simple retuning by value, as in the function declaration
T f( );
Cannot be used as an l-value, and the returned value can be changed directly as in
f( ).mutator( ). Calls the copy constructor.
Returning by constant value, as in
const T f( );
This case is the same as the previous case, but the returned value cannot be changed directly
as in
f( ).mutator( ).
Returning by reference as in
T& f( );
Can be used as an l-value, and the returned value can be changed directly as in
f( ).muta-
tor( )
. Does not call the copy constructor.
Returning by constant reference, as in
const T& f( );
Cannot be used as an l-value, and the returned value cannot be changed directly as in
f( ).mutator( ). Does not call the copy constructor.


THE ASSIGNMENT OPERATOR
If you overload the assignment operator =, you must overload it as a member operator.
If you do not overload the assignment operator
=, then you automatically get an assign-
ment operator for your class. This default assignment operator copies the values of
member variables from one object of the class to the corresponding member variables
of another object of the class. For simple classes, that is usually what you want. When
we discuss pointers, this default assignment operator will not be what we want, and we
will discuss overloading the assignment operator at that point.
3
This is because const T& does not call the copy constructor while const T does call the copy
constructor. Copy constructors are discussed in Chapter 10.
08_CH08.fm Page 336 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 337
Self-Test Exercises

OVERLOADING THE INCREMENT AND DECREMENT OPERATORS
The increment and decrement operators ++ and each have two versions. They can
do different things depending on whether they are used in prefix notation,
++x, or post-
fix (suffix) notation,
x++. Thus, when overloading these operators, you need to some-
how distinguish between the prefix and postfix versions so that you can have two
versions of the overloaded operator. In C++ this distinction between prefix and postfix
versions is handled in a way that at first reading (and maybe even on second reading)
seems a bit contrived. If you overload the
++ operator in the regular way (as a nonmem-
ber operator with one parameter or as a member operator with no parameters), then
you have overloaded the prefix form. To obtain the postfix version,

x++ or x , you add
a second parameter of type
int. This is just a marker for the compiler; you do not give
a second
int argument when you invoke x++ or x
For example, Display 8.6 contains the definition of a class whose data is pairs of
integers. The increment operator
++ is defined so it works in both prefix and postfix
notation. We have defined
++ so that it has the intuitive spirit of ++ on int variables.
This is the best way to define ++, but you are free to define it to return any kind of type
and perform any kind of action.
The definition of the postfix version ignores that
int parameter, as shown in Display
8.6. When the compiler sees
a++, it treats it as an invocation of IntPair::opera-
tor++(int)
, with a as the calling object.
The increment and decrement operator on simple types, such as
int and char,
return by reference in the prefix form and by value in the postfix form. If you want to
emulate what happens with simple types when you overload these operators for your
class types, then you would return by reference for the prefix form and by value for the
postfix form. However, we find it opens the door to too many problems to return by
reference with increment or decrement operators, and so we always simply return by
value for all versions of the increment and decrement operators.
12. Is the following legal? Explain your answer. (The definition of IntPair is given in Display 8.6.)
IntPair a(1,2);
(a++)++;


OVERLOADING THE ARRAY OPERATOR [

]
You can overload the square brackets, [], for a class so that they can be used with
objects of the class. If you want to use
[] in an expression on the left-hand side of an
assignment operator, then the operator must be defined to return a reference. When
overloading
[], the operator [] must be a member function.
pre
fi
x an
d

postfix
return
b
y
reference
08_CH08.fm Page 337 Wednesday, August 13, 2003 1:02 PM
338 Operator Overloading, Friends, and References
Display 8.6 Overloading ++
(part 1 of 2)
1 #include <iostream>
2 #include <cstdlib>
3 using namespace std;
4 class IntPair
5 {
6 public:
7 IntPair(int firstValue, int secondValue);

8 IntPair operator++( ); //Prefix version
9 IntPair operator++(int); //Postfix version
10 void setFirst(int newValue);
11 void setSecond(int newValue);
12 int getFirst( ) const;
13 int getSecond( ) const;
14 private:
15 int first;
16 int second;
17 };
18 int main( )
19 {
20 IntPair a(1,2);
21 cout << "Postfix a++: Start value of object a: ";
22 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
23 IntPair b = a++;
24 cout << "Value returned: ";
25 cout << b.getFirst( ) << " " << b.getSecond( ) << endl;
26 cout << "Changed object: ";
27 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
28 a = IntPair(1, 2);
29 cout << "Prefix ++a: Start value of object a: ";
30 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
31 IntPair c = ++a;
32 cout << "Value returned: ";
33 cout << c.getFirst( ) << " " << c.getSecond( ) << endl;
34 cout << "Changed object: ";
35 cout << a.getFirst( ) << " " << a.getSecond( ) << endl;
36 return 0;
37 }

38
39 IntPair::IntPair(int firstValue, int secondValue)
40 : first(firstValue), second(secondValue)
41 {/*Body intentionally empty*/}
You need not give a parameter name in a
function or operator declaration. For
++ it
makes sense to give no parameter since
the parameter is not used.
08_CH08.fm Page 338 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 339
Display 8.6 Overloading ++
(part 2 of 2)
42 IntPair IntPair::operator++(int ignoreMe) //Postfix version
43 {
44 int temp1 = first;
45 int temp2 = second;
46 first++;
47 second++;
48 return IntPair(temp1, temp2);
49 }
50 IntPair IntPair::operator++( ) //Prefix version
51 {
52 first++;
53 second++;
54 return IntPair(first, second);
55 }
56 void IntPair::setFirst(int newValue)
57 {
58 first = newValue;

59 }
60 void IntPair::setSecond(int newValue)
61 {
62 second = newValue;
63 }
64 int IntPair::getFirst( ) const
65 {
66 return first;
67 }
68 int IntPair::getSecond( ) const
69 {
70 return second;
71 }
S
AMPLE
D
IALOGUE
Postfix a++: Start value of object a: 1 2
Value returned: 1 2
Changed object: 2 3
Prefix ++a: Start value of object a: 1 2
Value returned: 2 3
Changed object: 2 3
08_CH08.fm Page 339 Wednesday, August 13, 2003 1:02 PM
340 Operator Overloading, Friends, and References
It may help to review the syntax for the operator [], since it is different from any
other operator we have seen. Remember that
[] is overloaded as a member operator;
therefore one thing in an expression using
[] must be the calling object. In the expres-

sion
a[2], a is the calling object and 2 is the argument to the member operator [].
When overloading
[], this “index” parameter can be any type.
For example, in Display 8.7 we define a class called
Pair whose objects behave like
arrays of characters with the two indexes
1 and 2 (not 0 and 1). Note that the expres-
sions
a[1] and a[2] behave just like array indexed variables. If you look at the defini-
tion of the overloaded operator
[], you will see that a reference is returned and that it is
a reference to a member variable, not to the entire
Pair object. This is because the
member variable is analogous to an indexed variable of an array. When you change
a[1] (in the sample code in Display 8.7), you want that to be a change to the member
variable
first. Note that this gives access to the private member variables to any pro-
gram, for example, via
a[1] and a[2] in the sample main function in Display 8.7.
Although
first and second are private members, the code is legal because it does not
reference
first and second by name but indirectly using the names a[1] and a[2].
Display 8.7 Overloading []
(part 1 of 2)
1 #include <iostream>
2 #include <cstdlib>
3 using namespace std;
4 class CharPair

5 {
6 public:
7 CharPair( ){/*Body intentionally empty*/}
8 CharPair(char firstValue, char secondValue)
9 : first(firstValue), second(secondValue)
10 {/*Body intentionally empty*/}
11
12 char& operator[](int index);
13 private:
14 char first;
15 char second;
16 };
17 int main( )
18 {
19 CharPair a;
20 a[1] = ’A’;
21 a[2] = ’B’;
08_CH08.fm Page 340 Wednesday, August 13, 2003 1:02 PM
References and More Overloaded Operators 341

OVERLOADING BASED ON L-VALUE VERSUS R-VALUE
Although we will not be doing it in this book, you can overload a function name (or
operator) so that it behaves differently when used as an l-value and when it is used as an
r-value. (Recall that an l-value means it can be used on the left-hand side of an assign-
ment statement.) . For example, if you want a function
f to behave differently depend-
ing on whether it is used as an l-value or an r-value, you can do so as follows:
Display 8.7 Overloading []
(part 2 of 2)
22 cout << "a[1] and a[2] are:\n";

23 cout << a[1] << a[2] << endl;
24 cout << "Enter two letters (no spaces):\n";
25 cin >> a[1] >> a[2];
26 cout << "You entered:\n";
27 cout << a[1] << a[2] << endl;
28 return 0;
29 }
30
31 //Uses iostream and cstdlib:
32 char& CharPair::operator[](int index)
33 {
34 if (index == 1)
35 return first;
36 else if (index == 2)
37 return second;
38 else
39 {
40 cout << "Illegal index value.\n";
41 exit(1);
42 }
43 }
S
AMPLE
D
IALOGUE
a[1] and a[2] are:
AB
Enter two letters (no spaces):
CD
You entered:

CD
Note that you return the member variable, not the
entire
Pair object, because the member variable
is analogous to an indexed variable of an array.
08_CH08.fm Page 341 Wednesday, August 13, 2003 1:02 PM
342 Operator Overloading, Friends, and References
class SomeClass
{
public:
int& f( ); // will be used in any l-value invocation
const int& f( ) const; // used in any r-value invocation

};
The two parameter lists need not be empty, but they should be the same (or else you
just get simple overloading). Be sure to notice that the second declarations of
f has two
occurrences of
const. You must include both occurrences of const. The ampersand
signs
& are of course also required.
■ Operators, such as + and ==, can be overloaded so that they can be used with objects
of a class type that you define.
■ An operator is just a function that uses a different syntax for invocations.
■ A friend function of a class is an ordinary function except that it has access to the
private members of the class, just like member functions do.
■ When an operator is overloaded as a member of a class, the first operand is the call-
ing object.
■ If your classes each have a full set of accessor functions, then the only reason to
make a function a friend is to make the definition of the friend function simpler and

more efficient, but that is often reason enough.
■ A reference is a way of naming a variable. It is essentially an alias for the variable.
■ When overloading the >> or << operators, the type returned should be a stream type
and should be a reference, which is indicated by appending an
& to the name of the
returned type.
ANSWERS TO SELF-TEST EXERCISES
1. The difference between a (binary) operator (such as +, *, or /) and a function involves the syn-
tax of how they are called. In a function call, the arguments are given in parentheses after the
function name. With an operator the arguments are given before and after the operator. Also,
you must use the reserved word
operator in the operator declaration and in the definition of
an overloaded operator.
2. Add the following declaration and function definition:
bool operator <(const Money& amount1, const Money& amount2);
bool operator <(const Money& amount1, const Money& amount2)
{
Chapter Summary
08_CH08.fm Page 342 Wednesday, August 13, 2003 1:02 PM
Answers to Self-Test Exercises 343
int dollars1 = amount1.getDollars( );
int dollars2 = amount2.getDollars( );
int cents1 = amount1.getCents( );
int cents2 = amount2.getCents( );
return ((dollars1 < dollars2) ||
((dollars1 == dollars2) && (cents1 < cents2)));
}
3. When overloading an operator, at least one of the arguments to the operator must be of a
class type. This prevents changing the behavior of
+ for integers.

4. If you omit the
const at the beginning of the declaration and definition of the overloaded
plus operator for the class
Money, then the following is legal:
(m1 + m2) = m3;
If the definition of the class Money is as shown in Display 8.1, so that the plus operator
returns by
const value, then it is not legal.
5.
const Money
Money::operator -(const Money& secondOperand) const
{
int allCents1 = cents + dollars*100;
int allCents2 = secondOperand.cents
+ secondOperand.dollars*100;
int diffAllCents = allCents1 - allCents2;
int absAllCents = abs(diffAllCents);
int finalDollars = absAllCents/100;
int finalCents = absAllCents%100;
if (diffAllCents < 0)
{
finalDollars = -finalDollars;
finalCents = -finalCents;
}
return Money(finalDollars, finalCents);
}
6. A friend function and a member function are alike in that they both can use any member of
the class (either public or private) in their function definition. However, a friend function is
defined and used just like an ordinary function; the dot operator is not used when you call
a friend function and no type qualifier is used when you define a friend function. A mem-

ber function, on the other hand, is called using an object name and the dot operator. Also,
a member function definition includes a type qualifier consisting of the class name and the
scope resolution operator,
::.
08_CH08.fm Page 343 Wednesday, August 13, 2003 1:02 PM
344 Operator Overloading, Friends, and References
7. //Uses cstdlib:
const Money operator -(const Money& amount1,
const Money& amount2)
{
int allCents1 = amount1.cents + amount1.dollars*100;
int allCents2 = amount2.cents + amount2.dollars*100;
int diffAllCents = allCents1 - allCents2;
int absAllCents = abs(diffAllCents);
int finalDollars = absAllCents/100;
int finalCents = absAllCents%100;
if (diffAllCents < 0)
{
finalDollars = -finalDollars;
finalCents = -finalCents;
}
return Money(finalDollars, finalCents);
}
8. Add the following declaration and function definition:
friend bool operator <(const Money& amount1,
const Money& amount2);
bool operator <(const Money& amount1,
const Money& amount2)
{
return ((amount1.dollars < amount2.dollars) ||

((amount1.dollars == amount2.dollars) &&
(amount1.cents < amount2.cents)));
}
9. To understand why it is not circular, you need to think about the basic message of over-
loading: A single function or operator name can have two or more definitions. That means
that two or more different operators (or functions) can share a single name. In the line
outputStream << "$-";
the operator << is the name of an operator defined in the library iostream to be used when
the second argument is a quoted string. The operator named
<< that we define in Display
8.5 is a different operator that works when the second argument is of type
Money.
10. If
<< and >> are to work as we want, then the first operand (first argument) must be cout
or
cin (or some file I/O stream). But if we want to overload the operators as members of,
say, the class
Money, then the first operand would have to be the calling object and so
would have to be of type
Money, and that is not what we want.
08_CH08.fm Page 344 Wednesday, August 13, 2003 1:02 PM

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×