Function Arguments and Parameters
Making our functions more useful and dynamic by providing them with additional values to use in their execution
We've seen how the execution of our functions can be controlled by conditional statements. These conditional statements allow our functions to 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 program like this:
#include <iostream>
using namespace std;
int Health{150};
void TakeDamage(){
cout << "Inflicting 50 Damage - ";
Health -= 50;
cout << "Health: " << Health << '\n';
}
int main(){
TakeDamage();
TakeDamage();
TakeDamage();
}Inflicting 50 Damage - Health: 100
Inflicting 50 Damage - Health: 50
Inflicting 50 Damage - Health: 0This function was quite inflexible. Every time we called it, it did 50 damage.
TakeDamage(); // 50 Damage
TakeDamage(); // 50 Damage
TakeDamage(); // 50 DamageFunction Arguments
Instead, we could pass some data into our function, letting the caller choose how much damage needs to be inflicted. 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 call a function. For example:
TakeDamage(20); // 20 Damage
TakeDamage(50); // 50 Damage
TakeDamage(100); // 100 DamageTo make this work, we also need to update our function to let it accept arguments, and to make use of them.
Function Parameters
When we call a function with an argument, that argument gets passed to a function parameter. To allow our function to accept an argument, we need to set up a parameter to receive that argument.
We do that between the ( and ) at the top of the function definition. Similar to a variable, a parameter has a type and a name. The same naming rules and recommendations apply - we should give our parameters meaningful names.
Below, we give our function a parameter of the int type, which we will call Damage:
void TakeDamage(int Damage) {
Health -= 50;
}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;
}Just like that, our code will now work. Our function can now be called with a different Damage value each time:
#include <iostream>
using namespace std;
int Health{150};
void TakeDamage(int Damage){
cout << "Inflicting " << Damage << " Damage";
Health -= Damage;
cout << " - Health: " << Health << '\n';
}
int main(){
TakeDamage(20);
TakeDamage(50);
TakeDamage(70);
}Inflicting 20 Damage - Health: 130
Inflicting 50 Damage - Health: 80
Inflicting 70 Damage - Health: 10Test your Knowledge
Functions with a Single Parameter
After running this code, what is the value of Result?
int Square(int x) {
return x * x;
}
int Result { Square(2) };Multiple Parameters
By having multiple parameters in our parameter list, our function can receive multiple arguments. We separate multiple parameters using a comma ,
Below, our AddNumbers() function has three parameters, all of the int type:
int AddNumbers(int x, int y, int z) {
return x + y + z;
}When providing arguments for those parameters when we call our function, we also separate them with a comma:
// 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 isMagical) {
// Magical damage is doubled
Health -= isMagical ? Damage * 2 : Damage;
}
TakeDamage(50, true);Implicit Conversion of Arguments
If we try to pass an argument of the incorrect data type, the compiler will try to do the same implicit conversion technique 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 the floating point number 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();Test your Knowledge
Functions with Multiple Parameters
After running this code, what is the value of Result?
int Multiply(float x, float y) {
return x * y;
}
int Result { Multiply(2, 2.5) };Optional Parameters
In our examples above, we added a second argument to the TakeDamage() function to denote whether the damage was magical.
void TakeDamage(int Damage, bool isMagical) {
// Magical damage is doubled
Health -= isMagical ? Damage * 2 : Damage;
}This made 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 parameters. For example, we can have our function assume that damage is not magical, but still let callers overrule that assumption if they need to:
// This is assumed to be non-magical damage
TakeDamage(50);
// This will override that assumption
TakeDamage(50, true);To make a parameter optional, we just need to give it a default value in our parameter list. This is the value that the parameter will have if the caller opted not to provide an argument for it.
We do this using = within the parameter list. For presentation reasons, the following example also moves our parameter list onto multiple lines. As with most things in C++, we're free to lay out our code as desired:
void TakeDamage(
int Damage,
bool isMagical = false
) {
// Magical damage is doubled
Health -= isMagical ? Damage * 2 : Damage;
}With this change, callers of our function are free to provide or omit the additional argument as preferred
// This still works, but the false isn't needed:
TakeDamage(50, false);
// It is equivalent to this:
TakeDamage(50);
// To override the default parameter:
TakeDamage(50, true);We can have any number of optional parameters:
void TakeDamage(
int Damage,
bool isPhysicalDamage = true,
bool canBeLethalDamage = true,
) {
// body here
}However, we cannot have any required parameters after an optional parameter.
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 required parameters cannot come after optional 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 would not be entirely clear, so C++ doesn't permit our function to be set up that way in the first place.
Test your Knowledge
Functions with Optional Parameters
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) };Using Expressions as Arguments
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 the respective parameter, or one that can be converted to that type. For example:
bool isWizard { true };
// 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 an appropriate type, we can use it:
int CalculateDamage() {
return 25;
};
TakeDamage(CalculateDamage(), true);We can even compose functions with themselves:
// This will become AddNumbers(1, 2, 12)
// Which will become 15
AddNumbers(1, 2, AddNumbers(3, 4, 5));Test your Knowledge
Argument Expressions
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)) };Common Confusion: Parameter Names vs Variable Names
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 that ultimately receives the value of that variable. Whilst they might contain the same value, they are two different variables.
Arguments are not mapped to parameters by their name, rather, they are mapped by their position.
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 in the following example:
#include <iostream>
using namespace std;
int x{5};
int y{2};
void SubtractNumbers(int a, int b){
cout << "\na = " << a
<< "\nb = " << b
<< "\nResult = " << a - b;
}
int main(){
cout << "Calculating " << x << " - " << y;
SubtractNumbers(x, y);
cout << "\n\nCalculating " << y << " - " << x;
SubtractNumbers(y, x);
}Calculating 5 - 2
a = 5
b = 2
Result = 3
Calculating 2 - 5
a = 2
b = 5
Result = -3Test your Knowledge
Function Argument Order
After running this code, what is the value of Result?
int x { 1 };
int y { 3 };
int Subtract(int Left, int Right) {
return Left - Right;
}
int Result { Subtract(y, x) };Summary
In this lesson, we explored the fundamental concepts of function arguments and parameters, letting our functions be more versatile. Here's a recap of the key points:
- Arguments and Parameters: Arguments are the data passed to a function, and parameters are the variables within the function that receive those arguments.
- Multiple Arguments: Functions can support multiple arguments - we use a comma
,to separate them. - Optional Arguments: We can give parameters default values using
=, which makes the corresponding arguments optional. - Implicit Conversion of Arguments: We saw how C++ handles different data types when passed as arguments, including implicit conversion and potential compile-time errors.
- Argument Expressions: Arguments don't need to be literal values like
42ortrue- we can use expressions such as variable names or other function calls to generate arguments. - Common Confusions: The name of an argument and it's corresponding parameter are not related. Arguments map to paramaters based on their order within the argument list, not by their names.
Scope
Learn more about how and when we can access variables, by understanding the importance of the scope in which they exist.