Процедуры и функции
Пользовательские функции, принимающие сложный объект Range
Известно, что в Excel объект Range может представлять несмежную область и являться объединением нескольких интервалов ячеек. Иначе говоря, один объект Range может задавать несколько массивов рабочего листа. Можно ли такой объект передать пользовательской функции и, если да, как его обрабатывать? Ответ: "можно", хотя соответствующий формальный параметр следует описывать особым образом. Процедуры и функции VBA допускают произвольное число параметров, это достигается за счет того, что один, последний по счету формальный параметр может иметь спецификатор ParamArray. В этом случае данный параметр задает фактически массив параметров с произвольным числом элементов. Именно эта техника и применяется для передачи в пользовательскую функцию сложного объекта Range, представляющего не один, а произвольное число массивов. У такой функции последний параметр должен иметь спецификатор ParamArray и быть массивом типа Variant.
Рассмотрим предыдущую задачу, немного усложнив ее, полагая, что при проверке кандидата на "медианность" используется произвольное число массивов. Вот как выглядит функция, решающая эту задачу:
Public Function IsMedianaForAll(Cand As Variant, ParamArray M() As Variant) As Integer 'Эта функция осуществляет те же вычисления, что и функция IsMediana 'Важное отличие состоит в том, что аргумент M может быть задан сложным объектом 'Range как объединение массивов. Dim i As Integer, j As Integer Dim Pos As Integer, Neg As Integer Pos = 0: Neg = 0 Dim Elem As Variant 'Теперь M - это массив параметров, а Elem - его элемент. For Each Elem In M 'Анализ типа параметра Elem If TypeName(Elem) = "Range" Then For i = 1 To Elem.Rows.Count For j = 1 To Elem.Columns.Count If Elem.Cells(i, j) > Cand Then Pos = Pos + 1 ElseIf Elem.Cells(i, j) < Cand Then Neg = Neg + 1 End If Next j Next i ElseIf TypeName(Elem) = "Variant()" Then 'TypeName is "Variant()" 'Это массив, но не совсем настоящий, для него не определены, 'например, функции границ: LBound, UBound. Dim Val As Variant For Each Val In Elem If Val > Cand Then Pos = Pos + 1 ElseIf Val < Cand Then Neg = Neg + 1 End If Next Val ElseIf TypeName(Elem) = "Integer()" Then 'Это настоящий массив целых VBA, для которого 'определены функции границ. For i = LBound(Elem) To UBound(Elem) If Elem(i) > Cand Then Pos = Pos + 1 ElseIf Elem(i) < Cand Then Neg = Neg + 1 End If Next i Else MsgBox ("При вызове IsMedianaForAll один из аргументов" _ & "не является массивом или объектом Range!") End If Next Elem IsMedianaForAll = Pos - Neg End Function9.2.
Комментируя работу этой функции, отметим:
- Эта функция может (и будет) вызываться как из процедур VBA, так и из формул рабочего листа Excel.
- Формально функция по-прежнему имеет два параметра Cand и M. Правда, теперь они поменялись местами, и параметр M стал последним. Фактически у этой функции теперь произвольное число параметров, поскольку параметр M, сохранив тип Variant, стал теперь массивом. Спецификатор ParamArray подчеркивает, что это специальный массив с произвольным числом элементов.
- Для работы с массивом M используется цикл типа For Each. В цикле выделяется очередной элемент Elem типа Variant, а дальше используется уже знакомый по функции IsMediana алгоритм проверки элемента Cand.
- Разбор случаев делается независимо для каждого из элементов массива M.
Демонстрацию использования этой функции начнем с ее вызова в процедуре VBA, которая передает ей целочисленный массив элементов:
Public Sub TestIsMedianaForAll() Const Size = 7 Dim Mas(1 To Size) As Integer Dim Cand As Integer Dim i As Integer Dim Res As Integer 'Инициализация массива целыми в интервале 1-20 Debug.Print TypeName(Mas) Randomize For i = 1 To Size Mas(i) = Int(Rnd * 21) Next i Cand = Int(Rnd * 21) Res = IsMedianaForAll(Cand, Mas) Debug.Print "Массив:" For i = 1 To Size Debug.Print Mas(i) Next i Debug.Print "Кандидат:", Cand Debug.Print "Результат:", Res End Sub
Приведем результаты выполнения этой процедуры:
Integer() Массив: 9 1 14 11 11 18 0 Кандидат: 12 Результат: -3
А теперь покажем, как эта функция вызывается в формулах рабочего листа Excel На том же рабочем листе, где мы проводили эксперименты с формулами, вызывающими функцию IsMediana, мы записали еще несколько формул, вызывающих функцию IsMedianaForAll. Заметьте, ее аргумент может иметь значительно более сложный вид, чем при вызове функции IsMedina.
Проанализируем четыре сделанных вызова:
- =IsMedianaForAll(7;M;N). В этом вызове наш кандидат - число 7 - проверяется по отношению к объединению двух массивов рабочего листа, заданных своими именами M и N. Формальных параметров у функции два, а фактических при вызове задается три. Два последних можно рассматривать как сложный объект Range, представляющий несмежную область ячеек и объединение вектора M и матрицы N. С программистской точки зрения, можно полагать, что передается массив с произвольным числом элементов, где каждый из них в свою очередь является массивом. Такой фактический параметр является допустимым значением формального параметра нашей функции, имеющего спецификатор ParamArray.
- =IsMedianaForAll(4,5;N;M)). В этом вызове мало нового в сравнении с предыдущим. Изменен порядок следования массивов N и M, изменен кандидат, - им стало число 4.5, не входящее ни в один из массивов. Как показывает результат, это число является медианой объединенных массивов.
- =IsMedianaForAll(7; {4;7;2}; {9;12;5}). Здесь в роли аргументов выступают массивы, заданные в виде констант, заключенных в фигурные скобки. Фактическое значение параметра M в этом случае представляет массив из двух элементов, каждый из которых в свою очередь является массивом.
- =IsMedianaForAll(7; {4;7;2}; {9;12;5}; M). Ситуация в этом вызове сложнее, так как число аргументов возросло, но, что более важно, среди них есть как массивы - константы, так и массив рабочего листа - вектор M. Тем не менее, все работает правильно.