Visual Basic in 12 Easy Lessons vel16.htm

Previous Page TOC Next Page



Lesson 8, Unit 16


Arguments and Scope



What You'll Learn


Now that you can write subprograms—including subroutine and function procedures stored in modules—it's time to learn how those routines can communicate with each other. Until now, all variables that you've defined have been local variables. A local variable is known only within the procedure in which you define the variable. Therefore, if one procedure calculated a variable and a second procedure needed to work with that same variable, the two procedures would have no way to share that variable in a way that both could work with the variable.

Until now, if two or more procedures had to work with a value, that value had to be a value from a control on the form. Controls are available to all procedures within the application. There won't always be controls for all values, however, especially the intermediate values needed in many large applications. Only those values displayed to the user should be stored in controls. The remaining intermediate values that you'll work with must stay in variables and be available to all procedures that work with those variables.

Three Kinds of Variable Scope


Concept: There are three kinds of variable scope: local, module, and global. This section explains how to define variables that fit in any of those three kinds of scope.



Definition: Scope determines how much of a program can access a variable.

You already know about all the data types that variables can take on. There are integers, strings, and decimal variables, as well as variant variables that can accept any data type. Not only do variables have names, contents, and data types, but variables also take on one of these three kinds of scope:



Note: If you were to look at CONSTANT.TXT, the module file that you added to AUTOLOAD.MAK, you would see that all of its named values are defined inside the (general) procedure. The location of those definitions and their definition using the Global statement dictate that all values inside CONSTANT.TXT are defined at the module level. If Microsoft defined all of CONSTANT.TXT's named constant values from within a CONSTANT.TXT procedure other than (general), no other procedure could access the values within CONSTANT.TXT.

When you need to define variables, you should no longer automatically use the Dim statement. Now that you know how to write programs that contain several procedures, one or more of which might need to share data, you'll need to decide not only how you want to define the variables (using Dim or Global), but also where you want to define those variables. Your choice depends on the scope that the variables need. If a variable is used only within a single procedure, there is no reason to define that variable to have module or global scope.

Review: The scope of data determines the extent to which other parts of the program can access that data. Controls always have global scope because any procedure in any of a program's modules can use control data. Variables defined in a module's (general) procedure using the Global keyword are also global and available from everywhere in the program. Variables defined in the (general) procedure using Dim are module variables and are available from any procedure in the module. Variables defined using Dim inside procedures (other than the (general) procedure) are local variables and are available only within that procedure.

Global Variables


Concept: The Global statement defines global variables. You can use Global only within a module's (general) procedure. Often, programmers add the Const keyword, as done throughout CONSTANT.TXT, to define named constant values that are variables whose values can't change.

The Global statement is similar to Dim. Here is the format of Global:


Global [Const] VarNa me [AS DataType] [= 
value]

You can define variables only as global variables in the (general) declarations procedure of a module. Every procedure within that entire application can access the global variables. Therefore, a module's Form_Load() procedure can initialize a global variable that you defined, and another module's function procedure can access and change that same variable. If you use the Const keyword, you must also assign the variable an initial value but not use a data type. Without Const, you must specify the data type.



Tip: Define constant names that contain all uppercase letters. Throughout the program, you'll more easily be able to distinguish variable from constant names.

The following statement, appearing in the (general) procedure of any non-form module, defines an integer global variable named MyGlobal. Any procedure within the entire application can initialize, access, and change the variable.


Global MyGlobal As Integer

The Const keyword enables you to name constants that you'll use throughout the rest of the program. For example, if your company's number of divisions is eight, you might want to define a global named constant like this:


Global Const NUMDIVS = 8

A named constant can never be changed, by user input, an assignment, or through any other kind of variable-changing statement for the rest of the program. NUMDIVS will always hold the value of 8. The advantage of using a named constant over the value of 8 everywhere in the program that needs to use the number of divisions is that, if the number of divisions change, you have to change the number in only one place, and the rest of the program automatically uses the new value. Also, the Const specifier keeps you or someone who modifies your code from accidentally overwriting the value elsewhere in the code. Figure 16.1 shows the error message box that Visual Basic displays if any line in the program attempts to change a named constant.

