UP | HOME

5. Functions

Table of Contents


Rationale

Breaking your code down into discrete functions allows you to re-use code in intelligent ways, and to make your code more efficient overall. You can use functions written by other people (by linking them to your own programs) and avoid having to write everything from scratch yourself.

Writing functions to perform common tasks means that you can essentially write your own meta-language. For example if you have functions already written to perform these tasks, your C program might look like this:

#include <stdio.h>

int main() {
     loadSubjects();
     excludeSubject(12);
     lowpassFilterData();
     collectMeansWithBinSize(8);
     pval = performANOVA();
     printf("my hypothesis is correct, p < %.3f\n", pval);
     return 0;
}

The idea is that once we know what sorts of operations on our data we will wish to do, we can write a set of functions (sometimes called subroutines), to abstract away the details, and provide us with a sort of high-level meta-language that we can use to carry out the steps we wish to.

Abstraction

Using functions to abstract away the details also means that as long as the function inputs and outputs are known, then the user doesn't really need to know the details of how the function performs its task. Another way to think about this: let's imagine you wrote a function lowpassFilterData(), and it worked well. Let's say your friend came along one day and noticed that it was taking a long time to process your data, and he suggested you use a different algorithm for low-pass filtering, and he gave you a file containing the code. As long as the inputs and outputs are the same (or you could write your own "wrapper" function to ensure this), then you can simply switch out your function for the new one, and all functionality should be the same. You can think of functions as "black boxes" with input and output wires. As long as the wires are labelled the same, and there are the same number, you can replace one box with a newer box, and perhaps get faster (or more accurate, for example) functionality without changing your main program.

Defining a Function

A function must be defined in the following way:

returnType functionName (arg1Type arg1Name, arg2Type arg2Name) {
   function_statement;
   function_statement;
   return returnVar;
}

This seems rather abstract, but we will see a concrete example in a moment. On the first line, we have to begin by declaring what data type the function will return once it finishes. You can define a function that doesn't return anything by using void. The next item is the name of the function, which you get to choose. Next, in round brackets, is a list of input arguments that the function expects to see when it is called. Each argument is delared by listing first the type of the argument, followed by its name. Then in the body of the function is code, which does whatever you want the function to do. Finally we have to return a value (if the return type is non-void).

Here is a more concrete example. Let's say we want to write a function that writes a message to the screen, "Hello, Paul":

void writeMessage(void) {
   printf("Hello, Paul\n");
}

Here the return type is void since the function doesn't return a value, and the input argument list is also void, since it doesn't expect any inputs. We can call the function in a program like this:

#include <stdio.h>

void writeMessage(void) {
   printf("Hello, Paul\n");
}

int main() {
        writeMessage();
        return 0;
}
Hello, Paul

Input Arguments

Now let's modify our function so that it accepts one input argument, which is a string containing the name we want to say hello to. So for example we could call the function with writeMessage("Dave") and it would print to the screen Hello, Dave, or we could call it with writeMessage("Victoria") and it would write to the screen Hello, Victoria, etc:

#include <stdio.h>

void writeMessage(char name[]) {
   printf("Hello, %s\n", name);
}

int main() {
        writeMessage("Dave");
        writeMessage("Victoria");
        return 0;
}
Hello, Dave
Hello, Victoria

For the moment don't worry about the char[] type, it is a character array, and we will talk about arrays in the next section.

Let's give our function two input arguments now, just to see how this is done:

#include <stdio.h>

void writeMessage(char name[], int n) {
   printf("Hello, %s %d\n", name, n);
}

int main() {
        writeMessage("Dave", 123);
        writeMessage("Victoria", 444);
        return 0;
}
Hello, Dave 123
Hello, Victoria 444

Return Value

Let's consider another example, one where we want our function to return a value. Let's write a function to compute Fibonacci numbers (Wikipedia). Fibonacci numbers are defined as:

\begin{equation} F_{n} = F_{n-1} + F_{n-2} \end{equation}

where \(F_{0}=0\) and \(F_{1}=1\).

#include <stdio.h>

int Fibonacci(int n) {
        if (n==0) return 0;
        else if (n==1) return 1;
        else return Fibonacci(n-1) + Fibonacci(n-2);
}

int main() {
        int n = 10;
        int Fn = Fibonacci(n);
        printf("Fibonacci(%d) = %d\n", n, Fn);
        return 0;
}
Fibonacci(10) = 55

Here we define the return type as int, as we want our function to return an integer. We define one input argument, called n, which is also an int type. Then in the body of the function, we do our calculations.

Unlike in some languages such as Python, Matlab, and R, in C, functions can only return a single value. There is a way to achieve the same result however, which is to return a pointer to a complex data type such as an array, or a structure. We will talk about complex data types in the next section.

Recursion

