Fundamentals

C++ functions: the full picture

Short answer: a function is a named block with typed inputs (parameters) and one typed output (the return value). Calling it runs the block with specific inputs and hands back the result.

Anatomy

int add(int a, int b) {   // return type, name, parameter list
    return a + b;         // body, ends by returning an int
}

Four things to read in that line: int is what comes back, add is the name, int a, int b are the inputs, and return produces the output. A function with no return value is declared void.

Calling is just substitution

#include <iostream>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int square(int x) {
    return x * x;
}

int main() {
    cout << add(2, 3) << endl;         // 5
    cout << square(add(1, 1)) << endl; // (2)^2 = 4 — a call inside a call
    return 0;
}

The second line is the point: a function call is a value, so it can sit anywhere a literal could. That is what makes functions composable.

Parameters are copies

This is the single most important fact about C++ functions:

#include <iostream>
using namespace std;

void tryToChange(int n) {
    n = 999;          // changes the local copy only
}

int main() {
    int x = 5;
    tryToChange(x);
    cout << x << endl;   // still 5
    return 0;
}

To actually change the caller's variable you must pass an address (pointer) or a reference. The full comparison, with diagrams, is in pass by value, pointer or reference.

Return vs print

A function that returns can be used in expressions; one that prints cannot:

#include <iostream>
using namespace std;

int doubled(int n) { return n * 2; }

void printed(int n) { cout << n * 2 << endl; }

int main() {
    int total = doubled(10) + 1;   // works: doubled() is a value
    cout << total << endl;
    printed(10);                   // works, but cannot be reused
    return 0;
}

House rule: compute in functions (return), print in main. It keeps the function testable and reusable.

Scope: where names live

#include <iostream>
using namespace std;

int counter = 0;        // global: visible everywhere

int bump() {
    int local = 5;      // local: exists only during this call
    counter++;
    return counter + local;
}

int main() {
    cout << bump() << endl;   // 6
    cout << bump() << endl;   // 7 — the global survived
    // cout << local;         // error: local is not in scope here
    return 0;
}

Prefer locals, pass what a function needs as parameters, and return what it produces. Globals are convenient and quietly make code hard to reason about.

Why split code at all

The three bugs

Practice: Prime Check and GCD are both cleaner with a helper function. Read next: recursion, then passing modes.