Figure 16.1. Visual Basic reminds you if you attempt to change a named constant.

The reason that you must specify an initial constant value is because the Global statement is the only place in a program where you can assign values to constants.

Stop and Type: Listing 16.1 contains three global variable and two global named constant definitions. The code must appear in the (general) procedure of a non-form module or Visual Basic will issue an error message.

Review: The Global statement enables you to define global variables. If you use the Const modifier to name global constants, you can't define the global's data type, but you must initialize the global constant at the time that you define the named constant.

Listing 16.1. Defines five global values.


1: Option Explicit

2:

3: ' Three global variables

4: Global G1 
As Single

5: Global G2 As Double

6: Global G3 As Single

7:

8: ' Two named constants

9: Global Const G4 = 495.42

10: Global Const G5 = 0

Analysis: Line 1 specifies that all variables within the module must be explicitly defined. The Option Explicit statement means that there's little chance that you'll misspell a variable name. Lines 4 through 6 define three global variables. Any procedure in the entire program, even procedures in other modules, can initialize, read, and change G1, G2, and G3. Lines 9 and 10 define two globally named constant values called G4 and G5. Both of the constants are defined in lines 9 and 10 because G4 and G5 can't be initialized anywhere else in the program.

Module Variables


Concept: Module variables are also defined in (general) procedures. Rather than use Global, you use Dim to define module variables. Module variables are available to all procedures within the module in which they are defined. Therefore, module variables are more limited in scope than global variables because they aren't available globally.

Module variables are considered slightly safer variables than global variables. Only the module in which you define module variables can access and change those variables. Therefore, technically, you could have two different modules in a single application, and each module could have the same module variable with the same name. Each module's module variables, however, would be separate variables. The code that accesses and changes the module variable within one of the modules changes only that module's variable.



Caution: Despite the fact that two modules within the same application can be named the same variable name without conflict, you should try not to duplicate variable names because of the maintenance confusion such double-defined variables can cause.

Unlike global variables, which you can define only in non-form modules, you can define module variables in the form's module or within any external module in your program. Generally, programmers rarely use module variables; most applications need either global values that are available through the application or local values that are specifically available only to individual procedures.

Stop and Type: Listing 16.2 defines two module variables. The module variables must be defined in the (general) procedure of any module within the application.

Review: Module variables, defined with Dim in the (general) procedure of any module, are available to any procedure within that module.

Listing 16.2. Code that defines two module variables.


1: Dim M1 As Integer

2: Dim M2 As Double

Analysis: Lines 1 and 2 define two module variables named M1 and M2. The definitions must appear within some module's (general) procedure. The variables aren't global because Dim defines them instead of Global.

Local Variables—The Safest Variables


Concept: Local variables are available only within the procedures in which they are defined. Use the Dim statements to define local variables. You may define local variables only within individual non-declarations procedures; you can't define local variables within the (general) procedure.

Global and module variables aren't considered as safe as local variables. Suppose that only three procedures within a large application need access to a variable. If you limit that variable's scope to those three procedures, you won't accidentally change those variables in other procedures by using the variables where you never intended to use them.

Most of your program's working variables should be local. You'll define those variables, as you've been doing throughout this book, and those variables aren't available outside their procedures. All of the Dim statements that you saw in this book, before this unit, defined local variables.



Caution: Local variables defined with Dim last only as long as the procedure's execution lasts.

When a procedure ends, its local variables defined with Dim go away and their values disappear. If execution were to re-enter that procedure, Visual Basic would define those variables all over again and initialize them again. Therefore, if execution enters a procedure a second time, the values of all local variables defined with Dim the last time that the procedure executed would no longer be in effect.

Stop and Type: Listing 16.3 shows a command button's event procedure that defines a local variable and uses that variable and a module variable to compute a value for a label's caption.

Review: When you declare a variable at the top of a procedure using Dim, you declare a local variable that Visual Basic recognizes only for the life of that procedure.

Listing 16.3. Using a module and a local variable within a procedure.


1: Sub cmdInvFactor_Click()

2: ' Adds an inventory factor to a module-level

