Декларация функции. Как объект языка Си, функцию необходимо объявить

Всем привет! В этой статье я хочу рассказать про то, что такое function declaration и function expression в языке программирования JavaScript .

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

В той статье рассказывалось про function declaration .

Если вы хотите задать function expression , то вам нужно написать так:

Var func = function(a, b) { return a + b; };

Т.е. мы присваиваем функцию переменной. Обратите внимание, что в конце стоит точка с запятой и это нужно делать всегда, т.к. это уже просто значение переменной, в function declaration точки с запятой ставить не нужно. По сути, если вы не поставите точку с запятой, то, скорее всего, ничего страшного не случится, однако есть такие ситуации, где это может сыграть с вами злую шутку и потом найти эту ошибку будет достаточно сложно.

Теперь поговорим об различиях этих двух объявлений функции.

Function Declaration

sum();

Function sum() {
alert("Сработало!");
}

Итак, если вы подумали, то вот правильный ответ: ДА , сработает.

Как же такое возможно?

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

Function Expression

Здесь все в точности, да наоборот.

Var func = function() {
alert("Сработало!");
};

Про такие функции интепретатор узнает лишь тогда, когда дойдет до нее в коде, поэтому пример выше не сработает. Однако, если написать вызов функции ниже, то все будет работать.

Зачем нужны function expression?

На самом деле они много где применяются, но я приведу здесь банальнейший до безобразия пример.

If(age >= 18) {
var func = function() {
alert("Добро пожаловать!");
};
} else {
var func = function() {
alert("Вам еще рановато!");
};
}

Теперь, в зависимости от условия, в переменную func будет записан разный исходный код и, следовательно, функции будут вести себя по-разному.

С function declaration такой фокус не пройдет.

If(age >= 18) {
function func() {
alert("Добро пожаловать!");
}
} else {
function func() {
alert("Вам еще рановато!");
}
}

Вот вам еще одна задачка. Подумайте, что будет делать код выше и какой alert (какая функция) сработает в итоге.

Интерпретатор смотрит код сверху вниз, следовательно, он запишет значение последней функции и ее и будет вызывать.

Итак, надеюсь, вы поняли различия между этими 2-мя объявлениями функций.

Выражения в JavaScript представляют собой комбинации операндов и операторов .

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

Рис. 1. Структура выражения в JavaScript

Операнды — это данные, обрабатываемые сценарием JavaScript. В качестве операндов могут быть как простые типы данных, так и сложные, а также другие выражения.

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

В зависимости от количества операндов различают следующие типы операторов:
унарный — в операции участвует один операнд;
бинарный — в операции участвуют два операнда;
тернарный — комбинирует три операнда.

Простейшая форма выражения — литерал — нечто, вычисляемое само в себя, например, число 100 , строка "Hellow world" . Переменная тоже может быть выражением, так как она вычисляется в присвоенное ей значение.

Выражения и операторы в JavaScript

1. Арифметические операторы

Арифметические операторы предназначены для выполнения математических операций, они работают с числовыми операндами (или переменными, хранящими числовые значения), возвращая в качестве результата числовое значение.

Если один из операндов является строкой, интерпретатор JavaScript попытается преобразовать его в числовой тип, а после выполнить соответствующую операцию. Если преобразование типов окажется невозможным, будет получен результат NaN (не число).

Таблица 1. Арифметические операторы
Оператор/Операция Описание Приоритет
+ Сложение Складывает числовые операнды. Если один из операндов — строка, то результатом выражения будет строка. 12
- Вычитание Выполняет вычитание второго операнда из первого. 12
- Унарный минус Преобразует положительное число в отрицательное, и наоборот. 14
* Умножение Умножает два операнда. 13
/ Деление Делит первый операнд на второй. Результатом деления может являться как целое, так и число с плавающей точкой. 13
% Деление по модулю (остаток от деления) Вычисляет остаток, получаемый при целочисленном делении первого операнда на второй. Применяется как к целым числам, так и числам с плавающей точкой. 13
var x = 5, y = 8, z; z = x + y; // вернет 13 z = x - y; // вернет -3 z = - y; // вернет -8 z = x * y; // вернет 40 z = x / y; // вернет 0.625 z = y % x; // вернет 3

