Understanding Subroutines and Functions in VB

Level:
Level1

Written By TheVBProgramer.

$ReqTestHarness$

You've already been exposed to the event procedures that VB creates when you want to execute code in response to a user action (such as the Click event of a command button). You can also write programmer-defined procedures (Subs and Functions), which you would use not to respond to an event, but to modularize your code.

 

The difference between a Sub and a Function is that a Sub does not produce a return value (i.e., one that can be assigned directly to a variable, whereas a Function does produce a return value). Both Subs and Functions can be called with or without parameters.

 

Calling a Sub Without Parameters

 

To call a Sub, you use the Call statement. The statement below calls a Sub named "AddEm".

 

Call AddEm

 

This would cause VB to execute the statements in the Sub named "AddEm" and then return to the statement following the Call.

 

Private Sub AddEm()

[statements]

End Sub

 

The keyword "Call" is optional, thus a call can be made simply by coding the name of the Sub:

 

AddEm ' same as "Call AddEm"

 

 

Example (Calling a Sub Without Parameters)

 

In the "Try It" example below, upon clicking the "Try It" button, the user is prompted to enter two numbers. The Addem Sub is called, which performs the addition. Program flow then returns to the Print statement, which displays the sum of the two numbers on the form.

 

Note that the variables involved are declared at the module-level (i.e., general declarations section, prior to any Subs or Functions) because the variables are common to both the cmdTryIt_Click event procedure and the AddEm Sub procedure.

 

Code:

 

Option Explicit

' Module-level variables are needed for this example

Dim mintNum1 As Integer

Dim mintNum2 As Integer

Dim mintSum As Integer

 

. . .

 

Private Sub cmdTryIt_Click()

 

mintNum1 = Val(InputBox("Enter first number:", "Add Program"))

mintNum2 = Val(InputBox("Enter second number:", "Add Program"))

AddEm ' Could also be coded as "Call Addem()" with or without the ()

Print "The sum is "; mintSum

 

End Sub

 

 

Private Sub AddEm()

 

mintSum = mintNum1 + mintNum2

 

End Sub

 

Screen-shots of the run:

 

 

 

 

 

Download the VB project code for the example above here.

 

 

Calling a Sub With Parameters

 

More often than not, a Sub procedure will be expecting one or more parameters. This enhances the reusability and flexibility of the Sub procedure. The full syntax for a Sub procedure is:

 

[Private | Public] Sub SubName[(parameter list)]

 

[statements]

End Sub

 

where "parameter list" is a comma-separated list of the parameter variables with their data types (i.e., var1 As datatype, var2 As datatype, etc.).

 

For example, if we were to modify the "AddEm" Sub to accept three Integer variables, the header for the "Addem" Sub would look like this:

 

Private Sub AddEm(pintNum1 As Integer, pintNum2 As Integer, pintSum As Integer)

 

The call to Addem must now specify, or pass, three integer variables to the Sub. The call can be coded in one of two ways. If the keyword Call is used, then the argument list (i.e., the set of variables that will be passed to the Sub) must be enclosed in parentheses:

 

Call AddEm(intNum1, intNum2, intSum)

 

If you omit the keyword Call, you must also omit the parenthesis surrounding the argument list:

 

AddEm intNum1, intNum2, intSum

 

Either variation of the syntax is acceptable, and one way is no more efficient than the other.

 

The important thing is that the argument list of the calling statement must match the parameter list of the Sub procedure one-for-one in terms of both the number of variables passed and the data types of the variables passed. The names of the corresponding argument/parameter variables can be (and often are) different – with the naming conventions I use, I start the variable names of the parameters in a Sub or Function header with the letter "p" for "parameter".

 

Example (Calling a Sub With Parameters)

 

In the "Try It" example below, as in the previous example, upon clicking the "Try It" button, the user is prompted to enter two numbers. The Addem Sub is called, which performs the addition. Program flow then returns to the Print statement, which displays the sum of the two numbers on the form.

 

