Механизм действия ссылочных типов
Рассмотрим три похожих фрагмента:
Фрагмент 1: | Фрагмент 2: | Фрагмент 3: | |||
Dim A As Integer
Dim B As Integer A = 5 B = A Debug.WriteLine(B) A = 100 Debug.WriteLine(B) | Dim A(2) As Integer
Dim B(2) As Integer A(2) = 5 B = A Debug.WriteLine(B(2)) A(2) = 100 Debug.WriteLine(B(2)) | Dim A As New Класс
Dim B As New Класс A.Поле= 5 B = A Debug.WriteLine(B.Поле) A.Поле= 100 Debug.WriteLine(B.Поле) | |||
Что будет напечатано: | Что будет напечатано: | Что будет напечатано: | |||
5
5 | 5
100 | 5
100 |
Вот код класса для третьего фрагмента:
Class Класс
Public Поле As Integer
Public Поле1 As Integer
End Class
Чтобы разобраться в работе трех фрагментов, вспомним, что все данные, с которыми работает любая программа, во время работы этой программы хранятся в ячейках оперативной памяти. Все ячейки пронумерованы, номер ячейки называют еще ее адресом (здесь я не вхожу в подробности о том, что на самом деле пронумерованы не ячейки, а байты).
1. Обычный тип. То, что в первом фрагменте распечатались две пятерки, вполне очевидно, но пояснения этой очевидности все-таки нужны, чтобы была понятней работа других фрагментов.
При выполнении оператора Dim A, B As Integer переменным A и B отводится в памяти по ячейке (которые заполняются нулями). При выполнении оператора A = 5 в ячейке A появляется пятерка:
Адреса | Значения | ||||||||||||
A | 000101 | 5 | |||||||||||
B | 000102 | 0 |
При выполнении оператора B = A пятерка из ячейки A копируется в ячейку B, а то, что впоследствии пятерка в ячейке A меняется на сотню, никак не влияет на содержимое ячейки B.
Все происходит привычно, потому что тип Integer, к которому принадлежат переменные A и B, относится к обычным типам.
Во втором и третьем фрагментах тоже на первый взгляд должны были быть напечатаны две пятерки. Но все произошло по-другому, так как массивы (второй фрагмент) и классы (третий фрагмент) представляют не обычные, а ссылочные
типы, у которых механизм предоставления памяти совсем другой. Разберемся в нем.
2. Массивы. Рассмотрим второй фрагмент. Вот что находится в памяти после выполнения оператора A(2) = 5:
Адреса |
Значения |
Адреса |
Значения |
||
A |
000101 |
002040 |
|
002040 |
0 |
002041 |
0 |
||||
002042 |
5 |
||||
B |
000102 |
002300 |
|
002300 |
0 |
002301 |
0 |
||||
002302 |
0 |
Пояснения: При выполнении операторов
Dim A(2) As Integer
Dim B(2) As Integer
каждой из переменных A и B отводится в памяти одна
ячейка и еще дополнительная область памяти для хранения элементов массива (по три ячейки, которые заполняются нулями). В этой одной ячейке находится адрес первой ячейки из области памяти или, по-другому, ссылка на эту область памяти. Ссылку еще называют указателем. Вот такой механизм. Таким образом, значением переменной A является совсем не набор из трех чисел, а всего лишь адрес, номер ячейки. Запомните это.
Когда выполняется оператор A(2) = 5, компьютер, зная, что массив является ссылочным типом, рассматривает значение 002040 в ячейке для переменной A именно, как адрес, а не число типа Integer, и поэтому не пытается записать в эту ячейку число 5, а отправляется по указанному адресу, где находит три ячейки. В какую из них записать число 5, ему указывает индекс 2.
Рассмотрим, как выполняется оператор B = A. Вам могло показаться по аналогии с первым фрагментом, что при этом все три числа из области памяти для A копируются в область памяти для B. Но нет, здесь по своему строгому, но привычному вам закону выполняется оператор присваивания. Закон этот говорит, что значение переменной A должно быть записано в ячейку для значения переменной B. Поскольку значением переменной A является адрес 002040, то он и копируется в ячейку для значения переменной B. Вот что получается:
Адреса |
Значения |
Адреса |
Значения |
||
A |
000101 |
002040 |
|
002040 |
0 |
002041 |
0 |
||||
002042 |
5 |
||||
B |
000102 |
002040 |
002300 |
0 |
|
002301 |
0 |
||||
002302 |
0 |
Вы видите, что три числа в области памяти 002300 не изменились. Почему же тогда первый из двух операторов Debug.WriteLine(B(2)) печатает пятерку, а не ноль? Вот почему. Механизм обращения к памяти здесь тот же, что и описанный парой абзацев ранее механизм обращения к памяти при выполнении оператора A(2) = 5. Компьютер, зная, что массив является ссылочным типом, рассматривает значение 002040 в ячейке для переменной B, как адрес, и отправляется по указанному адресу, где находит три ячейки. Индекс 2 указывает ему, что для печати нужно выбрать значение из последней ячейки, а там находится пятерка.
А кому же теперь нужна область 002300? В том-то и дело, что никому! Ссылок на нее теперь не существует и воспользоваться ей уже нельзя, даже если вы и захотите. Она превратилась в мусор (garbage). Уборкой мусора занимается VB и делает это без вашего ведома и участия.
Я думаю, что теперь мне не нужно объяснять, почему второй из двух операторов Debug.WriteLine(B(2)) напечатает сотню.
3. Классы. Аналогично рассматривается третий фрагмент. При создании объекта из класса, как и при создании массива, в памяти под объект отводится одна ячейка и еще дополнительно область памяти для хранения элементов объекта (в нашем случае две ячейки для полей, которые заполняются нулями). В этой одной ячейке находится ссылка на область памяти. Вот что будет в памяти при окончании работы третьего фрагмента:
Адреса |
Значения |
Адреса |
Значения |
|||
A |
000101 |
002040 |
|
Поле |
002040 |
100 |
Поле1 |
002041 |
0 |
||||
B |
000102 |
002040 |
Поле |
002300 |
0 |
|
Поле1 |
002301 |
0 |
Что напечатает фрагмент:
Dim A, B, C As Класс
A = New Класс
A.Поле = 100
B = A
C = New Класс
C.Поле = 50
A = C
Debug.WriteLine(A.Поле)
Debug.WriteLine(B.Поле)