Before we get into declaring and defining overloaded operators for our own classes,
let's understand what they are.
We've already seen what operators are and how to use them.
We have traditional operators with traditional meetings.
These include the plus symbol for addition, the equal sign for assignment and many more.
The c++ operators are defined to work with the primitive
or built in c++ types,
such as integer, doubles, longs and so forth.
In fact we've already seen that the c++ operators are overloaded to work with different types.
For example, the plus symbol can add two integers, two doubles,
an integer and a double and so forth.
C++ allows us to overload operators for our own user-defined types.
This allows our types to behave and feel similar to the built-in types.
more readable and more writeable. The only operator that the compiler provides a default implementation for
is the assignment operator, the single equal sign.
That's because the compiler must be able to assign one object to another.
All the other operators that can be overloaded must be explicitly defined by the programmer.
So what does operator overloading look like from the perspective of using the objects.
Well, first, let's see how we would write code if we were not overloading operators.
Suppose we've implemented our own class name number,
and that class can model any kind of number: an integer, a double,
a complex number, an irrational number, you name it.
If we want to be able to manipulate number objects so that we can add them, subtract them,
multiply them and so forth, we could implement non-member functions
or member functions to achieve what we want.
So if we wanted to add a and b, then multiply that result by the
quotient of c divided by d.
Our code might look like the first statement.
If we wanted to use member methods,
then we need objects and method names so our code might look like the second statement.
Are these statements very unreadable and very difficult to write?
Maybe it depends on your perspective, but the point is
that we're adding dividing and multiplying
and we've been using these operators to do that since we were little kids in school why can't we use them now.
Of course, the answer is that c++ doesn't know what to do with our user-defined types.
In most cases, it makes no sense to add or multiply or subtract your objects.
For example, what does it mean to subtract two player objects in a game
or multiply two account objects?
It's totally dependent on what you want to do.
In many cases, it doesn't make sense to overload certain operators for your classes.
In those cases don't do it. Only do it if it makes sense.
In this example, I would argue that it does make sense since the users of the number class
would know the meaning and how to use those operators we would overload.
Let's see what our calculation would look like if we overloaded the addition,
multiplication and division operators.
Here you can see that our user-defined number class
now looks and feels and behaves like the built in c++ types.
Operator overloading is syntactic sugar.
Behind the scenes, we're still calling methods or functions.
We'll see how it all works in the next video.
So what operators can be overloaded?
Well, c++ allows you to overload most of its operators.
There are a few exceptions and they're listed in the table.
We can't overload the scope resolution operator, the conditional operator,
the pointer to member operator, the dot operator and the sizeof operator.
Remember, just because an operator can be overloaded doesn't mean you should.
Don't overload it unless it makes sense and makes your code
more usable, more readable and more writable.
So as always, there are some basic rules that must be followed when you're overloading operators.
First the precedence and associativity of the operator that you're overloading
cannot be changed.
This makes perfect sense since c++ programmers
are already familiar with the precedence and associativity of its operators.
The arity of an operator cannot be changed.
So if an operator is a binary operator
which means that it's applied to two arguments, then this can't be changed.
C++ does not allow you to change the way that the current operators
work with the built-in types.
C++ does it all you create new operators that are not currently used by c++.
And finally, c++ allows you to overload operators using global functions
or member methods.
However, there are several operators that must be implemented
as member methods as you can see. Notice that the assignment operator
must be implemented as a member method.
Let's wrap up this video by looking at some
examples where operator overloading could be used.
The left column shows examples of using operators with the built-in types.
For example, we can use plus
and equals for addition of integers, doubles and logs and assignment.
We also use the insertion operator
with many built-in types so that we can easily insert values into streams.
Now take a look at the right column.
Here we have examples of using operators on user-defined types.
The first is std string.
We can use the plus operator to concatenate strings,
and we can compare strings using less than,
greater than less than or equal to and so forth.
In this section, we're going to create our own version of a string class named my string and,
we want to be able to overload some operators to concatenate mystrings
compare them, assign them and so forth.
The last example is the player class.
Does it make sense to overload operators for the player class?
Maybe, it depends on what we're trying to achieve.
For example, suppose we want to compare two player objects p1 and p2.
We might only care about their health attribute, and this would do just that.
What about comparing two player objects with the equality operator,
are we comparing names, health, xp,
all of those attributes or just some of them.
That's one of the issues with overloading operators.
We can make the operator mean anything we want.
So we want to make sure
that when we do overload operators it makes sense and the users of the class know about it.
Finally, it would make sense to overload the stream insertion operator
so that we can insert player objects into a stream.
So let's look at the class that we'll be using in this section of the course.
We're going to add overloaded operators to this basic class called mystring.
Mystring models a string and will implement it behind the scenes using a
raw c-style pointer.
I chose to include a raw pointer because it will allow us more practice
with our copy and move constructors,
and we'll also have to think about copy and move assignment
when we overload the assignment operator.
Right now the class contains a no args constructor,
a constructor that expects a c-style string or literal,
a copy constructor, a destructor, a display method that displays the string
and two getter methods. The first one returns the current length of the string
and the other returns a pointer to the string.
That's it.
At this point in the course, you should start feeling pretty comfortable
when you see a class declaration such as this.
Now let's head over to the ide, and we'll take a closer look
at this class and how it's implemented so that it's clear what
it does before we extend it by overloading operators.
Okay. So now I'm in the IDE. I'm in the section 14 workspace.
I'm in the MyString-Start project.
This project really doesn't do much. It's just the beginning project that
initially implements that my string class that we're going to extend throughout.
But I want to take a few minutes to go over that class to make sure that you 100% understand it
and I encourage you to walk through it, set some break points,
step through it using the debugger and be sure that you really,
really understand what's going on in this class.
The idea with this class is that I want to model strings.
Now we already have a standard string class I know.
Let's do it ourselves that way we really, really learn how to implement one of these things.
So what we're going to do is we're going to create our own my string class.
And here's an example of how you would use it in a main.
Here I'm creating empty which is just an
empty mystring object is going to call a no args constructor.
Here I'm going to create Larry, which again is a MyString object.
And I'm going to initialize it to that string Larry,
using an overloaded constructor.
And then finally, I've got another mystring called stooge.
And I'm initializing with Larry. So there we have a copy constructor call.
And then all I'm doing is calling the display method
for each one of those objects which is going to display
empty string, Larry and Larry again since we copied it,
okay. So let's take a look at the class.
This is the same class that I showed you in the slides a minute ago.
What we're doing here is we've got our my string class you can see it right up here.
And we're implementing a string behind the scenes
as a pointer crit to a character is basically the way a c-style string works.
Now we could have done this a lot of different ways. I chose to do it this way.
That way we can play around with raw pointers a little bit more,
understand
the copy constructor, understand the move constructor,
which we'll do soon and understand the assignment operators.
Now we've got a destructor. We've got the display method that displays the contents
of this string basically,
the string itself and its length, and then we've got a couple of getters.
Okay. So let's go through these and see how they're implemented,
and I'll walk you through a couple of examples.
And let's take a look at mystring cpp.
Okay. So here you can see that I'm using behind the scenes the c-string library,
which I need. I've got i o stream simply because I'm doing some output.
And here is mystring.h. I need to include that
because this is what I'm implementing, right. That's my spec. Here's my implementation.
So here's my no args constructor. You can see it right here.
Remember, the no args constructor gets called when it's code like this executes.
Let's see mystring
and let's say just a.,
right. So we're providing no construction information here.
So the no args constructor gets called. You can see it expects nothing.
And what we're doing here is we're creating an object called a. And let's just put a right here.
This is that object, it's just a blob right now.
And it has in it a pointer called str.
That's the one we really care about.
Now if we create an empty object, we could simply set that pointer to null pointer,
and we're done with it. I don't want to do that. I want to set
an empty string to an empty string that looks like that. In other words,
a single character with the null terminator in it
rather than a null pointer
that just makes it easier later if we ever display
or print out an empty string, it'll be fine.
We don't have to check for null pointers later.
Okay. So what are we going to do here? Well, we're going to allocate space for
one character.
Notice the brackets they're important here because we're going to delete characters
in that manner as well. So I'm going to allocate one character.
Remember, all this is happening on the heap.
This is happening on the stack.
And I'm going to assign that address to str, so str is pointing there.
And then what I'm doing is de-referencing the pointer and putting that terminator in there.
That's my empty string. If you try to display this, it will just display nothing.
Okay. So that's my no args constructor.
Now let's take a look at the
overloaded constructor here that expects a c-style string, right, our string literal.
So in this case,
this will be called with statements like this. Mystring,
let's call this -- again, let's call this a again and we'll just say hello.
Notice this the quotes and the curlies,
okay. At this point, you can see what's being passed in.
So this constructor matches and this is the constructor that executes. In this case,
I'm checking to see if by any chance somebody sent a null pointer here.
If they did, I'm just going to create an empty string. I'm just going to double check that just to be safe.
Normally, this else is going to execute. And I'll show you what's going on here.
Again, we're going to create that a object, it's right here.
It's got an attribute called str that's the only attribute it has actually.
And what we're going to do is we're going to allocate
this many characters on the heap.
How many characters is that? Well, I'm going to figure out what the length of
hello is, right. That's what s is, s is hello.
I'm going to figure out what's the length of s and add 1 to it because I need an extra character for that terminator.
Well, the c-string functions in c-string right here,
there's one called str land that does exactly that.
So what I'm doing is I'm just calling std strlen
which is going to return 5. I'm going to add 1 to it,
and I'm going to allocate 6 characters on the heap.
Then I'm going to call std string copy
to copy s to str. So at this point,
it's going to copy hello here,
it's going to put the null terminator. Those are my six characters, and I'm going to point to it.
So you can see I've indeed made a copy.
Okay. We never messed with hello
because it's const, and it's not mine so I don't want to mess with it.
Okay. So now let's make a copy constructor.
Let's go through that real quick. And you can see it defined right here on line 25,
right here. Now when does a copy constructor get called?
Well, remember, we've got an existing
object and I want to copy it and construct a new object from it.
This is not an assignment, it's a construction.
So what I've got is let's say I've got that string object again, Mystring
and let's call it a, and we'll use hello again.
So there's that object. Now I want to create a new object, I'll just say mystring.
We'll call it b, and I want to create it based on a.
So there's my copy.
That's my copy constructor being called.
Also a copy constructor, as you know, gets called if you pass an object by value to
a function lots of different places.
So let's see what's going on here. In this case, this
refers to the object that I'm creating,
and source is that guy right there.
So source in this case is that a object, hello.
Okay makes sense, good. So I'm creating the object.
I'm setting str, right.
So here's that new object I'm creating. It doesn't even have a name yet.
But let's just call it this because it's being pointed to by that pointer.
It's got str again, just like before.
And what I want to do is I want to copy that hello to this guy.
And I want to do a deep copy. I don't want to share this.
So what do I do? Well, again
I want to allocate space. How much space?
The length of that source object's string,
right.
A.str, in this case, which would be hello.
So 5 plus 1 is 6. Again I'm going to allocate 6 characters on the heap.
Remember, these are unnamed. It's all in the heap.
Then I'm going to do -- and again, I used std string length to do that.
Now I'm going to do std string copy
to copy the source string to the string, again, a deep copy.
So in this case, I'm copying over hello,
the null terminator and this guy's pointing to that.
That's it. So that's where we're at right now. The rest of the functions
you shouldn't have any issues with at this point in the course.
Let's go through them anyway. Here's my destructor obviously.
Remember, this you're responsible for
de-allocating the memory that you allocate.
So I've allocated some memory in the constructors.
I've got to be sure to de-allocate it in the destructor, and that's exactly what I'm doing here.
I've got a display method. This is a member method.
And the display member method just simply displays the string which is a c-style string
and the length separated by colon.
And then I've got some getters, one just returns the length of the string,
which I'm determining
every time dynamically I'm asking what the string length is.
We could have had -- let me just come back and and let me do this one first and the string returns the pointer
so I can print it it returns it as a const.
We could have added another attribute here
called length, and we could have maintained that length of that string all along.
But. I chose not to do that I'm just going to keep it as simple as we can.
And that's it. So let's take a look at the main, and we'll run it.
There's my main give me an empty my string create Larry and then copy Larry.
So when i run this,
you see the run says
display the empty string, and it's displayed right here. Obviously it's empty, and its length is 0.
Display Larry.
There's larry and its length is 5.
And there's stooge. It should be Larry because i use the copy constructor.
Again, I did a deep copy, and there's a 5.
Okay. So that's it. That runs through this program. As I said,
you, may want to step through it until you become really really comfortable with it. It's a very, very straightforward program.
It's using everything that we've learned so far.
And what we're going to do now is start by overloading the assignment operators.