Skip to content
Educora
Intermediate17 min4 / 8

Functions

Write your own functions in C: prototypes, passing parameters by value, return, void functions, standard math functions and the basics of recursion.

Check yourself
In this lesson you will learn
  • Define and call a function with a return type, a name and parameters
  • Explain why a function prototype is needed
  • Understand that parameters are passed by value (as copies)
  • Write a simple recursive function with a base case

You already know two functions well: main, where every program starts, and printf, which prints text. Large programs are made of hundreds of small functions: one calculates a score, one prints a table, one reads a file. Each function does one job, is written once and can be called as many times as you like. And when you find a bug, you fix it in just one place.

How a function is built

  • Return type — the type of the result the function gives back (int, double…). A function that returns nothing uses void.
  • Name — a name that says what the function does, for example max or print_line.
  • Parameters — the input values written in parentheses together with their types: (int a, int b). With no parameters you write (void).
  • Body — the code between the curly braces. return sends the result back to the caller and ends the function at once.

The C compiler reads a file from top to bottom. If main calls a function before that function is defined, the compiler has to know about it in advance. For this you write a prototype at the top of the file: the function's header followed by ;.

Definition
Function prototype

A declaration that gives a function's name, return type and parameter types, but no body. For example: int max(int a, int b);. Header files such as stdio.h consist mostly of prototypes.

C
#include <stdio.h>

int max(int a, int b);
double average(int a, int b, int c);

int main(void) {
    printf("Max: %d\n", max(12, 31));
    printf("Average: %.2f\n", average(5, 4, 4));
    return 0;
}

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

double average(int a, int b, int c) {
    return (a + b + c) / 3.0;
}
Expected output
Max: 31
Average: 4.33
Thanks to the prototypes, main can stay at the top of the file. Dividing by 3.0 gives a fractional result: 13 / 3 ≈ 4.33.

void functions and passing by value

Some functions return no result — they just do a job, such as drawing a line on the screen. Their return type is void. The program below also shows a very important rule: in C, arguments are passed by value. A function receives a copy of the variable, not the variable itself, so changing the parameter does not affect the caller's variable.

C
#include <stdio.h>

void print_line(int n) {
    for (int i = 0; i < n; i++) {
        printf("-");
    }
    printf("\n");
}

void try_to_change(int x) {
    x = 100;
    printf("Inside: x = %d\n", x);
}

int main(void) {
    int x = 5;
    print_line(12);
    try_to_change(x);
    printf("Outside: x = %d\n", x);
    print_line(12);
    return 0;
}
Expected output
------------
Inside: x = 100
Outside: x = 5
------------
The x inside the function is a copy: it became 100, while x in main stayed 5.

You don't have to write every function yourself. The standard library's math.h header has ready-made math functions: sqrt (square root), pow (power), fabs (absolute value) and more. They take and return double values. On Linux you may need to add -lm at the end when compiling such a program.

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

int main(void) {
    printf("%.2f\n", sqrt(16.0));
    printf("%.0f\n", pow(2, 10));
    printf("%.1f\n", fabs(-7.5));
    return 0;
}
Expected output
4.00
1024
7.5

Recursion: a function that calls itself

A function can also call itself — this is called recursion. A recursive function has two parts: a base case that solves the problem immediately, and a recursive step that makes the problem a bit smaller and calls the function again. The classic example is the factorial: n! = n · (n − 1)!, with the base case 1! = 1.

C
#include <stdio.h>

long long factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main(void) {
    for (int i = 1; i <= 5; i++) {
        printf("%d! = %lld\n", i, factorial(i));
    }
    printf("20! = %lld\n", factorial(20));
    return 0;
}
Expected output
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
20! = 2432902008176640000
Factorials grow very fast, so the long long type (%lld) is used — 20! no longer fits in an int.
How is factorial(4) computed?

Trace the call factorial(4) step by step.

Show solution
factorial(4) = 4 · factorial(3)
factorial(3) = 3 · factorial(2)
factorial(2) = 2 · factorial(1)
factorial(1) = 1 — the base case, the calls stop.
Now the results travel back: 2 · 1 = 2, 3 · 2 = 6, 4 · 6 = 24.

Key points

  • A function has a return type, a name, parameters and a body; return gives back the result.
  • If a function is called before its definition, its prototype must appear at the top of the file.
  • A function that returns nothing has the type void.
  • In C, arguments are passed by value: the function works with a copy.
  • A recursive function must have a base case, or the stack overflows.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
What is a function prototype?