3: ' inventory total when the user clicks the

4: ' inventory command 
button

5: Dim FactAdd As Single

6: FactAdd = .13

7: ' Add to the module variable named InventoryTotal

8: lblInvent.Caption = InventoryTotal + FactAdd

9: End Sub

Analysis: Line 1 begins a new event procedure. Therefore, any variable defined within the procedure, using Dim, must be a local variable. Line 5 defines the local variable named FactAdd. Line 8 then adds FactAdd to a module variable named InventoryTotal to compute the final label's result.

Passing Arguments


Concept: Local variables are considered the safest, and generally the best kinds of variables to define. However, there will be many times when a subroutine or function procedure needs a value from another's local variable. For example, suppose that one procedure calculates a value, and a second procedure must use that value in a different calculation before displaying a result on the form. This section explains how to pass local data from the procedure that defines the local variable to other procedures that need to work with that value.

When you call the built-in functions, you pass one or more arguments to the function so that the function's internal code has data to work with. When you call your own subroutine and function procedures, you also can pass arguments to them. The arguments are nothing more than the passing procedure's local variables that the receiving procedure needs to work with.

Once you pass data, that data is still local to the original passing procedure, but the receiving procedure has the opportunity to work with those values. Depending on how you pass the arguments, the receiving procedure might even be able to change those values so that when the passing procedure regains control, its local variables have been modified.

Figure 16.2 illustrates the terminology used in passing and receiving arguments from one procedure to another. A procedure, referred to as the calling procedure, passes one or more of its local variables, referred to as arguments, to the receiving procedure. The only reason that parentheses exist following a procedure name is to hold the arguments sent to the receiving function.

Figure 16.2. Showing the proper procedure-calling terminology.



Note: As you already know, if the receiving procedure is a function procedure, the function procedure usually returns a value to the calling procedure.

In Figure 16.2, the receiving arguments are I, J, and K. The receiving procedure uses the same names are the calling procedure in this figure, and that's usually the case. However, you don't have to use the same names. In other words, if RecProc() received the three variables as X, Y, and Z, then RecProc() would have three variables to work with named X, Y, and Z, and those three variables would have the same values as I, J, and K in the calling procedure. Therefore, the receiving names don't have to match the passed names, although they typically do to eliminate any confusion.

Declare the data type of all received arguments. If you must pass and receive more than one argument, separate the passed arguments and the received arguments (along with their declared data types), with commas. The following statement passes the three values to the subroutine in Figure 16.2:


Call RecProc(I, J, K)

The following statement begins RecProc() procedure:


Sub RecProc (I As Integer, J As Integer, K As Single)

The calling procedure already knows the data types of I, J, and K, but those values are unknown to RecProc(). Therefore, you'll have to code the data type of each received argument so that the receiving function knows the data type of the sent arguments.

If a subroutine or function procedure is to receive arrays, don't indicate the array subscripts inside the argument list. The following Sub statement defines a general-purpose subroutine procedure that accepts four arrays as arguments:


Sub WriteData (CNames() As String, CBalc() As Currency, 
CDate() As Variant, CRegion() As Integer)

The built-in UBound() function returns the highest subscript that's defined for any given array. The following statement, which might appear inside the WriteData() subroutine, stores the highest possible subscript for the CNames() array, so the subroutine won't attempt to access an array subscript outside the defined limit:


HighSub = UBound(CNames)


Warning: Don't forget that the Call statement is funny about the argument parentheses. If you use Call, you must also enclose the arguments in parentheses. You may omit the Call keyword, but if you do, omit the parentheses as well. Here is an equivalent Call statement as that shown in Figure 16.2:
RecProc I, J, K ' No Call, no parens!


Stop and Type: Suppose that you're writing a set of programs for a bookstore's inventory and customer tracking purposes. The owners of the bookstore require that the user enter a category code that describes the kind of item being tracked or bought, and the program will print the description of that category as the purchase is entered or as the item is tracked through inventory. In other words, you'll ask the user for a category code of 1, 2, 3, 4, or 5, and the program will return a description in a string message variable describing that category. The following Select Case would work:


