One of the most powerful features of inheritance is that base
class member functions are available to the derived class.
A derived class can directly invoke base class methods.
A derived class can also override or redefine base class methods.
This concept will provide us with a large amount of
power when we use polymorphic functions in the next section.
In order to redefine or override a method, you simply provide
a method in the derived class with the same name and signature
as a method in the base class.
Let's see an example.
Here we'll both use and override a base class method.
Suppose we have an account class and it has a deposit method that simply
increments a balance by an amount.
Now we have a savings account class, and it also needs a deposit
method, but its behavior will be slightly different in some way.
We can override the account classes deposit method in the
savings account class by providing a specialized implementation
using the same method signature.
Now when the savings account objects deposit funds, this
specialized method will be invoked.
But since the accounts deposit method does some of the work that we want
to do in the savings account deposit method, we don't have to duplicate
code, we can simply call the accounts deposit method from within the
savings accounts deposit method.
You can see the syntax makes sense.
We must prefix the method call with the account class so the compiler
knows that we're calling the deposit method in the account class.
It's super important that we don't try to do the actual deposit
in the savings account method.
Instead, let the account class do what it knows how to do.
If the base functionality for deposit ever changes, it won't
affect our savings account deposit method since all it's doing is
delegating this to the account class.
Let's finish up this video by talking about static binding.
By default, c++ does static binding of method calls.
All that means is that the compiler determines which methods are called
based on what it sees at compile time.
Static binding is super efficient and that's why it's the default in c++.
So if the compiler sees that a b is declared as a base object and we
call b.deposit, then the compiler will call the base classes deposit.
The same is true for a derived object.
If d is a derived object and d.deposit is called, this results in a call to
the derived classes deposit method.
While this is very efficient and fine in most cases, there
are times when we'd like the binding to take place at runtime.
We'll see that in the next section.
Let's see a couple of examples next.
In the first example here, you can see that b.deposit will call
the base classes deposit method because b is a base object.
And d.deposit we'll call the derived classes deposit method
since d is a type of derived.
But consider the last two lines of code.
In this case, we have a pointer ptr to a base object, but we
create a derived object on the heap and assign it to the pointer.
This is perfectly valid since the derived is a base.
But now if we call the deposit method through the pointer, the
compiler will call the base classes deposit method because it sees that
pointer is a pointer to a base class.
But perhaps it would make more sense to call the derives deposit method
in this example since the actual object pointed to is a derived object.
As I said, we'll learn how to achieve this kind of dynamic binding in
the next section, but it's very important to understand that by
default c++ uses static binding.
Okay, so I'm in the IDE.
I'm in the section 15 workspace in the RedefiningBaseClassMethods project.
And what we've got here is a real simple account class, and we'll derive
a savings account class from it, similar to what we did in the slides,
and we'll do it from scratch that way you can see how this works with using
and redefining base class methods.
So let's take a look at the account class.
We've got a friend here that allows us just to put account objects on a
stream and display them really easily.
And what we're modeling is the balance, right.
That's the whole idea of an account.
It models a balance, and it's a double in this case.
And then we've got some public methods here.
With no args constructor, we've got a constructor
right here that's overloaded where you supply the balance.
And then we've got a deposit and a withdrawal method.
Now the deposit and withdraw functionality is dead simple, right.
Give me an amount, I'll deposit it.
Give me an amount, I'll withdraw it.
The only thing we're checking is to make sure that there's enough balance
so that we can withdraw the funds.
Okay, so that's it.
Let's take a look at the implementation.
Now you'll notice that there are no copy constructors,
move constructors, overloaded assignment operators and so forth.
We don't need them.
We're modeling a double, and it really doesn't make
any sense to include them.
The compiler provided ones are perfectly adequate.
So let's take a look at the source.
And again, you can see real, real simple stuff here.
What I've got is my no args constructor right here.
And when that is invoked, then all I'm doing is I'm calling my
regular overloaded constructor right here, and I'm passing in a
0 to it so I'm delegating to it.
And at this point, that constructor just simply sets the balance to
balance, right, simple as that.
The deposit method increments the balance by whatever
amount was passed in.
The withdraw method expects an amount.
We want to make sure that there are sufficient funds.
If there are, we're just going to subtract the amount from the balance.
Otherwise, we'll just display a simple message insufficient funds.
And here's my overloaded insertion operator.
Okay, so there you go.
Simple stuff.
Now what we'll do is let's take a look at our savings account.
Okay, so here's the savings account class header file.
We're including account header file.
And a savings account is an account.
So you can see the inheritance right here.
Class savings account, public account.
We're using "is a" inheritance here.
And a savings account is an account, so it's going to provide a balance
just like the account, but what it does add is an interest rate.
So the interest rate is used when users deposit funds.
So if you deposit a 1000 dollars and we've got a 5% interest rate,
the amount that you deposit will be one 1000 plus 50 dollars.
So we're giving folks really good bonuses here, I wish
my bank account did this.
So anyway, so that's what we're going to do.
Now the withdraw method doesn't specialize
so the idea there is that we can use our parents or our
accounts withdraw method as is.
We don't have to do anything.
We don't have to declare it in here because we'll
automatically inherit it.
All right, so why don't we start doing this class right here.
So let's start writing the code for this class right here.
First thing we want to do is we want to declare that friend of ours
minus well get it out of the way.
And it's going to return a std output stream reference, its
operator and the insertion operator.
And what does expect?
Well, it expects a std ostream by reference and I'll just call that os.
This design pattern here you'll see over and over used in c++.
And in this case, it expects a savings account object, right.
And we'll call it account by reference.
So that's the prototype for that.
We'll implement that in the c++ file later.
Hopefully, I don't have any typos.
I'll deal with typos after when we compile that way.
We don't waste a lot of time now.
That's it.
So now what do we want to do?
Well, we have to add what we add, right.
We're adding an interest rate.
So we'll use a protected keyword here.
And we'll supply our interest rate, which will be a double,
and I'll just call it int rate.
Okay, that's it.
And now what we'll do is we'll provide some public methods,
in this case, our constructors.
So it'll be a savings account constructor, that'll
be a no args constructor.
We'll also supply a savings account constructor.
In this case, that will expect the balance, which will be handled by the
account part of me and the interest rate which will be handled by me,
me being the savings account class.
And we also have to handle the deposit method because we're
redefining it overriding it.
So in this case, we want the same function prototype
as we had in our parent.
So it's deposit, and I believe it was double amount.
That's it.
Notice withdraw is inherited.
Okay, and the functionality that we're using to withdraw funds in
the account class is all I need in the savings account class.
All right, so now let's implement this.
There's only a few methods to implement so it should be pretty
quick and pretty straightforward.
Here you go. I'm including my savings account header file.
And let's do the no args constructor.
Actually, let's do the second constructor first, the savings
account, the overloaded constructor and it's this guy right here and
expects a double for balance and another double for the interest rate.
Let's fix that typo here.
So those are the two parameters in this particular constructor.
Now what do we want to do here?
Well, we want to initialize the account part of us, right.
So we want to invoke our account part constructor,
right, our parents constructor.
And that expects just the balance.
That's all it knows about.
It doesn't know anything about the interest rate.
And then once that's initialized, I can come back and take care of the
interest rate and initialize that.
There is no body to this constructor.
You can just leave it like that.
If you want to type something like cout constructor called, that's fine.
But we really don't need to do anything else here.
Okay, so now how about the no args constructor?
Well, let's do that one.
That one's pretty easy to do, right.
So here's my no args constructor.
It expects nothing.
Now what do we want to do here?
Well, the simplest thing to do is just to delegate to my
overloaded constructor, right.
So what I want to do here is delegate to my savings account
overloaded constructor up here.
And what I want to pass into it is 0.0 for the balance,
and 0.0 for the interest rate.
And that's it.
That'll take care of that.
So my two constructors are done.
We don't have any destructors or copy constructors or anything like that.
So let's worry about the deposit method, right.
So let's say void and its savings account, it's my deposit
method, and it expects a double, which is the amount to deposit.
What we want to do here is we want to increment that amount by
that interest rate, as I said.
So what we want to do is we want to figure out how
much do we need to deposit.
So let's say amount.
That's, that same amount right here.
We could use another variable name if we want but this works just fine.
A local variable, I mean amount equals amount plus and we'll say
amount times the interest rate divided by a 100, that'll give us the
percentage, and that should do it.
We could have set amount plus equals as well to be a little clearer.
Now we know how much we need to deposit.
So rather than do it ourselves, let's just let the account
parent part of us do it.
So we'll say account, it's got a deposit method in there, and
we'll pass in the amount that we wanted to deposit for us.
That's it.
And then finally, we'll implement the friend overloaded operator.
So this returns a ref to an ostream, and we're overloading
operator, our insertion operator.
Again, std ostream by reference and we'll call it os.
And we've got a const savings account ref.
Okay, and all we're doing here is we're saying -- we're
putting onto that output stream, we'll say something
like savings account balance.
And we'll put the accounts balance on here followed by the interest rate
and the interest rate for the account.
And we know it's got it because it is a savings account right here.
That should do it, and we want to return that ostream.
All right, so let's compile this and see if we've got
any typos, we probably do.
And there's one right here.
Okay, cool, so we have a run.
Let me show you the main real quick.
And you'll notice one thing right away, it wasn't a lot
of work to do this, right.
That's the whole point of reusing.
All I had to do is create these constructors, right,
override that deposit method.
The rest is just came right in.
The withdraw method I didn't have to touch because I'm
inheriting it from my parent.
So let's take a look at the main real quick, and I just wrote a couple
simple test cases here where I'm creating an account a1 with a 1000
dollars in it, and I'm displaying it.
And I'm depositing 500 into it, I'm withdrawing a 1000 and then
I'm withdrawing 5000 and I'm going to get insufficient funds.
So let's build and run that.
And you can see here, we've got a 1000, 1500, 500 insufficient
funds, and we still have 500.
Okay, and then we've got some test cases down here that
work with these actual savings accounts that we just created.
So let's run those.
And you could see here now that I've got a savings account with a balance
of a 1000 and an interest rate of 5.
I've got -- after the deposit, notice that 1000 was deposited,
but 1050 was deposited because that was our specialized deposit method.
So now we've got a balance of 2050, same interest rate.
I'm going to withdraw 2000, which leaves us with
a 50 balance right here.
Then if I try to withdraw a 1000, obviously, I've got insufficient
funds, and then I display it again and we still have 50.
Okay, so there you go.
That's an example of reusing an account class.
And we're using the withdraw method, 100% right.
And if you look again at the deposit method, we really are reusing it,
we're actually calling it right here.
And all we're doing is tweaking the amount that we want deposited
and then just sending it right up.
Now one thing I should mention, we overloaded the insertion
operator here to work with the savings account simply to
display the interest rate, right.
If you send in a savings account object to this guy right here in
the account class, remember, a savings account is an account.
What will happen here is the object will get sliced and it will only print
out the account balance, not the interest rate because the compiler
is going to slice that part out.
So the only reason that we're implementing this guy right here on
line 18 in the savings account cpp is so that we can see the interest rate.
So what we'll do in the next video is I'll give you the beginnings
of an account class hierarchy.
And in the challenge, what you'll do is add some more accounts to
this that have more specialized behavior so you can get some
experience with inheritance.
And remember, all of this is using static binding,
which is real important.