Variable Scope: How VBA Decides When to Override V

2019-09-07 18:53发布

问题:

I have the following VBA code.

Option Explicit

Sub problem1()
    Dim speedmph As Double, speedkph As Double, speedfts As Double

    speedmph = Val(InputBox("Enter a speed (mph)"))
    Call convert(speedmph, speedkph, speedfts)
    MsgBox "The speed is " & speedkph & " km/hr and " & speedfts & "ft/s."

End Sub

Sub convert(speedmph, speedkph, speedfts)
    speedkph = speedmph * 1.609
    speedfts = speedmph * 1.467
End Sub

I've noticed that when I called the convert procedure as it is shown above, it worked. But if I tried to insert a value for speedkph (e.g. 0), the convert procedure does not override that and MsgBox prints out 0 for speedkph.

Why doesn't convert override the 0 even though it forces the variable to change in its function definition?

回答1:

Regarding scope, the parameters for a Function or Sub only have scope within the Function or Sub that they are declared in. The same thing holds true for variables declared with the keyword Dim (or ReDim for arrays). For undeclared variables, the scope is the next highest scope if a matching identifier exists (module level private variables, then global variables). If no identifier with a greater scope exists, it is implicitly declared as a Variant in local scope. So, in this line...

Sub convert(speedmph, speedkph, speedfts)

...the parameters speedmph, speedkph, and speedfts are all local to Sub convert (and are implicitly Variant because there aren't As statements), and in this line...

Dim speedmph As Double, speedkph As Double, speedfts As Double

...the parameters speedmph, speedkph, and speedfts are all local to Sub problem1().


That out of the way, let's address why your MsgBox shows anything at all. The default method of passing parameters is ByRef, so if you were to actually specify all of the implicit parts of your Sub declaration, it would look like this:

Public Sub convert(ByRef speedmph As Variant, ByRef speedkph As Variant, _
                   ByRef speedfts As Variant)

When you call it like this...

Call convert(speedmph, speedkph, speedfts)

...you pass it references to the local variables speedmph, speedkph, speedfts you declared in Sub problem1(). The Sub convert assigns values to those references, but each procedure only holds it's own copy of the reference in local scope. The identifiers are completely irrelevant at this point. In fact, this works exactly the same as your sample code (only the identifiers have been changed):

Sub problem1()
    Dim speedmph As Double, speedkph As Double, speedfts As Double

    speedmph = Val(InputBox("Enter a speed (mph)"))
    Call convert(speedmph, speedkph, speedfts)
    MsgBox "The speed is " & speedkph & " km/hr and " & speedfts & "ft/s."

End Sub

Sub convert(foo, bar, baz)
    bar = foo * 1.609
    baz = foo * 1.467
End Sub

Now to answer your specific question, the reason you can't use a "placeholder" value of 0 is that 0 isn't a variable - it's a literal. When it's passed to Sub convert, the only place the reference exists is as a variable on the call stack - Sub problem1() doesn't hold a reference to it, so there's no way for Sub problem1() to tell what Sub convert does with it. The stack variable is destroyed when Sub convert returns and the call stack unwinds. Basically, passing a literal to a ByRef parameter is functionally equivalent to passing it ByVal.