C ProgrammingUnit 612 min read

Functions in C: Definition, Types, Scope, Recursion & Practical Use

Unit 6 of C Programming covers functions—how to define, call, and use them in programs, including parameter passing, scope rules, recursion, and real-world applications like transaction processing in eSewa or order routing in Daraz.

TAKEAWAYS:

  • Functions modularize code by breaking programs into reusable blocks with a single responsibility (e.g., calculateTax()).
  • Passing arguments by value vs. by reference changes how functions modify data (e.g., swap() needs pointers).
  • Scope rules (local vs. global) determine variable lifetime and accessibility (e.g., static variables retain values between calls).
  • Recursion solves problems like tree traversals or factorial calculations but risks stack overflow if not optimized.
  • Header files (#include) and function prototypes (int add(int, int);) enable code organization and compilation.
  • Memory efficiency: Functions reduce redundancy (e.g., validateUser() reused across login/logout).

1. What is a Function?

A function is a self-contained block of code that performs a specific task. It:

  • Takes inputs (parameters/arguments).
  • Executes statements.
  • Returns an output (optional).

Why use functions?

  • Reusability: Write once, call anywhere (e.g., login() in eSewa).
  • Modularity: Divide programs into manageable parts (e.g., processOrder() in Daraz).
  • Debugging: Isolate errors to a single function.
flowchart TD
    A["Main Program"] -->|"Calls"| B["Function: calculateTax(income)"]
    B -->|"Returns"| C["taxAmount"]
    A -->|"Uses"| C

2. Function Syntax and Components

A function has 5 key parts:

  1. Return type: Data type of the result (int, void, etc.).
  2. Function name: Identifier (e.g., calculateTax).
  3. Parameters: Inputs in parentheses (e.g., (float income)).
  4. Body: Code block { ... }.
  5. Return statement: return value;.

Example: Simple Function

// Function prototype (declaration)
float calculateTax(float income);

// Function definition
float calculateTax(float income) {
    float tax;
    if (income > 500000) {
        tax = income * 0.15;  // 15% tax for high income
    } else {
        tax = income * 0.10;  // 10% tax for low income
    }
    return tax;
}

Real-World Link: In eSewa, the processPayment() function takes userID and amount as parameters, validates them, deducts the tax using calculateTax(), and returns a transaction ID. This mirrors the example above but with additional security checks.


3. Types of Functions

Type Description Example
Library Functions Predefined in C standard libraries (e.g., printf(), strlen()). sqrt(25)
User-Defined Created by the programmer for specific tasks. validateUser(username, password)
Recursive A function calls itself (e.g., factorial, Fibonacci). factorial(n)
Void Functions Perform tasks but do not return a value. displayWelcomeMessage()

4. Passing Arguments: Call by Value vs. Call by Reference

Method How It Works Example Use Case
Call by Value A copy of the argument is passed. Changes inside the function are lost. void increment(int x) Safe for read-only operations.
Call by Reference The memory address of the argument is passed. Changes affect the original. void swap(int *a, int *b) Modifying arrays or large data.

Example: Swapping Two Numbers (Call by Reference)

#include <stdio.h>
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    swap(&x, &y);  // Pass addresses
    printf("x = %d, y = %d", x, y);  // Output: x = 10, y = 5
    return 0;
}

Visual Trace:

flowchart LR
    A["Before swap(x=5, y=10)"] --> B["swap(&x, &y)"]
    B --> C["temp = *a (5)"]
    B --> D["*a = *b (10)"]
    B --> E["*b = temp (5)"]
    E --> F["After swap(x=10, y=5)"]

Real-World Link: In Pathao’s ride-matching system, the assignDriver() function uses call by reference to update the driverLocation and passengerLocation arrays directly, ensuring real-time GPS updates without copying large data.


5. Scope of Variables

Variables can be:

  1. Local: Exist only within the function.
  2. Global: Accessible throughout the program (use sparingly!).
  3. Static: Retain value between function calls.
Scope Lifetime Accessibility Example
Local Function call Only within the function. int sum = 0; in calculateSum()
Global Entire program Anywhere in the program. int totalUsers; at file scope.
Static Program duration Only within the function. static int count = 0;

Example: Static Variable

#include <stdio.h>
void counter() {
    static int count = 0;  // Retains value between calls
    count++;
    printf("Count: %d\n", count);
}

int main() {
    counter();  // Output: 1
    counter();  // Output: 2
    return 0;
}

Visual Trace:

count=1 (static)count=2 (static)TOP
Static variable retains value across function calls (scope example)

Real-World Link: Ncell’s call counter uses a static variable to track the number of failed login attempts per user. This persists across multiple login() calls without resetting.


6. Recursion

A function that calls itself to solve smaller instances of the same problem.

Base Case: Stops the recursion (e.g., factorial(0) = 1). Recursive Case: Breaks the problem into smaller subproblems.

Example: Factorial

#include <stdio.h>
int factorial(int n) {
    if (n == 0)  // Base case
        return 1;
    else
        return n * factorial(n - 1);  // Recursive call
}

int main() {
    printf("Factorial of 5: %d", factorial(5));  // Output: 120
    return 0;
}

Visual Trace (Stack Frames):

