【问题标题】:Populating VBA dynamic arrays填充 VBA 动态数组
【发布时间】:2012-02-09 16:20:26
【问题描述】:

以下代码给出错误 9“下标超出范围”。我的意思是声明一个动态数组,以便在向其中添加元素时尺寸会发生变化。像在 JS 中那样在数组中存储一些东西之前,我是否必须在数组上创建一个“点”?

Sub test_array()
    Dim test() As Integer
    Dim i As Integer
    For i = 0 To 3
        test(i) = 3 + i
    Next i
End Sub

【问题讨论】:

    标签: arrays vba


    【解决方案1】:

    在你的 for 循环中使用数组上的 Redim,如下所示:

    For i = 0 to 3
      ReDim Preserve test(i)
      test(i) = 3 + i
    Next i
    

    【讨论】:

    • 你为什么要在in循环中这样做? ReDim,尤其是当您添加 Preserve 时,是潜在的性能杀手。你知道循环要迭代多少次,所以一定要在循环之外进行。然后你只需要调整数组的大小一次,你不需要Preserve
    • @CodyGray 如果在进入循环时已经定义了最终数组大小,那么您是绝对正确的。将 Redim 放入循环中将成为性能杀手。但是,我假设数组的大小不是在进入循环时确定的。否则整个样本根本没有意义......
    • 必须在进入循环时被定义。您必须定义循环的范围。即使它是一个变量而不是像3 这样的常量,您仍然在For 语句中指定一个上限。使用它来动态初始化数组的大小。
    • 您可以使用“Exit For”离开阵列
    • 也许这个解决方案在优化方面不是很好,但如果你不知道前面的大小 - 例如在某些条件下进行 while 循环 - 那么这可能是最简单的解决方案
    【解决方案2】:

    正如 Cody 和 Brett 所提到的,您可以通过合理使用 Redim Preserve 来减少 VBA 减速。 Brett 建议 Mod 这样做。

    您也可以使用用户定义的TypeSub 来执行此操作。考虑下面的代码:

    Public Type dsIntArrayType
       eElems() As Integer
       eSize As Integer
    End Type
    
    Public Sub PushBackIntArray( _
        ByRef dsIntArray As dsIntArrayType, _
        ByVal intValue As Integer)
    
        With dsIntArray
        If UBound(.eElems) < (.eSize + 1) Then
            ReDim Preserve .eElems(.eSize * 2 + 1)
        End If
        .eSize = .eSize + 1
        .eElems(.eSize) = intValue
        End With
    
    End Sub
    

    仅当大小翻倍时才调用ReDim Preserve。成员变量eSize 跟踪eElems 的实际数据大小。当最终数组长度在运行时才知道时,这种方法帮助我提高了性能。

    希望这对其他人也有帮助。

    【讨论】:

    • 是的!也想为其他读者提供另一种选择。我意识到 op 已经接受了以前的答案。谢谢。
    • 那么这里有一个 +1 给你,有趣的选择! :)
    • +1。这类似于在 C++ 标准库中实现动态数组的方式。出于封装目的,我会将 push_back 设为方法,将 type 设为类。
    • 第一次调用PushBackIntArray 时,UBound(.eElems) 出现下标超出范围错误...有什么想法吗?
    【解决方案3】:

    是的,您正在寻找 ReDim 语句,该语句动态分配数组中所需的空间量。

    以下陈述

    Dim MyArray()
    

    声明一个没有维度的数组,所以编译器不知道它有多大,也不能在里面存储任何东西。

    但是您可以使用ReDim 语句来调整数组的大小:

    ReDim MyArray(0 To 3)
    

    如果您需要在保留其内容的同时调整数组的大小,您可以使用Preserve 关键字和ReDim 语句:

    ReDim Preserve MyArray(0 To 3)
    

    但请注意ReDim 和特别是ReDim Preserve 都具有很高的性能成本。如果可能的话,尽量避免在一个循环中一遍又一遍地这样做;你的用户会感谢你的。


    但是,在您的问题中显示的简单示例中(如果它不仅仅是一次性示例),您根本不需要ReDim。只需声明具有明确尺寸的数组:

    Dim MyArray(0 To 3)
    

    【讨论】:

    • +1 虽然我想补充一点,我认为应该明确指定下限:ReDim MyArray(0 To 3)
    • @Jean:这是个好建议。很多人都被 VB(A) 支持 0 以外的下限这一事实所困扰。明确总是好的做法。
    【解决方案4】:

    除了 Cody 有用的 cmets 之外,值得注意的是,有时您不知道数组应该有多大。这种情况下的两个选项是

    1. 创建一个足够大的数组来处理你认为会被抛出的任何东西
    2. 明智使用Redim Preserve

    下面的代码提供了一个例程示例,该例程将根据lngSize 变量确定myArray 的维度,然后通过使用Mod 测试添加其他元素(等于初始数组大小)即将超出界限

    Option Base 1
    
    Sub ArraySample()
        Dim myArray() As String
        Dim lngCnt As Long
        Dim lngSize As Long
    
        lngSize = 10
        ReDim myArray(1 To lngSize)
    
        For lngCnt = 1 To lngSize*5
            If lngCnt Mod lngSize = 0 Then ReDim Preserve myArray(1 To UBound(myArray) + lngSize)
            myArray(lngCnt) = "I am record number " & lngCnt
        Next
    End Sub
    

    【讨论】:

      【解决方案5】:

      我看到上面的许多(所有)帖子都依赖于LBound/UBound 调用但可能未初始化的 VBA 动态数组,是什么导致应用程序不可避免的死亡......

      乱码:

      Dim x As Long Dim arr1() As SomeType ... x = UBound(arr1) 'crashes

      正确代码:

      Dim x As Long Dim arr1() As SomeType ... ReDim Preserve arr1(0 To 0) ... x = UBound(arr1)

      ... 即任何代码,其中Dim arr1() 后面紧跟LBound(arr1)/UBound(arr1) 调用,而两者之间没有ReDim arr1(...),崩溃。回旋处是使用On Error Resume Next 并在LBound(arr1)/UBound(arr1) 调用之后立即检查Err.Number - 如果数组已初始化,则它应该为0,否则为非零。由于存在一些 VBA 内置错误行为,因此需要进一步检查数组的限制。详细解释大家看Chip Pearson's website(应该是VBA智慧的人类宝藏...)

      嘿,这是我的第一篇文章,相信它是清晰的。

      【讨论】:

      • 上面依赖 ubound/lbound 重新编码的评论不正确
      猜你喜欢
      • 2017-05-20
      • 1970-01-01
      • 2015-08-15
      • 1970-01-01
      • 2021-04-21
      • 1970-01-01
      • 2017-10-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多