4.2. Sub Procedures, Part IIThe previous section introduced the concept of a Sub procedure, but left some questions unanswered. Why can't the value of a variable be passed from an event procedure to a Sub procedure by just using the variable in the Sub procedure? How do Sub procedures pass values back to an event procedure? The answers to these questions provide a deeper understanding of the workings of Sub procedures and reveal their full capabilities. Passing by ValueIn Section 4.1, all parameters appearing in Sub procedures were preceded by the word ByVal, which stands for "By Value." When a variable is passed to such a parameter, we say that the variable is "passed by value." A variable that is passed by value will retain its original value after the Sub procedure terminatesregardless of what was done to the corresponding parameter inside the Sub procedure. Example 1 illustrates this feature. Example 1.
When a variable is passed by value, two memory locations are involved. At the time the Sub procedure is called, a temporary second memory location for the parameter is set aside for the Sub procedure's use and the value of the argument is copied into that location. After the completion of the Sub procedure, the temporary memory location is released, and the value in it is lost. So, for instance, the outcome in Example 1 would be the same even if the name of the parameter were amt. Passing by ReferenceAnother way to pass a variable to a Sub procedure is "By Reference." In this case the parameter is preceded by the reserved word ByRef. Suppose a variable, call it arg, appears as an argument in a call statement, and its corresponding parameter in the Sub procedure's header, call it par, is preceded by ByRef. After the Sub procedure is executed, arg will have whatever value par had in the Sub procedure. Hence, not only is the value of arg passed to par, but the value of par is passed back to arg. In Example 1, if the first line of the Sub procedure is changed to Sub Triple(ByRef num As Double) then the last number of the output will be 6. Although this feature may be surprising at first glance, it provides a vehicle for passing values from a Sub procedure back to the place from which the Sub procedure was called. Different names may be used for an argument and its corresponding parameter, but only one memory location is involved. Initially, the btnDisplay_Click() event procedure allocates a memory location to hold the value of amt (Figure 4.4(a)). When the Sub procedure is called, the parameter num becomes the Sub procedure's name for this memory location (Figure 4.4(b)). When the value of num is tripled, the value in the memory location becomes 6 (Figure 4.4(c)). After the completion of the Sub procedure, the parameter name num is forgotten; however, its value lives on in amt (Figure 4.4(d)). The variable amt is said to be passed by reference. Figure 4.4. Passing a variable by reference to a Sub procedure.
Passing by reference has a wide variety of uses. In the next example, it is used as a vehicle to transport a value from a Sub procedure back to an event procedure. Example 2. |
The following variation of Example 5 from the previous section uses a Sub procedure to acquire the input. The variables x and y are not assigned values prior to the execution of the first call statement. Therefore, before the call statement is executed, they have the value 0. After the call statement is executed, however, they have the values entered into the text boxes. These values then are passed by the second call statement to the Sub procedure DisplaySum.
Private Sub btnCompute_Click(...) Handles btnCompute.Click 'This program requests two numbers and 'displays the two numbers and their sum. Dim x, y As Double GetNumbers(x, y) DisplaySum(x, y) End Sub Sub GetNumbers(ByRef x As Double, ByRef y As Double) 'Record the two numbers in the text boxes x = CDbl(txtFirstNum.Text) y = CDbl(txtSecondNum.Text) End Sub Sub DisplaySum(ByVal num1 As Double, ByVal num2 As Double) 'Display numbers and their sum txtResult.Text = "The sum of " & num1 & " and " & num2 _ & " is " & (num1 + num2) & "." End Sub [Run, type 2 and 3 into the text boxes, and then click the button.]
|
In most situations, a variable with no preassigned value is used as an argument of a call statement for the sole purpose of carrying back a value from the Sub procedure.
The following variation of Example 2 allows the btnCompute_Click event procedure to be written in the input-process-output style. Just before the call statement CalculateSum (x, y, t) is executed, the value of t is 0. After the call, the value of t will be the sum of the two numbers in the text boxes. Private Sub btnCompute_Click(...) Handles btnCompute.Click 'This program requests two numbers and 'displays the two numbers and their sum. Dim x As Double 'First number Dim y As Double 'Second number Dim t As Double 'Total GetNumbers(x, y) CalculateSum(x, y, t) DisplayResult(x, y, t) End Sub Sub GetNumbers(ByRef num1 As Double, ByRef num2 As Double) 'Retrieve the two numbers in the text boxes num1 = CDbl(txtFirstNum.Text) num2 = CDbl(txtSecondNum.Text) End Sub Sub CalculateSum(ByVal num1 As Double, ByVal num2 As Double, _ ByRef total As Double) 'Add the values of num1 and num2 total = num1 + num2 End Sub Sub DisplayResult(ByVal num1 As Double, ByVal num2 As Double, _ ByVal total As Double) txtResult.Text = "The sum of " & num1 & " and " & num2 _ & " is " & total & "." End Sub |
Visual Basic provides a way to override passing by reference, even if the ByRef keyword precedes the parameter. If you enclose the variable in the call statement in an extra pair of parentheses, then the variable will be passed by value.
For instance, in Example 1, if you change the call statement to
Triple((amt))
then the fourth number of the output will be 2 even if the parameter num is preceded with ByRef.
When a variable is declared in an event or Sub procedure with a Dim statement, a portion of memory is set aside to hold the value of the variable. As soon as the End Sub statement for the procedure executes, the memory location is freed up; that is, the variable ceases to exist. The variable is said to be local to the procedure.
When variables of the same name are declared with Dim statements in two different procedures (either event or Sub), Visual Basic gives the variables separate identities and treats them as two different variables. A value assigned to a variable in one part of the program will not affect the value of the like-named variable in the other part of the program.
The following program illustrates the fact that each time a Sub procedure is called, its variables are set to their initial values; that is, numerical variables are set to 0 and string variables are set to the keyword Nothing. Private Sub btnDisplay_Click(...) Handles btnDisplay.Click 'Demonstrate that variables declared in a Sub procedure 'do not retain their values in subsequent calls Three() Three() End Sub Sub Three() 'Display the value of num and assign it the value 3 Dim num As Double lstResults.Items.Add(num) num = 3 End Sub [Run, and then click the button. The following is displayed in the list box.] 0 0 |
The following program illustrates the fact that variables are local to the part of the program in which they reside. The variable x in the event procedure and the variable x in the Sub procedure are treated as different variables. Visual Basic handles them as if their names were separate, such as xbtnDisplay_Click and xTrivial. Also, each time the Sub procedure is called, the value of variable x inside the Sub procedure is reset to 0. Private Sub btnDisplay_Click(...) Handles btnDisplay.Click 'Demonstrate the local nature of variables Dim x As Double = 2 lstResults.Items.Clear() lstResults.Items.Add(x) Trivial() lstResults.Items.Add(x) Trivial() lstResults.Items.Add(x) End Sub Sub Trivial() 'Do something trivial Dim x As Double lstResults.Items.Add(x) x = 3 lstResults.Items.Add(x) End Sub [Run, and then click the button. The following is displayed in the list box.] 2 0 3 2 0 3 2 |
Visual Basic provides a way to make a variable visible to every procedure in a form's code without being passed. Such a variable is called a class-level variable. The Dim statement for a class-level variable can be placed anywhere between the statements Public Class formName and End Class, provided that the Dim statement is not inside a procedure. Normally, we place the Dim statement just after the Public Class formName statement (We refer to this region as the Declarations section of the Code window.) A class-level variable is visible to every procedure. When a class-level variable has its value changed by a procedure, the value persists even after the procedure has finished executing. We say that such a variable has class-level scope. Variables declared inside a procedure are said to have local scope.
In general, the scope of a variable is the portion of the program that can refer to it. Class-level scope also is referred to as module-level scope, and local scope also is referred to as procedure-level scope. If a procedure declares a local variable with the same name as a class-level variable, then the name refers to the local variable for code inside the procedure.
The following program contains the class-level variables num1 and num2. Their Dim statement does not appear inside a procedure. It appears immediately following the statement Public Class frmAdd. Dim num1, num2 As Double 'Class-level variables Private Sub btnDisplay_Click(...) Handles btnDisplay.Click 'Display the sum of two numbers num1 = 2 num2 = 3 lstResults.Items.Clear() AddAndIncrement() lstResults.Items.Add("") lstResults.Items.Add("num1 = " & num1) lstResults.Items.Add("num2 = " & num2) End Sub Sub AddAndIncrement() 'Display numbers and their sum lstResults.Items.Add("The sum of " & num1 & " and " & _ num2 & " is " & (num1 + num2) & ".") num1 += 1 'Add 1 to the value of num1 num2 += 1 'Add 1 to the value of num2 End Sub [Run, and click the button. The following is displayed in the list box.] The sum of 2 and 3 is 5. num1 = 3 num2 = 4 |
In the preceding example, we had to click a button to assign values to the class-level variables. In some situations, we want to assign a value immediately to a class-level variable, without requiring the user to perform some specific action. This can be accomplished by declaring each class-level variable with a statement of the type
Dim variableName As varType = value
The following program assigns a value to a class-level variable as soon as it is created: Dim pi As Double = 3.14159 Private Sub btnCompute_Click(...) Handles btnCompute.Click 'Display the area of a circle of radius 5 txtArea.Text = "The area of a circle of radius 5 is " & (pi * 5 * 5) End Sub [Run, and then click the button. The following is displayed in the text box.] The area of a circle of radius 5 is 78.53975 |
Programs with Sub procedures are easier to debug. Each Sub procedure can be checked individually before being placed into the program.
In Appendix D, the section "Stepping through a Program Containing a General Procedure: Chapter 4" uses the Visual Basic debugger to trace the flow through a program and observe the interplay between arguments and parameters.
1. | What does the following code display in the list box when the button is clicked? Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim b As Integer = 1, c As Integer = 2 Rhyme() lstOutput.Items.Add(b & " " & c) End Sub Sub Rhyme() Dim b, c As Integer lstOutput.Items.Add(b & " " & c & " buckle my shoe.") b = 3 End Sub |
2. | Determine the output displayed in the list box when the button is clicked. Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim amt1 As Integer = 1, amt2 As Integer = 2 lstOutput.Items.Add(amt1 & " "& amt2) Swap(amt1, amt2) lstOutput.Items.Add(amt1 & " "& amt2) End Sub Sub Swap(ByRef num1 As Integer, ByRef num2 As Integer) 'Interchange the values of num1 and num2 Dim temp As Integer temp = num1 num1 = num2 num2 = temp lstOutput.Items.Add(num1 & " "& num2) End Sub |
3. | In Problem 2, change the Sub statement to Sub Swap(ByRef num1 As Integer, ByVal num2 As Integer) and determine the output. |
4. | In Problem 2, change the calling statement to Swap((amt1), (amt2)) and determine the output. |
In Exercises 1 through 18, determine the output displayed when the button is clicked.
1. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim num As Double = 7 AddTwo(num) txtOutput.Text = CStr(num) End Sub Sub AddTwo(ByRef num As Double) 'Add 2 to the value of num num += 2 End Sub |
2. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim term As String term = "Fall" Plural(term) txtOutput.Text = term End Sub |
3. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim dance As String dance = "Can " Twice(dance) txtOutput.Text = dance End Sub Sub Twice(ByRef dance As String) 'Concatenate the value of dance to itself dance &= dance End Sub |
4. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim a As Integer = 1, b As Integer = 3 lstOutput.Items.Add(a & " "& b) Combine(a, b) lstOutput.Items.Add(a & " "& b) Combine((a), b) lstOutput.Items.Add(a & " "& b) End Sub Sub Combine(ByRef x As Integer, ByVal y As Integer) x = y - x y = x + y lstOutput.Items.Add(x & " "& y) End Sub |
5. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim a As Double = 5 Square(a) txtOutput.Text = CStr(a) End Sub Sub Square(ByRef num As Double) num = num * num End Sub |
6. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim state As String = "NEBRASKA" Abbreviate(state) txtOutput.Text = state End Sub Sub Abbreviate(ByRef a As String) a = a.SubString(0, 2) End Sub |
7. |
Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim word As String = " " GetWord(word) txtOutput.Text = "Less is "& word & "." End Sub Sub GetWord(ByRef w As String) w = "more" End Sub |
8. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim hourlyWage, annualWage As Double hourlyWage = 10 CalcAnnualWage(hourlyWage, annualWage) txtOutput.Text = "Approximate Annual Wage: "& _ FormatCurrency(annualWage) End Sub Sub CalcAnnualWage(ByVal hWage As Double, ByRef aWage As Double) aWage = 2000 * hWage End Sub |
9. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim name As String = "", yob As Integer GetVita(name, yob) txtOutput.Text = name & " was born in the year "& yob End Sub Sub GetVita(ByRef name As String, ByRef yob As Integer) name = "Gabriel" yob = 1980 'Year of birth End Sub |
10. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim word1, word2 As String word1 = "fail" word2 = "plan" txtOutput.Text = "If you " Sentence(word1, word2) txtOutput.Text &= "," Exchange(word1, word2) txtOutput.Text &= " then you " Sentence(word1, word2) txtOutput.Text &= "." End Sub Sub Exchange(ByRef word1 As String, ByRef word2 As String) Dim temp As String temp = word1 word1 = word2 word2 = temp End Sub |
11. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim state As String = "Ohio " Team() End Sub Sub Team() Dim state As String txtOutput.Text = state vtxtOutput.Text &= "Buckeyes" End Sub |
12. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim a As Double = 5 Multiply(7) lstOutput.Items.Add(a * 7) End Sub Sub Multiply(ByRef num As Double) Dim a As Double a = 11 lstOutput.Items.Add(a * num) End Sub |
13. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim a As Double = 5 Multiply(7) End Sub Sub Multiply(ByVal num As Double) Dim a As Double txtOutput.Text = CStr(a * num) End Sub |
14. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim name, n As String name = "Ray" Hello(name) lstOutput.Items.Add(n & " and "& name) End Sub Sub Hello(ByRef name As String) Dim n As String n = name name = "Bob" lstOutput.Items.Add("Hello "& n & " and "& name) End Sub |
15. |
Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim num As Double = 1 Amount(num) Amount(num) End Sub Sub Amount(ByVal num As Double) Dim total As Double total += num 'Add the value of num to the value of total lstOutput.Items.Add(total) End Sub |
16. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim river As String river = "Wabash" Another() lstOutput.Items.Add(river & " River") Another() End Sub Sub Another() Dim river As String lstOutput.Items.Add(river & " River") river = "Yukon" End Sub |
17. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim n As Integer = 4, word As String = "overwhelming" lstOutput.Items.Add(n & " "& word) Crop(n, word) lstOutput.Items.Add(n & " "& word) Crop(n, (word)) lstOutput.Items.Add(n & " "& word) End Sub Sub Crop(ByVal n As Integer, ByRef word As String) n = word.Length - n word = word.Substring(word.Length - n) lstOutput.Items.Add(n & " "& word) End Sub |
18. | Private Sub btnCompute_Click(...) Handles btnCompute.Click Dim tax, price, total As Double tax = 0.05 GetPrice("bicycle", price) ProcessItem(price, tax, total) DisplayResult(total) End Sub Sub DisplayResult(ByVal total As Double) txtOutput.Text = "With tax, price is "& FormatCurrency(total) End Sub (Assume that the cost of the bicycle is $200.) |
In Exercises 19 and 20, find the errors.
19. | Private Sub btnCompute_Click(...) Handles btnCompute.Click Dim a, b, c As Double a = 1 b = 2 Sum(a, b, c) txtOutput.Text = "The sum is "& c End Sub Sub Sum(ByVal x As Double, ByVal y As Double) Dim c As Double c = x + y End Sub |
20. | Private Sub btnDisplay_Click(...) Handles btnDisplay.Click Dim ano As String = "" GetYear(ano) txtOutput.Text = ano End Sub Sub GetYear(ByRef yr As Double) yr = 2006 End Sub |
In Exercises 21 through 24, rewrite the program so input, processing, and output are each performed by calls to Sub procedures.
21. | Private Sub btnCompute_Click(...) Handles btnCompute.Click 'Calculate sales tax Dim price, tax, cost As Double lstOutput.Items.Clear() price = CDbl(InputBox("Enter the price of the item:")) tax = .05 * price cost = price + tax lstOutput.Items.Add("Price: "& price) lstOutput.Items.Add("Tax: "& tax) lstOutput.Items.Add("-------") lstOutput.Items.Add("Cost: "& cost) End Sub |
22. |
Private Sub btnDisplay_Click(...) Handles bnDisplay.Click 'Letter of acceptance Dim name, firstName As String, n As Integer lstOutput.Items.Clear() name = InputBox("What is your full name?") n = name.IndexOf(" ") firstName = name.Substring(0, n) lstOutput.Items.Add("Dear "& firstName & ",") lstOutput.Items.Add("") lstOutput.Items.Add("We are proud to accept you to Yale.") End Sub |
23. | Private Sub btnDisplay_Click(...) Handles bnDisplay.Click 'Determine the area of a rectangle Dim length, width, area As Double length = CDbl(txtLength.Text) width = CDbl(txtWidth.Text) area = length * width txtOutput.Text = "The area of the rectangle is "& area End Sub |
24. | Private Sub btnCompute_Click(...) Handles btnCompute.Click 'Convert feet and inches to centimeters Dim str As String Dim feet, inches, totalInches, centimeters As Double str = "Give the length in feet and inches. " feet = CDbl(InputBox(str & "Enter the number of feet.")) inches = CDbl(InputBox(str & "Enter the number of inches. ")) totalInches = 12 * feet + inches centimeters = 2.54 * totalInches txtOutput.Text = "The length in centimeters is "& centimeters End Sub |
In Exercises 25 and 26, write a line of code to carry out the task. Specify where in the program the line of code would occur.
25. | Declare the variable str as a string variable visible to all parts of the program. |
26. | Declare the variable str as a string variable visible only to the btnTest_Click event procedure. |
In Exercises 27 through 32, write a program to perform the stated task. The input, processing, and output should be performed by calls to Sub procedures.
27. | Request a person's first name and last name as input and display the corresponding initials. |
28. | Request the amount of a restaurant bill as input and display the amount, the tip (15 percent), and the total amount. |
29. | Request the cost and selling price of an item of merchandise as input and display the percentage markup. Test the program with a cost of $4 and a selling price of $6. Note: The percentage markup is (sellingprice cost) / cost. |
30. | Read the number of students in public colleges (12.1 million) and private colleges (3.7 million) from a file, and display the percentage of college students attending public colleges. |
31. | Read a baseball player's name (Sheffield), times at bat (557), and hits (184) from a file and display his name and batting average. Note: Batting average is calculated as (hits)/(times at bat). |
32. | Request three numbers as input, and then calculate and display the average of the three numbers. |
33. | The Hat Rack is considering locating its new branch store in one of three malls. The following file gives the monthly rent per square foot and the total square feet available at each of the three locations. Write a program to display a table exhibiting this information along with the total monthly rent for each mall. (Assume the nine lines of the file MALLS.TXT contain the following data: Green Mall, 6.50, 583, Red Mall, 7.25, 426, Blue Mall, 5.00, 823.) |
34. | Write a program that uses the data in the file CHARGES.TXT to display the end-of-month credit-card balances of three people. (Each set of four lines gives a person's name, beginning balance, purchases during month, and payment for the month.) The end-of-month balance is calculated as [finance charges] + [beginning-of-month balance] + [purchases] - [payment], where the finance charge is 1.5 percent of the beginning-of-month balance. (Assume the 12 lines of the file CHARGES.TXT contain the following data: John Adams, 125.00, 60.00, 110.00, Sue Jones, 0, 117.25, 117.25, John Smith, 350.00, 200.50, 300.00.) |
35. | Write a program to produce a sales receipt. Each time the user clicks on a button, an item and its price should be read from a pair of text boxes and displayed in a list box. Use a class-level variable to track the sum of the prices. When the user clicks on a second button (after all the entries have been made), the program should display the sum of the prices, the sales tax (5 percent of total), and the total amount to be paid. Figure 4.5 shows a sample output of the program. |
1. | 0 0 buckle my shoe. 1 2 This program illustrates the local nature of the variables in a Sub procedure. Notice that the variables b and c appearing in the Sub procedure have no relationship whatsoever to the variables of the same name in the event procedure. In a certain sense, the variables inside the Sub procedure can be thought of as having alternate names, such as bRhyme and cRhyme. |
2. | 1 2 2 1 2 1 Both variables are passed by reference and so have their values changed by the Sub procedure. |
3. | 1 2 2 1 2 2 Here amt1 is passed by reference and amt2 is passed by value. Therefore, only amt1 has its value changed by the Sub procedure. |
4. | 1 2 2 1 1 2 Both variables are passed by value, so their values are not changed by the Sub procedure. |