Unit 4: Functions

CAP1008 — C Programming 8 min read

A function is a self-contained block of code that performs one task and can be called repeatedly, letting a large program be split into smaller, testable, reusable units. C programs are built from functions; execution always begins in main(), which calls other functions in turn.

  • Modularity: Each function isolates one job, so faults are localised and code is reused instead of duplicated.
  • The three parts: Every function needs a declaration (prototype), a definition (body), and one or more calls.
  • Prototype: A statement of the form returnType name(paramTypes); placed before use, telling the compiler the signature so it can check arguments.
  • Return type: The type of the value sent back with return; void means no value is returned.
  • Parameters vs arguments: Parameters are the variables in the definition; arguments are the actual values supplied at the call.

II. Function Definition — the body that does the work

A. Structure and syntax

The definition supplies the actual statements executed when the function is called.

  • General form: header plus body.
    C
    returnType functionName(parameterList) {
        /* declarations and statements */
        return value;   /* optional */
    }
  • Header components: returnType fixes what return sends back; functionName is the identifier used in calls; parameterList lists typed formal parameters, e.g. int add(int a, int b).
  • return statement: Ends the function and hands one value to the caller; return a + b; both computes and exits. A void function may use a bare return; or none.
  • Empty parameter list: int getValue(void) states explicitly that no arguments are taken.

B. Worked example

C
int square(int n) {     /* definition */
    return n * n;
}
int main(void) {
    int r = square(5);  /* call, r becomes 25 */
    printf("%d", r);
    return 0;
}
  • Flow: control jumps to square with n = 5, computes 25, returns it to the assignment, then resumes main.

III. Predefined Functions and User Defined Functions

Functions come from two sources: ready-made ones supplied by C, and ones the programmer writes.

A. Predefined functions

Functions already written, compiled and shipped inside the standard library.

  • Definition: Also called built-in or library functions; you call them but never write their bodies.
  • Header requirement: Their prototypes live in headers included with #include, e.g. printf and scanf need <stdio.h>, sqrt needs <math.h>.
  • Examples: sqrt(16.0) returns 4.0; strlen("cat") returns 3; pow(2,3) returns 8.0.
  • Advantage: Tested, optimised code that saves effort and guarantees correctness for common tasks.

B. User defined functions

Functions the programmer creates for tasks the library does not cover.

  • Definition: Written, named and defined by the developer, e.g. a computeGrade() for a specific marking rule.
  • Why: Encapsulate application-specific logic, reduce repetition, and make the program readable.
  • Steps: declare prototype → define body → call. Missing any step causes a compile or link error.
  • Contrast with predefined:
    1. Predefined: source hidden, behaviour fixed by the standard, available everywhere the header is included.
    2. User defined: source visible and editable, behaviour chosen by the programmer, scoped to the program that defines it.

IV. Scope Rules — Local and Global Scope

Scope is the region of a program where a declared name is visible and usable; C decides it by where the declaration sits.

A. Local scope

A name declared inside a block is visible only within that block.

  • Definition: Variables declared inside a function or {} block are local; they exist only while the block runs.
  • Lifetime: Created on entry, destroyed on exit (automatic storage); a fresh copy each call.
  • Isolation: Two functions may each declare int i; with no clash, because each i is separate.
  • Example: in void f(){ int x = 5; }, x cannot be seen or used in main.

B. Global scope

A name declared outside all functions is visible to the whole file after its declaration.

  • Definition: Global variables are declared above main or between functions and can be read or changed by any function below.
    C
    int count = 0;          /* global */
    void inc(void){ count++; }   /* sees count directly */
  • Lifetime: Exist for the entire program run (static storage), keeping their value between calls.
  • Shadowing: A local variable with the same name hides the global inside that block; the local wins until the block ends.
  • Caution: Globals create hidden dependencies between functions, so prefer parameters and returns where practical.

V. Basics of Pointers — variables that hold addresses

A pointer is a variable whose value is the memory address of another variable, letting code reach data indirectly.

A. Pointer declaration

