Тверской государственный университет
Опубликован: 13.09.2006 | Доступ: свободный | Студентов: 3491 / 369 | Оценка: 4.65 / 4.29 | Длительность: 30:37:00
Специальности: Программист, Менеджер
Лекция 10:

Процедуры и функции

Пользовательские функции, принимающие сложный объект 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 Function
9.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, допускающей сложные объекты Range

Рис. 9.2. Вызов функции IsMedianaForAll, допускающей сложные объекты Range

Проанализируем четыре сделанных вызова:

  • =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. Тем не менее, все работает правильно.
полина есенкова
полина есенкова
Дмитрий Вологжин
Дмитрий Вологжин
Добрый день, прошел тесты с 1 по 9, 10 не сдал, стал читать лекцию и всё пройденные тесты с 1 по 9 сбросились, когда захотел пересдать 10 тест.