factorial(5)factorial(4)factorial(3)factorial(2)factorial(1)factorial(0)=1TOP
Recursive call stack for factorial(5) (base case at bottom)

Real-World Link: NEPSE’s stock price prediction uses recursive algorithms to analyze historical data patterns. For example, calculating the n-th Fibonacci number (common in technical analysis) is done recursively:

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

Warning: Recursion can cause stack overflow for large n (e.g., fibonacci(1000)). Use iteration or memoization for optimization.


7. Function Prototypes and Header Files

Function Prototype: Declares a function before its definition (helps compilation).

// Prototype (in header file or before main)
int add(int a, int b);

```figure
{"type":"tree","root":{"v":"main.c","children":[{"v":"header.h","children":[{"v":"function_prototype()"},{"v":"constant_definitions"}]},{"v":"main()","children":[{"v":"include header.h"},{"v":"function_call()"},{"v":"printf()"}]}]},"caption":"File structure for prototypes and header usage"}

// Definition (later in the code) int add(int a, int b) { return a + b; }

Header Files: Store prototypes and constants (e.g., #include "math_utils.h"). Example:

// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int add(int, int);
float divide(float, float);
#endif

Real-World Link: Daraz’s order processing system uses header files to separate:

  • order.h: Prototypes for placeOrder(), cancelOrder().
  • order.c: Definitions of these functions.
  • main.c: Calls these functions without needing to see their implementations.

8. Advantages and Disadvantages of Functions

Advantages Disadvantages
Code reusability (e.g., login() in multiple apps). Overhead: Function calls add slight performance cost.
Modularity: Easier to debug/test. Complexity: Recursion can be hard to follow.
Security: Limit access to critical functions (e.g., adminOnly()). Scope issues: Misusing globals can cause bugs.
Teamwork: Different programmers can work on separate functions. Memory: Deep recursion may cause stack overflow.

9. Common Pitfalls and Best Practices

  1. Uninitialized Variables: Always initialize local variables (e.g., int sum = 0;).
  2. Incorrect Return Types: Ensure void functions don’t return values.
  3. Passing Wrong Arguments: Mismatched types (e.g., passing float to int function).
  4. Infinite Recursion: Forgetting the base case (e.g., factorial(-1)).
  5. Global Variable Overuse: Prefer local/static variables to avoid side effects.

Best Practices:

  • Use descriptive names (e.g., calculateVAT() instead of calc()).
  • Limit function size to ~20-30 lines for readability.
  • Comment prototypes to explain parameters and return values.

In the Real World

  1. eSewa’s Transaction System:

    • Idea Used: Modular functions (validateUser(), deductAmount(), generateReceipt()).
    • How: Each function handles a specific step (e.g., deductAmount() takes userID and amount, checks balance, and returns success/failure). Call by reference ensures the user’s balance is updated directly.
  2. Pathao’s Driver-Passenger Matching:

    • Idea Used: Recursion + Call by Reference.
    • How: The findNearestDriver() function recursively checks drivers within a radius, updating the driverLocation array via pointers. This avoids copying large GPS data.
  3. Ncell’s Billing System:

    • Idea Used: Static variables + Functions.
    • How: The checkSMSLimit() function uses a static int usedSMS; to track usage across calls, ensuring accurate billing without resetting between function invocations.
  4. Daraz’s Order Queue:

    • Idea Used: Function prototypes + Header files.
    • How: order.h declares processOrder(), cancelOrder(), and updateInventory(), while order.c defines them. This separation lets the team update order logic without breaking the main program.

Exam Tip

  1. Function Definition vs. Prototype:

    • Definition: Includes the body (e.g., int add(int a, int b) { return a + b; }).
    • Prototype: Only declares (e.g., int add(int, int);).
    • Exam Trick: If asked to "write a function," provide the full definition. If asked to "declare," give the prototype.
  2. Tracing Recursion:

    • Always show the stack frames (like the factorial example above). Examiners love step-by-step traces.
    • Common Mistake: Forgetting the base case. Always include it in your answer.
  3. Call by Value vs. Reference:

    • Value: Changes inside the function are lost (e.g., swap(int a, int b) won’t work).
    • Reference: Use pointers (swap(int *a, int *b)) to modify original data.
    • Exam Question: "Why does this code not swap x and y?" → Answer: Call by value (no pointers).
  4. Static Variables:

    • Key Point: Retain value between calls but are local to the function.
    • Exam Question: "What will this program print?" (with a static counter) → Trace the retained value.
  5. Header Files:

    • Why Use Them?: To avoid redefining functions and for code organization.
    • Exam Tip: If asked to "organize this code," split functions into .h and .c files.
  6. Real-World Scenarios:

    • Bank Loan Interest: Write a function calculateEMI(float principal, float rate, int years) and trace it for a loan of ₹5,00,000 at 8% for 5 years.
    • Traffic Light Control: Use a void changeLight() function with static int currentLight to cycle through red/green/yellow.

Pro Tip: For practical exams, always draw the stack frames for recursive functions and show variable changes in tables for call-by-reference examples. This visual approach guarantees full marks.

Based on the TU BIM syllabus for C Programming (IT232), unit 6.

Discussion

Loading…