Skip to main content

Lecture 5: Functions

Prerequisites: Arrays; control flow with if/switch and loops.

1. Overview

  • As programmes grow beyond a handful of lines, keeping all logic inside main() quickly becomes unmanageable; functions let us decompose a large problem into small, self-contained, reusable units.
  • After this chapter you will be able to declare, define, and invoke your own functions, choose between iterative and recursive solutions, pass both scalar values and arrays to functions, and reason about variable scope and lifetime.

2. Learning Outcomes

After completing this chapter, students will be able to:

  1. Explain why functions are essential for modular programme design, code reuse, and ease of testing.
  2. Distinguish between a function declaration (prototype), a function definition, and a function call, and place each correctly within a C source file.
  3. Define functions with various return types (int, float, void, etc.) and parameter lists, including functions that accept no parameters.
  4. Describe how function invocation works at the machine level—stack frames, argument passing, and the return mechanism.
  5. Write recursive functions with a correct base case and recursive case, and compare the trade-offs between recursion and iteration.
  6. Pass one-dimensional arrays to functions and understand why the array size must be supplied as a separate parameter.
  7. Distinguish between local and global variable scope and articulate best practices for minimising the use of global state.
  8. Combine user-defined functions with standard-library functions to build modular programmes.

3. Key Terms

Term (EN)中文Notes
Function declaration (prototype)函数声明(原型)Ends with ;; no body. Tells the compiler about name, return type, and parameter types.
Function definition函数定义Contains the actual body (implementation). Must match its prototype.
Function call (invocation)函数调用The point in code where execution transfers to the function.
Parameter (formal parameter)形参The variable names listed in the function header.
Argument (actual parameter)实参The concrete values or expressions supplied at the call site. Easily confused with parameter.
Return type返回类型The data type of the value the function hands back; void if none.
Pass by value值传递The default in C for scalar types—a copy is passed.
Pass by reference引用传递In C, achieved via pointers. Arrays are effectively passed by reference.
Stack frame栈帧A block of memory on the call stack holding a function's local variables, parameters, and return address.
Recursion递归A function that calls itself; must have a base case to terminate.
Base case基线条件The condition under which a recursive function stops calling itself.
Scope作用域The region of source code in which a variable is visible.
Local variable局部变量Declared inside a function or block; visible only there.
Global variable全局变量Declared outside all functions; visible to every function that follows its declaration.
Library function库函数Pre-built functions provided by the C Standard Library, e.g. printf(), sqrt().
User-defined function用户自定义函数Functions written by the programmer.
Modular design模块化设计Structuring a programme as a collection of independent, well-defined modules (functions).

4. Core Concepts

4.1 Why Functions?

Consider a programme that performs several arithmetic operations on user input. Without functions, every operation lives inside main(), producing what is sometimes called monolithic code. Such code is hard to read, hard to test, and impossible to reuse. Functions address all three problems: each function encapsulates a single responsibility, can be tested independently, and can be called from anywhere in the programme (or even from other programmes, via header files and separate compilation).

A useful mnemonic is DRYDon't Repeat Yourself. Whenever you find yourself copying and pasting a block of code, that block is a candidate for extraction into a function.

Minimal example File: p05_a.c Build: gcc p05_a.c -o p05_a Run: ./p05_a

/* p05_a.c – Monolithic vs. modular: a first taste of functions */
#include <stdio.h>

/* Function declarations (prototypes) */
int add(int x, int y);
int subtract(int x, int y);

/* main */
int main(void) {
int a, b;

printf("Enter two integers: ");
scanf("%d %d", &a, &b);

printf("Sum: %d\n", add(a, b));
printf("Difference: %d\n", subtract(a, b));

return 0;
}

/* Function definitions */
int add(int x, int y) {
return x + y;
}

int subtract(int x, int y) {
return x - y;
}

What you should see: The programme prompts for two integers, then prints their sum and difference—each computed by a dedicated function rather than by inline arithmetic in main().

Takeaway: Extracting even trivial operations into named functions makes the intent of main() immediately clear: read input → compute → print results.

