Области видимости переменных

Шапка-невидимка. Введите в окно кода такой код:

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.

Наши рекомендации