Note in the Fibonacci example above, that in the body of our function, if the value of the input argument n is not 0 or 1, then the function ends up calling itself (on line 6 of the code listing). When a function calls itself, this is called recursion, or a recursive function call. Recursion allows for very compact code, and for intuitive definitions. You may see recursion used in mathematical functions, and also in algorithms like sorting and searching. The cost of recursion is that sometimes the overhead involved in the computer repeatedly calling functions over and over again, can be costly, but this really depends on the nature of the algorithm. For Fibonacci numbers, recursion is OK for small n but once n becomes large, it is really slow. As an exercise, you could try to re-code the Fibonacci function using a loop instead of recursion. Another thing to try is memoization (look it up, I didn't mis-spell it).

Argument Checking

Note that if we pass an argument of the wrong type to a function, the program may still compile, and even run, and it will simply spit out crazy values. Sometimes we will get a compiler warning, but sometimes not. Be very careful that the input values you pass to functions, and the output values you receive from functions, are what is expected.

Variable Scope

Any variables declared inside a function, are local to that function, and are not accessible outside of the function. Similarly, code within a function doesn't have access to variables that have been declared outside of that function (for example in another function, or in main()). If you want this functionality, then you can specify that a variable be global. Any variable declared outside of any function (it also has to be outside of main()) is global, and can be seen by every function. In C, global variables are known as external variables (they are external to any function).

For example in the following code, the varibale myGlob is declared outside of main() and outside of myFunc() and thus can be accessed by code within both. The variable myInt is declared within the function myFunc() and is thus local to myFunc() and cannot be accessed outside of myFunc() (for example within main()). Similarly, the variable myChar is declared within main() and so cannot be seen within myFunc().

#include <stdio.h>

float myGlob = 3.14;

void myFunc(void) {
        int myInt = 8;
        printf("my favourite number is %d\n", myInt);
        printf("my favourite float is %.2f\n", myGlob);
//      printf("my favourite letter is $c\n", myChar); // THIS WOULD NOT WORK
}

int main() {
        char myChar = 'x';
        printf("my favourite letter is %c\n", myChar);
        myFunc();
        printf("my favourite float is %.2f\n", myGlob);
//      printf("my favourite number is %d", myInt); // THIS WOULD NOT WORK
        return 0;
}
my favourite letter is x
my favourite number is 8
my favourite float is 3.14
my favourite float is 3.14

Automatic vs Static Variables

We talked about variable scope and the idea that variables declared within a function are local to that function. What actually happens is that each time a function is called by another piece of code, all the variables declared within the function are created (that is, memory is allocated to hold them). When a function is finished, all of that local memory storage is de-allocated, and those variables essentially disappear. This is known as automatic local variables (they are automatically created and then destroyed as the function is called, and then finishes).

If you want local variables to persist, you can declare them as static local variables. You simply insert the word static in front of the variable type when you declare it inside your function. When declared in this way, the variable will not be destroyed when the function exits, but it (and its value) will persist. Next time the function is called, the value will have retained the value from the previous function call. It's a sort of global variable, but one that is still only accessible within the function in which it's declared.

Here's an example program that maintains a running count of the number of times the function myFun() has been called.

#include <stdio.h>

void myFunc(void) {
        static int num = 0;
        num++;
        printf("myFunc() has been called %d times so far\n", num);
}

int main() {
        myFunc();
        myFunc();
        myFunc();
//      printf("num = %d\n", num);  // THIS WOULD NOT WORK
        return 0;
}
myFunc() has been called 1 times so far
myFunc() has been called 2 times so far
myFunc() has been called 3 times so far

When would you want to use static variables? One general case, like above, is when you want to keep track of the number of times a function has been called. Another reason has to do with efficiency… if for example your function declares a large local variable whose values don't change from one function call to the next, it may be more efficient to declare it as static, so that it is created and initialized only once.

Variadic Functions

A variadic function is one which accepts a variable number of input arguments. In C we can write functions that are variadic. Sometimes this may be useful.

Here is a simple example of how one would do this, taken from here. See this page for more details.

#include <stdarg.h>
#include <stdio.h>

int
add_em_up (int count,...)
{
  va_list ap;
  int i, sum;

  va_start (ap, count);         /* Initialize the argument list. */

  sum = 0;
  for (i = 0; i < count; i++)
    sum += va_arg (ap, int);    /* Get the next argument value. */

  va_end (ap);                  /* Clean up. */
  return sum;
}

int
main (void)
{
  /* This call prints 16. */
  printf ("%d\n", add_em_up (3, 5, 5, 6));

  /* This call prints 55. */
  printf ("%d\n", add_em_up (10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

  return 0;
}
16
55

Note we have to #include <stdarg.h> in order to use the handful of functions necessary to work with the argument list.

Note we have already seen a sort of variadic function, which is the main() function, which as we know can accept a variable number of input arguments when the program is started at the command line.

Exercises

function should be called fib and should take as input a single integer value n, and should return an integer value representing the nth Fibonacci number.

The code example in the notes uses recursion to accomplish this. Write your own function that doesn't use recursion, but uses a loop instead.

I should be able to paste your function at the bottom of this C program [ code ] and it should run:

// gcc -Wall -o go 5_1_go.c

#include <stdio.h>

int fib(int n);

int main(int argc, char *argv[])
{
  printf("fib(%d)=%d\n", 10, fib(10));
  return 0;
}

int fib(int n) {
  // your code goes here
}
$ gcc -o go 5_1_go.c
$ ./go
fib(10)=55
  • 2 Write a function that determines whether an integer is prime. The

function should take as input a single integer, and return a 1 if the input is prime, and a 0 if it is not.

I should be able to paste your function at the bottom of this C program [ code ] and it should run:

// gcc -Wall -o go 5_2_go.c

#include <stdio.h>

int isprime(int n);

int main(int argc, char *argv[])
{
  printf("isprime(%d)=%d\n", 12, isprime(12));
  printf("isprime(%d)=%d\n", 17, isprime(17));
  return 0;
}

int isprime(int n) {
  // your code goes here
}
$ gcc -o go 5_2_go.c
$ ./go
isprime(12)=0
isprime(17)=1
  • 3 Write a program that prints out the first 1000 prime numbers. You

can find a list to verify the correctness of your program here.

1: 2
2: 3
3: 5
4: 7
5: 11
... (deleted for brevity) ...
996: 7879
997: 7883
998: 7901
999: 7907
1000: 7919

Solutions


Paul Gribble | Summer 2012
This work is licensed under a Creative Commons Attribution 4.0 International License
Creative Commons License