Previous Page TOC Next Page



- 13 -
Building Your Own Functions


structured program


What You'll Learn About


So far, every program you've written has contained one and only one function that you wrote—main(). Visual C++ programs can contain many more functions that you write. The longer your program is, the better it is to break it into several small functions. By following a building-block approach, you separate routines into their own areas and make debugging easier because you can focus on specific problems without letting the rest of the code get in your way.

Writing a function in addition to main() does require a little fancy programming footwork, but writing multiple functions isn't difficult at all.

Separating Code




A program with many small functions is a lot easier to maintain than one long program. When writing separate functions, you have to manage the communication between those functions so that they can "talk" to each other.

Why does a book contain separate chapters? For one thing, one long, continuous book would seem to drag on and on. Chapters give the reader breaks, even if the reader starts another chapter right after finishing one. Chapters give the reader a sense of accomplishment.

More important, separate chapters—especially in a technical book such as this one—allow the reader to zero in on specific topics. Suppose that you picked up this book already knowing how to program in Visual C++, but you'd never taken the time to master the switch statement. You wouldn't need to read the entire book. Instead, you could turn directly to Unit 10 and get exactly the information you needed without the rest of the text getting in the way.

definition

A structured program is a modular program that contains one function for each task the program does.

Structured programming becomes a habit when you begin to use it. People are used to breaking large tasks into smaller, more manageable ones. Programs often need to be broken down.

Suppose that you are writing a program that computes and prints payroll figures for employees. As the payroll clerk enters each person's payroll figures, the program should perform input validation, compute payroll results, compute tax amounts, and print the results.

Here is a skeleton version of what such a program might look like:


void main()

{

  // This is only an outline of a program

  //

  // Visual C++ code to validate employee data

  //

  // Visual C++ code to calculate payroll amounts

  //

  // Visual C++ code to compute taxes

  //

  // Visual C++ code to print the results

}


This program would more likely contain a large loop that continued running until all employee data was entered, validated, and printed, but we're keeping things simple for now.

There's nothing wrong with building a program such as this. For a real-world payroll system, however, this program would be extremely long. If the employee payroll figures were computed incorrectly, you would have to find exactly where in main() the problem occurred. Tracking such a bug would require you to make sure that the input validation code, the calculation code, and the printing code all worked properly.

If, however, you structured the program into separate functions, putting only one task in each function, you could more easily test each routine separately. Changing one routine wouldn't be likely to introduce as many bugs in other areas, as would be the case with one long program.



Each separate function might be several lines long. The important thing to keep in mind when writing separate functions is that each function should do, at most, one task. As a rule of thumb, each function shouldn't be longer than a single screen's length. If it is any longer, the function probably does too much work and you should break it into more functions.

When you write a function, you must start it with a name, parentheses, and surrounding braces, just like with main(). For example, here is the familiar outline of main():


void main()

{

  // First, define any variables

  // The body of main() goes here

}

When writing additional functions, keep this format in mind. Here is the outline of a function called CalcWages():


void CalcWages()

{

  // First, define any variables

  // The body of CalcWages() goes here

}

You never explicitly call the main() function. The runtime system always begins at the main() function. However, if you want to execute (or call, in Visual C++ terminology) another function in your program, you call that function by name. In fact, you have already seen this when using the string copy (strcpy) and string length (strlen) functions in Unit 6. For example, if you want main() to call the CalcWages() function, main() could do so with this statement:


CalcWages();   // Call the CalcWages() function

After CalcWages() finished, main() would regain control and continue on its way. Of course, any function can call any other function. After main() calls a function, that called function might in turn call yet another function.



In really good programs (the ones that you write), main() should be little more than a controlling function for the other functions. In other words, main() should contain very few Visual C++ statements other than function calls to other functions. In a way, main() acts like a table of contents for your program, controlling the execution of the rest of the code. If you need to change the program later, main() gives you the "big picture" that shows you how the rest of the program operates.

One additional advantage of separate functions is that you can call a routine more than once from different places in the program. For example, if you wanted to print your company's name and address at the top of every screen and report produced by a program, you could write a single function named PrintCompanyInfo() and call it from anywhere in the program in which the company information needed to appear.



As you saw with main(), the function call should be described with a return type. When a return type is void, it means that no data is returned. This will be covered later in the next unit, for now just accept that function definitions start with void.

Listing 13.1 contains a version of the payroll program described earlier. Instead of having one long main() function, this program is broken into several separate functions, each of which accomplishes a single task.



Break your programs into several small functions to aid in debugging and to decrease the number of bugs.

Listing 13.1 isn't a working program, only the outline of one.

Input Listing 13.1. An outline of a structured payroll program.

1: void main()

2: {

3:   // This is only an outline of a program

4:   //

5:   GetInput();    // Get and validate the input data

6:   CalcWages();   // Calculate the payroll results

7:   CalcTaxes();   // Calculate the payroll taxes

8:   PrintResults();   // Print the results

9:   return;        // Return to the IDE

10: }

11:

12: //***********************************************

13: void GetInput()