Declaring a pointer states the type of data it will point to.

  • Syntax: type *name; — the * marks it a pointer, type is the pointed-to type.
    C
    int *p;      /* pointer to int */
    float *q;    /* pointer to float */
  • Type matters: int * and float * are distinct; the base type sets how many bytes are read and how pointer arithmetic steps.

B. Initialization

A pointer is made to point somewhere using the address-of operator &.

  • Address-of &: &x yields the memory address of x.
    C
    int x = 10;
    int *p = &x;   /* p now holds the address of x */
  • Null pointer: int *p = NULL; marks a pointer that points to nothing, guarding against use of an uninitialised address.
  • Danger: A pointer declared but not initialised holds a garbage address; dereferencing it is undefined behaviour.

C. Accessing values using pointers

The indirection operator * reaches the value stored at the held address.

  • Dereference *: *p means "the value at the address in p".
    C
    int x = 10;
    int *p = &x;
    printf("%d", *p);   /* prints 10 */
    *p = 20;            /* changes x to 20 through p */
  • Two roles of *: in a declaration it creates a pointer; in an expression it dereferences one.
  • Result: editing *p edits x itself, since both name the same memory.

VI. Passing Arguments by Value and by Address

How arguments reach a function decides whether the caller's own variables can be changed.

A. Passing arguments by value

The function receives copies of the arguments, so the originals are untouched.

  • Mechanism: each parameter is a fresh local copy; changes stay inside the function.
    C
    void addOne(int n){ n = n + 1; }   /* local copy only */
    int a = 5;
    addOne(a);        /* a is still 5 */
  • Use when: the function only needs to read the data or return a single new value.

B. Passing arguments by address

The function receives addresses, so it can modify the caller's variables.

  • Mechanism: pass &variable; the parameter is a pointer, and dereferencing it edits the original.
    C
    void addOne(int *n){ *n = *n + 1; }
    int a = 5;
    addOne(&a);       /* a becomes 6 */
  • Contrast:
    1. By value: safe, isolated, but cannot alter the caller and copies large data.
    2. By address: can alter the caller and avoids copying, but risks accidental changes.
  • Classic use: swap(int *x, int *y) genuinely exchanges two variables, impossible by value; also lets a function effectively "return" several results.

VII. Recursion — a function that calls itself

Recursion solves a problem by having a function call itself on a smaller version of the same problem.

A. Base case and recursive case

Every correct recursion needs a stopping condition and a self-call that moves toward it.

  • Base case: the simplest input, solved directly without recursion; without it the calls never stop (stack overflow).
  • Recursive case: the function reduces the problem and calls itself, e.g. n * factorial(n-1).
  • The stack: each call is pushed with its own local copies; returns unwind in reverse order.

B. Worked example

C
int factorial(int n){
    if (n == 0) return 1;        /* base case */
    return n * factorial(n - 1); /* recursive case */
}
  • Trace of factorial(3): 3 * factorial(2) → 3 * 2 * factorial(1) → 3 * 2 * 1 * factorial(0) → 3 * 2 * 1 * 1 = 6.
  • Versus iteration: recursion is clearer for naturally self-similar tasks (trees, gcd), but uses more memory through stacked calls; a loop is cheaper when the logic is flat.

VIII. Library Functions — the standard toolkit

Library functions are the predefined functions grouped into standard header files, giving common operations without reinventing them.

A. Common headers and their functions

Each header declares a family of related routines that you access by including it.

  • <stdio.h>: input/output — printf, scanf, getchar, fopen.
  • <math.h>: mathematics — sqrt(x), pow(x,y), ceil(x), sin(x); math routines usually take and return double.
  • <string.h>: strings — strlen, strcpy, strcat, strcmp.
  • <stdlib.h>: utilities — malloc, free, atoi, rand, exit.
  • <ctype.h>: character tests — isalpha, isdigit, toupper.

B. Linking and use

Including a header only supplies prototypes; the actual code is joined at link time.

  • #include <math.h>: brings in prototypes so the compiler checks calls; the linker then attaches the compiled library object.
  • Consistency: these functions behave identically across programs, so strlen("hello") always returns 5.
  • Practical gain: reliable, portable building blocks let the programmer focus on user defined logic rather than low-level detail.