2. Операторы присваивания

Операторы присваивания используются для присваивания значений переменным. Комбинированные операторы позволяют сохранить первоначальное и последующее значение в одной переменной.

var a = 5; // присваиваем переменной a числовое значение 5 var b = "hellow"; // сохраняем в переменной b строку hellow var m = n = z = 10; // присваиваем переменным m, n, z числовое значение 10 x += 10; // равнозначно x = x + 10; x -= 10; // равнозначно x = x - 10; x *= 10; // равнозначно x = x * 10; x /= 10; // равнозначно x = x / 10; x %= 10; // равнозначно x = x % 10;

3. Операторы инкремента и декремента

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

var x = y = m = n = 5, z, s, k, l; z = ++x * 2; /* в результате вычислений вернет значение z = 12, x = 6, т.е. значение x сначала увеличивается на 1, а после выполняется операция умножения */ s = y++ * 2; /* в результате вычислений вернет значение s = 10, y = 6, т.е. сначала выполняется операция умножения, а после в переменной y сохраняется увеличенное на 1 значение */ k = --m * 2; // вернет значение k = 8, m = 4 l = n-- * 2; // вернет значение l = 10, n = 4

4. Операторы сравнения

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

Если оба операнда не могут быть успешно преобразованы в числа или строки, операторы всегда возвращают false .

Если оба операнда являются строками/числами или могут быть преобразованы в строки/числа, они будут сравниваться как строки/числа.

Если один операнд является строкой/преобразуется в строку, а другой является числом/преобразуется в число, то оператор попытается преобразовать строку в число и выполнить сравнение чисел. Если строка не является числом, она преобразуется в значение NaN и результатом сравнения будет false .

Чаще всего операции сравнения используются при организации ветвлений в программах.

Таблица 4. Операторы сравнения
Оператор/Операция Описание Приоритет
== Равенство Проверяет две величины на совпадение, допуская преобразование типов. Возвращает true , если операнды совпадают, и false , если они различны. 9
!= Неравенство Возвращает true , если операнды не равны 9
=== Идентичность Проверяет два операнда на «идентичность», руководствуясь строгим определением совпадения. Возвращает true , если операнды равны без преобразования типов. 9
!== Неидентичность Выполняет проверку идентичности. Возвращает true , если операнды не равны без преобразования типов. 9
> Больше Возвращает true , если первый операнд больше второго, в противном случае возвращает false . 10
>= Больше или равно Возвращает true , если первый операнд не меньше второго, в противном случае возвращает false . 10
Возвращает true , если первый операнд меньше второго, в противном случае возвращает false . 10
Возвращает true , если первый операнд не больше второго, в противном случае возвращает false . 10
5 == "5"; // вернет true 5 != -5.0; // вернет true 5 === "5"; // вернет false false === false; // вернет true 1 !== true; // вернет true 1 != true; // вернет false, так как true преобразуется в 1 3 > -3; // вернет true 3 >= "4"; // вернет false

5. Логические операторы

Логические операторы позволяют комбинировать условия, возвращающие логические величины. Чаще всего используются в условном выражении if .

(2 < 3) && (3===3); // вернет true, так как выражения в обеих скобках дают true (x < 10 && x > 0); // вернет true, если значение x принадлежит промежутку от 0 до 10 !false; // вернет true

6. Побитовые операторы

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

