Modules vs. Functions

Susan had a question about this new notion of a function, as related to modules:

Susan: So a module has nothing to do with blocks and functions? If a function only "calls" another function, then how do you call a module?

Steve: You can't call a module. In fact, although a few language features apply to modules rather than functions, modules don't really have much significance in C++ other than as places to store related functions.

When we call a function, we usually have to provide it with input (for example, some values to be averaged) and it usually produces output which we use in further processing (for example, the average of the input values). Some functions, though, have only one or the other. For example, some functions are organized in pairs consisting of one storage function and one retrieval function; the first stores data for the second to retrieve later. In that case, the storage function may not give us anything back when we call it, and the retrieving function may not need any input from us.

To see how and why we might use a function, let's take a look at a program having some duplicated code (Figure 5.1).

Figure 5.1. A sample program with duplicated code (code ofunc.cpp)
#include <iostream>
using namespace std;

int main()
{
   short FirstWeight;
   short SecondWeight;
   short FirstAge;
   short SecondAge;
   short AverageWeight;
   short AverageAge;

   cout << "Please type in the first weight: ";
   cin >> FirstWeight;

   cout << "Please type in the second weight: ";
   cin >> SecondWeight;

   AverageWeight = (FirstWeight + SecondWeight) / 2;

   cout << "Please type in the first age: ";
   cin >> FirstAge;

   cout << "Please type in the second age: ";
   cin >> SecondAge;

   AverageAge = (FirstAge + SecondAge) / 2;

   cout << "The average weight was: " << AverageWeight << endl;
   cout << "The average age was: " << AverageAge << endl;

   return 0;
}

I'd like you to look particularly at this line:

AverageWeight = (FirstWeight + SecondWeight) / 2;

and this one:

AverageAge = (FirstAge + SecondAge) / 2;

These two lines are awfully similar; the only difference between them is that one of them averages two weights and the other averages two ages. While this particular example doesn't take too much code to duplicate, it may not be difficult for you to imagine the inefficiency and nuisance of having to copy and edit many lines of code every time we want to do exactly the same thing with different data. Instead of copying the code and editing it to change the name of the variables, we can write a function that averages whatever data we give it.

Figure 5.2 is a picture of a function call. The calling function (1) is main; the function call is at position (2). The called function is Average (3), and the return is at position (4); the returned value is stored in the variable AvgAge, as indicated by the assignment operator = in the statement

AvgAge = Average(FirstAge,SecondAge);

Figure 5.2. A function call


and the calling function, main, resumes execution at line (5).

By the way, it's important to distinguish between returning a value from a function, which is optional, and returning control from a called function to its calling function, which always happens at the end of the called function (unless the program has terminated due to an error in the called function).[1]

[1] If you don't provide a return statement in a function that you're calling, then the called function will just return to the calling function when it gets to its closing }. However, this is not legal for a function that is defined to return a value. This of course leads to the question of why we'd call a function that doesn't return a value. One possibility is that the function exists only to produce output on the screen, rather than to return any results. The actions that a function performs other than returning a value are called side effects.

While we're on the subject of the calling function, you may be wondering why we started the example at the beginning of main. That's because every C++ program starts executing at that point. When the main function calls another function, such as Average, then main is suspended until Average is finished. When Average finishes, main resumes where it left off.

This isn't limited to one "level" of calls. The same thing can happen if Average (for example) calls another function, let's say Funcx; Average will wait until Funcx returns before continuing. Then when Average finishes, it will return to main, which will take up where it left off.

This idea of calling and returning from functions led to the following discussion with Susan:

Susan: So if you wanted to be really mean you could get into someone's work in progress and stick a return somewhere in the middle of it and it would end the program right there? Now that I am thinking about it, I am sure you could do a whole lot worse than that. Of course, I would never do such a thing, but what I am saying is that whatever you are doing when the program gets to the return statement, then it is the end? Next stop, C:?

Steve: Yes and no. If you're in main, then a return statement means the program is finished and if it is a console mode program like the ones we are writing here, you will indeed see the command line prompt come up on your screen. If you're in a function other than main, it means "return to the function that called this function". In the case of a function that returns a value, the expression in the return statement tells the compiler what value to use in place of the function call. For example, the statement AvgAge = Average(i,j); sets AvgAge to the result in the return statement of the function Average. As you can see by looking at that function, the returned value is the average of the two input values, so that is the value that AvgAge is set to by this statement.

