Структура программы

В предыдущих разделах мы рассмотрели те элементарные "кирпичики" (операторы и выражения), из которых строится программа. В ранних языках программирования, например в первых версиях Basic, на этом все и заканчивалось. Программа — это последовательность операторов, и все. Пока программы невелики, такой подход вполне работоспособен. Если в программе 10 операторов, то никакой дополнительной структуры не нужно. Но если в про грамме 10 000 операторов или 10 000 000 операторов (а такие программы есть, и они работают), то без введения дополнительной структуры не обойтись.

В VBA поддерживается следующая структура программы. На высшем уровне иерархии стоит приложение, далее идут проекты, связанные с фактическими документами этого приложения, на третьем уровне находятся модули (модули приложения, модули пользователя, модули класса, модули форм и модули ссылок). А на последнем уровне находятся процедуры и функции этих модулей.

Данная структуризация программ полностью, удовлетворяет принципам структурного и модульного программирования. В настоящем разделе мы подробно обсудим работу на уровне модуля, о работе с приложениями и проектами будет рассказано в разделе 20.6 "Редактор Visual Basic for Application".

Итак, модуль — это часть программы, оформленная в виде, допускающем ее независимую трансляцию. Модуль в свою очередь состоит из двух разделов: раздела объявлений (Declarations) и раздела процедур и функций. В разделе Declarations описываются глобальные переменные, типы, определенные пользователем, и перечисляемые типы. В следующем же разделе описываются процедуры и функции. Процедура — это минимальная семантически законченная программная конструкция, допускающая выполнение. В конце концов, просто так операторы не выполняются и не пишутся, они находятся в описании процедур и функций.

Область видимости — это область программы, где имя переменной видимо, а значит, возможен доступ к ее значению.

Существует три уровня видимости и пять способов объявления:

Процедура (область видимости — только та процедура, в которой переменная объявлена).

Модуль (область видимости — все процедуры модуля, в котором переменная объявлена):

Приложение (область видимости — все процедуры всех модулей активного приложения):

Процедуры, как и все определяемые пользователем элементы VBA, требуют объявления. Объявление процедуры имеет следующий синтаксис:

[Private | Public | Friend] [Static] Sub имяПроцедуры ([ списокАргументов ]}

[ блокОператоров1 ]

[Exit Sub]

[ блокОператоров2 ] End Sub

Ключевое слово Private задает следующую область видимости для процедуры — только модуль, в котором она описана. То есть ее могут вызывать только процедуры того же модуля. Ключевое слово Public, наоборот, объявляет процедуру доступной для всех модулей проекта. По умолчанию процедура общедоступна, т. е. имеет статус Public. Что касается использования ключевого слова Friend, то о нем мы расскажем чуть позже, когда речь пойдет о классах VBA.

После операторов объявления идет необязательное ключевое слово static, определяющее все локальные переменные статическими. Данный оператор равносилен тому, что каждая из описанных в теле процедуры- переменных объявлялась бы как static.

ИмяПроцедуры — это идентификатор процедуры, удовлетворяющий всем правилам создания имен.

После имени идут обязательные скобки, но необязательный список-Аргументов. Давайте подробнее рассмотрим объявление одного аргумента; если их больше, то они просто разделяются запятой.

[Optional] [ByVal | ByRef] [ParamArray] имя-Аргумента [()] [As типДанных] [= значениеПоУмолчанию]

Ключевое слово Optional означает, что аргумент необязателен и его можно опустить при вызове процедуры. По умолчанию аргумент обязателен. Все необязательные аргументы должны находиться после обязательных. Естественно, что необязательный параметр значениеПоУмолчанию — это значение необязательного аргумента, если он не будет задан при вызове процедуры. Если в конструкцию входит ключевое слово ParamArray, то использование ключевого слова Optional невозможно.

В качестве фактических параметров процедура может получать не только значения констант, но и значения переменных. При передаче процедуре переменных в качестве параметров может использоваться один из двух способов: ByVal (по значению) и ByRef (по ссылке).

Чтобы понять, в чем разница между этими двумя способами, нужно рассмотреть хранение и механизм передачи параметров "изнутри". Итак, при объявлении переменной какого-либо типа выделяется область в памяти компьютера, в которой будет храниться значение переменной. Размер этой области, понятно, зависит от типа этой переменной. Теперь, зная, как устроена переменная внутри компьютера, рассмотрим вопрос о передаче ее в качестве параметра в процедуру.

