简体   繁体   English

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

[英]Assigning multi cell range values to a variable

I have a confusion about below statements 我对以下陈述感到困惑

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

After using above statement we can use x as a two dimensional array but if we declare x as an array like below 使用上面的语句后,我们可以将x用作二维数组,但是如果我们将x声明为如下数组

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

Then using the above statemnet gives me compile time error stating Can't assign to an array . 然后使用上面的statemnet给我编译时错误,指出Can't assign to an array

My doubt is how the code runs fine when x is declared as variant but gives me error when it is declared as an array. 我的疑问是,将x声明为variant时代码如何正常运行,但是将其声明为数组时却给我错误。

tl;dr - There isn't enough information provided by the return type definition for the compiler to determine how allocate memory or how to make determine offsets for casting at compile time. tl; dr-返回类型定义没有为编译器提供足够的信息来确定如何分配内存或如何确定在编译时进行转换的偏移量。

In order to understand why these assignments are not compatible, it helps to understand the underlying data structures that VBA is using to represent each of them. 为了理解为什么这些分配不兼容,它有助于理解VBA用于表示每个分配的基础数据结构。

The .Value property of a Range returns a Variant . Range.Value属性返回Variant The type stored in the Variant is determined by the number of cells in that Range . 存储在类型Variant是由小区的数目来确定Range If there's more than one cell, it returns a Variant containing an array of Variant . 如果有多于一个的小区时,它返回一个Variant包含的阵列Variant Note that is has to return a Variant - otherwise you would need to index into an array any time you wanted the value from a single cell. 请注意, 必须返回Variant否则,只要您需要单个单元格中的值,就需要索引到数组中。

VBA is a COM based language, so when you declare something as a Variant , it is stored in a COM VARIANT structure that consists of a VARTYPE the describes the contained data and either a pointer to the underlying data (types preceded with an asterisk in the union) or the data itself (types not preceded with a asterisk in the union). VBA是一种基于COM的语言,因此,当您将某些内容声明为Variant ,它将存储在COM VARIANT结构中,该结构由VARTYPE组成,该VARTYPE描述所包含的数据以及指向基础数据的指针(类型中以星号开头的类型)联合)或数据本身(联合中未加星号的类型)。 In memory, it looks like this: 在内存中,它看起来像这样:

内存变化

So, when you use the assignment x = Range("A1:C3").Value , you get a VARTYPE that describes an array of Variant . 因此,当您使用赋值x = Range("A1:C3").Value ,您将获得一个描述了Variant数组的VARTYPE。 This is important, as you'll see below. 这很重要,如下所示。

If the Range only has one cell you also get a Variant , but it contains the underlying type - not an array. 如果Range仅具有一个单元格,则您还将获得Variant ,但它包含基础类型- 而不是数组。

When you declare an array in VBA, it is stored in a COM SAFEARRAY structure that describes the array in a way that makes it usable by other COM clients. 在VBA中声明数组时,该数组存储在COM SAFEARRAY结构中,该结构以使该数组可被其他COM客户端使用的方式描述该数组。 In memory it looks like this (note that this is a one dimensional array - the SAFEARRAYBOUND at the end is actually an array with the number of elements in cDim): 在内存中看起来像这样(请注意,这是一个一维数组-SAFEARRAYBOUND实际上是一个数组,其中包含cDim中的元素数):

内存中的SafeArray

That is basically what you get with the declaration Dim x(1 To 3, 1 To 3) As Integer (except there will be 2 SAFEARRAYBOUNDs at the end). 这基本上就是用声明Dim x(1 To 3, 1 To 3) As Integer (除了最后会有2个SAFEARRAYBOUNDs)。

Note that there are 2 very important differences between the 2 data types. 请注意,这两种数据类型之间有2个非常重要的区别。 The loosely typed declaration Dim x As Variant allows the runtime to determine what is contained in the data area. 宽松类型的声明Dim x As Variant允许运行时确定数据区域中包含的内容。 In the case of the Range.Value assignment, you'll get a VARTYPE of array of Variant which is a compatible type (which is also why Dim x() As Variant will compile). 对于Range.Value赋值,您将获得一个Variant类型为Variant的数组,这是一个兼容类型(这也是为什么Dim x() As Variant会编译的原因)。 The declaration Dim y(1 To 3, 1 To 3) As Integer is fixed at compile time . 声明Dim y(1 To 3, 1 To 3) As Integer 在编译时固定 More importantly, since the size of a SAFEARRAY structure in memory is determined by the number of dimensions, the memory can be allocated by the compiler at compile time. 更重要的是,由于内存中SAFEARRAY结构的大小由维数决定,因此编译器可以在编译时分配内存。 However, the amount of memory allocated for an arbitrary SAFEARRAY structure returned by a COM call cannot. 但是,不能为COM调用返回的任意SAFEARRAY结构分配的内存量。 Furthermore, the size of the memory area pointed at is determined by the byte length of the contained type and the total number of elements. 此外,指向的存储区域的大小由所包含类型的字节长度和元素总数决定。 The compiler protects against the possibility of a mismatch by disallowing the assignment. 编译器通过禁止分配来防止不匹配的可能性。

In fact, this is likely the reason why you can't directly obtain a pointer to a SAFEARRAY (the only way to do so is to cast to a Variant and manually dereference the pointer from it's data area): 实际上,这很可能是您无法直接获取指向SAFEARRAY的指针的原因(唯一的方法是将其转换为Variant并从其数据区域手动取消引用该指针):

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

So breaking it down, you can't do this because the compiler doesn't have enough information to safely make the run-time cast. 因此,将其分解,您将无法执行此操作,因为编译器没有足够的信息来安全地进行运行时强制转换。 If you want to do a little poking under the hood, this code demonstrates what is happening under the hood: 如果您想在后台进行一些戳操作,则以下代码演示了正在发生的情况:

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

Your first declaration is an array of Variant , where each element is 12 bytes long. 您的第一个声明是Variant数组,其中每个元素长12个字节。 Your second declaration is an array of Integer where each element is 2 bytes long. 您的第二个声明是一个Integer数组,每个元素的长度为2个字节。 Neither the length of the returned memory area simply nor the appropriate cast can reliably determined at compile time. 在编译时,不能简单地确定返回的存储区的长度或适当的转换。 VBA is protecting you from access violations and/or runtime bad casts. VBA正在保护您免受访问冲突和/或运行时错误强制转换。

If I have understood your query correctly then the reason is very simple. 如果我正确理解了您的查询,则原因非常简单。

To transfer the data to an array from a worksheet it needs to be resizable. 要将数据从工作表传输到数组,需要调整大小。 In your example, it is fixed. 在您的示例中,它是固定的。

Try this 尝试这个

Dim x() As Variant

ReDim x(1 To 3, 1 To 3)

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

Now why a Variant and not a String ? 现在为什么要使用Variant而不是String Because you do not know what is the data type of the cell contents. 因为您不知道单元格内容的数据类型是什么。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM