Skip to main content

    Lesson 6 • Intermediate

    Functions

    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.

    What You'll Learn

    • Declare and define functions with parameters and return types
    • Pass by value vs by reference (int&) vs const reference (const std::string&)
    • Give parameters default arguments (rightmost only)
    • Overload one function name for different parameter types
    • Write a prototype/declaration so code can call a function defined later
    • Know when inline matters (and when to ignore it)

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

    returnType functionName(paramType p1, paramType p2) {
        // body — the code that runs when you call it
        return value;   // hand a result back (omit for void)
    }

    Worked example: parameters & return types

    Read every comment, run it, and check the output matches.

    Try it Yourself »
    C++
    #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
    ...

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

    🎯 Your turn: write a function

    Return the product, then call the function in main().

    Try it Yourself »
    C++
    #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: 
    ...

    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

    Worked example: value, reference, const reference

    Watch which calls change n and which leave it alone.

    Try it Yourself »
    C++
    #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 on
    ...

    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:

    🎯 Your turn: pass by reference

    Add the & so the function edits the caller's score.

    Try it Yourself »
    C++
    #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.

    Worked example: defaults & overloading

    See a default width fill in, and one name handle three types.

    Try it Yourself »
    C++
    #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 << e
    ...

    🔎 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

    • 💡 Pass big objects by const&: const string &s avoids copying the whole string and protects it from accidental edits.
    • 💡 One function, one job: if a function is doing several things, split it — small functions are easier to name, test, and reuse.
    • 💡 Defaults belong in one place: put a default argument on the declaration or the definition, not both, or the compiler complains.
    • 💡 Overload by type, not by return: int f() and double f() with the same parameters won't compile — the parameter list must differ.

    Common Errors (and the fix)

    • "My value didn't change!" — you passed by value, so the function edited a copy. If the caller's variable must change, take the parameter by reference: void f(int &x) instead of void f(int x).
    • "control reaches end of non-void function" — a function declared to return something has a path that never hits a return. Make sure every branch returns a value.
    • "call of overloaded 'f(...)' is ambiguous" — two overloads match the arguments equally well (e.g. an int argument that could convert to double or long). Make the argument's type explicit or remove the clashing overload.
    • "'add' was not declared in this scope" — you called a function before the compiler had seen it. Add a declaration/prototype above the call, or move the definition higher.
    • "default argument given for parameter 1 ... after ..." — a parameter with a default sits to the left of one without. Move all defaulted parameters to the right.

    📋 Quick Reference

    ConceptSyntax
    Void functionvoid greet() { }
    Return a valueint add(int a, int b) { return a + b; }
    Declaration / prototypeint add(int a, int b);
    Default argumentdouble area(double l, double w = 1.0);
    By referencevoid f(int &x)
    Const referencevoid f(const string &s)
    OverloadingSame name, different parameter list
    Inlineinline int sq(int x) { return x*x; }

    Frequently Asked Questions

    Q: 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.

    Q: 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.

    Q: 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.

    Q: 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.

    Q: 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.

    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.

    🎯 Mini-Challenge: build two functions

    Use a default argument and overloading-style flexibility.

    Try it Yourself »
    C++
    #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 tota
    ...

    🎉 Lesson Complete

    • ✅ A function is returnType name(parameters) { ... return value; }; use void when nothing comes back
    • By value copies; by reference (int&) edits the caller; const reference (const std::string&) is read-only with no copy
    • Default arguments must be the rightmost parameters
    • Overloading reuses a name with different parameter lists — not just a different return type
    • ✅ A declaration/prototype lets you call a function defined later; inline mainly enables header definitions
    • Next lesson: Arrays & Vectors — store and process collections of data efficiently

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service