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...