Español Português
preview
От начального до среднего уровня: Массив (II)

От начального до среднего уровня: Массив (II)

MetaTrader 5Примеры | 21 апреля 2025, 07:23
264 0
CODE X
CODE X

Введение

В предыдущей статье От начального до среднего уровня: Массив (I), мы начали говорить об одной из самых сложных и трудных для освоения тем в программировании. Знаю, что многие могут сказать, что это довольно просто, и в моих словах о том, что это не так, просто нет смысла. Однако, по мере продвижения вперед, вы поймете, почему я говорю, что эта тема сложна и трудна для освоения. Потому что это является основой для всего остального.

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

Здесь самая большая трудность заключается в том, что по сути, мы должны представлять какие-то вещи, не затрагивая другие, еще не рассмотренные темы. И я пытаюсь объяснить необходимость их создания и применения, без фактической демонстрации. Это связано с тем, что гораздо важнее понимать концепцию, лежащую в основе того или иного инструмента, чем сам инструмент. А поскольку многие программисты игнорируют концепции и сосредотачиваются только на инструментах, иногда у них быстро заканчиваются варианты. Это потому, что задачи решают не инструменты. Их решают концепции. Как молоток: им можно что-то к чему-то прибить. Но проблему решает не инструмент, а концепция. Да, молотком можно что-то прибить, но это не единственное, что можно делать с его помощью. С таким же успехом молотком можно что-то разбить. Хотя для этого и есть инструмент получше — кувалда.

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


ROM-массив

В принципе, существует два способа объявить массив. Один из способов — объявить статический массив, а другой — объявить его как динамический массив. Хоть на практике понять каждый из них относительно просто, есть некоторые небольшие тонкости, которые затрудняют или полностью исключают правильное понимание того, что такое массив динамический и что такое массив статический. Это особенно характерно для других языков. Особенно для C и C++. Тем не менее, даже в MQL5 у вас порой могут возникать сомнения. Это связано с тем, что основное и существенное различие между статическим массивом и массивом динамическим заключается в возможности изменения его размера во время выполнения кода.

Рассуждая таким образом, кажется, что классифицировать массив как динамический или статический довольно просто. При этом стоит помнить, что строка — это тоже массив. Только массив особый. Это затрудняет классификацию строки как статической или динамической. Но не важно, давайте игнорировать этот факт. И мы не будем работать напрямую с типом string. Таким образом мы избежим путаницы и начнем разбираться в этой теме.

По сути, и это даже не обсуждается, независимо от того, какой язык программирования вы будете изучать в будущем, массив константного типа всегда будет статическим массивом. Неважно, какой язык вы используете, такой массив ВСЕГДА будет статическим.

Хорошо, но на каком основании мы можем утверждать, что константный массив всегда имеет статический тип? Причина в том, что константный массив (а рассматривать его следует именно так) на самом деле представляет собой ROM. Возможно, на данный момент это не имеет особого смысла. Это связано с тем, что наше приложение всегда будет работать в определенной области оперативной памяти. Как же мы можем представить себе ROM, откуда можно только считывать данные, внутри оперативной памяти, которая, в свою очередь, позволяет нам считывать и записывать информацию в любой своей точке? Это бессмысленно.

Именно по этой причине, мой любезный читатель, и необходимо хорошо понимать, что такое константы и переменные. Однако, с точки зрения нашего приложения, мы можем использовать ROM, не ставя под угрозу целостность и удобство использования всего приложения. На самом деле, использование константных массивов для создания ROM небольших размеров в приложении встречается часто. Это довольно распространено в более сложных кодах или в тех, которые требуют очень конкретных значений, которые не будут меняться.

Подумайте о сообщениях, которые используются для информирования о чем-либо. Вы можете собрать их все вместе и перевести на разные языки. В момент загрузки приложения будет проверяться язык сообщений и будет создаваться ROM. Таким образом, приложением cмогут пользоваться разные пользователи, говорящие на разных языках. Это было бы приложением для массива, который находился бы в ROM. Однако, если вы используете файлы с переводами и вам необходимо загрузить правильный файл, у нас больше не будет массива типа ROM. Или чисто статического. Хотя это все равно можно было бы так классифицировать.