Таблица 6. Побитовые операторы
Оператор/Операция Описание Приоритет
& Побитовый И Если оба бита равны 1 , то результирующий бит будет равен 1 . В противном случае результат равен 0 . 8
| Побитовый ИЛИ Если один из операндов содержит в позиции 1 , результат тоже будет содержать 1 в этой позиции, в противном случае результат в этой позиции будет равен 0 . 6
^ Исключающее ИЛИ Если одно, и только одно значение содержит 1 в какой-либо позиции, то и результат будет содержать 1 в этой позиции, в противном случае результат в этой позиции будет равен 0 . 7
~ Отрицание Выполняется операция побитового отрицания над двоичным представлением значения выражения. Любая позиция, содержащая 1 в исходном выражении, заменяется на 0 . Любая позиция, содержащая 0 в исходном выражении, становится равной 0 . Положительные числа начинаются с 0 , отрицательные - с -1 , поэтому ~ n == -(n+1) . 14
Оператор сдвигает биты первого операнда влево на число битовых позиций, установленных вторым операндом. Для заполнения позиций справа используются нули. Возвращают результат того же типа, что левый операнд. 11
>> Побитовый сдвиг вправо Оператор сдвигает биты первого операнда вправо на число битовых позиций, установленных вторым операндом. Цифры, сдвинутые за пределы диапазона, удаляются. Самый старший бит (32й) не меняется, чтобы сохранить знак результата. Если первый операнд положителен, старшие биты результата заполняются нулями; если первый операнд отрицателен, старшие биты результата заполняются единицами. Сдвиг значения вправо на одну позицию эквивалентен делению на 2 (с отбрасыванием остатка), а сдвиг вправо на две позиции эквивалентен делению на 4 и т. д. 11
>>> Побитовый сдвиг вправо без учета знака Оператор сдвигает биты первого операнда вправо на число битовых позиций, установленных вторым операндом. Слева добавляются нули независимо от знака первого операнда. Цифры, сдвинутые за пределы диапазона, удаляются. 11
var x = 9, y = 5, z = 2, s = -5, result; // 9 эквивалентно 1001, 5 эквивалентно 0101 result = x & y; // вернет 1 (эквивалентно 0001) result = x | y; // вернет 13 (эквивалентно 1101) result = x ^ y; // вернет 12 (эквивалентно 1100) result = ~ y; // вернет -6 (эквивалентно 1100) result = x << y; // вернет 288 (эквивалентно 100100000) result = x >> z; // вернет 2 (эквивалентно 10) result = s >>> z; // вернет 1073741822 (эквивалентно 111111111111111111111111111110)

7. Строковые операторы

Существует несколько операторов, которые работают со строками особым образом.

"1" + "10"; // вернет "110" "1" + 10; // вернет "110" 2 + 5 + " цветных карандашей"; // вернет "7 цветных карандашей" "Цветных карандашей " + 2 + 5; // вернет "Цветных карандашей 25" "1" > "10"; // вернет false "10" <= 10; // вернет true "СССР" == "ссср"; // вернет false x = "micro"; x+= "soft"; // вернет "microsoft"

8. Специальные операторы

