Functions
Arrays, functions and pointers12 min readLesson 2 of 4
2.1 Why functions
A function groups statements under a name. You call it with arguments and read its return value. Splitting a program into functions keeps each piece short enough to read and lets you test one piece at a time.
Every function has a return type, a name, a parameter list, and a body: int square(int n) { return n * n; }.
void as the return type means the function returns no value. Parameters are the names inside the parentheses, and arguments are the values passed at the call.
#include <stdio.h>
int square(int n)
{
return n * n;
}
int main(void)
{
printf("square(7) = %d\n", square(7));
return 0;
}Output
square(7) = 49
2.2 Declarations and definitions
The compiler reads the file from top to bottom. It needs to know a function's name, return type, and parameter types before the first call. A function declaration, or prototype, gives that information and ends with a semicolon: int square(int n);.
The definition repeats the signature and adds the body in braces. A definition is also a declaration, so a file with the definition above main needs no separate prototype.
Put prototypes near the top of the file when definitions appear later, or move main to the bottom. Current compilers reject or warn about a call to a function that was never declared.
#include <stdio.h>
int maximum(int a, int b); /* prototype */
int main(void)
{
printf("%d\n", maximum(4, 9));
return 0;
}
int maximum(int a, int b)
{
return a > b ? a : b;
}Output
9
2.3 Passing values
C passes arguments by value. The parameter receives a copy, so assigning to the parameter inside the function leaves the caller's variable unchanged.
A function such as void add_one(int n) { n = n + 1; } increases its own copy. The caller's variable keeps the value it had.
To change a caller's variable, pass its address and let the function work through a pointer, which the pointers lesson covers.
Arrays differ from other arguments. An array argument becomes a pointer to its first element, so a function can modify the array's elements unless the parameter is declared const.
#include <stdio.h>
void try_to_change(int n)
{
n = 100;
printf("inside: %d\n", n);
}
int main(void)
{
int x = 5;
try_to_change(x);
printf("outside: %d\n", x);
return 0;
}Output
inside: 100 outside: 5
2.4 Return values
return ends the function and hands a value back. The value is converted to the declared return type, which is another place a double can lose its fraction when the function returns int.
A function whose return type is void can use return; to leave early. A function whose return type is not void must return a value on every path the caller can take.
Reaching the closing brace of a non-void function without a return is undefined behaviour for a caller that uses the result. main is the exception: it returns 0 when control reaches the end.
Keep one return at the end, or one in each branch that decides the result. A function with many scattered returns is harder to check.
#include <stdio.h>
void report(int value)
{
if (value < 0) {
printf("Negative values are not allowed.\n");
return;
}
printf("Value: %d\n", value);
}
int main(void)
{
report(-3);
report(12);
return 0;
}Output
Negative values are not allowed. Value: 12
2.5 Local scope
A variable declared inside a function body is local to that function. It exists while the function runs and disappears when the function returns. Another function cannot see it, even if that function uses the same name.
Every call gets a fresh set of local variables. A recursive function therefore has its own copy of each local variable at every level of the recursion, which keeps the smaller subproblems from overwriting each other's data.
A block inside a function can declare its own variables, and those disappear at the closing brace of that block.
#include <stdio.h>
int 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! = %d\n", i, factorial(i));
}
return 0;
}Output
1! = 1 2! = 2 3! = 6 4! = 24 5! = 120
2.6 Designing small functions
A function that does one job is easier to name and test. Inputs arrive as parameters and the result leaves through return. A function that reads a global variable or prints as a side effect is harder to reuse, though printing is a reasonable main job in a practice program.
Give a function a name that says what it produces, such as average or is_even. A name like process forces the reader to open the body.
Keep the parameter list short. When several related values travel together, a struct is often the better container.
#include <stdio.h>
int is_even(int n)
{
return n % 2 == 0;
}
double average(int a, int b, int c)
{
return (a + b + c) / 3.0;
}
int main(void)
{
printf("is_even(10) = %d\n", is_even(10));
printf("average = %.2f\n", average(4, 7, 10));
return 0;
}Output
is_even(10) = 1 average = 7.00