Чтобы было легче понять эту концепцию, поскольку она важна для того, чтобы знать, как и когда можно использовать вызовы стандартной библиотеки для управления массивами, давайте рассмотрим простой пример. Такие вещи хорошо видны на практике.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const char Rom_01[]  = {72, 101, 108, 111, 33};
07.     const char Rom_02[8] = {'H', 'e', 'l', 'o', '!'};
08. 
09.     PrintFormat("%c%c%c%c%c%c", Rom_01[0], Rom_01[1], Rom_02[2], Rom_01[2], Rom_02[3], Rom_01[4]);
10. }
11. //+------------------------------------------------------------------+

Код 01

Результат выполнения кода 01 приводится ниже.

Изображение 01

Это довольно просто понять. Однако, здесь нас интересуют строки шесть и семь. В обоих случаях создаются два ROM с одинаковым, по-видимому, содержимым, но совершенно разных размеров. Но подождите-ка, как это возможно? Я не понимаю. Глядя на код, я вижу, что они содержат по пять элементов. И что все показанные элементы одинаковы. Они просто по-разному объявлены. Но несмотря на это, я все равно не могу понять, почему эти ROM отличаются. Мне это не кажется логичным.

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

ROM_01, объявленный в строке шесть, состоит из пяти элементов. Обычно в коде, где мы создаем статический массив, мы встречаем именно такой тип объявления. То есть, внутри скобок нет никакого значения. Хотя, и это сбивает с толку очень многих, не объявлять значения в скобках в случае константного массива — это совершенно нормально. И даже довольно распространено. Это связано с тем, что вы можете добавить значения в массив ВО ВРЕМЯ РЕАЛИЗАЦИИ. Эти добавленные значения будут полностью заблокированы и не могут быть изменены впоследствии. При этом, размер массива в итоге становится равен количеству содержащихся в нем элементов.

Теперь, что касается ROM_02, то тут все немного по-другому. В данном случае, у нас объявлено пять элементов. Однако, поскольку мы утверждаем, что массив состоит из восьми элементов, три из этих элементов неизвестны. Нужно быть осторожными при использовании этих неизвестных элементов. Это связано с тем, что они могут иметь определенное значение, но также может оказаться, что значение будет совершенно случайным. Это будет зависеть от того, как компилятор создаст массив. Помните, что мы работаем с массивами константного типа.

В любом случае, в обоих случаях у нас будет массив статического типа. Другими словами, количество элементов, НЕ ИЗМЕНИТСЯ на протяжении всего времени жизни массива. В одном случае у нас всегда будет пять элементов, а в другом — всегда восемь. Не забываем, что первый элемент имеет индекс, равный нулю. То есть отсчет всегда начинается с нуля.

Отлично. Мы знаем, сколько элементов в каждом из массивов. Но что произойдет, если мы попытаемся прочитать элемент с индексом, равным пяти? Помните, что отсчет начинается с нуля. Что ж, в этом случае, мы можем провести небольшой эксперимент, чтобы вы могли это проверить. Для этого мы изменим один элемент в коде, чтобы он выглядел так, как показано ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const char Rom_01[]  = {72, 101, 108, 111, 33};
07.     const char Rom_02[8] = {'H', 'e', 'l', 'o', '!'};
08. 
09.     const uchar pos = 6;
10. 
11.     PrintFormat("%c%c%c%c%c%c", Rom_01[0], Rom_01[1], Rom_02[2], Rom_01[2], Rom_02[3], Rom_01[4]);
12. 
13.     PrintFormat("Contents of position %d of the ROM_02 array: %d", pos, Rom_02[pos - 1]);
14.     PrintFormat("Contents of position %d of the ROM_01 array: %d", pos, Rom_01[pos - 1]);
15. }
16. //+------------------------------------------------------------------+

Код 02

