159.234 Lecture 10
Download
Report
Transcript 159.234 Lecture 10
159.234
LECTURE 13
Operator Overloading
Today:
const member functions
Overloading Operators
• Postfix/infix increment operator
• Binary operators overloading
• Overloading I/O operators
Textbook p.203—216
1
const Member Functions
A member function that is not allowed to modify data
members of its class is declared as a const member
function.
class Point {
public:
Point(): x(0),y(0){};
Point(int a,int b){x = a;y = b;}
void print() const;
private:
int x, y;
};
void Point::print() const {
cout <<" x = " << x <<", y = " << y;
}
2
const Member Functions
CORRECT:
void Point::print() const
{ cout <<" x = " << x <<", y = " << y;}
WRONG:
void Point::print() const
{ x=4; cout <<" x = " << x <<", y = " << y;}
Error message: Cannot modify a const object.
3
Operator Overloading
We have already seen Function overloading.
Operators + - etc, can also be re-defined!
User-defined types can be treated in exactly the same way as simple
types.
e.g. for a 'complex' type we can allow the addition of two variables.
Operators can be thought of as functions:
-x
x+y
->
->
negate(x)
add(x,y)
with operator overloading, we use 'natural operators’ rather than having
to use functions.
4
Operator Overloading
We can overload:
arithmetic
logical
comparison
assignment
index
function
structure pointer
Only 4 operators cannot be
overloaded:
+ - etc
&& ||
> == etc
=
[]
()
->
new, delete
dot
conditional
scope
.
?:
::
sizeof
5
Operator Overloading
Although the semantics of an operator can be extended, we
cannot change its syntax, the grammatical rules that govern
its use, such as the number of operands, precedence and
associativity remains the same.
e.g. the multiplication operator will enjoy higher precedence
than the addition operator.
When an operator is overloaded, its original meaning is not lost.
e.g. An operator+ that is overloaded to add two vectors, can still
be used to add two integers.
6
Defining Operator Overloading
To define an additional task to an operator, we must specify what it
means in relation to the class to which the operator will be applied.
This is done with the help of a special function called operator function
which describes the task.
General form of an operator function:
return type classname :: operator (op-arglist)
{
function body //task defined
}
Operator being
overloaded
operator is the function name
7
Binary Operator Overloading
e.g. overloading the + operator:
Using a member function
class clock {
public:
clock(int seconds=0) : total(seconds) {}
clock operator+(int seconds) {
clock temp;
temp.total = total + seconds;
return temp;
}
void print(void) {cout << total << endl;}
private:
int total;
};
...
clock a(50), b;
b = a + 10;
b.print();
There is an implicit first
argument (the this pointer).
It takes some time to get
used to it.
Allows only expressions
with an object on the
left-hand side of the
operator
8
Binary Operator Overloading
e.g. overloading the + operator:
Using a friend function
class clock {
public:
clock(int seconds=0) : total(seconds) {} //for construction and conversion
friend clock operator+(clock c1, clock c2);
void print(void) {cout << total << endl;}
private:
int total;
};
It is normal for symmetric
Binary operators to be
overloaded by friend functions.
clock operator+(clock c1, clock c2)
{
return (c1.total + c2.total);
}
9
Unary Operator Overloading
e.g. overloading the prefix ++ operator:
class clock {
public:
clock(int seconds=0) : total(seconds) {}
clock operator++() {
total++;
return *this;
}
void print(void) {cout << total << endl;}
private:
int total;
};
...
a = ++b;
b.print();
10
Overloading the postfix ++ Operator
Can we differentiate between prefix and postfix?
In the code previously shown - the compiler uses the overloaded ++ for
both postfix and prefix.
To create different prefix and postfix versions we need some help from the
compiler.
The compiler knows whether the code is prefix or postfix. If it is postfix the
compiler uses an 'ad hoc' convention, it tries to find a version of operator++
with an integer argument:
clock operator++(int) {
clock temp = *this;
total++;
return temp;
}
The value of the integer is
irrelevant, but it gives us
the chance to write two
different functions.
11
Overloading the [ ] Operator
Arrays are often abused.
Range violations are common.
Negative indices or indices greater than the
maximum cause programs to access wrong
data.
We can create an array class that
overloads [ ], and checks array bounds:
Returning a reference to
p[i] does not return a copy
of p[i], but returns p[i]
itself!
class vect {
public:
vect(int n) : size(n) {
p = new int[size];
assert(p!=NULL);
for (int i=0;i<size;i++) {
p[i] = 0;
}
}
~vect() {delete [] p;}
int& operator[](int i) {
assert((i>=0) && (i<size));
return p[i];
}
private:
int *p;
int size;
12
};
Operator Overloading
Why does operator[] return an int & rather than an int?
vect v(10);
int i;
i = v[0]; // read the value at v[0];
v[0] = 3; // write to v[0]!
// we return a reference
// so we can alter the value
Functions that return references can appear on the left
hand side of an assignment statement!
13
Operator Overloading
We have seen several examples of how
operators can be overloaded – but this is
not the whole story.
a b //where is an operator +, - , etc
is replaced by the compiler with:
a.operator(b)
and it then looks to see whether you have
written this particular member function.
'this' becomes the lhs and b the rhs.
14
Operator Overloading
1
a.operator(b)
It also checks the 'type' of b - so operator can be overloaded.
If it cannot find any member function that matches, it replaces the text
with:
2
operator(a,b)
A non-member function, with two arguments, and tries again to find a
match in the functions you have defined.
You may choose to use either way.
15
Operator Overloading
We are not allowed to overload operators for the standard types
operator+(int i, int j)
will not work.
This leads to some difficulties.
Suppose we have a clock class, and want to be able to say:
clock a,b,c;
c = a + b;
This works if we have either a member function of clock
clock clock::operator+(clock y);
or a non-member function:
clock operator+(clock x, clock y);
16
Operator Overloading
Suppose we also want to say:
c = a + 10;
This is ok if we have either
clock clock::operator+(int i);
or
clock operator+(clock x, int i);
What if we want to say:
c = 10 + a;
Now we run in to difficulties.
The compiler cannot convert this into a member function because we are not
allowed to treat 'int' as a class and overload its operators.
int::operator(clock x);
is wrong!
17
Operator Overloading
The compiler can only try to match:
clock operator+(int i, clock x);
We have lost symmetry.
If we want to allow 10 + a as well as a + 10 then it is neater to make them
both non-member functions.
Non-member functions cannot access private data!
We must either make them friends, (frowned on, perhaps)
or
provide functions to access the data – get() and set() routines, or whatever
we want to call them (better but more work)
18
Operator Overloading
Remember that some operators change the value of one of
their arguments, and others do not.
c++;
the increment operator adds one to the value of c
a+b
operator+ does not change the value of either a or b, it
returns their sum
19
Overloading I/O Operators
cout << a;
Can we overload the output operator?
Yes – but remember that the compiler first tries to find a member
function of the ostream class (cout is an object of ostream), that
accepts a clock object as a parameter
cout.operator<<(a);
The writer of the ostream class will not have included this function!
And you cannot add it.
Our only option is to supply the compiler with a non-member
function to match
operator<<(cout, a);
20
Operator Overloading
class clock
{
clock(int seconds=0);
friend ostream & operator<<(ostream &out, const clock &x);
friend istream & operator>>(istream &in, clock &x);
...
unsigned long total;
};
Stream extraction (output) operator
Note: operator precedence: left to right
ostream & operator<<(ostream &out, const clock &x)
{
operator<< (also known as stream deletion
int h, m, s;
operator) is usually called several times in a
h = x.total / 3600;
single statement.
m = (x.total / 60) % 60;
Consequently, the output of each call to
s = x.total % 60;
out << h << “:” << m << “:” << s; operator<< cascades into the next call as input.
That’s why it needs to return a reference to the
return out;
21
output
stream
(cout).
}
Operator Overloading
Stream insertion (input) operator
istream & operator>>(istream &in, clock &x)
{
int h, m, s;
in >> h >> m >> s; //input retrieved as h m s
x.total = h*3600 + m*60 + s;
return in;
}
22
Operator Overloading
Overloading the assignment operator.
The copy constructor is called whenever we
• pass an object as an argument,
• return an object, or
• intialise an object with one of the same type
There is a need for a copy constructor because the default
system copy constructor performs only shallow copy, and is not
normally adequate when pointers are involved.
Therefore, we need to perform a deep copy to copy what the
pointers are pointing to.
23
Operator Overloading
When we assign an object to another:
str a, b;
a = b;
NO copy constructor is involved. It is an assignment. If we want
assignment to behave like a deep copy constructor, we need to
overload the assignment operator.
str& str::operator=(const str &x){
delete[] s;
Rule of thumb: Any time a class needs
s = new char[128];
an explicit copy constructor defined,
strcpy(s, x.s);
it also needs an assignment operator
return *this;
24
defined.
}
Argument Types
Argument types.
You may always pass objects as arguments – sometimes we
really want to – but most times, if we do, they lead to very
inefficient code. If you pass across an object a copy constructor
will be called.
Instead of passing an object – most of the time we pass a
reference to an object instead. Only a pointer is copied!
However, passing a reference is not normally acceptable. If the
actual argument to a function is a constant, then the compiler
will not let you pass it to a reference parameter – the function
may try to change the value of a constant!
25
Argument Types
The answer is to pass a 'const' reference parameter.
Now the compiler can check that the function does not
change its value (it will complain if it does try to) and constant
can safely be passed in the knowledge that they can't be
changed.
Passing a const reference is more efficient than passing
an object.
26
Return Types
These are again quite complicated. It is tempting to think that the assignment
operator does not 'return' anything and so it should be void
void str::operator=(str x)
However this is NOT correct.
It is perfectly correct to write
a = b = c;
Associativity of assignment operator: Right
which is the same as:
a = (b = c);
(b = c) has the 'value' of a.
operator= must return an str object. Or to make it more efficient a reference to
27
an str object.
Return Types
Should it return a const reference?
Well surprisingly no!
We may want to stop anyone from writing:
(a = b) = c;
but strangely enough this is legal for integers!
(i = 3) = 2;
first it assigns 3 to i and then assigns 2 to i
And if we can do it with integers we should be allowed to do it with
strings.
28
Return Types
There was one final blunder when we overloaded the assignment operator for
strings!
a = a;
Isn't very sensible, but it is legal. A program that uses our str class will fail if this
statement occurs.
We must modify our function as follows:
str & str::operator=(const str &x){
if (&x == this) {
return *this;
}
delete[] s;
s = new char[128];
strcpy(s,x.s);
return *this;
}
Allows multiple assignment
with right-to-left associativity
to be defined
29
Overloading Assignment and
Subscripting Operators
Common Characteristics
• Must be done as non-static member functions
• Usually involve a reference return type
30
Return Types
Be careful when returning reference types. Consider
the case of the prefix ++ operator:
clock& operator++(){
total++;
return *this;
}
This is ok, and more efficient than returning a clock
object (a constructor would be called!)
31
Return Types
But in the case of the postfix ++ operator:
clock& operator++(int) {
clock temp = *this;
total++;
return temp;
}
This is wrong! We are returning a reference to temp, and temp no
longer exists!
For certain functions we must return an object, not a reference.
const clock operator++(int) {
clock temp = *this;
total++;
return temp;
}
32
33
34
Return Types
Postfix ++ is nearly always less efficient than
prefix ++ for user defined classes.
To see demo:
view Clock3.cpp
35