When designing large programs, it was noticed that a segment of code was often used more than once during the program. And in very large programs, there might be more than one of these repeated segments. In the following 'diagram', each letter represents a small segment/fragment of code:
A, B, C, B, C, A, D, E, B, C, F, G, C, H, I, A, B, C, D, J, H, ...
Note how the code segments labeled B and C occur many times — often together. A is also repeated several times. See:
A, B, C, B, C, A, D, E, B, C, F, G, C, H, I, A, B, C, D, J, H, ... - - - - - - - - - - - - - - - - ---- ---- ---- ---- ------- -------
So H appears twice. A appears three times (twice in an ABC group). B appears four times (always in a BC group). And C appears five times (once by itself). Finally D also appears twice.
By grouping such segments of code together in a common place, we can avoid writing them several times. Instead, we just refer to them when the point in the program comes where we would have re-typed them. This grouping is known as creating (or extracting) a module (or function). When we refer to the module, we are invoking (or calling) it — asking it to do its job at this time.
When grouped into functions, the above code might look like:
S: A T: B U: C W: D X: H Y: T, U // B, C Z: S, Y // A, B, C Z, Y, S, W, E, Y, F, G, U, X, I, Z, W, J, X, ...
Notice how much shorter the sequence is now. (Also remember that each letter here stands for a group of logically connected statements — not necessarily just a single statement. And what's more, the statements don't have to be extremely simple assignment, input, or output — they can be branches or loops or calls to other functions!)
While this may look more complicated, forming functions this way actually allows us many benefits:
Up until now, you have thought of yourself as just a single individual. However, in reality, you've been two people since this class began. As a programmer, you are both the application coder as well as the application tester. You perform the role of end-user when the testing phase of a programming project is at hand.
With the advent of functions, you will become two more people (sort of). You will still be the application coder and tester, but you will also be the coder of and caller of functions. As mentioned above, coding functions is fairly simple — you get to concentrate on one simple task. This is because when you code a function, you aren't concerned with how it will be used in an application. You are only concerned that the function performs its duty well.
Then, when that is done, you switch hats back to the application coder. Now you are concerned with how you will use this function in your application. You are the function caller as well as the application coder.
Don't get confused between function caller and end-user. One is a programmer who is re-using code written by someone (maybe herself, maybe not). The other is some schmuck who's bought your program and is trying to run it.
In computer terms, input means values needed before a calculation can be carried out. Likewise, output means value(s) produced from such calculations.
One thing that causes problems at this point for many people is that no longer are all inputs coming from the keyboard and all outputs going to the screen. A function often gets information from the function caller (the programmer who is calling it). Such information is known as function input. Functions also often return information to the caller. This information is known as function output.
To make the distinction between console and function I/O more clear, let's settle on the following definitions:
Again, function I/O is information passed to/from a function and some calling part of the program (some other function). Console I/O is information passed across the program boundary to/from the user.
In any programming language you need to describe to the compiler what your function looks like to those who call it. In other words, you need to show it somehow what inputs and what output your function expects/provides. Since it is just a machine, it doesn't care about semantics — it doesn't care about the meaning of the inputs and output. It only cares about the types (short, char, etc.) and the order in which they are listed. The compiler also needs to know a name by which your function will be called.
To group all of these things together, the language designers formed the function header. The header of a function starts with the type of the output from the function. Then comes the function's name. Finally comes a parenthesized, comma-separated list of inputs. In computer language terms, an input is called an argument and an output is called a return value. An argument typically consists of both a type and a name. The type is for the compiler to know what types of inputs to expect. The name is so those calling your function can know the meaning of the inputs. For instance, it is legal for a function to have a header such as:
double pow(double, double)
But this is difficult to understand. We know that the function is known as pow and that it will return a double. We also know that it will accept two inputs of type double. But we have no idea what those two inputs mean — what they are. It makes much more sense to human programmers trying to call the function to know that the first input is a number and the second input is the power to which the number is to be taken:
double pow(double base, double exponent)
We might even want to put a comment on such a function to make sure we are understood:
// Function pow takes a base and exponent and returns the base // taken to the exponent power: // // exponent // base // double pow(double base, double exponent)
Now, to put this together, we need to tell the compiler HOW to actually perform this operation. For this we need to do as we've been doing with main all along: give it a function body. This is known as a function definition. It is the header of the function followed by a brace-enclosed series of statements which end in a return statement:
// Function pow takes a base and exponent and returns the base // taken to the exponent power: // // exponent // base // double pow(double base, double exponent) { double power; // code to raise base to the exponent'th power return power; }
Notice that here we are returning the value of a local variable. This variable will exist only within this function and therefore won't interfere with other variables in other functions which happen to have the same name. (You would replace the comment in the body with actual code, of course; but this isn't necessary to our purpose here.)
If you use a Pascal-style layout for your program, you can put the function definitions before the functions are called and all will work perfectly well. However, in a C-style program layout, the function definitions all follow main. In order for the compiler to know what is going on before a function gets called, we must introduce it to the function in some way. It need not know the entire definition of the function before it is called (one of the magic things about the compiler), but it does need to know what the header looks like. So, we place the header above main so that it might understand the call(s) to the function that it will see in main. But it would look funny to have those headers up there on lines alone. So we append a semi-colon to the end of them and call them a prototype (a function declaration, if you will):
// Function pow takes a base and exponent and returns the base // taken to the exponent power: // // exponent // base // double pow(double base, double exponent);
Any of the normal types (short, float, long, char, etc.) can be used with input or output types. There is, in addition a special type to be used with special functions that require no input from their caller or return no output to their caller: void. A void in the real world is like a vacuum — empty, devoid, lacking. Therefore, what you are saying by using the type void is that there is nothing coming in (or going out):
void func_name(type arg, type arg, ...) // function that // returns nothing ret_type func_name(void) // function that // takes nothing void func_name(void) // function that // neither takes // nor returns // anything
Some people worry that they'll never understand all of this function nonsense. Balderdash! It just takes time. How long did it take you to learn to tie your shoes? Ride a bike? Walk and chew gum at the same time? Learning any new skill takes time and practice! But, here are a few tips to make your learning (hopefully) easier:
Calling a function in C++ is done much like calling a function in algebra. To call the function pow mentioned above, for instance, you would do something like:
double result, base, exponent; base = 2.0; exponent = 3.0; result = pow(base, exponent); // result gets 8.0 assigned to it
Note that this looks much like the algebra expression y = f(x). The primary difference is that in C++, functions can take many arguments or no arguments; or they might even return no value. The calls all look similar:
void print_greeting(void); short get_short(void); short twice(short x); void print_answer(short y); int main(void) { short x, y; print_greeting(); x = get_short(); y = twice(x); print_answer(y); return 0; } // function definitions here
Note that there are no types listed in the function calls. Only the variables (if any) are mentioned — not their types. This is because the compiler already knows the types of the variables and it also knows the types of the arguments. Since they match, all is well.
Note also one very important point: you must always have a set of parentheses on a function call — even if they are empty! To store a returned value, you must use an assignment statement. If, on the other hand, you don't care to store the value, but merely want to use it, you can do things like:
void print_greeting(void); short get_short(void); short twice(short x); void print_answer(short y); int main(void) { print_greeting(); print_answer(twice(get_short())); return 0; } // function definitions here
In other words, pass the result of the inner-most function to the next outer function as an argument. This is known as function call nesting. You've probably seen it in algebra: y = f(g(x)). It saves on variable space, but can make things more difficult to read.
It does bring up a good point, though. twice is expecting a short named x, but here it gets some result from get_short. This result happens to be a short, but it doesn't really have a name (unless you want to call it get_short). And yet this code works just fine. That implies to us that it isn't the names of arguments that matter to the compiler. Indeed, it is only the type of the argument and the position of the argument within the argument list that determines what is what to the compiler. It ignores names. So we could re-write our first function call example in several ways. One is to call the function with differently named variables:
double result, x, y; x = 2.0; y = 3.0; result = pow(x, y); // result gets 8.0 assigned to it
It doesn't matter to pow that the arguments that it gets are of a different name than the ones it expects. This is the difference between actual arguments (those provided by the caller) and formal arguments (those expected by the function). The arguments in a function's header (formal ones) are merely local names for values that the caller sends. This allows the function to work in widely ranging applications.
Note that this is similar to algebra where it didn't matter what value you passed to f(x), it would still give the correct answer. x was just a variable whose value could change. We could have equally well used the variable u or maybe one called t.
The compiler merely looks at the actual argument list and the formal one and matches the arguments up by position and type. If all works out, it makes the call — copying the values from the actual arguments into the formal ones:
//double double double result = pow(x, y); double pow(double base, double exponent);
It notes that the first actual argument is a double and that the corresponding formal argument is also a double: good. Then it looks at the next argument: again, double lines up with double. Now it copies the value of the first actual argument (x) into the first formal argument (base) and likewise with the second actual and formal arguments (y & exponent). Now, when pow executes, base will have the same value as x and exponent will have the same value as y. Thus, the returned value will still be the result of raising x to the yth power — even though the names don't match exactly.
Oh, and on returning, the compiler notes that the answer that pow returns is of type double and that the variable being assigned this value (result) is also of type double and so the assignment works fine, too.
Some other ways to call functions are also now evident (since the names don't matter):
ans = pow(x, y); // result name doesn't matter ans = pow(x, 4.0); // literal instead of variable ans = pow(4.0, y); // same, but other argument ans = pow(x, y*4); // expression instead of variable ans = pow(x*3, 4); // expression instead of variable // and literal for other argument cout << pow(x, y) << endl; // nest function call in printing // call
Thus, the fact that names of arguments don't matter to the compiler gives the function call mechanism MUCH more power than it could otherwise have (no pun intended).
For another complete example, see the examples section. Also check out the notes there — especially the dataflow diagram!