LEARNING · STAGE 00 · PROGRAMMING BASICS

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 return does 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:

main.cppC++
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 (n in int square(int n)). It's a "placeholder variable" that exists only inside the function.
  • Argument — the specific value passed at the call site (5 in square(5)). The same function with one parameter can have any number of different arguments in different calls.
One parameter, different argumentsC++
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":

main.cppC++
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:

main.cppC++
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

PartExampleRole
Return typeint, void, double…what the function returns (or nothing, if void)
Namesquarethe identifier by which the function is called
Parameterint n in the declarationa "placeholder" for a value inside the function
Argument5 in square(5)the specific value at a specific call
returnreturn 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.

Worse — the same if/else chain copied three timesC++
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.

Better — the logic is written once, in a functionC++
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().

Best — the output is also moved into its own functionC++
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; } — call multiply(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; from square — 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 double function modeled on average — for example, double square2(double x) { return x * x; } — and compare the call square2(2.5) with the integer square(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.

Function declared but never called

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).

A void function tries to return a value
void printGreeting(std::string name)
{
    return name;   // void returns nothing — return with a value is an error here
}
console (illustration)
g++ main.cpp -o main main.cpp: In function 'void printGreeting(std::string)': main.cpp:4:12: error: return-statement with a value, in function returning 'void' 4 | return name; | ^~~~ Compilation failed.

Either change the return type to something suitable (std::string), or remove the value from return.

Not all execution paths return a value
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
}
console (illustration)
g++ main.cpp -o main main.cpp: In function 'int sign(int)': main.cpp:7:1: warning: control reaches end of non-void function [-Wreturn-type] 7 | } | ^ Warning, not an error — but the result is unpredictable.

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.

Parameter or argument?
In this code — what is the parameter here?
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).

Find the mistake
The compiler will refuse to build this code. How do you fix it?
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).

Predict the result
This program compiles without a single error. What will it output?
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.

★★ 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.

★★ 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.

14Terms

TermMeaning
Functiona named reusable block of code: return type, name, parameters, body
Parametera placeholder name in the function declaration, existing only inside it
Argumentthe specific value passed to the function at a call
returnan instruction handing a value out and immediately terminating the function
voidthe 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 int pattern, but also with void, double, char for 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 just int.
  • 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.

💡 Thoughts from an experienced programmer

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.

🎮 How does this relate to Unreal Engine?

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.