.. _more-functions: More on Functions ################# .. include:: /header.inc .. vim:filetype-rst spell: Last time, we reviewed the basics of functions and their use as a decomposition block in programming. This time, we want to look further at how functions interact with their environment, and how we call functions into life. Structure of a function *********************** A basic function is a simple container that holds a block of code we want to activate from time to time. As we say last time, this container can live within a single program file, or be split off into a file of its own, often with other supporting routines needed to make the primary function - well - function! We define an interface to the function that sets us up to split the function off and have other teams of programmers built the function with a guarantee that we can hook things up at a later time. Let's look closer at that interface. The prototype revisited *********************** The protype is everything needed for the compiler to make sure your function is being used properly. The code is not necessary, but everything on that first line in a function definition is neded. When we split off the code (everytthing inside of the curly brackets - including the brackets, we simply place a semicolon and you have the function prototype. Normal (if there is such a thing) functions return a single value as a result of their work. This is the mathematician's view of a function. We define this return type as part of the prototype. We also provide the function name, and a parameter list we eill use to pass information to the function. Those parametrs are normally used by the function in its processing, when we call it into life. Two questions crop up right away: What if I need more than one value to be returned, and what if I have a bunch of data to pass to the function? Parameters in detail ==================== There are actually two kinds of parameters we can pass to a function: Value Parameters and Address Parameters. The simplest of these is the value parameter. Value parameters ---------------- The simple definition of a value parameter is just a parameter that receives a copy of some information from the caller's world. The caller places a reference to a variable they can access in the right spot in the parameter list on a call to our function. The system will look up the current value of that variable, and copy it into a place where the function can access it. We can also place a literal value in the parameter list if the parameter is a value parameter. Here is an example: .. code-block:: c #include ... float mySin(float angle) { // do stuff with angle return sin(angle); } ... float myAngle = 10.0; float sinX = mySin(myAngle); float sinZ = mySin(10.5); Which gives this: sinX = -0.544021 sinY = -0.544021 In this example, the `mySin` function is defined with a single formal parameter named ``angle``, which must be a float. In the first call to this function, we pass in the current value from the `myAngle` variable through the interface. What that means is that when the `mySin` function wakes up, it finds an initialized local variable that it knows by the name `angle` that it can play with. The initial value is whatever was in the caller's `myAngle` variable at the time of the call. If you think about it, using this scheme, there is no way for the `mySin` function to alter the value of the caller's `myAngle` variable. To the mathematician, this is a good thing, since altering anything in the caller's world is called a side-effect, which is something we do not really want - most of the time. It would be nasty for the caller to discover that their variable had been messed with just because they asked us for the sin of that angle! How do we set up value parameters? Well, it is easy, because we do not need to do anything special, this is the default kind of parameter for all single valued data types (that leaves out arrays as we shall see in a moment!) Address parameters ------------------ If we need to pass a large amount of data to a function, it is inefficient to send down a copy, so we send down information the function can use to locate the parameter directly, and let it play with the caller's data! This is not necessarily a good thing, since the function could do damage to something it just needs to peek at, but we may have no option but to do this. Formally, what we send to the function is a reference to the caller's data, but it really is just a special container containing the physical memory address where the data lives! Since we let the function know where the caller's data is in memory, they can use that information to modify the caller's data, or just look at the values. Setting up this kind of interface is quite easy. Here is our example from above, modified for reference parameters. .. code-block:: c float mySin(float & angle) { // do stuff with angle return sin(angle); } ... float myAngle = 10.0; float sinX = mySin(myAngle); Can you spot the change. It is a single character between the data type and the parameter name. With this character in place we can translate the parameter declaration by reading it backward and using is the address of when we read the `&` character. .. code-block:: c float & angle This becomes .. code-block:: text angle is the address of a float Which float? Why whatever the caller provides on an actual call. Trying to compile the original code will result in an error. The first call will work fine, since we called `mySIn` with a variable name. But the second call will fail. We are saying that we are sending down the address of a container that the function can use. That last call to the function does not pass in a variable name - it passes down a literal value, which has no address! So this last call will not work. Now, if I let my `mySin` function play badly, I can alter the caller's world: .. code-block:: c int mySin(float & angle) { angle = 20.0; return sin(angle); } Here I am doing two things: returning a value the normal way, and returning a value through the caller's variable. If you do this kind of thing it is vital that you document what you are up to - the user of your function will speak kindly of you if you do this! Here is my output from this example: .. code-block:: c myAngle before the call = 10 sinX = 0.912945 myAngle after the call = 20 Passing arrays to functions *************************** We will go over arrays in detail later, but you should be familiar with array basics. Since arrays can involve a large number of data items, the designers of the array data type decided that arrays would always be passed to functions as address parameters. You do not need to specify the & character to set this up. Remember, when we send down an array to a function, we are only providing the function with the location of the first element of the array, and the data type of each element. The function has no way to figure out how many items are in that array, so we always include another parameter to tell it where to stop: .. code-block:: c void displayArray(int data[], int size) { for(int i=0;i #include #include using namespace std; float myAngle = 10.0; float mySin(float angle) { return sin(angle); } float sinX = mySin(myAngle); float sinY = mySin(10.0); int main(int argc, char *argv[]) { cout << "sinX = " << sinX << endl; cout << "sinY = " << sinY << endl; return EXIT_SUCCESS; } Look this over and see where things are defined. Most of the time, we define variables inside functions that need them. These are called local variables which means they are local to the function where they are defined. Other functions cannot see those names, and cannot access them either. If we define variables outside of any function, we call those variables global, and all functions can see those variables and use them. (Technically, only those functions defined below the variable declaration can access them, but you can fix this by moving the global variables to the top of your code, above any functions definitions). Why use global variables? ************************* Well, the short answer is don't! Many programmers discourage the use of globals, since the code becomes very non-portable if you use them. Consider the example of a function written to access the data it needs from a global variable. If you find this routine handy, and want to use it in another program, you need to hook your function into that new program and you are done, right? Nope! You must then modify your new program and provide the required global variable. What happens if that name is in use in that new program already? Well, you can see how the problems keep multiplying. Ultimately, you must provide the code of your function to the programmers in the new project, because they might need to modify your names! Huge problem! If you write a program that uses no global variables, you do not need to provide the source code with your function, just a linkable object file. This can be handy if you want to protect your source code from prying eyes! Overloading functions We have mentioned this concept earlier in our study. If you provide a different signature for a function, that is a different prototype, you can define two (or more) functions with the same name but different parameters, and/or return types. This is handy when you want to provide the same functionality for different data types. Like this: .. code-block:: c float mySin(float angle); double mySin(double angle); As you might suspect, the code inside these two functions will be very similar. So similar in fact, that C++ provides us a way to generate functions where the only difference is in data type. We will see this templating feature later in the course.