14: {

15:   // Visual C++ code to validate employee data

16:   return;   // Return to main()

17: }

18:

19: //***********************************************

20: void CalcWages()

21: {

22:   // Visual C++ code to calculate payroll amounts

23:   return;   // Return to main()

24: }

25:

26: //***********************************************

27: void CalcTaxes()

28: {

29:   // Visual C++ code to compute taxes

30:   return;   // Return to main()

31: }

32:

33: //***********************************************

34: void PrintResults()

35: {

36:   // Visual C++ code to print the results

37:   return;   // Return to main()

38: }


There is no output for Listing 13.1 because the program is incomplete as shown.

Analysis

Look at main() (lines 1 through 10). It controls the flow of the rest of the program. You can glance at main() and tell what the rest of the code does without wading through the rest of the code! Of course, using meaningful function names and ample comments helps make main() a true "control panel" that describes the rest of the code.

After the program is broken into separate functions, the code is certainly longer than the all-in-one function version you saw earlier. However, brevity isn't the key to structured programs—readability is.

In this listing, main() is the only function shown that calls another function. However, that doesn't have to be the case. Any of the subsequent functions could call any other function. The separating asterisk comments aren't required, but they do help you find where each function begins and ends.

definition

Recursion occurs when one function calls itself or when two functions call each other.

As with infinite loops, you can get into trouble if a function calls itself, or if function A calls function B and function B then calls function A. Recursive techniques can be helpful in advanced programming, but for now you'll want to stay away from them. (You'll be cursing your recursive programs!) If you write recursive functions without knowing exactly how to eliminate infinite recursion calls, you'll get a runtime error called stack overflow. Believe me, you don't want to overflow your stack!

Local and Global Variables




Variables have either local or global scope. Their scope determines whether or not another function can use them.

definition

A local variable can be used only within the block in which you define it.

All variables that you've defined in this book have been local variables. In other words, if you were to define a variable after main()'s opening brace, another function couldn't use that variable for anything. Only main() could use it. Perhaps you can see a problem: If separate functions are better than one long function, how can those functions work with the same data? If the user enters payroll values in one function, how can another function calculate with those values?

definition

A block is code between braces.

The concept of a block becomes important when discussing local variables. Variables aren't local to functions, but variables are local to blocks. For example, all the variables you've defined so far have been local to main() simply because you defined them at the top of main()'s primary block. A function always contains at least one pair of braces; therefore, a function always contains at least one block.

You can define variables at the top of any block, not just at the top of a function. When the block ends, all variables defined at the top of that block go away forever! When a block ends, all variables defined at the top of that block disappear and can never be used again unless you define them again elsewhere and reinitialize the variable with a new value.

Consider the following main() function:


void main()

{

  int i = 9;      // Local to main()'s large block

  cout << "i is " << i << endl;

    {               // Begin a new block

      int j = 17;   // Local to this block only

      cout << "j is " << j << endl;

    }               // j goes away!

  cout << "i and j are " << i

       << " and " << j << endl;   // Error!!!

  return;

}                // i goes away here!

i is local to the entire main() function because it's defined right after main()'s opening brace. i doesn't go away until its block ends at the end of main(). Therefore, from main()'s opening brace to its matching closing brace, i is active and is said to be visible.

j is a different story. Notice that the code doesn't define j until a new block begins. j is defined at the top of main()'s inner block; therefore, j is available (visible) only within that block. At the location of the inner block's closing brace, j disappears, causing an error on the subsequent cout that occurs right before return.

You might not be opening new blocks in the middle of a function and defining new variables inside them as done here, but it's vital that you understand the following:

definition

A global variable is visible throughout the rest of the program.

The opposite of a local variable is a global variable. At first, it seems as if global variables are the answer for multifunction programs, because they let more than one function see the same variable. However, global variables lend themselves to sloppy programming practices, so you should avoid defining too many of them.

You must define a global variable before a function begins. That is, you must define the variable before a function name or between two functions (after one function's closing brace and before another's beginning line). If you define global variables, it is good practice to define all of them before main() even though Visual C++ lets you define them later between other functions. Putting all your global variable definitions in one location helps you find them later.

All global variables are visible from their point of definition down in the source code. That is, after you define a global variable, any statement in any function that follows that global variable will be able to use it. The global variable won't go away until the program ends.

You can't use a global variable before its definition.

The problem with global variables is that they're too visible. All functions that follow their definition can access the global variables whether they need to or not. Local variables keep data on a need-to-know basis. Only functions that define local variables can use them, but any function can use visible global variables. One problem with global variables is that you can too easily change a global variable in one function and then inadvertently reinitialize that variable in another function. Also, global variables are sometimes difficult to find. If you define global variables at the top of a 20-page program, and you're working in the middle of that program, you'll have to flip back and forth from the beginning to the middle to see how those variables were defined. When you use local variables, their definitions always appear close to their usage.

All this discussion of local and global variables presents the following problem: With rare exceptions, you should use only local variables, but doing so keeps other functions that need access to those variables from using them. Luckily, you can teach Visual C++ to share data between only those functions that need access to certain variables. You'll learn how Visual C++ shares data between specific functions in the next section.

