CODE: C CPP Functions, and Functions Types

Functions are reusable blocks of code that perform a specific task.While C focuses on procedural functions and direct memory access, C++ builds on that and introduces object-oriented programming, type safety, and modern callable features.

Understanding Functions in C and C++

A program without functions is like a machine without modules: difficult to maintain, impossible to scale, and fragile to change.

Functions are the primary abstraction unit in both C and C++, allowing developers to divide complex systems into understandable, reusable, and testable components.

At the lowest level, a function is simply a controlled jump in execution with an agreed contract for inputs, outputs, and preserved state. At higher levels, especially in C++, functions become expressive tools for abstraction, safety, and performance.


What is a Function?

A function is a reusable block of code that performs a specific task and can optionally return a value to its caller.

c
int add(int a, int b) {  // Definition
    return a + b;
}

int result = add(3, 5);  // result = 8

When you use functions

  • When logic must be reused
  • When behavior should be isolated
  • When code must be testable or replaceable

Why functions exist

  • Reduce duplication
  • Improve readability
  • Enable modular design
  • Allow separate compilation and linking

In embedded systems, functions also define hardware boundaries (drivers, ISRs, callbacks).

In large C++ systems, they define interfaces, contracts, and behaviors.


Function Declaration vs Definition (C)

In C and C++, a function may be declared before it is defined.

c
int multiply(int x, int y); // Declaration (prototype)

int multiply(int x, int y) { // Definition
    return x * y;
}

A declaration tells the compiler:

  • The function name
  • Parameter types
  • Return type

A definition provides:

  • The executable logic
  • The actual implementation

This separation enables:

  • Header/source file organization
  • Cross-file compilation
  • Faster build times
  • Clear API boundaries
ConceptPurposeWhy it matters
DeclarationIntroduces functionEnables early usage
DefinitionImplements functionProduces executable code

Function Parameters & Return Types

Functions communicate with callers through parameters and return values.

PatternExampleWhen to useWhy
No returnvoid reset()Action-only logicNo result needed
Scalar returnint status()Status codesSimple signaling
Floating returnfloat temp()Sensor dataPrecision
Pointer parametervoid write(int* p)Modify caller dataEfficiency
Struct parametervoid draw(Point p)Grouped dataClarity

cpp
void updateValue(int* value) {
    *value += 1;
}

Passing pointers enables:

  • In-place modification
  • Reduced copying
  • Direct memory interaction

Functions in C (also valid in C++)

Functions in Header Files

c
// math.h
int add(int, int);
float readTemp();

Headers expose interfaces, not implementations.

Why this matters

  • Allows independent compilation
  • Enforces modular design
  • Essential for embedded drivers and portable libraries

Standard (Named) Functions

c
int add(int a, int b) {
    return a + b;
}

This is the backbone of C programming.

Use when

  • The function has a clear responsibility
  • Logic will be reused
  • Debugging clarity matters

`void` Functions (No Return)

Void functions emphasize side effects, not results.

c
void triggerRelay() {
    printf("Relay triggered\n");
}

When: Hardware control, logging, state changes, UI triggers.

Why: No output needed, minimal footprint.


Parameterized Functions

They make behavior data-driven, enabling flexible logic without duplication.

c
void setVoltage(float volt) {
    printf("Voltage: %f\n", volt);
}

When: Function behavior depends on input (sensor value, config, etc.)

Why: Makes logic dynamic and reusable.


Recursive Functions

Recursion mirrors problem structure, not machine efficiency.

c
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

When: Tree traversal, divide-and-conquer, math sequences, parsing.

Why: Natural for nested repeated problems.

Must include a base case to avoid stack overflow.

Variadic Functions (`...`) using `va_list`

When argument count is unknown at compile time

c
#include <stdarg.h>

int sum(int count, ...) {
    va_list args;
    va_start(args, count);

    int total = 0;
    for (int i = 0; i < count; ++i)
        total += va_arg(args, int);

    va_end(args);
    return total;
}

When: Unknown number of arguments at runtime (printf-style APIs).

Why: Flexibility in input count.

Not type-safe, prefer C++ templates when possible.

Function Pointers

Functions can be treated as data.

c
void reboot(void) {
    printf("Rebooting...\n");
}

void (*fn)(void) = reboot;
fn();

When: Callbacks, schedulers, drivers, passing behavior as parameter.

Why: Allows functions to be treated as data.


Callback Functions (Pointer-based)

Callbacks decouple what happens from when it happens, essential in embedded and event-driven systems.

c
void execute(void (*callback)(void)) {
    callback();
}

execute(reboot);

When: Event-driven systems, embedded tasks, strategy injection.

Why: Decouples logic, increases modularity.


`static` Functions (File-private)

static limits visibility to the translation unit.

c
static int internalCounter(void) {
    return 7;
}

When: Utility function used only inside this file.

Why: Prevents symbol collisions — good for portable codebases.

  • Prevents symbol collisions
  • Improves encapsulation
  • Enables compiler optimizations

`extern` Function Declaration (Cross-file)

Used to expose functions across compilation units while keeping implementation separate.

c
// api.h
extern void sharedBoot(void);

When: Multi-file modular systems.

Why: Enables separation between interface and implementation.


Functions using `struct`, `union`, `enum`

These allow structured data exchange without global state.

c
struct Point {
    int x;
    int y;
};

void printPoint(struct Point p) {
    printf("%d, %d\n", p.x, p.y);
}

When: Passing grouped data or system states.

Why: Organizes complex inputs/outputs.


Functions in C++