Как видим, теперь в девятой строке этого кода 02 есть константа, указывающая, к какому элементу мы хотим получить доступ. В данном случае нам нужен шестой элемент. Однако, он находится на пятой позиции. Вы можете подумать, что поскольку мы ищем элемент на пятой позиции, и у нас есть пять объявленных элементов, мы хотим вывести десятичное значение этого элемента в строках 13 и 14, то есть, 33. По факту, это самый естественный способ мышления. Однако, этот наиболее естественный способ мышления ошибочен. Это происходит потому, что отсчет начинается с нуля. Поэтому, пятый объявляемый элемент на самом деле является элементом, индекс которого равен четырем. Таким образом, когда мы попытаемся получить доступ к пятому индексу, в коде кое-что произойдет. Результат вы можете увидеть ниже.

Изображение 02

Обратите внимание, что здесь происходят две странные вещи. Во-первых, была выполнена строка 13, и показанный результат равен нулю. То есть массив, объявленный в строке 7, содержит скрытые значения. Но самое главное — это сообщение об ошибке, которое можно увидеть на изображении 02. Оно указывает, что в строке 14 кода 02 происходит попытка доступа к чему-то за пределами массива.

Поскольку массив, который будет использоваться в строке 14, — это именно тот массив, объявленный в строке 6, вы можете не знать, почему возникла эта ошибка. А это происходит потому, что вы пытаетесь получить доступ к элементу, который находится на пятой позиции. По идее, поскольку массив состоит из пяти элементов, это должно быть возможно, но, повторю еще раз, отсчет начинается с нуля.

Чтобы подтвердить это, давайте внесем одно изменение. Это изменение находится в девятой строке кода 02. Если изменить значение шесть, которое вы там видите, на значение пять, как показано в строке кода ниже, все изменится.

const uchar pos = 5;

При повторной компиляции и запуске кода 02 с использованием строки, показанной выше, результат в терминале будет таким.

Изображение 03

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

Хорошо, поскольку ROM-массивы всегда являются статическими, в том числе и тогда, когда мы объявляем их динамическими, здесь больше нечего о них сказать. Это связано с тем, что данный тип массива, где попытки записи считаются ошибкой, а код не компилируется, не позволяет нам говорить о чем-то большем по этой теме. Поэтому, давайте перейдем к новой, где поговорим о другом типе массива  немного сложнее. А все потому, что в него можно записывать данные.


RAM-массив

Чтобы понять этот массив, необходимо сначала понять и усвоить тип массива ROM. Объявление может быть статическим или динамическим. В отличие от ROM-массива, содержимое и количество элементов которого постоянны, в RAM-массиве все немного сложнее. Это связано с тем, что количество элементов может быть постоянным, а может и не быть. Кроме того, содержимое каждого элемента может меняться по мере выполнения кода.

Что ж, судя по этим первым словам, вы, возможно, представляете себе, что этот тип массива — сущий кошмар. Хотя для его правильного использования потребуется лишь немного больше внимания. Здесь каждая деталь имеет значение. Однако, есть некоторые детали, связанные с массивами в MQL5, которые я хотел бы, чтобы вы пока игнорировали, мой дорогой читатель. Это связано с тем, что существует особый тип массива, который служит буфером. Это касательно MQL5. Но этот тип будет лучше объяснен в другой раз, когда мы будем говорить о расчетах и программировании, ориентированных на MetaTrader 5. Пока же то, что будет объяснено здесь, не относится к этим массивам, чье назначение — служить буферами данных. Целью настоящей статьи является общее объяснение использования массивов в программировании. Ориентированное не только и не столько на MQL5, но на любой язык программирования.

Хорошо, давайте сделаем следующее: основываясь на последнем коде, представленном в предыдущей теме, удалим зарезервированное слово const из обоих объявлений. Делая это, мы снимаем ограничение массивов. Однако, следует пояснить одну очень важную деталь. Вносимые изменения нацелены на создание кода, показанного ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     char Ram_01[]  = {72, 101, 108, 111, 33};
07.     char Ram_02[8] = {'H', 'e', 'l', 'o', '!'};
08. 
09.     const uchar pos = 5;
10. 
11.     PrintFormat("%c%c%c%c%c%c", Ram_01[0], Ram_01[1], Ram_02[2], Ram_01[2], Ram_02[3], Ram_01[4]);
12. 
13.     PrintFormat("Contents of position %d of the RAM_02 array: %d", pos, Ram_02[pos - 1]);
14.     PrintFormat("Contents of position %d of the RAM_01 array: %d", pos, Ram_01[pos - 1]);
15. }
16. //+------------------------------------------------------------------+