It is time to correct something that was said earlier in the book. With local scope, it is quite acceptable to use the same name in the program several times, as long as the scope is different. This doesn't mean that using the same name all the time is efficient or necessarily a good idea. Instead, it is a rule, which means that you can write a function without worrying about names used elsewhere in the program. For example, when writing loops, tradition has it that the loop counter is called i. It would be very inconvenient if every for loop you write had to invent a new counter name. However, if you call a function that also uses a for loop using i, the program needs to know that the i you refer to is the local i and not the calling function's i.

A further complication is that C++ does not stop you reusing a name in a new block that has already been declared. The rule is that where there is a duplicate name, C++ will use the nearest declaration. Look at the following example:


#include <iostream.h>

void main()

  {

    int counter = 0;

      {  // a new block

         int counter = 5;

         counter++;

      }

    cout << counter;

    return;

  }

In this snippet of code, the increment (++) is applied to the new block counter. This variable then disappears and cannot be seen by the main counter. So the result of the code snippet is to print:


0


When a name is reused, the type can be different.

Listing 13.2 contains a program with both local and global variables.



Local variables are safe, but they're visible only within the block in which you define them. Global variables often are messy and lead to program bugs, but they allow for mass sharing of data.

Input Listing 13.2. A simple program with three global and three local variables.

1:// Filename: LOCGLO.CPP

  2:// Defines six variables, two local and two global

  3:// and one both local and global

  4: #include <iostream.h>

  5:

  6: // Define two global variables

  7: int global1 = 10;   // Global variables can be any data

  8: int global2 = 20;   // type, just as local variables can be

  9: int both1   = 30;   // a global variable

 10:

 11: void main()

 12: {

 13:   int local1 = 14;

 14:

 15:   // The next line shows that globals are available

 16:   cout << "At the top of main(), " << endl;

 17:   cout << "The globals are " << global1 << ", " << global2

 18:        << " and " << both1 << endl;

 19:   cout << "The local variable local1 is " << local1 << endl;

 20:

 21:   // Create a new block

 22:   {

 23:     int local2 = 21;   // Local to this block only

 24:     int both1 = 90;    // Local to this block only

 25:                        // It hides global both1

 26:     cout << endl <<"In main()'s inner block, the globals are";

 27:                        // Globals are still available

 28:     cout << " " << global1 << " and " << global2 << endl;

 29:

 30:     cout << "Printing both1 sees the local variable "

 31:          << both1 << endl;

 32:

 33:     cout << "The local variable local1 is "

 34:          << local1 << endl;

 35:     cout << "The local variable local2 is "

 36:          << local2 << endl;

 37:

 38:     both1++;

 39:     cout << "The increment only sees local both1 - now "

 40:          << both1 << endl;

 41:

 42:     cout << "(local2 and local both1 about to disappear...)"

 43:          << endl;

 44:   }   // This terminates all valid use of local2

 45:       // and the local version of both1

 46:

 47:   cout << "Toward the end of main(), the globals are";

 48:   cout << " " << global1 << ", " << global2

 49:        << " and " << both1 << endl;

 50:

 51:   cout << "See how global both1 was not affected by the ++"

 52:        << endl;

 53:

 54:   cout << "The local variable local1 is " << local1 << endl;

 55:   cout << "The local local2 and local both1 "

 56:        << "are no longer valid.";

 57:

 58:   return;

 59: }   // All variables go away at end of execution

Output


At the top of main(),

The globals are 10, 20 and 30

The local variable local1 is 14

In main()'s inner block, the globals are 10 and 20

Printing both1 sees the local variable 90

The local variable local1 is 14

The local variable local2 is 21

The increment only sees local both1 - now 91

(local2 and local both1 about to disappear...)

Toward the end of main(), the globals are 10, 20 and 30

See how global both1 was not affected by the ++

The local variable local1 is 14

The local local2 and local both1 are no longer valid.

Analysis

Figure 13.1 helps to show where each of the variables in Listing 13.1 are defined and where they lose their visibility. It's important to note that if the program contained additional functions below main(), the global variables global1 and global2 would be fully visible to those functions.

Figure 13.1. Pointing out the local and global variables.

After main()'s inner block finishes in line 44, local2 goes away completely. The program defines local2 at the top of the inner block, right after the opening brace in line 23, and local2 goes away when the block ends (where the matching closing brace appears in line 44). local1 exists for the whole of main. global1 and global2 exist for a slightly longer time, from just before main starts to just after main ends.

both1 is first declared as a global variable in line 9. In the outer main block, only the global both1 exists, and this is printed out in line 18. The second declaration of both1 in line 24 does not destroy the original declaration of line 9. This is shown by the increment in line 38, which sees the local both1. The program prints the changed value in line 40. The local both1 disappears at the end of the inner block in line 44, and you can see in line 49 that the original both1 is affected by neither the local declaration in line 24 nor the increment in line 38.