Select Case CatCode

 Case 1:

 CatMessage = "Book"

 Case 2:

 CatMessage = "Magazine"

 Case 3:

 CatMessage = "Newspaper"

 
Case 4:

 CatMessage = "Writing Supplies"

 Case 5:

 CatMessage = "Software"

 Case Else:

 CatMessage = "The category code is in error"

End Select

The problem is that this lengthy Select Case is needed throughout the program and needed by several other Visual Basic programs that you're writing as well. You're much better off storing this code as a general-purpose function procedure such as the one shown in Listing 16.4, and calling the function from wherever the code is needed, like this:


CatDesc = DispCatCode$(CatCode) ' CatDesc is a string

The function procedure enables you to pass the category code number argument and return the string description that matches that argument. As long as you store the procedure in a module file, any application that you add the module file to will be able to call the function and receive the message.

Review: By passing arguments between function and subroutine procedures, your procedures can share local data. If a procedure doesn't need access to another's local variable, you'll never pass that procedure the variable. By passing data only as needed, you'll be provided data access on a need-to-know basis; that is, only those procedures that need access to data get access. If all of your variables are global, any procedure could inadvertently change another's variable, and such logic errors are often difficult to find.

Listing 16.4. A general-purpose function procedure that you can call from any other procedure.


1: Function DispCatCode$(CatCode As Integer)

2: Dim 
CatDesc As String

3: Select Case CatCode

4: Case 1:

5: CatMessage = "Book"

6: Case 2:

7: CatMessage = "Magazine"

8: Case 3:

9: CatMessage = "Newspaper"

10: Case 4:

11: CatMessage = "Writing Supplies"

12: 
Case 5:

13: CatMessage = "Software"

14: Case Else:

15: CatMessage = "The category code is in error"

16: End Select

17: DispCatCode = CatMessage ' Returns a description

18: End Function

Analysis: The DispCatCode$() function could be stored in an external module file along with several other subroutine and function procedures that your applications often need. The module file acts like a toolchest with tools (procedures) that you can use (call) any time you need them.

Line 1 defines the procedure to be a function that receives one integer argument from the calling code. Line 2 defines a local string variable that the Case statements will use as a temporary storage location for the appropriate description. Lines 4 through 15 then test the passed integer to see whether the category code is 1, 2, 3, 4, 5, or another value that would indicate an error. Line 17 ensures that the appropriate message is returned to the calling procedure.

Receiving Two Ways: By Address and By Value


Concept: There are two ways to receive passed arguments: by address and by value. The method that you use determines whether the receiving procedure can change the arguments so that those changes remain in effect after the calling procedure regains control. If you pass and receive by address (the default method), the calling procedure's passed local variables may have been changed in the receiving procedure. If you pass and receive by value, the calling procedure can access and change its received arguments, but those changes don't retain their effects in the calling procedure.

Subroutines and functions can always use their received values and also change those arguments. If a receiving procedure changes one of its arguments, the corresponding variable in the calling procedure is also changed. Therefore, when the calling procedure regains control, the value (or values) that the calling procedure sent as an argument to the called subroutine may be different than before the call.

Arguments are passed by address, meaning that the passed arguments can be changed by their receiving procedure. If you want to keep the receiving procedure from being able to change the calling procedure's arguments, you must pass the arguments by value. To pass by value, precede any and all receiving argument lists with the ByVal keyword, or enclose the passed arguments in parentheses.



Tip: It's generally safer to receive arguments by value because the calling procedure can safely assume that its passed values won't be changed by the receiving procedure. Nevertheless, there may be times when you want the receiving procedure to permanently change values passed to it, and you'll need to receive those arguments by address.

Stop and Type: Listing 16.5 shows two subroutine procedures. One, named Changes(), receives its arguments by address. The second procedure, NoChanges() receives its arguments by value. Even though both procedures multiply their arguments by ten, those changes affect the calling procedure's variables only when Changes() is called but not when NoChanges() is called.

