We went over the basic idea of a function earlier (see C++ Functions). We have a few more function related topics to discuss before you can build your own, or use functions provided by others in your programs.
Sometimes, grasping new concepts takes work, and it seems a few of you are having difficulty getting a handle on these function things. Let’s try another approach.
A function is just a box with some code in it that someone (maybe you) wrote. The box has a name painted on the side and a troll lives inside!
On the top of the box are a few slots through which we can shove cards with information on them. Each slot is of a particular shape and can only accept cards of the right shape. There is another slot, also with a particular shape, out of which the troll will shove a card with an answer on it. Neat!
Now let’s go back to our program - the one we are writing at the moment. When we want the troll to do some work for us, we find the box with the right name and shake it to wake up the troll. We write down the information on the cards and put them in the right slots on top, and we wait. Not long, trolls are quick.
When the troll does whatever magic the code inside directs him to do, he writes the final answer on a card and shoves it out the output slot. We take that card and do with it what we want.
That is all a function does.
Trolls have a union and it is very strict about Trolls working on unauthorized projects. So, when the Troll union sets up a box and assigns a Troll to work in the box, it requires a contract between the Troll and the human who will give it work. Actually, this contract defines the number and shape of the input slots and the output slot (only one is allowed). The Troll takes up residence in the box, files away at the box to create the slots and waits for work. The contract tells the human exactly how to give work to the Troll. Each Troll box has a unique contract that includes all the information about the name of the Troll, and the slots in the box!
The troll is picky! He will only do work when the right kinds of cards are shoved into all the input slots. And, he will only follow the instructions found in the code inside the box. If the instructions work properly, the troll will come up with a final answer and write it down on the output card. The total set of chunks of information involved in this operation constitute a contract between the human needing troll assistance and the troll needing something to do. Here is the contract:
Now, the shapes of the cards correspond to the types of data to be moved into or out of the box. These are the input parameters and the output return value.
Each input slot has a name painted on the inside of the box, so only the troll can see it. The troll uses those names to figure out the starting value for variables (with those names) in the code inside the box. When a card is shoved into a slot, the data on the card is used as the value of the corresponding variable.
The human decides to have a Troll do something, so a set of data items is recorded onto cards of the right shape to fit in the slots, then the human slides the cards into the slots, bangs on the lid to wake up the Troll and sits back to wait until the Troll sends the result out the output slot! (The buzz word here is call by value since the Troll has no idea where those data items came from)
Occasionally, we either have too much data to write down on cards, or we want more the Troll to modify some data in out human world. In this case, we will not set up a slot for input data at all, we will set up a slot for a piece of rope. When the Troll wants to work with the data in this weird slot, he pulls on the rope and a box from the human world holding the data will be pulled into the box. The Troll can modify the contents of that box as he wishes, then shove it back out of the box and it goes right back to where it was in the human world. (The buzz word here is call by reference since the Troll will have direct access to data from the human world and can mess with it!)
The neat thing about all this is that neither the human nor the troll can see through the walls of the box. The human does not care how the troll does his job, and the troll does not care where the final answer goes. Both do their job treating the other as invisible!
It the troll in the box does a trick that we humans find useful, we can clone the troll and the box and use it in other programs. Reusable trolls - er - functions are what we really want to build, they save us a lot of work as we built other programs.
Some trolls can do work without needing information coming in through silly slots. So their boxes have no slots. They start work as soon as they get shaken up! When they are done, they write something down on a card and shove it out the output slot! The parameter list for this function is empty!.
Other trolls are stranger yet, they will not tell you what they did. Their boxes have no output slot, they just take information in and do some kind of work. What we do not know (yet) is that the trolls can pass information to other trolls who do other tricks, some of which can cause information to sneak out into our world without needing to give anything back to the human who shook them up. Here, the return value is missing. We do not really like these trolls much since it is hard to understand what is going on. Sometimes, they are useful, but for now, we will concentrate on simpler trolls. It is nicer when the troll does a job and gives us back a simple answer. Oh well, life with trolls!
OK, so the troll is really the computer following the code inside the box, and the box is really a function. The input slots are the parameters, and the output slot is the return value. To get a return value, we put some value in a return statement in the function code.
We shake up the box by calling the function, and we provide parameters by evaluating the expressions we write on the input cards. (Parameters can be simple numbers, variables, or complex expressions. In the end all of them will be evaluated and the final number written on the card for the troll).
Boxes with no input slots are functions with no parameters. We say the parameter list is void.
Boxes with no output slot are void functions.
Parameters with rope passed into the box are reference parameters.
Did this help?
So far, we have not found a race of Trolls who will do out bidding. So we work with different kinds of Trolls - YOU! That is we set up contracts between two humans. One human will build the box and design the code that goes inside. The other human will use the box to do something useful in a program. We can call these humans the producer of a function, and the consumer (user) of a function. The contract we write between these two folks is designed so both humans know what to do to build a function that will really work!
We can place the contract on one of two places: in the file where we will both define the function and include code that uses it (this is the simple case we will use for a bit longer), or, we can place the contract in a separate file called a header file, usually one that is named something.h, where the something part describes what the box is all about. In this case, the actual code for the function is probably in another file!
In fact a single header file may hold contracts for a bunch of related functions (like a library of math functions). We let our system know that we want to use the functions in a library by including the header file containing the contracts. Once this is done, the compiler (and linker) will hook up our code to the compiled code for the library and we can build a complete program.
You have already seen this, you have been including the DarkGDK.h file in your program so you can access all the cool functions that do our graphics!
The actual contract is just the top line from the function definition, but no function body is included. In place of the curly bracket pair we provide when we write an actual function, we just place a semicolon.
This is called a function prototype.
We will see later that we can build functions in separate files, but this example will show everything in one file:
Suppose we want to write a function that checks if a point on the screen is inside defined region on a line (remember the zone example from earlier?) Here is the contract for the function:
int checkInZone(int x, int startX, int startY);
This is the function prototype for the real function. The semicolon tells the compiler not to expect the actual code here!
Later in the code we write the real function:
int checkInZone(int x, int StartX, int startY) {
// do stuff to figure this out
return result;
}
Here, we repeat the function prototype text, but provide code in place of the semicolon.
That point where the comment lives is where the logic to do the job lives. We figure out the final answer and return it to the caller. The data types in the parameter list are important, they tell the user what is expected of them when they activate this function.
There is a simple rule we need to know about that defines how the compiler works. The compiler needs to know everything needed about any name before you will be allowed to use that name in your code. In the case of the name of a function, the compiler needs to know a number of things to check that you are using it correctly:
All of this is provided in the contract, the function prototype. With this information, the compiler can make sure that the actual function code is properly set up, and that any calls to the function are proper as well. All of this is possible as long as the compiler sees the function prototype before it sees an attempt to either define the function, or use it!
Note
The headers must be processed by the compiler before any attempt to use functions is made by any code in the file. For this reason, include lines are usually right at the top of a file.
Header files are a big part of programming in larger programs, since they allow us to carve up a giant program into a number of smaller pieces and let more programmers work on the pieces. We create a header file and fill it with function prototypes for all the functions in a single file. We then give the header files to anyone who needs to use the functions defined, and they include those headers in their program files. As you might suspect, including a file tells the compiler to just read the text from that file as though it had been type in here.
The include line can take two forms, though:
If you are creating your own library of functions, and the header file is with all your other code files, the include specifies the name and path to the header file within your project area. In this case, we place double quotes around the header file name.
#include "my_library.h"
There are many system level libraries included with any standard C/C++ compiler. These libraries are distributed as compiled files, orfen without including source code for the routines. The linker will take care of merging everything together to build the program.
In this case, the header files are located with the compiler files which are somewhere on your system, often hiding from you. SInce we do not know where they are, we include these files with a special notation:
#include <iostream>
The angle brackets tell the compiler to hunt these headers down wherever the compiler lives!
Trolls might need to have a few variables of their own to work with when they do their job. These variables are created as each job starts up. Formally, these parameters act like variables as well, and the names of them are actually known inside the box. The troll has a stack of cards available that can be used inside the box as needed to process the data. The weird thing is that all those cards are gathered up and thrown away as the Troll completes the work. The next time the Troll is awakened, the old cards are gone and new ones are set up to process the new task.
The term for us computer folks for these cards is local variables. They are local to the function where they are defined. The names used for these variables can only be seen by code inside the function body. That means you can use those names again in another function with no problems. While the function operates, local variables act like normal variables we have been using all along. That disappearing act can be a bit upsetting when you start, but you learn to live with it!
Local variables are created by normal variable declarations inside of the function body. Unless you initialize them, they hold junk as they are created, and no trace of the contents of those containers will exist after the function ends.
Note
If you think about it, the variables we set up inside our main function are local to that function as well. When we call a function and place a variable in the parameter list, the value is copied into the parameter inside the function. The two names can (and usually are) different!
If we declare variables outside of any function body, they are called global variables. All functions can access these variables if they like. There is a minor glitch in this, the variables must be declared above the functions so the compiler can get all the information needed about the variable from the declaration before it sees an attempt to use that variable.
Although this might seem to be an easy way to share a variable and let multiple functions use them, it is something most good programmers do not do. If you try to create a function that does something useful and use it in another program, global variables make this hard to do. Not only do you need to learn how to use the function, but you need to set up the needed global variables or the function will not work. We want our Trolls sealed up in the box - they get rowdy otherwise!