Scope is a feature of C++ that is designed to make programs more maintainable. The use of local variables reduces problems often called side-effects. The problem of debugging programs is that you can easily examine code—even as it executes using the Visual C++ debugger—and understand how the code you are watching is affected by changes in variable values. It is much harder to understand how code in a different part of the program is affected. A global variable enables you to accidentally affect how an unrelated function works. This problem is so important that C++ introduces a completely new way of organizing the visibility of variables called classes, which we will cover in Lesson 11.

Sharing Variables Among Functions




To share local variables between two functions, you must pass the variables—not unlike a quarterback does with a football—from the calling function (the quarterback) to the receiving function (the receiver).

This section shows you how to pass data from one function to another. The passing described here is one-way. In the next section, you'll learn how to return a value from one function to another function. This section also finally tells you why you have to put parentheses after function names!

When a function needs to work with values, the calling function passes the values inside the parentheses. The receiving function's code will have a similar group of parentheses with variables inside to capture that passed data. Remember that the values you pass are called arguments and the receiving variables are called the parameters of the function. (Don't be too worried by this fine distinction; most people use the terms interchangeably.)

Parameters are the vehicles in which you pass data from one function to the next. Ordinarily, one function can't access another's local data. However, if a function passes one or more of its local data values as arguments, another function can receive that data and work with it as if it had access all along.

If you want main() to pass two integer values, v1 and v2, to a function called GetThem(), main() could pass the values like this:


GetThem(v1, v2);   // Call the GetThem() function and pass two values

The first two lines of GetThem() would look something like this:


void  GetThem(int v1, int v2)

