CSE101 Unit 3 Notes

User-Defined Functions and Storage Classes

This unit focuses on the creation and utilization of functions in C programming, as well as the different storage classes that define the scope, visibility, and lifetime of variables. Mastering these concepts will enhance your ability to write modular, efficient, and maintainable code.

Functions in C

Functions are the building blocks of a C program. They allow you to encapsulate code for specific tasks, improving modularity and reusability.

Introduction to Functions

Definition

  • Function: A self-contained block of code that performs a specific, well-defined task. Functions can take input, process it, and return an output.

Purpose

  • Modularity: Breaks down complex problems into smaller, manageable pieces.
  • Reusability: Functions can be called multiple times from different parts of a program, reducing code duplication.
  • Maintainability: Easier to debug and update code organized into functions.

Types of Functions

  1. Library Functions: Predefined functions provided by C's standard libraries.

    • Examples: printf(), scanf(), sqrt(), strlen().
    • Located in header files like <stdio.h>, <math.h>, <string.h>.
  2. User-Defined Functions: Functions created by the programmer to perform specific tasks.

Function Prototypes

Definition

  • A function prototype declares a function's name, return type, and parameters to the compiler before its actual definition.
  • It informs the compiler about the function's interface, enabling type checking during function calls.

Syntax

return_type function_name(parameter_list);
  • return_type: Data type of the value the function returns (use void if no value is returned).
  • function_name: Identifier used to call the function.
  • parameter_list: Types and names of the parameters passed to the function (can be empty).

Purpose

  • Ensures the compiler knows about the function before it's used.
  • Allows functions to be called before their definitions in the code.
  • Helps the compiler catch mismatches in return types and parameter lists.

Example

// Function prototype
int add(int a, int b);

int main() {
    int sum = add(5, 3);
    printf("Sum: %d\n", sum);
    return 0;
}

// Function definition
int add(int a, int b) {
    return a + b;
}

Function Definition

Components

  1. Return Type: Specifies the data type of the value the function returns.
  2. Function Name: The identifier used to call the function.
  3. Parameter List: Variables that receive values when the function is called.
  4. Function Body: Code enclosed within {}, containing declarations and statements that define what the function does.

Syntax

return_type function_name(parameter_list) {
    // Declarations and statements
    return value; // If not void
}

Example

// Function definition
float multiply(float x, float y) {
    float product = x * y;
    return product;
}

int main() {
    float result = multiply(4.5, 2.0);
    printf("Product: %.2f\n", result);
    return 0;
}

Function Calls

Syntax

function_name(argument_list);
  • argument_list: The values or variables passed to the function parameters.

Passing Arguments

There are two primary ways to pass arguments to functions in C:

  1. Pass by Value:

    • Copies the value of each argument into the function's parameters.
    • Modifications inside the function do not affect the original variables.

    Example:

    void displayValue(int num) {
        num = num * 2; // Modifies local copy
        printf("Inside function: %d\n", num);
    }
    
    int main() {
        int val = 5;
        displayValue(val);
        printf("Outside function: %d\n", val); // val remains 5
        return 0;
    }
  2. Pass by Reference (Using Pointers):

    • Passes the memory address of the argument to the function.
    • Allows the function to modify the original variable.

    Example:

    void increment(int *num) {
        (*num)++; // Dereferences pointer and increments value
    }
    
    int main() {
        int val = 5;
        increment(&val); // Passes address of val
        printf("Incremented Value: %d\n", val); // val is now 6
        return 0;
    }

Function Call Example

int sum(int a, int b) {
    return a + b;
}

int main() {
    int x = 10;
    int y = 20;
    int result = sum(x, y); // Function call
    printf("Sum: %d\n", result);
    return 0;
}

Math Library Functions

Header File

  • To use math functions, include the <math.h> header file.
    #include <math.h>

Common Functions

  1. sqrt(double x):

    • Computes the square root of x.
    • Example:
      double root = sqrt(16.0); // root = 4.0
  2. pow(double base, double exponent):

    • Raises base to the power of exponent.
    • Example:
      double result = pow(2.0, 3.0); // result = 8.0
  3. fabs(double x):

    • Returns the absolute value of x (floating-point number).
    • Example:
      double absValue = fabs(-5.5); // absValue = 5.5
  4. abs(int n) (Requires <stdlib.h>):

    • Returns the absolute value of n (integer).
    • Example:
      int absInt = abs(-10); // absInt = 10
  5. Trigonometric Functions:

    • sin(double x)
    • cos(double x)
    • tan(double x)
    • Input angle x is in radians.
  6. Exponential and Logarithmic Functions:

    • exp(double x): Computes e^x.
    • log(double x): Natural logarithm (base e).
    • log10(double x): Common logarithm (base 10).