The difference this time is that the three variables (the two addends and the sum) are passed as arguments to the Sub Addem. When the call is made, the Sub Addem adds pintNum1 and pintNum2, storing the result in pintSum. When program flow returns to the Print statement, the value of pintSum that was calculated in AddEm is available in its counterpart intSum.

 

The variables involved are declared at the local level (i.e., in the cmdTryIt_Click procedure, rather than in the general declarations section). A general rule-of-thumb is that variables should be as limited in scope as possible.

 

Code:

 

Option Explicit

 

. . .

Private Sub cmdTryIt_Click()

 

' The variables can be declared at the local level

Dim intNum1 As Integer

Dim intNum2 As Integer

Dim intSum As Integer

 

intNum1 = Val(InputBox("Enter first number:", "Add Program"))

intNum2 = Val(InputBox("Enter second number:", "Add Program"))

 

AddEm intNum1, intNum2, intSum ' Could also be coded as:

' Call Addem(intNum1, intNum2, intSum)

Print "The sum is "; intSum

 

End Sub

 

Private Sub AddEm(pintNum1 As Integer, pintNum2 As Integer, pintSum As Integer)

pintSum = pintNum1 + pintNum2

End Sub

 

When this example is run, it will behave exactly the same as the previous example.

 

Download the VB project code for the example above here.

 

ByRef vs. ByVal

 

Variables can be passed to a subroutine "by reference" or "by value". The default is "by reference". The differences are as follows:

 

  • When variables are passed by reference, only the memory addresses of the variables are passed. This means that the values passed can be modified by the subroutine and would have the new value after the subroutine exits and returns control to the calling program. A variable that is expected to contain a "return" value or output value must be passed by reference.

 

  • When variables are passed by value, a copy of the variable is passed. This means that even if the values passed are modified by the subroutine, those modified values remain local to that subroutine. When the subroutine exits and returns control to the calling program, the variables that were passed by value retain the value they originally had before the call. Appropriate variables to pass by value would be "input" values to the subroutine. "Input" values to the subroutine can be passed either by reference or by value; it would be "proper" to pass them by value. As long as the subroutine does not modify the input variables, there is no "harm" in passing them by reference. Be advised that you take a bit of a performance hit when you pass subroutine arguments by value – passing by reference is faster.

 

Using our previous example, to explicitly pass the "input" variables (the two addends) by value and the "output" variable (the sum) by reference, you would modify the Sub procedure header as follows:

 

Private Sub AddEm(ByVal pintNum1 As Integer, _

ByVal pintNum2 As Integer, _

ByRef pintSum As Integer)

pintSum = pintNum1 + pintNum2

End Sub

 

No change would be made to the statement that calls the Sub.

 

Calling a Function

 

To call a Function, the syntax is identical to an assignment statement that uses a VB built-in function:

 

VariableName = FunctionName[(argument list)]

 

For example, if I made a function called "AddEm" that returned a value (like the sum, for example), I could invoke the function as follows:

 

intSum = AddEm(intNum1, intNum2)

 

The above statement would cause the following to happen:

  • The procedure AddEm would be called, passing intNum1 and intNum2
  • The AddEm procedure would add the values of intNum1 and intNum2 to produce its return value, the sum.
  • When the AddEm procedure exits, its return value would be stored in the variable intSum

 

The format of the function procedure itself is:

 

[Private | Public] Function FunctionName[(parameter list)] [As datatype]

 

[statements]

FunctionName = value

[statements]

End Function

 

As when calling a Sub, the items passed in the argument list of the call to the Function must match the parameter list of the Function header in number and datatypes. The datatype of the return value must match the datatype of the target variable name in the assignment statement that calls the Function.

 

VB is "idiosyncratic" in the way it implements functions:

 

Ø       In most languages that support functions (such as C, Java, Pascal, etc.), the return value is set in a "return" statement, such as

 

return(value);

 

Also, in most languages that support functions, control is returned to the calling statement as soon as the return value is set .

 

Ø       On the other hand, with VB, the return value is set by assigning a value to the Function name within the Function's body:

 