{

definition

A function definition is the function's first line, which names the function and describes the parameters.

When a function receives one or more parameters, you can't leave the function's definition line with empty parentheses. The parentheses must describe to the function exactly what data is coming, as well as the data types. Figure 13.2 helps describe the process and terminology of passing data from main() to GetThem().

Figure 13.2. Passing data requires a little setup in the function's definition line.

There are three ways to pass data from one function to another:

The method you use depends on your data and application. As you might imagine, the way you pass variables depends on what you need to do with them. C++ enables you to control whether the receiving routine can change the value of the variable as seen by the called function.

Passing by Value


Passing by value is sometimes called passing by copy. When you pass an argument from one function to another, the argument's value is passed to the receiving function, but the variable itself isn't passed.

It's vital for you to understand that passing by value means that the receiving function can't change the passing function's variable in any way. Only the value of the passing function's variable—not the variable itself—is passed. When the value gets to the receiving function, the receiving function accepts that value into its own local variable parameter and works with its own variable that contains the passed value. However, nothing the receiving function does will change anything in the passing function. The receiving function can use the local variable as if it had been declared as a local variable.

Study the program in Listing 13.3. main() is the passing function and GetIt() is the receiving function. The argument i is passed from main() to GetIt(). No special syntax is needed. By default, Visual C++ passes the data by value.



Passing by value is safer than passing by address. When you pass by value, the "owner" of the original variable—the passing function—passes the variable's value but retains all rights to that variable. The receiving function simply uses the value in its own variable without changing the passing function's version of the variable.

Input Listing 13.3. Passing from main() to GetIt() by value.

  1:// Filename: VALUPASS.CPP

  2:// Passes an argument from main()

  3:// to another function by value

  4:#include <iostream.h>

  5:

  6:void GetIt(int i);   // The function's prototype

  7:

  8:void main()

  9:{

 10:  int i;

 11:

 12:  cout << "I'm now in main()... " << endl;

 13:  cout << "Please enter a value for i: ";

 14:  cin >> i;

 15:

 16:  GetIt(i);   // No data types, just the variable

 17:

 18:  cout << endl << endl;

 19:  cout << "Back in main(), the value is still "

 20:       << i << endl;

 21:  return;   // Return to the QuickWin

 22:}

 23://***********************************************************

 24:void GetIt(int i)

 25:{

 26:  // GetIt() now has a local variable called i also

 27:  cout << endl << endl

 28:       <<"Just got to GetIt() and i is " << i << endl;

 29:

 30:  i *= 5;

 31:

 32:  cout << "After multiplying by 5, i is now " << i << endl;

 33:  return;   // Return to main()

 34:}

Output


I'm now in main()...

Please enter a value for i: 3

Just got to GetIt() and i is 3

After multiplying by 5, i is now 15

Back in main(), the value is still 3

Analysis

By studying the output, you can see that GetIt() can change i only inside GetIt(). After GetIt() returns control to main(), you see that main()'s i was left intact with its original value. main()'s i and GetIt()'s i are two completely different variables. One is main()'s local variable and the other is GetIt()'s local variable.

One interesting thing to note is that GetIt() didn't even have to call the variable i. If GetIt()'s definition line read


void GetIt(int myVal)

GetIt() would print and multiply myVal by 5, and the results would be identical to the preceding output. Only the value of main()'s i was passed, not the variable itself.

main() didn't have to list i's data type in the parentheses on line 16 because main() already knows i's data type. Only the receiving function, which knows nothing about the argument before the pass, must be told the data type of the value being sent.

Never put a semicolon at the end of a function's definition line. For example, line 24 would never have a semicolon at the end of it. At the point where you call a function, however, you must put a semicolon, as done in line 16.

Figure 13.3 shows how main() sends the value of the variable, and not the variable itself, to GetIt(). This figure assumes that the user entered a 3, as shown in the previous output.

Figure 13.3. main() passes the value of 3, not i.

If more than one value was passed from main() to GetIt(), all the values would be sent, but the receiving function couldn't change main()'s copy of the variables. When you pass more than one variable, separate the variables with commas. When a function receives more than one parameter, separate the receiving data types and parameters with commas. For example, if main() passed an integer named i, a character named c, and a floating-point value named x to GetIt(), the line in main() might look like this:


GetIt(i, c, x);   // Pass three values

The definition of GetIt() would look like this:


void GetIt(int i, char c, float x)   // Definition line

As mentioned earlier, GetIt() could have renamed the argument values and used the new names as its parameters, but GetIt() could never change main()'s copy of any of the variables. A parameter is a declaration of local scope. It will hide a like-named global variable, and a local variable in main does not exist in other functions. (Recall that a local variable does not exist after the closing brace of the function.) A function that is called is not within the scope of a calling function.

Detour for a Moment with Prototypes


We're not done with Listing 13.3. Aren't you curious about the duplication of GetIt()'s definition line in line 6? Line 6 is called a prototype. All functions that you write using Visual C++ must include a prototype.

definition

A prototype models a function's definition.

As you know, you must declare all variables before you can use them. Also, you must declare all functions before you call them. main() is the exception because it's already known by the runtime system that runs your program. (main() is known as a self-prototyping function.)

Declaring (prototyping) a function almost seems like overkill because it's so easy. To prototype a function, you must copy the function's definition line (the function's first line with parameters and everything else) to the top of your program. The best place to prototype your program's functions is right after your #include directives. As long as you prototype anywhere before main(), you'll be okay, but most people prototype right after the #includes.

A prototype tells Visual C++ what to expect. For instance, if you tell Visual C++ via a prototype that a particular function should receive a float followed by a double, but in the program you inadvertently pass the function a char followed by a double, Visual C++ can catch the error during compilation. Without the prototype, Visual C++ would have to promote the value any way it could, and a char doesn't promote very well to a double. Your program results would be incorrect, and it would be difficult to track down the error and figure out exactly where your data got messed up.



In C++, the protoype is correctly termed a declaration. When you give the full body of the function, this is called the definition of the function. You must always declare a function before using it in your code, but you can define it any time you like. Also, a definition counts as a declaration too.

Many Visual C++ programmers prototype their functions right before compiling. With Visual C++, that's easy to do. Follow these steps to copy and paste function definitions into prototypes:

  1. Highlight the function's definition (the first line in the function) by clicking and dragging the mouse or by holding down the Shift key and pressing the arrow keys until the line you want is highlighted.

  2. Select Edit Copy (Ctrl+C or Ctrl+Ins) to copy the highlighted line to the Windows clipboard.

  3. Move the cursor to the top of the program and insert a new line where you want the prototype to go.

  4. Select Edit Paste (Ctrl+V or Shift+Ins) to copy the clipboard's line to the prototype area.

  5. Add a semicolon to the end of the prototype. All prototypes require semicolons so that Visual C++ can distinguish a prototype from a function's definition line.



Ah, It Makes Sense Now!

You've been dutifully including the appropriate header files, such as IOSTREAM.H and STRING.H, as needed. Until now, you've never fully understood why they're needed. Each time you learned a function that required a different header file, you began using that header. Now you know enough to understand why those header files are so important.

As mentioned a moment ago, you must prototype all functions before you can use them. The only exception is main(). That means that you must prototype functions that you didn't even write, such as strcpy.

What does the prototype for strcpy() look like? You don't know, yet you've already been prototyping strcpy() and all the other library functions. When Visual C++ supplies a library function, it prototypes that function for you in one of the header files. Therefore, when you include a header file, you're including a ton of prototypes for all functions you might use that are related to that header file. By including STRING.H, for instance, you started prototyping strcpy() from the very beginning. (The adventurous among you might want to open up the include file in C:\msvc\include\string.h to see what you can find. Aside from some technical stuff, which we won't worry about, you should be able to find a prototype for the string functions that you can recognize.)

If, in the beginning, I had gone into a lot of detail about the need to define all functions with a prototype before you call them, you might have given up too early. Now that you understand prototypes, the header files should make a lot more sense.


A prototype doesn't need to have the actual parameter names listed inside its parentheses, only the data types. The following are considered to be identical prototypes for a function named CalcIt():


void CalcIt(float i, double d, char c);


void CalcIt(float, double, char);

A prototype helps Visual C++ with data types, not variable names, so names aren't needed in the prototype itself. Parameter names are needed, however, in the function's definition line. Most programmers use the cut-and-paste method described earlier to create their prototypes; therefore, most programmers' prototypes do contain parameter names.



Although you don't need the parameter name, it is very useful to put the name in to help describe what the parameter does.


Another Detour to Address


definition

Passing by address means that the variable itself (its location in memory) is passed and received.

The second way of passing data to a function is passing by address. First, what is an address? An address is the location in memory of a variable. When you name a variable, C++ sets aside a piece of memory and C++ also makes a note internally of where that variable has been put. Unlike some languages, C++ let's the programmer find this address value. To ask for the address of a variable, you can use this syntax:


addressOfVariable = &someVariable;

in which the & means address of.

However, you cannot hold an address in just any data type. C++ holds addresses as pointers and each data type has its own pointer type. So, when a pointer is declared, C++ needs to know the data type and that the variable is to hold a pointer, using the pointer operator (*). Yes, it does look like the multiplication symbol. C++ has to use the context to work out what was meant. Here is an example:


int i = 5; // integer

int *iptr; // pointer to integer

iptr = &i; // iptr now holds the address of i

The final trick is to know how to get the variable back from a pointer. The answer is that you use the dereference operator. The bad news is that it looks remarkably the same as multiply and the pointer declaration symbol, *. Here is a further example to show you how it works:


int i = 5; // integer

int j = 10;

int *iptr;   // pointer to integer

iptr = &i;   // iptr now holds the address of i

j = *iptr;   // j now holds 5, the contents of

             // the place iptr points to

*iptr = 77;  // the place where iptr points to, i,

             // now holds 77


We will cover pointers in further detail in Lesson 8.

Having had another minor detour, let's get back to discussing what this has to do with function calls. Note the last statement in the previous code snippet. It enables you to update a variable by using its address. This gives a way of beating C++'s safety belt of copying the value, which stops you from updating the real value of the variable. If you take the address of a value that you want to update in a program and then pass that as a parameter, even if C++ copies that address, you can still access the same memory location that you passed. So, when Visual C++ passes a variable by address, in effect it passes the variable itself, which means that the receiving function can change the calling function's variable. When the calling function regains control, any variable just passed by address might be changed if the called function changed the argument.

Aside from character arrays, you haven't looked at arrays in much detail, but there is a significant difference in an array variable and a normal variable: An array variable is really a pointer in disguise. So when you pass an array as a parameter, you are really passing its address. This means that when you pass an array, you are effectively passing the contents of the array by address. Therefore, when you pass an array, the contents of the array can also be changed.

Passing by value is safer than passing by address because the called function can't change the calling function's variables. However, there are times when you want the called function to change the calling function's variables. For example, Listing 13.4 contains a program that asks the user for his or her name in main(). The user's name is then passed to ChangeIt(), where the first and last characters in the name are changed to #. When main() regains control, it prints the name array, showing that the called function did indeed change main()'s version of the array.

Input Listing 13.4. Passing an array is passing by address.

  1:// Filename: PASSADDR.CPP

  2:// Passing an array means that the called function

  3:// can change the same variable as in the calling function

  4:#include <iostream.h>

  5:#include <string.h>

  6:

  7:void ChangeIt(char userName[30]);   // Prototype

  8:

  9:void main()

 10:{

 11:  char userName[30];

 12:

 13:  cout << "What is your name? ";

 14:  cin.getline(userName, 30);

 15:

 16:  // Send the name to changeIt() by address

 17:  ChangeIt(userName);

 18:

 19:  cout << "After a return to main(), here is your name: ";

 20:  cout << userName;

 21:  return;

 22:}

 23://********************************************************

 24:void ChangeIt(char userName[30])

 25:{

 26:  int endPlace;

 27:  userName[0] = '#';   // Change first letter in name

 28:  endPlace = strlen(userName) - 1;   // Find location of

 29:                                     // final letter in name

 30:  userName[endPlace] = '#';

 31:

 32:  return;   // Return to main() - optional

 33:}

Output


What is your name? Graham Ward

After a return to main(), here is your name: #raham War#

Analysis

The user fills the array named userName with a string value at line 14. On line 17, main() then passes the array named userName to the function named ChangeIt(). ChangeIt() changes the first and last letter of the name to pound signs.

ChangeIt() changes main()'s array because all arrays are passed by address. Therefore, when ChangeIt() overwrites two letters with pound signs, main() knows about the change. When ChangeIt() returns to main(), the array is still changed. main() displays the array in line 20. As you can see from the output, the array that main() displays is indeed the array that ChangeIt() changed. You can't pass an array by value. Therefore, you must be on guard when passing arrays. Always be aware that the called function could change the array. If the called function does change the array, the calling function's array will reflect those changes.



It turns out that when you pass an array to a function, you don't have to put the subscript in the receiving function's parameter brackets. The 30 in line 24 is optional, although Visual C++ can use it to make sure that the arrays are the same size.

Passing a nonarray variable by address requires some of that really strange syntax that you saw briefly earlier. Perhaps it's best that the designers of Visual C++ included the extra syntax, because that might make you pass by address only when you need to. It's important when writing long, structured programs (especially if you're on a team of programmers writing a large system) to make sure that only functions that need to change variables get a chance to do so.

To pass a nonarray variable by address, precede the parameter in the calling function with an ampersand (&), the address of operator. The ampersand tells Visual C++ that you want that specific nonarray variable passed by address. Visual C++ therefore passes the variable by address and lets the called function change the variable in both places.



Instead of passing nonarrays by address, pass them by reference as described in the next section. Passing by reference does not require the messy syntax that is required when passing nonarrays by address, but passing by reference achieves the same effect. Learn both methods, however, so that you can recognize other people's code when they pass nonarray variables by address.

In addition to including an ampersand in the calling function, you must also precede the variable with an asterisk (*) in the receiving function wherever the variable appears. It looks quite awkward, as you can see in Listing 13.5.

Input Listing 13.5. Passing an integer by address and a character by value.

  1:// Filename: OVERRIDE.CPP

  2:// Overrides the default method of passing

  3:// a nonarray variable. This program passes

  4:// a nonarray variable by address and,

  5:// therefore, requires some extra notation.

  6:#include <iostream.h>

  7:

  8:void ChangeIt(int *i, char c);

  9:

 10:void main()

 11:{

 12:  int i;

 13:  char c;

 14:

 15:  // Assign two values in main()

 16:  i = 10;

 17:  c = 'X';

 18:

 19:  cout << "Before leaving main(), i and c are "

 20:       << i << " and " << c << endl;

 21:

 22:  ChangeIt( &i, c);   // Pass the integer by address

 23:

 24:  cout << "Back in main(), i and c are "

 25:       << i << " and " << c << endl;

 26:  return;

 27:}

 28://*********************************************************

 29:void ChangeIt(int *i, char c)

 30:{

 31:  *i = 26;

 32:  c = 'p';   // main()'s version of c won't change

 33:             // but we can still use c locally

 34:

 35:  return;   // Return to main()

 36:}

Output


Before leaving main(), i and c are 10 and X

Back in main(), i and c are 26 and X

Analysis

If changeIt() were longer and had referred to i several times throughout the function, i would have needed an asterisk (*i) every place it appeared. Again, overriding the passing of nonarray data requires some strange syntax, but when a function has to change another function's data, passing by address is a good way to accomplish that change.

Passing by Reference


C++ provides a third way to pass data between functions that C++'s predecessor, C, does not support. When you pass data by reference, if the called function changes the data, C++ applies those same changes to the calling function's data.

The end result of passing by reference is identical to that of passing by address with one exception: You don't need to mess with all the strange overriding syntax when passing nonarrays by reference. As you learned at the end of the previous section, if you pass nonarrays by address you must precede the passed arguments with ampersands and also precede all parameters in the called function with asterisks. When passing variables by reference, you only have to precede the receiving parameters with ampersands.

Be careful when passing by reference! Remember that any changes applied to the receiving parameters will also apply to the sending function's arguments.

Listing 13.6 shows a program that passes several parameters by reference. The called functions change the calling function's arguments.

Input Listing 13.6. Passing arguments by reference.

1:// Filename: POOL.CPP

  2:// Calculates the cubic feet in a swimming pool

  3:#include <iostream.h>

  4:

  5:void GetValues(int &length, int &width, int &depth);

  6:void CalcCubic(int length, int width, int depth, int &cubic);

  7:void PrintCubic(int cubic);

  8:

  9:void main()

 10:{

 11:   int length, width, depth, cubic;

 12:

 13:   GetValues(length, width, depth);

 14:   CalcCubic(length, width, depth, cubic);

 15:   PrintCubic(cubic);

 16:   return;

 17:}

 18://*******************************************************

 19:void GetValues(int &length, int &width, int &depth)

 20:{

 21:   cout << "What is the pool's length? ";

 22:   cin >> length;

 23:   cout << "What is the pool's width? ";

 24:   cin >> width;

 25:   cout << "What is the pool's average depth? ";

 26:   cin >> depth;

 27:   return;

 28:}

 29://*******************************************************

 30:void CalcCubic(int length, int width, int depth, int &cubic)

 31:{

 32:   cubic = length * width * depth;

 33:   return;

 34:}

 35://*******************************************************

 36:void PrintCubic(int cubic)

 37:{

 38:   cout << endl

 39:        << "The pool has " << cubic << " cubic feet";

 40:   return;

 41:}

Output


What is the pool's length? 12

What is the pool's width? 33

What is the pool's average depth? 4

The pool has 1584 cubic feet

Analysis

main() is nothing more than a function-calling control function. However, on line 11, main() defines three variables to be used in subsequent calculations. main() calls GetValues() on line 13. The job of GetValues() is to collect user values for main()'s three variables. main() passes the three arguments by reference due to the ampersands in front of each parameter in the function declaration on line 5 and the function defintion starting in line 19, which must match.

Once it is passed by reference, when GetValues() changes a parameter through the user's keyboard entry, the values change in main() as well. In CalcCubic, the only value that needs to be updated is the result of the calculation, cubic. For safety, the only value that the function is allowed to change is cubic; all the other arguments are passed by value in line 30. Therefore, if the programmer made a mistake in the calculation and accidentally put the result into length, CalcCubic would be the only function that had the wrong value in length after the mistake.

Finally, when printing the result in line 36 (PrintCubic), the function does not change the value, so there is no need to allow cubic to be updated.



In this section, you learned that there are three ways to pass variables from one function to another. When you pass by value, the called function can't change the calling function's variables. When you pass by address, the called function can change the calling function's variables. Passing by reference performs the same job as passing by address, but you can pass nonarrays by reference without the messy syntax required when you use address passing.


Homework



General Knowledge


  1. Which is better, a program with one long main() function or a program with lots of smaller functions?

  2. What's one advantage of structured programs?

  3. Why are modular programs with lots of functions easier to maintain than programs with only one or a few long functions?

  4. What is recursion?

  5. Variables can be defined in two places in a program. List the two places.

  6. What's the difference between local and global variables?

  7. What's a block?

  8. Which variable is safer, a local or a global variable?

  9. When does a local variable go away?

  10. When does a global variable go away?

  11. What are the three ways you can pass variables?

  12. Which is safer, passing by address or by value?

  13. What's another name for passing by value?

  14. How can you pass a nonarray variable by value?

  15. How can you pass a nonarray variable by address?

  16. How do you pass a variable by reference?

  17. What's a prototype?

  18. How can prototypes help eliminate bugs?

  19. How can you prototype library functions such as strlen()?

  20. Why don't you need to prototype main()?

  21. Suppose main() passes a variable by value. If the called function changes the parameter, will main()'s value also change?

  22. Suppose main() passes a variable by address. If the called function changes the parameter, will main()'s value also change?

  23. Suppose main() passes a variable by reference. If the called function changes the parameter, will main()'s value also change?

  24. True or false: Once you define a local variable, all functions in the program can use the variable.

  25. True or false: You can't use a global variable before its definition.

  26. True or false: The following is a prototype.


    
    void aFunction(void)

  27. True or false: The following is a prototype.

    
    void aFunction(void);

  28. True or false: You can pass an array by value.


    What's the Output?



  29. What's the output of the following program?

    
    // Filename: U16OUT1.CPP
    
    #include <iostream.h>
    
    void DoubleIt(int i);
    
    void main()
    
    {
    
      int i = 19;
    
      DoubleIt(i);
    
      cout << "i is now " << i << endl;
    
      return;
    
    }
    
    //***************************************************
    
    void DoubleIt(int i)
    
    {
    
      i *= 2;
    
      return;
    
    }

  30. What's the output of the following program?

    
    // Filename: U16OUT2.CPP
    
    #include <iostream.h>
    
    void DoubleIt(int *i);
    
    void main()
    
    {
    
      int i = 19;
    
      DoubleIt(&i);
    
      cout << "i is now " << i << endl;
    
      return;
    
    }
    
    //***************************************************
    
    void DoubleIt(int *i)
    
    {
    
      *i *= 2;
    
      return;
    
    }

    Find the Bug


  31. What's wrong with the following function?

    
    void SqThem(i, j)
    
    {
    
      i = i * i;
    
      j = j * j;
    
      cout << "i squared is " << i << endl;
    
      cout << "j squared is " << j << endl;
    
      return;
    
    }

    Write Code That. . .


  32. Write a program that prints, in main(), the double-precision area of a circle, given that a user's double-precision radius is passed to the function. The formula for calculating a circle's area is

    
    area = 3.14159 * (radius * radius)

  33. Rewrite the following function to receive and process its parameters by address instead of by value:

    
    void DoubleThem(float x, float y)
    
    {
    
      x *= 2.0;
    
      y *= 2.0;
    
      cout << "The first parameter is now " << x << endl;
    
      cout << "The second parameter is now " << y << endl;
    
      return;
    
    }

  34. Rewrite the following function to receive and process its parameters by reference instead of by value:

    
    void DoubleThem(float x, float y)
    
    {
    
      x *= 2.0;
    
      y *= 2.0;
    
      cout << "The first parameter is now " << x << endl;
    
      cout << "The second parameter is now " << y << endl;
    
      return;
    
    }

  35. Write a program whose main() function calls a function that asks the user for a number from 1 to 80. (Keep looping if the user enters a value outside that range.) Return to main() and pass that number to a function named asterisk(), which will print that number of asterisks in a line across the screen.

    Extra Credit


  36. Write a program that, in main(), asks the user for the number of vacation days he or she has left at work this year. As soon as the user enters the number of days, pass that value to a second function. In the called function, print the number of hours of vacation that the user still has (24 times the number of days). Print the message Enjoy! if the user has more than five days of vacation time. Return to main() and terminate the program as usual.

  37. Write a function that receives two integers by address. Switch the values of the two integers in the function. Due to the passing by address, when the called function returns control to the calling function, the values will still be swapped in the calling function.

Previous Page Page Top TOC Next Page