[英]Assigning multi cell range values to a variable
我对以下陈述感到困惑
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声明为variant时代码如何正常运行,但是将其声明为数组时却给我错误。
tl; dr-返回类型定义没有为编译器提供足够的信息来确定如何分配内存或如何确定在编译时进行转换的偏移量。
为了理解为什么这些分配不兼容,它有助于理解VBA用于表示每个分配的基础数据结构。
Range
的.Value
属性返回Variant
。 存储在类型Variant
是由在小区的数目来确定Range
。 如果有多于一个的小区时,它返回一个Variant
包含的阵列Variant
。 请注意, 必须返回Variant
否则,只要您需要单个单元格中的值,就需要索引到数组中。
VBA是一种基于COM的语言,因此,当您将某些内容声明为Variant
,它将存储在COM VARIANT结构中,该结构由VARTYPE组成,该VARTYPE描述所包含的数据以及指向基础数据的指针(类型中以星号开头的类型)联合)或数据本身(联合中未加星号的类型)。 在内存中,它看起来像这样:
因此,当您使用赋值x = Range("A1:C3").Value
,您将获得一个描述了Variant
数组的VARTYPE。 这很重要,如下所示。
如果Range
仅具有一个单元格,则您还将获得Variant
,但它包含基础类型- 而不是数组。
在VBA中声明数组时,该数组存储在COM SAFEARRAY结构中,该结构以使该数组可被其他COM客户端使用的方式描述该数组。 在内存中看起来像这样(请注意,这是一个一维数组-SAFEARRAYBOUND实际上是一个数组,其中包含cDim中的元素数):
这基本上就是用声明Dim x(1 To 3, 1 To 3) As Integer
(除了最后会有2个SAFEARRAYBOUNDs)。
请注意,这两种数据类型之间有2个非常重要的区别。 宽松类型的声明Dim x As Variant
允许运行时确定数据区域中包含的内容。 对于Range.Value
赋值,您将获得一个Variant
类型为Variant
的数组,这是一个兼容类型(这也是为什么Dim x() As Variant
会编译的原因)。 声明Dim y(1 To 3, 1 To 3) As Integer
在编译时固定 。 更重要的是,由于内存中SAFEARRAY结构的大小由维数决定,因此编译器可以在编译时分配内存。 但是,不能为COM调用返回的任意SAFEARRAY结构分配的内存量。 此外,指向的存储区域的大小由所包含类型的字节长度和元素总数决定。 编译器通过禁止分配来防止不匹配的可能性。
实际上,这很可能是您无法直接获取指向SAFEARRAY的指针的原因(唯一的方法是将其转换为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
您的第一个声明是Variant
数组,其中每个元素长12个字节。 您的第二个声明是一个Integer
数组,每个元素的长度为2个字节。 在编译时,不能简单地确定返回的存储区的长度或适当的转换。 VBA正在保护您免受访问冲突和/或运行时错误强制转换。
如果我正确理解了您的查询,则原因非常简单。
要将数据从工作表传输到数组,需要调整大小。 在您的示例中,它是固定的。
尝试这个
Dim x() As Variant
ReDim x(1 To 3, 1 To 3)
x = Range("A1:C3").Value
现在为什么要使用Variant
而不是String
? 因为您不知道单元格内容的数据类型是什么。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.