We have been using a TakeDamage
function quite a lot in previous examples. It is a simple function that allows our Characters to take an integer amount of damage:
// Take 30 Damage
Character.TakeDamage(30);
However, it might be useful to have another version of that function. Instead of taking an integer amount of damage, that function could let our characters take a percentage of their health as damage.
We could call that version by passing a floating point number:
// Take 20% damage
Character.TakeDamage(0.2f);
f
in 0.2f
?In an earlier note covering the concepts of literals, we pointed out that 0.2
is actually interpreted as a double
, rather than a float
. For it to be a float literal, we would need to add an f
, as in 0.2f
.
We agreed to leave the f
off because it makes our code easier to understand for beginners, and it also would let us point out scenarios where the f
matters.
The above example is one such scenario.
Imagine we have two versions of a function - one that accepts an int
as a parameter, and the other that accepts a float
. We call that function with a double
as a parameter, such as 0.2
.
We've already seen that a double
can be implicitly converted to a float
. The problem here is that a double
can also be implicitly converted to an int
.
Therefore, the compiler doesn't know what we want. Does it convert the double to an int and call the first function, or does it convert the double to a float and call the second? Rather than guess, it asks us to clarify our intentions.
We can help it out by adding the f
to our literal - 0.2f
. Then, we are passing a float
rather than a double
, and the compiler can be certain we want to call the version of the function that uses a float
.
The upcoming lesson on Static Casting will introduce how we can take full control over this process, rather than always relying on implicit conversions.
This approach of having multiple functions with the same name is an example of static overloading. Lets see how we can set this up!
This behaviour, where we have a function with the same name, but with different parameter types, is called an overloaded function.
The way we create it is perhaps exactly like you'd expect. We simply define two functions with the same name - one that accepts an integer, and the other that accepts a float:
class Character {
public:
void TakeDamage(int Damage) {
Health -= Damage;
};
void TakeDamage(float Percent) {
Health *= (1-Percent);
};
private:
int Health { 150 };
}
After running the following code, what is the value of MyBool
?
bool GetBool(float Number) { return true; }
bool GetBool(int Number) { return false; }
bool MyBool { GetBool(5) };
In the real world, our TakeDamage
functions are likely to be a lot more complicated than the simple example above. For example:
class Character {
public:
void TakeDamage(int Damage) {
Health -= Damage;
PlayAnimation();
RenderEffects();
PlaySound();
UpdateAI();
UpdateHealthBar();
};
void TakeDamage(float Percent) {
Health *= (1-Percent);
PlayAnimation();
RenderEffects();
PlaySound();
UpdateAI();
UpdateHealthBar();
};
private:
int Health { 150 };
}
These functions are now duplicating code, and will be difficult to maintain in the future.
A common pattern to deal with this is to have only one function be our primary version, where all the heavy work is done.
The overloaded versions can just be simple wrappers that convert arguments to the appropriate values, then defer to the primary version of the function. That might look something like this:
class Character {
public:
void TakeDamage(int Damage) {
Health -= Damage;
PlayAnimation();
RenderEffects();
PlaySound();
UpdateAI();
UpdateHealthBar();
};
void TakeDamage(float Percent) {
int Damage { Health * Percent };
TakeDamage(DamageToInflict)
};
private:
int Health { 150 };
}
Now, we have the primary TakeDamage
function, where most of our effort can be focused. The float
version of our function is just a simple wrapper around it.
After running the following code, what is the value of MyBool
?
bool GetBool(float Number) { return true; }
bool GetBool(int Number) { return false; }
bool MyBool { GetBool(5.0) };
The next lesson will introduce Static Casting.
Casting is how we can take full control of the data conversion process. It lets us explicitly issue instructions to convert our data into different types. This means we're taking full control of the process, and not always relying on implicit conversion as we have been thus far.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way