Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming Functions
Back to top
On this page
Contents

Functions, or how to think about fewer things at a time

Functions are one of the two key high-level skills in C programming (the other being the handling of data). For this reason before considering how to write functions we shall briefly consider:

  • What is a function?
  • Why are functions useful?

What is a function?

We've already written one function, main() and used standard functions such as sqrt() etc.

There is nothing special about the standard functions such as sin() and sqrt() except that they have already been written for us and there is nothing special about main() apart from the fact that it is the first function run when the program starts and its return value is returned to what ran the program rather that to the function that called it.

Everything we have learnt about writing main() and using built-in functions also applies to writing other functions, we are just extending what we have already done.

A separate piece of code with its own local variables

The basic concept of a function is extremely simple:

A function is a self-contained piece of code which can have its own local variables and either performs a specified task or calculates and returns a value.

A function is almost like a separate "mini-program" that we can write separately from the rest of the program, without having to think about the rest of the program when we are writing it.

Examples of the "perform a task" type of function include printf() whilst the "calculate and return a value" type include the math.h functions sin(), cos(), sqrt(), etc.

It can be given arguments

A function can be given arguments

Passing values to functions

When we call functions such as sin() or sqrt() we have to tell them what value we want to find the sine or square-root of.

When we have called functions we have passed them arguments which are just mathematical expressions which are evaluated and the result is passed to the function so it knows exactly what to do. For example: calculate the square-root of 3.14.

Receiving the values into the function

In our first lab session with logo we could define a procedure with the names of some variables following it:

 to polypolygon :POLYS :SIDES   and if we then typed polypolygon 5 3 then the procedure would run with the variable POLYS equal to 5 and the variable SIDES equal to 3.

C uses a similar simple mechanism for functions to receive the values of their arguments which extends the fact that functions can have their own local variables and that these variables can be given initial values when they are declared.

Functions have special local variables called parameters whose values are initialised with the value of the arguments they were called with.

(We shall see how to do this below.)

It can return a value

A function can return a value

Just like main() any function can return a value to whatever called it via the return statement. An obvious example of this is that sqrt() returns the square root of its argument, and the type of this return value is a double.

Why are functions useful?

Before we see how to write other functions we should ask "Why bother? Why not just put everything inside main()?".

The purpose of a function is to make life easier for the person calling it.

This leads to the useful criterion:

Decisions about what a function should do, or whether the function should exist at all, are usually answered by asking the question "which choice makes it easier for the person who calls this function?"

The usefulness of functions comes from a combination of two factors.

1. Code that gets used more than once

Code that would otherwise need to appear more than once in a program should be made into a function.

Example: the simple "raise to an integer power" code

Recall the code snippet from the previous lecture that calculated a power for a positive integer exponent:

   result = 1.0;
  while ( exponent > 0 ) { // Positive exponent, as before
    result *= base;
    --exponent;
  }

When faced with wanting to use this piece of code twice, we may be tempted to just copy-and-paste it to a new place, thus having two copies of the above snippet in our code. A much better approach is to make it into a function and to then call that function twice.

Of course, if we know we are likely to use some code several times we should write it as a function at the very beginning. But if we do write some code and then find we need to use it again, we mustn't be afraid to convert it into its own separate function. This is usually the better option.

Why use a function, not copy and paste?

  1. It's shorter. This is surprisingly important as we will spend quite a lot of time looking through our code trying to find mistakes (bugs). More code means more to look through.
  2. It's easier to test.
  3. It's easier to fix and extend.

2. Hiding complexity

  1. Example: organising a party
  2. To let us consider the issues in a more-familiar context.
  3. A group of friends are organising an event such as a wedding reception or a 50th birthday party for a well-off relative. You have a budget of a few thousand pounds for around 100 guests.
  4. First imagine you are the coordinator. Think of some examples of tasks that could be delegated to a trustworthy friend.
  5. For one of these tasks think of some information that you would need to keep track of if you were the person given the task but would not need to bother anybody else with with, assuming that everybody trusts each other and are interested in results not how they are arrived at.
  6. Now think of some information that you would need to be told to be able to do the task. What would you need to know? (Imagine that the details of what is required have been given to the party coordinator but not to you and that anything you need to know comes through the coordinator.)

In this analogy the separate tasks that can be done without bothering anybody else with the details represent our functions. The information the person doing the tasks needs to keep track of but the others don't need to know represent the function's local variables and the information the sub-organiser needs to be told in order to do their task represent the functions arguments.