Usage

  • Include Math Library: Always include <math.h>.
  • Linking Math Library: When compiling, link the math library using the -lm option.
    • Example:
      gcc program.c -o program -lm

Example Program

#include <stdio.h>
#include <math.h>

int main() {
    double angle_degree = 30.0;
    double angle_radian = angle_degree * (M_PI / 180.0); // Convert to radians
    
    double sine_value = sin(angle_radian);
    
    printf("The sine of %.2f degrees is %.2f\n", angle_degree, sine_value);
    return 0;
}

Recursive Functions

Definition

  • A recursive function is one that calls itself directly or indirectly to solve a problem.

Components

  1. Base Case:

    • The condition under which the recursion terminates.
    • Prevents infinite recursion.
  2. Recursive Case:

    • The part of the function that calls itself with a modified argument.

Usage

  • Recursive functions are often used for tasks that can be broken down into similar subtasks.

Examples

Factorial Function
  • Definition: The factorial of a non-negative integer n is the product of all positive integers less than or equal to n.

  • Recursive Formula:

    factorial(n) = n * factorial(n - 1), for n > 1
    factorial(0) = 1
    factorial(1) = 1
  • Implementation:

    int factorial(int n) {
        if (n == 0 || n == 1) {
            return 1; // Base case
        } else {
            return n * factorial(n - 1); // Recursive call
        }
    }
    
    int main() {
        int num = 5;
        int fact = factorial(num);
        printf("Factorial of %d is %d\n", num, fact);
        return 0;
    }
Fibonacci Series
  • Definition: A sequence where each number is the sum of the two preceding ones.

  • Recursive Formula:

    fibonacci(n) = fibonacci(n - 1) + fibonacci(n - 2), for n > 1
    fibonacci(0) = 0
    fibonacci(1) = 1
  • Implementation:

    int fibonacci(int n) {
        if (n == 0) {
            return 0; // Base case
        } else if (n == 1) {
            return 1; // Base case
        } else {
            return fibonacci(n - 1) + fibonacci(n - 2); // Recursive call
        }
    }
    
    int main() {
        int n = 10;
        printf("Fibonacci series up to %d terms:\n", n);
        for (int i = 0; i < n; i++) {
            printf("%d ", fibonacci(i));
        }
        printf("\n");
        return 0;
    }

Notes on Recursion

  • Advantages:

    • Simplifies code for problems that have a recursive nature.
  • Disadvantages:

    • Can lead to stack overflow if recursion depth is too great.
    • Generally less efficient than iterative solutions due to function call overhead.
  • Best Practices:

    • Always ensure there is a valid base case.
    • Test recursive functions thoroughly.

Scope Rules in C

Scope rules determine the visibility and lifetime of variables and functions within a program.

Local Scope

Definition

  • Variables declared inside a function or block {} have local scope.
  • They are accessible only within that function or block.

Lifetime

  • Lifetime: From the point of declaration until the end of the block.
  • Memory allocated to local variables is released when the block is exited.

Example

void example() {
    int localVar = 10; // Local variable
    printf("Local variable: %d\n", localVar);
}

// Outside the function, localVar is not accessible.

Global Scope

Definition

  • Variables declared outside of all functions have global scope.
  • Accessible from any function after their point of declaration.

Lifetime

  • Lifetime: From the program's start until it terminates.

Example

int globalVar = 20; // Global variable

void display() {
    printf("Global variable in display(): %d\n", globalVar);
}

int main() {
    printf("Global variable in main(): %d\n", globalVar);
    display();
    return 0;
}

Parameters Scope

  • Function Parameters: Variables declared in a function's parameter list.
  • Treated as local variables within the function.

Example

void display(int num) {
    // 'num' has local scope within 'display' function
    printf("Number: %d\n", num);
}

// 'num' is not accessible outside the function.

Storage Classes in C

Storage classes in C determine the scope, visibility, lifetime, and default initial value of variables.

Automatic Storage Class (auto)

Default Storage Class

  • All local variables declared within a block are automatic by default.

