创建一个空的二维数组

6
我不喜欢未初始化的VBA数组,因为每次使用UBound()For Each之前都需要检查数组是否已初始化,以避免异常,并且没有本地VBA函数可以检查它。这就是为什么我初始化数组,至少要用a = Array()将它们置为空。这在大多数情况下消除了额外检查的必要性,因此1d数组没有问题。
出于同样的原因,我尝试创建一个空的2d数组。不能简单地执行ReDim a(0 To -1, 0 To 0)、转置1d空数组或类似的操作。我偶然发现的唯一方法是使用MSForms.ComboBox,将空数组分配给.List属性并读取回来。以下是示例,在Excel和Word中可行,您需要向VBA项目中插入UserForm,将ComboBox放置在其中,并添加以下代码:
Private Sub ComboBox1_Change()

    Dim a()
    ComboBox1.List = Array()
    a = ComboBox1.List
    Debug.Print "1st dimension upper bound = " & UBound(a, 1)
    Debug.Print "2nd dimension upper bound = " & UBound(a, 2)

End Sub

组合框改变后的输出为:

1st dimension upper bound = -1
2nd dimension upper bound = 0

实际上,这真的是调试中的空2d数组:

locals window

有没有更优雅的方法创建一个空的二维数组,而不使用ComboBox或一般的UserForm控件?


6
我不知道答案,但我找到了一个很棒的方法来崩溃Excel。我想知道这是否会影响其他人。请尝试在子程序中仅写入以下一行(先保存您的工作):a=Array();redim a(,0 to 0) - JNevill
2
你现在让我玩这个了。 - user1274820
2
我认为这是一个 XY 问题。你应该问的是“如何轻松检查数组是否已初始化?”CPearsom.com 这里提供了一个好的解决方案。 - chris neilsen
1
我必须承认,这个问题本身可能听起来相当广泛,因此很容易被视为XY问题。请注意,我已经发布了链接如何检查数组是否初始化,在我看来,类似OERN的方法必须是最后的手段,唯一使用它们的借口是显著的简单性和性能优势。而真正的重点是改变2D数组处理模式并摆脱额外的检查。你知道,“如果一开始的想法不荒谬,那就没有希望了。” - omegastripes
1
昨晚我在这个问题上瞎搞了一通,但最终没有结果。我认为发生的情况是你碰巧遇到了一个罕见的情况,VBA能够返回一个维度为(0到-1, 0到0)的数组,并且无法通过任何非hacky的方式重复。我唯一的想法是在C#中构建一个自定义库,并在你的代码中"引用"它。然而,如果你想分发这个表格,这并不是一个好的解决方案,而且我只是猜测你是否能够在自定义库中完成这个操作。 - JNevill
显示剩余5条评论
2个回答

5

这只适用于Windows系统(不适用于Mac):

Option Explicit

#If Mac Then
#Else
    #If VBA7 Then
        Private Declare PtrSafe Function SafeArrayCreate Lib "OleAut32.dll" (ByVal vt As Integer, ByVal cDims As Long, ByRef rgsabound As SAFEARRAYBOUND) As LongPtr
        Private Declare PtrSafe Function VariantCopy Lib "OleAut32.dll" (pvargDest As Any, pvargSrc As Any) As Long
        Private Declare PtrSafe Function SafeArrayDestroy Lib "OleAut32.dll" (ByVal psa As LongPtr) As Long
    #Else
        Private Declare Function SafeArrayCreate Lib "OleAut32.dll" (ByVal vt As Integer, ByVal cDims As Long, ByRef rgsabound As SAFEARRAYBOUND) As Long
        Private Declare Function VariantCopy Lib "OleAut32.dll" (pvargDest As Variant, pvargSrc As Any) As Long
        Private Declare Function SafeArrayDestroy Lib "OleAut32.dll" (ByVal psa As Long) As Long
    #End If
#End If

Private Type SAFEARRAYBOUND
    cElements As Long
    lLbound As Long
End Type
Private Type tagVariant
    vt As Integer
    wReserved1 As Integer
    wReserved2 As Integer
    wReserved3 As Integer
    #If VBA7 Then
        ptr As LongPtr
    #Else
        ptr As Long
    #End If
End Type

Public Function EmptyArray(ByVal numberOfDimensions As Long, ByVal vType As VbVarType) As Variant
    'In Visual Basic, you can declare arrays with up to 60 dimensions
    Const MAX_DIMENSION As Long = 60
    
    If numberOfDimensions < 1 Or numberOfDimensions > MAX_DIMENSION Then
        Err.Raise 5, "EmptyArray", "Invalid number of dimensions"
    End If

    #If Mac Then
        Err.Raise 298, "EmptyArray", "OleAut32.dll required"
    #Else
        Dim bounds() As SAFEARRAYBOUND
        #If VBA7 Then
            Dim ptrArray As LongPtr
        #Else
            Dim ptrArray As Long
        #End If
        Dim tVariant As tagVariant
        Dim i As Long
        '
        ReDim bounds(0 To numberOfDimensions - 1)
        '
        'Make lower dimensions [0 to 0] instead of [0 to -1]
        For i = 1 To numberOfDimensions - 1
            bounds(i).cElements = 1
        Next i
        '
        'Create empty array and store pointer
        ptrArray = SafeArrayCreate(vType, numberOfDimensions, bounds(0))
        '
        'Create a Variant pointing to the array
        tVariant.vt = vbArray + vType
        tVariant.ptr = ptrArray
        '
        'Copy result
        VariantCopy EmptyArray, tVariant
        '
        'Clean-up
        SafeArrayDestroy ptrArray
    #End If
End Function

现在你可以创建具有不同维度和数据类型的空数组:

Sub Test()
    Dim arr2D() As Variant
    Dim arr4D() As Double
    '
    arr2D = EmptyArray(2, vbVariant)
    arr4D = EmptyArray(4, vbDouble)
    Stop
End Sub

更新于2022年9月30日

我在我的GitHub MemoryTools 库中创建了一个名为EmptyArray的方法(与原来的签名相同)。该版本可以在Windows和Mac上工作。


3

我不知道,伙计 - 我觉得你偶然找到这个属性还是挺惊险的。

我可能会在这里停下来,只是说:

Function Empty2DArray() As Variant
With CreateObject("Forms.ComboBox.1")
    .List = Array()
    Empty2DArray = .List
End With
End Function

使用方式如下:a = Empty2DArray

你不需要创建用户窗体或组合框 - 可以直接使用CreateObject

但是正如其他人所说,当检查数组是否已初始化时,可能更有意义的是进行错误处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接