• Keine Ergebnisse gefunden

THE LIFETIME OF LOCAL VARIABLES

Im Dokument Turbo Tutor A Self-Study (Seite 154-162)

Unlike the global variables that are declared in the declaration part of the main program, the local variables of a subprogram are created (assigned places in memory) each time the subprogram is entered, and destroyed each time control returns to the calling program or subpro-gram. Thus, the lifetime of a local variable-that is, the time during which it will be able to retain its assigned value-is said to be limited to the current invocation of the subprogram. What does this mean? Well, suppose you have a procedure A that declares the local variable X in its declaration part. Now suppose you call A and cause it to set X to the value 3. When you call A again, can you assume that X will still be 3?

No! And, if you do, you may be inviting disaster, since X may have any value the next time you make a call. Thus, the rule of thumb is that all local variables should be considered undefined upon entrance to a subprogram.

PARAMETERS

In our first example of a procedure (procedure GetNumber), each call to the procedure causes a number input by the user to be assigned to the global variable NewNumber. Since the variable NewNumberis overwrit-ten each time GetNumber is called, it will most likely be necessary to save the value of New Number in some other variable immediately after the call, like this:

GetNumber;

MenuChoice .= NewNumber;

While this is a perfectly valid way of getting a number, it has two potential problems. First, you must always remember to perform the

Procedures and Functions 135

assignment; if you don't, the value the user entered will be lost.

Second, the variable New Number must be declared globally and must not be used for anything else! If the procedure GetNumber is called within a procedure that defines its own variable New Number, then it will be impossible to retrieve the result.

Earlier we mentioned that one of the purposes of using subprograms is to avoid this sort of unexpected naming conflict when many program-mers are working on parts of the same program. Local variables solve this problem for data objects that are used entirely within the subpro-gram. Parameters handle the problem of naming data that is passed to and from the subprogram.

You have already seen and used parameters when you have called the built-in procedures Readln and Writeln. In the statement

Readln(A);

the variable A is passed as a parameter to the procedure Readln, which in turn gets data from the keyboard or a file and places that data in the variable. In the statement

Writeln('Hello, world, my name is Joe.');

the constant string 'Hello, world, my name is Joe' is passed as a parameter to the Writeln procedure. To show how to declare proce-dures with parameters, let's rewrite our GetNumber procedure to return a number in a parameter, rather than in a global variable:

procedure GetNumber(var NewNumber : integer);

