There have been two kinds of parameters used thus far in the examples of procedures. One kind is used to input data into the procedure only. When the procedure is called, the value represented by the actual parameter is copied into the memory location set aside for the formal parameter. At this point, there are two copies of the information in memory. If any changes are made to the local copy owned by the procedure (with the formal parameter name), the actual object copied by the calling routine is unaffected. Figure 4.3 illustrates.
A value parameter is a formal parameter naming a memory location that is set aside while the procedure is active, and into which the contents of an actual parameter will be copied at the time the procedure is called. The two copies of the information are decoupled (independent of one another).
PROCEDURE Write (ch: CHAR); (* actual declaration *) . . . character := "A"; Write (character); (*call to this in a program *)
In some situations, one prefers to get things (often numbers) back from procedures, rather than have the procedure just take an action. To do this, one employs the second kind, a formal parameter whose name is simply attached to the same memory location that is already being used by the actual parameter. Any changes made to the formal parameter within the procedure body are automatically reflected in the actual parameter, because the two are simply different names for the same memory location. They are "tightly coupled". This action is illustrated in figure 4.4.
A variable parameter is a formal parameter whose name is attached to the same memory location as that named by the actual parameter at the time the call is made. The two names are coupled. These are distinguished from value parameters in the formal parameter list of the procedure heading by preceding them with the reserved word VAR.
PROCEDURE Read (VAR ch: CHAR); (* declaration *) . . . WriteString ("Do you want to do this again?"); Read (answer); (* call to Read in a program *)
Here are some more examples:
PROCEDURE GetNextDay (VAR day : CARDINAL); PROCEDURE Discrim (a, b, c : REAL; VAR d : REAL; VAR ok : BOOLEAN);
The procedure GetNextDay can (and presumably does) directly modify whatever variable is aliased to day by assigning a new value to day. Since both the actual parameter and the formal parameter name the same memory location, both names get new values at once.
The procedure Discrim is capable of directly modifying whatever is assigned to d and ok, but it can only use whatever data is copied to a, b, and c when it is invoked. Any changes it might make to these inside the procedure are not reflected in changes to the original entities owned by the calling routine that were copied to them.
Assuming that numberOfDay is a CARDINAL, w, x, y, and z are REAL and done is of type BOOLEAN, here are some legitimate calls to these two procedures:
GetNextDay (numberOfDay); Discrim (w, x, y, z, done); Discrim (3.0, x, 2.5, z, done);
Here are some that are not correct, with the reasons:
GetNextDay (3) (* 3 is not a variable *) GetNextDay (x) (* x is not a cardinal *) Discrim (5, x, y, z, done); (* 5 is not real *) Discrim (w, x, y, 3.9, done); (* 3.9 is not a variable *) Discrim (w, x, y, done, z); (* wrong order of parameters *)
As the first and fourth of these incorrect examples illustrate, one must assign a variable to a variable parameter. After all, one cannot expect that a literal value will be somehow changed. (Can a 3 become not a 3?)
It is now possible ( figure 4.5) to diagram the parameter list completely.
Here is something else that might seem to be permitted, but that is not. Suppose cardNum is a CARDINAL, and in fact cardNum happens to equal 5, and that one has:
PROCEDURE Fetch (VAR intNum : INTEGER)
Then a call Fetch (cardNum) produces a compiler error, because the types of an actual and formal VAR parameter must be identical, not merely assignment compatible, as are CARDINAL and INTEGER. Value parameters on the other hand, need only be assignment compatible.
To further illustrate the use of variable parameters, here is a procedure that swaps the values of two variables that are passed to it.
PROCEDURE Swap (VAR firstNum, secondNum : REAL); VAR temp : REAL; BEGIN temp := firstNum; firstNum := secondNum; secondNum := temp; END Swap;
Notice the use of the local variable temp to hold the value of firstNum while the value of secondNum is assigned the name firstNum. The procedure Swap would be called by naming, as parameters, the two real variables whose values one wanted interchanged. The following sequence in the main program:
rVar1 := 2.5; rVar2 := 7.8; Swap (rVar1, rVar2); WriteReal (rVar1, 0);
would output 7.8 as the value of rVar1.
Write a program module that will sort and print in ascending order three reals input from the keyboard.
Ask for a number Read it and assign to first variable Ask for a number Read it and assign to second variable Ask for a number Read it and assign to third variable Sort the numbers if first is greater than second then swap the two if second is greater than third then swap the two if first is now greater than second then swap the two Print the numbers out in order
As one looks this refinement over, it is apparent that reading and assigning are done three times with three different numbers and that there are potentially three swaps. Both subtasks (reading/assigning, and swapping) should therefore be procedures, so one makes this:
procedure 1 Read a number Assign it to a variable parameter procedure 2 Swap two variable parameters Main Program Call procedure 1 for the three numbers in turn. continue as outlined in the first refinement, but the swap is a procedure invocation.
Variables Required: procedure 1: only the single VAR parameter, and a boolean for recycling. procedure 2: two VAR parameters, and a temporary real. Main Program: three reals, and a character variable for carriage returns. Imports Needed: ReadReal, WriteFixed, Done WriteString, WriteLn, ReadChar, SkipLine ReadResult, ReadResults
Here is the code:
MODULE SortThree; (* Written by R.J. Sutcliffe *) (* using P1 MPW Modula-2 for the Macintosh computer *) (* last revision 1993 02 26 *) FROM STextIO IMPORT WriteString, WriteLn, ReadChar, SkipLine; FROM SRealIO IMPORT ReadReal, WriteFixed; FROM SIOResult IMPORT ReadResult, ReadResults; VAR num1, num2, num3 : REAL; key : CHAR; PROCEDURE GetNum (VAR theNum : REAL); VAR ok : BOOLEAN; BEGIN REPEAT WriteString ("Type the number here ==> "); ReadReal (theNum); ok := (ReadResult() = allRight); SkipLine; IF NOT ok (* use saved value for testing *) THEN WriteLn; WriteString ("error in input number; try again."); WriteLn; END; UNTIL ok; END GetNum; PROCEDURE Swap (VAR firstNum, secondNum : REAL); VAR temp : REAL; BEGIN temp := firstNum; firstNum := secondNum; secondNum := temp; END Swap; BEGIN (* main program *) WriteString ("This program sorts three numbers "); WriteString ("into ascending order."); WriteLn; WriteLn; (* obtain the three numbers *) WriteString ("First number: "); GetNum (num1); WriteString ("Second number: "); GetNum (num2); WriteString ("Third number: "); GetNum (num3); (* now sort the numbers. *) IF num1 > num2 (* check first two *) THEN Swap (num1, num2); (* trade if necessary *) END; IF num2 > num3 (* if second, third out of order *) THEN Swap (num2, num3); (* trade them *) IF num1 > num2 (* and re-check new #1 & 2 *) THEN Swap (num1, num2); (* trading if necessary *) END; (* inner if *) END; (* outer if *) (* numbers are sorted, so do print out *) WriteString ("In ascending order, the numbers are: "); WriteLn; WriteFixed (num1, 5,0); WriteLn; WriteFixed (num2, 5,0); WriteLn; WriteFixed (num3, 5,0); WriteLn; WriteString ("Press a key to continue ==>"); ReadChar (key); END SortThree.
A run follows:
This program sorts three numbers into ascending order. First number: Type the number here ===> 13.9 Second number: Type the number here ===> 11.98 Third number: Type the number here ===> 16.756 In ascending order, the numbers are: 11.98000 13.90000 16.75600 Press a key to continue ==> q