Таблица 8. Специальные операторы
Оператор/Операция Описание Приоритет
. Обращение к свойству Осуществляет доступ к свойству объекта. 15
, Множественное вычисление Вычисляет несколько независимых выражений, записанных в одну строку. 1
Индексация массива Осуществляет доступ к элементам массива или свойствам объекта. 15
() Вызов функции, группировка Группирует операции или вызывает функцию. 15
typeof Определение типа данных Унарный оператор, возвращает тип данных операнда. 14
instanceof Проверка типа объекта Оператор проверяет, является ли объект экземпляром определенного класса. Левый операнд должен быть объектом, правый - должен содержать имя класса объектов. Результат будет true , если объект, указанный слева, представляет собой экземпляр класса, указанного справа, в противном случае - false . 10
in Проверка наличия свойства В качестве левого операнда должна быть строка, а правым - массив или объект. Если левое значение является свойством объекта, вернется результат true . 10
new Создание объекта Оператор создает новый объект с неопределенными свойствами, затем вызывает функцию-конструктор для его инициализации (передачи параметров). Также может применяться для создания массива. 1
delete Удаление Оператор позволяет удалять свойство из объекта или элемент из массива. Возвращает true , если удаление прошло успешно, в противном случае false . При удалении элемента массива его длина не меняется. 14
void Определение выражения без возвращаемого значения Унарный оператор, отбрасывает значение операнда и возвращает underfined . 14
?: Операция условного выражения Тернарный оператор, позволяет организовать простое ветвление. В выражении участвуют три операнда, первый должен быть логическим значением или преобразовываться в него, а второй и третий - любыми значениями. Если первый операнд равен true , то условное выражение примет значение второго операнда; если false - то третьего. 3
document.write("hello world"); // выводит на экран строку hello world i = 0, j = 1; // сохраняет значения в переменных function1(10, 5); // вызов функции function1 с параметрами 10 и 5 var year = ; // создает массив с элементами typeof {a:1}; // вернет "object" var d = new Date(); // создаем новый объект с помощью конструктора Date() d instanceof Date; // вернет true var mycar = {make: "Honda", model: "Accord", year: 2005}; "make" in mycar; // вернет true var obj = new Object(); // создает пустой объект var food = ["milk", "bread", "meat", "olive oil", "cheese"]; delete food; // удаляет четвертый элемент из массива food x > 10 ? x * 2: x / 2; // возвращает значение x * 2, если x > 10, в противном случае x / 2

9. Комментарии в JavaScript

Однострочный комментарий: перед текстом комментария нужно поставить символы // .

Как объект языка Си, функцию необходимо объявить. Объявление функции пользователя, т.е. ее декларация, выполняется в двух формах – в форме описания и в форме определения.

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

В стандарте языка используется следующий способ декларации функций:

тип_результата ID_функции(тип переменной1, …, тип переменной N);

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

Описание прототипа дает возможность компилятору проверить соответствие типов и количества параметров при фактическом вызове этой функции.

Пример описания функции fun со списком параметров:

float fun(int, float, int, int);

Полное определение функции имеет следующий вид:

тип_результата ID_функции(список параметров)

код функции

Тип результата определяет тип выражения, значение которого возвращается в точку ее вызова при помощи оператора return <выражение> .

Если тип функции не указан, то по умолчанию предполагается тип int .

Список параметров состоит из перечня типов и идентификаторов параметров, разделенных запятыми.

Функция может не иметь параметров, но круглые скобки необходимы в любом случае.

Если функция не возвращает никакого значения, она должна быть описана как функция типа void (пустая).

В данном случае оператор return можно не ставить.

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

Пример функции, определяющей наименьшее значение из двух целочи­слен­ных переменных:

int min (int x, int y)

