Notifications

No notifications

/Phase 2

Functions, Overloading & Lambdas

Functions — Reusable Blocks of Logic 🧩

A function packages logic behind a name and a signature. C++ adds three powers C doesn't have: overloading, default arguments, and lambdas.

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

int main() { cout << add(2, 3); // call site }

Pass-by-Value vs Pass-by-Reference

void byValue(int x)        { x = 99; }   // local copy
void byRef  (int& x)       { x = 99; }   // modifies caller
void byCRef (const int& x) { /* read */ } // read-only, no copy

> Rule of thumb: small types → by value; big types → const T&; need to modify → T&.

Default Arguments

void greet(string name, string greeting = "Hello") {
    cout << greeting << ", " << name << "!\n";
}
greet("Asha");                  // Hello, Asha!
greet("Asha", "Hi");            // Hi, Asha!

Overloading — Same Name, Different Signature

int    area(int side)              { return side * side; }
double area(double r)              { return 3.14 * r * r; }
int    area(int w, int h)          { return w * h; }

The compiler picks the right one from argument types.

Lambdas — Anonymous Inline Functions

auto square = [](int x) { return x * x; };
cout << square(5);              // 25

vector<int> v{3,1,4,1,5}; sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // descending

On this page

Detailed Theory

Functions are how you give a name to *what* something does. In C++ they're cheap, fast, and very flexible.

Anatomy

returnType name(paramType1 p1, paramType2 p2, ...) {
    // body
    return value;     // omit if returnType is void
}

int max3(int a, int b, int c) {
    int m = a;
    if (b > m) m = b;
    if (c > m) m = c;
    return m;
}

Declaration vs Definition

A declaration announces the function exists; a definition provides the body.

int add(int, int);                       // declaration (a.k.a. prototype)

int main() { return add(2, 3); }

int add(int a, int b) { return a + b; } // definition (anywhere later)

In real projects: declarations go in .h/.hpp headers, definitions in .cpp source files.

Parameter Passing

StyleSyntaxWhen to use
By valueint xCheap small types (int, double, char, pointer)
By referenceint& xFunction needs to modify the caller's variable
By const referenceconst T& xBig types you only read (string, vector, classes)
By pointerint* xOptional argument (nullptr allowed)

void scale(vector<int>& v, int factor) {     // modifies caller
    for (int& x : v) x *= factor;
}

void print(const vector<int>& v) { // read-only, no copy for (int x : v) cout << x << ' '; }

> Avoid passing big things by value — you pay the copy cost. const T& is essentially free.

Return Values

A function returns by value by default — and the compiler optimises this aggressively (RVO/NRVO):

vector<int> makeRange(int n) {
    vector<int> v;
    for (int i = 0; i < n; i++) v.push_back(i);
    return v;            // no copy in modern C++ (move/RVO)
}

You can also return references:

int& at(vector<int>& v, int i) { return v[i]; }
at(v, 3) = 99;          // modifies v[3]

⚠️ Never return a reference to a local — it dies when the function returns.

Default Arguments

double power(double base, int exp = 2) { /* ... */ }
power(3.0);            // exp defaults to 2
power(3.0, 5);

Default values must come from the right of the parameter list. Put defaults in the declaration, not the definition (when split into header/source).

Function Overloading

Multiple functions can share a name if their parameter signatures differ.

void log(int x)            { cout << "int: "    << x; }
void log(double x)         { cout << "double: " << x; }
void log(const string& s)  { cout << "string: " << s; }

Overload resolution picks the best match at compile time. Return type alone cannot overload:

int    f();
double f();   // ❌ ambiguous — same parameter list

inline & constexpr Functions

inline int double_it(int x) { return x * 2; }   // hint to inline (also: avoids ODR errors in headers)

constexpr int factorial(int n) { // can be evaluated at compile time return (n <= 1) ? 1 : n * factorial(n - 1); } constexpr int F5 = factorial(5); // computed at compile time

Lambdas — The Modern Star ⭐

A lambda is an anonymous function you can write inline:

auto add = [](int a, int b) { return a + b; };
cout << add(3, 4);          // 7

Capture List

int x = 10;
auto f1 = [x](int y)  { return x + y; };          // capture by value
auto f2 = [&x](int y) { x += y; return x; };      // capture by reference
auto f3 = [=](int y)  { return x + y; };          // capture all-by-value
auto f4 = [&](int y)  { x += y; return x; };      // capture all-by-reference

Lambdas Shine With STL Algorithms

vector<int> v{5, 1, 4, 2, 3};

sort(v.begin(), v.end(), [](int a, int b){ return a > b; });

int evens = count_if(v.begin(), v.end(), [](int x){ return x % 2 == 0; });

for_each(v.begin(), v.end(), [](int x){ cout << x << ' '; });

Recursion

Functions can call themselves. Just make sure the recursion ends.

int fib(int n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}

Common Pitfalls

BugCause
Slow for big argsForgot const T& — copying entire vectors
Dangling referenceReturning reference to local variable
Ambiguous overloadTwo equally-good matches
Lambda outlives captureCaptured &local after function returned

Cheat-Sheet

NeedIdiom
Read big argvoid f(const T& x)
Modify callervoid f(T& x)
Optional 2nd argvoid f(int a, int b = 0)
Same name, different typesoverload f(int), f(double)
Inline anonymous fnauto g = [](int x){ return x*x; };
Compile-time fnconstexpr int f(int n) { ... }

Functions are how you grow a program. The next topic — strings — is your first real-world use of all this.