Functions, or how to think about fewer things at a time
Comments and questions to John Rowe.
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?
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.
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.
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.)
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.
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.
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 ) { 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?
- 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.
- It's easier to test.
- It's easier to fix and extend.
- Example: organising a party
- To let us consider the issues in a more-familiar context.
- 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.
- First imagine you are the coordinator. Think of
some examples of tasks that could be delegated to a
trustworthy friend.
- 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.
- 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:
result = 1.0;
while ( exponent > 0 ) { result *= base;
--exponent;
}
while ( exponent < 0 ) { result /= base;
++exponent;
}
Step through this code
Also
step though
this code with a negative exponent
- Why do we not have an if() statement to distinguish between
the two cases of positive and negative exponents?
- Hint for this question.
- First, step through the code for the positive exponent (first
link).
- What happens to the first loop? Does it execute?
- What is the value of the exponent after the loop?
- Given this (possibly new) value of the exponent what happens
to the second loop? Does it execute?
- 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.
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.
Having looked at the "whys" let's look at the
how.
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:
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.
- 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().
- 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.)
- 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.
- But what is the value of x and how does it get it? There is no assignment of x inside the function.
- Can you guess x might get its value? (Think
of our procedures such as in our Mr Turtle practical.)
- Hint: think of sin(x)
- Imagine you had to write the system's sin() function for use when a programmer included <math.h>. It would start like this:
- double sin(double angle) { ...
- 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?
- 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.
- 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().
- Instinctively, what would you be the value assigned to r?
- What do you think is the value of x inside of xplus2()?
- Would this cause xplus2() to return the number you expected? If not, try to rethink your answers.
- Step through the above example.
- To see the very basics of a function.
- Step through the above example in a new window.
- Step through the code until it evaluates the argument
to xplus2() (which is just r).
- Now step once more. Notice how xplus2() starts up
and a new variable (x) springs into life.
- Notice that the initial value of x is 1.7.
- Had the call to xplus2() been xplus2(r+2),
what do you think would have been the initial value of (x)
inside xplus2()?
- Now step through the code twice more so that
the program calculates the value of the return statement.
Note its value.
- Step once more and notice how that value has been assigned
to r2 inside main().
- 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);
- Our first function: square
- To write a very simple function
- Create a new on-line program in a new window, with a suitable title and opening comment.
- 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.
- Have your function calculate the square of the parameter and return it.
- Inside main(), call the function and print out
the result, checking it looks reasonable.
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>
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:
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.
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.
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
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.
- Our second function: the sum of the first N integers
- To write a slightly more complicated function, and to write
a function that returns an int
- 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.
- Create a new on-line program in a new window, with a suitable title and opening comment.
- 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.
- 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.
- 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.
- 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.
- 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>
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.
- A function with two return statements.
- To demonstrate the ability of a function to have more than one
return statement.
- 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.
- Create a new on-line program in a new window, with a suitable title and opening comment.
- 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.
- 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.
- Call the function from within main() and print the result
(again from with main()).
- 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; // 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.
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 .
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. Suppose your main() function had variable called y and you called printf("Hello, world\n");
Would life be easier for you if: - If, unknown to you, the printf() function also had a variable called y then the value of y inside main() might change? Or:
- You knew that printf() was guaranteed never to change the value of y, even if it had a variable called y itself?
-
- Why?
- 2. Suppose your program contained the statement printf("%g\n", x);
Would life be easier for you if: - You knew the printf() function might change the value of x. Or:
- You knew that printf() was guaranteed never to change the value of x?
-
- Why?
- Changing the values of variables inside different functions
- 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.
- 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.
- 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.
- What values do you expect your program to print out and in what order?
- Build & run. Check your previous answer was correct and if not work out why.
- Now the big one: add another printf() statement in main() after the call to your function.
- 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()?
-
- 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;
mypow(7.2, 4);
printf("7.2 to the power 4 is %g\n", result);
return 0;
}
Step though this code.
- Step through the above bad (but still key) code.
- To observe the two separate variables called result
- Step through the above example in a new window.
- Start the program and step through until mypow()
- Observe how a new index card appears for the
variable result inside mypow() completely
unrelated to the one inside main().
- 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);
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.
- Step through the above "Key example".
- To demonstrate that parameters are ordinary local
variables independent of any variables with the same
name in other functions.
- 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.
- Step through the above "Key example" in a new window.. (Note that for brevity
we have called mypow() directly from main().)
- Step through the program until mypow() is called for
the first time.
- 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.
- Carry on stepping through mypow() noticing how
it only accesses "it's own" index cards and completely ignores
those belonging to main().
- 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.
- 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.
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.
- 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
- 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.
- 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>
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);
printf("power is %f, exponent is %d\n", pow2, exponent);
}
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;
}
Step through this code
- Function prototypes
- To use a prototype, using the example of a function that multiplies two numbers together
- Create a new on-line program in a new window, with a suitable title and opening comment.
- 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) {
- Call your function from within main() and print out its value. Build & run and check the result is correct.
- Now move your function to below main() but do not yet write a prototype. Build & run. What happens?
Now we will create a prototype:
- Copy the start of the declaration of your function up to the closing parenthesis of the parameter list, but not including the opening brace.
- Paste this into a blank line above main() and add a final semi-colon.
- Build & run: it should now be OK.
- 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
The text of each key point is a link to the place in the web page.
Log in