4.2 Function Declaration, Definition, and Call

Every user-defined function in C involves three distinct elements that beginners frequently conflate.

  1. Declaration (prototype) — tells the compiler what the function looks like (name, return type, parameter types) before the function is used. It ends with a semicolon and carries no body. Prototypes are conventionally placed above main() or collected in a header file (.h).

  2. Definition — provides the actual implementation: the body enclosed in braces. The signature must match the prototype exactly. Definitions are typically placed after main() (or in a separate .c file).

  3. Call (invocation) — the expression that transfers execution to the function. Arguments are passed inside parentheses and must match the parameter types declared in the prototype.

The general syntax is:

return_type function_name(parameter_list); /* declaration */

return_type function_name(parameter_list) { /* definition */
/* statements */
return value; /* omitted if return_type is void */
}

function_name(argument1, argument2, ...); /* call */

A function that takes no parameters and returns nothing uses void in both positions:

void print_hello(void); /* declaration */

void print_hello(void) { /* definition */
printf("Hello, World!\n");
}

print_hello(); /* call */

Note that in the declaration, parameter names are optional (only types are required), but including names improves readability.

Minimal example File: p05_b.c Build: gcc p05_b.c -o p05_b Run: ./p05_b

/* p05_b.c – Demonstrating declaration, definition, and call */
#include <stdio.h>

/* Declaration (prototype) – parameter names optional here */
void print_hello(void);

int main(void) {
print_hello(); /* Call */
return 0;
}

/* Definition */
void print_hello(void) {
printf("Hello, World!\n");
}

What you should see: Hello, World! printed once.

Takeaway: The three-step pattern—declare → call → define—is the idiomatic C workflow. Placing prototypes above main() allows the compiler to check every call site for type correctness before it ever sees the full definition.

4.3 Return Types and Parameters

The return type specifies what kind of value (if any) a function hands back to its caller. It must agree with the type of the expression in the return statement. If a function performs an action without producing a result, its return type is void. The parameter list names the input data the function operates on; each parameter has a type and a name, and multiple parameters are separated by commas. A function that accepts no input declares its parameter list as void.

It is important to note that in C, int f() and int f(void) are not identical: the former declares a function accepting an unspecified number of arguments (an obsolescent feature inherited from K&R C), whilst the latter explicitly states that the function takes no arguments. Always prefer the (void) form for clarity.

Minimal example File: p05_c.c Build: gcc p05_c.c -o p05_c Run: ./p05_c

/* p05_c.c – Return types and parameters */
#include <stdio.h>

int is_even(int num);
void print_stars(int count);

int main(void) {
int n;

printf("Enter a number: ");
scanf("%d", &n);

if (is_even(n))
printf("%d is even.\n", n);
else
printf("%d is odd.\n", n);

printf("Enter number of stars: ");
scanf("%d", &n);
print_stars(n);

return 0;
}

/* Returns 1 if num is even, 0 otherwise */
int is_even(int num) {
return (num % 2 == 0) ? 1 : 0;
}

/* Prints 'count' asterisks followed by a newline; returns nothing */
void print_stars(int count) {
for (int i = 0; i < count; i++)
putchar('*');
putchar('\n');
}

What you should see: For input 4 the programme reports "4 is even." For input 5 it prints five asterisks on one line.

Takeaway: Choose void when a function exists purely for its side-effect (e.g., printing); choose a concrete type when the caller needs a computed value.

4.4 Function Invocation and the Call Stack