FunctionName = value

Also, if executable statements are present after the assignment statement that sets the return value is set, those statements will be executed (i.e., setting the return value does NOT automatically cause control to be returned to the caller).

 

Following is the code to implement "AddEm" as a function:

 

Example (Calling a Function)

 

Option Explicit

. . .

 

Private Sub cmdTryIt_Click()

 

' The variables can be declared at the local level

Dim intNum1 As Integer

Dim intNum2 As Integer

Dim intSum As Integer

 

intNum1 = Val(InputBox("Enter first number:", "Add Program"))

intNum2 = Val(InputBox("Enter second number:", "Add Program"))

 

intSum = AddEm(intNum1, intNum2)

Print "The sum is "; intSum

 

End Sub

 

Private Function AddEm(pintNum1 As Integer, _

pintNum2 As Integer) _

As Integer

Addem = pintNum1 + pintNum2

End Function

 

 

When this example is run, it will behave exactly the same as the previous two examples.

 

Download the VB project code for the example above here.

 

 

Functions That Return Boolean Values

 

Consider the following function, called IsOdd, which determines whether or not a number is odd:

 

Private Function IsOdd(pintNumberIn) As Boolean

 

If (pintNumberIn Mod 2) = 0 Then

IsOdd = False

Else

IsOdd = True

End If

 

End Function

 

Now consider the following section of code that calls this function:

 

Private Sub cmdTryIt_Click()

 

Dim intNumIn As Integer

Dim blnNumIsOdd As Boolean

 

intNumIn = Val(InputBox("Enter a number:", "IsOdd Test"))

 

blnNumIsOdd = IsOdd(intNumIn)

 

If blnNumIsOdd Then

Print "The number that you entered is odd."

Else

Print "The number that you entered is not odd."

End If

End Sub

 

Note that the Boolean variable blnNumIsOdd was used to hold the return value of the function, and was then subsequently used in an If statement. There's nothing wrong with doing this; however, there is a shortcut coding technique you can use with functions that return Boolean values. You can eliminate the "middle man" (the Boolean variable) by calling the function directly in the If statement. Following is the procedure shown above modified to use this technique:

 

Private Sub cmdTryIt_Click()

 

Dim intNumIn As Integer

 

intNumIn = Val(InputBox("Enter a number:", "IsOdd Test"))

 

If IsOdd(intNumIn) Then

Print "The number that you entered is odd."

Else

Print "The number that you entered is not odd."

End If

End Sub

 

Download the VB project code for the example above here.

 

Using Optional Arguments

 

VB allows you to create Subs or Functions that have optional arguments – i.e., arguments that may or may not be passed; and if not passed will assume a default value. Optional arguments are specified in the procedure header with keyword Optional. All Optional arguments must be placed at the end of the argument list (they must follow any required arguments). For example, the procedure header

 

Private Sub MySub(pblnFlag As Boolean, Optional plngNumber As Long)

 

specifies that the last argument is Optional. Any call to MySub must pass it at least one argument (a Boolean) in order to avoid an error. If the caller so chooses, a second argument (a Long) can also be passed to MySub. Consider the following calls to MySub:

 

MySub ' Error: Required argument is missing

MySub True ' OK – required argument is passed

MySub False, 10 ' OK – both required and optional arguments passed

 

Optional arguments that are not passed will take on default values. Numeric arguments will default to 0, Strings will default to "", Booleans will default to False, etc. So in the case of the second example above, the value of plngNumber would be 0.

 

You can explicitly set the value of an Optional argument by assigning it a value in the procedure header, using syntax like the following:

 

Private Sub MySub(pblnFlag As Boolean, Optional plngNumber As Long = 1)

 

In this case, if the argument for plngNumber is not passed, it will have the default value of 1; otherwise, it will have the value of whatever was passed. Default values can only be used with Optional arguments.

 

Following is an example using a Sub that takes in three Optional arguments with default values. The Sub calculates the gross pay of an employee:

 

Code:

 

Private Sub cmdTryIt_Click()

 