Susan: OK, but what about the return 0; at the end of the main program? Why should it be 0?

Steve: The return statement in main can specify a different value if you wish. However, the custom is to return 0 from main to mean "everything went well" and some value greater than 0 to mean "there's a problem". This isn't entirely arbitrary, because a batch file can test that return value and use it to alter the flow of the execution of commands in the batch file.

Susan: OK, let's see if I have this right: The return statement has to match the main statement. This is so confusing. Look, when you say "The value that is returned, 0, is an acceptable value of the type we declared in the line int main ()" — since I see no 0 anywhere around int main () — you are referring to the int. An int can have a value of 0, right?

Steve: Right, the 0 has to match the int. That's because a function can have a return type, just like the type of a variable. In this case, int is the type of the main function and the value is filled in by the return statement.

Susan: OK, then all this is saying is that the value that is produced is the same type as that declared at the beginning of a program. Since we declared the type of main as an int, if the value produced were a letter or a picture of a cow, then you would get an error message?

Steve: Well, actually a letter (i.e., a char) would be acceptable as an int, due to rules left over from C. Otherwise, you're exactly correct.

Susan: Hey, where is the 0 coming from to be returned?

Steve: It's specified as a literal value in the return statement; you could put any legal int value in there instead, if you wanted to.

Susan: So the return value doesn't have to be a 0?

Steve: Right.

Susan: So 0 could be another int value, but it can't be a variable? Even I don't know what I am talking about now!

Steve: I think I've confused you unnecessarily. You can certainly return a value that is specified as a variable, such as return i;. What I meant was that the 0 we're returning in this case is a constant, not a variable.

Susan: The picture helps with the calling confusion. But I don't understand why main is the calling function if the calling function suspends execution. How can you initiate a function if it starts out suspended? But I am serious.

Steve: The main function starts execution as the first function in your program. Therefore, it isn't suspended unless and until it calls another function.

An Example of Using a Function

I think it's time for a more detailed example of how we would use a function. Suppose we want to average several sets of two numbers each and we don't want to write the averaging code more than once. The Average function just illustrated provides this service; its input is the two numbers we want to average and its output is the average. Figure 5.3 shows the code for the function Average without all the lines and arrows:

Figure 5.3. A function to average two values
short Average(short First, short Second)
 {
 short Result;
 Result = (First + Second) / 2;

 return Result;
 }

You can try out this function in a running program in the usual way. The name of the program is func1.cpp and you can see it in Figure 5.5 on page 235.[2] As had become routine, I couldn't sneak this idea (of writing a function) past Susan without a discussion.

[2] If you run this program under the debugger and look at the variables at the beginning of the program, please don't be confused by the seemingly random values that all of the variables start out with. These are just the garbage values that happen to be lying around in memory where those variables reside; as we've already seen, variables that haven't yet been assigned a value are called uninitialized variables. The variables in this program are all initialized before they are used, but you can look at them in the debugger before the initializing statements have been executed.

Susan: Where you say "and we don't want to write the averaging code more than once", are you just saying if you didn't do the Average function thing then you would have to write this program twice? I mean for example would you have to write a program separately for weights and then another one from the beginning for ages?

Steve: We wouldn't have to write an entirely separate program; however, we would have to write the averaging code twice. One of the main purposes for writing a function is so that we don't have to repeat code.

To analyze this piece of code, let's start at the beginning. Every function starts with a function declaration, which tells the compiler some vital statistics of the function. The function declaration consists of three parts:

  1. A return type;

  2. The function's name;

  3. An argument list.

In the case of our Average function, the function declaration is short Average(short First, short Second). The return type is short, the name of the function is Average, and the argument list is (short First, short Second). Let's take these one at a time.

Returning Data to the Calling Function

The first part of the function declaration is the return type, in this case short. This indicates that the function Average will provide a value of type short to the calling function when the Average function returns. Looking at the end of the function, you will see a statement that says return Result;. Checking back to the variable definition part of the function, we see that Result is indeed a short, so the value we're returning is of the correct type. If that were not the case, the compiler would tell us that we had a discrepancy between the declared return type of our function and the type actually returned in the code. This is another example where the compiler helps us out with static type checking, as mentioned in Chapter 3; if we say we want to return a short and then return some other incompatible type such as a string, we've made a mistake.[3] It's much easier for the compiler to catch this and warn us than it is for us to locate the error ourselves when the program doesn't work correctly.