Код 03

При запуске этого кода 03, вы получите тот же результат, что и на изображении 03. Но есть одно принципиальное различие. И оно заключается как раз в том, что МЫ НЕ РАБОТАЕМ С ROM. А работаем с RAM. Это позволяет нам изменить код 03 так, чтобы создать то, что показано далее.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     char Ram_01[]  = {72, 101, 108, 111, 33};
07.     char Ram_02[8] = {'H', 'e', 'l', 'o', '!'};
08. 
09.     uchar pos = 5;
10. 
11.     PrintFormat("%c%c%c%c%c%c", Ram_01[0], Ram_01[1], Ram_02[2], Ram_01[2], Ram_02[3], Ram_01[4]);
12. 
13.     PrintFormat("Contents of position %d of the Ram_02 array: %d", pos, Ram_02[pos - 1]);
14.     PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - 1]);
15. 
16.     Ram_02[pos - 1] = '$';
17.     PrintFormat("Contents of position %d of the Ram_02 array: %d", pos, Ram_02[pos - 1]);
18. 
19.     Ram_01[pos - 1] = '$';
20.     PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - 1]);
21. }
22. //+------------------------------------------------------------------+

Код 04

Посмотрите, код 04 очень нетривиален и любопытен. И именно благодаря тому, что в строках 16 и 19 мы пытаемся изменить значение определенной позиции в массиве. Правильное понимание этого может пробудить любопытство даже у самых искушенных. Итак, когда мы запустим код 04, мы получим результат, показанный ниже.

Изображение 04

Обратите внимание, что значения можно было изменять. Это было бы невозможно, если бы мы использовали массив типа ROM. Прямо сейчас вы, должно быть, задаетесь вопросом: можем ли мы изменять любую информацию внутри массива? Ответ: это зависит от обстоятельств.

Обратите внимание на следующее, мой дорогой читатель: массив в строке шесть, несмотря на то, что объявлен как динамический, по-прежнему остается статическим, поскольку ему не присвоен индекс количества элементов. Это происходит потому, что мы инициализируем его прямо в момент объявления. Массив в седьмой строке полностью статичен, хотя количество возможных элементов в нем больше, чем количество элементов, которые мы объявляем при инициализации массива.

Исходя из этого факта, можно сказать следующее: если мы используем индекс, указывающий на какой-либо элемент, мы можем изменить его значение. Верно? Верно. Фактически, вы могли бы использовать код, аналогичный показанному ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     char r[10] = {72, 101, 108, 108, 111, 33};
07. 
08.     string sz0;
09. 
10.     sz0 = "";
11.     for (uchar c = 0; c < ArraySize(r); c++)
12.         sz0 = StringFormat("%s%03d, ", sz0, r[c]);
13. 
14.     Print(sz0);
15. 
16.     Print("Modifying a value...");
17. 
18.     r[7] = 36;
19. 
20.     sz0 = "";
21.     for (uchar c = 0; c < ArraySize(r); c++)
22.         sz0 = StringFormat("%s%03d, ", sz0, r[c]);
23. 
24.     Print(sz0);
25. }
26. //+------------------------------------------------------------------+

Код 05

При выполнении кода 05, результат будет таким, как мы видим на изображении ниже.

Изображение 05

То есть, это работает. Можно изменить любое значение, при условии, что элемент, к которому мы обращаемся, действительно существует в массиве. Хотя строка 18 сработала только потому, что в строке 6 мы указали, что массив будет содержать 10 элементов.

Хорошо, я думаю, что первая часть понятна. Но всегда ли нам придется работать таким образом? То есть, при объявлении массива, обязательно ли указывать, сколько элементов он должен содержать, или какие именно это будут элементы? На самом деле, это вопрос, вызывающий много сомнений у начинающих. И ответом снова будет фраза: все зависит от обстоятельств.

Однако, и важно, чтобы это было понятно вам, дорогой читатель, для константного массива, то есть массива типа ROM, нужно ВСЕГДА объявлять содержащиеся в нем элементы или их количество в момент создания массива. Важная деталь: объявление самих элементов, в данном случае, обязательно, а вот указание их количества  опционально.

