Tip:
Highlight text to annotate it
X
We're going to begin the discussion of overloading operators. You're familiar with the operators, they're symbols. And what are they?
They're functions, actually. For your user-defined types, you're going to be able to now define these symbol
operations like multiplication and etc, etc. So, let's see how this works. Okay, for number one, let's discuss just what an operator is.
An operator is a function and it's a different kind of function. It's a symbol. We usually talk about functions as having parameters to which we pass
arguments and operators as having parameters to which we pass operands. Well, they're the same thing, operators and arguments are the
same. The difference between a function and an operator is that the name of an operator is "operator"
and then the symbol. In C++, operator is a key word, and that becomes part of the name of that function. So, it's going to be operator
and then whatever symbol. For instance operator*, operator+, operator less than less than. Whatever it is. And of course operators
are going to be called differently from the way functions are called and we're going to take a look at some examples.
We speak of operator overloading because all operators have already been defined for the built in C++ types. You cannot create new operators.
So, if you can't create new ones,
then the old ones have to be redefined, and hence you're overloading that operator. Of course you can define operators as either members of a
class or nonmembers of a class. And, remember that when we are defining an operator, we're defining
it for a user-defined type. In other words, a new class of objects.
So that function can either be defined outside the class or it can be defined inside the class.
So, let's take a look at some examples of calls. Here's a binary operator, I just chose star as an example, you use it as
'var1' times 'var2'. A binary operator, it has two operands or two parameters.
Okay, so 'var1' is the left-hand-side operand and 'var2' is the right-hand-side operand. If this function, if this operator '*', and I'm going to refer
to the functions, the operators, as "operator * ()".
This is the way I'll refer to that function. If it's a nonmember function then 'var1' and 'var2' are arguments you pass to the parameters, but if it is
a member function, then 'var1' is the calling object and 'var2' is passed to the parameter. And remember
that every member function has a calling object. It is something that you want to really, really remember. Every member
function has a calling object. Some object of that class has to call that
function. If you always remember that, that'll help you a lot. Now, let's take a look at a unary operator.
A unary operator has one argument, or one operand. So, I've chosen the ~ operator as
an example. And the way you use that is, you have the symbol,
the ~ in this case, juxtaposed to the operand that you're going to operate on. But, the symbol comes before
that operand. So, 'var1' is the only operand. If that function,
that operator ~, is a nonmember function, then 'var1' is what is passed to the parameter. And
if it's a member function, then 'var1' is the calling object.
So, let's review, real quickly, for binary operators.
If it's defined as a nonmember, it's going to have two parameters.
If it's a member, then what? Well, there are two operands, but one has to be the calling object so there's only one parameter.
One operand, the one on the left, is the calling object and one on the right fills the single parameter.
But for a unary operator, if it's in nonmember function, yes it will have one parameter and the operand and that it's operating on is passed
to that parameter. But if it is a member function, then it has no parameters, because the one and only operand has to be the calling object.
Always remember the calling object.
Let's review, real quickly, what some of the operators are. Okay, the first ones you saw were the insertion/extraction operators,
and yes you can overload those. And yes, that means it's a binary operator.
That means that "cin" is an operand and 'var' is an operand. These are objects that you're passing to this function.
There the arithmetic operators: +, -, *, /, etc. The mod operator, the assignment operator, arithmetic assignment operators,
these quick guys: +=, -=, *=, etc. There are the bracket and parenthesis operators.
Yes they are operators, they can be overloaded and many, many others.
The logical operators, the relational operators.
There are five operators that you cannot overload. Three of these you've been introduced to: the scope resolution operator, the dot operator, this
dot star operator, sizeof operator and Ternary operator or conditional operator. The dot star and sizeof we will not talk about in this course.
But, in any case they cannot be overloaded. Alright, there are some additional rules. As I said before, you can't create new operators.
So, for instance if you want a "b^" operator, you cannot do this. You can't say, "var1 b^ var2". That's illegal, can't do that.
Can't invent operators. Okay, if you overload an operator as a nonmember function,
then at least one parameter has to be a user- defined type.
You can't change the arity of an operator. If it's binary it stays binary. If it's unary it stays unary.
Now, you have to note that there are some operators that are both unary and binary.
The '-' operator, that can be either negation of some quantity or it can be subtraction.
And the star operator is also binary and unary. You know it's binary for multiplication, you've seen that. It is also the
pointer dereferencing operator which you know nothing about, but that's a unary operator.
You can't change the order of precedence of the of operators and
there are certain operations that have to be overloaded as member functions of a class and they are the assignment bracket
function evaluation and the member selection operator, again of which you know nothing about (that last one).
There can't be any default arguments for operator overloads. And some rules of thumb. Now, these are not hard and fast rules.
This first one is a reasonable rule: make your definitions make sense. For example, if you are going to create a complex number class,
you can define the plus operator, but certainly don't define it for subtraction or multiplication or
division or something like that. It just doesn't make any sense.
As a very general rule, and again this is not a hard and fast rule, if an operator is going to change an object then
make it a member. If it's not, then make a nonmember.
You want to define symmetric and asymmetric pairs in terms each other. An example of a symmetric pair is the "==" and
"!=" pair. So if you define one, define the other in terms of that one. And we'll see an example of this.
Asymmetric pairs are things like multiplication and the multiplication equals.
So, star and star equal, I call asymmetric pair. So, as a first example, let's recall our "mult_fracs" function.
This is an absolutely horrible function. It returns a fraction and takes two fractions. so I'm going to do something like:
What I'm emulating is "h = f*g", and that's what I want. I want to be able to write this. This is good.
This is horrible. So, I'm going to overload the star operator.
Let's take a look at how we do it. So, notice it's very similar. The only thing that's changing is the name of the function. And that is
operator star. So, I'm going to declare it as a nonmember function.
So you can see it's not scoped here as fraction, therefor I'm going to declare it as a friend so that I can access the private member variables easily.
The name of the function is "operator *", it returns a fraction; it takes a fraction and a fraction. Notice I pass const reference, of course you know why.
And here's the definition. Now, note that in the definition I'm going to use *= between two fraction objects. Result is a fraction right-
hand-side is a fraction. So, I'm using the *= operator in the definition of the * operator.
And of course that has to be defined. Here's the definition. So, again I'm defining the star operator and the star equals operator.
We're going to go through the details of how this is defined in another lesson (we'll actually step through a code trace), but the important
aspect of this to consider at this point is this guy right here. I have the return value being a pointer to the calling object. In C++
sometimes you need to get hold of, in a function, in a member function, you need to get ahold of
the calling object. And the way we do that is with
a pointer called "this". "This" is a pointer. So, what's a pointer? A pointer is a different kind of variable. It holds the address
of some type of object. So in this case, in this example I've shown here, I have this pointing to an int.
It's different, it's not an integer. It's pointing to a memory address.
So in this case, 'x' refers to a pointer and '*x' refers to the int that's being pointed to. So, when you're talking about user-defined types,
then "this" is a pointer or an address of the calling object of the current function being executed. But, "this",
and that's the key word, is not the calling object. It's a pointer to, it's an address of the calling object. To get to the calling object then you have
to use "*this". That is the calling object. So, in this function I want to return a reference to a fraction. I'm going to return
"*this" which is the calling object.
Why am I returning a reference? Well, that gives us the ability to chain. So, I can do something like this:
If I did not return a reference,
then this I could not do. That's enough for this session. We'll take a look at a step through trace of
calling these two operators in the next session.