Review: The method by which you send and receive arguments determines how long a receiving procedure's changes stay in effect. If the receiving procedure changes one or more of its received-by-address arguments, those changes remain in effect when the calling procedure regains control. Therefore, when calling a procedure that accepts arguments passed and received by address, be aware that the passed values could be different when control returns to the calling procedure.

Listing 16.5. One procedure receives by address and the other receives by value.


1: Sub Changes (N As Integer, S As Single)

2: ' Receives arguments by address

3: N = N * 2 ' Double both

4: S = S * 2 ' arguments

5: ' When the calling routine regains control,

6: ' its two local variables will now be twice

7: ' as 
much as they were before calling this.

8: End Sub

9: 

10: Sub NoChanges (ByVal N As Integer, ByVal S As Single)

11: ' Receives arguments by value

12: N = N * 2 ' Double both

13: S = S * 2 ' arguments

14: ' When the calling routine regains control,


15: ' its two local variables will not be

16: ' changed from their original values.

17: End Sub

Analysis: Line 1 defines the Changes() procedure to receive two arguments by address. Therefore, when lines 3 and 4 double those two values, the calling procedure's variables will be doubled also.

Line 10 defines the NoChanges() procedure that receives its two arguments by value. The ByVal keyword tells Visual Basic that no matter what happens to the arguments in the NoChanges() procedure, the calling procedure's values will be unaffected by the change.

The following statements would call these two procedures properly:


Call Changes(N, S)


Call NoChanges(X, S)

Again, the variable names in the calling procedures do not have to match the corresponding received names in the receiving argument lists. Therefore, the calling procedure sends its local variable named X to NoChanges(), which references that value as N inside the receiving procedure. If you want to send data to either procedure and ensure that the sent variables can't be changed even in the procedure that receives by address, enclose each sent argument in parentheses like this:


Call Changes( (N), (S) )

Call NoChanges( (X), (S) )

The special parentheses coding overrides any by address passing and ensures that the passed values will retain their original values after the called procedures finish.

Homework



General Knowledge


  1. What is the name of the scope of a variable defined inside a procedure?

  2. What is the name of the scope of a variable defined using the Dim keyword inside a module?

  3. What is name of the scope of a variable defined using the Global keyword?

  4. True or false: You can define a global variable inside the form's module.

  5. True or false: You can define a module variable inside the form's module.

  6. True or false: You can define a module variable inside a non-form module.

  7. Which kind of scope offers the least amount of safety?

  8. Which kind of scope is used the least?

  9. How are the values in CONSTANT.TXT scoped?

  10. What does the Const keyword do?

  11. Why must you assign initial values to named constants when you define named constants?

  12. Where must you define all named constants?

  13. True or false: Your application can have two different variables with the same name.

  14. What happens to local variables when their procedures end?

  15. What is the name of the procedure that sends arguments to another?

  16. What is the name of the procedure that receives arguments from another?

  17. True or false: Built-in functions need no arguments.

  18. Why must you specify data types for each argument in receiving argument lists?

  19. True or false: You sometimes use the Call statement to call function procedures.

  20. True or false: You can pass and receive at most one value to and from function procedures.

  21. How many ways can a procedure receive variables?

  22. Describe what the term by address means to the program.

  23. Describe what the term by value means to the program.

  24. Describe the two ways to ensure that arguments pass by value.

  25. When receiving arrays, what function tells the receiving procedure the highest subscript value available for the array?


Write Code That...


  1. Define two global variables that you could use to hold a person's last name and age.

  2. Define two global constants that hold the number of days in the week and the number of months in a year.


Find the Bug


  1. What's wrong with the following global variable definitions?
    Global Const X1 As Integer
    Global Const X2 As Integer = 19
    Global X3 = 19
    Global X4 As Integer = 19

  2. Merle needs to write a function procedure that accepts an integer variable and a string array that contains 45 elements. Merle keeps getting an error with the following function definition statement. Perhaps you can tell Merle what he needs to do.
    Function Report(Age As Integer, CoNames(45) As String)


Extra Credit


  1. Write a general-purpose function procedure that accepts one string argument and returns a string that contains only every other letter of the passed string. Return a null string, "", if the passed string contains fewer than 2 characters.

Previous Page Page Top TOC Next Page