So far, we've been imagining functions as standalone blocks of code. They've been, for the most part, isolated from our surrounding program.
The next step in making our functions more useful will be to allow them to communicate with other parts of our code.
Return values is the main way our functions can send some information to the function that called them. This is done by providing that calling function with some data.
In our exploration of functions so far, we have waved off the syntax such as void
and int
that we've been putting before our names:
void TakeDamage() {}
int main() {}
In this lesson, we will see what exactly these are for.
You may have recognised int
as a data type that we've seen in other contexts. Indeed, these parts of our syntax are data types.
We've seen that a function can run any code we need. But, functions can also return information to the code that called it.
With C++ being a strongly typed language, we need to specify what type of data our functions will be returning. That is what the int
and void
in the function headers refer to.
The main
function returns an integer, so we have a int
in the heading of the function. Our TakeDamage
function doesn't return anything, so we placed the type void
in that position.
main
?The main
function gets some special treatment, given its unique status. That is explained below, under the section on Exit Codes
In the below example, we are initialising a variable called Level
but, this time, we are initialising that variable to the result of calling a function:
int Level { CalculateLevel() };
For this to work, the act of calling our CalculateLevel
function must result in an integer value.
We can make this happen in two steps - first, we place the int
keyword before our function name, to declare that this function is going to return an integer:
int CalculateLevel() {
// Code here
};
Secondly, we update the body of our function to ensure it actually returns an integer, by using the return
keyword:
int CalculateLevel() {
return 5;
};
With that, our initial line of code will now work, and we're able to set a variable using the return value of a function. After running the following code, Level
will have a value of 5
.
int CalculateLevel() {
return 5;
};
int Level { CalculateLevel() };
After running the following code, what will be the value of Health
?
float GetHealth() {
return 100.0;
}
float Health { GetHealth() };
After running the following code, what will be the value of isDead
?
bool GetIsDead() {
return false;
}
bool isDead { GetIsDead() };
We've seen from our TakeDamage
function in previous lessons that not all functions need to return something.
In those cases, we have set the return type to be void
:
void TakeDamage() {};
However, lets update this function to return something too. For example, it could return the amount of damage that was inflicted:
int TakeDamage() {
Health -= 50;
return 50;
}
Now, not only does our function have an effect (ie, reducing Health
) - it also returns something, which might be useful in the locations where our TakeDamage
function is called:
int Score { 0 };
int main() {
int DamageInflicted = TakeDamage();
// We can now use the value returned by this function
// For example, we might want to update a scoreboard
Score += DamageInflicted;
}
We could also have implemented the above example in a single statement:
int Score { 0 };
int main() {
Score += TakeDamage();
}
Note, just because a function returns something, that does not obligate the caller to make use of what is being returned.
If our main
function had no use for the return value, but just wanted to call the function because of its other effects, it can just continue to call the function as it did before:
int main() {
TakeDamage();
}
After running this code, what will be the value of Health
?
int Health { 100 };
void TakeDamage() {
Health -= 50;
}
TakeDamage();
After running this code, what will be the value of NewHealth
?
int Health { 100 };
void TakeDamage() {
Health -= 50;
}
int NewHealth { Health };
After running this code, what will be the value of Health
?
int Health { 100 };
int TakeDamage() {
return Health - 50;
}
TakeDamage();
After running this code, what will be the value of Health
?
int Health { 100 };
int TakeDamage() {
Health -= 50;
return 50;
}
Health -= TakeDamage();
When using conditional logic, we may need to have multiple return
statements in our code. For example:
bool isDead { true };
int TakeDamage() {
if (isDead) {
return 0;
} else {
Health -= 50;
return 50;
}
}
If our function has a return type, we should make sure that every possible branch through our function results in something of that type being returned.
We cannot return multiple values from a function - a function can only return a single value, of a single type.
Later, we will introduce more complex data types - including things that can be thought of as "containers" - a single object that can contain other objects inside it.
For now though, just treat our functions as being limited to returning a single int
, bool
or float
.
Something very important to note with functions is that, once they hit a return
statement, they immediately stop running and return control back to their caller.
Were we to write our TakeDamage
function like in the following example, Health
would never be reduced, because the function will always have hit the return
statement before hitting line 3.
Line 3 is what is sometimes referred to as "unreachable code". Whilst our code can still work, this often indicates we made a mistake:
int TakeDamage() {
return 50;
Health -= 50; // Unreachable code
}
The first return statement a function encounters is not necessarily the first return statement in its body. As we saw in previous examples, a return
statement can be inside conditional blocks such as an if
statement.
By combining conditional logic with the property that functions stop once they return
, we can make our code a lot more concise. For example, we could reduce this function we saw earlier:
bool isDead { true };
int TakeDamage() {
if (isDead) {
return 0;
} else {
Health -= 50;
return 50;
}
}
To something that does exactly the same thing with less code:
bool isDead { true };
int TakeDamage() {
if (isDead) return 0;
Health -= 50;
return 50;
}
In the above example, the else
block has been removed. This is because, if isDead
is true, our function will return
before any code in the else
block would have been reached anyway.
Therefore, having that code within an else
statement is redundant.
After running this code, what will be the value of Health
?
bool isDead { true };
int getHealth() {
return isDead ? 0 : 50;
return 100;
}
int Health { getHealth() }
After running this code, what will be the value of Health
?
bool isDead { false };
int getHealth() {
if (isDead) {
return 0;
}
return 100;
}
int Health { getHealth() };
return
vs cout
A common misunderstanding with beginners, particularly when creating the basic functions typical of introductory courses, is the difference between logging and returning a value from a function.
The following are not equivalent:
void MyFunction() {
cout << 5;
}
int MyFunction() {
return 5;
}
If a function isn't working as you expect, particularly issues around return values, ensure you are actually using the return
keyword to return the value - not simply logging it out to the console.
The other common mistake beginners make when trying to use a function is not actually calling it. Rather, they will just refer to the function, such as below:
int GetLevel() {
return 5;
}
int Level { GetLevel };
This will cause a rather cryptic error message. In the future, we will see scenarios where we want to refer to a function without actually calling it.
For now, if we see any errors around our use of functions, ensure we are calling the function using ()
:
int GetLevel() {
return 5;
}
int Level { GetLevel() };
Something odd about our main
function might have caught your attention by this point. Our main
function has a return value specified as int
, but we haven't been returning an int
from it. Lets discuss why this still seems to work.
We should first understand what that the int
returning from our main
function is supposed to represent. This integer is expected to be an exit code - a number that explains why our program stopped running.
Just like our function can return a value to another function that called it, our entire program can return a value to the program that executed it, such as the operating system.
You may have noticed something like this in your terminal:
The program 'MyProgram.exe' has exited with code 0.
The code 0
here indicates that your main
function returned 0
. Try returning a different value from main
, and see how this output changes.
int main() { return 5; }
The program 'MyProgram.exe' has exited with code 5.
Returning 0
from the main
function announces that your program exited as expected, under normal circumstances. In other words, it did not crash.
When using other software, you may have sometimes seen a program crash and then your operating system displaying a popup to give you the option to report the crash. That popup was most likely triggered by a program not returning 0
from its main function.
However, we didn't return 0
(or anything else) from our main
function. The reason for this discrepancy, and the reason our code even compiles despite seemingly breaking the rules, is that the main
function gets some special treatment.
The C++ specification states:
If control reaches the end of
main
without encountering areturn
statement, the effect is that of executingreturn 0;
Our other functions don't share this property - if they have a return type, they need to return something.
After running this code, what will be the value of Health
?
bool isDead { true };
int getHealth() {
if (isDead) {
return 0;
}
}
int Health { getHealth() };
We've seen that if our functions specify a return type, they always need to return something. This includes returning something from every conditional branch, if we are using them.
However, our functions don't necessarily need to return something of the correct type - just something that can be converted to the correct type.
Well discuss this idea in more detail in the next lesson.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way