程序疑难解答 (Visual Basic)
此页列出在使用程序时可能出现的一些常见问题。
从函数程序返回数组类型
如果 Function
程序返回数组数据类型,则不能使用 Function
名称将值存储在数组元素中。 如果尝试这样做,编译器会将其解释为对 Function
的调用。 以下示例生成编译器错误:
Function AllOnes(n As Integer) As Integer()
For i As Integer = 1 To n - 1
' The following statement generates a COMPILER ERROR.
AllOnes(i) = 1
Next
' The following statement generates a COMPILER ERROR.
Return AllOnes()
End Function
语句 AllOnes(i) = 1
生成编译器错误,因为它似乎使用错误数据类型的参数(标量 Integer
而非 Integer
数组)调用 AllOnes
。 语句 Return AllOnes()
生成编译器错误,因为它似乎在没有参数的情况下调用 AllOnes
。
正确方法:若要修改要返回的数组的元素,请定义一个内部数组作为局部变量。 以下示例编译没有错误:
Function AllOnes(n As Integer) As Integer()
Dim iArray(n - 1) As Integer
For i = 0 To n - 1
iArray(i) = 1
Next
Return iArray
End Function
程序调用未修改参数
如果倾向于允许程序更改调用代码中参数的基础编程元素,则必须通过引用来传递该元素。 但是,即使按值传递,程序也可以访问引用类型参数的元素。
基础变量。 要允许过程替换基础变量元素本身的值,程序必须声明参数 ByRef。 此外,调用代码不得将参数括在括号中,因为这会替代
ByRef
传递机制。引用类型元素。 如果声明参数 ByVal,则程序无法修改基础变量元素本身。 但是,如果参数是引用类型,则程序可以修改它所指向的对象的成员,即使它不能替换变量的值。 例如,如果参数是数组变量,则程序无法为其分配新数组,但可以更改它的一个或多个元素。 已更改的元素反映在调用代码中的基础数组变量中。
以下示例定义了两个程序,它们按值获取数组变量并对其元素进行操作。 程序 increase
只向每个元素添加一个数组变量。 程序 replace
将新数组分配给参数 a()
,然后向每个元素添加一个数组。 但是,重新分配不会影响调用代码中的基础数组变量,因为 a()
被声明为 ByVal
。
Public Sub increase(ByVal a() As Long)
For j As Integer = 0 To UBound(a)
a(j) = a(j) + 1
Next j
End Sub
Public Sub replace(ByVal a() As Long)
Dim k() As Long = {100, 200, 300}
a = k
For j As Integer = 0 To UBound(a)
a(j) = a(j) + 1
Next j
End Sub
以下示例对 increase
和 replace
进行了调用:
Dim n() As Long = {10, 20, 30, 40}
Call increase(n)
MsgBox("After increase(n): " & CStr(n(0)) & ", " &
CStr(n(1)) & ", " & CStr(n(2)) & ", " & CStr(n(3)))
Call replace(n)
MsgBox("After replace(n): " & CStr(n(0)) & ", " &
CStr(n(1)) & ", " & CStr(n(2)) & ", " & CStr(n(3)))
第一个 MsgBox
调用显示“After increase(n): 11, 21, 31, 41”。 由于 n
是一个引用类型,因此 increase
可以更改其成员,即使它传递了 ByVal
。
第二个 MsgBox
调用显示“After replace(n): 11, 21, 31, 41”。 由于 n
传递 ByVal
,因此 replace
无法通过向 n
变量分配一个新数组来修改该变量。 当 replace
创建新的数组实例 k
并将其分配给局部变量 a
时,它将丢失调用代码传入的对 n
的引用。 当它增加 a
的成员时,只有本地数组 k
受到影响。
正确方法:要修改基础变量元素本身,请通过引用传递它。 以下示例演示了 replace
声明中的更改,允许其在调用代码中将一个数组替换为另一个数组:
Public Sub replace(ByRef a() As Long)
无法定义重载
如果要定义程序的重载版本,则必须使用相同的名称,但使用不同的签名。 如果编译器无法将声明与具有相同签名的重载区分开来,将会生成错误。
程序的签名由程序名称和参数列表确定。 每个重载必须与所有其他重载具有相同的名称,但在签名的至少一个其他组件中必须不同于其他所有重载。 有关更多信息,请参见 Procedure Overloading。
以下项(即使它们与参数列表相关)不是程序签名的组件:
- 程序修饰符关键字,例如
Public
、Shared
和Static
。 - 参数名称。
- 参数修饰符关键字,例如
ByRef
和Optional
。 - 返回值的数据类型(转换运算符除外)。
无法通过仅改变上述一项或多项来重载程序。
正确方法:要定义程序重载,必须改变签名。 由于必须使用同一名称,因此必须更改参数的数量、顺序或数据类型。 在泛型程序中,可以更改类型参数的数量。 在转换运算符 (CType Function) 中,可以改变返回类型。
具有 Optional 和 ParamArray 参数的重载决策
如果使用一个或多个 Optional 参数或 ParamArray 参数重载程序,则必须避免重复任何隐式重载。 有关信息,请参阅重载程序的注意事项。
调用重载程序的错误版本
如果程序具有多个重载版本,应熟悉其所有参数列表,并了解 Visual Basic 对重载之间的调用的解析方式。 否则,可以调用非预期的重载。
确定要调用的重载后,请注意遵守以下规则:
- 按正确顺序提供正确的参数数。
- 理想情况下,参数应具有与相应参数完全相同的数据类型。 在任何情况下,每个参数的数据类型都必须扩大为其对应参数的数据类型。 即使将 Option Strict 语句设置为
Off
也是如此。 如果重载需要从参数列表进行任何收缩转换,则该重载不符合调用条件。 - 如果提供需要扩大的参数,请使其数据类型尽可能接近相应的参数数据类型。 如果两个或多个重载接受你的参数数据类型,编译器会将调用解析为调用最少扩大的重载。
在准备参数时,可以通过使用 CType 函数转换关键字来降低数据类型不匹配的可能性。
重载决策失败
调用重载过程时,编译器会尝试消除除其中一个重载外的所有重载。 如果成功,它会对该重载的调用进行解析。 如果消除所有重载,或者无法将符合条件的重载减少为单个候选项,则会生成错误。
以下示例演示了重载决策过程:
Overloads Sub z(ByVal x As Byte, ByVal y As Double)
End Sub
Overloads Sub z(ByVal x As Short, ByVal y As Single)
End Sub
Overloads Sub z(ByVal x As Integer, ByVal y As Single)
End Sub
Dim r, s As Short
Call z(r, s)
Dim p As Byte, q As Short
' The following statement causes an overload resolution error.
Call z(p, q)
在第一次调用中,编译器将消除第一个重载,因为第一个参数的类型 (Short
) 会缩小到相应参数的类型 (Byte
)。 然后,它将消除第三个重载,因为第二个重载中的每个参数类型(Short
和 Single
)都将扩大到第三个重载中的相应类型(Integer
和 Single
)。 第二个重载需要更少的扩大,因此编译器将其用于调用。
在第二次调用中,编译器无法根据收缩消除任何重载。 它消除第三个重载的原因与第一次调用相同,因为它可以通过减少扩大参数类型来调用第二个重载。 但编译器无法在第一个和第二个重载之间解析。 每个重载都有一个定义的参数类型,该类型会扩大到其他重载中的相应类型(Byte
到 Short
,Single
到 Double
)。 因此,编译器将生成重载决策错误。
正确方法:若要保证准确无误地调用重载程序,请使用 CType 函数将实参数据类型与形参类型匹配。 以下示例显示了对 z
的调用,该调用强制决策到第二个重载。
Call z(CType(p, Short), CType(q, Single))
具有 Optional 和 ParamArray 参数的重载决策
如果程序的两个重载具有相同的签名,则除了最后一个参数在一个重载中声明为 Optional,在另一个重载中声明为 ParamArray 外,编译器会根据最接近的匹配来解析对该程序的调用。 有关详细信息,请参阅 Overload Resolution。