return
StatementsSo 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 are the main way our functions can send some information to the function that calls them.
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 recognized 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 int
, so we have an int
in the heading of the function. The void
keyword is how we specify that a function doesn’t return anything. So, in our TakeDamage
function, we specified the return type as void
.
main
?The main
function gets some special treatment, given its unique status. That is explained later in this lesson.
In the below example, we are initializing a variable called Level
but, this time, we are initializing 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 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 access what our function call returned at the point we called it.
For example, we could use this return value to initialize a variable:
#include <iostream>
using namespace std;
int CalculateLevel(){ return 5; };
int main(){
int Level{CalculateLevel()};
cout << "Level: " << Level;
}
Level: 5
Or as the operands of operators:
#include <iostream>
using namespace std;
int CalculateLevel(){ return 5; };
int main(){
cout << "Level: " << CalculateLevel();
cout << "\nHealth: " << CalculateLevel() * 5;
}
Level: 5
Health: 25
After running the following code, what will be the value of Health
?
1float GetHealth() {
2 return 100.0;
3}
4float Health { GetHealth() };
After running the following code, what will be the value of isDead
?
bool GetIsDead() {
return false;
}
bool isDead { GetIsDead() };
return
StatementsWe'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() {
Health -= 50;
}
However, let's 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 returned value
// For example, we could 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();
}
Anything a function does that affects the state of our program aside from returning a value is sometimes referred to as its side effects. For example, updating a variable that is defined outside the body of our function is an example of a side effect.
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
?
1int Health { 100 };
2int TakeDamage() {
3 Health -= 50;
4 return 50;
5}
6Health -= TakeDamage();
return
Statements with ConditionalsWhen 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.
In C++, and most programming languages, we cannot return multiple objects 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 object like an int
, bool
, or float
.
return
StatementsIt's crucial to understand that when a function encounters a return
statement, it immediately stops executing and passes control back to the calling function.
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.
1int TakeDamage() {
2 return 50;
3 Health -= 50; // Unreachable code
4}
Line 3 is what is sometimes referred to as "unreachable code". Whilst our code can still work, this often indicates we made a mistake. Most IDEs can also detect unreachable code in simple cases like this.
The fact that functions stop executing as soon as a return
call is found is something we can use to our advantage when structuring our code.
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 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() };
When it comes to function return values, there are a couple of common mistakes worth highlighting:
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 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 calling it when they intend to. 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. If we see any errors around our use of functions, ensure we are calling the function using ()
:
int GetLevel() {
return 5;
}
int Level { GetLevel() };
main
's Return TypeSomething odd about our main
function might have caught your attention by this point. Our main
function has a return value specified as int
,
This integer returned from our main
function is expected to be an exit code - a number that explains why our main
function ended - and therefore, why our program quit.
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 output like this in your terminal while we’ve been running our code:
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 our 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 displays 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.
main
?You may note we haven’t been returning 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 a return statement, the effect is that of executing return 0;
Our other functions don't share this property - if they have a return type, they need to return something.
We tend to take advantage of this unique main
behavior in our code samples to reduce the amount of syntax students have to look at when learning. But if preferred, feel free to explicitly return 0;
from your main function.
After running this code, what will be the value of Health
?
bool isDead { true };
int GetHealth() {
if (isDead) {
return 0;
}
}
int Health { GetHealth() };
In this lesson, we explored several key aspects of function return statements in C++. Here's a brief overview of what we've covered:
int
, float
, and void
.return
Statements: The lesson highlighted the importance of the return
statement in functions and how it passes values back to the calling function.void
return type, which do not return any value.if
statements.return
Statements: The concept of early returns in functions was explained, showing how they can make code more concise.In the upcoming lesson, we'll shift our focus to a new but related topic: Implicit Type Conversions in C++. This lesson will cover:
return
StatementsAllow our functions to communicate with their caller by returning values when they complete
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way