【问题标题】:Preventing extra elements in VBA dynamic arrays防止 VBA 动态数组中的额外元素
【发布时间】:2010-09-17 18:23:41
【问题描述】:

正如标题所述,有没有办法防止额外元素在非零基时出现在 VBA 动态数组中?

例如,当使用类似于以下的代码时:

While Cells(ndx, 1).Value <> vbNullString
    ReDim Preserve data(1 To (UBound(data) + 1))
    ndx = ndx + 1
Wend

在处理结束时,您有一个额外的空数组元素。虽然这可以通过以下方式消除:

ReDim Preserve data(1 To (UBound(data) - 1))

这似乎不是解决此问题的最佳方法。

因此,有没有办法一开始就阻止创建额外的元素?最好是在循环内部不需要额外逻辑的东西。

【问题讨论】:

    标签: arrays excel optimization vba vb6


    【解决方案1】:

    这个问题被问到已经有一段时间了;但是,我今天遇到了同样的问题并这样解决了:

    有一种方法可以做到这一点,即定义一个永远不会使用的第一个元素。如果你需要一个从零开始的数组,你可以像这样定义一个空数组

    Dim data()
    Dim i as Long
    
    ReDim data(-1 To -1) ' Empty array. We never use data(-1).
    
    For i = 0 To UBound(data)
        ...
    Next i
    

    如果你需要一个基于 1 的数组,你会这样做

    ReDim data(0 To 0) ' Empty array. We never use data(0).
    
    For i = 1 To UBound(data)
        ...
    Next i
    

    【讨论】:

      【解决方案2】:

      VB6 和 COM 混合使用基于 0 和 1 的索引(数组是基于 0 的,除非您使用 Option Base 进行更改或以其他方式明确声明)。

      COM 集合在早期的 COM 对象模型中通常从 1 开始,但有时从 0 开始...

      Dim/Redim data(1 To N) 是一个包含从 1 到 N 索引的 N 个元素的数组

      Dim/Redim data(0 to N-1) 是一个包含从 0 到 N-1 索引的 N 个元素的数组

      Dim/Redim data(N) 是从 0 到 N 索引的 N+1 个元素的数组(如果 Option Base 为 0)

      最后一种情况有时会让人混淆,data(N) 通常表示 data(0 To N),它是 N+1 个元素的数组。

      就我个人而言,我总是将数组显式声明为 (0 To N-1),并且不依赖 Option Base,这对于使用一种以上语言的开发人员来说更熟悉。

      有一种极端情况:VBA 不支持零长度数组,您必须始终至少有一个元素(对于多维数组中的每个维度)。因此,您可以声明的最小数组是具有一个元素的 data(0 To 0) 或 data(1 To 1)。

      在您的情况下,我怀疑您正在创建一个包含一个元素的数组,然后每次通过循环添加一个元素:

      ReDim data(1 To 1)    
      While Cells(ndx, 1).Value <> vbNullString    
          ReDim Preserve data(1 To (UBound(data) + 1))    
          ndx = ndx + 1
      Wend
      

      相反(暂时不考虑在循环中调用 ReDim Preserve 的效率),您应该使用:

      ReDim data(1 To 1)    
      nStartIndex = ndx
      While Cells(ndx, 1).Value <> vbNullString    
          ' On the first iteration this does nothing because
          ' the array already has one element
          ReDim Preserve data(1 To ndx - nStartIndex + 1)    
          ndx = ndx + 1
      Wend
      

      【讨论】:

        【解决方案3】:

        因此,这已成为一个烦人的小问题,因为看起来确实没有办法阻止该问题的出现。根据其他用户提供的答案,我厌倦了以下解决问题的方法。

        Using a Collection - 虽然这种方法在您需要读取和存储数据的情况下非常有效,但您不能将用户定义的类型与 Collection 一起使用。能够定义项目键很有用,因为您可以使用它来交叉引用两个集合;但是,在 VBA 中,no way to get the list of keys in the Collection 可能会受到限制。

        Reading an Excel Range into an Array - 另一种非常好的方法,但是当您提前知道范围将是什么时,它似乎效果最好。如果您必须即时计算范围,那么您可能会发现自己处于使用 Collection 或较小的 ReDim 数组更容易使用的情况。

        使用 ReDim Preserve 动态构建阵列 - 虽然这可能是一个相当简单的操作,但它涉及两个问题。一个是ReDim 可能是一项代价高昂的操作,因为 Visual Basic 实际上会创建一个具有给定大小的新数组,复制旧数组,然后删除旧数组(或为 Visual Basic .NET 中的垃圾收集器释放它)。因此,如果您要使用非常大的数组,您会希望尽量减少对 ReDim 的调用。

        此外,您可能会遇到类似于问题中的情况,即您在数组的开头或数组的末尾有一个空的额外元素。解决此问题的唯一方法似乎是在执行操作之前检查是否需要调整大小,或者在返回结果之前删除空元素。

        【讨论】:

          【解决方案4】:

          当然,自从我完成经典的 VB6 以来已经有一段时间了,而且我的 VBA 体验更加生疏……如果内存服务,VB 的数组语法不同,不仅仅是基数是 1 而不是 0。该语法还表示,您指定的“大小”并不表示数组中元素的总数,而是表示可寻址的最后一个索引。

          【讨论】:

            【解决方案5】:

            Visual Basic 数组是从零开始的。不过,这可以通过Option Base 语句进行更改。

            对于你的数组,额外的元素是因为你做了一个UBound() + 1,UBound 已经给你正确的数字了。如果数组有 5 个元素,UBound 将为 5。但最后一个索引将为 4,因此 ReDim to UBound 将为您提供一个大小为 +1 的数组。

            由于数组无论如何都很难使用(即在 VBA 中),并且ReDim Preserve 实际上是对新的固定大小数组的数组复制操作,因此我建议您尽可能使用 Collections。它们更容易迭代 (For Each ... In ...),并且在添加、查找和删除元素方面效率更高。

            ' creation ' 
            Dim anyValue as Variant
            Dim c as New Collection
            
            ' and adding values '
            c.Add anyValue, strKey
            
            ' iteration '
            For Each anyValue in c
              Debug.Print anyValue
            Next c
            
            ' count values '
            Debug.Print c.Count
            
            ' element deletion '
            c.Delete strKey
            

            (您可以使用 VBScript 中的 Scripting.Dictionary 来增加舒适度,但您需要先引用它。)

            您也可以拥有多个维度的集合,只需将它们相互放在一起即可。

            【讨论】:

            • 谢谢,我忘记了 VBA 中的集合,它们在这方面的工作比动态数组好得多;但是,当使用 Collection 有点矫枉过正时,有没有办法避免出现这些额外的元素?
            • 什么额外的元素?不会有额外的元素。您可以在需要时向集合中添加一些东西,无需预先选择尺寸(如果您这么认为的话)。
            • @Tomalak - 在将 ReDim Preserve 用于动态数组时,不在集合中。
            • VBA 中没有“动态数组”之类的东西。您总是在使用 ReDim 创建原件的全新副本。我在我的回答中指出了您的 ReDim 数学在哪里出错。 :-)
            • @Tomalak - 我正在处理的 VBA 代码没有使用从零开始的数组,因此执行 UBound 在这里实际上不起作用。此外,我想不出一个更好的名称来调整数组的大小,然后称它们为“动态数组”——这就是 ReDim 或多或少允许你做的事情。
            【解决方案6】:

            ReDim Preserve 是一个相对昂贵的操作,可能不是每次迭代都要做的事情。最好将数组ReDim 设置为比您需要的更大的上限(可能是块),然后在完成后将数组减少到所需的上限。

            此外,您可能想研究将 Excel 范围读入数组的其他方法,例如

              Dim a()
              With Sheet1
                a = .Range(.Range("A1"), .Range("A1").End(xlDown)).Value
              End With
              Debug.Print a(1, 1)
            

            循环通常很慢:)

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-04-16
              • 2013-04-19
              • 2014-08-15
              • 2010-12-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多