Механизм действия ссылочных типов
Рассмотрим три похожих фрагмента:
| Фрагмент 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
|
Задание 21.
Что напечатает фрагмент:
Dim A, B, C As Класс
A = New Класс
A.Поле = 100
B = A
C = New Класс
C.Поле = 50
A = C
Debug.WriteLine(A.Поле)
Debug.WriteLine(B.Поле)