2016-07-31 9 views
2

У меня есть путаница относительно ниже утвержденийПрисвоение значений мульти диапазона ячеек переменных

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

После использования выше заявления мы можем использовать х, как двумерный массив, но если мы объявляем е в виде массива, как показано ниже

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

После этого, используя приведенную выше statemnet, я дам ошибку времени компиляции, указав Can't assign to an array.

Мое сомнение в том, как код работает нормально, когда x объявлен как вариант, но дает мне ошибку, когда он объявлен как массив.

+0

Зачем вам *** ожидать, что *** сможет назначить «Variant», содержащий 2d-массив, который можно назначить 1-му массиву «Integer»? – Comintern

+0

My bad, x должен был быть объявлен как двухмерный массив, но даже после этого я получаю ту же ошибку. –

+0

Почему вы ожидаете, что сможете назначить «Variant», содержащий 2d-массив, который можно назначить ** * 2d массив *** 'Integer'? – Comintern

ответ

1

Если я правильно понял ваш запрос, причина очень проста.

Чтобы передать данные в массив из листа, он должен быть изменен. В вашем примере это исправлено.

Попробуйте

Dim x() As Variant 

ReDim x(1 To 3, 1 To 3) 

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

Теперь почему Variant и не String? Потому что вы не знаете, что такое тип данных содержимого ячейки.

3

tl; dr - Недостаточно информации, предоставленной определением типа возврата для компилятора, чтобы определить, как распределить память или как определить смещения для кастинга во время компиляции.

Чтобы понять, почему эти назначения несовместимы, это помогает понять основные структуры данных, которые VBA использует для представления каждого из них.

.Value Недвижимость Range возвращает Variant. Тип, хранящийся в Variant, определяется количеством ячеек в, которое Range. Если имеется более одной ячейки, она возвращает Variant, содержащий массив Variant. Обратите внимание, что это имеет значение return a Variant - в противном случае вам нужно будет индексировать массив в любое время, когда вы хотите получить значение из одной ячейки.

VBA является COM на основе языка, поэтому, когда вы заявляете что-то как Variant, она хранится в COM VARIANT структуру, которая состоит из VARTYPE теге описывает содержащиеся на нем данные и либо указатель на исходных данных (типы предшествуют звездочку в союзе) или сами данные (типы, которым не предшествует звездочка в объединении). В памяти, это выглядит следующим образом:

Variant in memory

Таким образом, когда вы используете назначение x = Range("A1:C3").Value, вы получите VARTYPE, который описывает массив Variant. Это важно, как вы увидите ниже.

Range Если имеет только одну ячейку вы также получаете Variant, но она содержит базовый тип - не массива.

Когда вы объявляете массив в VBA, он сохраняется в структуре COM SAFEARRAY, которая описывает массив таким образом, который делает его пригодным для использования другими COM-клиентами. В памяти он выглядит следующим образом (обратите внимание, что это один одномерный массив - SAFEARRAYBOUND в конце фактически массив с количеством элементов в CDIM):

SafeArray in memory

Это в основном то, что вы получаете с декларация Dim x(1 To 3, 1 To 3) As Integer (за исключением того, что в конце будет 2 SAFEARRAYBOUND).

Обратите внимание, что существует 2 очень важных различия между двумя типами данных. Объявление с произвольным типом Dim x As Variant позволяет среде выполнения определять, что содержится в области данных. В случае назначения Range.Value вы получите массив VARTYPE из Variant, который является совместимым типом (именно поэтому Dim x() As Variant скомпилирует). Декларация Dim y(1 To 3, 1 To 3) As Integer - , установленная во время компиляции. Что еще более важно, поскольку размер структуры SAFEARRAY в памяти определяется количеством измерений, память может быть выделена компилятором во время компиляции. Однако объем памяти, выделенной для произвольной структуры SAFEARRAY, возвращаемой COM-вызовом, не может. Кроме того, размер размера указанной области памяти определяется длиной байта содержащегося типа и общим количеством элементов. Компилятор защищает от возможности несоответствия путем отказа от назначения.

На самом деле, это, вероятно, причина, почему вы не можете непосредственно получить указатель на 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 защищает вас от нарушений доступа и/или неудачных попыток выполнения.

Смежные вопросы