# 4.3 Functions

## 4.3 Functions

#### **Introduction to Modular Programming**

Functions are the fundamental building blocks for creating modular, reusable, and maintainable code in C programming. They allow a complex problem to be divided into smaller, manageable, and logically separate tasks (modules). Each function performs a specific operation, promoting code reusability (write once, use many times), reducing redundancy, and improving program organization and readability. This unit covers how to define, declare, call, and control the flow of data into and out of functions, including the powerful concept of recursion.

***

### 1. Defining and Calling Functions

#### 1.1 Function Definition

1. **Purpose**: To specify **what** the function does. It is the actual implementation of the function's task.
2. **Syntax**:

   ```c
   return_type function_name(parameter_list) {
       // Body of the function (statements)
       // ...
       return expression; // Optional, based on return_type
   }
   ```
3. **Components**:
   * **Return Type**: The data type of the value the function returns to the caller. Use `void` if the function does not return any value.
   * **Function Name**: A valid identifier following the rules for naming variables.
   * **Parameter List**: A comma-separated list of variable declarations (the function's *formal parameters*). These are placeholders for the input values the function will receive. Can be empty: `()`.
   * **Function Body**: The block of statements enclosed in `{ }` that defines the operations performed by the function.
   * **`return` Statement**:
     * Terminates the function's execution.
     * Returns control to the point where the function was called (the caller).
     * For non-`void` functions, it must be followed by an expression whose value is returned to the caller.

#### 1.2 Function Call

1. **Purpose**: To **execute** or *invoke* the function. This transfers program control to the function.
2. **Syntax**:

   ```c
   function_name(argument_list);
   ```

   * **Arguments (Actual Parameters)**: The actual values or variables passed to the function. They must match the number, order, and compatible types of the formal parameters in the function definition.
3. **Flow of Control**:
   1. When a function call is encountered, control jumps to that function's definition.
   2. The function body executes.
   3. When the function finishes (hits a `return` statement or the closing `}`), control returns to the point immediately following the call.

#### 1.3 Example: A Simple Function

```c
#include <stdio.h>

// 1. FUNCTION DEFINITION
// Function to calculate and return the square of a number
int square(int num) { // 'num' is a formal parameter
    int result = num * num;
    return result; // Returns an integer value
}

int main() {
    int x = 5;
    // 2. FUNCTION CALL
    int sq = square(x); // 'x' is the actual argument
    printf("Square of %d is %d\n", x, sq); // Output: Square of 5 is 25
    
    // Function call can also be used directly in an expression
    printf("Square of 3 is %d\n", square(3)); // Output: Square of 3 is 9
    
    return 0;
}
```

#### 1.4 Categories of Functions (Based on arguments and return value)

1. **Function with no arguments and no return value (`void`)**:

   ```c
   void printMessage() {
       printf("Hello from a function!\n");
       // No return statement needed, or just `return;`
   }
   // Call: printMessage();
   ```
2. **Function with arguments and no return value (`void`)**:

   ```c
   void printSum(int a, int b) {
       printf("Sum = %d\n", a + b);
   }
   // Call: printSum(10, 20);
   ```
3. **Function with no arguments but with a return value**:

   ```c
   int getNumber() {
       int n;
       printf("Enter a number: ");
       scanf("%d", &n);
       return n;
   }
   // Call: int value = getNumber();
   ```
4. **Function with arguments and a return value** (Most common):

   ```c
   int max(int a, int b) {
       if (a > b) return a;
       else return b;
   }
   // Call: int m = max(15, 8);
   ```

***

### 2. Function Prototypes (Declaration)

#### 2.1 Purpose of a Prototype

1. **Informs the Compiler**: Tells the compiler about the function's name, return type, and the number & types of its parameters **before** the function is actually defined or called.
2. **Enables Separate Compilation**: Allows functions to be defined in any order, even after `main()`, or in separate files.
3. **Enforces Type Checking**: The compiler checks function calls against the prototype to ensure the correct number and type of arguments are used. This helps catch errors early.

#### 2.2 Syntax of a Prototype

```c
return_type function_name(parameter_type_list);
```

* The **parameter names are optional** in the prototype (but specifying them is good practice for readability). Only the types are mandatory.
* It ends with a semicolon (`;`).

#### 2.3 Placement and Usage

Prototypes are typically placed **at the top of the source file**, before the `main()` function or any function calls.

```c
#include <stdio.h>

// FUNCTION PROTOTYPES (Declarations)
int square(int);        // Parameter name omitted
int max(int a, int b);  // Parameter names included (recommended)
void greet(void);       // For a function with no parameters

int main() {
    greet(); // Call is valid because compiler has seen the prototype
    int result = max(square(5), 10);
    printf("Result: %d\n", result);
    return 0;
}

// FUNCTION DEFINITIONS (can be after main)
void greet() {
    printf("Welcome!\n");
}

int square(int num) {
    return num * num;
}

int max(int a, int b) {
    return (a > b) ? a : b;
}
```

**Without the prototypes, calling `greet()`, `max()`, or `square()` before their definitions would result in a compilation warning/error.**

***

### 3. Argument Passing Mechanisms

How arguments (data) are transferred from the calling function to the called function is crucial. C supports two primary mechanisms.

#### 3.1 Call by Value

1. **Mechanism**: A **copy** of the argument's value is passed to the function's formal parameter.
2. **Consequence**: Any changes made to the formal parameter **inside the function do not affect** the original argument in the calling function.
3. **Use Case**: When you want to use the argument's value without modifying the original variable. This is the **default** method in C for simple data types (`int`, `float`, `char`, etc.).
4. **Example**:

   ```c
   void swap_by_value(int x, int y) {
       int temp = x;
       x = y;
       y = temp;
       printf("Inside function: x=%d, y=%d\n", x, y);
   }
   int main() {
       int a = 5, b = 10;
       swap_by_value(a, b); // Copies of a and b are sent
       printf("In main: a=%d, b=%d\n", a, b);
       return 0;
   }
   // Output:
   // Inside function: x=10, y=5
   // In main: a=5, b=10  <-- Original values UNCHANGED
   ```

#### 3.2 Call by Reference (Using Pointers)

1. **Mechanism**: The **memory address** (reference) of the argument is passed to the function. The formal parameter must be a **pointer**.
2. **Consequence**: Since the function now has the address of the original variable, any changes made using this pointer **directly affect** the original argument in the calling function.
3. **Use Case**: When you need a function to modify the original variables (e.g., swapping two numbers, reading into a variable, returning multiple values) or when passing large data structures (arrays, structs) efficiently (to avoid copying).
4. **Syntax**: The function's parameter is declared as a pointer. The caller passes the address using the **address-of operator (`&`)**.
5. **Example**:

   ```c
   void swap_by_reference(int *x, int *y) { // Pointers as parameters
       int temp = *x; // Dereference to get value
       *x = *y;       // Dereference to assign value
       *y = temp;
       printf("Inside function: *x=%d, *y=%d\n", *x, *y);
   }
   int main() {
       int a = 5, b = 10;
       swap_by_reference(&a, &b); // Addresses of a and b are sent
       printf("In main: a=%d, b=%d\n", a, b);
       return 0;
   }
   // Output:
   // Inside function: *x=10, *y=5
   // In main: a=10, b=5  <-- Original values ARE SWAPPED
   ```

#### 3.3 Comparison: Call by Value vs. Call by Reference

| Aspect                 | Call by Value                                 | Call by Reference (via Pointers)               |
| ---------------------- | --------------------------------------------- | ---------------------------------------------- |
| **What is passed**     | Copy of the value of the argument             | Address (memory location) of the argument      |
| **Effect on Original** | No change to the original argument            | Can change the original argument               |
| **Parameter Type**     | Ordinary variable (`int`, `float`, etc.)      | Pointer variable (`int *`, `float *`, etc.)    |
| **Argument in Call**   | Variable name or constant                     | Address of variable using `&` operator         |
| **Use Case**           | For input, when modification is not required  | For output/input-output, or for large data     |
| **Memory & Speed**     | Creates a copy; less efficient for large data | No copy of data; more efficient for large data |
| **Safety**             | Safe; original data is protected              | Risk of unintended side-effects if misused     |

***

### 4. Recursive Functions

#### 4.1 Concept of Recursion

1. **Definition**: A function that **calls itself** directly or indirectly to solve a problem.
2. **Principle**: It breaks down a complex problem into smaller, identical sub-problems until reaching a simple, non-recursive base case that can be solved directly.
3. **Analogy**: Similar to mathematical induction.

#### 4.2 Key Components of a Recursive Function

1. **Base Case (Terminating Condition)**:
   * A condition that stops the recursion.
   * It is a simple, solvable instance of the problem.
   * **Crucial**: Without a base case (or with an incorrect one), the recursion continues infinitely, leading to a stack overflow error.
2. **Recursive Case**:
   * The part where the function calls itself with a **modified (typically smaller)** argument, moving closer to the base case.

#### 4.3 How Recursion Works (The Call Stack)

1. Each recursive call creates a new activation record (stack frame) on the system's **call stack**.
2. This frame stores the function's parameters, local variables, and return address.
3. When the base case is reached, the calls begin to return, unwinding the stack one frame at a time.

#### 4.4 Classic Example: Factorial of a Number

```c
#include <stdio.h>

// Recursive function to calculate factorial
int factorial(int n) {
    // 1. Base Case
    if (n == 0 || n == 1) {
        return 1;
    }
    // 2. Recursive Case
    else {
        return n * factorial(n - 1); // Function calls itself
    }
}

int main() {
    int num = 5;
    printf("Factorial of %d is %d\n", num, factorial(num));
    return 0;
}
// Output: Factorial of 5 is 120
```

**Visualizing the Call Stack for `factorial(3)`**:

```
factorial(3)
  -> 3 * factorial(2)
        -> 2 * factorial(1)
              -> returns 1 (base case)
        -> returns 2 * 1 = 2
  -> returns 3 * 2 = 6
```

#### 4.5 Another Example: Fibonacci Sequence

```c
int fibonacci(int n) {
    if (n == 0) return 0;       // Base case 1
    else if (n == 1) return 1;  // Base case 2
    else return fibonacci(n - 1) + fibonacci(n - 2); // Recursive case
}
```

#### 4.6 Advantages and Disadvantages of Recursion

1. **Advantages**:
   * **Elegance and Clarity**: Code is often simpler, cleaner, and more intuitive for problems that have a natural recursive definition (e.g., tree traversals, Tower of Hanoi, factorial).
   * **Solves Complex Problems**: Ideal for problems with recursive structures (divide and conquer, backtracking).
2. **Disadvantages**:
   * **Performance Overhead**: Each function call involves overhead (pushing/popping stack frames), making it slower than iterative solutions for simple problems.
   * **Memory Usage**: Uses the call stack. Deep recursion can cause **stack overflow**.
   * **Debugging Difficulty**: Can be harder to trace and debug than iterative loops.

#### 4.7 When to Use Recursion?

* Use recursion when the problem definition is recursive (e.g., tree/graph algorithms, searching/sorting like quicksort/mergesort, fractal generation).
* Prefer iteration for simple linear problems (e.g., factorial, Fibonacci) where an iterative loop is straightforward and more efficient.

#### 4.8 Tail Recursion

A special case where the recursive call is the **last operation** in the function. Some compilers can optimize tail recursion into iteration, eliminating stack overhead.

```c
// Tail Recursive version of factorial (requires an accumulator)
int factorial_tail(int n, int accumulator) {
    if (n == 0) return accumulator;
    return factorial_tail(n - 1, n * accumulator); // Recursive call is last
}
// Initial call: factorial_tail(5, 1)
```

***

### 5. Scope and Lifetime of Variables in Functions

#### 5.1 Local Variables

* **Defined Inside** a function or block `{ }`.
* **Scope**: Only accessible within that function/block.
* **Lifetime**: Created when the function is called, destroyed when the function returns.
* **Storage**: Typically on the **stack**.

#### 5.2 Global Variables

* **Defined Outside** all functions (usually at the top of the file).
* **Scope**: Accessible from any function in the file after its declaration.
* **Lifetime**: Exists for the entire duration of the program.
* **Use with Caution**: Can lead to unintended side-effects and make code harder to debug. Generally avoided in favor of function parameters and return values.

#### 5.3 Static Local Variables

* Declared inside a function with the `static` keyword.
* **Scope**: Still local to the function (cannot be accessed from outside).
* **Lifetime**: Exists for the entire program duration. **Retains its value between function calls**.
* **Example - Function Call Counter**:

  ```c
  void counter() {
      static int count = 0; // Initialized only once
      count++;
      printf("Function called %d times.\n", count);
  }
  int main() {
      counter(); // Output: Function called 1 times.
      counter(); // Output: Function called 2 times.
      return 0;
  }
  ```

**Conclusion**: Functions are essential for structured programming. Defining functions with clear purposes, using prototypes for safety, choosing the correct argument passing mechanism (value vs. reference), and understanding recursion are key skills. Mastery of these concepts enables you to break down complex problems, write reusable code, and build robust, well-organized programs.