Это правило жесткое и не допускает изменений или интерпретаций. В то же время, для массивов типа RAM нет четко установленного правила. Все зависит от цели или задачи, которых вы хотите достичь. Тем не менее, объявление значений для динамического массива, если тот не является ROM-массивом, встречается не часто. Как видно в строке шесть кода 04, это происходит потому, что таким образом мы преобразуем динамический массив в статический. Нечто подобное происходит и в случае массива в седьмой строке того же кода 04.

Хотя, с точки зрения практического применения, массив, объявленный так, как показано в строке шесть кода 04, можно заменить другим, как можно увидеть чуть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     char  Ram_01[5],
07.           pos = 5;
08.     
09.     Ram_01[0] = 72;
10.     Ram_01[1] = 101;
11.     Ram_01[2] = 108;
12.     Ram_01[3] = 111;
13.     Ram_01[4] = 33;
14. 
15.     PrintFormat("%c%c%c%c%c%c", Ram_01[0], Ram_01[1], Ram_01[2], Ram_01[2], Ram_01[3], Ram_01[4]);
16. 
17.     PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - 1]);
18.     Ram_01[pos - 1] = '$';
19.     PrintFormat("Contents of position %d of the Ram_01 array: %d", pos, Ram_01[pos - 1]);
20. }
21. //+------------------------------------------------------------------+

Код 06

Как видим, в коде 06 теперь есть объявление статического массива в строке шесть. Таким образом мы сообщаем компилятору, что он нам нужен для выделения блока памяти. Это будет сделано полностью автоматически. Так, во время компиляции, компилятор выделит в памяти достаточно места для размещения указанного количества элементов. В данном случае — пяти элементов. А поскольку тип данных — char, пять байт будут выделены и доступны для немедленного использования.

Теперь представьте это выделенное пространство, как переменную объемом в пять байт. Вы можете использовать его так, как вам удобнее. Мы также можем задать произвольное число. Например, если вместо пяти, как показано в строке шесть, мы укажем 100, мы создадим переменную размером 100 байт. Напомню, что максимальная ширина данных в настоящее время составляет 8 байт, или 64 бита. То есть, мы могли бы поместить чуть более 12 таких 8-байтовых переменных в этот 100-байтовый массив.

В любом случае, когда вы запустите код 06, вы увидите то, что показано чуть ниже.

Изображение 06

Теперь обратите внимание, что инициализация выполнена не в момент объявления массива, а между строками девять и тринадцать. Это работает так же, как и в строке 18 этого кода 06, как и в ранее приводимых примерах кода.

Хотя тут есть небольшой нюанс. Поскольку массив создается как статический, если вам по какой-либо причине потребуется больше места, не получится выделить дополнительное пространство в памяти. Это ограничение появляется на этапе выполнения кода. По этой причине статические массивы используются в очень специфических ситуациях. То же самое касается и динамических массивов. Но если вместо того, чтобы задавать значение, указывающее количество элементов в массиве (как показано в строке 6 этого кода), использовать строку, показанную ниже — результат будет другим.

    char  Ram_01[]

Мы получим объявление полностью динамического массива. А теперь будь внимателен, дорогой читатель. Делая это, мы сообщаем компилятору, что именно мы, программисты, будем управлять массивом  выделять и освобождать память по мере необходимости. Однако, как только будет выполнена девятая строка, возникнет ошибка. Это связано с попыткой получить доступ к памяти, которая еще не была выделена. Эту ошибку можно увидеть чуть ниже.

Изображение 07

Подобные ошибки нередко встречаются в программах с большим количеством переменных. Хотя исправить это очень просто. Достаточно просто выделить память перед использованием массива. Затем, после использования массива, нам необходимо будет ее освободить. Это освобождение должно быть выполнено явно, поскольку это считается хорошей практикой программирования.

В некоторых простых случаях  в гораздо более скромных и менее профессиональных кодах, такое освобождение не производится. Хотя так делать не рекомендуется, так как выделенная память обычно не освобождается автоматически просто потому, что вы перестали ее использовать. Поэтому, начинайте сразу делать все правильно. Если вы выделили память, освободите ее сразу, как только отпала в ней необходимость, чтобы избежать странных ошибок во время выполнения кода.

