Functions

Reviewed & published by Brayan K

By the end of this lesson you'll be able to write your own C++ functions with parameters and return values, choose between passing by value, reference, and const reference, give parameters default values, and overload a function name — the skills that turn long scripts into clean, reusable code.

Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

A function is a recipe card. You write the steps once — "make a coffee" — and from then on you just say "make a coffee" instead of re-listing every step. The parameters are the ingredients you hand over (the bean, the cup size); the return value is the finished coffee handed back. Passing by value is like giving the chef a photocopy of your shopping list — they can scribble on it and your original is untouched. Passing by reference is handing over the original list itself, so any note they make is on your copy. That single distinction explains most "why didn't my value change?" confusion in C++.

1. Declaring & Defining Functions

A function has a return type (what it hands back), a name, a list of parameters (its inputs), and a body. The shape is always the same: returnType name(parameters) { ... return value; }. Use void when the function just does something and hands nothing back. Read this worked example, run it, then you'll write your own.

#include <iostream>
#include <string>
using namespace std;

// A function = a named block of code you can call by name, again and again.
// Pattern:  returnType  name(parameters) { body; return value; }

// void = "returns nothing". It just DOES something.
void greet(string name) {
    cout << "Hello, " << name << "!" << endl;   // prints, returns nothing
}

// Returns an int. The 'return' hands a value back to the caller.
int add(int a, int b) {
    return a + b;            // the result of add(...) IS this value
}

// Two parameters, returns a double (a decimal number).
double averageOf(double a, double b) {
    return (a + b) / 2.0;
}

int main() {
    greet("Alice");                     // Hello, Alice!
    greet("Bob");                       // Hello, Bob!

    int sum = add(15, 27);              // capture the returned value
    cout << "15 + 27 = " << sum << endl;     // 15 + 27 = 42

    double avg = averageOf(8.0, 5.0);  // use the return value directly too
    cout << "average = " << avg << endl;     // average = 6.5
    return 0;
}

// ✅ Expected output:
//    Hello, Alice!
//    Hello, Bob!
//    15 + 27 = 42
//    average = 6.5

Your turn. The program below is almost complete — fill in the two blanks marked ___ using the hints, then run it.

#include <iostream>
using namespace std;

// 🎯 YOUR TURN — finish the rectangleArea function, then press "Try it Yourself".

// Write a function that takes width and height and RETURNS their product.
int rectangleArea(int width, int height) {
    return ___;             // 👉 multiply the two parameters (width * height)
}

int main() {
    int area = ___;         // 👉 call rectangleArea(5, 3) and store the result
    cout << "Area: " << area << endl;

    // ✅ Expected output:
    //    Area: 15
    return 0;
}

2. By Value vs by Reference vs const Reference

How you write a parameter decides whether the function can change the caller's data. By value (int x) gives the function a private copy — changes stay inside. By reference (int&x) lets the function edit the caller's actual variable. By const reference (const std::string&) gives read-only access with no copy, which is the efficient way to pass big objects like strings and vectors you only need to read.

MethodSyntaxChanges caller?Use when
By valuef(int x)❌ No (copy)Small types; you want safety
By referencef(int &x)✅ YesMust modify the original
By const reff(const string &s)❌ No (read-only)Large objects; efficiency
#include <iostream>
#include <string>
using namespace std;

// Pass by VALUE — the function gets a COPY. Changes stay inside the function.
void tryToDouble(int x) {
    x *= 2;                 // only the copy changes
}

// Pass by REFERENCE (int&) — the function works on the CALLER's variable.
void doubleInPlace(int &x) {
    x *= 2;                 // the caller's value really changes
}

// Pass by CONST REFERENCE (const std::string&) — no copy, AND read-only.
// Perfect for big objects you only need to read, like strings or vectors.
void announce(const string &message) {
    cout << "Announcement: " << message << endl;
    // message = "no";     // ❌ would not compile — const = read-only
}