Если переменная передается в процедуру по ссылке (то есть с использованием перед ее именем ключевого слова ByRef), то процедуре будет передан адрес этой переменной в памяти. При этом происходит отождествление формального аргумента процедуры и переданного фактического параметра.

Тем самым вызываемая процедура, изменяя значение формального параметра, изменяет значение переменной.

Если же переменная передается по значению (то есть с использованием перед ее именем ключевого слова Byval), то компилятор создает временную копию этой переменной и именно адрес этой переменной-копии передается процедуре. Тем самым вызываемая процедура, изменяя значение формального параметра, изменяет значение переменной-копии (но не самой переменной), которая будет уничтожена после завершения работы процедуры. По умолчанию в VBA переменные передаются по ссылке.

После рассмотрения способов передачи переменных в процедуру вернемся к дальнейшему обзору ее конструкции. Следующий параметр paramArray может появиться только перед последним аргументом в списке аргументов, указывая, что он является необязательным массивом элементов типа variant.

имяАргумента — это идентификатор, составленный согласно правилам создания имен и представляющий аргумент в теле процедуры. Параметр типданных — это либо встроенный тип данных, либо тип, определенный пользователем. По умолчанию типДанкых является Variant.

После описания процедуры идет блокОператоров1 (обычно называемый телом процедуры), в котором могут быть использованы значения аргументов" процедуры. Если в ходе выполнения операторов процедуры встречается оператор Exit Sub (выход из процедуры), то выполнение процедуры прекращается и управление передается оператору, следующему за оператором вызова процедуры.

Функция отличается от процедуры тем, что помимо выполнения операторов ею возвращается некоторое значение. Синтаксис описания функции немногим отличается от процедуры:

[Public | Private | Friend] [Static]

Function имяФункции [(списокАргументов)] [As типЗначения]

[блокОператоров1]

[имяФункции = Выражение] [Exit Function]

[блокОператоров2]

[имяФункции = Выражение] End Function

Во-первых, ключевое слово sub заменено на Function. Во-вторых, после объявления аргументов следует указать тип возвращаемого функцией значения. В-третьих, в теле функции есть присваивание имени функции какого-либо значения, имеющего объявленный тип. Конечно, подобное присваивание необязательно, но тогда ваша функция будет похожа на процедуру, а результатом функции окажется значение по умолчанию, определенное для соответствующего типа. И наконец, вместо ключевого слова Exit Sub для выхода из функции используется ключевое слово Exit Function.

Давайте на примере об издательстве и магазинах опишем процедуру инициализации массива заявок. Итак, у нас есть аргумент массива, который передается процедуре, а процедура инициализирует его с помощью стандартной функции inputBox. Для определения верхней и нижней границ массива используются стандартные функции LBound и uBound.

В случае с функцией в качестве аргументов вводится массив заявок и количество книг на складе, а возвращается логическое значение True, если удовлетворяются заявки всех магазинов, и False — в противном случае. Кстати, аргумент количество книг мы сделаем необязательным, по умолчанию равным 5000.

Программа 20.14. Объявление процедур и функций

Public Sub InitBookShops(arr() As Integer)

Dim i, str For i = LBound (arr) To UBound (arr)

str = "Ввести заказ для магазина №" & i arr(i) = InputBox(str) Next i End Sub

Public Function SaleAbility(arr() As Integer, _ '

Optional numOfBooks As Integer = 5000) As Boolean

Dim sumOfBooks

For Each elem In arr

sumOfBooks = sumOfBooks + elem Next If sumOfBooks < numOfBooks Then

SaleAbility = True Else

SaleAbility = False End If End Function

Замечание

В теле функции SaleAbility имеется сознательно допущенная нерациональность. Вместо последнего условного оператора if. . .Then. . .Else можно и нужно написать эквивалентный, более эффективный оператор присваивания SaleAbility = sumOfBooks < numOfBooks. Этим замечанием мы специально акцентируем внимание читателя на подобных мелких, но важных "хитростях" хорошего стиля программирования.