[3] What do I mean by an incompatible type? C++ allows us, for example, to return a char variable where a short (or an int) is expected; the compiler will convert the char into either of those types for us automatically. This is convenient sometimes, but it reduces the chances of catching an error of this kind and therefore is less safe than it could be. This practice, called implicit conversion, is a legacy from C, which means that it can't be changed for practical reasons even though it is less than desirable theoretically.

Susan wanted to know more about the return type. Here's the conversation that ensued:

Susan: This return type thing — it will have to be the same type of value as the output is?

Steve: For our purposes here, the answer is yes. As I've already mentioned, there are exceptions to this rule but we won't need to worry about them.

Susan: Do you always use the word return when you write a function?

Steve: Yes, except that some functions have no return value. Such functions don't have to have an explicit return statement, but can just "fall off the end" of the function, which acts like a return; statement. This is considered poor form, though; it's better to have a return statement.

The function name (in this case, Average) follows the same rules as a variable name. This is not a coincidence, because both function names and variable names are identifiers, which is a fancy word for "user-defined names". The rules for constructing an identifier are pretty simple, as specified by the C++ Standard:[4] “An identifier is an arbitrarily long sequence of letters and digits. ... Upper- and lower-case letters are different. All characters are significant.” (p. 14)

[4] The official name of the C++ standard is ISO/IEC 14882:1998(E).

In other words:

  1. Your identifiers can be as long as you wish. The compiler is required to distinguish between two identifiers, no matter how many identical characters they contain, as long as at least one character is different in the two names.[5]

    [5] You don't have to worry about wasting space in your program by using long identifiers. They go away when your program is compiled and are replaced by addresses of the variables or functions to which they refer.

  2. They can be made of any combination of letters and digits, as long as the first character is a letter.[6]

    [6] For historical reasons, the underscore character _ counts as a letter. However, do not begin a variable or function name with an underscore, as such names are "reserved" for use by compiler writers and other language implementers.

  3. The upper and lower case versions of the same character aren't considered equal as far as names are concerned; that is, the variable xyz is a different variable from Xyz, while XYZ is yet another variable. Of course, you may get confused by having three variables with those names, but the compiler considers them all distinct.

By the way, the reason that the first character of an identifier can't be a digit is to make it easier for the compiler to figure out what's a number and what isn't. Another rule is that identifiers cannot conflict with names defined by the C++ language (keywords); some examples of keywords that we've already seen are if and short.

Finally, we have the argument list. In this case, it contains two arguments, a short called First, which holds the first number that our Average function uses to calculate its result; and a second argument (also a short) called Second, which of course is the other number needed to calculate the average. In other cases, there might be several entries in the argument list, each of which provides some information to the called function. But what exactly is an argument?

Function Arguments

The question of what is an argument is more subtle than it may appear. An argument is a value that is supplied by a function (the calling function) that wishes to use the services of another function (the called function). For example, the calling function might be our main function, and the called function might be our Average function, while the arguments are two short values to be averaged. Arguments like the ones here are actually copies of values from the calling function; that is, the compiler will initialize the variable named in the argument list of the called function to the value supplied by the calling function. This process of making a copy of the calling function's argument is referred to as call by value, and the resulting copy is called a value argument.[7] Figure 5.4 is an example of this argument passing mechanism at work with only one argument.

[7] This discussion might make you wonder whether there's another type of argument besides a value argument. There is, and we'll find out about it in Chapter 6.

Figure 5.4. Argument passing with one argument (codeirthday.cpp)
#include <iostream>
using namespace std;

short Birthday(short age)
{
 age ++;
 return age;
}

int main()
{
 short x;
 short y;

 x = 46;
 y = Birthday(x);

 cout << "Your age was: " << x << endl;
 cout << "Happy Birthday: your age is now " << y << endl;

 return 0;
}

In this program, main sets x to 46 and then calls Birthday with x as the argument. When Birthday starts, a new variable called age is created and set to 46, because that's the value of x, the argument with which main called Birthday. Birthday adds one to its variable age, and then returns the new value of that variable to main. What number will be printed for the variable x by the line cout << "Your age was: " << x << endl;? Answer: 46, because the variable age in Birthday was a copy of the argument from main, not the actual variable x named in the call to Birthday. On the other hand, the value of y in the main program will be 47, because that is the return value from Birthday.

As you might have guessed, the notion of copying the argument when a function is called occasioned an intense conversation with Susan.

Susan: This is tough. I don't get it at all. Does this mean the value of the short named x will then be copied to another location in the function named Birthday?