int main() {
    int n = 10;
    tryToDouble(n);
    cout << "After tryToDouble: " << n << endl;   // 10  (unchanged — it was a copy)

    doubleInPlace(n);
    cout << "After doubleInPlace: " << n << endl;  // 20  (changed — by reference)

    string title = "Functions are reusable";
    announce(title);                               // Announcement: Functions are reusable
    return 0;
}

// ✅ Expected output:
//    After tryToDouble: 10
//    After doubleInPlace: 20
//    Announcement: Functions are reusable

Now you try. Right now addBonus takes a copy, so the caller's score never changes. Add the one character that turns the parameter into a reference:

#include <iostream>
using namespace std;

// 🎯 YOUR TURN — make addBonus change the caller's variable.

// Add 'bonus' to 'score'. Because score should change for the CALLER,
// it must be passed BY REFERENCE.
void addBonus(int ___ score, int bonus) {   // 👉 add the & so it's a reference (int &score)
    score += bonus;
}

int main() {
    int score = 100;
    addBonus(score, 50);
    cout << "Score: " << score << endl;

    // ✅ Expected output:
    //    Score: 150
    return 0;
}

3. Default Arguments & Overloading

Default arguments let a parameter fall back to a value when the caller leaves it out — handy for optional settings. They must be the rightmost parameters. Overloading means giving several functions the same name but different parameter lists; the compiler picks the right one from the argument types. Note: overloads must differ in their parameters, not just the return type.

#include <iostream>
#include <string>
using namespace std;

// DEFAULT ARGUMENT — if the caller omits width, it becomes 1.0.
// Defaults must be the RIGHTMOST parameters.
double area(double length, double width = 1.0) {
    return length * width;
}

// FUNCTION OVERLOADING — same name, DIFFERENT parameter lists.
// The compiler picks the right one from the argument types.
void show(int value) {
    cout << "int: " << value << endl;
}
void show(double value) {
    cout << "double: " << value << endl;
}
void show(const string &value) {
    cout << "string: " << value << endl;
}

int main() {
    cout << "area(5, 3) = " << area(5.0, 3.0) << endl;  // 15  (both given)
    cout << "area(5)    = " << area(5.0) << endl;        // 5   (width defaults to 1.0)

    show(42);          // int: 42      -> picks show(int)
    show(3.14);        // double: 3.14 -> picks show(double)
    show("hello");     // string: hello -> picks show(const string&)
    return 0;
}

// ✅ Expected output:
//    area(5, 3) = 15
//    area(5)    = 5
//    int: 42
//    double: 3.14
//    string: hello

🔎 Deep Dive: declarations (prototypes) & inline

C++ reads a file top to bottom, so a function must be known before you call it. A declaration (or prototype) announces a function's signature without its body — note the semicolon. You can declare it up top and write the full definition later, even below main().

int add(int a, int b);   // declaration / prototype — just the signature

int main() {
    cout << add(2, 3);   // works: the compiler already knows add
}

int add(int a, int b) {  // definition — the actual body, defined later
    return a + b;
}

inline is a hint that the compiler may paste a small function's body straight into the call site instead of making a real call. Modern compilers decide this for you, so you rarely add it for speed. Its real day-to-day use is letting a function be defined in a header that many files include without a "multiple definition" linker error.

inline int square(int x) { return x * x; }   // safe to define in a header

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

ConceptSyntax
Void function
Return a value
Declaration / prototype
Default argument
By reference
Const reference
OverloadingSame name, different parameter list
Inline

Mini-Challenge: Greeting & Total

No blanks this time — just a brief and an outline to keep you on track. Write the two functions yourself (one uses a default argument, the other combines defaults with multiple inputs), then check your output against the comments.

#include <iostream>
#include <string>
using namespace std;

// 🎯 MINI-CHALLENGE: a small greeting + total tool
// 1. Write  string greeting(string name, string title = "friend")
//    that RETURNS "Hi <title> <name>!"   (title defaults to "friend").
// 2. Write  int total(int a, int b, int c = 0)  that RETURNS a + b + c
//    (c defaults to 0 so it works with two OR three numbers).
// 3. In main(): print greeting("Sam") and greeting("Dr. Lee", "Professor"),
//    then print total(2, 3) and total(2, 3, 4).
//
// ✅ Expected output:
//    Hi friend Sam!
//    Hi Professor Dr. Lee!
//    5
//    9

