This lesson is a quick introductory tour of functions within C++. It is not intended for those who are entirely new to programming. Rather, the people who may find it useful include:
It summarises several lessons from our introductory course. Those looking for more thorough explanations or additional context should consider completing Chapter 2 of that course.
In C++, a function that is called MyFunction
that returns void
(ie, nothing) and does nothing looks like this:
void MyFunction() {}
Here are examples of functions that return data. Note that we specify the return type in the function heading:
float ReturnFloat() {
return 5.5f;
}
bool ReturnBool() {
return true;
}
Similar to variables, return statements can be implicitly cast to the return type of the function:
// This returns the integer 5
int ReturnInt() {
return 5.5f;
}
We invoke functions using their name, and the ()
operator:
ReturnFloat();
main
FunctionOur program needs to define an entry point - the function that will be run when our program is executed.
This is typically a function that returns an int
and is called main
:
int main() {
return 0;
}
The return value of the main
function is an exit code, which describes why our program stopped. Returning 0
indicates the program ran as expected. Any other number is an error code.
If the main
function returns nothing, it is assumed to have returned 0
. This is a behavior that is specific just to the main
function. Every other function must return something of the type specified in its heading, or something that can be converted to that type.
// This is valid
int main() {}
// This is not valid
int SomeFunction() {}
We define parameters between the ()
in the function definition. We specify their type and name, and separate multiple parameters using commas. Below, we declare our function has two parameters, both of type int
, which we’re calling x
and y
:
int Add(int x, int y) {
return x + y;
}
We provide arguments for these parameters between the (
and )
when calling the function. Below, we pass 1
to x
and 2
to y
:
int main() {
Add(1, 2); // 3
}
A function’s arguments are the values we pass to a function when we call it.
A function’s parameters are the variables that receive those values, which we defined alongside the function’s name
These terms are sometimes used interchangably, when the meaning is clear from the context.
We can define default values for parameters, making them optional. We do this using the =
token within the function definition:
int Add(int x = 1, int y = 2) {
return x + y;
}
We can pass {}
as an argument to use the default value in that position, or omit it entirely if it’s at the end of our argument list:
int Add(int x = 1, int y = 2) {
return x + y;
}
int main() {
// Use no defaults
Add(3, 4); // 7
// Use default for 1st parameter
Add({}, 5); // 6
// Use default for 2nd parameter
Add(2, {}); // 4
Add(2); // 4
// Use default for both parameters
Add({}, {}); // 3
Add({}); // 3
Add(); // 3
}
Function return types can be set to auto
which will ask the compiler to infer what it should be based on the return
statements:
// Compiler deduces it will return an int
auto Add(int x, int y) {
return x + y;
}
auto
Function ParametersFunction parameters can also have a type of auto
, but this doesn’t mean what we might expect.
Using auto
in a function’s parameter list creates a function template. We explain templates in detail later in this course.
Like with variables, the compiler will attempt to implicitly convert function arguments and return values into the appropriate type. It will only present an error if it cannot do the conversion.
// Will return 2
int GetInt() {
return 2.5f;
}
int Add(int x, int y) {
return x + y;
}
// All expressions will return 2
// they are equivalent to Add(2, 2)
Add(2, 2);
Add(2.5f, 2.5f);
Add(2, 2.5f);
Add(2.5, 2.5);
It’s possible to have two functions with the same name in the same scope.
The way the compiler determines which function we want to call is by comparing the argument types to the parameter types:
// Integer Overload
int Add(int x, int y) {
return x + y;
}
// Float Overload
float Add(float x, float y) {
return x + y;
}
Add(2, 2); // Calling Integer Overload
Add(2.5f, 2.5f); // Calling Float Overload
If it is ambiguous what function we want to call, the compiler will throw an error. The following is ambiguous because the compiler doesn’t know which variation of Add
to use:
Add(2, 2.5f);
It could convert the first argument to a float
, and call the overloads that accept a (float
, float
) argument list. Or, it could convert the second argument to an int, and call the (int
, int
) overload.
It doesn’t know what we want, so it throws an error.
error: 'Add': overloaded functions have similar conversions
could be 'float Add(float,float)'
or 'int Add(int,int)'
trying to match argument list '(int, float)'
The following also throws an error, as we provide two double
arguments. A double
can be converted to either a float
or an int
so again, we’re being ambiguous:
Add(2.5, 2.5);
error: 'Add': ambiguous call to overloaded function
could be 'float Add(float,float)'
or 'int Add(int,int)'
trying to match argument list '(double, double)'
We can remove the ambiguity by explicitly casting our arguments to the desired type.
// Calling (int, int) Overload
Add(2, static_cast<int>(2.5f));
// Calling (float, float) Overload
Add(
static_cast<float>(2.5),
static_cast<float>(2.5)
);
Functions can be declared and defined as separate statements. A function declaration looks the same as a definition, but without the body:
int Add(int x, int y);
A function’s return type, name, and parameter types are often called the function prototype or function signature.
Function prototypes technically don’t need to name their parameters:
int Add(int, int);
But it’s common to include the names, to remind ourselves (or other developers reading our code) what the parameters are for.
Function declarations allow our functions to be called even if they are not yet defined at the place where they are called.
This is referred to as a forward declaration:
int Add(int x, int y);
int main() {
Add(1, 2);
}
int Add(int x, int y) {
return x + y;
}
The difference between a declaration and a definition will become more important as we advance through this course.
We don’t necessarily have access to all of our functions and variables from anywhere in our program.
Scopes define which variables are accessible, and from where. Scopes are typically created any time we have a set of braces, {
and }
For example, a function body creates a scope.
Expressions can access their own scope, and any ancestor (parent, grandparent, etc) scope:
// Global scope
int GlobalInt{0};
void MyFunction() {
// Local scope
int LocalInt{0};
// This is fine - we're accessing
// a variable in the parent scope
GlobalInt++;
}
But they cannot access any descendant (child, grandchild, etc) scopes:
void MyFunction() {
int LocalInt{0};
{
// Fine - LocalInt is in a parent scope
++LocalInt;
int ChildInt{1};
}
// Error - we don't have access to child scope
ChildInt++;
}
In this lesson, we've covered the fundamentals of functions. We've seen how to define functions, call them, and control their visibility using scopes. Key takeaways:
Learn the basics of writing and using functions in C++, including syntax, parameters, return types, and scope rules.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games