C++ Cheatsheet
Functions
Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Declaration and Definition
// Declaration (prototype) — in header int add(int a, int b); // Definition — in .cpp int add(int a, int b) { return a + b; } // Combined declaration + definition double square(double x) { return x * x; } // Void function void greet(const std::string& name) { std::cout << "Hello, " << name << "\n"; }
Parameters
// Pass by value (copy) void byVal(int x) { x = 99; } // caller's variable unchanged // Pass by reference (alias to caller's variable) void byRef(int& x) { x = 99; } // caller's variable IS changed // Pass by const reference (read-only, no copy) void byConstRef(const std::string& s) { /* s unmodifiable */ } // Pass by pointer void byPtr(int* p) { *p = 99; } // caller: byPtr(&x); void byConstPtr(const int* p) { /* *p unmodifiable */ } // Move semantics (avoid unnecessary copy of expensive objects) void takesVec(std::vector<int> v) { /* owns the data */ } // caller: takesVec(std::move(myVec));
Rule of thumb: pass small/trivial types by value; pass large objects by const&; pass when ownership is transferred by value with std::move.
Default Arguments
void log(const std::string& msg, int level = 1, bool newline = true); log("hello"); // level=1, newline=true log("hello", 2); // level=2, newline=true log("hello", 3, false); // Defaults must trail non-defaulted params. // Define defaults only in the declaration (not definition in separate TU).
Return Values
int sum(int a, int b) { return a + b; } // Return by reference (must outlive the function!) int& getElement(std::vector<int>& v, int i) { return v[i]; } getElement(v, 0) = 99; // assign through reference // Return multiple values std::pair<int, int> divmod(int a, int b) { return {a / b, a % b}; } auto [q, r] = divmod(10, 3); // C++17 // std::tuple for more than two std::tuple<int, double, std::string> multi() { return {1, 3.14, "hi"}; } auto [a, b, c] = multi(); // Named Return Value Optimization (NRVO) — compiler elides copy: std::vector<int> makeVec() { std::vector<int> v = {1, 2, 3}; return v; // usually no copy (NRVO) }
Function Overloading
int max(int a, int b) { return a > b ? a : b; } double max(double a, double b) { return a > b ? a : b; } int max(int a, int b, int c){ return max(max(a, b), c); } // Overloads must differ by parameter types or count (NOT return type alone) // void f(int); and int f(int); — NOT valid overloads
inline, constexpr, [[nodiscard]]
// inline: hint to expand at call site; also suppresses ODR error for // definitions in headers (modern headers with definitions use inline) inline int inc(int x) { return x + 1; } // constexpr: evaluated at compile time when args are compile-time constants constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); } constexpr int f6 = factorial(6); // 720, computed at compile time int runtime = factorial(n); // also works at runtime // consteval (C++20): MUST be evaluated at compile time consteval int sq(int x) { return x * x; } // [[nodiscard]]: warn if caller ignores the return value [[nodiscard]] int readFile(const std::string& path); // [[nodiscard("reason")]] (C++20)
Variadic Functions
// C-style variadic (avoid; use variadic templates instead) #include <cstdarg> int sumAll(int count, ...) { va_list ap; va_start(ap, count); int total = 0; for (int i = 0; i < count; ++i) total += va_arg(ap, int); va_end(ap); return total; } // Variadic templates (C++11, type-safe) template<typename... Args> void print(Args... args) { (std::cout << ... << args); // fold expression (C++17) } print(1, " ", 2.5, " ", "hello"); // Fold expressions (C++17) template<typename... Ts> auto sum(Ts... vals) { return (... + vals); } // left fold: ((a + b) + c)
Function Pointers
// Declare: return_type (*name)(param_types) int (*fp)(int, int) = add; int result = fp(2, 3); // call through pointer // Simplify with alias using BinaryOp = int(*)(int, int); BinaryOp op = add; // Passing function pointers void apply(int x, int y, int (*op)(int, int)) { std::cout << op(x, y) << "\n"; } apply(3, 4, add); // Array of function pointers int (*ops[])(int, int) = {add, sub, mul}; ops[0](1, 2);
std::function (Type-Erased Callable)
#include <functional> std::function<int(int, int)> f = add; // function pointer f = [](int a, int b){ return a + b; }; // lambda f = std::bind(add, std::placeholders::_1, 10); // partial application // Useful for storing heterogeneous callables; slight runtime overhead vs. templates
std::bind and Partial Application
#include <functional> int add(int a, int b) { return a + b; } auto add5 = std::bind(add, std::placeholders::_1, 5); add5(3); // 8 // Prefer lambdas (clearer) auto add5L = [](int a){ return add(a, 5); };
Recursion
// Direct recursion int fib(int n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); // exponential without memoization } // Tail-recursive style (compiler may optimize, not guaranteed in C++) int factorial(int n, int acc = 1) { if (n <= 1) return acc; return factorial(n - 1, n * acc); } // Memoization with static or std::unordered_map #include <unordered_map> int fibMemo(int n) { static std::unordered_map<int, int> memo; if (n <= 1) return n; if (memo.count(n)) return memo[n]; return memo[n] = fibMemo(n-1) + fibMemo(n-2); }
Argument-Dependent Lookup (ADL / Koenig Lookup)
namespace Lib { struct Widget {}; void process(Widget w); // found via ADL } Lib::Widget w; process(w); // OK — compiler searches Lib:: because w is of type Lib::Widget
Special Qualifiers
// noexcept: promise not to throw (enables optimizations) int safe() noexcept { return 42; } void maybe() noexcept(false); // same as default void alwaysNoexcept() noexcept(true); // noexcept conditional on expression template<typename T> void f(T x) noexcept(noexcept(T{x})); // noexcept if copy-construct is noexcept // [[maybe_unused]] — suppress unused-parameter warning void callback(int value, [[maybe_unused]] int unused) { use(value); } // Deleted functions void noAlloc() = delete; // call is compile error
Common Pitfalls
- Returning a reference to a local variable — undefined behavior (local is destroyed on return).
- Default argument not in declaration — put defaults only in the declaration visible to callers.
- Overload resolution ambiguity — calling
f(0)when bothf(int)andf(double*)exist. - Passing array by value decays to pointer — use
std::spanor template on size.
// Wrong: void f(int arr[10]); // arr is actually int* // Right: void f(std::span<int> arr); // C++20 template<std::size_t N> void f(int (&arr)[N]); // reference to array of known size