Functions
Programs in C are usually divided into a set of functions,
each with a special purpose. Functions in C are an extension of the
mathematical idea of a function.
Mathematical Functions
A relation is a set of ordered pairs
R = {(x, y)}
where the x's come from a set called the domain and
the y's come from a set called the range. A
function is a special kind of relation F that has the propert
that if (a, b) is in F and
(a, c) is in F, then b = c.
If (x, y) is in a function F, then we write
F(x) = y.
Some examples of functions are the sine function y = sin x,
the exponential function y = exp (x), and the absolute
value function y = |x|. The domain for sine is
the set of real numbers; the range is the set of reals in the interval [-1..1].
The domain for the exponential function is the set of reals; the range
is the set of positive reals. The domain for absolute value is the
set of all reals; the range is the set of all positive reals.
For the purposes of computing, the domain variable (x here)
is called a parameter, and the range variable (y here)
is called the return value; the function is said to
return the return value.
We can think of functions with other kinds of domains and ranges. For
example, some functions are predicates, whose domains are
the set of truth values. Consider a function p(n)
whose domain is the set of positive integers and range is the set
{0, 1}. p(n) = 1 if and only if n is prime.
This is a different kind of function to think about, with a finite
range, but is just as much a function as sine or exponential.
Or, think about a function with more than one parameter. We usually
don't think of basic arithmetic operations like addition and multiplication
as being functions, but we can: consider a function
y = add (x, z) where the return value is the
sum of the two parameters (what is the domain?).
C Functions
We have seen several C functions, such as exp, sin
and sqrt. It turns out printf and scanf
are also functions; we usually ignore their return values, though.
C allows us to write our own functions. Indeed, most of the C
functions we use like printf etc. are written in C.
The format for defining a C function is:
type name ( parameter-list);
{body}
Where the parameter-list is a list of parameter declarations
(a lot like variable declarations) separated by commas. This list can
be empty for functions that don't accept any parameters. Each parameter
name must be preceded by a type, like int or float.
The body enclosed in curly braces is some C code that computes the
function, then returns the computed value. The body may also do other
things, like printing output or changing values.
Here is a simple example. The following function, named twice,
returns the double parameter multiplied by two:
double twice (double a) {
return a * 2.0;
}
The return statement is a control structure that causes the function
to end and returns the value to whatever C code called the function.
We could use this function like this:
int main () {
double x, y;
x = 20.0;
y = twice (x);
printf ("%f\n", y);
}
and the output of the program would be "40.0".
When we use the function like this, we are calling or invoking
the function. When calling a function, the stuff between the parentheses
can be any expression in the domain of the function (x in this case),
and is called the argument (or arguments, if there is more than
one). When the function begins execution, the variable-like identifier(s)
it uses (a in this case) is(are) called the parameter(s).
We are said to pass arguments to functions.
Call By Value
C functions have the call by value property. This means that
when you pass a variable to a function, the value of the variable
is passed, not the variable itself. So if you change the value of a parameter
in a function, the original argument remains unchanged. For example,
let's look at a function that computes the square of an integer, then
returns it:
int square (int i) {
i = i * i;
return i;
}
and now let's use this function to find the square of some number:
int main () {
int a, b;
a = 10;
b = square (a);
printf ("%i\n", b);
}
What is the value of a at the end of this program? It is still
10, even though the value of i at the end of the square
function is 100 (10 squared). b, of course, is 100, having been
assigned the return value of square.
Properties of Functions
You can do anything in a C function that you can in main; main
itself is just another C function, it just happens to be the first one
executed when Unix runs your C program. Functions in C are not as restricted
as their mathematical counterparts; they can print things and affect
the program's state in other ways, and they don't have to return a value.
If a function is intended to compute and return a value, it should be
idempotent; that means it always returns the same value given
the same parameters and should not change the program state by printing
something out or changing other variables. This is not a rule of C,
it's just a good idea so that our functions don't go around doing things
without us knowing; it is sometimes appropriate to break this rule.
A function that is intended to do something like print something or
change values of variables, but not compute any value to return, may
be given a return type of void, meaning "doesn't return anything."
Functions are also often used to break up a C program into smaller
components. You wouldn't want a huge program to be all in main;
it would be hard to follow the concepts because of all the details
(can't see the forest for the trees). Functions can be contained in
separate files and compiled separately from one another, so if a
single function in a huge program is changed, we need not recompile the
whole program, just that one function.
Function definitions or declarations must appear before they are first used.
A function declaration without a definition (using a semicolon instead
of a body contained in curly braces) can let the compiler know that the
function exists somewhere so the compiler will compile code using that
function properly. If you look in stdio.h, for example,
you'll see a lot of these function declarations (it's usually in
/usr/include/stdio.h). The functions aren't defined there;
they're in some library somewhere else, but these declarations inform
the compiler of their existence.
More C Functions
Here are some examples of C functions.
This function finds the absolute value of its float parameter.
There's already a function in math.h called fabs
that does this, but that takes all the fun out of doing it yourself:
float absolute (float f) {
if (f < 0)
return -f;
else
return f;
}
The factorial of a non-negative integer n is
the product of all the integers from 1 to n, usually written
n!. The factorial of 0 is defined as 1. This C function
computes n! and works for 0:
int factorial (int n) {
int i, product;
product = 1;
for (i=1; i<=n; i++) product = product * i;
return product;
}
Now that we have this function, we can use it anywhere just like we would
sin or sqrt, except we would use it on ints
instead of floats.
Now let's look at that predicate that returns 1 if and only if its
integer parameter is a prime number:
int is_prime (int n) {
int i;
for (i=2; i<n; i++) if (n % i == 0) return 0;
return 1;
}
Notice how if a factor of n is ever found, we immediately
return from the function with a value of false (0), and return
true (1) only if we made it all the way though the for loop.
We can use this function to write a C program that prints the prime
numbers from 2 through 100:
int main () {
int i;
for (i=2; i<=100; i++) if (is_prime (i)) printf ("%i\n", i);
exit (0);
}
You might notice that the variable name i is declared in
both is_prime and in main. Each time through
the for loop in main, the other i is
changed. This is OK; they are two different variables. A variable
declared inside a function, also known as a local variable,
only exists inside that function. If the same name is used in a different
function, it's a different variable and is unrelated to the other variable,
like two different people with the same first name.
Now let's look at a function whose purpose is not to compute something
but to print something. It won't return anything, so we'll declare it
as returning void. It doesn't need any parameters, so we'll
leave the parameter list empty (we can also stick the word void
in there if we want to):
void print_prompt (void) {
printf ("Please enter a number: ");
}
This function simply prints a prompt and returns. It can be used by
main or some other function as a prelude to a scanf.
We can go one more step, actually reading in a number and returning it:
float get_number (void) {
float f;
printf ("Please enter a number: ");
scanf ("%f", &f);
return f;
}
We can use this function somewhere we need to get a number from the
user, just by assigning a variable like this:
int main () {
float x;
x = get_number ();
printf ("You entered %f\n", x);
exit (0);
}
Extra for Experts
Here's another definition for the factorial function:
int factorial (int n) {
if (n == 0) return 1;
return n * factorial (n-1);
}
This takes advantage of the fact that n! is the same as
n times (n-1)!, and that the factorial of 0 is 1.
This is an example of a recursive function that is defined in
terms of itself; they key to understanding it is that each "copy" of
it that is called has a different value and different memory location
for its version of the parameter n. Recursion is a powerful
technique; often, using a recursive function cuts down on the code
and reduces the complexity of a problem. We'll see more recursive
functions in the future...