Print "Hours", " Base Rate", " O/T Rate", " Base Pay", " O/T Pay", " Gross Pay"

Print "-----", " ---------", " --------", " --------", " -------", " ---------"

 

CalcGrossPay ' Default all three arguments

CalcGrossPay 25 ' Default rightmost two arguments

CalcGrossPay 42, 30 ' Default rightmost argument

CalcGrossPay 43, 28, 1.1 ' Pass all three arguments

CalcGrossPay 40, , 1 ' Default second argument

CalcGrossPay , 37 ' Default first and third arguments

CalcGrossPay , , 1.25 ' Default first two arguments

 

End Sub

Private Sub CalcGrossPay(Optional pintHoursWorked As Integer = 40, _

Optional psngBaseHourlyRate As Single = 35, _

Optional psngOvertimeRate As Single = 1.5)

 

Dim sngBasePay As Single

Dim sngOvertimePay As Single

Dim sngGrossPay As Single

 

If pintHoursWorked <= 40 Then

sngBasePay = psngBaseHourlyRate * pintHoursWorked

sngOvertimePay = 0

Else

sngBasePay = psngBaseHourlyRate * 40

sngOvertimePay = (pintHoursWorked - 40) _

* psngBaseHourlyRate _

* psngOvertimeRate

End If

 

sngGrossPay = sngBasePay + sngOvertimePay

 

Print pintHoursWorked, _

Format$(Format$(psngBaseHourlyRate, "Fixed"), "@@@@@@@@@@"), _

Format$(Format$(psngOvertimeRate, "Fixed"), "@@@@@@@@@@"), _

Format$(Format$(sngBasePay, "Currency"), "@@@@@@@@@@"), _

Format$(Format$(sngOvertimePay, "Currency"), "@@@@@@@@@@"), _

Format$(Format$(sngGrossPay, "Currency"), "@@@@@@@@@@")

End Sub

 

If you build this example on your own, make sure your form is wide enough to display all columns.

 

Screen-shots of the run:

 

 

 

Download the VB project code for the example above here.

 

The following example demonstrates the use of the IsMissing function, which can be used to test if an Optional Variant argument has been passed to a procedure (the IsMissing function will not work properly with non-Variant Optional arguments). This example produces the same output as the previous example:

 

Private Sub cmdTryIt_Click()

 

Print "Hours", " Base Rate", " O/T Rate", " Base Pay", " O/T Pay", " Gross Pay"

Print "-----", " ---------", " --------", " --------", " -------", " ---------"

 

CalcGrossPay ' Default all three arguments

CalcGrossPay 25 ' Default rightmost two arguments

CalcGrossPay 42, 30 ' Default rightmost argument

CalcGrossPay 43, 28, 1.1 ' Pass all three arguments

CalcGrossPay 40, , 1 ' Default second argument

CalcGrossPay , 37 ' Default first and third arguments

CalcGrossPay , , 1.25 ' Default first two arguments

 

End Sub

Private Sub CalcGrossPay(Optional pvntHoursWorked As Variant, _

Optional pvntBaseHourlyRate As Variant, _

Optional pvntOvertimeRate As Variant)

 

Dim sngBasePay As Single

Dim sngOvertimePay As Single

Dim sngGrossPay As Single

 

If IsMissing(pvntHoursWorked) Then pvntHoursWorked = 40

If IsMissing(pvntBaseHourlyRate) Then pvntBaseHourlyRate = 35

If IsMissing(pvntOvertimeRate) Then pvntOvertimeRate = 1.5

 

If pvntHoursWorked <= 40 Then

sngBasePay = pvntBaseHourlyRate * pvntHoursWorked

sngOvertimePay = 0

Else

sngBasePay = pvntBaseHourlyRate * 40

sngOvertimePay = (pvntHoursWorked - 40) _

* pvntBaseHourlyRate _

* pvntOvertimeRate

End If

 

sngGrossPay = sngBasePay + sngOvertimePay

 

Print pvntHoursWorked, _