{ Get a number from the user and return begin {Statement Part of procedure GetNumber

Writeln('Please enter an integer from "

Writeln(/inclusive. Please try again.');

Readln(Temporary);

end;

NewNumber := Temporary;

end; { procedure GetNumber }

Note that the only change made to this procedure (other than to the introductory comment) was in the very first line, where we changed the procedure heading to read

procedure GetNumber(var NewNumber : integer);

What does this accomplish? First, it tells the compiler that when the procedure is called, it can expect to find the name of a data object of type integer in parentheses following the procedure name. Second, it says that inside the procedure that data object will be referred to by the name New Number, regardless of what it might have been named in the part of the program calling the procedure. Finally, the var preceding the name New Number indicates that a variable (rather than an expres-sion or a constant) must be passed for that parameter, and that the procedure will have the ability to alter the value of that variable. It is important to note that NewNumber is not a variable itself; rather, it is an identifier that represents another variable whose identity is decided by the procedure call (and can change from call to call). Such an object is called a formal, or dummy, parameter. If we were to call our new procedure GetNumber as follows:

GetNumber(A);

First, any expression can be passed as the actual parameter, rather than only as a variable. Second, any changes (such as assignments) that are made to the formal parameter in the subprogram do not affect the value of the actual parameter. (After all, it does not make sense to assign a value to an expression.) Instead, they are made to a copy of the value of the expression, which is created when the procedure is entered.

Such parameters are called value parameters, and the "one-way" infor-mation flow they provide is useful for two reasons. First of all, they allow the values of constants and expressions, as well as variables, to serve as input to a subprogram. Without them, statements such as Writeln('Hello, world, my name is Joe.');

would be impossible to write without assigning the string to a variable.

The second use of value parameters is as a precautionary measure.

Because a subprogram works on a copy of a value parameter, rather

Procedures and Functions 137

than on the value parameter itself, it will never make unwanted modifi-cations to that parameter. Be forewarned, however, that one of the most common mistakes made by beginning programmers is to do the reverse: forget to declare as a var parameter a parameter that should be changed by a subprogram. If this happens, the compiler will not complain, but it will appear as if the subprogram is not working properly. Figure 12-3 shows the syntax of a formal parameter list-the part of a subprogram declaration where you specify the names and types of the subprogram's formal parameters.

formal parameter list

Figure 12-3 Syntax Diagram of Formal Parameter List

A subprogram can have almost any number of parameters, subject only to the limitations of the particular compiler, and those parameters can be of almost any type.

One more brief point about parameters. Because formal parameters are considered to be declared as identifiers within the block of their proce-dures, they can "hide" identifiers declared at a higher level of the program just as other local identifiers can. For the same reason, a locally declared constant, type, or variable may not have the same identifier as a formal parameter.

FUNCTIONS

As we've seen, a procedure can change the values of variable (var) parameters passed to it. The calling program (or subprogram) can then continue to use the modified parameters for whatever purpose it wants. However, it often happens that the programmer wants only a single value back from the subprogram, and it is inconvenient (and messy) to set aside a variable just to hold that value.

Suppose, for example, that you were to write a procedure to find the square root of an integer (approximated to the next lowest integer):

procedure ISqrt(Value : integer; var Root: integer);

var OddSeq,Square : integer;

begin { procedure ISqrt } Odd Seq -1;

Square := 0;

repeat

OddSeq := OddSeq + 2;

Square := Square + Odd Seq until Value ( Square;

Root := Succ(OddSeq div 2);

if Value (= Square-Root then Root := Pred(Root)

end; { procedure ISqrt }

This procedure would take Value, find its square root, and set Root to that value. The calling program might use it as follows:

repeat

Write('Enter value: '); Readln(Square);

ISqrt(Square, Root);

Writeln('The square root is I, Root) until Square = 0;

In the previous example, the variable Root is only used to carry the value of the square root from the call to ISqrt to the Writeln statement.

If there are many variables like this in a program, it can become clumsy to keep track of them all-and most of them will not be in use most of the time (a poor use of memory space). We mentioned functions earlier (in Chapter 10, "Defined Scalar Types"), and as you may recall, a function is used in an expression in place of the value that it produces, with the value becoming part of the expression when it is evaluated.

When you declare a function, you create a heading similar to that of a procedure, except that you specify the type of the returned value. (This is necessary so that Turbo Pascal knows how to "fit" the returned value into the expression properly.)

The syntax of a function declaration is shown in Figure 12-4.

Figure 12-4 Syntax Diagram of Function Declaration

As we just mentioned, the only difference between a function declara-tion and a procedure declaradeclara-tion is that in the funcdeclara-tion declaradeclara-tion a type is required.

Here is an example of a function that is equivalent to the square root procedure shown earlier. Note that there is no parameter to hold the root-the name of the function itself is used to represent the value.

function ISqrt(Value integer): integer;

var

OddSeq,Square,Root integer;

Procedures and Functions 139

begin { Statement Part of function ISqrt } . { same code as before

ISqrt := Root { The value is returned by assigning to the function name as if it were a variable } end; { function ISqrt }

The function can now be used in a program like this:

repeat

Write(/Enter value: '); Readln(Square);

Writeln(/The square root is I,ISqrt(Square)) until Square = 0;

Now, the main program (or any subprogram that wants to use this subprogram) does not need to declare a variable to hold the square root. This means a simpler program, with less room for errors and naming conflicts.

A function can be used anywhere that a constant or an expression of the same data type could be used. Suppose you wanted to find the fourth root of a given integer value. You could rewrite the program this way:

repeat

Write(/Enter value: '); Readln(FourthPower);

Writeln(/The fourth root is ',ISqrt(ISqrt(FourthPower))) until FourthPower = 0;

When writing a function, take care to ensure that you have set the function identifier to some value before exiting. As shown previously, you do this by assigning some value to the function name, as if it were a variable. Actually, you can assign a value to the function identifier many times; however, if you do, the last value you assign before the function terminates will be the value returned.

While you may always make assignments to the function identifier, you may not retrieve the value you assigned to that identifier by including it in an expression. Why? Because the compiler will interpret that use of the function name as another call to the function. This is why we made the identifier Root a local variable, rather than eliminat-ing it entirely from our function in the previous example. Had we tried to use the identifier ISqrt to hold the intermediate values of the root as it was being computed, we would have had no way of getting them back.

Since an attempt to get the value of the function identifier within a function is construed by the compiler as another call to the same function, it follows that functions (and procedures as well) can call themselves. Subprograms that do this are called recursive subprograms, and are discussed in detail in the next section.

RECURSIVE SUBPROGRAMS

Sometimes, the easiest way to describe a task is to describe it in terms of a subprogram that calls itself to get a job done. This is called a recursive subprogram. For instance, there is a function in mathematics called the factorial function, which gives the product of all the positive integers up to, and including, that integer. For instance, 5 factorial

When this function is called, it looks to see if the value of the parameter N is less than or equal to 1. (Because 256! is much too large to hold even in a variable of type real, we've restricted the parameter to the type byte. This way, we also need not worry about negative numbers.) If N is 1 or 0, there is no multiplying to be done, since 1! = 1 (and, incidentally, O! is considered to be 1 as well). Otherwise, we set the function identifier Factorial equal to N times the Factorial of N - 1.

Note well the different uses and meanings of the function identifier on the left- and right-hand sides of the assignment operator. On the left, the identifier is used without a list of parameters, and represents a place where the result of the function is to be stored. On the right, the same identifier is used with a list of parameters to represent the result of a call to that function. The calls will "nest" more and more deeply until Factorial is called with the value 1; at that point, the series of calls will unwind, doing the necessary multiplication at each step.

Since a procedure can call other procedures (including itself), proce-dures can be recursive subprograms as well. It is also possible to build more complex recursive structures where one procedure or function calls a second, which calls the first, which calls the second, and so on.

Recursive subprograms often make a calculation very simple to pro-gram. But beware, like loops (another repetitive process), it is possible for such structures to get out of control and run forever. It is possible

Procedures and Functions 141

for the calls to nest indefinitely, or until the machine no longer has the memory available to keep track of them. (As we mentioned before, space for local variables is allocated each time a subprogram is called.

Thus, each call can potentially use up a large block of memory, and the available space can be exhausted very quickly.)

So always use recursive subprograms with care, and make sure that there is a statement that will not cause more recursion (like the previous statement Factorial:

=

1) in every recursive subprogram.

FORWARD DECLARATIONS

Occasionally, the rule that all identifiers must be declared before they are used can keep you from making your program do what you want.

For instance, as we mentioned earlier, you may want to write a subprogram that calls another, which in turn calls the first, and so on.

The problem with writing such a structure, however, is which subpro-gram do you declare first? No matter which one you choose, the other subprogram will not have been defined yet, and therefore you will not be able to call it. Here is a simple program illustrating the problem.

program Example;

var Alpha : integer;

procedure Test1(var A : integer);

begin { procedure Test1 } A := A-1;

if A > 0 then Test2(A);

Writeln(A) ;

end; { procedure Test1 }

procedure Test2(var A : integer);

begin { procedure Test2 } A := A div 2;

i f A > 0 then TesU(A) ; Writeln(A) ;

end; { procedure Test2

begin { Statement Part of program Example } Alpha := 15;

TesU(Alpha)

end. { program Example

As you can see, Test1 calls Test2 and Test2 calls Test1. As it stands, this program won't compile; you'll get an "Unknown identifier" error when it finds the reference to Test2 within Test 1. If you swapped Test1 and Test2, you'd get a similar error within Test2.

The solution to this problem is to tell the compiler, before it gets to the procedure Test1, that the procedure Test2 will be declared later. This is done with a forward declaration, as shown in the following example:

progral Example;

var Alpha : integer;

procedure Test2{var A integer); forward;

procedure TestL{var A integer);

begin { procedure TestL }

end; { procedure TestL }

procedure Test2 {(var A : integer)};

{ We've commented out the parameter list;

it was supplied earlier. } begin { procedure Test2 }

end; { procedure Test2

begin { Statement Part of program Example } Alpha := L5;

TesU{Alpha)

end. { program Example }

The forward declaration of Test2 contains only the procedure or function heading (the information necessary for the compiler to check any calls to it for a correct name and parameter list) and the reserved word forward. The actual body of Test2 occurs after Test1. Now Test1 can call Test2 (because of the forward declaration) and Test2 can call Test1 (since the latter precedes the former).

Note that when Test2 is fmally declared, its parameter list is omitted (though we recommend showing it in a comment as a reminder of what the parameters and their types are). The parameter list may not be repeated; Turbo Pascal already "knows" what the parameter list is, and does not need the redundant (and possibly inconsistent) information.

Im Dokument Turbo Tutor A Self-Study (Seite 154-162)