It’s been a bit over two months since the previous post. In it we learnt how to define simple functions that don’t take or return any parameters. We also learnt that we need to declare a function if we’re calling it at a point in our program where it hasn’t been defined yet.
Today we’re resuming the course exactly where we left it. If you need a refresher then go ahead and re-read the previous chapter. As always, don’t hesitate to ask questions in the comments below if anything isn’t clear.
Let’s see a few more properties of functions in C:
Functions have their own variables
In C, each function can have its own local variables. We declare these at the beginning of the function body, before any statements. We’ve already seen how to declare variables inside main(); it works in exactly the same way for any other function:
Any variable that we define inside a function will be a local variable. This means that the variable is only available from inside the function where it is defined. We can even use the same variable name again and again inside different functions without conflict.
Let’s see an example program to illustrate how this works:
The program above defines two functions: main(), which we always need, and aster7(). Both of them use an integer variabled called i. main() uses it to create a loop that calls aster7() five times, and aster7() uses a different variable with the same name to run a loop that prints 7 asterisks. Each of the variables is local to the function that defines it, so they don’t conflict with each other.
We can compile the program to see that it runs as expected:
This is because both i variables are local and isolated from the i variable in any other function. Changing the i variable inside aster7() doesn’t affect the i variable inside main(), and vice versa.
Compare this with how this would work in MSX-BASIC. BASIC has no concept of local variables, so we can’t use the same variable name in two different places at the same time without affecting the whole program (for example, a FOR loop using the I variable that calls a subroutine that changes the I variable).
Local variables give C a very important advantage over BASIC: in BASIC, writing a big program involves keeping track of variable names in order to avoid changing them by mistake somewhere else in the code. In C it’s easy to split a big program into several simple functions with their local variables, without having to worry about how variables are used elsewhere.
Functions that accept parameters
The function aster7() in the program we’ve just seen always does the same thing: it prints 7 asterisks. We can easily make this function more useful by modifying it so it accepts a parameter indicating how many asterisks we want it to print.
We define functions that accept parameters like this:
A function can have as many parameters as we want and these can be of any type. However, each parameter can consist of only a single value. This means that we can’t pass a whole array as a parameter to a function (but there’s a way to around this that we’ll see when we learn about pointers).
The program below illustrates how to define and use a function that accepts parameters. It’s the same program as below, in which the aster7() function has been renamed to asters() and modified to accept an integer value indicating how many asterisks to print:
This version of the program calls the asters() function in three different ways:
- asters(5) passes a fixed integer value to the function
- asters(i) passes the value of the i variable to the function
- asters(4-i) passes the result of the arithmetic operation to the function
As you can see, we can pass any expression as a parameter, as long as the resulting type of the expression matches the type of the parameter. In this particular case, the parameter of the asters() function is an int, so we can pass it anything (a value, variable or expression) of type int.
If we compile and run the program we can see how this works:
Warning about the parameter types
Whenever we call a function it is very important to ensure that the values we pass are of the appropriate type. Otherwise, the function will most likely not work as expected.
As an example, consider the function definition below. It accepts two parameters, ch and n. The first one is of type char and the second one is an int:
In this particular case the three calls to the putline() function are all correct and equivalent:
- putline(‘A’, 5); — A character constant and an integer
- putline((char)65, 5); — An integer value casteed into a char and an integer
- putline((char)0x41, 5); — Same as the previous case
…and the calls below are both incorrect:
- putline(65, 5); — 65 is an int
- putline(0x41, 5); — 0x41 is an unsigned
Remember that we described these type-related rules some time ago when we described the data types in MSX-C.
We have to be especially careful when passing the result of an expression as a parameter, because the type of the operands can affect the type of the result. For example:
- putline(‘A’ + i, 10); — Assume that i is an integer
As we saw in the chapter about data types, adding a char and an int gives a result of type int. This would conflict with the type required by putline(). The correct way to pass the result of this expression is to cast it into a char:
- putline((char)(‘A’ + i), 10);
Functions help make programs easier to understand
In chapter 38 we learnt that we can use terminal escape sequences to clear the screen or position the cursor in a given location. As a reminder, the sequence of characters below moves the cursor to screen coordinates X,Y:
¥033, Y, Y+32, X+32
The C language doesn’t have a LOCATE statement to position the cursor like MSX-BASIC does, but we can easily make our own function that takes the screen coordinates and puts the appropriate escape sequence on the screen:
In this case, defining and using the locat() function makes the program longer than if we printed the escape sequence directly from inside the loop. However, consider what the program would look like in that case:
The second version is shorter, but it isn’t easy to understand what the first printf() does. If we’re working on a big program and have to debug it sometime in the future, the first version will be much, much easier to fix.
Consider splitting your programs in functions for readability whenever possible.
A function can finish its execution before reaching the end of its code
Most of the time a function executes all the instructions it contains and then returns to the code that called it. However, it is possible to stop its execution at any time using the return statement.
For example, let’s modify the locat() function in the program above so that it will check that the coordinates are in the range (0,0)-(39,19) and return without doing anything if they aren’t:
A function can be aborted at any point, even from deep inside a loop.
In this post we’ve learnt about local variables and how to define functions that accept parameters. We’ve seen how to make sure that the data passed to a function is of the appropriate type, and we’ve learnt how to abort the execution of a function at any point.
In the next post
In the next post we’ll learn about the most useful type of functions: the ones that return values. We’ll also learn about the VOID type.
This series of articles is supported by your donations. If you’re willing and able to donate, please visit the link below to register a small pledge. Every little amount helps.