We've seen how the execution of our functions can be controlled by conditional statements. These conditional statements allow our functions to potentially do different things each time they are called.
There is another way we can make our functions more flexible. We can pass some additional data to the function whenever we call it. These pieces of data are called the arguments of a function call.
Previously, we had a function like this:
int Health { 100 };
bool isDead { false };
void TakeDamage() {
Health -= 50;
if (Health <= 100) isDead = true;
}
This function was quite inflexible. Every time we called it, it did 50
damage.
TakeDamage(); // 50 Damage
TakeDamage(); // 50 Damage
TakeDamage(); // 50 Damage
Instead, what we could do is pass some data into our function, letting the caller choose how much damage needs to be taken. That way, we could do a different amount of damage each time the function is called.
That value would be called an argument. We pass an argument between the (
and the )
that we've been using every time we called a function. For example:
TakeDamage(20);
TakeDamage(50);
TakeDamage(2000);
To make this work, we also need to update our function to let it accept arguments, and to make use of them.
To allow our function to accept an argument, we add it to the parameter list between the (
and )
at the top of the function.
int Health { 100 };
bool isDead { false };
void TakeDamage(int Damage) {
Health -= 50;
if (Health <= 100) isDead = true;
}
A parameter (sometimes called "param") is a variable that is defined in the signature of the function. The signature is the combination of the function's name, the return type, and the type of all of it's parameters.
The signature is what we typically see on the first line of the function but, given C++ doesn't care about white space, the signature could span multiple lines.
In the above example, we define an integer parameter called Damage
that, when the function is called, will be initialised to the value passed as the argument when calling the function.
Even though "parameter" and "argument" are not quite the same thing, they are used somewhat interchangeably when developers talk about functions.
We just infer the meaning from the context in which they're used.
Technically, the data you pass when calling the function is the argument. The data that the function receives is the parameter.
For reasons we will see soon, those aren't necessarily the same values.
Once we have set up our function to accept an argument, we can then update the body of the function to make use of that data where required. A parameter acts just the same as a variable within our function.
Instead of decreasing Health
by 50
, we can decrease it by whatever value is stored in the Damage
parameter
void TakeDamage(int Damage) {
Health -= Damage;
if (Health <= 100) isDead = true;
}
Just like that, our code snippit will now work. Our function can now be called with a different Damage
value each time.
TakeDamage(20);
TakeDamage(50);
TakeDamage(2000);
After running this code, what is the value of Result
?
int Square(int x) {
return x * x;
}
int Result { Square(2) };
Our functions can be set up to receive multiple arguments. To do this, we just define more variables in our parameter list, separated by a comma ,
int AddNumbers(int x, int y, int z) {
return x + y + z;
}
And we do the same thing with the arguments we pass in:
// This will return 6
AddNumbers(1, 2, 3);
The parameters do not need to match the return type of the function, nor do they need to match the type of other parameters:
int Health { 100 };
void TakeDamage(int Damage, bool isPhysicalDamage) {
// Physical damage is reduced to 50% by armour
Health -= isPhysicalDamage ? Damage / 2 : Damage;
}
TakeDamage(50, true);
If we try to pass an argument of the incorrect data type, C++ will try to do the implicit conversion we saw in previous lessons.
For example, if we try to pass an argument of 5
(an int
) into a parameter that has a type of float
, the parameter will be 5.0f
.
If we attempt to pass a data type that cannot be converted to what the parameter expects, the code will not compile, and we’ll be notified of the error.
// Float can be converted to int, so this will do 25 damage
TakeDamage(25.0f);
// Bool can be converted to int, so this will do 1 damage
TakeDamage(true);
// String cannot be converted to int, so this will be an error
TakeDamage("Hello!");
// This will also be an error - we have to provide something
TakeDamage();
After running this code, what is the value of Result
?
int Multiply(float x, float y) {
return x * y;
}
int Result { Multiply(2, 2.5f) };
In our examples above, we added a second argument to the TakeDamage
function to denote whether the damage was physical or magical.
void TakeDamage(int Damage, bool isPhysicalDamage) {
// Physical damage is reduced to 50% by armour
Health -= isPhysicalDamage ? Damage / 2 : Damage;
}
This did make our function more flexible, but might make it a bit less intuitive to use. Often, we'll want our function to behave a certain way by default, but also to give callers the option to override that default behavior.
For scenarios like this, we can define optional arguments. For example, we can have our function assume that damage is physical, but still let callers overrule that assumption if they need to:
// This will assumed to be physical damage
TakeDamage(50);
// This will override that assumption
TakeDamage(50, false);
The way we set up an optional parameter is to give it a default value in our parameter list. This is the value that the parameter will have, if it is not provided as an argument when the function is called.
We do this using the =
operator within the parameter list:
void TakeDamage(int Damage, bool isPhysical = true) {
Health -= isPhysical ? Damage / 2 : Damage;
}
// This still works, but the true is unnecessary
TakeDamage(50, true);
// It is equivalent to this:
TakeDamage(50);
// To override the default parameter:
TakeDamage(50, false);
We can have any number of optional parameters:
void TakeDamage(
int Damage,
bool isPhysicalDamage = true,
bool canBeLethalDamage = true,
) {
// body here
}
However, we cannot have required parameters after optional parameters.
We will see workarounds for this soon, but for now, just note that something like this would be impossible:
void TakeDamage(
int Damage,
bool isPhysicalDamage = true,
bool canBeLethalDamage
) {
// body here
}
To understand why optional parameters cannot come before required parameters, consider what would happen were we to call this function with an argument list like this:
TakeDamage(50, false);
What are we setting to false
here? Is it isPhysicalDamage
or canBeLethalDamage
? Our intent is not clear. So, the compiler does not allow our function to be set up that way.
After running this code, what is the value of Result
?
int Multiply(int x, int y = 3) {
return x * y;
}
int Result { Multiply(2) };
After running this code, what is the value of Result
?
int Multiply(int x = 2, int y) {
return x * y;
}
int Result { Multiply(2) };
In the previous examples, all our arguments were passed as "literal" values, for example, 25
and true
.
In reality, we can use any expression that results in a value of the correct type, for example:
bool isWizard { false };
// This will be equivalent to TakeDamage(50, true)
TakeDamage(25 * 2, !isWizard);
Our arguments can also be determined by calls to other functions. As long as that other function returns something of the correct data type, or something that can be converted to the correct data type, we can use it:
int CalculateDamage() {
return 25;
};
TakeDamage(CalculateDamage(), true);
We can even compose functions with themselves:
// Will become AddNumbers(1, 2, 12)
// Which will become 15
AddNumbers(1, 2, AddNumbers(3, 4, 5));
After running this code, what is the value of Result
?
int Multiply(int x, int y = 3) {
return x * y;
}
int Result { Multiply(2, Multiply(5)) };
Frequently, the value we want to use in an argument of a function is simply stored in a variable:
int DamageToInflict { 50 };
void TakeDamage(int Damage) {
// Do stuff
}
TakeDamage(DamageToInflict)
This example is often a point of confusion, as the names do not match up. We created a variable called DamageToInflict
and passed it to a function. But the name within the function was different - it was called Damage
.
What we call the variable outside the function does not correlate with what we call the parameter. Whilst they might contain the same value, they are two totally different variables.
Arguments are not mapped to parameters by their name, rather, they are mapped by their order.
When we call a function, the first argument maps to the first parameter; the second argument maps to the second parameter, and so on.
We can see this here:
int x { 5 };
int y { 2 };
int SubtractNumbers(int a, int b) {
return a - b;
}
// This will be 3
int Result1 = SubtractNumbers(x, y);
// This will be -3
int Result2 = SubtractNumbers(y, x);
If these results make sense, perhaps we can make things slightly more complicated to solidify the point. Lets rename our function parameters to also be called x
and y
:
int x { 5 };
int y { 2 };
int SubtractNumbers(int x, int y) {
return x - y;
}
// This will be 3
int Result1 = SubtractNumbers(x, y);
// This will be -3
int Result2 = SubtractNumbers(y, x);
We now have variables called x
and y
both outside and inside the function. However, the thing to note here is that this does not change either of the results.
This is because they're different variables - the x
on line 1 is not the same x
that is on lines 3 and 4.
Having "shadowed variables" like this is generally not a good practice. It is something to avoid in our code because it can cause confusion.
However, it is totally valid C++, and we still need to understand why it works. The x
and y
outside the functions are different variables to those inside. Whilst they have the same name, the exist in different scopes.
Scopes will be the topic of our next lesson.
After running this code, what is the value of Result
?
int x { 1 };
int y { 3 };
int Subtract(int x, int y) {
return x - y;
}
int Result { Subtract(y, x) };
This concept is quite confusing, so don't worry if the code and answer in the previous question didn't make sense.
The next lesson explores this topic in more detail, and will help build our understanding.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way