C++ extends C’s procedural model into object-oriented, generic, and functional paradigms.

Member Functions

C++98/03 - Member Functions in Classes/Structs

cpp
struct LED {
    void on() {
        std::cout << "ON\n";
    }
};

Member functions bind behavior to data, enabling object modeling.

When: Encapsulate behavior with data.

Why: Better organization than C, models real objects.


C++11 - Default Parameters

Reduces overload clutter and simplifies function usage.

cpp
void boot(int delay = 5);

When: Optional arguments exist.

Why: Simplifies usage.


C++11 - `inline` (header-safe, stronger than C)

Encourages the compiler to remove call overhead for small, hot functions.

cpp
inline int square(int x) { return x*x; }

When: Small, frequent calls.

Why: Removes call overhead.


Lambda Functions

C++11 - Lambda Functions ([](){}) (the anonymous function) - without name

Lambdas enable local behavior definition.

cpp
auto task = [](){ std::cout << "Run\n"; };

When: Local short logic, callbacks, comparators.

Why: No need for separate function, cleaner.

Captures:

cpp
int x = 10;
auto f1 = [x](){};
auto f2 = [&x](){ x++; };

Ideal for callbacks, STL algorithms, and event handlers.


`std::function`

C++11 - std::function Wrapper, a type-erased wrapper for any callable entity.

cpp
std::function<void()> task = reboot;

When: Store any callable (lambda, pointer, functor).

Why: Flexible strategy systems, good for modular IoT/SaaS.

std::function vs function pointer:

std::function is a flexible, type-erased wrapper for _any_ callable (functions, lambdas, functors) with a matching signature, offering runtime polymorphism but with overhead, while a function pointer is a simple, direct address to a _specific_ function, offering speed and compile-time efficiency but limited to bare functions or non-capturing lambdas.


Function Overloading

C++11 - Function Overloading (Compile-time)

Overloading is a type of compile-time polymorphism where multiple methods share the same name but have different parameters (number or type), allowing the compiler to choose the right one

cpp
int add(int, int);
double add(double, double);

When: Same operation with different input types.

Why: Cleaner APIs, less naming noise.

you **cannot** overload a method by changing only its return type

Function Override

C++11 - Function Overriding (Runtime Polymorphism)

C++11 - Function Override Safety Keyword

Replace base class behavior at runtime.

cpp
void start() override;

When: Ensure you're overriding a virtual function.

Why: Prevents mistakes, Flexible architectures.


Virtual Functions

C++11 - virtual Functions (Supports Override → Runtime)

Enable runtime polymorphism.

cpp
class Device {
public:
    virtual void start();
};

When: Abstraction layers, plugin device drivers, polymorphic systems.

Why: Runtime decision which function to execute.


Member Function Pointers

C++11+ - Member Function Pointers

Used in advanced frameworks, state machines, and reflection-like systems.

cpp
void (LED::*methodPtr)() = &LED::on;

When: Advanced decoupled class method calls.

Why: Low-level abstraction.


Template Functions

C++11+ - Template Functions

Templates provide compile-time polymorphism with zero runtime cost.

cpp
template<typename T>
T maxVal(T a, T b) {
    return (a > b) ? a : b;
}

When: Generic scalable logic.

Why: Type safe, reusable for all types.


Variadic Templates

C++11+ - Variadic Templates (Safer than C ...)

Type-safe alternative to C variadic functions.

cpp
template<typename... Args>
auto sum(Args... args) { return (args + ...); }

When: Accept multiple args safely.

Why: Type safe, compiler checked.


Threaded Functions

C++11+ - Threaded Functions

Encapsulates parallel execution safely and portably.

cpp
std::thread t(runTask);
t.join();

When: Parallel execution.

Why: Better abstraction than C threads.


Coroutines

C++20 - Coroutines

Enable async flows without thread overhead.

cpp
generator<int> counter() {
    co_yield 1;
}

When: Async generators, cooperative tasks.

Why: Modern async without threads.


C++17 - `noexcept`

cpp
void safeBoot() noexcept;

When: Critical safe execution.

Why: Guarantees no exceptions.


C++11 - `final`

cpp
virtual void start() final;

When: Prevent further overrides.

Why: Protects core behavior.


C++11 — Deleted Functions

cpp
void connect() = delete;

When: Prevent unsafe or unwanted usage.

Why: API protection.

Functors (`operator()`)

cpp
class Double {
public:
    int operator()(int x) { return x*2; }
};

When: Object behaves like a function.

Why: Works with STL.

Friend Functions

cpp
friend void debug(Device&);

When: Debugging or internal access.

Why: Access private members safely.

Final Summary

Function TypePure CC++ Added Version
Standard / named✔ inherited
void✔ inherited
Recursive✔ inherited + recursive lambda
Variadic ...✔ inherited
Function pointer✔ inherited
Callback✔ inherited + lambdas/std::function
static/extern✔ inherited
Member methods✔ C++98+
Overload✔ C++98+
Override✔ C++11+ runtime
Overwrite/hide✔ limited✔ full support
Lambda✔ C++11+
Templates✔ C++11+ / 17 / 20
Coroutine✔ C++20
constexpr✔ C++11/14/17
noexcept/final/delete✔ C++11+

When to use which language?

Use **C** when:

  • Writing procedural embedded drivers
  • Minimal runtime environment
  • Direct hardware callback injection

Use **C++ when**:

  • Building scalable systems (like VarThings IoT/SaaS)
  • Modeling devices as objects
  • Writing type-safe APIs
  • Using modern callbacks (lambdas), templates, coroutines
  • Needing overload + override behavior