C++ · Beginner

Functions in C++

10 min readUpdated September 24, 2026Every example verified

In short: A C++ function is a named block with a return type and a parameter list, such as int fareFor(int stops). It must be declared before it is called, receives a copy of each argument unless the parameter is a reference, and hands back a value with return, or nothing when its return type is void.

Naming a piece of work

A function gives a name to a computation so it can be called from several places, tested on its own and read as a single idea. main is a function; every other function follows the same pattern: a return type, a name, a parenthesised list of parameters with their types, and a body in braces. int fareFor(int stops) promises to accept one int and give back one int. Calling it, fareFor(4), runs the body with stops set to 4 and replaces the call with the returned value.

return expression; ends the function and delivers the value. A function whose return type is not void must return on every path; the compiler warns if a path reaches the closing brace without a return, and the behaviour if that happens at run time is undefined. A void function returns nothing and exists for its effect, such as printing; a bare return; may leave it early.

C++ compiles a file from top to bottom and must know a function's signature before it can check a call to it. Either define the function above the code that calls it, or put a declaration, also called a prototype, above the caller: double area(double side); with a semicolon instead of a body. The definition can then appear anywhere, even below main. Header files are essentially collections of such declarations.

By default, arguments are passed by value: the function receives a copy, and changes to the parameter never reach the caller's variable. That is safe and usually what you want. When a function must change the caller's variable, declare the parameter as a reference with &: void addTax(double& price) works on the caller's own price. A reference parameter marked const, as in const std::string& name, avoids copying a possibly large object while promising not to change it, and it is the standard way to pass strings and containers into a function that only reads them. The pointers and references lesson goes deeper into what a reference is.

A parameter can have a default argument, written once in the declaration: std::string greet(const std::string& name, const std::string& title = "Dr"). Callers that leave the argument out get the default. Defaults must come last in the list, and the definition must not repeat them.

Two functions can share a name if their parameter lists differ in number or type; this is overloading, and the compiler picks the version that matches the arguments. area(3) and area(3, 4.5) can therefore be two functions with one natural name. Overloading on the return type alone is not allowed, because the call site would not say which one is meant.

Variables declared inside a function are local: they are created when the function starts, are invisible outside it, and are destroyed when it returns. Two functions may both have a variable called total without any conflict, which is a large part of what makes functions safe to write independently.

Syntax

 C++ · syntax
returnType name(parameterType parameter, ...) {   // definition
    // body
    return value;                                  // omit in a void function
}

returnType name(parameterType parameter, ...);     // declaration (prototype)

void change(int& x);                // reference: the caller's variable is used
void show(const std::string& s);    // const reference: read without copying
int scale(int n, int factor = 2);   // default argument, written once

int area(int side);                 // overloads: same name,
int area(int w, int h);             // different parameter lists

result = name(argument, ...);       // a call

Parameters are the names in the definition; arguments are the values supplied at the call. Each argument is converted to the parameter's type, so area(3) passes 3.0 to a double parameter.

Defining, calling and reusing a function

A tram fare rule is written once and used four times, both directly and from another function.

 C++
#include <iostream>

// Returns the tram fare in pence for a journey of the given number of stops.
int fareFor(int stops) {
    int fare = 120 + 35 * stops;
    if (fare > 400) {
        fare = 400;      // the daily cap
    }
    return fare;
}

// Returns nothing; it exists for its side effect of printing.
void printFare(int stops) {
    std::cout << stops << " stop(s): " << fareFor(stops) << "p\n";
}

int main() {
    printFare(1);
    printFare(4);
    printFare(12);
    int total = fareFor(2) + fareFor(3);
    std::cout << "Two journeys: " << total << "p\n";
    return 0;
}

Output

1 stop(s): 155p
4 stop(s): 260p
12 stop(s): 400p
Two journeys: 415p

Both functions are defined above main, so no separate declarations are needed. fareFor computes and returns; printFare calls it and prints, returning nothing. Because the cap lives in one place, changing it changes every fare in the program. The last call shows a returned value used inside a larger expression, as any int would be.

Pass by value versus pass by reference

The same tax calculation, written twice; only the reference version changes the caller's variable.

 C++
#include <iostream>
#include <string>

void addTaxByValue(double price) {
    price = price * 1.2;      // changes the copy only
}

void addTaxByReference(double& price) {
    price = price * 1.2;      // changes the caller's variable
}

void describe(const std::string& name, double price) {
    std::cout << name << " costs " << price << "\n";
}

int main() {
    double lamp = 50.0;
    addTaxByValue(lamp);
    std::cout << "After the by-value call: " << lamp << "\n";
    addTaxByReference(lamp);
    std::cout << "After the by-reference call: " << lamp << "\n";
    describe("desk lamp", lamp);
    return 0;
}

Output

After the by-value call: 50
After the by-reference call: 60
desk lamp costs 60

addTaxByValue receives a copy of lamp, multiplies the copy and throws it away, so lamp is still 50. addTaxByReference declares price as a reference, another name for the caller's lamp, so the assignment changes the original. describe takes the name as a const std::string&: no copy is made, and the compiler would reject any attempt to modify it inside the function. The literal "desk lamp" is converted to a temporary std::string for the call.

Prototypes, overloading and default arguments

Three declarations let main call functions whose bodies come later; two share a name and one has a default.

 C++
#include <iostream>
#include <string>