Format$(Format$(pvntBaseHourlyRate, "Fixed"), "@@@@@@@@@@"), _

Format$(Format$(pvntOvertimeRate, "Fixed"), "@@@@@@@@@@"), _

Format$(Format$(sngBasePay, "Currency"), "@@@@@@@@@@"), _

Format$(Format$(sngOvertimePay, "Currency"), "@@@@@@@@@@"), _

Format$(Format$(sngGrossPay, "Currency"), "@@@@@@@@@@")

End Sub

 

Download the VB project code for the example above here.

 

Using Named Arguments

The following example demonstrates the use of the named arguments, where you can specify the arguments to be passed in the calling statement using the syntax

 

ArgumentName:=value

 

Using named arguments, the arguments to the procedure can be passed in any order. All required arguments must be passed, but optional arguments can be omitted if desired. Using named arguments saves coding when calling a procedure that has a lot of optional arguments but you only need to pass a few of them.

 

Following is the code for BoxVolume program using named arguments:

 

Private Sub cmdTryIt_Click()

 

Print "Hours", " Base Rate", " O/T Rate", " Base Pay", " O/T Pay", " Gross Pay"

Print "-----", " ---------", " --------", " --------", " -------", " ---------"

 

CalcGrossPay

CalcGrossPay pintHoursWorked:=25

CalcGrossPay pintHoursWorked:=42, psngBaseHourlyRate:=30

CalcGrossPay pintHoursWorked:=43, psngBaseHourlyRate:=28, psngOvertimeRate:=1.1

CalcGrossPay pintHoursWorked:=40, psngOvertimeRate:=1

CalcGrossPay psngBaseHourlyRate:=37

CalcGrossPay psngOvertimeRate:=1.25

 

End Sub

Private Sub CalcGrossPay(Optional pintHoursWorked As Integer = 40, _

Optional psngBaseHourlyRate As Single = 35, _

Optional psngOvertimeRate As Single = 1.5)

 

Dim sngBasePay As Single

Dim sngOvertimePay As Single

Dim sngGrossPay As Single

 

If pintHoursWorked <= 40 Then

sngBasePay = psngBaseHourlyRate * pintHoursWorked

sngOvertimePay = 0

Else

sngBasePay = psngBaseHourlyRate * 40

sngOvertimePay = (pintHoursWorked - 40) _

* psngBaseHourlyRate _

* psngOvertimeRate

End If

 

sngGrossPay = sngBasePay + sngOvertimePay

 

Print pintHoursWorked, _

Format$(Format$(psngBaseHourlyRate, "Fixed"), "@@@@@@@@@@"), _

Format$(Format$(psngOvertimeRate, "Fixed"), "@@@@@@@@@@"), _

Format$(Format$(sngBasePay, "Currency"), "@@@@@@@@@@"), _

Format$(Format$(sngOvertimePay, "Currency"), "@@@@@@@@@@"), _

Format$(Format$(sngGrossPay, "Currency"), "@@@@@@@@@@")

End Sub

 

Download the VB project code for the example above here.

 

 

Exiting a Sub or Function Early

 

If you need to exit a Sub or Function procedure "early", you can use the Exit Sub or Exit Function statements, respectively.

 

Example:

 

Private Sub cmdTryIt_Click()

 

Dim intNumIn As Integer

 

intNumIn = Val(InputBox("Enter a non-zero number:", "Test"))

If intNumIn = 0 Then

Print "You entered zero or a non-numeric value."

Exit Sub

End If

 

Print "The number you entered was: "; intNumIn

 

End Sub

If you enjoyed this post, subscribe for updates (it's free)

Thanks heaps

Thanks Heaps
- Aaron

Thanks

Thanks

Many Thanks!

Very Helpful...

thank you

thank you

Thanks

Many beginner programmers forgot to use function for structured programming. Thank you for reminding us again!

Thanks

Many beginner programmers forgot to use function for structured programming. Thank you for reminding us again!

learnt useful

really it was usefull

subroutine

SATISFIABLE ANSWER

thank you

thank you