Помимо вышеописанного объявления процедур и функций, в VBA существует особый вид процедур. Это процедуры реакции на событие, вызываемое системой или пользователем (см. главу 22 "Разработка приложения"). Например, для документов Word определены события open и close, для рабочих книг Еxcel — Beforesave и Beforedose, для объектов пользовательских классов — initialize и Terminate, нажатие кнопки диалогового окна тоже является событием и т. д. Пользователь способен сам создать процедуру реакции на подобные события, например попросить выводить сообщение "До свидания, спасибо за работу!" при закрытии документа.

Синтаксис подобной процедуры такой же, как и у обыкновенной, только в ее имени сначала указан объект, с которым будет связано событие, потом — символ подчеркивания (_), а затем — собственно имя события.

Программа 20.15. Процедура реакции на событие

Private Sub Document_Close()

MsgBox ("До свидания, спасибо за работу!")

End Sub

Замечание

Событийное управление — это способ структуризации программного кода, основанный на следующей идее. Имеется некоторое предопределенное множество поименованных событий. События могут быть явным образом связаны с объектами, а могут быть связаны неявным образом или быть связаны с неявными объектами, в последнем случае события обычно называют системными. События могут возникать. Возникновение события подразумевает, что состояние системы изменилось определенным образом. С событием может быть связана процедура, которая называется реакцией на событие. При возникновении события автоматически вызывается процедура реакции. В современных системах программирования, поддерживающих событийное управление, предусматривается большое число самых разнообразных событий, реакции на которые могут быть определены в программе, например: нажатие клавиши на клавиатуре, попадание указателя мыши в определенную область экрана, достижение внутренним таймером заданного значения, открытие заданного файла и т. д. В программе, полностью управляемой событиями, нет основного потока управления, он находится вне программы (в операционной системе или в административной системе времени выполнения, т. е. там, где реализован механизм возникновения событий). Управление в программу попадает только в форме вызова процедуры реакции. Такая организация программы обеспечивает высокую модульность, прозрачность, сбалансированность структуры и другие полезные свойства. Понятно, что если связать события с командами приложения (как обычно и делается), то событийное управление как нельзя лучше подходит для реализации интерфейса пользователя.


Совет

В современных системах программирования имеются богатые и все время развивающиеся библиотеки готовых компонент, которые называются элементами управления (controls) и тесно интегрированы со встроенными механизмами событийного управления. Использование готовых элементов управления удобно, продуктивно и должно быть рекомендовано в большинстве случаев. Более подробная информация по этому вопросу дана в главе 22 "Разработка приложения".

Естественно, что помимо объявления процедур и функций их нужно использовать — вызывать. Существует несколько способов это сделать, причем "несколько" это очень слабо сказано. VBA предоставляет пользователю настолько гибкую систему вызова процедур и функций, что ему приходится задумываться над тем, каким из способов вызывать процедуру.

Первый, самый простой, вызов:

ИмяПроцедуры СписокФактическихПараметров

ИмяПроцедуры — это ваше Вызываемой Процедуры, а списокФактическихПара - метров — это список фактических параметров, передаваемых процедуре при ее вызове. Он должен соответствовать обязательному списку аргументов, заданному при объявлении процедуры. Фактические параметры, если их больше одного, перечисляются через запятую; их порядок обязан соответствовать объявленным аргументам. Заметьте, что при подобном вызове процедуры нет необходимости заключать список фактических параметров в скобки.

Также можно вызывать процедуру, используя ключевое слово Call: Call имяПроцедуры (СписокФактическихПараметров)

Суть всех параметров при данном способе вызова остается той же. Отличие заключается в том, что список фактических параметров необходимо обязательно заключать в скобки.

Вызов функции немного отличен от вызова процедур. Главное отличие в том, что можно передавать вычисляемое значение функции некоторой переменной, таким образом, вызов функции имеет следующий вид:

ИмяПеременной = ИмяФункции (СписокФактическихПараметров)

Давайте на примере вышеобъявленных процедур и функций покажем их вызов.

Программа 20.16. Вызов процедур и функций

Sub Test() .

Dim bookshops(1 To 25) As Integer Dim result As Boolean

Init bookshops

result = SaleAbility(bookshops, 3000)

MsgBox(result) End Sub

В рассмотренных способах вызова фактические параметры располагались в той же последовательности, что и формальные аргументы при объявлении процедуры или функции. В ряде случаев, особенно когда процедура содержит большое количество необязательных аргументов, можно воспользоваться уникальной возможностью VBA — именованными аргументами. При подобном вызове, кроме значения фактического параметра, указывается и имя