int main() {
    // your code here
    return 0;
}

🎉 Lesson Complete

Practice quiz

What return type means a function hands nothing back?

  • null
  • none
  • void
  • empty

Answer: void. void means the function returns nothing — it just does something.

If you pass an int by value and the function changes it, what happens to the caller's variable?

  • It stays unchanged (the function got a copy)
  • It changes too
  • It becomes 0
  • The program won't compile

Answer: It stays unchanged (the function got a copy). Pass by value gives the function a copy, so the caller's variable is untouched.

How do you let a function modify the caller's variable?

  • Pass by value
  • Use const
  • Return void
  • Pass by reference, e.g. void f(int &x)

Answer: Pass by reference, e.g. void f(int &x). A non-const reference (int &x) lets the function edit the caller's actual variable.

Why pass a large object as 'const std::string&'?

  • It is required for all strings
  • No copy is made and it is read-only
  • It makes the function return faster
  • It converts the string to a number

Answer: No copy is made and it is read-only. A const reference avoids copying the big object and guarantees the function won't change it.

Where must default arguments appear in a parameter list?

  • Rightmost
  • Leftmost
  • Anywhere
  • Only in the middle

Answer: Rightmost. Defaults must be the rightmost parameters so the compiler can match arguments left to right.

What must differ between overloaded functions?

  • Only the return type
  • The function name
  • The parameter list (types or number)
  • Nothing

Answer: The parameter list (types or number). Overloads must differ by their parameter list, not just the return type.

What is a function declaration (prototype)?

  • The function body
  • The signature ending in a semicolon, with no body
  • A call to the function
  • A comment describing it

Answer: The signature ending in a semicolon, with no body. A declaration/prototype gives the name, return type, and parameter types, ending with a semicolon.

Given 'double area(double length, double width = 1.0)', what does area(5.0) return?

  • 0
  • 1
  • 6
  • 5

Answer: 5. width defaults to 1.0, so 5.0 * 1.0 = 5.

What is the main practical use of 'inline' in modern C++?

  • Guaranteeing a speed boost
  • Allowing a function definition in a header without multiple-definition errors
  • Making a function private
  • Preventing recursion

Answer: Allowing a function definition in a header without multiple-definition errors. inline mainly lets a function be defined in a header included by many files without linker errors.

What does 'add(15, 27)' return for 'int add(int a, int b){ return a + b; }'?

  • 1527
  • 27
  • 42
  • 15

Answer: 42. It returns the sum a + b, which is 42.

Continue this course

Frequently asked questions

What is the difference between a declaration and a definition?

A declaration (also called a prototype) tells the compiler a function's name, return type, and parameter types, ending with a semicolon: int add(int, int);. A definition provides the actual body. You can declare a function near the top of a file and define it later — that lets main() call functions written below it.

When should I pass by value, by reference, or by const reference?

Pass by value for small types (int, double, bool, char) when the function only needs a copy. Use a non-const reference (int&) when the function must change the caller's variable. Use a const reference (const std::string&) for large objects you only read — it avoids an expensive copy and the const stops you from changing them by accident.

Why must default arguments be the rightmost parameters?

Because C++ matches arguments to parameters left to right. If a middle parameter had a default but a later one did not, the compiler could not tell which arguments you meant to skip. So once one parameter has a default, every parameter to its right must have one too.

How does function overloading decide which version to call?

Overloads must differ by their parameter list (the types or number of parameters), not just the return type. The compiler looks at the argument types at the call site and picks the best match. If two overloads match equally well, you get an 'ambiguous call' error and must make the types clearer.

What does inline do?

inline is a hint that the compiler may replace a call to a small function with the function's body directly, avoiding call overhead. Modern compilers usually decide this for you, so you rarely need it for speed. Its main practical use today is allowing a function definition to live in a header included by many files without a 'multiple definition' linker error.

Related lessons