Lesson content
Read, practise, then check your understanding
Functions divide a program into named operations with explicit inputs and outputs. A good function has one coherent responsibility, a documented contract, and no hidden dependency that could be passed as a parameter.
Declaration and definition
A declaration makes the interface known; a definition supplies the body.
double average(const int values[], size_t count); // declaration
double average(const int values[], size_t count) { // definition
if (count == 0) return 0.0;
long long total = 0;
for (size_t i = 0; i < count; ++i) total += values[i];
return (double)total / (double)count;
}
Put public declarations in headers and definitions in source files. Include the function’s own header in its implementation so the compiler detects mismatches.
C passes by value
Every argument is passed by value. To let a function modify a caller’s object, pass its address.
bool divide(int numerator, int denominator, double *result) {
if (result == NULL || denominator == 0) return false;
*result = (double)numerator / denominator;
return true;
}
This pattern returns status separately from output. Document whether pointer parameters may be null and who owns pointed-to memory. Array parameters adjust to pointers, so pass a length alongside them.
Scope and private helpers
A function defined with static at file scope has internal linkage; only that translation unit can name it. This is useful for private helpers and lets the compiler optimize with more context.
Variadic functions
Functions such as printf accept a variable argument list. Custom variadic functions use <stdarg.h>, but extra arguments carry limited type information. Prefer an array, structure, or explicit interface when practical.
Recursion
A recursive function calls itself on a smaller problem and must have a base case.
unsigned long long factorial(unsigned n) {
if (n < 2) return 1;
return n * factorial(n - 1);
}
Recursion consumes stack space and can overflow for deep input. Iteration is often more robust for linear tasks. Recursion is natural for trees and divide-and-conquer algorithms when depth is controlled.
Function design checklist
- Validate inputs at the boundary.
- Keep parameter lists understandable; group related configuration in a struct.
- Return a documented status or value on every path.
- Keep functions short enough that invariants remain visible.
- Avoid global mutable state when data can be passed explicitly.
Knowledge check
Answer every question correctly to complete this chapter.
0 of 10 checks passed
Your progress is saved on this device.