1

正如标题所述,有没有办法防止额外的元素在非零基时出现在 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))

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

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

4

6 回答 6

3

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

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

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

循环通常很慢:)

于 2008-10-17T15:19:34.893 回答
1

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

对于你的数组,额外的元素是因为你做了 a 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 来增加舒适度,但您需要先引用它。)

您还可以通过简单地将它们放在彼此内部来拥有多个维度的集合。

于 2008-10-17T14:54:02.497 回答
1

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
于 2008-10-17T17:42:43.233 回答
1

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

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

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

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

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

于 2008-10-17T19:34:40.170 回答
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
于 2012-03-19T16:43:16.970 回答
0

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

于 2008-10-17T17:54:42.727 回答