ПОНЯТНО О Visual Basic NET (том 2)

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


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

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  компьютер напечатает вот что:
8
11
Запустите проект в пошаговом режиме. После выполнения оператора 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
Здесь компьютер напечатает
44
Спрашивается: что это было – одна и та же переменная или разные? А если разные, то почему предпочли локальную, ведь из процедуры видны обе? Или нет? Ответом будет закон «Своя рубашка ближе к телу»: Если к вам в гости пришли Петя и Коля, а у вас в доме уже живет член семьи Коля, то вы пришедшего Колю не пускаете на порог. А с Петей все нормально. В переводе на язык программистов:
Локальная и модульная переменные с одинаковыми именами являются разными переменными, причем модульная переменная не видна из процедуры, где объявлена локальная переменная с тем же именем.
Говорят: локальная переменная затеняет в своей процедуре модульную с тем же именем.
Тщательно пережевывайте пищу. Если вы поняли все, что я только что объяснял, то без компьютера определите, что напечатает программа:
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.

Содержание раздела