И в завершение, давайте посмотрим, как нужно исправить код для корректного использования динамического массива. Далее мы заменяем код 06 на код 07.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     char   r[];
07.     string sz0;
08.     
09.     PrintFormat("Allocated enough position for %d elements", ArrayResize(r, 10));
10. 
11.     r[0] = 72;
12.     r[1] = 101;
13.     r[2] = 108;
14.     r[3] = 111;
15.     r[4] = 33;
16. 
17.     sz0 = "";
18.     for (uchar c = 0; c < ArraySize(r); c++)
19.         sz0 = StringFormat("%s%03d, ", sz0, r[c]);
20.     Print(sz0);
21. 
22.     Print("Modifying a value...");
23. 
24.     r[7] = 36;
25. 
26.     sz0 = "";
27.     for (uchar c = 0; c < ArraySize(r); c++)
28.         sz0 = StringFormat("%s%03d, ", sz0, r[c]);
29.     Print(sz0);
30. 
31.     ArrayFree(r);
32. }
33. //+------------------------------------------------------------------+

Код 07

И вот это, мой любезный читатель, код, использующий чисто динамический массив. Как видите, он очень похож на код 05. Это сделано намеренно. Именно для того, чтобы показать, что мы можем делать одно и то же, просто разными способами. Результат выполнения кода 07 можно увидеть ниже.

Изображение 08

Но заметьте, что на изображении 08 отмечено несколько важных моментов. Я хочу, чтобы вы внимательно отнеслись к тому, что я собираюсь объяснить. Потому что это очень важно. Обычно менее аккуратные программисты, или те, чей код не будет использоваться в критически важных целях, просто используют память, не инициализируя ее должным образом. Подобная практика порождает огромное количество ошибок, которые крайне сложно обнаружить. Даже очень опытным разработчикам. Здесь я показываю одну из таких ошибок.

Обратите внимание: память была выделена в строке 9 кода 07. Между строками 11 и 15 мы присваиваем значения некоторым позициям в этой памяти. Однако, когда мы читаем память, чтобы получить ее содержимое, мы видим, что там присутствует странная информация. Эта информация называется мусором, потому что она существует в памяти, хотя ее там быть не должно.

Но самая важная часть здесь  это строка 22, поскольку именно она указывает, что в этот момент мы будем изменять память. Это делается в строке 24. Однако, при следующем прочтении памяти возникает ощущение, что программа знает, что дальше произойдет. Еще до того, как мы записали значение в ту позицию памяти, оно уже там появилось. Будто по волшебству.

Хорошо, так почему же это произошло? Речь не идет о магии или путешествиях во времени. Причина напрямую связана с тем, что выделенная память не находится под вашим контролем. Распределением памяти занимается операционная система, и там может быть все, что угодно. Она может содержать остатки данных от других программ, называемые «мусором», или просто набор нулей. Содержимое всегда случайно. Однако, если операционная система выделит одну и ту же область памяти для двух последовательных запусков, где память сначала была выделена, затем освобождена и снова выделена  будь то в рамках одного и того же кода или разных — вероятность появления там мусора очень высока.

И если предположить, будто в этой памяти хранится то или иное значение, можно столкнуться с очень трудноустранимой ошибкой. Вот почему НИКОГДА, а еще лучше НИКОГДА НИКОГДА не стоит думать, что в выделенной памяти уже есть какие-либо достоверные данные. НИКОГДА. Всякий раз при выделении памяти, ОЧИЩАЙТЕ эту область или, как минимум, полностью инициализируйте ее. В MQL5 это можно сделать несколькими способами. Лично я предпочитаю использовать для таких целей довольно простой и эффективный вызов стандартной библиотеки. Ниже приведен исправленный вариант кода 07.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     char   r[];
07.     string sz0;
08.     
09.     PrintFormat("Allocated enough position for %d elements", ArrayResize(r, 10));
10. 
11.     ZeroMemory(r);
12. 
13.     r[0] = 72;
14.     r[1] = 101;
15.     r[2] = 108;
16.     r[3] = 111;
17.     r[4] = 33;
18. 
19.     sz0 = "";
20.     for (uchar c = 0; c < ArraySize(r); c++)
21.         sz0 = StringFormat("%s%03d, ", sz0, r[c]);
22.     Print(sz0);
23. 
24.     Print("Modifying a value...");
25. 
26.     r[7] = 36;
27. 
28.     sz0 = "";
29.     for (uchar c = 0; c < ArraySize(r); c++)
30.         sz0 = StringFormat("%s%03d, ", sz0, r[c]);
31.     Print(sz0);
32. 
33.     ArrayFree(r);
34. }
35. //+------------------------------------------------------------------+