// Declarations: main can call these before their bodies appear.
double area(double side);
double area(double width, double height);
std::string greet(const std::string& name, const std::string& title = "Dr");

int main() {
    std::cout << "Square: " << area(3) << "\n";
    std::cout << "Rectangle: " << area(3, 4.5) << "\n";
    std::cout << greet("Okafor") << "\n";
    std::cout << greet("Lindqvist", "Professor") << "\n";
    return 0;
}

double area(double side) {
    return side * side;
}

double area(double width, double height) {
    return width * height;
}

std::string greet(const std::string& name, const std::string& title) {
    return "Good morning, " + title + " " + name;
}

Output

Square: 9
Rectangle: 13.5
Good morning, Dr Okafor
Good morning, Professor Lindqvist

The compiler chooses between the two area functions by counting arguments: one argument selects the square version, two the rectangle version. area(3) passes an int where a double is expected, which converts silently. greet("Okafor") fills in the default title from the declaration; the definition below repeats the parameter list without the = "Dr", as required. The bodies sit under main and the prototypes make that legal.

Ways to pass a parameter

ParameterWhat the function getsCaller's variableUse for
int na copyunchangedsmall values the function only reads
int& nthe caller's variable itselfmay changeresults the function must hand back through the argument
const std::string& sthe caller's object, read-onlyunchangedstrings and containers the function only reads, without copying

Common mistakes

  • Calling a function above its definition without a prototype

    Why it goes wrong: C++ checks calls against a known signature, so a call to a function defined further down fails with 'fareFor' was not declared in this scope.

    Fix: Define the function above the caller, or add a declaration such as int fareFor(int stops); near the top of the file.

     C++ · fix
    int fareFor(int stops);   // declaration
    
    int main() {
        int fare = fareFor(2);   // legal: the declaration is above
        std::cout << fare << "\n";
        return 0;
    }
    
    int fareFor(int stops) {  // definition, anywhere below
        return 120 + 35 * stops;
    }
  • Forgetting to return a value

    Why it goes wrong: A function declared to return int that computes a result but never writes return compiles with a -Wreturn-type warning, and the caller receives an undefined value.

    Fix: Return on every path, including inside each branch of an if/else. Treat the warning as an error.

  • Expecting a by-value parameter to change the argument

    Why it goes wrong: void reset(int counter) { counter = 0; } resets its own copy; the caller's counter keeps its old value.

    Fix: Declare the parameter as a reference, int& counter, or return the new value and assign it at the call site.

  • Repeating a default argument in the definition

    Why it goes wrong: Writing = "Dr" in both the prototype and the definition is an error: default argument given for parameter ... after previous specification.

    Fix: Write the default once, in the declaration the callers see.

Where you use this

A program that reads records, computes something and prints a report divides naturally into functions: one that reads a record, one that computes the result for a record, one that prints a line. Each can be written and tested alone, and main becomes a short description of the whole job rather than a wall of detail. When the pricing rule changes, only the computing function is touched. Functions that take their inputs as parameters and return their result, without touching outside variables, are the easiest kind to reason about and to reuse; reference parameters are for the cases where a function genuinely needs to hand back more than one thing or to update a large object in place.

 C++ · in practice
int readQuantity();
int priceFor(int quantity, int unitPence);
void printLine(const std::string& item, int pence);

int main() {
    int qty = readQuantity();
    printLine("tea", priceFor(qty, 180));
}

Key points

  • A definition has a return type, a name, typed parameters and a body; a declaration is the same header ending in a semicolon.
  • A function must be declared or defined above the code that calls it.
  • Non-void functions must return a value on every path; void functions return nothing.
  • Arguments are copied into parameters by default; T& lets the function change the caller's variable; const T& reads a large object without copying.
  • Default arguments are written once, in the declaration, and must be the last parameters.
  • Overloaded functions share a name and differ in parameter number or types, never only in return type.
  • Local variables belong to the function and vanish when it returns.

Try it yourself

Write a function double toFahrenheit(double celsius) that converts a temperature using the formula celsius * 9 / 5 + 32, and use it in main so that the program prints the converted value instead of repeating the input.

Your program
#include <iostream>

// Write toFahrenheit here.

int main() {
    double celsius;
    std::cin >> celsius;
    std::cout << celsius << " C = " << celsius << " F\n";
    return 0;
}
Input the program receives: 25
Expected output: 25 C = 77 F

Practise this

Exercises for this lesson are in the C++ practice set.

Open the C++ playground

Frequently asked questions

Why must a C++ function be declared before it is called?

The compiler reads the file once from top to bottom and needs the function's return type and parameter types to check the call and generate the right code. A declaration (prototype) such as int fareFor(int stops); supplies that information without the body, so definitions can be arranged in any order below it. Headers exist largely to share these declarations between files.

When should a parameter be a reference in C++?

Use a plain reference (int& n) when the function must modify the caller's variable, for instance to return two results. Use a const reference (const std::string& s) when the argument is large and the function only reads it, which avoids a copy. Pass small values such as int, double and char by value; copying them is as cheap as it gets.

Can two C++ functions have the same name?

Yes, if their parameter lists differ in the number or the types of parameters. This is overloading, and the compiler selects the version matching the arguments at each call. Two functions that differ only in return type cannot coexist, because a call would not indicate which one was meant.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with GCC 13 (C++17) at build time by the publishing checks, and the output shown is what it printed. Running C++ inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C++17) locally.