Out of sight, out of mind

In our "party" example we divided our top-level task into independent sub-tasks to make the whole process more manageable (with the sub-tasks performed by different people). In reality some of those sub-tasks would have had their own "sub-sub-tasks". For example the caterer would have had to work out where to buy the vegetables but would not bother the client with this information.
 
I've been in (and paid for!) this situation: if I had had to worry about where the caterer was going to buy the ingredients, what van the wine company was going to use to deliver the drink or or how the band was going to find a trumpeter the event would never have been organised. Only delegation made this possible.

If it is simple to describe what a piece of code does but quite complicated to actually write it, it is much easier to write it as a separate "mini-program" or function that lets us think about the task separately from the main program. Conversely, when we return to the main program we can use our function without having to think about how it does its job, just as we do with sin() or sqrt().

This requires that we have thoroughly tested our function, as otherwise we cannot rely on it.

Functions allow us to divide complicated tasks into sub-tasks that can be thought about separately.

Example: extending the "raise to an integer power" code

The power code above is getting a bit complicated and if we were in the middle of something that was already quite complicated we could get quite distracted by it. We saw a similar problem in our preface lecture where we could all easily multiply two one-digit numbers together but had problems with two-digit numbers (over twelve!).

You may have already experienced this with your course work: a piece of code may be quite easy to understand but adding just a little bit more complexity leads to a disproportionate increase in the difficulty of understanding it. (If you have found this, don't worry: it's not just you! If you haven't found it yet: you will.)

Handling negative exponents

Our power code above doesn't handle negative values of exponent. We can fix this easily enough, all we have to do is to have a second loop that divides the result by the initial base (rather than multiplies by it) and which subtracts one from the exponent rather than adds one to it. The code is now complicated enough to warrant a brief comment:

  // Calculate base to the power of exponential
  result = 1.0;
  while ( exponent > 0 ) { // Positive exponent, as before
    result *= base;
    --exponent;
  }
  while ( exponent < 0 ) { // NEW: handle negative exponents
    result /= base;
    ++exponent;
  }
Step through this code


Also step though this code with a negative exponent

  1. Why do we not have an if() statement to distinguish between the two cases of positive and negative exponents?
  1. Hint for this question.
  2. First, step through the code for the positive exponent (first link).
    1. What happens to the first loop? Does it execute?
    2. What is the value of the exponent after the loop?
    3. Given this (possibly new) value of the exponent what happens to the second loop? Does it execute?
  3. Now, step through the example for a negative exponent (second link) and ask yourself the same questions as above.

Problem: what if base == 0?

If we look at the new code above we see a problem: it divides result by base which will clearly give us problems if base equals zero. It's not clear how we should deal with this: do we use an if () to test for it and if base is zero what do we do? Often, such problems only get noticed after the code has been used for a while so we would have to go through the code making sure we had found every copy and fixing them all.

Criteria for a good function

For a chunk of code to be a good candidate to be made into a separate function, even if we are using it only once, it must simplify our mental model of the problem by allowing us to think, not of the individual steps involved but of a simple description of the task.

A good function does a single, specific task.

A good function can be specified unambiguously.

A good function is either considerably more complicated to implement than to describe or saves the same code being repeated more than once in the program.

If there are variables that are needed to perform the task but are not needed afterwards that could well be a sign that it should be a function.

The markers will be extremely tough on this

Whenever we are tempted to copy and paste more than a couple of lines of code it should almost certainly be a function.

Use cut and paste, not copy and paste.


A good function does the whole job

Although this shouldn't need saying, it's surprisingly common for students to write functions that do most of the job and expect the rest to be done inside the calling function, such as main().

But which is easier for the person calling the function: a function that does the whole job or one that does most of it and expects him or her to do the rest?

Wherever possible we make sure our functions do the whole job, not just most of it.

Writing our own functions

Having looked at the "whys" let's look at the how.

We have written one function, and used others, already

As mentioned above, we've already written one function, main(), and used standard system functions such as sin(), cos() etc. C is very consistent and does not have one set of rules for system functions, another for main() and another for other functions we write ourselves.

Everything we have learnt about using standard system functions and in writing main() applies to all the functions we write ourselves.

A recap of some things we have already learnt

  • The declaration "int main()" means that main() returns a int value back to whatever called it (so our other functions can return values too).
  • Functions such as sin() return a double not an int (so there must be a way of getting our functions to return a double too).
    • If "int main()"  means that main() returns an int, how would we declare a function called foobar that returned a double?
  • We use the notation "return expression;" to return a value from main() to whatever called it (so that's how our other functions will return values);
  • We pass the value of the argument to sin() by putting it inside the bracket, sin(x), and multiple arguments to pow() and printf() by separating them by commas (so that's how we will pass arguments to our functions).
  • We pass the value of mathematical expressions, not variables, as arguments to our functions: sin(x), sin(x+y) and sin(1.7) etc. (So we will also pass the values of mathematical expressions to our functions, we cannot "pass a variable").

A simple example

The following shows the simplest possible function, purely for demonstration purposes:

// Demonstration function: return its argument plus 2;
double xplus2(double x) {
  return x + 2.0;
}

int main() {
  double r, r2;

  r = 1.7;
  r2 = xplus2(r);
  printf("%g plus two is %g\n", r, r2);

  return 0;
}
Step through this code

Although this function is too simple for us to ever want to write, it does show the essential elements of writing and using functions.

  1. The code for the function occurs outside of main(), in this case it is before main() before although it is also possible to write functions after main().
  2. The function declaration starts: double xplus2 meaning that the function returns a double back to whatever called it. (Compare this with int main which means that main() returns an int back to whatever called it.)
  3. The body of the function is just one statement: return x + 2; . This means that if at that point the variable x had the value, say 5.1, the function would return 7.1.
    1. But what is the value of x and how does it get it? There is no assignment of x inside the function.
    2. Can you guess x might get its value? (Think of our procedures such as in our Mr Turtle practical.)
      1. Hint: think of sin(x)
      2. Imagine you had to write the system's sin() function for use when a programmer included <math.h>. It would start like this:
      3. double sin(double angle) { ...
      4. In this situation how does the sin() function get given the value of angle? When you call sin() how do you give it the value of the angle?
  4. The declaration of x is in a different place from where we have previously seen declarations: it is inside the brackets following the function name and there is no second declaration of x inside the function itself. Notice that we use x inside the function just like any other variable. Local variables declared inside the brackets after the function are called parameters and are  how we pass values into a function.
  5. Finally inside main() we see the code r = xplus2(1.7); This causes the code inside xplus2() to be run and a value returned back to main().
    1. Instinctively, what would you be the value assigned to r?
    2. What do you think is the value of x inside of xplus2()?
    3. Would this cause xplus2() to return the number you expected? If not, try to rethink your answers.
    1. Step through the above example.
    2. To see the very basics of a function.
    3. Step through the above example in a new window.
    4. Step through the code until it evaluates the argument to xplus2() (which is just r).
    5. Now step once more. Notice how xplus2() starts up and a new variable (x) springs into life.
    6. Notice that the initial value of x is 1.7.
    7. Had the call to xplus2() been xplus2(r+2), what do you think would have been the initial value of (x) inside xplus2()?
    8. Now step through the code twice more so that the program calculates the value of the return statement. Note its value.
    9. Step once more and notice how that value has been assigned to r2 inside main().
    10. Notice too how xplus2() has finished and x has vanished.

In this example we have assigned the value to the variable r and printed it out. We could equally have chosen to print it directly or use it in a more general mathematical expression:
 

  printf("result is %g\n", xplus2(1.7));
  y = xplus2(8.1);
  z = y * xplus2(5.9) + xplus2(-0.7);

  1. Our first function: square
  2. To write a very simple function
  3. Create a new on-line program in a new window, with a suitable title and opening comment.
  4. Above main() write a simple function which like the function above is of type double (ie it returns an double value to the function that called it) and which has a single double parameter. The general form of your function should be like this:
    double function_name(double parameter_name) {
    
      }

    NB: Do not copy and paste the function in the notes above! Typing it in yourself will help you remember it.
  5. Have your function calculate the square of the parameter and return it.
  6. Inside main(), call the function and print out the result, checking it looks reasonable.

A more realistic example

Most functions will include more than just one line and will have a mix of parameters and "ordinary"  local variables that do not receive initial values from whatever called them. The following, more realistic, example demonstrates exactly the same principles as the previous ones, however we shall go into them in  greater detail . We can package our previous "power" code into its own function (with a fairly crude way of handling the "base is zero, exponent is negative" problem):

#include <stdio.h>
//
// Return base to the power of exponent.  
// If base == zero and exponent < zero, 
// print a warning and return zero
//
double mypow(double base, int exponent) {
  double result = 1.0;

  if (base == 0.0 && exponent < 0 ) {
    printf("WARNING: mypow called with zero base and negative exponent\n");
    return 0.0;
  }

  while (exponent > 0) {
    result *= base;
    --exponent;
  }
  
  while (exponent < 0) {
    result /= base;
    ++exponent;
  }
  
  return result;
}


int main() {
  printf("Base: %g\n", mypow(6.1, 3));

  return 0;
}
Step through this code


(See the appendix For more discussion on the "base is zero, exponent is negative" problem.)

We examine the key points in turn:

Functions are written outside of other functions

In this case we see that mypow() is right at the top of the file, immediately following the #include directives. Functions may be in any order, before or after main() (which is just a function itself anyway). Trying to write a function inside of another function will produce an error. See this Good-bad example.

Functions are written outside the body of other functions.

Avoid ambiguity with a preceding comment

The comment before the function must be precise

The hidden danger when using functions is that it is easy to be slightly ambiguous about what a function does (including what it does when something goes wrong), what values it expects or if there's something that needs to be done first before the function can be called.

That's what the comment before the function is for! Notice that the comment before our function is both short and specific, i.e. it says what it does if there's an error.

We have slightly dodged the question of how to handle zero to negative powers by just printing a warning and returning the value of zero. With marginal cases like this, what we do is sometimes less important than the fact that it is clearly documented.

Another important way of removing confusion and ambiguity is to use consistent assumptions throughout the program: e.g. always use SI units, "y" is always up, etc.

Use comments before the start of functions to avoid subtle ambiguities.

The return type

After the initial comment we next see that mypow() returns a double, in much the same way as main() returns an int:

double mypow(double base, int exponent) {
 

The word "double" before "mypow" shows the return type. There are two parameters, base and exponent, which are a double and an int respectively. We shall discuss parameters in more detail a little later.

typename myfunction() means that myfunction returns a value of type typename

The return value

We have already seen how main() returns its result (usually 0 for success) by the statement: return 0; Our function does exactly the same. The statement:

  return result;
 

returns the value of result to the calling function.

We may return the value of any expression, for example the statement:

  return a + b * 1.7 * c;

would evaluate a + b * 1.7 * c and return that value.

The statement return expression; quits the function and returns that value to the calling function.

  1. Our second function: the sum of the first N integers
  2. To write a slightly more complicated function, and to write a function that returns an int
  3. Background: for N > 0 the sum of the integers from 1 to N is N*(N+1)/2. We will write a very simple function to do this calculation.
  4. Create a new on-line program in a new window, with a suitable title and opening comment.
  5. Again, above main() write a simple function of type int (ie it returns an int value to whatever called it) and which has a single int parameter. The general form of your function should be like this:
    int function_name(int parameter_name) {
    
    }
    • Note: when you enter the initial opening { and press <Return> the editor in the on-line compiler page will add the closing } for you.
  6. Use sensible names for your function and its parameter to indicate that the function calculates the sum of the numbers from one to the parameter value.
  7. Have your function calculate and return the correct value. At this stage you need not worry about what happens if it is accidentally called with a negative number.
  8. Now inside main() have a call to your function with a suitably small positive integer and print out the result.
    • You may choose to do this either directly, as in the second example above, or by declaring an int variable and setting it to the result of of your function and then printing out its value.
    • Either way as the value is an int you must use the %d format, not %g as in the example where we were printing a double.
  9. Build & run and make sure your program prints out the correct value.

Multiple return statements

Although unusual is is possible to have more than one return statement, in which case the functions quits when it executes the first one it comes to:

#include <stdio.h>
#include <math.h>

// Return the square root of the argument or minus one if it is negative
double sqrtOrMinusOne(double x) {
  if ( x >= 0 ) 
    return sqrt(x);
  
  printf("Warning: %g is negative\n", x);
  return -1;
}
Step through this code

 

If x is greater than or equal to zero the statement "return sqrt(x);" will be executed, meaning that the function will immediately quit and return sqrt(x) back to the calling function. Otherwise it will print a warning and return -1.

  1. A function with two return statements.
  2. To demonstrate the ability of a function to have more than one return statement.
  3. In this mini-exercise we will create an int function that accepts a double argument and returns one if the argument is greater than zero, minus one if it is equal to zero and zero if the argument is exactly equal to zero.
  4. Create a new on-line program in a new window, with a suitable title and opening comment.
  5. Above main() create a suitable function with a suitable name. Make sure that the type of the function is int and that the type of the parameter is double.
  6. Using an if() statement and multiple return statements, return the integer values 1, -1 or zero according to whether the argument is greater, less than or equal to zero.
  7. Call the function from within main() and print the result (again from with main()).
  8. Build & run Check it for all three possibilities.

Functions may have more than one return statement: the first one to be executed causes the function to return immediately with that value.

Receiving the returned value in the calling function

We have already seen how to use the value returned by built-in maths functions such as sin(), sqrt(), etc either by assigning them to variables, using them in expressions or printing them out:

  x = sin(y);
  root = (-b + sqrt(b*b - 4*a*c))/(2*a);
  printf("cos(%g) = %g\n", y, cos(y));
 


Values returned by our functions are used in exactly the same way as mathematical functions such as sin() and sqrt() and can be used inside any mathematical expression

Examples of using the return values of the function mypow():

  x = mypow(y, 5);
  a = 2.2 * mypow(b, -6);
  printf("%g to the power %d is %g\n", y, k, mypow(y, k));
 

Functions don't have to return a value

If a function is there to do a job rather than calculate a value it may be declared as type void. In this case the return statement is optional and the function just returns when it reaches the end:

void anotherfun(double y) {
  // Some code here...
}

However, a return statement (without a value) may be used to return before the end of the function if required:

void yetanotherfun(double y) {
  // Some code here...
  if (some_condition)
    return; // No return value for a void function

 // More code here...
 }

A function of type void does not return a value to the function that called it.

A common convention is for functions that do a job to return an integer value indicating success or failure or some other information. For example, printf() returns the number of characters successfully output. This value is usually ignored.

Function parameters

Apart from the fact that mypow() is declared as double not int, the main difference between mypow() and main() is that the parentheses following the function name are not empty but contain two variable declarations separated by commas:

int mypow(double base, int exponent)

This declares two local variables, base and exponent, called parameters which can then be used within the body of the function just like any variables declared within that function's { ... } block. The initial values of the parameters will be set to the values of the arguments the function was called with. Other than their initialisation parameters are exactly the same as any other local variable.

Unlike ordinary declarations, each parameter must have its own type-name even if they are all the same:

double good_function(double x, double y)   // Correct
double bad_function(double a, b) // WRONG!


Any { ... } block can have its own variables whose values are not preserved if the block is re-entered.

The only thing that distinguishes parameters from "ordinary" local variables declared inside the { ... } block is that the parameters have their values initialised to the values of the corresponding expressions passed as arguments when the function is called.

Remember An expression may be a constant, the value of a variable or an arithmetic expression involving "+ - / * %", etc.

For example, suppose mypow() is called from within main(), or any other function, as:

  double a = 3.0, b = 1.0, x, y;
  int j = 6;

  x = mypow(2.0, 3);
  y = mypow(a - b, j/2);

For each call to mypow() the parameters (local variables) base and exponent will be initialised to 2.0 and 3 respectively because in each case the expressions that form the two arguments are evaluated and the resulting value is passed to the function.

mypow() then calculates the required answer, which we have imaginatively called "result" and the statement:

  return result;

causes the function to finish and return the value of "result".

Thus inside the calling function x and y will have the value 8.0 .

Local variables: a vitally important principle


As we have already seen with main(), functions can have their own local variables. This raises the question:

  • Suppose we have a function called myfun(), which has a local variable with the same name as a variable inside main(). Let's call the variable "mass". Are the variables in any way related? Will changing mass inside myfun() also change the value of mass inside main()?

The question becomes even more important when we remember that the built-in functions such as sqrt() or printf() must also have their own local variables whose names we don't even know! Let's look at the principle we introduced above and see if this helps us work out the the answer.
 

The purpose of a function is to make life easier for the person calling it.

  1. 1. Suppose your main() function had variable called y and you called  printf("Hello, world\n");
    Would life be easier for you if:
    1. If, unknown to you, the  printf() function also had a variable called y then the value of y inside main() might change?   Or:
    2. You knew that printf() was guaranteed never to change the value of y, even if it had a variable called y itself?
  2. Why?
  3. 2. Suppose your program contained the statement printf("%g\n", x);
    Would life be easier for you if:
    1. You knew the printf() function might change the value of x.  Or:
    2. You knew that printf() was guaranteed never to change the value of x?
  4. Why?

  1. Changing the values of variables inside different functions
  2. To see if changing the value of a variable in one function affects the value of a variable of the same name inside another function.
  3. In your previous "sum of a series" program, add a variable to both main() and your "sum of series" function with the same name but the value of 1 in main()and 2 in your function. The declaration of the variable should occur before the call to your function.
  4. Print out the value of your variable inside main before calling your function and once inside the function.
    • As always with "diagnostic" print, add enough text to your printf() statements so that you know which is which.
    1. What values do you expect your program to print out and in what order?
  5. Build & run. Check your previous answer was correct and if not work out why.
  6. Now the big one: add another printf() statement in  main() after the call to your function.
    1. What value do you expect it to print out? Will the change to the value of the variable inside our function change the value of the variable in main()?
  7. Build & run

A little thought will soon show that it would be almost impossible to write a computer program if changing the value of a variable in one function changed the value of a variable of the same name in another function. . For example, although we have never seen the source code for the printf() function, we can be sure it contains some local variables. It would be disastrous if we were to accidentally give one of our variables the same name as one inside printf(): we would call printf() and the value of our variable would suddenly change!

I would be pretty upset if somebody else committed a murder and they sent me to jail on the grounds that we had the same first name

Changing the value of a local variable in one function has no effect on the value of variables with the same name inside other functions.

Example: two variables with the same name in different functions

The following function calls mypow() in the doomed hope that "result" in main() will be set to the value of the (different) local variable "result" inside mypow().

#include <stdio.h>
int main() {
  double result; /* The same name as inside mypow() */

  mypow(7.2, 4);

  printf("7.2 to the power 4 is %g\n", result);  /* Wrong! */

  return 0;
}

Step though this code.

  1. Step through the above bad (but still key) code.
  2. To observe the two separate variables called result
  3. Step through the above example in a new window.
  4. Start the program and step through until mypow()
  5. Observe how a new index card appears for the variable result inside mypow() completely unrelated to the one inside main().
  6. Continue through the code watching mypow()'s variable result being changed whilst main()'s is completely ignored.

The word "void" means that the function doesn't return a value. In this case the "return" statement is optional.



Example: parameters are a temporary, local copies of the calling arguments

Consider the following three ways of calling mypow() from another function:

void testfunction() {
  double testval = 2.0, base = 2.0;
  int testexp = 3, exponent = 3;
  double pow1, pow2, pow3;
 
  pow1 = mypow(2.0, 3);
  pow2 = mypow(testval, testexp);
  pow3 = mypow(base, exponent); // Surely the computer will take the hint!

  printf("power is %f, exponent is %d\n", pow3, exponent);
}

Key example: step through the complete program. (NB: for brevity the complete example does not include the variables testval and testexp.)

Some programming adopt the opposite approach and do change the value of the calling parameter. In some of them, if we then call the function with a constant as a parameter the program will crash.

In each case, the parameter exponent inside mypow() is initialised to three and during the course of the function it is assigned the value of zero. Whilst it's fairly clear that mypow() won't be able to change the value of the mathematical constant "3" to zero(!), it's not quite so clear in the second and third case that "testexp" and/or "exponent" won't have their value changed to zero.

In the third case we've gone as far as using the same variable names as the actual function arguments: the variable exponent "happens" to have the same name as used within the body of mypow(). Might the compiler take this as some sort of hint?

C is completely consistent and in all cases the argument within the function is a brand new local variable whose only connection to the calling parameter is that the value of the parameter is copied to become the initial value of the local argument.

  1. Step through the above "Key example".
  2. To demonstrate that parameters are ordinary local variables independent of any variables with the same name in other functions.
  3. This is just an extended version of the previous one with the addition that we have passed the values of base and exponent to mypow(). It makes no difference: we are passing their values not the variables themselves.
  4. Step through the above "Key example" in a new window.. (Note that for brevity we have called mypow() directly from main().)
  5. Step through the program until mypow() is called for the first time.
  6. Notice how mypow()'s variables spring into existence and that although two of them have the same names as variables inside main() they have their own separate index cards, with different index numbers.
  7. Carry on stepping through mypow() noticing how it only accesses "it's own" index cards and completely ignores those belonging to main().
  8. Now carry on until mypow() returns. Step through until mypow() is called again, this time with the values of base and exponent as its arguments.
  9. Notice that the mypow() and its variables are exactly the same as before and there is still no linkage between base and exponent in mypow() and base and exponent in main().

Parameters are variables which are local to the function but whose values are initialised to the value of the corresponding argument in the function call.

Function prototypes

Checking the number of arguments, etc.

It's quite easy to miss out an argument to a function. In addition, if we use a function inside another arithmetic expression the compiler will need to know what type of result (int, double, etc) it returns. An obvious example is:

  y = mypow(x, 4) / 8;

where it needs to know whether to use integer or floating-point division. If the source to mypow() occurs further up the source file then the compiler will already have seen it and it will know its return and argument types. But if mypow() occurs further down the page then it won't.

Before we can call a function we must have told the compiler the type of value it returns and the number and types of its arguments.

There are two ways of dealing with this:

1. Have the function code before the place where it is called

This is what we have done so far. It does work but has the disadvantage that our program ends up being in the reverse order with main() at the end, making it hard to understand.

2. Explicitly tell the compiler the type of the function and its arguments

This is called a prototype, it appears outside of any function, before either the function itself or any other function that calls it. It looks like:

double mypow(double value, int exponent);

The names of the parameters are optional in function prototypes.

The easiest way to create a function prototype is to copy and paste the start of our function declaration and to replace the brace with a semicolon.

A prototype doesn't declare a new function, it just says that a function is declared somewhere else and tells the compiler its type and the types of its arguments.

If the compiler then meets either the function declaration, or a call to the function, and either does not match the prototype it will complain.

The conventional place to put our prototypes is just before the first function in the file.

Example: the previous power code re-ordered

This is exactly the same code as above but with main() placed first, thus requiring a prototype for mypow(). We have highlighted the relevant lines.

  1. First we see the prototype which does not create a new function but just says what type of value it returns, and the number and types of its arguments
  2. Then inside main() we see two calls to mypow(). In each case the compiler checks that the number and type of its arguments agrees with the prototype.
  3. Finally, below main we see the actual function code which creates the function. The compiler checks that the number and type of its parameters agrees with the prototype.
#include <stdio.h>
#include <stdio.h>
// Demonstrate prototypes and local variables 

// Function prototype
double mypow(double base, int exponent);

int main() {
  double base = 2;
  int exponent = 3;
  double pow1, pow2;
 
  pow1 = mypow(2.0, 3);
  pow2 = mypow(base, exponent); // Surely the computer will take the hint!

  printf("power is %f, exponent is %d\n", pow2, exponent);
}

//
// Return base to the power of exponent.  
// If base == zero and exponent < zero, 
// print a warning and return zero
//
double mypow(double base, int exponent) {
  double result = 1.0;

  if (base == 0.0 && exponent < 0 ) { // Or fabs(base) < 1e-50
    printf("WARNING: mypow called with zero base and negative exponent\n");
    return 0.0;
  }

  while (exponent > 0) {
    result *= base;
    --exponent;
  }
  
  while (exponent < 0) {
    result /= base;
    ++exponent;
  }
  
  return result;
}
Step through this code


  1. Function prototypes
  2. To use a prototype, using the example of a function that multiplies two numbers together
  3. Create a new on-line program in a new window, with a suitable title and opening comment.
  4. First we shall write the code without using a prototype, so above main() write a simple function that takes two doubles as arguments and returns their product. (Of course this would be too simple for a real function: this is just an example).
    • Remember that each parameter has its own type:
      double myfunction(double x, double y) {
  5. Call your function from within main() and print out its value. Build & run and check the result is correct.
  6. Now move your function to below main() but do not yet write a prototype. Build & run. What happens?
    Now we will create a prototype:
  7. Copy the start of the declaration of your function up to the closing parenthesis of the parameter list, but not including the opening brace.
  8. Paste this into a blank line above main() and add a final semi-colon.
  9. Build & run: it should now be OK.
  10. Now change your function call inside main() so that it "accidentally" has only one argument rather than two. What happens?

void parameter list

The proper way to specify that a function takes no arguments is to give the prototype a parameter list of void:

float myfun(void);

float myfun(void) {
 ...
}

Having an prototype with no parameters disables argument checking which is a Bad Thing:

float myfun(); // Don't do this

Summary

The text of each key point is a link to the place in the web page.

What is a function?

Why are functions useful?

1. Code that gets used more than once

2. Hiding complexity

Criteria for a good function

Writing our own functions

We have written one function, and used others, already

Local variables: a vitally important principle

Function prototypes

Log in
                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top