Steve: Yes, the value in the short named x will be copied to another short called age before the execution of the first line in the function Birthday. This means that the original value of x in main won't be affected by anything that Birthday does.

Susan: Now for the really confusing part. I don't understand where you say "An argument like the one here (short age) is actually a copy of a value in the calling function". Now, I have read this over and over and nothing helped. I thought I understood it for a second or two and then I would lose it; finally I have decided that there is very little in this section that I do understand. Help.

Steve: When you write a function, the normal behavior of the compiler is to insert code at the beginning of the function to make a copy of the data that the calling function supplies. This copy of the data is what the called function actually refers to, not the original. Therefore, if you change the value of an argument, it doesn't do anything to the original data in the calling function.

If you (the programmer of the function) actually want to refer to the data in the calling function and not a copy of it, you can specify this when you write the function. There are cases in which this makes sense and we'll see some of them in Chapter 6.

Susan: I don't understand why it is a copy of the calling function and not the called function.

Steve: It's not a copy of the calling function; it's a copy of the value from the calling function, for the use of the called function. In the sample program, main sets x to 46 and then calls Birthday with x as the argument. When Birthday starts, a new variable called age is created, and set to 46, because that's the value of x, the argument with which main called Birthday. Birthday adds 1 to its variable age, and returns the new value of age to main. What will be printed by the line "cout << x << endl;"? Answer: 46, because the variable age in Birthday was a copy of the value of the argument from main, not the actual variable (x) specified in the call to Birthday. Does this explanation clarify this point?

Susan: I still don't understand the program. It doesn't make any sense. If x = 46, then it will always be 46 no matter what is going on in the called function. So why call a function? You know what, I think my biggest problem is that I don't understand the argument list. I think that is where I am hung up on this.

Steve: The arguments to the function call (x, in the case of the function call Birthday(x)) are transferred to the value of the argument in the function itself (the short variable age, in the case of the function Birthday(short age)).

Susan: In that case, why bother putting an x there, why just not put 46? Would it not do the same thing in the called function, since it is already set to 46?

Steve: Yes, but what if you wanted to call this function from another place where the value was 97 rather than 46? The reason that the argument is a variable is so you can use whatever value you want.

Susan: If we called Birthday with the value 46, then the 46 would be 46++, right?

Steve: 46++ is a syntax error, because you can't change the value of a literal constant. Only a variable can be modified.

Susan: So if you want to state a literal value, do you always have to declare a variable first and then set a variable to that literal value?

Steve: No, sometimes you can use a literal value directly without storing it in a variable. For example,

cout << 15;

or

cout << "Hello, my name is Steve Heller";

What I was trying to say is that you can't change a literal value. Thus, 15++; is not legal because a literal value such as 15 represents itself, that is, the value 15. If you could write 15++;, what should it do? Change all occurrences of 15 to 16 in the program?

Susan: Okay. Now, how does age get initialized to the value of x?

Steve: The compiler does that when it starts the function, because you have declared in the function declaration that the argument to the function is called age, and you have called the function with an argument called x. So the compiler copies the value from x into age right before it starts executing the function.

Susan: Oh, I see. That makes sense, because maybe later on you want to call the same function again and change only a little part of it, but you still need the original to be the same, so you can just change the copy instead of the original. Is that the purpose?

Steve: Not quite. The reason that the called function gets a copy of data, rather than the original, is so that the person writing the calling function knows that the original variable hasn't been changed by calling a function. This makes it easier to create programs by combining your own functions with functions that have already been written (such as in the library). Is that what you meant?

Susan: So is everything copied? I am getting confused again, are you going to talk a little more about copying in the book? Have I just not gotten there? Anyway, if you haven't mentioned this more, I think you should, it explains hidden stuff.

Steve: Don't worry, we're going to go into much more detail about how this works. In fact, it's a major topic in the rest of the book.

How the Average Function Works

The same analysis that we have just applied to the Birthday function applies also to the Average function that we started out with; the arguments First and Second are copies of the values specified in the call to Average.

Now that we have accounted for the Average function's input and output, we can examine how it does its work. First, we have a variable definition for Result, which will hold the value we will return to the calling function; namely, the average of the two input values.

Then we calculate that average, with the statement

Result = (First + Second) / 2;

Once the average has been calculated, we're ready to return it to the calling program which is accomplished by the line return Result;. Finally, we reach the closing }, which tells the compiler that the function is done.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset