Last 30 Days
No notifications
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
}
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&.
void greet(string name, string greeting = "Hello") {
cout << greeting << ", " << name << "!\n";
}
greet("Asha"); // Hello, Asha!
greet("Asha", "Hi"); // Hi, Asha!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.
auto square = [](int x) { return x * x; };
cout << square(5); // 25vector<int> v{3,1,4,1,5};
sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // descending
Functions are how you give a name to *what* something does. In C++ they're cheap, fast, and very flexible.
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;
}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.
| Style | Syntax | When to use |
| By value | int x | Cheap small types (int, double, char, pointer) |
| By reference | int& x | Function needs to modify the caller's variable |
| By const reference | const T& x | Big types you only read (string, vector, classes) |
| By pointer | int* x | Optional 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.
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.
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).
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 listinline 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
A lambda is an anonymous function you can write inline:
auto add = [](int a, int b) { return a + b; };
cout << add(3, 4); // 7int 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-referencevector<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 << ' '; });
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);
}| Bug | Cause | |||
| Slow for big args | Forgot const T& — copying entire vectors | |||
| Dangling reference | Returning reference to local variable | |||
| Ambiguous overload | Two equally-good matches | |||
| Lambda outlives capture | Captured &local after function returned | Cheat-Sheet | Need | Idiom |
| Read big arg | void f(const T& x) | |||
| Modify caller | void f(T& x) | |||
| Optional 2nd arg | void f(int a, int b = 0) | |||
| Same name, different types | overload f(int), f(double) | |||
| Inline anonymous fn | auto g = [](int x){ return x*x; }; | |||
| Compile-time fn | constexpr int f(int n) { ... } |
Functions are how you grow a program. The next topic — strings — is your first real-world use of all this.