Functions in C — practice with 3 bus programs: searches with two results, arrays that transform and the route statistics (with scanf)
There’s a moment in IC2 when the lecturer writes two almost identical functions on the board and asks which one modifies the original array. One takes int v[] and the other int *v. Nearly the whole class points at the second one, convinced the asterisk makes the difference. Both modify it: an array in C always travels as an address, however you declare it. This practice is about turning that idea, and the idea of passing variables by reference, into tools for solving real problems.
This is the hands-on part of the functions in C theory. There are three complete, medium-level programs, all using bus route 201 and written in the IC2 lab style (prototypes at the top, main in the middle and definitions at the end): search functions over the trips’ delays, functions that transform arrays in place and a statistics calculator that reads the passengers at each stop with scanf. The data have what real data have: repeats, negatives and gaps. Every output comes from a real run with gcc 14.
Table of Contents
Before you start
You should be comfortable with:
- How to write a prototype, a definition and a call, and what
voidmeans. - The difference between passing
x(a copy) and passing&x(the address) to anint *parameter. - That an array reaches the function as the address of its first box, and its size has to be passed separately.
- What
returnis and that it can only return one value.
If any of these feels shaky, go over the theory (and pointers in C) before carrying on.
Compile each program with gcc -Wall -std=c11 program.c -o program. With -Wall gcc warns you about almost every mistake in this topic: calls without a prototype, paths without return and sizeof on an array parameter. You can also try them in the site’s C playground; the third one reads from the keyboard, so type the data in the Input tab.
Program 1: search functions on arrays
We have the delays, in minutes, of route 201’s 10 trips on one day. We want several queries: on which trip there was a given delay, which was the worst and the best (and on which trip), how many times a delay repeats and whether the data are sorted. Each query will be a function.
The idea
A search has to return two things: whether it found the value and where. There are two patterns, and both come up in IC2:
- Pattern 1: return the position with
returnand use-1as the “not there” signal, because it’s never a valid position. - Pattern 2: return 1 or 0 with
returnand leave the position in one ofmain‘s variables through a pointer (int *pos).
The maximum and minimum use the second pattern the other way round: the value goes out via return and the position via the pointer.
Before you code, write each function’s signature and mark which parameters are inputs (the array, n, the value searched for) and which are outputs (int *pos). The outputs will carry & in the call.
The code
#include <stdio.h>
/* Prototypes: at the top, so main can use them */
int find(int v[], int n, int target);
int find_pos(int v[], int n, int target, int *pos);
int max_with_pos(int v[], int n, int *pos);
int min_with_pos(int v[], int n, int *pos);
int count(int v[], int n, int value);
int is_sorted(int v[], int n);
void show(int v[], int n);
int main(void) {
int delays[10] = {42, 7, 19, 7, 85, 26, 7, 54, 13, 7}; /* minutes, 10 trips */
int sorted_ones[5] = {1, 3, 5, 7, 9};
int n = 10, pos, r;
printf("Delays: ");
show(delays, n);
r = find(delays, n, 85); /* pattern 1: return with -1 */
if (r != -1) printf("85 min: trip %d\n", r);
r = find(delays, n, 99);
if (r == -1) printf("99 min: no trip\n");
if (find_pos(delays, n, 26, &pos)) /* pattern 2: 1/0 and position via pointer */
printf("26 min: trip %d\n", pos);
int max = max_with_pos(delays, n, &pos);
printf("\nWorst delay: %d min (trip %d)\n", max, pos);
int min = min_with_pos(delays, n, &pos);
printf("Best delay: %d min (trip %d)\n", min, pos);
printf("Trips with 7 min: %d\n", count(delays, n, 7));
printf("\nDelays sorted? %s\n", is_sorted(delays, n) ? "Yes" : "No");
printf("{1,3,5,7,9} sorted? %s\n", is_sorted(sorted_ones, 5) ? "Yes" : "No");
return 0;
}
/* Position of the first match, or -1 (an impossible index) */
int find(int v[], int n, int target) {
for (int i = 0; i < n; i++)
if (v[i] == target)
return i; /* the first one and out */
return -1;
}
/* Returns 1/0 and leaves the position in *pos: two results */
int find_pos(int v[], int n, int target, int *pos) {
for (int i = 0; i < n; i++)
if (v[i] == target) {
*pos = i; /* write into main's variable */
return 1;
}
return 0; /* *pos isn't touched */
}
/* The value via return; the position, via pointer */
int max_with_pos(int v[], int n, int *pos) {
int p = 0;
for (int i = 1; i < n; i++)
if (v[i] > v[p]) p = i;
*pos = p;
return v[p];
}
int min_with_pos(int v[], int n, int *pos) {
int p = 0;
for (int i = 1; i < n; i++)
if (v[i] < v[p]) p = i; /* < and not <=: keep the first one */
*pos = p;
return v[p];
}
int count(int v[], int n, int value) {
int c = 0;
for (int i = 0; i < n; i++)
if (v[i] == value) c++;
return c;
}
/* 1 if each element is <= the next one */
int is_sorted(int v[], int n) {
for (int i = 0; i < n - 1; i++) /* n - 1: I compare with v[i + 1] */
if (v[i] > v[i + 1])
return 0;
return 1;
}
void show(int v[], int n) {
for (int i = 0; i < n; i++)
printf("%d ", v[i]);
printf("\n");
}
Output
Delays: 42 7 19 7 85 26 7 54 13 7
85 min: trip 4
99 min: no trip
26 min: trip 5
Worst delay: 85 min (trip 4)
Best delay: 7 min (trip 1)
Trips with 7 min: 4
Delays sorted? No
{1,3,5,7,9} sorted? Yes
Check it with the data {42, 7, 19, 7, 85, 26, 7, 54, 13, 7}: 85 is in box 4 and 26 in box 5; 99 isn’t there and find returns -1. The worst delay is 85 (trip 4); the best, 7, appears on trips 1, 3, 6 and 9, and the function keeps the first because it compares with < and not <=. The 7 appears 4 times. The delays aren’t sorted (42 > 7 in the very first comparison) and {1, 3, 5, 7, 9} is.
Trace of max_with_pos, which keeps the position of the best seen so far (p), not its value:
i | v[i] | v[p] before | v[i] > v[p]? | p after |
|---|---|---|---|---|
| 1 | 7 | 42 | No | 0 |
| 2 | 19 | 42 | No | 0 |
| 3 | 7 | 42 | No | 0 |
| 4 | 85 | 42 | Yes | 4 |
| 5…9 | 26, 7, 54, 13, 7 | 85 | No | 4 |
At the end, *pos = 4 writes into main‘s pos and return v[4] returns 85.
Questions you might ask
Why -1 and not 0 for “not found”? Because 0 is a valid position: the first trip. If find returned 0 when it found nothing, you wouldn’t know whether the value is in box 0 or isn’t there.
Why doesn’t find_pos touch *pos if it doesn’t find it? Because when the function returns 0 it only promises one thing: that the value isn’t there. main mustn’t read pos in that case, which is why it uses it inside the if. Setting *pos to a made-up value would only hide mistakes.
Why does is_sorted go up to n - 1? Because it compares each box with the next, v[i + 1]. If i reached n - 1, v[i + 1] would be box n, which doesn’t exist.
Can a function call show if it’s defined afterwards? Yes, because its prototype is at the top. The order of the definitions doesn’t matter as long as all the prototypes come before the first call.
Calling find_pos(delays, n, 26, pos) without &. pos is an int with no value and the function would use it as an address. gcc 14 makes it a compile error (int where int * is expected).
Try it yourself
- Write
int find_last(int v[], int n, int x), which returns the last match by walking the array from back to front. - Make
min_with_poskeep the last occurrence of the minimum. Which single character do you have to change?
Program 2: functions that transform arrays
Now the functions don’t query: they change the array. We reverse the day’s departures, rotate them (the first shift goes to the end, or the last to the front), filter the positive passenger changes into a new array and remove repeated route numbers from a list.
The idea
Since the array arrives as an address, any v[i] = … inside the function changes main‘s: no return needed. The delicate part is two functions that also change a size:
filter_positivescreates a new array and has to tellmainhow many elements it put in: it receivesint *m.remove_repeatsshrinks the array itself: it receivesint *nand decrements it every time it closes a gap.
And rotate_k reuses rotate_right: one function calling another instead of repeating code.
Before you code, draw the boxes and decide the direction of each walk. If you’re shifting elements right, walk from back to front; if you’re shifting them left, from front to back. In the wrong direction, the first value gets copied into every box.
The code
#include <stdio.h>
void swap(int *a, int *b);
void reverse(int v[], int n);
void rotate_right(int v[], int n);
void rotate_left(int v[], int n);
void rotate_k(int v[], int n, int k);
void filter_positives(int v[], int n, int res[], int *m);
void remove_repeats(int v[], int *n);
void show(const char *title, int v[], int n);
int main(void) {
int departures[8] = {10, 20, 30, 40, 50, 60, 70, 80}; /* minutes after 9:00 */
show("Departures: ", departures, 8);
reverse(departures, 8);
show("Reversed: ", departures, 8);
reverse(departures, 8); /* again: back as they were */
rotate_right(departures, 8);
show("Rotate right: ", departures, 8);
rotate_left(departures, 8);
rotate_left(departures, 8);
show("Rotate left x2: ", departures, 8);
int zones[5] = {1, 2, 3, 4, 5};
rotate_k(zones, 5, 2);
show("Zones, rotate 2: ", zones, 5);
int change[8] = {-3, 7, -1, 15, -8, 4, 0, 12}; /* on minus off */
int positives[8], m;
filter_positives(change, 8, positives, &m); /* &m: the function fills it in */
printf("Positives (%d): ", m);
show("", positives, m);
int routes[9] = {201, 202, 201, 301, 202, 201, 303, 301, 210};
int n = 9;
remove_repeats(routes, &n); /* &n: the size changes */
printf("Routes (%d): ", n);
show("", routes, n);
return 0;
}
void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
/* Only up to the middle: n / 2 swaps */
void reverse(int v[], int n) {
for (int i = 0; i < n / 2; i++)
swap(&v[i], &v[n - 1 - i]);
}
/* [1,2,3,4] -> [4,1,2,3] */
void rotate_right(int v[], int n) {
int last = v[n - 1];
for (int i = n - 1; i > 0; i--) /* from back to front */
v[i] = v[i - 1];
v[0] = last;
}
/* [1,2,3,4] -> [2,3,4,1] */
void rotate_left(int v[], int n) {
int first = v[0];
for (int i = 0; i < n - 1; i++) /* from front to back */
v[i] = v[i + 1];
v[n - 1] = first;
}
/* Reuses another function: k one-box rotations */
void rotate_k(int v[], int n, int k) {
for (int i = 0; i < k % n; i++)
rotate_right(v, n);
}
/* Copies the positives to res and leaves how many in *m */
void filter_positives(int v[], int n, int res[], int *m) {
*m = 0;
for (int i = 0; i < n; i++)
if (v[i] > 0) {
res[*m] = v[i];
(*m)++; /* brackets: the value, not the pointer */
}
}
/* Leaves each number once, in the order it appears */
void remove_repeats(int v[], int *n) {
int i = 0;
while (i < *n) {
int j = i + 1;
while (j < *n) {
if (v[j] == v[i]) {
for (int k = j; k < *n - 1; k++) /* close the gap */
v[k] = v[k + 1];
(*n)--; /* the array shrinks */
} else {
j++;
}
}
i++;
}
}
void show(const char *title, int v[], int n) {
printf("%s", title);
for (int i = 0; i < n; i++)
printf("%d ", v[i]);
printf("\n");
}
Output
Departures: 10 20 30 40 50 60 70 80 Reversed: 80 70 60 50 40 30 20 10 Rotate right: 80 10 20 30 40 50 60 70 Rotate left x2: 20 30 40 50 60 70 80 10 Zones, rotate 2: 4 5 1 2 3 Positives (4): 7 15 4 12 Routes (5): 201 202 301 303 210
- Reverse does 8 / 2 = 4 swaps (10↔80, 20↔70, 30↔60, 40↔50). Reversing twice leaves the array as it was.
- Rotate right once: 80 moves to the front. Rotate left twice on top of that: 80 goes back to the end and 10 goes after it. Result: 20 … 80 10.
- Rotate 2 on the zones {1, 2, 3, 4, 5}: two right rotations, 4 5 1 2 3. The
k % nmakes rotating 7 the same as rotating 2. - Filter: from {-3, 7, -1, 15, -8, 4, 0, 12} come 7, 15, 4 and 12 (0 isn’t positive) and
mis 4. - Remove repeats: out of nine route numbers, five are left, in the order they first appeared.
Trace of remove_repeats on {201, 202, 201, 301, 202, 201, 303, 301, 210}:
i | v[i] | Repeats it closes | Array afterwards | *n |
|---|---|---|---|---|
| 0 | 201 | boxes 2 and 5 | 201 202 301 202 303 301 210 | 7 |
| 1 | 202 | box 3 | 201 202 301 303 301 210 | 6 |
| 2 | 301 | box 4 | 201 202 301 303 210 | 5 |
| 3 | 303 | none | same | 5 |
| 4 | 210 | none | same | 5 |
Notice that when it closes a gap, j does not move on: the next number has just moved into box j and it has to be checked too.
Questions you might ask
Why (*m)++ and not *m++? Because ++ comes before *: *m++ would move the pointer m (the local copy of the address) and main‘s counter would stay at 0. With the brackets, the value pointed to is incremented.
Why res[*m] = v[i]? Because *m is both the counter and the first free box of the result array: with 0 positives it writes to box 0, with 1 to box 1…
Why does filter_positives need an 8-box array if only 4 come out? Because the function doesn’t know in advance how many it will find: in the worst case they’re all positive. The destination array needs room for all of them.
Why does rotate_k do k % n rotations? Rotating an n-box array n times leaves it unchanged. With the remainder, rotating a 5-box array 12 times is rotating it 2, and you save 10 useless laps.
Passing n by value to remove_repeats. The function would shrink its copy, main would still think there are 9 numbers and would print the leftovers at the end of the array.
Try it yourself
- Write
void scale(int v[], int n, int factor), which multiplies every box, and use it to turn minutes into seconds. - Change
remove_repeatsso that it also returns, withreturn, how many numbers it removed, as well as updating*n.
Program 3: the route statistics calculator
The last program brings everything together: it reads from the keyboard how many stops there are and how many passengers got on at each, and works out the mean, the median, the mode (with how many times it repeats) and the variance. Each statistic is a function, and one of them calls another.
The idea
read_data fills main‘s array with scanf and returns how many values it read (0 if something fails, so main stops). The statistics are functions that return a double. Two important decisions:
- The median needs sorting, but if it sorted
main‘s array it would leave it changed for the other functions. It works on a local copy. - The mode returns two things: the value (via
return) and how many times it appears (via the pointerint *frequency).
Before you code, decide which functions may change the array and which may not. Only read_data should write to it; for the others, you can declare the parameter const int v[] and gcc will protect you.
The code
#include <stdio.h>
#define MAX 100
int read_data(int v[], int max);
double mean(int v[], int n);
double median(int v[], int n);
int mode(int v[], int n, int *frequency);
double variance(int v[], int n);
void sort(int v[], int n);
int main(void) {
int passengers[MAX];
int n = read_data(passengers, MAX);
if (n == 0) return 1; /* no data, no statistics */
int times;
int m = mode(passengers, n, ×); /* value via return, times via pointer */
printf("\n=== ROUTE 201 STATISTICS ===\n");
printf("Stops: %d\n", n);
printf("Mean: %.2f\n", mean(passengers, n));
printf("Median: %.1f\n", median(passengers, n));
printf("Mode: %d (%d times)\n", m, times);
printf("Variance: %.2f\n", variance(passengers, n));
printf("Data untouched: ");
for (int i = 0; i < n; i++) printf("%d ", passengers[i]);
printf("\n");
return 0;
}
/* Reads how many values there are and the values; returns n (0 if something fails) */
int read_data(int v[], int max) {
int n;
printf("How many stops? (1-%d): ", max);
if (scanf("%d", &n) != 1 || n < 1 || n > max) {
printf("Invalid number of stops\n");
return 0;
}
for (int i = 0; i < n; i++) {
printf(" Passengers at stop %d: ", i + 1);
if (scanf("%d", &v[i]) != 1) return 0; /* &v[i]: address of that box */
}
return n;
}
double mean(int v[], int n) {
int sum = 0;
for (int i = 0; i < n; i++) sum += v[i];
return (double)sum / n; /* the cast, BEFORE dividing */
}
/* Sorts a COPY: main's array isn't touched */
double median(int v[], int n) {
int copy[MAX];
for (int i = 0; i < n; i++) copy[i] = v[i];
sort(copy, n);
if (n % 2 == 1) return copy[n / 2];
return (copy[n / 2 - 1] + copy[n / 2]) / 2.0; /* even: mean of the two in the middle */
}
int mode(int v[], int n, int *frequency) {
int best = v[0];
*frequency = 0;
for (int i = 0; i < n; i++) {
int c = 0;
for (int j = 0; j < n; j++)
if (v[j] == v[i]) c++;
if (c > *frequency) { /* > and not >=: the first to appear wins */
*frequency = c;
best = v[i];
}
}
return best;
}
/* Calls another function: mean */
double variance(int v[], int n) {
double m = mean(v, n), s = 0;
for (int i = 0; i < n; i++)
s += (v[i] - m) * (v[i] - m);
return s / n;
}
void sort(int v[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (v[j] > v[j + 1]) {
int t = v[j]; v[j] = v[j + 1]; v[j + 1] = t;
}
}
Output
With the passengers at 8 stops (15, 8, 42, 8, 27, 19, 8 and 35):
How many stops? (1-100): 8 Passengers at stop 1: 15 Passengers at stop 2: 8 Passengers at stop 3: 42 Passengers at stop 4: 8 Passengers at stop 5: 27 Passengers at stop 6: 19 Passengers at stop 7: 8 Passengers at stop 8: 35 === ROUTE 201 STATISTICS === Stops: 8 Mean: 20.25 Median: 17.0 Mode: 8 (3 times) Variance: 151.94 Data untouched: 15 8 42 8 27 19 8 35
Checked by hand:
- Mean: 15 + 8 + 42 + 8 + 27 + 19 + 8 + 35 = 162, and 162 / 8 = 20.25.
- Median: sorted they are 8 8 8 15 19 27 35 42. With 8 values (even) it’s the mean of the two in the middle, (15 + 19) / 2 = 17.0.
- Mode: 8 appears 3 times; nothing else repeats.
- Variance: the mean of the squared distances to 20.25. For example, 42 contributes (42 − 20.25)² = 473.06; adding all eight and dividing by 8 gives 151.94.
- Data untouched:
main‘s array is still in the order it was typed, because the median sorted its copy.
Trace of mode (only the laps where something changes):
i | v[i] | Times it appears (c) | c > *frequency? | best / *frequency |
|---|---|---|---|---|
| 0 | 15 | 1 | Yes (1 > 0) | 15 / 1 |
| 1 | 8 | 3 | Yes (3 > 1) | 8 / 3 |
| 2…7 | 42, 8, 27, 19, 8, 35 | 1 or 3 | No | 8 / 3 |
Questions you might ask
Why &v[i] in the scanf? scanf needs the address where it should leave the number. v[i] is the integer in box i; &v[i] is its address. v + i would work too.
Why does read_data return n instead of taking int *n? Both work. Returning it gives a cleaner call (int n = read_data(...)) and uses 0 as the error signal. What matters is that the size reaches main one way or another.
Why / 2.0 in the median? With two ints, (15 + 18) / 2 would give 16 instead of 16.5. The 2.0 forces a decimal division.
Why does variance call mean instead of working it out again? To avoid repeating code: if one day you change how the mean is calculated, you only change it in one place. A function can call another as long as its prototype is at the top.
What happens if I type letters instead of a number? scanf returns 0, read_data returns 0 and main ends with return 1 without calculating anything. Better than dividing by an n that has no value.
Sorting the original array inside median. The statistics would come out right, but “Data untouched” would come out sorted, and any function called afterwards would see the data in a different order. A function that only has to look shouldn’t leave a trace.
Try it yourself
- Add
int stops_above_mean(const int v[], int n)and show how many stops are above the mean. - Make
read_dataask again if the number of stops isn’t valid, instead of ending.
Common mistakes in this practice
- Forgetting the
&when calling a function with output parameters:find_pos(v, n, x, pos). *m++instead of(*m)++to increment a counter received through a pointer.- Passing the size by value to a function that changes it (
remove_repeats). - Using 0 as “not found” when 0 is a valid position.
- Sorting the original array in a function that should only look.
- Walking in the wrong direction when shifting elements, copying the same value into every box.
Watch it step by step in Python Tutor
In Python Tutor, choosing C, a function that returns one value via return and another via a pointer is easy to see. Paste this program:
#include <stdio.h>
int max_with_pos(int v[], int n, int *pos) {
int p = 0;
for (int i = 1; i < n; i++) {
if (v[i] > v[p]) {
p = i;
}
}
*pos = p;
return v[p];
}
int main(void) {
int delays[5] = {42, 7, 85, 26, 13};
int pos;
int max = max_with_pos(delays, 5, &pos);
printf("%d on trip %d\n", max, pos);
return 0;
}
What to watch step by step:
- Before the call,
posexists inmain‘s frame but has no value. - On entering
max_with_pos,vandposare two arrows: one tomain‘s array and one to itsposvariable. - During the
for, only the local variablepchanges: 0, and then 2 when 85 appears. - On
*pos = p;main‘sposbox is filled in, at the other end of the arrow. - On the way back,
return v[p]reachesmaxand the function’s frame disappears. It should print85 on trip 2.
Summary
- An array reaches the function as an address:
int v[]andint *vare the same and both let you modify the original. - To “return” several things, one goes out via
returnand the others via pointers; in the call, the outputs carry&. - For “not found”, a value that’s never valid (-1 for a position) or a 1/0
returnwith the position via pointer. - If a function changes an array’s size, it receives
int *nand uses(*n)--or(*n)++. - A function that only looks works on a copy (or receives
const int v[]) so it leaves no trace. - A function can reuse another (
rotate_k,variance) if its prototype is at the top.
Checklist: have you practised it?
- Have you run the three programs and checked the outputs?
- Do you know when to use the
-1pattern and when the 1/0 one with the position via pointer? - Do you know why
(*m)++needs the brackets? - Can you explain why the median works on a copy?
- Have you checked the mean and the median by hand?
- Have you tried the “Try it yourself” ideas without looking at the theory?
If you answered yes to almost all of them, you’re ready for the functions in C exercises, where I don’t hand you the code. See you there!
Next step: exercisesFunctions in C — solved exercises with the bus: the second worst delay, stops that close and open and timetables that merge (plus the cheat sheet for the exam)Did this article help you? Here is how you can support SergioLearns →




The hardware that holds up the operating system — theory with my IC2 assignment on the printer, an interrupt that stops everything and a cache that hits (and the infinite loop that didn’t freeze my laptop)