Статьи

Понимание переменных, массивов, циклов и нуля: аналогия с запиской после публикации

Что на самом деле делает var , и почему установка myObject = null фактически не удаляет объект? Эти вопросы связаны с фундаментальной концепцией кодирования, относящейся к тому, какой язык вы предпочитаете: AS3, JavaScript или C #, и могут быть поняты с помощью нескольких общих предметов из шкафа канцелярских товаров.


Давайте начнем с основ. Предположим, вы хотите сохранить возраст вашего друга Билла:

1
var ageOfBill:Number = 24;

(Я собираюсь использовать AS3 для этого примера, но основные понятия одинаковы в JavaScript и C #.

В JavaScript синтаксис практически одинаков, но мы не указываем, что age это число:

1
var ageOfBill = 24;

В C # мы не используем ключевое слово var , но мы указываем тип переменной:

1
short ageOfBill = 24;

Надеюсь, не настолько, чтобы запутаться.)

Так что здесь происходит? Думайте об этом так:

  • var (или short в C #) означает «получить новую заметку Post-it».
  • ageOfBill означает «написать ageOfBill сверху, пером».
  • = 24 означает «напиши 24 на заметке карандашом».
Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Что если позже мы поймем, что Билл на самом деле моложе, чем мы думали?

1
2
3
var ageOfBill = 24;
//…later…
ageOfBill = 20;

Это просто означает, что мы нашли нашу заметку ageOfBill , ageOfBill 24 и вместо нее написали 20 .

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Мы могли бы написать var снова:

1
2
3
var ageOfBill:Number = 24;
//…later…
var ageOfBill:Number = 20;

… но это не очень хороший код, потому что var говорит: «получить свежую заметку». Если вы сделаете это, компилятор, как правило, выяснит, что вы имеете в виду — то есть, что вы хотите изменить то, что написано в существующей ageOfBill Post-it, вместо того, чтобы фактически получить новую — но он, вероятно, будет жаловаться.

1
Warning: #3596: Duplicate variable definition.

Это зависит от языка и вашей среды программирования.

Так можем ли мы попросить компилятор получить свежую заметку Post-it и написать на ней метку ручкой, не написав на ней что-нибудь карандашом? Возможно, мы могли бы сделать это для друга Билла Марти, чей возраст мы не знаем:

1
var ageOfMarty:Number;

На самом деле (по крайней мере в AS3) это даст свежую заметку Post-it, напишите ageOfMarty сверху, пером … и затем напишите начальное значение по умолчанию 0 там карандашом:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Таким образом, другими словами, мы не можем иметь такую ​​заметку Post-it, если она не принимает какое-то значение.

Хорошо, а что если мы хотим сохранить возраст лучшего друга Билла Теда, который, как мы знаем, ровесник?

1
var ageOfTed:Number = ageOfBill;

Здесь происходит то, что компьютер смотрит на ageOfBill Post-it, затем копирует число, написанное на нем карандашом, в новый Post-it, на котором он пишет ageOfTed поверх ручки в ручке.

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Это всего лишь копия; если мы затем изменим значение ageOfBill это не повлияет на ageOfTed :

1
ageOfBill = 21;
Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Так! Это все довольно просто, и, возможно, даже интуитивно понятно. Теперь поговорим о первой общей болевой точке: массивах.


Думайте о массиве как связыватель кольца.

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

(Я собирался сказать ролодекс …

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

… но я понял, что никогда не видел ни одного в реальной жизни.)

Каждый лист в переплете похож на одну из заметок Post-it, за исключением того, что в верхней части не написано перо. Вместо этого мы ссылаемся на каждый лист по имени подшивки и номеру страницы листа.

Давайте предположим, что у нас есть массив всех наших друзей в произвольном порядке. Кто на первой странице (страница № 0)?

1
trace(friends[0]);

( trace() просто записывает строку в выходные данные отладки; в JavaScript вы можете использовать console.log() а в C # вы можете использовать Console.WriteLine() для той же цели.)

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Это Билл!

Итак, что же делает следующая строка?

1
var firstFriend:String = friends[0];

Он получает новую заметку Post-it (из-за ключевого слова var ), firstFriend записывает в firstFriend , а затем копирует все, что написано на первой странице подшивки, на эту заметку карандашом.

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

(Помните, String просто означает кусок текста.)

Мы можем переписать то, что написано на любой странице подшивки, как и в заметках Post-it:

1
friends[0] = «Kyle»;
Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

… и, конечно, это не влияет на firstFriend пост-пост.

Связыватель является удачной аналогией, потому что — как и в случае с массивом — вы убираете страницы, добавляете новые и переставляете их. Но помните, что отдельные страницы действуют так же, как заметки Post-it, за исключением того, что у них нет собственных меток пера, только номера страниц.

Надеюсь, все еще довольно просто. Итак, вот интересный вопрос: что происходит, когда вы делаете следующее?

1
var listOfNames:Array = friends;

Э-э …


Я немного обманул здесь, потому что я много говорил о массивах, даже не объясняя, как мы их создали. Итак, давайте займемся этим сейчас.

Предположим, вы вводите:

1
var friends:Array = [«Bill», «Marty», «Ted»];

…Что происходит?

Ну, как обычно, var friends означает, что мы получаем свежую заметку Post-it и пишем friends сверху, пером:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Но что мы пишем на нем карандашом?

Это вопрос с подвохом: мы ничего не пишем.

Видите, Array означает «получить новое связующее кольцо». И ["Bill", "Marty", "Ted"] означает «положить три страницы в переплет с этими именами»:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации
Вы не можете видеть страницы «Марти» и «Тед», но они полностью там.

А потом? Это просто! Прикрепляем пост-заметку friends к обложке переплета:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Теперь, когда мы пишем:

1
trace(friends[0]);

… мы знаем, что нам нужно найти friends надписью Post-it, а затем посмотреть на то, что написано на первой странице (страница № 0) переплетчика, к которому он прикреплен.

На самом деле существует очень мало типов переменных, в которых значение записывается в заметку Post-it карандашом. В AS3 единственными такими типами (называемыми «примитивами») являются:

  • Number
  • String
  • int
  • uint
  • Boolean

Для всего остального — Object , MovieClip , XML и т. Д. — мы прикрепляем заметку Post-it к самому элементу.

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

(Детали немного отличаются в JavaScript и C #, но в целом применима та же идея.)

Итак, вернемся к нашему предыдущему вопросу. Когда мы печатаем:

1
var listOfNames:Array = friends;

…что происходит?

Опять же, мы знаем, что var listOfNames означает «получить новую заметку Post-it и написать listOfNames поверх пера». И теперь мы знаем, что Array означает, что мы будем прикреплять заметку Post-it к чему-то (переплет), а не писать что-то на Post-it карандашом.

Ранее, когда мы делали нечто подобное, мы копировали содержимое одной заметки Post-it на другую. Так вот, должны ли мы получить новый новый переплет и скопировать в него все страницы из подшивки friends ?

Вообще-то, нет! Все, что мы делаем, — это listOfNames новую listOfNames Post-it к тому же переплету, что и заметка friends Post-it.

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Теперь friends и listOfNames ссылаются на один и тот же массив . Так что если мы напишем:

1
listOfNames[0] = «Emmett»;

… тогда friends[0] также будут Emmett , потому что listOfNames[0] и friends[0] ссылаются на одну и ту же страницу в том же переплете! И поскольку эта страница содержит только String (помните, что это «примитивный» тип), то мы просто стерли все, что было написано на этой странице ранее, и вместо этого написали Emmett .


При таком взгляде null довольно легко понять. Это утверждение:

1
friends = null;

… просто означает «удалить заметку friends из того, к чему она сейчас привязана».

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

friends Пост-он все еще существует , он просто ни к чему не прилип. Так что если вы наберете:

1
trace(friends[0]);

или

1
friends[0] = «Henry»;

… тогда вы получите сообщение об ошибке, потому что вы пытаетесь сослаться на первую страницу переплета, на которой friends постят — он застрял — но он ни к чему не привязан!

Итак, чтобы быть ясным, установка friends = null не влияет на связыватель вообще. Вы все еще можете получить к нему доступ через listOfNames . И вы даже можете напечатать:

1
friends = listOfNames;

… чтобы вернуться к старой ситуации:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Как я уже сказал, установка friends = null не влияет напрямую на связыватель, но может иметь косвенный эффект.

Видите ли, если нет заметок Post-it, прикрепленных к переплету вообще, то никто не сможет снова получить доступ к переплету. Это будет просто лежать, совершенно недоступно. Но то, что все эти связующие (и другие объекты) лежат, совершенно заброшенные, — это настоящая трата пространства — они загромождают память компьютера.

Вот где приходит сборщик мусора . Это инструмент, который периодически проверяет наличие любых «потерянных» объектов и выбрасывает их в мусорное ведро — и как только они исчезают, они исчезают навсегда; если массив является сборщиком мусора, то и все его страницы тоже.

Для большинства практических целей это не влияет на вас вообще; объекты будут собирать мусор только в том случае, если они потеряны и не могут быть найдены вашим кодом. Если у вас их много, то вы можете время от времени замечать небольшую задержку, когда сборщик мусора выполняет свою работу (для активного сбора мусора требуется немного времени). Преимущество состоит в том, что это освобождает больше места (памяти) для вашего приложения.

(Если вы хотите узнать больше об этой теме, прочитайте сообщения Дэниела Сидиона о сборке мусора и объединении объектов .)


Ладно, есть еще одна важная концепция, и она самая сложная.

Рассмотрим этот фрагмент:

1
2
3
4
var firstFriendDetails:Array = [«Bill», 20];
var secondFriendDetails:Array = [«Marty», 16];
var thirdFriendDetails:Array = [«Ted», 20];
var allFriends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails];

Как, черт возьми, это работает?

Давайте начнем с того, что мы знаем. Первые три строки просты; для каждого мы:

  • Получить свежее связующее.
  • Вставьте страницу с именем друга, написанным на нем карандашом.
  • Для nsert другая страница с возрастом друга, написанным на нем карандашом.
  • Получите свежий Post-it и напишите соответствующий ярлык на ручке.
  • Прикрепите Post-it к переплету.
Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Что касается этой строки:

1
var allFriends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails];

… нам понадобится строка и лента.

Мы можем думать об этой одной строке как об эквивалентном фрагменте:

1
2
3
4
var allFriends:Array = [];
allFriends[0] = firstFriendDetails;
allFriends[1] = secondFriendDetails;
allFriends[2] = thirdFriendDetails;

Первая строка проста: получите новую подшивку и новую заметку Post-it, напишите allFriends на allFriends Post-it и приклейте ее к переплету.

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Что касается второй строки:

1
allFriends[0] = firstFriendDetails;

Помните, что я сказал, что каждая страница в переплете похожа на заметку Post-it, за исключением того, что ничего не написано пером. Если бы первой страницей была Post-it, то мы просто прикрепили бы ее к передней части переплета firstFriendDetails , верно?

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

… но это не может быть как на передней части этого переплета, так и внутри другого переплета. Итак, вместо этого мы используем строку:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

То же самое для двух других:

Понимание переменных, массивов, циклов и нулей: аналогия с запиской после публикации

Поэтому, когда мы хотим узнать, на что allFriends[2] , мы просто открываем allFriends на этой странице и следуем за строкой, что, конечно, приводит к thirdFriendDetails .

Аналогично, для allFriends[1][0] мы сначала allFriends[1] , к какому связующему allFriends[1] , а затем посмотрим на первую страницу этого связывателя … поэтому allFriends[1][0]Marty !


Теперь соберите всю эту информацию и помните об этом при чтении этого фрагмента:

01
02
03
04
05
06
07
08
09
10
var friends:Array = [«Bill», «Marty», «Ted», «Emmett», «Henry»];
var currentIndex:int = 0;
var currentFriend:String;
 
while (currentIndex < 5) {
    currentFriend = friends[currentIndex];
    trace(currentFriend);
}
var lastFriend:String = currentFriend;
trace(lastFriend);

Что если мы currentFriend значение currentFriend внутри цикла?

01
02
03
04
05
06
07
08
09
10
var friends:Array = [«Bill», «Marty», «Ted», «Emmett», «Henry»];
var currentIndex:int = 0;
var currentFriend:String;
 
while (currentIndex < 5) {
    currentFriend = friends[currentIndex];
    currentFriend = «Herbert»;
    trace(currentFriend);
}
trace(friends[0]);

Что, если массив содержит непримитивные объекты (мувиклипы, изображения, массивы, трехмерные объекты и т. Д.)?

01
02
03
04
05
06
07
08
09
10
var friends:Array = [firstFriend, secondFriend, thirdFriend, fourthFriend, fifthFriend];
var currentIndex:int = 0;
var currentFriend:MovieClip;
 
while (currentIndex < 5) {
    currentFriend = friends[currentIndex];
    currentFriend = sixthFriend;
}
 
//What is the value of friends[0] now?

Наконец, что если массив содержит другие массивы, которые сами содержат примитивы?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
var firstFriendDetails:Array = [«Bill», 20];
var secondFriendDetails:Array = [«Marty», 16];
var thirdFriendDetails:Array = [«Ted», 20];
var fourthFriendDetails:Array = [«Emmett», 50];
var fifthFriendDetails:Array = [«Henry», 36];
var friends:Array = [firstFriendDetails, secondFriendDetails, thirdFriendDetails, fourthFriendDetails, fifthFriendDetails];
 
var currentIndex:int = 0;
var currentFriend:Array;
 
while (currentIndex < 5) {
    currentFriend = friends[currentIndex];
    currentFriend[0] = «John»;
    currentFriend = [«Herbert», 29];
}

Как вы думаете, что ценность friends[3][0] будет после этого?


Вот несколько других важных замечаний, которые вы должны знать:

В AS3 и JavaScript объекты похожи на массивы, за исключением того, что каждая страница упоминается по метке, а не по номеру страницы. Таким образом, вы можете ввести:

1
2
3
4
var detailsOfBill:Object = {};
detailsOfBill.title = «Esquire»;
detailsOfBill.bandName = «Wyld Stallyns»;
detailsOfBill.allNames = [«Bill», «S.», «Preston»];

… и это похоже на получение нового переплета, наклеивание detailsOfBill посте на лицевой стороне и заполнение ее тремя страницами. На первой странице title надписи написан пером, а карандашом написано слово Esquire ; вторая страница имеет лейбл bandName в перо и Wyld Stallyns в карандаш. На третьей странице есть надпись allNames но на ней ничего не написано карандашом; вместо этого строка прикрепляет ее к другому обычному переплету, страницы которого не помечены: первая страница говорит о Bill , вторая говорит о S. , а третья говорит о Preston , все карандашом.

(Чтобы сделать вещи еще более запутанными, массивы технически представляют собой особую форму объекта. И если вы считаете, что это плохо, функции также можно рассматривать как тип объекта! Но это тема для будущей статьи …)

Я сказал, что объекты собирают мусор, если к ним не прикреплены записи Post-it, но это упрощение. Если одна страница связывателя указывает на объект через строку (т. myArray[0] = myObject Если myArray[0] = myObject или аналогичный), то этот объект не будет подвергаться сборке мусора. То же самое касается, если страница связывателя указывает на другой связыватель (массив), или если страница связывателя объекта указывает на другой связыватель объекта … и так далее. Это даже применимо, если единственный способ получить доступ к объекту — через Post-it, прикрепленный к подшивке, у которой одна страница привязана к другой подшивке, столько слоев, сколько вам нужно.

По сути, сборщик мусора собирает элемент, только если он не может быть достигнут через любую другую переменную.

Это объясняет, почему объекты на экране обычно не могут быть собраны. В AS3, если MovieClip или другой тип DisplayObject присутствует в списке отображения, он автоматически добавляется к тому, что по сути является скрытым массивом экранных объектов (доступ к которому можно получить через getChildAt () ). Подобные структуры существуют в JavaScript и C #. Таким образом, если на экране появляется изображение и вы удаляете все ссылки на него из переменных, массивов и объектов, оно все равно не будет собирать мусор, пока вы не удалите его из списка отображения.


Я надеюсь, что это помогает прояснить ситуацию. Это, безусловно, запутанная концепция, когда вы впервые сталкиваетесь с ней: некоторые переменные на самом деле содержат значение , а другие просто содержат ссылку на объект. Не беспокойтесь, если вы не уверены на 100%, что именно происходит; это будет иметь больше смысла после небольшой практики (и нескольких ошибок!).

Однако, если у вас есть какие-то конкретные вопросы по этому поводу, просто оставьте комментарий ниже, и я сделаю все возможное, чтобы ответить.

Спасибо за прочтение!