Области видимости переменных
Шапка-невидимка. Введите в окно кода такой код:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a As Integer = 5
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim b As Integer = 4
a = b
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
a = b
End Sub
End Class
VB подчеркнул во второй из двух процедур переменную b. Ошибка. При наведении мышки на ошибку VB выдал подсказку: Name 'b' is not declared, что означает «Имя b не объявлено». Как не объявлено?! Ведь в первой процедуре мы b объявили! В чем дело? Дело в так называемых областях видимости. Закон такой:
Переменная, объявленная внутри процедуры, не видна снаружи, в том числе и из других процедур.
Происходит вот что: Когда первая процедура выполняется, ее переменная b живет и здравствует, но второй процедуре от этого никакой выгоды нет, так как она сама в это время спит летаргическим сном и ждет своей очереди. Когда же мы ее будим щелчком по кнопке Button2, то оказывается, что первая процедура уже заснула, а значит и ее переменная b уничтожена. Уничтожена – не уничтожена! Какая разница, если это все равно чужая переменная, то есть принадлежащая другой процедуре?!
Умный VB предвидит эту ситуацию и подчеркивает переменную b во второй процедуре, намекая, что неплохо бы ее там объявить. Ну что ж, объявим, добавив оператор во вторую процедуру:
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
Dim b As Integer = 1
a = b
End Sub
Теперь все в порядке и все работает.
Стеклянные стены. Вопрос: а почему VB не жаловался на переменную a? Ведь мы ее не объявляли ни в одной процедуре. Ответ: а потому что мы ее объявили вне процедур. Закон такой:
Переменная, объявленная вне процедур, видна изо всех процедур окна кода.
Создается и инициализируется такая переменная раньше тех, что объявлены в процедурах, а уничтожается позже.
Тезки. Еще один вопрос, «философский»: правда ли, что переменная b из первой процедуры и переменная b из второй процедуры – это одна и та же переменная, время от времени рождающаяся и умирающая, или же это «тезки» – две разные переменные, только с одним именем? Ответ однозначный: верно второе. В доказательство рассмотрим ситуацию, когда обе процедуры работают одновременно:
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim b As Integer
b = 11
Процедура()
Debug.WriteLine(b)
b = 4
End Sub
Sub Процедура()
Dim b As Integer
b = 8
Debug.WriteLine(b)
End Sub
После щелчка по кнопке Button3 компьютер напечатает вот что:
Запустите проект в пошаговом режиме. После выполнения оператора b=11 компьютер прыгает на процедуру пользователя, создает ее переменную b и начинает процедуру выполнять. Обратите внимание, что выполнение первой процедуры при этом не завершено, а значит и переменная b этой процедуры не уничтожена. К сожалению, пошаговый режим не показывает нам разницу значений этих переменных, однако оператор Debug.WriteLine(b) делает это безошибочно. Первой выполняется печать в процедуре пользователя и, естественно, печатается 8. Затем компьютер возвращается в первую процедуру, прямо к выполнению ее оператора Debug.WriteLine(b). Если бы переменная b была одна на обе процедуры, то конечно была бы напечатана еще раз 8. Однако у каждой из процедур своя переменная, а чужая вообще не видна, поэтому оператор Debug.WriteLine(b) спокойно печатает свою переменную b, которая равна 11.
Итак:
Переменные с одинаковыми именами, объявленные в разных процедурах, являются разными переменными. Не путайте.
Терминология. Подводим итоги:
Мы обнаружили две области видимости переменных: конкретная процедура или все окно кода.
Если переменные объявлены внутри процедуры, то они и использованы могут быть только внутри нее. Их зона видимости – эта конкретная процедура. Называют такие переменные локальными переменными.
Параметры процедуры тоже являются локальными переменными в этой процедуре, так как они объявлены именно в ее заголовке.
Если переменные объявлены вне процедур, то они могут быть использованы во всем окне кода внутри любой процедуры. Называют такие переменные модульными переменнымиили переменными уровня модуля. О причине такой терминологии поговорим попозже (21.9).
Тезки из разных областей. Теперь вопрос относительно модульной переменной: что будет, если нам захотелось назвать одинаковыми именами локальную переменную и переменную уровня модуля? Проверим:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim a As Integer = 5
Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
Dim a As Integer = 44
Debug.WriteLine(a)
End Sub
End Class
Здесь компьютер напечатает
Спрашивается: что это было – одна и та же переменная или разные? А если разные, то почему предпочли локальную, ведь из процедуры видны обе? Или нет? Ответом будет закон «Своя рубашка ближе к телу»: Если к вам в гости пришли Петя и Коля, а у вас в доме уже живет член семьи Коля, то вы пришедшего Колю не пускаете на порог. А с Петей все нормально. В переводе на язык программистов:
Локальная и модульная переменные с одинаковыми именами являются разными переменными, причем модульная переменная не видна из процедуры, где объявлена локальная переменная с тем же именем.
Говорят: локальная переменная затеняет в своей процедуре модульную с тем же именем.
Тщательно пережевывайте пищу. Если вы поняли все, что я только что объяснял, то без компьютера определите, что напечатает программа:
Public Class Form1
Inherits System.Windows.Forms.Form
Windows Form Designer generated code
Dim x As Integer 'x - модульная переменная
Dim z As Integer 'z - модульная переменная
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
x = 1 : z = 2 'x и z - модульные переменные
B()
Debug.WriteLine(x & " " & z) 'x и z - модульные переменные
End Sub
Private Sub B()
Dim x As Integer 'x - локальная переменная
Dim y As Integer 'y - локальная переменная
x = 20 : y = 30 : z = 40
End Sub
End Class
Ответ приведен в конце подраздела.
Если с ответом не сходится, то значит вы поняли не все, и вот вам пояснение:
Оператор Debug.WriteLine(x & " " & z) находится снаружи процедуры В, и поэтому локальная переменная х=20, объявленная внутри В, из него не видна. Зато прекрасно видна модульная переменная х=1, которую он и печатает. Переменная же z не объявлена внутри В, поэтому она является модульной переменной, и оператор z=40 с полным правом меняет ее значение с 2 на 40.
А теперь приведу длинное пояснение «ближе к железу»:
Переменная х, объявленная снаружи процедуры, это совсем другая переменная, чем х, объявленная в процедуре, и помещаются эти переменные в разных местах памяти. Поэтому и не могут друг друга испортить. Вы можете вообразить, что это переменные с разными именами xмод и xлок.
Переменная, объявленная снаружи процедуры, видна из любой процедуры окна кода и каждая процедура может ее испортить. Однако, когда процедура натыкается на переменную x, объявленную внутри самой этой процедуры, то она, благодаря описанному выше механизму, работает только с ней и не трогает переменную x, объявленную снаружи.
Вот порядок выполнения программы:
В оперативной памяти VB отводит ячейки под Х мод и Z мод.
Процедура Button1_Click начинает выполняться с присвоения значений Х мод = 1 и Z мод = 2. Почему мод, а не лок? Потому что переменные с именами X и Z в процедуре Button1_Click не объявлены, а значит волей-неволей процедуре приходится пользоваться модульными переменными.
Вызывается процедура В. При этом в оперативной памяти отводится место под Х лок и У лок.
Присваиваются значения Х лок = 20, У лок = 30 и Z мод = 40.
Программа выходит из процедуры В. При этом исчезают переменные Х лок (20) и У лок (30). А Z мод (40) остается.
Компьютер печатает Х мод = 1 и Z мод = 40.
Таким образом программа напечатает 1 и 40.