将多单元格范围值分配给变量

时间:2016-07-31 15:38:19

标签: arrays vba excel-vba excel

我对以下陈述感到困惑

Dim x as variant
x=Range("A1:C3").value

使用上面的语句之后我们可以使用x作为二维数组,但是如果我们将x声明为如下所示的数组

Dim x(1 to 3,1 to 3) as integer
x=Range("A1:C3").value 

然后使用上面的statemnet给出了编译时错误,说明Can't assign to an array

我怀疑当x被声明为变量时代码运行良好但在声明为数组时给出错误。

2 个答案:

答案 0 :(得分:3)

tl; dr - 编译器的返回类型定义没有提供足够的信息来确定如何分配内存或如何在编译时确定转换的偏移量。

为了理解为什么这些赋值不兼容,有助于理解VBA用于表示每个赋值的基础数据结构。

.Value的{​​{1}}属性返回Range。存储在Variant中的类型由Variant 中的单元格数决定。如果有多个单元格,则会返回包含Range数组的Variant。请注意, 必须 返回Variant - 否则,只要您想要来自单个单元格的值,就需要索引到数组中。

VBA是一种基于COM的语言,因此当您将某些内容声明为Variant时,它会存储在由VARIANT组成的COM VARTYPE结构中,该结构描述所包含的数据和指向基础数据的指针(联合中带有星号的类型)或数据本身(联合中不带星号的类型)。在内存中,它看起来像这样:

Variant in memory

因此,当您使用作业Variant时,您将获得一个描述x = Range("A1:C3").Value数组的VARTYPE。这很重要,如下所示。

如果Variant只有一个单元格,那么您还会获得Range,但它包含基础类型 - 数组。< / p>

在VBA中声明一个数组时,它存储在一个COM SAFEARRAY结构中,该结构以一种使其可供其他COM客户端使用的方式来描述该数组。在内存中它看起来像这样(请注意,这是一维数组 - 最后的SAFEARRAYBOUND实际上是一个包含cDim中元素数量的数组):

SafeArray in memory

这基本上就是你使用声明Variant得到的结果(除了最后会有2个SAFEARRAYBOUND)。

请注意,2种数据类型之间存在2个非常重要的差异。松散类型的声明Dim x(1 To 3, 1 To 3) As Integer允许运行时确定数据区域中包含的内容。对于Dim x As Variant赋值,您将获得Range.Value数组的VARTYPE,这是兼容类型(这也是Variant将编译的原因)。声明Dim x() As Variant在编译时 已修复 。更重要的是,由于内存中SAFEARRAY结构的大小由维度数决定,因此编译器可以在编译时分配内存。但是,为COM调用返回的任意SAFEARRAY结构分配的内存量不能。此外,指向的存储区的大小由所包含类型的字节长度和元素总数确定。编译器通过禁止分配来防止不匹配的可能性。

实际上,这可能是您无法直接获取指向SAFEARRAY的指针的原因(唯一的方法是转换为Dim y(1 To 3, 1 To 3) As Integer并手动取消引用指针#&1&# 39; s数据区域):

Variant

因此,将其分解,您无法执行此操作,因为编译器没有足够的信息来安全地进行运行时转换。如果你想在引擎盖下做一点点,这段代码演示了幕后发生的事情:

Dim x(1 To 3, 1 To 3) As Integer
Debug.Print VarPtr(x)   '<- Type mismatch.

您的第一个声明是Public Declare Sub CopyMemory Lib "kernel32" Alias _ "RtlMoveMemory" (Destination As Any, Source As Any, _ ByVal length As Long) Public Type ComVariant VarType As Integer Reserved1 As Integer Reserved2 As Integer Reserved3 As Integer DataArea As Long End Type Public Sub ExamineVariables() Dim x As Variant x = Range("A1:C3").Value Dim testV As ComVariant CopyMemory testV, x, LenB(testV) Debug.Print testV.VarType '= 8204 = 0x200C = VT_ARRAY & VT_VARIANT Debug.Print testV.DataArea 'Varies - is a SafeArray pointer. Dim y(1 To 3, 1 To 3) As Integer View2dArrayType y End Sub Public Sub View2dArrayType(vbArray As Variant) Dim testV As ComVariant 'The VT_BYREF can be ignored - it is an artifact of the cast to Variant. CopyMemory testV, vbArray, LenB(testV) Debug.Print testV.VarType '= 24578 = 0x6002 = VT_ARRAY & VT_BYREF & VT_I2 End Sub 数组,其中每个元素长度为12个字节。您的第二个声明是Variant数组,其中每个元素的长度为2个字节。在编译时,返回的内存区域的长度和适当的强制转换都不能可靠地确定。 VBA正在保护您免受访问冲突和/或运行时错误转换。

答案 1 :(得分:1)

如果我已正确理解您的查询,那么原因很简单。

要将数据从工作表传输到数组,需要调整大小。在您的示例中,它已修复。

试试这个

Dim x() As Variant

ReDim x(1 To 3, 1 To 3)

x = Range("A1:C3").Value

现在为什么是Variant而不是String?因为您不知道单元格内容的数据类型是什么。