Functions: parameters, arguments, return
You've been using a function since the very first lesson — main() is a function, just with the special role of entry point. It's time to figure out how functions work in general: how to declare your own, what parameters and arguments are, and what return does. At the end of the lesson, we'll use this same technique to break down the character-program code into separate functions — they'll come in handy again in one of the upcoming lessons.
01What you'll learn
- How to declare your own function: return type, name, parameters, body
- How a parameter differs from an argument
- What
returndoes and why the type of the returned value must match the declared one void— functions that return nothing
02Why functions if we already have loops
A loop repeats the same code many times in a row, in one place. But what if the same calculation (say, "the area of a square with side N") is needed in different, not necessarily adjacent places in the program? Copying the formula into each such place is the same duplication that loops solve, just for "the same code in different places" rather than "the same code many times in a row." A function is exactly that: a named, reusable piece of code — write it once, use it by name as many times as you need.
03Declaring a function
You've been writing one function since the very first lesson, you just didn't think of it in those terms — main(). It has a return type (int), a name (main), empty parameter parentheses, and a body { }. Your own function is built from the same template — just with the name, parameters, and body that you choose yourself:
int square(int n)
{
return n * n;
}
int main()
{
int result = square(5);
std::cout << result; // 25
return 0;
}
int before square is the type of the value the function will return (just like main(), which is also declared as int main() — its return value is simply used differently by the operating system than ordinary functions). square is the name, following the same identifier rules as variables. (int n) is the parameter: the value the function accepts when called. The body is what executes on every call.
04Parameter and argument — different words for different roles
These two terms get confused most often, even though the difference is simple:
- Parameter — the name in the function declaration (
ninint square(int n)). It's a "placeholder variable" that exists only inside the function. - Argument — the specific value passed at the call site (
5insquare(5)). The same function with one parameter can have any number of different arguments in different calls.
std::cout << square(5); // argument 5 → parameter n receives 5 → result 25
std::cout << square(10); // argument 10 → parameter n receives 10 → result 100
On every call, the parameter n is, in essence, a new variable that exists only for the duration of the function's execution and receives the value of the passed argument. Changes to n inside the function do not affect the variable that may have been used as the argument outside — the parameter lives its own separate life. Why this is so, and what determines where a variable is "visible" and where it isn't, is a whole separate big topic — we'll cover it in detail in the very next lesson.
05return — what a function returns
return has already appeared in main() — return 0;. In a function with a non-empty return type (like int square(int n)), return is mandatory on every execution path, and the type of the returned value must match the declared function type (or at least be convertible to it): int square(...) cannot return a std::string. return also immediately terminates the function — code after it on the same execution path will not run, similar to how break terminates a loop.
06void — a function without a return value
Not every function needs to return something — sometimes the function's only purpose is simply to perform an action, like printing a greeting. Writing int and inventing a meaningless return 0; just for form would be dishonest: the return type should reflect what the function actually does. For this case, there's a separate type — void ("empty"), meaning exactly "this function returns nothing":
void printGreeting(std::string name)
{
std::cout << "Hello, " << name << "!\n";
}
int main()
{
printGreeting("Ada");
printGreeting("Alan");
return 0;
}
In a void function, return; without a value is allowed (to terminate the function early, like break for a loop), but return with a value is not — it's a compile error: there's nowhere to return the value to, the return type is explicitly declared as "nothing."
07Not just int: float, double, and char as function types
Everything above worked with int and void — but the parameter and return type can be any type a variable can hold (the topic about basic data types). Fractional calculations are the most common reason to create a function with double parameters and a double return:
double average(double a, double b)
{
return (a + b) / 2.0;
}
int main()
{
double result = average(3.0, 4.0);
std::cout << result; // 3.5
return 0;
}
The rule is the same as for square: the type before the name is the type of the value the function will return, and the type before the parameter name is the type of the value it will accept. Here both parameters and the return are double, because the average of two numbers is almost never an integer. float works the same way (also a fractional type, just with less precision) — float half(float x) { return x / 2.0f; } is declared and called using the same pattern; you just need to change the word float instead of double on the parameter and return.
The return type and the parameter type don't have to match each other — only what's actually written in return and what's actually passed at the call. The gradeFor function later in this lesson will return a char while accepting an int — this is a perfectly normal and common combination: the parameter and return are chosen independently, each for its own role.
08Parts of a function — table
| Part | Example | Role |
|---|---|---|
| Return type | int, void, double… | what the function returns (or nothing, if void) |
| Name | square | the identifier by which the function is called |
| Parameter | int n in the declaration | a "placeholder" for a value inside the function |
| Argument | 5 in square(5) | the specific value at a specific call |
return | return n * n; | the value handed out; immediately terminates the function |
09Worse → better: from copy-paste to a function
The task — return to the character program and output a letter grade based on hp for three squad members: 95, 75, and 50 health points (90 and above — 'A', 70–89 — 'B', below 70 — 'F'). The logic is the same — but it can be written in different ways.
int hp1 = 95;
if (hp1 >= 90) { std::cout << "A" << "\n"; }
else if (hp1 >= 70) { std::cout << "B" << "\n"; }
else { std::cout << "F" << "\n"; }
int hp2 = 75;
if (hp2 >= 90) { std::cout << "A" << "\n"; }
else if (hp2 >= 70) { std::cout << "B" << "\n"; }
else { std::cout << "F" << "\n"; }
int hp3 = 50;
if (hp3 >= 90) { std::cout << "A" << "\n"; }
else if (hp3 >= 70) { std::cout << "B" << "\n"; }
else { std::cout << "F" << "\n"; }
It works correctly, but if the grade boundaries ever need to change (say, raising the threshold for 'A' to 95), you'd have to find and fix three identical blocks — and it's easy to forget one of them.
char gradeFor(int hp)
{
if (hp >= 90) { return 'A'; }
else if (hp >= 70) { return 'B'; }
else { return 'F'; }
}
int main()
{
std::cout << gradeFor(95) << "\n";
std::cout << gradeFor(75) << "\n";
std::cout << gradeFor(50) << "\n";
return 0;
}
The same result, but the rule is now in one place — to change the boundary you'll only need to do it inside gradeFor, and that fix immediately affects all calls. Note: gradeFor returns a char rather than printing it itself — the calculation and the output are deliberately separated, with the output remaining outside, in main().
char gradeFor(int hp)
{
if (hp >= 90) { return 'A'; }
else if (hp >= 70) { return 'B'; }
else { return 'F'; }
}
void printReport(int hp)
{
std::cout << "HP: " << hp << ", Grade: " << gradeFor(hp) << "\n";
}
int main()
{
printReport(95);
printReport(75);
printReport(50);
return 0;
}
printReport is a void function that, inside itself, calls another function, gradeFor. Functions can call each other without restrictions — this is completely normal practice: each function is responsible for exactly one thing (calculating the grade — separately, printing the report — separately), and main() becomes shorter and reads like a list of steps. gradeFor in this form will come in handy again — the character program will soon come together as a complete console program, and this function will become one of its building blocks.
10Try changing it
- Add a second parameter to
square:int multiply(int a, int b) { return a * b; }— callmultiply(3, 4). - In
printGreeting, add a third call with a different name — no need to change the function body, just a new call with a new argument. - Try removing
return n * n;fromsquare— the compiler will warn (or refuse to build) that a function with a non-empty return type doesn't always return something. - Write your own
doublefunction modeled onaverage— for example,double square2(double x) { return x * x; }— and compare the callsquare2(2.5)with the integersquare(2): the same idea (multiply by itself), two different types.
11Live debugging: common mistakes
The exact error text depends on the compiler and its version — below is the typical g++-style appearance.
int square(int n) { return n * n; } int main() { return 0; }
Compiles without errors and without warnings — a function declaration merely describes what it does, but by itself it executes nothing. The code inside square will run only when it's actually called by name: square(5).
void printGreeting(std::string name)
{
return name; // void returns nothing — return with a value is an error here
}
Either change the return type to something suitable (std::string), or remove the value from return.
int sign(int x)
{
if (x > 0)
{
return 1;
}
// no return for the case x <= 0 — but the int return type promises a value always
}
Calling such a function when x <= 0 is undefined behavior akin to reading an uninitialized variable: some garbage will be returned. On many real projects, such warnings are intentionally turned into build errors (the -Werror flag). The correct fix is to add an else with its own return.
12Check yourself
Three questions: the difference between parameter and argument with a concrete example, diagnosing a compile error, and predicting what happens without a call.
int triple(int x) { return x * 3; }
std::cout << triple(7) << triple(2);Show explanation
x is the parameter, the one and only, part of the function declaration. 7 and 2 are two different arguments in two different calls of the same function with one parameter (§04).
void logMessage(std::string text)
{
return text;
}Show explanation
void means "the function returns nothing," while return text; tries to return a value — a contradiction the compiler will not let through (see §06 and §11). The fix: either change the return type to std::string, or remove the value from return (leave just return; or remove it entirely).
int square(int n) { return n * n; }
int main() { return 0; }Show explanation
Nothing will be output. square is declared but never called anywhere — a function declaration only describes what it can do; by itself it executes nothing (§11). The code inside square would run only on an actual call like square(5).
13Practice
★★ Write a function int square(int n) that returns n * n, call it at least once, and print the result via std::cout.
Steps: 1) decide the return type and parameter type — both are integers, so int; 2) write the signature int square(int n) and a body with a single return; 3) in main(), call square with any numeric argument and pass the result to std::cout.
The signature is built from the pattern in the "Declaring a function" section: the return type matches what stands after return inside the body, and the parameter matches what's passed at the call.
Solution: int square(int n) { return n * n; } — then in main(): std::cout << square(5);
★★ Write a void function printStars(int count) that uses a loop to print count stars * in a row, then a newline. Call it twice with different arguments.
Steps: 1) the function returns nothing — so the return type is void; 2) the parameter count is an integer, int; 3) inside — a loop that repeats count times and each time prints one * character; 4) after the loop — a separate output of "\n"; 5) call the function twice with two different numbers so the difference is visible.
The loop inside the function must actually use count in its condition (for example, for (int i = 0; i < count; i++)) — otherwise the number of stars won't change with the argument.
Solution: void printStars(int count) { for (int i = 0; i < count; i++) { std::cout << "*"; } std::cout << "\n"; } — call it twice: printStars(3); printStars(5);
★★ Write a function double average(double a, double b) that returns the arithmetic mean of two numbers, call it, and print the result via std::cout.
Steps: 1) the result is a fractional number (the average of two numbers is almost never an integer), so both the parameters and the return type are double; 2) the body — one line: add both parameters and divide by 2.0 (not 2 — that's a more reliable way to get true fractional division); 3) call average with two numbers and print the result.
The return must actually add and divide both function parameters — not substitute numbers, but specifically a and b, so the result changes with the call arguments.
Solution: double average(double a, double b) { return (a + b) / 2.0; } — then in main(): std::cout << average(3.0, 4.0);
14Terms
| Term | Meaning |
|---|---|
| Function | a named reusable block of code: return type, name, parameters, body |
| Parameter | a placeholder name in the function declaration, existing only inside it |
| Argument | the specific value passed to the function at a call |
return | an instruction handing a value out and immediately terminating the function |
void | the return type for functions that return nothing |
15What you can do now
Before this lesson, all the code lived inside a single main(). Now it can be broken down into named, reusable pieces:
- You can declare your own function with the appropriate return type, parameters, and body — not only following the
intpattern, but also withvoid,double,charfor a specific task. - You can explain the difference between a parameter and an argument using any concrete call, not just recite the definition.
- You can notice when the same piece of code should be extracted into a function instead of copying it again.
- You can predict what happens if a function is declared but never called — and why the compiler doesn't treat it as an error.
- You can spot, in someone else's code, a function that promises a return value but actually ignores it (or vice versa — a void that tries to return something).
16Summary
- A function = return type + name + parameters in parentheses + body
{ }. - A parameter is a name in the declaration; an argument is the specific value at a specific call.
return value;— hands out the result and immediately terminates the function; the value's type must match the declared return type.void— a function without a return value;return;without a value is allowed,return value;is not.- The return type and the parameter type are chosen independently of each other and can be anything —
int,double,char,bool, and so on — for a specific task, not justint. - A function declaration executes nothing by itself — an explicit call by name is required.
Functions have broken the code down into named pieces — but so far every variable is still visible from everywhere it's written, without explicit boundaries. The next lesson is scope: where exactly a variable exists, and what happens to a parameter after the function finishes.
The most common beginner mistake with functions is writing them "after the fact," already drowning in repetitive code. The right habit is the opposite: if you feel you're about to copy a piece of code for the second time — that's the signal to stop and extract it into a function right now, before copying, not after the third repetition. And one more thing: a good function name is a verb describing an action (printStars, gradeFor), not a noun. If a function name doesn't form a meaningful verb — perhaps it does not one thing but several at once, and it's worth splitting into two.
Member functions of a class in Unreal C++ (methods of Actors and components) are governed by the same rules — return type, name, parameters, return. The difference comes later: by marking a function with the UFUNCTION(BlueprintCallable) macro, it can also be called from Blueprint — but the function itself remains an ordinary C++ function on the inside, fundamentally no different from square in this lesson.