Example
You can copy a VBA array into an array of the same type using the =
operator. The arrays must be of the same type otherwise the code will throw a «Can’t assign to array» compilation error.
Dim source(0 to 2) As Long
Dim destinationLong() As Long
Dim destinationDouble() As Double
destinationLong = source ' copies contents of source into destinationLong
destinationDouble = source ' does not compile
The source array can be fixed or dynamic, but the destination array must be dynamic. Trying to copy to a fixed array will throw a «Can’t assign to array» compilation error. Any preexisting data in the receiving array is lost and its bounds and dimenions are changed to the same as the source array.
Dim source() As Long
ReDim source(0 To 2)
Dim fixed(0 To 2) As Long
Dim dynamic() As Long
fixed = source ' does not compile
dynamic = source ' does compile
Dim dynamic2() As Long
ReDim dynamic2(0 to 6, 3 to 99)
dynamic2 = source ' dynamic2 now has dimension (0 to 2)
Once the copy is made the two arrays are seperate in memory, i.e. the two variables are not references to same underlying data, so changes made to one array do not appear in the other.
Dim source(0 To 2) As Long
Dim destination() As Long
source(0) = 3
source(1) = 1
source(2) = 4
destination = source
destination(0) = 2
Debug.Print source(0); source(1); source(2) ' outputs: 3 1 4
Debug.Print destination(0); destination(1); destination(2) ' outputs: 2 1 4
Copying Arrays of Objects
With arrays of objects the references to those objects are copied, not the objects themselves. If a change is made to an object in one array it will also appear to be changed in the other array — they are both referencing the same object. However, setting an element to a different object in one array won’t set it to that object the other array.
Dim source(0 To 2) As Range
Dim destination() As Range
Set source(0) = Range("A1"): source(0).Value = 3
Set source(1) = Range("A2"): source(1).Value = 1
Set source(2) = Range("A3"): source(2).Value = 4
destination = source
Set destination(0) = Range("A4") 'reference changed in destination but not source
destination(0).Value = 2 'affects an object only in destination
destination(1).Value = 5 'affects an object in both source and destination
Debug.Print source(0); source(1); source(2) ' outputs 3 5 4
Debug.Print destination(0); destination(1); destination(2) ' outputs 2 5 4
Variants Containing an Array
You can also copy an array into and from a variant variable. When copying from a variant, it must contain an array of the same type as the receiving array otherwise it will throw a «Type mismatch» runtime error.
Dim var As Variant
Dim source(0 To 2) As Range
Dim destination() As Range
var = source
destination = var
var = 5
destination = var ' throws runtime error
Копирование массивов
Вы можете скопировать массив VBA в массив того же типа, используя оператор =
. Массивы должны быть одного типа, иначе код будет генерировать ошибку компиляции «Can not assign to array».
Dim source(0 to 2) As Long
Dim destinationLong() As Long
Dim destinationDouble() As Double
destinationLong = source ' copies contents of source into destinationLong
destinationDouble = source ' does not compile
Исходный массив может быть фиксированным или динамическим, но целевой массив должен быть динамическим. Попытка скопировать в фиксированный массив вызовет ошибку компиляции «Can not assign to array». Любые существующие данные в принимающем массиве теряются, а его границы и размеры изменяются так же, как исходный массив.
Dim source() As Long
ReDim source(0 To 2)
Dim fixed(0 To 2) As Long
Dim dynamic() As Long
fixed = source ' does not compile
dynamic = source ' does compile
Dim dynamic2() As Long
ReDim dynamic2(0 to 6, 3 to 99)
dynamic2 = source ' dynamic2 now has dimension (0 to 2)
Как только копия сделана, два массива являются отдельными в памяти, то есть две переменные не являются ссылками на одни и те же базовые данные, поэтому изменения, внесенные в один массив, не отображаются в другом.
Dim source(0 To 2) As Long
Dim destination() As Long
source(0) = 3
source(1) = 1
source(2) = 4
destination = source
destination(0) = 2
Debug.Print source(0); source(1); source(2) ' outputs: 3 1 4
Debug.Print destination(0); destination(1); destination(2) ' outputs: 2 1 4
Копирование массивов объектов
С массивами объектов копируются ссылки на эти объекты, а не сами объекты. Если изменение в объекте в одном массиве будет также изменено в другом массиве — они оба ссылаются на один и тот же объект. Однако установка элемента в другой объект в одном массиве не приведет его к этому объекту в другом массиве.
Dim source(0 To 2) As Range
Dim destination() As Range
Set source(0) = Range("A1"): source(0).Value = 3
Set source(1) = Range("A2"): source(1).Value = 1
Set source(2) = Range("A3"): source(2).Value = 4
destination = source
Set destination(0) = Range("A4") 'reference changed in destination but not source
destination(0).Value = 2 'affects an object only in destination
destination(1).Value = 5 'affects an object in both source and destination
Debug.Print source(0); source(1); source(2) ' outputs 3 5 4
Debug.Print destination(0); destination(1); destination(2) ' outputs 2 5 4
Варианты, содержащие массив
Вы также можете скопировать массив в переменную варианта и из нее. При копировании из варианта он должен содержать массив того же типа, что и принимающий массив, иначе он будет вызывать ошибку «Ошибка несоответствия типа».
Dim var As Variant
Dim source(0 To 2) As Range
Dim destination() As Range
var = source
destination = var
var = 5
destination = var ' throws runtime error
Возвращаемые массивы из функций
Функция в нормальном модуле (но не модуле класса) может возвращать массив, помещая ()
после типа данных.
Function arrayOfPiDigits() As Long()
Dim outputArray(0 To 2) As Long
outputArray(0) = 3
outputArray(1) = 1
outputArray(2) = 4
arrayOfPiDigits = outputArray
End Function
Результат функции можно затем поместить в динамический массив того же типа или варианта. К элементам также можно получить доступ, используя второй набор скобок, однако это вызовет функцию каждый раз, поэтому лучше сохранить результаты в новом массиве, если вы планируете использовать их более одного раза
Sub arrayExample()
Dim destination() As Long
Dim var As Variant
destination = arrayOfPiDigits()
var = arrayOfPiDigits
Debug.Print destination(0) ' outputs 3
Debug.Print var(1) ' outputs 1
Debug.Print arrayOfPiDigits()(2) ' outputs 4
End Sub
Обратите внимание, что возвращаемое на самом деле является копией массива внутри функции, а не ссылкой. Поэтому, если функция возвращает содержимое массива Static, его данные не могут быть изменены процедурой вызова.
Вывод массива через выходной аргумент
Обычно это хорошая практика кодирования для аргументов процедуры как входных данных и для вывода через возвращаемое значение. Однако ограничения VBA иногда требуют, чтобы процедура выводила данные через аргумент ByRef
.
Вывод в фиксированный массив
Sub threePiDigits(ByRef destination() As Long)
destination(0) = 3
destination(1) = 1
destination(2) = 4
End Sub
Sub printPiDigits()
Dim digits(0 To 2) As Long
threePiDigits digits
Debug.Print digits(0); digits(1); digits(2) ' outputs 3 1 4
End Sub
Вывод массива из метода класса
Выходной аргумент также может использоваться для вывода массива из метода / процедуры в модуле класса
' Class Module 'MathConstants'
Sub threePiDigits(ByRef destination() As Long)
ReDim destination(0 To 2)
destination(0) = 3
destination(1) = 1
destination(2) = 4
End Sub
' Standard Code Module
Sub printPiDigits()
Dim digits() As Long
Dim mathConsts As New MathConstants
mathConsts.threePiDigits digits
Debug.Print digits(0); digits(1); digits(2) ' outputs 3 1 4
End Sub
Передача массивов на прохождение
Массивы могут передаваться в процедуры, помещая ()
после имени переменной массива.
Function countElements(ByRef arr() As Double) As Long
countElements = UBound(arr) - LBound(arr) + 1
End Function
Массивы должны передаваться по ссылке. Если не указан какой-либо передающий механизм, например myFunction(arr())
, то VBA будет считать ByRef
по умолчанию, однако хорошая практика кодирования делает его явным. Попытка передать массив по значению, например myFunction(ByVal arr())
приведет к ошибке компиляции «Array argument должно быть ByRef» (или ошибке компиляции «Синтаксическая ошибка», если Auto Syntax Check
не проверена в параметрах VBE) ,
Передача по ссылке означает, что любые изменения в массиве будут сохранены в процессе вызова.
Sub testArrayPassing()
Dim source(0 To 1) As Long
source(0) = 3
source(1) = 1
Debug.Print doubleAndSum(source) ' outputs 8
Debug.Print source(0); source(1) ' outputs 6 2
End Sub
Function doubleAndSum(ByRef arr() As Long)
arr(0) = arr(0) * 2
arr(1) = arr(1) * 2
doubleAndSum = arr(0) + arr(1)
End Function
Если вы хотите избежать изменения исходного массива, будьте осторожны, чтобы написать функцию, чтобы она не меняла никаких элементов.
Function doubleAndSum(ByRef arr() As Long)
doubleAndSum = arr(0) * 2 + arr(1) * 2
End Function
Альтернативно создайте рабочую копию массива и работайте с копией.
Function doubleAndSum(ByRef arr() As Long)
Dim copyOfArr() As Long
copyOfArr = arr
copyOfArr(0) = copyOfArr(0) * 2
copyOfArr(1) = copyOfArr(1) * 2
doubleAndSum = copyOfArr(0) + copyOfArr(1)
End Function
Копирование значений из диапазона ячеек в массив и обратно с помощью VBA Excel. Простейшие примеры обмена значениями между диапазоном и массивом.
Как известно, VBA обрабатывает информацию в массивах значительно быстрее, чем в ячейках рабочего листа Excel. Поэтому, при работе с большими объемами данных, удобнее использовать массивы, чем наблюдать во время выполнения кода за мерцанием изображения на экране или просто смотреть в неизменную картинку, если обновление экрана отключено (Application.ScreenUpdating = False). Здесь обмен значениями между массивом и диапазоном ячеек будет вполне уместен.
Копирование значений из диапазона ячеек в массив
Чтобы скопировать значения из диапазона ячеек в массив, необходимо объявить переменную универсального типа (As Variant) и присвоить ей значения диапазона ячеек с помощью оператора присваивания (=):
Dim a As Variant a = Range(«A1:C3») |
VBA Excel автоматически преобразует объявленную переменную в двумерный массив, соответствующий размерности диапазона ячеек, в нашем случае в массив — a(1 To 3, 1 To 3), и заполняет его значениями. Нумерация измерений массивов, созданных таким образом, начинается с единицы (1).
Можно, в этом случае, объявить сразу динамический массив, чтобы изначально указать, что эта переменная будет массивом. Так как свойством диапазона ячеек по умолчанию в VBA Excel является значение (Value), его можно в коде явно не указывать, но, при желании, можно и указать. Получится такая конструкция, аналогичная первой:
Dim a() As Variant a = Range(«A1:C3»).Value |
Стоит отметить, что для копирования значений из диапазона ячеек в массив можно использовать только обычную переменную или динамический массив универсального типа (Variant). VBA Excel автоматически преобразовывает их в двумерный массив. Если объявить двумерный массив с указанной заранее размерностью, использовать его не получится, будет сгенерирована ошибка с сообщением: Can’t assign to array (Нельзя назначать массив).
Копирование значений из массива в диапазон ячеек
Значения в диапазон ячеек добавляются из массива с помощью оператора присваивания (=):
Range(«A6:F15») = a ‘или Range(«A6:F15»).Value = a ‘где a — переменная двумерного массива |
Обратите внимание, что вставить значения в диапазон ячеек можно только из двумерного массива. Размерность такого массива может начинаться с нуля (0). Количество элементов в измерениях массива должно совпадать с количеством строк и столбцов в диапазоне ячеек. Если вам нужно вставить значения в одну строку или в один столбец, укажите размерность единственной строки или единственного столбца как (0) или (1 To 1), если вы хотите использовать нумерацию измерений своего массива с единицы. Например, для записи десяти значений из массива в одну строку можно объявить такой массив — massiv(9, 0), или в один столбец — massiv(0, 9).
Для вставки значений в диапазон ячеек из массива идеально подойдет массив, созданный для копирования в него значений из диапазона. В этом случае, данные с рабочего листа Excel переносятся в массив, обрабатываются и, после обработки, вставляются обратно в ту же или другую таблицу на том же или другом рабочем листе.
Обмен значениями между двумя диапазонами
Обмен значениями можно осуществить в VBA Excel не только между массивом и диапазоном, но и между двумя диапазонами одинаковой размерности:
Range(«B2:D6») = Range(«G7:I11»).Value |
У диапазона, являющегося источником значений, обязательно должно быть указано свойство Value
.
Если диапазон ячеек, принимающий значения, по размеру меньше диапазона-источника, то он будет заполнен полностью:
Range(«B2:D6») = Range(«G5:L13»).Value |
Если принимающий диапазон ячеек по размеру больше передающего, то часть его будет заполнена значениями диапазона-источника, а остальные ячейки — значениями #Н/Д
:
Range(«B2:D6») = Range(«G7:H9»).Value |
Простейшие примеры обмена значениями
Эти примеры составлены так, чтобы вам не пришлось совершать лишних действий, просто скопируйте их в свой модуль любой книги Excel с поддержкой макросов и запустите по очереди на выполнение.
Пример 1
Заполнение двумерного массива значениями и и их присвоение диапазону ячеек на рабочем листе Excel:
Sub Test1() Dim a(2, 2) As Variant a(0, 0) = «телепузик» a(0, 1) = «журналист» a(0, 2) = «ящерица» a(1, 0) = «короед» a(1, 1) = «утенок» a(1, 2) = «шмель» a(2, 0) = 200 a(2, 1) = 300 a(2, 2) = 400 Range(«A1:C3»).Value = a End Sub |
В данном случае переменная массива не обязательно должна быть универсального типа (As Variant), например, если бы в нее записывались только текстовые данные, ее можно было бы объявить как строковую (As String), и все бы работало.
Пример 2
Объявление обычной переменной универсального типа, присвоение ей значений из диапазона ячеек «A1:C3», записанных кодом первого примера, и вставка этих значений из полученного двумерного массива в диапазон «D10:F12»:
Sub Test2() Dim a As Variant a = Range(«A1:C3») Range(«D10:F12») = a End Sub |
Естественно, указанные диапазоны ячеек расположены на активном листе.
Пример 3
Допустим, на рабочем листе «Лист1» в ячейках «A1:A5» записано количество какого-то товара, а в ячейках «B1:B5» — его цена. Необходимо к этой информации добавить сумму каждого товара, умножив количество на цену, и перенести данные на «Лист2».
Sub Test3() Dim a As Variant, i As Long a = Лист1.Range(«A1:C5») For i = 1 To 5 a(i, 3) = a(i, 1) _ * a(i, 2) Next Лист2.Range(«A1:C5») = a End Sub |
Массив создан сразу с размерностью 5×3 с элементами под суммы. Даже если на первом листе в ячейках «C1:C5» есть какие-то значения, в массиве они будут перезаписаны результатами вычислений.
Копирование значений из массива в массив
Этот пример показывает, как в VBA Excel можно скопировать значения из одного массива в другой:
Sub Test4() Dim arr1, arr2 arr1 = Range(«G7:I11») arr2 = arr1 Range(«B2:D6») = arr2 End Sub |
копирование массива в массив
, массив в екселе
- Подписаться на тему
- Сообщить другу
- Скачать/распечатать тему
|
|
Здравствуйте. Помогите начинающему советом:) Сообщение отредактировано: Kugeod — 08.04.09, 21:10 |
MIF |
|
Moderator Рейтинг (т): 414 |
Мож, стоит попробовать загрузить из старого массива в новый, а не наоборот: new(i,j)=old(i,j) |
Kugeod |
|
Я окончательно запутался Dim newM(1000,100) As Integer Sub Massiv() Выводит ошибку subscript out of range Почему не работает? |
Akina |
|
Цитата Kugeod @ 09.04.09, 06:46 Почему не работает?
Потому что с логикой косяк. Сообщение отредактировано: Akina — 09.04.09, 07:31 |
GDK |
|
Dim newM(1000, 100) As Integer Dim oldM(1000, 100) As Integer Dim i As Integer: Dim j As Integer Sub Massiv() Randomize For i = 1 To 1000 For j = 1 To 100 oldM(i, j) = Fix(Rnd + 0.5) If i > 500 Then newM(i, j) = oldM(i, j) Next j ‘If i > 500 Then newM(i, j) = oldM(i, j) Next i End Sub Это надо было? И вот ещё, обрати внимание. Объявляешь массив 0-1000, 0-100. Т.е. такой массив будет содержать 1001 «строку» и 101 «колонку». А цикл начинаешь с номера 1. Т.е. не будет никаких действий с 0-й строкой и нулевой колонкой. Добавлено 09.04.09, 08:50 Добавлено 09.04.09, 08:53 |
Akina |
|
Цитата GDK @ 09.04.09, 08:42 И вот ещё, обрати внимание. Объявляешь массив 0-1000, 0-100. Т.е. такой массив будет содержать 1001 «строку» и 101 «колонку». А цикл начинаешь с номера 1. Т.е. не будет никаких действий с 0-й строкой и нулевой колонкой.
Это при условии что выше нет незахваченного при копировании Option Base 1. |
Kugeod |
|
Спасибо за ответы, почти разобрался с копированием. Вопрос возник. Sub Massiv() Dim g As Integer Randomize For i = 1 To 100 For j = 1 To 10 oldM(i, j) = Fix((Rnd + 0.6) * 2) If i > 50 Then newM(i, j) = oldM(i, j) ‘это выражение присваивает новому массиву значения только с (50,1) до (100,10)?? Next j Next i Worksheets(1).Range(Cells(1, 1), Cells(100, 10)).Value = newM ‘ это я гдето подсмотрел что так можно:) End Sub Если первый комментарий в этом коде верный то почему итог выглядит как массив (1,1) (100,10) у которого значения до 50 заполнены нулями, а потом только выводится то что я хочу получить. Как сделать чтобы новый массив был только из 50 элементов по условию? и соответственно выводил 50 на 10. |
Akina |
|
Цитата Kugeod @ 09.04.09, 12:43 Если первый комментарий в этом коде верный то почему итог выглядит как массив (1,1) (100,10) у которого значения до 50 заполнены нулями, а потом только выводится то что я хочу получить. При определении переменной типа Integer (неважно, одиночка или массив) ей присваивается значение «ноль». Потом ты часть массива перезаписываешь — меняешь нули на значение, — а начало так и остаётся всё в нулях. Правильнее — так: Dim oldM(1 to 100, 1 to 10) As Integer Dim newM(1 to 50, 1 to 10) As Integer Sub Massiv() Dim i As Integer, j As Integer Randomize Timer For i = 1 To 100 For j = 1 To 10 oldM(i, j) = (Rnd + 0.6) * 2 If i > 50 Then newM(i-50, j) = oldM(i, j) Next j Next i Worksheets(1).Range(Cells(1, 1), Cells(50, 10)).Value = newM End Sub |
Kugeod |
|
В принципе я с вашей помощью почти разобрался. Akina вы предлагаете if i>50 then newM(i-50,j)…
А если у меня сложнее условие и я хочу на выходе получить массив только с теми значениями которые мне нужны, это вообще возможно? Без нулей? Как автоматически уменьшит размер выходного массива чтоб он был размером только под те значения, которые отфильтруются? Sub Massiv() Dim g As Integer Randomize Timer g = 1 For i = 1 To 100 For j = 1 To 10 oldM(i, j) = (Rnd + 0.6) * 2 If (i > 50 And i < 60) Or (i > 80 And i < 90) Then newM(g, j) = oldM(i, j) Next j If (i > 50 And i < 60) Or (i > 80 And i < 90) Then g = g + 1 Next i Worksheets(1).Range(Cells(1, 1), Cells(g, 10)).Value = newM End Sub ввел переменную g чтобы вывести только те значения которые меня интересуют. Но все равно такое ощущение, что можно по другому, слишком нагромоздил.. Тем более что выходной массив при этом не уменьшается, а значит смысл теряется:( |
Akina |
|
ReDim Preserve И вообще неплохо бы побольше читать. |
GDK |
|
Осторожнее с ReDim Preserve! Используйте только для одномерных массивов! Для многомерных массивов данные, хранящиеся в «2-м, 3-м, …» измерениях «обнуляются». Твоё безобразие — не безобразие. Сообщение отредактировано: GDK — 10.04.09, 13:13 |
Akina |
|
Цитата GDK @ 10.04.09, 13:12 Для многомерных массивов данные, хранящиеся в «2-м, 3-м, …» измерениях «обнуляются». Что??? никогда не было такого! |
Kugeod |
|
Да вот ReDim Preserve мне не помог никак:( Потому как Akina посоветовал мне почитать книгу и я прочел что можно изменять только последнее измерение многомерного массива. А меня то как раз интересует изменение первого измерения, так как мне оно не известно пока не закончится цикл, в этом же цикле вы предлагаете копировать массив в массив. За циклом его не скопировать а в цикле не переопределить первое измерение массива |
Akina |
|
Цитата Kugeod @ 10.04.09, 21:24 А меня то как раз интересует изменение первого измерения, так как мне оно не известно пока не закончится цикл
Поменяйте местами индексы |
Kugeod |
|
Поменяю индексы и что получится? Если имеете ввиду поменять местами циклы то это не возможно ввиду условий:( Написал бы ктонить что НИКАК я б и голову не ломал |
0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
0 пользователей:
- Предыдущая тема
- VB for Application
- Следующая тема
[ Script execution time: 0,0500 ] [ 16 queries used ] [ Generated: 16.04.23, 06:50 GMT ]
How to overcome the restrictions of ReDim Preserve
in multidimensional arrays
As mentioned by @ScottCraner, a ReDim Preserve
can change only the last dimension of a given (datafield) array.
Therefore trying to resize a 2-dimensional array’s first dimension (=»rows») will fail.
However you can overcome this inconvenience applying the relatively unknown filtering capability of Application.Index()
(c.f. section [2]
) and profit from the additional bonus of less loops.
Further reading: see Some pecularities of the Application.Index()
function
Sub GetRowsEqual10000()
With Sheet1
Dim lastRow As Long: lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Dim rng As Range: Set rng = .Range("A2:F" & lastRow)
End With
'[1] get data
Dim data: data = rng
'[2] rearrange data via Application.Index() instead ReDim Preserve plus loops
data = Application.Index(data, ValidRows(data, Condition:=10000), Array(1, 2, 3, 4, 5, 6))
End Sub
Help function ValidRows
()
Function ValidRows(arr, Condition) As Variant
'Purpose: a) check condition (e.g. values equalling 10000) and b) get valid row numbers c) in a 2-dim vertical array
ReDim tmp(1 To UBound(arr)) ' provide for 1-based 2-dim array
Dim i As Long, ii As Long
For i = 1 To UBound(arr) ' loop through 1st "column"
If arr(i, 1) = Condition Then ' a) check condition
ii = ii + 1: tmp(ii) = i ' b) collect valid row numbers
End If
Next i
ReDim Preserve tmp(1 To ii) ' resize tmp array (here the 1st dimension is also the last one:)
ValidRows = Application.Transpose(tmp) ' c) return transposed result as 2-dim array
End Function
Edit due to comment (2020-04-22)
Short hints to the most frequent use of Application.Index()
:
Frequently the Application.Index()
function is used to
get an entire row or column array out of a 2-dim array without need to loop.
Accessing your 1-based 2-dimensional datafield array like that requires to
indicate a single row or column number and
to set the neighbour argument column or row number to 0
(zero), respectively which might result in e.g.
Dim horizontal, vertical, RowNumber As Long, ColumnNumber As Long
RowNumber = 17: ColumnNumber = 4
horizontal = Application.Index(data, RowNumber, 0)
vertical = Application.Index(data, 0, ColumnNumber)
(Addressing a single array element will be done directly, however via data(i,j)
instead of a theoretical Application.Index(data, i, j)
)
How to use Application.Index()
for restructuring/filtering purposes:
In order to profit from the advanced possibilities of Application.Index()
you
need to pass not only the array name (e.g. data
), but the row|column arguments as Arrays, e.g.
data = Application.Index(data, Application.Transpose(Array(15,8,10)), Array(1, 2, 3, 4, 5, 6))
Note that the rows parameter becomes a «vertical» 2-dim array by transposition, where Array(15,8,10)
would even change the existing row order
(in the example code above this is done in the last code line within the ValidRows()
function).
The columns argument Array(1,2,3,4,5,6)
on the other hand remains «flat» or «horizontal» and
allows to get all existing column values as they are.
So you eventually you are receiving any data elements within the given element indices
(think them as coordinates in a graphic).