This chapter describes:
- What Is a Procedure?
- Defining and Invoking Function Procedures
- Function Procedure Example
- Defining and Invoking Sub Procedures
- Sub Procedure Example
- Rules of Passing Arguments
- Example - Passing Arguments by Reference
- Example - Passing Arguments by Value
- Passing Array as Arguments
- Variable Scope in Procedures
- Example - Variable Scopes
Notes and samples in this chapter are based Visual Basic 6.0.
What Is a Procedure?
A Procedure is a unit of code outside of the main execution code. But it can be executed by an invoking statement in the main execution code. There are 3 aspects about procedures:
1. Defining a procedure.
2. Invoking a procedure.
3. Exchanging data between the main execution code and a procedure.
VB offers two types of procedures:
1. Function Procedure - A procedure that returns a value explicitly.
2. Sub Procedure - A procedure that does not return any value explicitly.
Defining and Invoking Function Procedures
A "Function" statement defines a function procedure with the following syntax:
Function function_name(argument_list)
statement_block
function_name = return_value
End Function
where "function_name" is the name of the function, "argument_list" a list of variables used to pass data into and/or out of the function, and "return_value" is the value to be returned explicitly to the invoking statements.
Of course, "argument_list" is optional.
Assigning the return value to the function name is also optional. If not given, default value will be returned to the invoking statements. But this is not recommended.
Invoking a function procedure is simple, no need of any special statements. Just use the function name with an argument list in any expression:
... function_name(argument_list) ...
This will cause the system to:
- Stop evaluating the expression.
- Map data or variables based on the argument list.
- Execute the entire statement block defined inside the function.
- Take the value returned in the function name.
- Continue to evaluate the expression.
If you want terminate a function procedure early, you can use the "Exit" statement:
Exit FunctionFunction Procedure Example
To help you understand the concept of function procedure, I wrote the following the example, function_f2c.html:
<html> <body> <!-- function_f2c.html Copyright (c) 2006 by Dr. Herong Yang. http://www.herongyang.com/ --> <pre> <script language="vbscript"> d = F2C(70.0) document.writeln("Received Celsius = " & d) d = F2C(212.0) document.writeln("Received Celsius = " & d) Function F2C(dFahrenheit) document.writeln("") document.writeln("Converting Fahrenheit = " & dFahrenheit) dCelsius = (dFahrenheit - 32.0 ) / 1.8 document.writeln("Returning Celsius = " & dCelsius) F2C = dCelsius End Function </script> </pre> </body> </html>Here is the output:
Converting Fahrenheit = 70 Returning Celsius = 21.1111111111111 Received Celsius = 21.1111111111111 Converting Fahrenheit = 212 Returning Celsius = 100 Received Celsius = 100Easy to understand. Right?
Defining and Invoking Sub Procedures
A Sub Procedure is similar to a function procedure. It can be defined with the "Sub" statement:
Sub sub_name(argument_list) statement_block End Subwhere "sub_name" is the name of the sub procedure (subroutine), and "argument_list" a list of variables used to pass data into and/or out of the subroutine.
Of course, "argument_list" is optional.
Notice that subroutine does not return any values.
Invoking a subroutine is different than a function procedure. You can use one of the two syntaxes below:
1. Explicit call with "Call" statement:
Call sub_name(argument_list)2. Explicit call with subroutine name:
sub_name argument_listBoth syntaxes will cause the system to:
- Stop execution in main code flow.
- Map data or variables based on the argument list.
- Execute the entire statement block defined inside the subroutine.
- Continue to execute main code flow.
If you want terminate a sub procedure early, you can use the "Exit" statement:
Exit SubSub Procedure Example
Here is a short example of subroutines, function_sub.html:
<html> <body> <!-- function_sub.html Copyright (c) 2006 by Dr. Herong Yang. http://www.herongyang.com/ --> <pre> <script language="vbscript"> Call Hello("Tom") Hello "Herong" Sub Hello(sName) document.writeln("") document.writeln("Helo " & sName) End Sub </script> </pre> </body> </html>Here is the output:
Helo Tom Helo HerongNotice the two different ways of calling a subroutine.
Rules of Passing Arguments
As shown in previous examples, passing arguments to procedures seems to be a simple job. But it may cause confusion if you don't following the rules. VB script has the following rules on passing arguments to procedures:
1. By default, arguments are passed by reference. In this case, an argument name can be used as a variable that referring (sharing) the same data as the calling code.
2. But, arguments can be passed by value, if you put the key word "ByVal" before the argument name. In this case, an argument name can be used as a variable that contains a copy of the data provided by the calling code.
3. Of course, you put the word "ByRef" before an argument name to declare a pass-by-reference argument explicitly.
4. A pass-by-reference argument can be used to allow the procedure to alter data that is associated with a variable in the calling code. This allows the procedure to output data back to the calling code.
5. A pass-by-value argument is safer than a pass-by-reference argument, because the procedure only gets a copy of the data. Any changes to that data will not affect the original data in the calling code.
6. Arrays can be passed by reference.
7. Arrays can also be passed by value. In this case, the procedure will get a copy of the array.
8. I don't know how to specify an array as the return value of a function procedure.
Example - Passing Arguments by Reference
To see how passing arguments by reference works, I wrote the following example, function_swap_by_ref.html:
<html> <body> <!-- function_swap_by_ref.html Copyright (c) 2006 by Dr. Herong Yang. http://www.herongyang.com/ --> <pre> <script language="vbscript"> document.writeln("") document.writeln("Test 1: Swapping two literals by reference") document.writeln(" Before Sub: " & "Apple" & " | " & "Orange") Call SwapByRef("Apple", "Orange") document.writeln(" After Sub: " & "Apple" & " | " & "Orange") vFirst = "Dog" vSecond = "Cat" document.writeln("") document.writeln("Test 2: Swapping two variables by reference") document.writeln(" Before Sub: " & vFirst & " | " & vSecond) Call SwapByRef(vFirst, vSecond) document.writeln(" After Sub: " & vFirst & " | " & vSecond) Sub SwapByRef(ByRef vLeft, ByRef vRight) vTemp = vLeft vLeft = vRight vRight = vTemp document.writeln(" In Sub: " & vLeft & " | " & vRight) End Sub </script> </pre> </body> </html>Here is the output:
Test 1: Swapping two literals by reference Before Sub: Apple | Orange In Sub: Orange | Apple After Sub: Apple | Orange Test 2: Swapping two variables by reference Before Sub: Dog | Cat In Sub: Cat | Dog After Sub: Cat | DogHere are my comments about this example:
- Test 1 shows that data literal can be used for a "ByRef" argument. By you will not be able to receive the change done by the subroutine.
- Test 2 shows that using variable for a "ByRef" argument lets to receive the change by the subroutine. After the subroutine call, values in vFirst and vSecond have been swapped.
- "ByRef" keyword is optional.
Example - Passing Arguments by Value
To see how passing arguments by value works, I wrote the following example, function_swap_by_val.html:
<html>
<body>
<!-- function_swap_by_val.html
Copyright (c) 2006 by Dr. Herong Yang. http://www.herongyang.com/
-->
<pre>
<script language="vbscript">
document.writeln("")
document.writeln("Test 1: Swapping two literals by value")
document.writeln(" Before Sub: " & "Apple" & " | " & "Orange")
Call SwapByVal("Apple", "Orange")
document.writeln(" After Sub: " & "Apple" & " | " & "Orange")
vFirst = "Dog"
vSecond = "Cat"
document.writeln("")
document.writeln("Test 2: Swapping two variables by value")
document.writeln(" Before Sub: " & vFirst & " | " & vSecond)
Call SwapByVal(vFirst, vSecond)
document.writeln(" After Sub: " & vFirst & " | " & vSecond)
Sub SwapByVal(ByVal vLeft, ByVal vRight)
vTemp = vLeft
vLeft = vRight
vRight = vTemp
document.writeln(" In Sub: " & vLeft & " | " & vRight)
End Sub
</script>
</pre>
</body>
</html>
Here is the output:
Test 1: Swapping two literals by value Before Sub: Apple | Orange In Sub: Orange | Apple After Sub: Apple | Orange Test 2: Swapping two variables by value Before Sub: Dog | Cat In Sub: Cat | Dog After Sub: Dog | CatHere are my comments about this example:
- Test 1 is useless.
- Test 2 shows that "ByVel" arguments will not bring any changes back to the calling code. After the subroutine call, values in vFirst and vSecond have not been changed at all.
Passing Array as Arguments
As I mentioned earlier, arrays can also be passed as arguments. If an array is passed by reference, the procedure is working on the same array as the calling code. If an array is passed by value, the procedure is working on a independent copy of the array in the calling code.
Here is an example code of using array as an argument, function_reverse_array.html:
<html>
<body>
<!-- function_reverse_array.html
Copyright (c) 2006 by Dr. Herong Yang. http://www.herongyang.com/
-->
<pre>
<script language="vbscript">
document.writeln("")
document.writeln("Test 1: Reversing a data literal")
bOk = ReverseArray("Apple")
aPets = Array("Bird", "Cat", "Dog", "Fish", "Rabbit")
document.writeln("")
document.writeln("Test 2: Reversing an array")
document.writeln(" Before Sub: " & Join(aPets))
bOk = ReverseArray(aPets)
document.writeln(" After Sub: " & Join(aPets))
Function ReverseArray(ByRef aList)
If IsArray(aList) Then
iMin = LBound(aList)
iMax = UBound(aList)
For i=iMin to iMax\2
j = iMax - (i-iMin)
vTemp = aList(i)
aList(i) = aList(j)
aList(j) = vTemp
Next
ReverseArray = True
Else
document.writeln("Error: You are not giving an array.")
ReverseArray = False
End If
End Function
</script>
</pre>
</body>
</html>
Here is the output:
Test 1: Reversing a data literal Error: You are not giving an array. Test 2: Reversing an array Before Sub: Bird Cat Dog Fish Rabbit After Sub: Rabbit Fish Dog Cat Bird
My "ReverseArray" function worked perfectly. You can take it a utility function to your application.
Variable Scope in Procedures
Variable Scope - The area of source code where a variable is accessible.
If you are not using procedures, variable scope is very simple. The scope of a variable is: from the statement where it is defined to the last statement of the code.
If you are using procedures, variable scope gets more complex. Here are some basic rules:
1. Global - If a variable is defined in the main code, its scope is from the statement where it is defined to the last statement of the entire code including all procedures.
2. Local - If a variable is defined in a procedure code, its scope is from the statement where it is defined to the last statement of the procedure.
3. Collision - If a variable is explicitly defined in a procedure code has the same name as a variable defined in the main code, the variable of the main code become in-accessible within this procedure.
There are some interesting consequences of those rules:
- The nice thing about rule #1 is that variables defined the main code are automatically accessible in all procedures. You don't have to pass them as reference arguments to share them in a procedure.
- The bad thing about rule #2 is that if you are using temporary variable in a procedure without explicit declaration, you could accidentally change the value of a global variable of the same name.
- Rule #3 helps us to avoid the bad impact of rule #3, if you declare all temporary variables explicitly in procedures.
Example - Variable Scopes
To help you understand variable scope concepts, I wrote the following sample code, function_variable_scope.html:
<html>
<body>
<!-- function_variable_scope.html
Copyright (c) 2006 by Dr. Herong Yang. http://www.herongyang.com/
-->
<pre>
<script language="vbscript">
Dim vGlobalDim
vGlobalDim = "Cat"
vGlobalNoDim = "Dog"
Dim vTempDim
vTempDim = "Bird"
vTempNoDim = "Fish"
Call ScopeCheck()
document.writeln("")
document.writeln("Current value after Sub:")
document.writeln(" vGlobalDim = " & vGlobalDim)
document.writeln(" vGlobalNoDim = " & vGlobalNoDim)
document.writeln(" vLocalDim = " & vLocalDim)
document.writeln(" vLocalNoDim = " & vLocalNoDim)
document.writeln(" vTempDim = " & vTempDim)
document.writeln(" vTempNoDim = " & vTempNoDim)
Sub ScopeCheck()
Dim vLocalDim
vLocalDim = "Apple"
vLocalNoDim = "Orange"
Dim vTempDim
vTempDim = "Banana"
vTempNoDim = "Grape"
' Updating values
vGlobalDim = vGlobalDim & " - Updated by Sub"
vGlobalNoDim = vGlobalNoDim & " - Updated by Sub"
vLocalDim = vLocalDim & " - Updated by Sub"
vLocalNoDim = vLocalNoDim & " - Updated by Sub"
vTempDim = vTempDim & " - Updated by Sub"
vTempNoDim = vTempNoDim & " - Updated by Sub"
' Showing values
document.writeln("")
document.writeln("Current value in Sub:")
document.writeln(" vGlobalDim = " & vGlobalDim)
document.writeln(" vGlobalNoDim = " & vGlobalNoDim)
document.writeln(" vLocalDim = " & vLocalDim)
document.writeln(" vLocalNoDim = " & vLocalNoDim)
document.writeln(" vTempDim = " & vTempDim)
document.writeln(" vTempNoDim = " & vTempNoDim)
End Sub
</script>
</pre>
</body>
</html>
Here is the output:
Current value in Sub: vGlobalDim = Cat - Updated by Sub vGlobalNoDim = Dog - Updated by Sub vLocalDim = Apple - Updated by Sub vLocalNoDim = Orange - Updated by Sub vTempDim = Banana - Updated by Sub vTempNoDim = Grape - Updated by Sub Current value after Sub: vGlobalDim = Cat - Updated by Sub vGlobalNoDim = Dog - Updated by Sub vLocalDim = vLocalNoDim = vTempDim = Bird vTempNoDim = Grape - Updated by Sub
Here are my comments about this example:
- There are 6 variables in this example: vGlobalDim, vGlobalNoDim, vLocalDim, vLocalNoDim, vTempDim, and vTempNoDim.
- The behavior of vGlobalDim and vGlobalNoDim is pretty consistent, defined in the main code; and accessible in the procedure. "Dim" or not makes no difference.
- The behavior of vLocalDim and vLocalNoDim is also consistent, define in the procedure, not accessible in the main code. Notice that vLocalDim and vLocalNoDim are empty in the "after Sub" message.
- The behavior of vTempDim and vTempNoDim shows that "Dim" statement forces vTempDim to a new local variable. So we have two variables of the same name, one in the main code, and one in the procedure. This is why vTempDim still has the old value after the subroutine call.
Conclusions
- Be careful about the syntax of calling subroutine without the keyword "Call". You can not put argument list inside parentheses.
- Function procedure provides the return value through the function name.
- "Exit Function/Sub" can be used to terminate a procedure.
- "ByRef" keyword is optional. It shares the data with the calling code.
- "ByVal" keyword is required. It makes a copy of the data received from the calling code.
- Variable defined in the main code is globally accessible in any procedure.
- Variable defined in a procedure is only accessible in that procedure.
- Recursive calling of a procedure is allowed.
- I don't know how to specify an array as the return value of a function procedure.
discuss this topic to forum