Scope and Lifetime

  • Scope: Local to the block or function in which they are declared.
  • Lifetime: Exists only during the execution of the block.

Declaration

  • Using auto keyword is optional (redundant), as it's the default.

Example

void function() {
    auto int i = 0; // Equivalent to 'int i = 0;'
    printf("Value of i: %d\n", i);
}

External Storage Class (extern)

Purpose

  • Declares a global variable or function that is defined in another file or later in the same file.

Scope and Lifetime

  • Scope: Global.
  • Lifetime: For the entire duration of the program.

Usage

  • Declaration: extern data_type variable_name;
  • Does not allocate memory; refers to a variable defined elsewhere.

Examples

Multiple File Program
  • File1.c (Variable definition):

    int count = 5; // Definition of 'count'
  • File2.c (Variable declaration):

    extern int count; // Declaration of 'count'
    
    void display() {
        printf("Count: %d\n", count);
    }
  • Compilation:

    gcc File1.c File2.c -o program

Notes

  • extern can also be used to extend the visibility of functions across files.

Register Storage Class (register)

Purpose

  • Suggests that the variable be stored in a CPU register instead of RAM for faster access.

Scope and Lifetime

  • Scope: Local to the block.
  • Lifetime: Within the block.

Notes

  • The compiler may ignore the register keyword if insufficient registers are available.
  • Cannot take the address (&) of a register variable.

Example

void function() {
    register int i;
    for (i = 0; i < 1000; i++) {
        // Some operations
    }
}

Static Storage Class (static)

Purpose

  • Local Variables:

    • Retains their value between function calls.
  • Global Variables and Functions:

    • Restricts their scope to the file (internal linkage).

Local Static Variables

Scope and Lifetime
  • Scope: Local to the block.
  • Lifetime: Exists for the entire program execution.
Initialization
  • Initialized only once at program start.
  • Default initial value is zero if not explicitly initialized.
Example
void counter() {
    static int count = 0; // Static local variable
    count++;
    printf("Count: %d\n", count);
}

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

Global Static Variables

  • Scope: Accessible only within the file where they are declared.
  • Prevents external files from accessing the variable.
Example
static int globalVar = 10; // Static global variable

void display() {
    printf("GlobalVar: %d\n", globalVar);
}

Static Functions

  • Purpose: Function is only visible within the file it is declared.
  • Prevents naming conflicts in multi-file programs.
Example
static void helperFunction() {
    // Function code
}

void publicFunction() {
    helperFunction(); // Can call static function within the same file
}

Summary of Storage Classes

Storage Class Scope Lifetime Default Initial Value Storage Location
auto Block Block Garbage Value Stack
register Block Block Garbage Value CPU Register
static (local) Block Program Zero Static Memory
static (global) File Program Zero Static Memory
extern Global Program Zero Static Memory

Best Practices with Functions and Storage Classes

Effective use of functions and storage classes enhances code quality and maintainability.

Functions

Meaningful Names

  • Use descriptive names for functions and parameters to convey their purpose.

Example:

// Bad
int f(int x) { /*...*/ }

// Good
int calculateSum(int numbers[], int size) { /*...*/ }

Single Responsibility

  • Each function should perform a single, well-defined task.

Limiting Parameters

  • Functions should ideally have a limited number of parameters (preferably less than five).
  • For multiple related parameters, consider using structures.

Documentation

  • Comment functions to explain their purpose, parameters, return values, and any side effects.

Example:

/**
 * Calculates the factorial of a non-negative integer.
 * @param n The number to calculate the factorial of.
 * @return The factorial of n.
 */
int factorial(int n) { /*...*/ }

Storage Classes

static Local Variables

  • Use when you need to preserve the value of a local variable between function calls.

Global Variables

  • Use global variables sparingly.
  • Global variables can make debugging difficult due to potential unintended side effects.

extern Variables

  • Minimize use to reduce dependencies between different files.
  • Prefer passing variables as parameters.

register Variables

  • Modern compilers optimize variable storage; manual use of register is often unnecessary.

Recursion

Base Case

  • Always ensure recursion has a base case to prevent infinite loops.

Example:

void infiniteRecursion() {
    infiniteRecursion(); // No base case, leads to stack overflow
}

Stack Overflow

  • Be cautious of the maximum recursion depth; deep recursion can cause stack overflow.

Consider Iterative Solutions

  • For problems where recursion depth can be large, an iterative approach may be more efficient.

Post a Comment