Код 08

Запустив код 08, вы получите то, что видите ниже.

Изображение 09

Можно заметить, что единственное различие между изображениями 08 и 09 заключается именно в позиции памяти. В первом запуске там присутствовал мусор. Но когда мы добавляем строку 11, которую можно увидеть в коде 08, этот тип ошибок, при которых использовался или мог использоваться мусор, перестает возникать. Вот так просто. Хотя, как я уже сказал, в MQL5 есть и другие функции с тем же назначением. Все зависит от вашего выбора и цели, которой нужно достичь.


Заключительные соображения

В этой статье, которая на самом деле содержит основы чего-то гораздо более сложного, чем то, что в ней показано, я продемонстрировал немного более профессиональный взгляд на массив. Хотя вы, возможно, и не поняли, что я хотел показать, поскольку объяснять такого рода вещи гораздо сложнее, чем любые другие темы в программировании. В любом случае, я думаю, что что хотя бы часть своей основной задачи я выполнил. Например, объяснил, как можно создать память ROM внутри оперативной памяти RAM. Мы также рассмотрели, как происходит выбор между использованием динамического массива или массива статического.

Однако важно отметить, что никогда не следует делать никаких предположений, кода речь идет о массивах или доступе к памяти. Потому что неинициализированные элементы могут содержать мусор. А использование мусора в коде может серьезно ухудшить ожидаемые результаты или затруднить решение проблем взаимодействия между различными программами.

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

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/15472

Прикрепленные файлы |
Anexo.zip (3.32 KB)
От новичка к эксперту: Главное на пути к торговле на MQL5 От новичка к эксперту: Главное на пути к торговле на MQL5
Раскройте свой потенциал! Вас окружают возможности. Узнайте 3 главных секрета, с помощью которых вы начнете изучать MQL5 или перейдете на новый уровень владения этим языком. Погрузимся в обсуждение советов и рекомендаций, в равной степени полезных и начинающим, и профи.
Критерии тренда. Окончание Критерии тренда. Окончание
В этой статье мы рассмотрим особенности применения некоторых критериев тренда на практике. А также сделаем попытку разработать несколько новых критериев. Основное внимание будет уделено эффективности применения этих критериев для анализа рыночных данных и трейдинга.
Анализ настроений в Twitter с помощью сокетов Анализ настроений в Twitter с помощью сокетов
Этот инновационный торговый бот интегрирует платформу MetaTrader 5 с языком Python в целях использования анализа настроений в социальных сетях в режиме реального времени для автоматизированного принятия торговых решений. Путем анализа настроений в Twitter, связанных с конкретными финансовыми инструментами, бот преобразует тенденции социальных сетей в действенные торговые сигналы. Он использует архитектуру «клиент-сервер» с сокетной связью, обеспечивая бесперебойное взаимодействие между торговыми возможностями MetaTrader 5 и вычислительной мощностью Python. Система демонстрирует потенциал объединения финансовой математики с обработкой текстов на естественном языке, предлагая передовой подход к алгоритмической торговле, использующей альтернативные источники данных. Бот не только демонстрирует серьезные перспективы, но и указывает на области для дальнейшего совершенствования, включая более продвинутые методы анализа настроений и улучшенные стратегии управления рисками.
Нейросети в трейдинге: Актер—Режиссёр—Критик (Окончание) Нейросети в трейдинге: Актер—Режиссёр—Критик (Окончание)
Фреймворк Actor–Director–Critic — это эволюция классической архитектуры агентного обучения. В статье представлен практический опыт его реализации и адаптации к условиям финансовых рынков.