When a function is called, the CPU performs a well-defined sequence of actions:

  1. The current execution context (return address, caller's local variables) is saved.
  2. A new stack frame is allocated on the call stack.
  3. Arguments are copied into the frame's parameter slots (this is pass by value).
  4. Execution transfers to the first statement of the function body.
  5. When the function executes return, its stack frame is destroyed (popped), and control resumes at the call site.

The call stack is a Last-In-First-Out (LIFO) data structure. If main() calls func1(), which in turn calls func2(), the stack grows as follows:

| func2() | ← top (most recent frame)
| func1() |
| main() | ← bottom

When func2() returns, its frame is popped; when func1() returns, its frame is popped; finally only main() remains.

Minimal example File: p05_d.c Build: gcc p05_d.c -o p05_d Run: ./p05_d

/* p05_d.c – Observing the call stack through print statements */
#include <stdio.h>

void func2(void) {
printf(" Entering func2\n");
printf(" Leaving func2\n");
}

void func1(void) {
printf(" Entering func1\n");
func2();
printf(" Back in func1\n");
}

int main(void) {
printf("Entering main\n");
func1();
printf("Back in main\n");
return 0;
}

What you should see:

Entering main
Entering func1
Entering func2
Leaving func2
Back in func1
Back in main

The indentation mirrors the depth of the call stack at each point.

Takeaway: Understanding the call stack is essential for debugging (stack traces) and for reasoning about recursion, where the stack can grow very deep.

4.5 Recursion

A recursive function is one that calls itself. Every correct recursive solution consists of at least two parts:

  • Base case — a condition under which the function returns a value without making a further recursive call. This prevents infinite recursion.
  • Recursive case — the function calls itself with a smaller or simpler version of the original problem, gradually approaching the base case.

The classic example is the factorial function:

n!={1if n1n×(n1)!otherwisen! = \begin{cases} 1 & \text{if } n \le 1 \\ n \times (n-1)! & \text{otherwise} \end{cases}

Each recursive call pushes a new stack frame. Once the base case is reached, the frames unwind in reverse order, each multiplying its local n by the result returned from the frame above.

Minimal example File: p05_e.c Build: gcc p05_e.c -o p05_e Run: ./p05_e

/* p05_e.c – Recursive factorial */
#include <stdio.h>

int factorial(int n);

int main(void) {
int result = factorial(5);
printf("5! = %d\n", result);
return 0;
}

int factorial(int n) {
if (n <= 1) /* base case */
return 1;
else /* recursive case */
return n * factorial(n - 1);
}

What you should see: 5! = 120

Takeaway: Recursion expresses self-similar problems elegantly, but every recursive call consumes stack space. For simple accumulations (like factorial), an iterative loop is usually more efficient.

4.6 Recursion versus Iteration

Any recursive algorithm can be rewritten iteratively (and vice versa). The table below summarises the trade-offs:

AspectRecursive approachIterative approach
MechanismFunction call stackLoops and local variables
ReadabilityOften more concise for naturally recursive problems (e.g., tree traversal)May require explicit stack management for such problems
MemoryEach call adds a stack frame; risk of stack overflow for deep recursionConstant extra memory (typically one or two loop variables)
PerformanceFunction-call overhead on every iterationGenerally faster due to absence of call overhead

Guideline: Use recursion when it significantly simplifies the logic (e.g., divide-and-conquer algorithms). Use iteration for straightforward accumulations such as factorial or summation, where the iterative version is equally clear and avoids stack-overflow risk.

Minimal example — iterative factorial File: p05_f.c Build: gcc p05_f.c -o p05_f Run: ./p05_f

/* p05_f.c – Iterative factorial for comparison */
#include <stdio.h>

int factorial_iter(int n);

int main(void) {
int result = factorial_iter(5);
printf("5! = %d\n", result);
return 0;
}

int factorial_iter(int n) {
int result = 1;
for (int i = 2; i <= n; i++)
result *= i;
return result;
}

What you should see: 5! = 120 — identical output, but with constant stack usage.

Takeaway: Always ask yourself whether the recursive structure genuinely aids comprehension; if not, prefer the iterative form.

4.7 Pass by Value

In C, when a scalar variable (e.g., int, float, char) is passed to a function, the function receives a copy of the value. Consequently, any modification the function makes to its parameter has no effect on the original variable in the calling function. This mechanism is known as pass by value.

Minimal example File: p05_g.c Build: gcc p05_g.c -o p05_g Run: ./p05_g

/* p05_g.c – Demonstrating pass by value */
#include <stdio.h>

void increment(int num) {
num = num + 1;
printf("Inside function: %d\n", num);
}

int main(void) {
int x = 5;
increment(x);
printf("Outside function: %d\n", x);
return 0;
}

What you should see:

Inside function: 6
Outside function: 5

The original variable x in main() is unchanged because increment() operated on a copy.

Takeaway: If you need a function to modify the caller's variable, you must use pointers (covered in Lecture 7). For now, return the new value instead: x = increment(x);.

4.8 Passing Arrays to Functions

Arrays behave differently from scalars when passed to functions. In C, the name of an array evaluates to a pointer to its first element. When you pass an array to a function, you are not passing a copy of the entire array; rather, you are passing the memory address of element zero. This means the function can read and modify the original array elements.

Because the size information is lost when an array decays to a pointer, it is standard practice to pass the array size as a separate parameter:

return_type function_name(type arr[], int size);

Note that writing int arr[10] in the parameter list is syntactically valid but misleading—the compiler silently ignores the 10 and treats it as int arr[] (i.e., int *arr).

Minimal example File: p05_h.c Build: gcc p05_h.c -o p05_h Run: ./p05_h

/* p05_h.c – Passing an array to a function */
#include <stdio.h>

void print_array(int arr[], int size);

int main(void) {
int numbers[] = {10, 20, 30, 40, 50};
int length = sizeof(numbers) / sizeof(numbers[0]);

print_array(numbers, length);

return 0;
}

void print_array(int arr[], int size) {
for (int i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}

What you should see: 10 20 30 40 50

Takeaway: Because the function receives a pointer rather than a copy, changes to arr[i] inside the function will be reflected in the caller's array. Always pass the size explicitly; never rely on a hard-coded constant.

4.9 Variable Scope: Local and Global

The scope of a variable is the region of source code in which that variable is visible and may be referenced.

  • Local variables are declared inside a function (or a block delimited by { }). They are created when execution enters the block and destroyed when execution leaves it. They are invisible to all other functions.
  • Global variables are declared outside of all functions, typically at the top of the source file. They are visible to every function defined after the declaration within the same translation unit, and they persist for the entire lifetime of the programme.

When a local variable has the same name as a global variable, the local variable shadows (hides) the global one within its scope.

Minimal example File: p05_i.c Build: gcc p05_i.c -o p05_i Run: ./p05_i

/* p05_i.c – Local vs. global variables */
#include <stdio.h>

int counter = 10; /* global variable */

void show_counter(void) {
int counter = 5; /* local variable – shadows the global */
printf("Local counter: %d\n", counter);
}

int main(void) {
printf("Global counter: %d\n", counter);
show_counter();
return 0;
}

What you should see:

Global counter: 10
Local counter: 5

Takeaway: Prefer local variables wherever possible. Global state introduces hidden coupling between functions and makes programmes harder to reason about, test, and maintain. If a global variable is genuinely necessary, declare it clearly at the top of the file and document its purpose.

4.10 Library Functions versus User-Defined Functions

C provides a rich set of pre-built library functions in its standard headers. Common examples include printf() and scanf() from <stdio.h>, sqrt() and pow() from <math.h>, strlen() and strcpy() from <string.h>, and malloc() and free() from <stdlib.h>. To use a library function, you include the relevant header; there is no need to write its definition.

User-defined functions are those you write yourself to encapsulate application-specific logic. A well-designed programme typically combines both: library functions for common operations and user-defined functions for domain logic.

For the curious, many library implementations are open source. The GNU C Library (glibc), for instance, is used on most Linux systems, and its source code is available at https://sourceware.org/git/?p=glibc.git.

Minimal example File: p05_j.c Build: gcc p05_j.c -o p05_j -lm Run: ./p05_j

/* p05_j.c – Combining a user-defined function with a library function */
#include <stdio.h>
#include <math.h> /* for sqrt() */

float square(float x);

int main(void) {
float num;

printf("Enter a number: ");
scanf("%f", &num);

float sq = square(num);
printf("Square: %.2f\n", sq);
printf("Square root: %.2f\n", sqrt(sq));

return 0;
}

float square(float x) {
return x * x;
}

What you should see (for input 4):

Square: 16.00
Square root: 4.00

Note the -lm flag when compiling: on many Unix systems the maths library must be linked explicitly.

Takeaway: Do not reinvent the wheel—use library functions for standard operations—but do not hesitate to write your own functions when no suitable library function exists.

5. Worked Examples

Example 1: Maximum of Three Integers

Problem: Write a function max_of_three(int a, int b, int c) that returns the largest of three integers. Demonstrate its use in main().

Steps:

  1. Declare the prototype above main().
  2. In the definition, compare pairs: first find the larger of a and b, then compare the result with c.
  3. Call the function with user-supplied values and print the result.

Reference code File: p05_ex1.c Build: gcc p05_ex1.c -o p05_ex1 Run: ./p05_ex1

/* p05_ex1.c – Maximum of three integers */
#include <stdio.h>

int max_of_three(int a, int b, int c);

int main(void) {
int a, b, c;

printf("Enter three numbers: ");
scanf("%d %d %d", &a, &b, &c);

printf("Maximum is: %d\n", max_of_three(a, b, c));

return 0;
}

int max_of_three(int a, int b, int c) {
int max = a;
if (b > max) max = b;
if (c > max) max = c;
return max;
}

Output:

Enter three numbers: 9 4 7
Maximum is: 9

Why it works: The function initialises max to a and then conditionally updates it if b or c is larger. This two-comparison approach is both simple and efficient, avoiding nested conditional expressions that can be hard to read.

Example 2: Simple Calculator Using Multiple Functions

Problem: Build a modular calculator that reads an expression of the form num1 op num2 (where op is one of +, -, *, /) and prints the result. Each arithmetic operation should be encapsulated in its own function.

Steps:

  1. Declare four prototypes: add, subtract, multiply, divide, each taking and returning float.
  2. In main(), read the expression using scanf with the format "%f %c %f".
  3. Use a switch statement to dispatch to the correct function.
  4. Handle division by zero as a special case.

Reference code File: p05_ex2.c Build: gcc p05_ex2.c -o p05_ex2 Run: ./p05_ex2

/* p05_ex2.c – Simple calculator with modular functions */
#include <stdio.h>

float add(float x, float y);
float subtract(float x, float y);
float multiply(float x, float y);
float divide(float x, float y);

int main(void) {
float num1, num2, result;
char operator;

printf("Enter expression (e.g., 10 + 5): ");
scanf("%f %c %f", &num1, &operator, &num2);

switch (operator) {
case '+': result = add(num1, num2); break;
case '-': result = subtract(num1, num2); break;
case '*': result = multiply(num1, num2); break;
case '/':
if (num2 == 0) {
printf("Error: Division by zero.\n");
return 1;
}
result = divide(num1, num2);
break;
default:
printf("Error: Invalid operator.\n");
return 1;
}

printf("Result: %.2f\n", result);
return 0;
}

float add(float x, float y) { return x + y; }
float subtract(float x, float y) { return x - y; }
float multiply(float x, float y) { return x * y; }
float divide(float x, float y) { return x / y; }

Output:

Enter expression (e.g., 10 + 5): 10 + 5
Result: 15.00

Why it works: Each operation is isolated in its own function, making it trivial to add new operations (e.g., modulus) or to unit-test individual functions. The switch in main() serves purely as a dispatcher, keeping the control logic separate from the computation.

Example 3: Modular Student-Mark Analyser

Problem: Write a programme that reads an array of student marks and reports the maximum, minimum, and average—each computed by a separate function.

Steps:

  1. Define read_marks(), find_max(), find_min(), and calculate_average().
  2. Pass the array and its size to each function.
  3. Print the results from main().

Reference code File: p05_ex3.c Build: gcc p05_ex3.c -o p05_ex3 Run: ./p05_ex3

/* p05_ex3.c – Modular mark analysis */
#include <stdio.h>

#define NUM_STUDENTS 5

void read_marks(int arr[], int size);
int find_max(int arr[], int size);
int find_min(int arr[], int size);
float calculate_average(int arr[], int size);

int main(void) {
int marks[NUM_STUDENTS];

printf("Enter marks for %d students: ", NUM_STUDENTS);
read_marks(marks, NUM_STUDENTS);

printf("Maximum mark: %d\n", find_max(marks, NUM_STUDENTS));
printf("Minimum mark: %d\n", find_min(marks, NUM_STUDENTS));
printf("Average mark: %.2f\n", calculate_average(marks, NUM_STUDENTS));

return 0;
}

void read_marks(int arr[], int size) {
for (int i = 0; i < size; i++)
scanf("%d", &arr[i]);
}

int find_max(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++)
if (arr[i] > max) max = arr[i];
return max;
}

int find_min(int arr[], int size) {
int min = arr[0];
for (int i = 1; i < size; i++)
if (arr[i] < min) min = arr[i];
return min;
}

float calculate_average(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++)
sum += arr[i];
return (float)sum / size;
}

Output:

Enter marks for 5 students: 78 65 89 92 74
Maximum mark: 92
Minimum mark: 65
Average mark: 79.60

Why it works: Each function has a single, well-defined responsibility. The read_marks() function modifies the caller's array directly (because arrays are passed by reference in C), whilst the other three functions merely read from it. This separation makes testing straightforward—one can supply a hard-coded array to find_max() without needing to invoke read_marks().

6. Common Mistakes

  1. Forgetting the prototype: If a function is called before its definition and no prototype is visible, the compiler may assume it returns int (in older standards) or may reject the code outright (C99 onwards). Always provide a prototype above main().

  2. Mismatched return type: Declaring a function as int but returning a float (or vice versa) causes implicit truncation or unexpected behaviour. Ensure the return expression matches the declared return type exactly.

  3. Modifying a parameter and expecting the caller's variable to change: Because C uses pass by value for scalars, writing num = num + 1; inside a function does not affect the caller's variable. Return the new value or use a pointer.

  4. Omitting the array size parameter: Since an array parameter decays to a pointer, there is no way for the function to determine the array length internally. Forgetting to pass the size leads to out-of-bounds access or hard-coded magic numbers.

  5. Missing base case in recursion: Without a base case, a recursive function calls itself indefinitely, consuming stack frames until the programme crashes with a stack overflow. Always verify that the base case is reachable for every valid input.

  6. Using sizeof(arr) inside a function to determine array length: Inside a function that receives int arr[], sizeof(arr) returns the size of a pointer (typically 4 or 8 bytes), not the size of the original array. The sizeof trick for computing array length only works in the scope where the array is declared.

  7. Shadowing a global variable unintentionally: Declaring a local variable with the same name as a global variable causes the local to shadow the global within that scope. This can lead to subtle bugs when the programmer intends to modify the global. Use distinct names, or (better still) avoid globals altogether.

7. Practice

7.1 Basic

  • P1: Write a programme defining and calling a function print_hello() that prints "Hello, World!". The function takes no arguments and returns nothing. Acceptance: The programme compiles without warnings (gcc -Wall) and prints Hello, World! exactly once.

  • P2: Write a function is_even(int num) that returns 1 if the integer is even and 0 otherwise. Demonstrate it in main() by reading an integer and printing whether it is even or odd. Input/Output: Enter number: 44 is even. Acceptance: Correct output for both even and odd inputs, including 0 and negative numbers.

  • P3: Write a function print_stars(int count) that prints a given number of * characters on one line, followed by a newline. The function should return nothing. Input/Output: Enter number of stars: 5***** Acceptance: Correct output for count = 0 (prints only a newline) and positive values.

7.2 Required

  • P4: Create a function max_of_three(int a, int b, int c) that returns the largest of three integers. Test it in main() with user input. Input/Output: Enter three numbers: 9 4 7Maximum is: 9 Acceptance: Works correctly when two or all three values are equal, and for negative inputs.

  • P5: Write a recursive function fibonacci(int n) that returns the n-th Fibonacci number, where fib(1) = fib(2) = 1. Test it for n = 6. Input/Output: Enter n: 66th Fibonacci number: 8 Acceptance: Correct output for n = 1 through n = 10.

  • P6: Write a function calculate_average(int arr[], int size) that returns the average of the elements in an integer array. Demonstrate it in main(). Input/Output: Enter 5 numbers: 2 4 6 8 10Average: 6.00 Acceptance: The average is printed to two decimal places; function uses a float or double return type.

  • P7: Write a programme demonstrating local versus global variables. Declare a global variable counter initialised to 10. Create a function show_counter() with a local variable also named counter initialised to 5. Print both to illustrate shadowing. Output: Global counter: 10 / Local counter: 5 Acceptance: The programme clearly shows that the global is unaffected by the local declaration.

7.3 Optional

  • P8: Write a programme with a user-defined function square(float x) and use the library function sqrt() from <math.h> to compute the square root of the squared result. Verify that the round-trip recovers the original value. Input/Output: Enter number: 4Square: 16.00 / Square root: 4.00 Constraints: Compile with -lm to link the maths library.

  • P9: Write a modular programme with four functions—read_marks(), find_max(), find_min(), and calculate_average()—to analyse an array of student marks. Input/Output: Enter marks for 5 students: 78 65 89 92 74Maximum mark: 92 / Minimum mark: 65 / Average mark: 79.60 Acceptance: The programme handles the case where all marks are identical.

  • P10: Write a temperature-conversion programme using two functions: celsius_to_fahrenheit(float celsius) and fahrenheit_to_celsius(float fahrenheit). Demonstrate both conversions. Input/Output: Enter temperature in Celsius: 100100.00°C = 212.00°F / Enter temperature in Fahrenheit: 3232.00°F = 0.00°C Acceptance: Results are accurate to two decimal places.

8. Mini Task

Goal: Build a mini statistics toolkit. The programme reads an array of up to 20 floating-point numbers from the user, then uses separate functions to compute and display the mean, maximum, minimum, and range (max − min). Optionally, add a function that prints a simple horizontal bar chart of the data.

Deliverables:

  • README.md: Explains how to compile and run the programme, and includes sample output.
  • src/stats.c: The main source file containing all functions and main().
  • report.md: A brief reflection (150–300 words) on the design decisions you made—e.g., why you chose certain function signatures, any difficulties encountered, and how you resolved them.

9. Further Reading

  1. Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language, 2nd edn. Prentice Hall — Chapter 4: Functions and Program Structure.
  2. King, K. N. (2008). C Programming: A Modern Approach, 2nd edn. W. W. Norton — Chapter 9: Functions.
  3. GNU C Library (glibc) source code: https://sourceware.org/git/?p=glibc.git — explore implementations of standard library functions such as printf and qsort.

10. Appendix

A. Compilation Quick Reference

CommandPurpose
gcc file.c -o fileCompile a single-file programme
gcc -Wall -Wextra file.c -o fileCompile with extra warnings enabled
gcc file.c -o file -lmLink the maths library (required for <math.h> functions on many systems)
./fileRun the compiled programme

B. Factorial Call-Stack Trace

The following trace illustrates the stack frames created when evaluating factorial(4):

Call: factorial(4) → frame 4 pushed
Call: factorial(3) → frame 3 pushed
Call: factorial(2) → frame 2 pushed
Call: factorial(1) → frame 1 pushed (base case)
Return: 1 → frame 1 popped
Return: 2 * 1 = 2 → frame 2 popped
Return: 3 * 2 = 6 → frame 3 popped
Return: 4 * 6 = 24 → frame 4 popped

C. Fibonacci Sequence Reference Table

n12345678910
fib(n)11235813213455

D. Temperature Conversion Formulae

F=C×95+32C=(F32)×59F = C \times \frac{9}{5} + 32 \qquad\qquad C = (F - 32) \times \frac{5}{9}