формального аргумента, которому соответствует данное значение. Для этого после имени аргумента ставятся двоеточие и знак равенства, после которого идет фактический параметр. Это очень удобный прием, позволяющий видеть связь аргумента с фактическим параметром.

Программа 20.17. Использование именованных аргументов

Sub Test2()

Dim bookshops(1 To 25) As Integer

Dim result As Boolean

Init bookshops

result = SaleAbility(arr := bookshops, numOfBooks := 3000)

MsgBox(result) End Sub

При описании синтаксиса объявления процедуры мы вскользь упомянули ключевое слово ParamArray, настало время уделить ему достойное внимание. Возможность передавать процедуре заранее неизвестное число фактических параметров является опять-таки уникальной по отношению ко многим другим языкам программирования.

Например, мы создадим процедуру подсчета общего количества заявок с использованием параметра ParamArray. При вызове этой процедуры в процедуре Tests можно задавать сколько угодно фактических параметров массива заявок.

Программа 20.18. Использование параметра ParamArray

Sub FullSum(ParamArray arr() As Variant)

Dim sum As Integer

For i = LBound(arr) To UBound(arr) sum = sum + arr(i)

Next i

MsgBox (sum) End Sub

Sub Test3 ()

FullSum 100, 2000, 350, 450 End Sub

Рассмотрим еще один способ вызова процедур или функций — рекурсивный вызов, т. е. вызов, при котором процедура вызывается из своего же тела. Стандартный пример рекурсивной функции — вычисление факториала.

Программа 20.19. Рекурсивный вызов функции

Function fctrl(n As Integer) As Variant

If (n <= 1) Then fctrl = 1

Else

fctrl = n * fctrlfn - 1) End If End Function

Sub Test4 ()

MsgBox fctrl(20) End Sub

Как правило, рекурсивными вызовами не стоит злоупотреблять, поскольку при рекурсивных вызовах достаточно быстро заполняется стековая память компьютера и на их обработку тратится гораздо больше времени. В нашем случае можно избежать использования рекурсивного вызова его заменой на обыкновенный цикл. Хотя, безусловно, есть ситуации, когда использование рекурсивных методов заметно ускоряет работу, например при работе с древовидными структурами данных.

В заключение мы рассмотрим пример, показывающий различие между передачей параметров по ссылке и по значению, в котором приведены две процедуры: RefVal и MainCalc. Вспомогательная процедура RefVal использует три формальных аргумента, описанные по-разному. Далее в теле этой процедуры каждый из них увеличивается на единицу, а затем их значения выводятся на экран. Основная процедура MainCalc устанавливает значения переменных а, ь и с, а затем передает их в качестве параметров процедуре RefVal. При этом первый параметр передается по ссылке (по умолчанию), второй — по значению, а третий — снова по ссылке. После возврата из процедуры RefVal основная процедура также выводит на экран значения трех переменных, передававшихся в качестве параметров. Всего на экран выводится шесть значений. Сначала это числа 11, 21 и 31 (все полученные значения увеличились на 1 и выводятся процедурой RefVal). Затем это числа И, 20 и 31 (эти значения выводятся процедурой MainCalc, причем переменные, переданные по ссылке, увеличились, а переменная, переданная по значению, — нет).

Программа 20.20. Разница между ByRef и ByVal

Sub RefVal(x, ByVal у, ByRef z)

х = х + 1

у = у + 1

z = z + 1

MsgBox(х)

MsgBox(у)

MsgBox (z) End Sub

Sub MainCalc 0 a = 10 b = 20 с = 30 Call RefVal(a, b, c)

MsgBox(a) MsgBox(b) MsgBox(c) End Sub

Итак, мы рассмотрели работу с процедурами и функциями, определяемыми пользователем, но помимо этой возможности, есть широкий набор всевозможных встроенных процедур и функций, позволяющих программисту не задумываться над реализацией стандартных операций, таких как сравнение строк или взятие синуса от некоторого числа. К сожалению, рамки нашей книги не дают возможность описать даже малую часть встроенных процедур и функций (да это и ни к чему), но вы всегда сможете найти их полный перечень и описание во встроенной справке Outlook.