return (x

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

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

В JavaScript есть множество способов сделать одно и то же. В этом есть и хорошее, и плохое. Для новичка это точно плохо, так как ему придется не только изучить большее количество информации, но и появится больше мест для совершения потенциальных ошибок. Это может происходить при определении функций.

Есть множество различных способов объявить функцию:

Function A() {}; // декларация функции var B = function () {}; // функциональное выражение var C = (function () {}); // функциональное выражение с оператором группировки var D = function foo () {}; // именованное функциональное выражение var E = (function () {})(); // самовызывающееся функциональное выражение var F = new Function(); // конструктор функции var G = new function() {}; // вырожденный случай: конструктор объекта
В таком обилии сложно не запутаться, не так ли? Как правило, в повседневной жизни мы используем не более трех различных типов объявления функций, и это отлично работает. Однако если копнуть поглубже, то может оказаться, что большинство из нас даже не подозревает какой объём таинств и подводных камней хранит в себе операция объявления функции.

Согласно документации ECMA синтаксис определения функции следующий:
ДекларацияФункции: function Идентификатор (Параметры) { ТелоФункции } ФункциональноеВыражение: function Идентификатор (опционально) (Параметры) { ТелоФункции }
Хоть и выглядят эти определения вполне схожими, но между декларацией функции и функциональным выражением есть большая разница. Декларация функции (Function Declaration ) создается до выполнения любого кода, в то время как функциональное выражение (Function Expression ) будет создано только в момент, когда интерпретатор дойдёт до данной строки кода.

Функциональное выражение - это объявление функции в контексте какого-либо выражения.

Рассмотрим несколько примеров функциональных выражений:

Оператор присваивания
var a = function() {};
Это классический пример задания функционального выражения через присваивание. Оператор присваивания ожидает справа выражение, именно поэтому функция становится частью выражения.
Немного пофантазировав, можно придумать следующие примеры:

Var a = function() { return 1; }() + 12; // 13 var b = function() { return 1; } + ""; // function (){return 1} var c = function() { return 1; } + "" - 1; //NaN

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

Function foo() { return 1; } // undefined function foo() { return 1; }(); // Uncaught SyntaxError: Expected () to start arrow function, but got "}" instead of "=>" (function foo() { return 1; }()) // 1 (function foo() { return 1; })() // 1
Принципиальной разницы между третьим и четвертым вариантом нет, так как в первом случае мы выполняем выражение, которое определяет и сразу же исполняет функцию, а во втором случае выполняется выражение, определяющее функцию, которая затем будет выполнена.

Оператор запятая
Оператор запятая вычисляет значение каждого своего операнда (слева направо) и возвращает значение последнего операнда.

0, function() { return 1; }(); // 1

Операторы (+, -, !, ~, void)
+function() { return false; }(); // 0 -function() { return false; }(); // -0 !function() { return false; }(); // true ~function() { return false; }(); // -1 void function() { return false; }(); //undefined
Комбинированные операторы:
!-function () { return false; }(); // true var c = 5 * (2 - function () {return 1}()) // 5 var c = 5 * 2 - -~function () {return 1}() // 8

Отличие именованных функциональных выражений от не именованных:

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

Var f = function getFactorial (n) { return n ? n * getFactorial(n - 1) : 1; }; f(5); // 120

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

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

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

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

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

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

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

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

  • Оператор Dim объявляет переменную внутри тела процедуры и в любом месте процедуры, но до операторов, использующих ее. Время жизни данной переменной - это рамки процедуры, т. е. при входе в данную процедуру под переменную выделяется память и происходит ее инициализа ция, затем, в ходе выполнения процедуры, значение переменной может меняться, после выхода из процедуры выделенная память освобождается, и, соответственно, теряется значение переменной.
  • Оператор static антагонистичен оператору Dim, он объявляет статическую переменную. Разница в том, что при выходе из процедуры у статической переменной память не отбирается, а становится (в силу области видимости) временно недоступной, а соответственно, сохраняется ее значение, которым при повторном обращении к процедуре можно воспользоваться.

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

  • Оператор Private объявляет переменную в разделе описаний Declarations (вне процедур модуля).
  • Оператор Dim абсолютно аналогичен (в данном случае) оператору Private.

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

  • Оператор Public объявляет переменную в разделе описаний Declarations.

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

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

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

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

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

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

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

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

Имя-Аргумента [()] [= значениеПоУмолчанию]

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

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

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

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

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

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

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

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

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

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

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

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

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

[блокОператоров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

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 ("До свидания, спасибо за работу!")

Замечание

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


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

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

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

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

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

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

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

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

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

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

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

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

result = SaleAbility(bookshops, 3000)

MsgBox(result) End Sub

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

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

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

Dim bookshops(1 To 25) As Integer

Dim result As Boolean

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)

MsgBox (sum) End Sub

FullSum 100, 2000, 350, 450 End Sub

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

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

Function fctrl(n As Integer) As Variant

If (n <= 1) Then fctrl = 1

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

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)

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.