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. Anyone looking for more thorough explanations or additional context should consider 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 ()
:
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.
We define parameters between the ()
in the function specification. We specify their type and name, and separate multiple parameters using commas:
int Add(int x, int y) {
return x + y;
}
int main() {
Add(1, 2); // 3
}
We can define default values for parameters, making them optional. We can pass {}
as an argument to use the default value in that position, or omit the arguments entirely if they are at the end:
int Add(int x = 1, int y = 2) {
return x + y;
}
int main() {
// Use no defaults
Add(2, 3); // 5
// Use default for 1st parameter
Add({}, 2); // 3
// Use default for 2nd parameter
Add(2, {}); // 4
Add(2); // 4
// Use default for both parameters
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:
auto Add(int x, int y) {
return x + y;
}
Function parameters can also have a type of auto
, but this doesn’t mean what we might expect. It creates a function template, which is explained 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 whether it should convert the first argument or the second argument:
Add(2, 2.5f);
It could convert the first 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 (float
, float
)Â overload.
It doesn’t know what we want, so it throws an error.
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);
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 = 1) {
return x + y;
}
The difference between a declaration and a definition will become more important as we advance through this course.
Functions and any other block delimited by {
and }
create a new local scope. Expressions can access their own scope, and any ancestor (parent, grandparent, etc) scope. But they cannot access any descendant (child, grandchild, etc)Â scopes:
// 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++;
}
// This is fine - same scope
++GlobalInt;
// Not fine - we do not have access
// to MyFunction's scope from here
++LocalInt;
— First Published
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.