English Русский Español 日本語 Português
preview
Von der Grundstufe bis zur Mittelstufe: Array (II)

Von der Grundstufe bis zur Mittelstufe: Array (II)

MetaTrader 5Beispiele |
34 0
CODE X
CODE X

Einführung

Im vorherigen Artikel „Von der Grundstufe zur Mittelstufe: Array (I)“ haben wir uns mit einem der komplexesten und schwierigsten Themen der Programmierung beschäftigt. Ich weiß, dass viele einwenden werden, dass es eigentlich ganz einfach ist und dass es keinen Sinn macht, wenn ich es als kompliziert bezeichne. Im weiteren Verlauf werden Sie jedoch verstehen, warum ich behaupte, dass dieses Thema tatsächlich komplex und schwer zu bewältigen ist. Schließlich ist sie die Grundlage für alles andere.

Wenn ich erst einmal erklärt und demonstriert habe, wie dieses Konzept wirklich angewendet werden kann, werden Sie, lieber Leser, der es schafft, das Gezeigte vollständig zu verstehen, zweifellos erkennen, warum es noch andere Funktionen in einer Programmiersprache gibt. Denn wenn man diese Grundlage erst einmal verstanden hat, wird alles andere viel leichter zu meistern und zu verstehen sein.

Die größte Herausforderung besteht darin, die Dinge so darzustellen, dass wir nicht in andere Themen eintauchen, die wir noch nicht behandelt haben. Ich versuche zu veranschaulichen, warum bestimmte Elemente geschaffen wurden, ohne sie jetzt schon zu enthüllen. Der Grund dafür ist, dass das Verständnis des Konzepts, das diesen Instrumenten zugrunde liegt, viel wichtiger ist als das Verständnis der Instrumente selbst. Und da viele Programmierer das Konzept oft ignorieren und sich zu sehr auf das Werkzeug konzentrieren, geraten sie manchmal in eine Sackgasse. Das liegt daran, dass nicht das Werkzeug das Problem löst, sondern das Konzept. Er ist wie ein Hammer - er kann zum Einschlagen von Nägeln verwendet werden. Er kann aber auch zu anderen Zwecken eingesetzt werden - zum Beispiel zum Abriss von Dingen. Auch wenn es vielleicht ein besseres Werkzeug für den Abriss gibt, wie einen Vorschlaghammer.

Bevor wir beginnen, gibt es eine Voraussetzung, die notwendig ist, um diesen Artikel vollständig zu verstehen: Es ist wichtig zu wissen und zu verstehen, was eine Variable ist und was eine Konstante ist.


ROM-Typ-Arrays

Es gibt im Wesentlichen zwei Möglichkeiten, ein Array zu deklarieren. Die eine ist die Deklaration eines statischen Arrays, die andere die Deklaration als dynamisches Array. In der Praxis ist es zwar relativ einfach, die einzelnen Typen zu verstehen, aber es gibt bestimmte Feinheiten, die ein klares Verständnis dessen, was ein dynamisches Array und ein statisches Array wirklich sind, erschweren oder sogar verhindern können. Vor allem, wenn man andere Programmiersprachen wie C und C++ in Betracht zieht. Aber auch hier in MQL5 kann es Momente geben, in denen Sie sich etwas unsicher fühlen. Denn der grundlegende und wesentliche Unterschied zwischen einem statischen und einem dynamischen Array liegt in der Fähigkeit des letzteren, seine Größe während der Ausführung des Codes zu ändern.

Wenn man so darüber nachdenkt, scheint es einfach zu sein, ein Array als dynamisch oder statisch zu klassifizieren. Es ist jedoch wichtig zu wissen, dass eine Zeichenkette auch ein Array ist. Allerdings handelt es sich um eine besondere Art von Array. Daher ist es schwierig, sie als rein statisch oder dynamisch zu klassifizieren. Doch lassen wir diese Tatsache einmal beiseite. Wir werden uns nicht direkt mit dem String-Typ befassen. Auf diese Weise werden wir Verwirrung vermeiden und sicherstellen, dass wir ein klares Verständnis für dieses Thema entwickeln.

Grundsätzlich - und das ist in allen Programmiersprachen, die Sie in Zukunft lernen werden, unbestritten - ist ein konstantes Array immer ein statisches Array. Unabhängig von der Programmiersprache, die Sie verwenden, wird es IMMER statisch sein.

Aber warum können wir getrost behaupten, dass ein konstantes Array immer statisch ist? Der Grund dafür ist, dass ein konstantes Array, und so muss man es sich vorstellen, im Wesentlichen ein ROM-Speicher ist. Zu diesem Zeitpunkt macht das vielleicht nicht viel Sinn. Schließlich wird unsere Anwendung immer in einem Bereich des RAM geladen und ausgeführt. Wie können wir uns also einen ROM-Speicher, der nur das Lesen von Daten erlaubt, in einer RAM-Umgebung vorstellen, in der wir jederzeit Daten sowohl lesen als auch schreiben können? Das erscheint definitiv unlogisch.

Genau aus diesem Grund müssen Sie verstehen, was Konstanten und Variablen wirklich bedeuten, lieber Leser. Aus der Sicht unserer Anwendung ist es in der Tat möglich, ROM-ähnliches Verhalten im RAM zu nutzen, ohne die Integrität oder Nutzbarkeit der Anwendung zu beeinträchtigen. In der Tat ist die Verwendung von konstanten Arrays zur Erstellung eines kleinen ROMs innerhalb einer Anwendung relativ üblich. Besonders in komplexeren Codebasen oder in Anwendungen, die sehr spezifische Werte erfordern, die sich nie ändern dürfen.

Denken Sie an die Nachrichten, die zur Meldung von Ereignissen oder Informationen verwendet werden. Sie können diese Nachrichten konsolidieren und in verschiedene Sprachen übersetzen. Wenn die Anwendung geladen wird, kann sie die zu verwendende Sprache bestimmen und den ROM-Speicher entsprechend aufbauen. Dies würde es ermöglichen, dass die Anwendung von verschiedenen Nutzern mit unterschiedlichen Sprachpräferenzen genutzt werden kann. Dies ist ein klassisches Beispiel dafür, wie sich ein Array verhalten könnte, als befände es sich im ROM. Wenn Sie jedoch stattdessen Dateien verwenden, um die Übersetzungen dynamisch zu laden, wäre die Anordnung nicht mehr rein ROM-artig. Oder rein statisch. Allerdings könnte man sie immer noch so einstufen.

Um dieses Konzept leichter zu verstehen, da es entscheidend dafür ist, wie und wann Standardbibliotheksaufrufe zur Verwaltung von Arrays verwendet werden, sehen wir uns ein einfaches Beispiel an. Sie zeigt dieses Konzept in der Praxis.

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. //+------------------------------------------------------------------+

Code 01

Wenn Code 01 ausgeführt wird, sieht das Ergebnis wie folgt aus.

Abbildung 01

Das ist ganz einfach zu verstehen. Wir interessieren uns hier jedoch für die Zeilen sechs und sieben. In beiden Fällen werden zwei ROMs mit demselben offensichtlichen Inhalt erstellt. Allerdings sind ihre Größen völlig unterschiedlich. Aber warten Sie einen Moment. Wie kann das sein? Das verstehe ich nicht. Wenn ich mir den Code ansehe, kann ich sehen, dass beide Arrays fünf Elemente haben. Und alle dort gezeigten Elemente sind identisch. Sie werden nur anders deklariert. Aber warum sind diese beiden ROMs unterschiedlich? Das macht keinen Sinn.

Sie haben völlig Recht, liebe Leserin, lieber Leser, dass beide Arrays die gleichen Elemente deklarieren. Wenn auch auf unterschiedliche Weise. Der Unterschied liegt jedoch in der Art und Weise, wie sie deklariert werden. Achten Sie hier genau auf einen wichtigen Punkt.

ROM_01, das in Zeile sechs deklariert wird, enthält fünf Elemente. Typischerweise sehen wir in Code, in dem wir ein statisches Array erstellen, diese Art von Erklärung. Beachten Sie, dass innerhalb der Klammern kein Wert angegeben ist. Aber (und das ist der Punkt, an dem viele Leute verwirrt sind) die Nichtdeklaration eines Wertes innerhalb der Klammern für ein konstantes Array ist völlig normal. Und sogar ziemlich häufig. Der Grund dafür ist, dass Sie dem Array während der Implementierung mehr oder weniger Werte hinzufügen können. Sobald diese Werte festgelegt sind, werden sie vollständig verriegelt und können nicht mehr geändert werden. Infolgedessen wird die Größe des Arrays auf die Anzahl der vorhandenen Elemente festgelegt.

Bei ROM_02 liegen die Dinge ein wenig anders. In diesem Fall werden fünf Elemente explizit angegeben. Da wir jedoch angeben, dass das Array acht Elemente hat, bleiben drei dieser Elemente undefiniert. Bei der Verwendung dieser undefinierten Elemente müssen Sie vorsichtig sein. Das liegt daran, dass sie gültige Daten oder auch völlig zufällige Werte enthalten können. Dies hängt davon ab, wie der Compiler das Array initialisiert. Denken Sie daran, dass wir es hier mit konstanten Arrays zu tun haben.

In jedem Fall arbeiten wir in beiden Szenarien mit statischen Arrays. Das heißt, dass sich die Anzahl der Elemente während der gesamten Lebensdauer des Arrays NICHT ÄNDERT. In einem Fall haben wir immer fünf Elemente, im anderen Fall immer acht Elemente. Denken Sie daran, dass das erste Element einen Index von Null hat. Die Zählung beginnt also immer bei Null.

Großartig. Wir wissen, wie viele Elemente in jedem der Arrays vorhanden sind. Aber was passiert, wenn wir versuchen, auf das Element bei Index fünf zuzugreifen? Denken Sie daran, dass die Zählung bei Null beginnt. Nun, in diesem Fall können wir einen kleinen Test durchführen, damit Sie beobachten können, was passiert. Ändern wir dazu etwas im Code, sodass er wie unten dargestellt aussieht.

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. //+------------------------------------------------------------------+

Code 02

Beachten Sie, dass wir jetzt in Zeile neun von Code 02 eine Konstante haben, die angibt, auf welches Element wir zugreifen wollen. In diesem Fall geht es um das sechste Element. Sie befindet sich an Position fünf. Man könnte denken: „Nun, da wir nach dem Element an Position fünf suchen und fünf Elemente deklariert haben und wir den Dezimalwert dieses Elements ausgeben wollen, werden die Zeilen 13 und 14 beide denselben Wert ausgeben - in diesem Fall 33.“ Dies ist in der Tat die natürlichste Art zu denken. Das ist jedoch falsch. Das liegt daran, dass die Zählung bei Null beginnt. Daher ist das fünfte angegebene Element tatsächlich das Element mit dem Index vier. Wenn wir also versuchen, auf den Index fünf zuzugreifen, wird etwas im Code passieren. Das Ergebnis ist unten dargestellt.

Abbildung 02

Beachten Sie, dass hier zwei seltsame Dinge geschehen. Der erste ist die Tatsache, dass Zeile 13 ausgeführt wurde und das angezeigte Ergebnis Null ist. Das bedeutet, dass das in Zeile sieben deklarierte Array versteckte Werte enthält. Der springende Punkt ist jedoch die Fehlermeldung, die in dieser Abbildung 02 zu sehen ist. Sie sagt uns, dass in Zeile 14 von Code 02 versucht wird, auf etwas außerhalb des Arrays zuzugreifen.

Da das in Zeile 14 verwendete Array das in Zeile 6 deklarierte Array ist, werden Sie sich vielleicht fragen, warum der Fehler auftritt. Dies geschieht genau deshalb, weil Sie versuchen, auf das Element an der fünften Stelle zuzugreifen. Da das Array aus fünf Elementen besteht, sollte dies eigentlich möglich sein. Aber auch hier gilt: Die Zählung beginnt bei Null.

Und um dies zu bestätigen, sollten wir eine Änderung vornehmen. Diese Änderung befindet sich in Zeile 9 von Code 02. Wenn Sie den Wert sechs, den Sie dort sehen, in den Wert fünf ändern, wie in der folgenden Codezeile dargestellt, wird sich alles ändern.

const uchar pos = 5;

Wenn wir Code 02 erneut kompilieren und ausführen und dabei die oben gezeigte aktualisierte Zeile verwenden, sieht das Ergebnis im Terminal wie folgt aus.

Abbildung 03

Haben Sie bemerkt, dass der Code jetzt den Inhalt korrekt anzeigen kann? Ich glaube, damit werden Sie verstehen, wie wir auf Arrays zugreifen sollten. Und wie dieser kleine Unterschied in der Array-Deklaration zu völlig unterschiedlichen Ergebnissen führen kann.

Da ROM-Arrays immer statisch sind, auch wenn sie auf eine Weise deklariert werden, die dynamisch erscheinen mag, gibt es hier nicht viel mehr zu sagen. Das liegt daran, dass diese Art von Array, bei der jeder Schreibversuch zu einem Kompilierungsfehler führt, wenig Diskussionsstoff bietet. Lassen Sie uns also zu einem neuen Thema übergehen, bei dem wir eine andere Art von Array behandeln werden - eine, die etwas komplexer ist. Dies ist darauf zurückzuführen, dass wir darauf schreiben können.


RAM-Typ-Arrays

Die Voraussetzung für das Verständnis dieser Art von Arrays ist, dass man die ROM-Arrays verstanden hat. Dort kann die Erklärung entweder statisch oder dynamisch sein. Im Gegensatz zu ROM-Arrays, deren Inhalt konstant ist und deren Anzahl der Elemente ebenfalls feststeht, sind die Dinge bei RAM-Arrays jedoch etwas komplexer. Der Grund dafür ist, dass die Anzahl der Elemente konstant sein kann oder auch nicht. Außerdem kann der Inhalt der einzelnen Elemente bei der Ausführung des Codes variieren.

Nach diesen einleitenden Worten werden Sie vielleicht denken, dass diese Art von Array ein lebender Albtraum ist. Es erfordert jedoch etwas mehr Aufmerksamkeit, um es richtig einzusetzen. Hier macht jedes Detail den Unterschied aus. Es gibt jedoch einige Besonderheiten bei Arrays in MQL5, die Sie, lieber Leser, vorerst ignorieren sollten. Es gibt nämlich einen speziellen Array-Typ, der als Puffer fungiert. Dies ist spezifisch für MQL5. Aber dieser Typ wird zu einem anderen Zeitpunkt besser erklärt, wenn wir uns mit den Berechnungen und der Programmierung für den MetaTrader 5 befassen. Was wir hier erklären, gilt also zunächst nicht für diese Puffer-Arrays. In diesem Moment geht es darum, eine allgemeine Erklärung von Arrays in der Programmierung zu geben. Nicht nur in MQL5, sondern in allen Programmiersprachen.

Okay, gehen wir also wie folgt vor: Basierend auf dem letzten Code aus dem vorherigen Thema, entfernen wir das reservierte Wort „const“ aus beiden Deklarationen. Auf diese Weise heben wir die Beschränkungen auf, die den Arrays auferlegt sind. Es gibt jedoch ein sehr wichtiges Detail, das erklärt werden muss, wenn wir diese Änderung vornehmen, und das in dem nachfolgend gezeigten Code behandelt wird.

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. //+------------------------------------------------------------------+

Code 03

Wenn Sie diesen Code 03 ausführen, erhalten Sie das gleiche Ergebnis wie in Abbildung 03. Es gibt jedoch einen grundlegenden Unterschied. Und sie liegt genau darin, dass wir nicht mit ROM arbeiten. Stattdessen arbeiten wir mit RAM. Dies ermöglicht es uns, den Code 03 so zu ändern, dass er wie unten dargestellt aussieht.

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. //+------------------------------------------------------------------+

Code 04

Dieser Code 04 ist recht merkwürdig und in gewisser Weise sogar ein wenig faszinierend. Das liegt einfach daran, dass wir in den Zeilen 16 und 19 versuchen, den Wert an einer bestimmten Position im Array zu ändern. Dies ist ein Detail, das, wenn es gut verstanden wird, die Neugierde der Wagemutigen unter uns wecken kann. Wenn wir also Code 04 ausführen, erscheint das Ergebnis wie unten dargestellt.

Abbildung 04

Beachten Sie, dass es tatsächlich möglich war, die Werte zu ändern. Dies wäre nicht zulässig, wenn wir ein ROM-Array verwenden würden. An dieser Stelle fragen Sie sich vielleicht: Können wir alle Informationen innerhalb des Arrays ändern? Nun, die Antwort auf diese Frage lautet: Das kommt darauf an.

Beachten Sie Folgendes, liebe Leserin, lieber Leser: Das Array in Zeile sechs ist zwar dynamisch, weil wir keine explizite Elementanzahl angeben, bleibt aber statisch. Der Grund dafür ist, dass wir sie zum Zeitpunkt der Deklaration initialisieren. Das Array in Zeile sieben ist vollständig statisch, aber die Anzahl der möglichen Elemente darin ist größer als die Anzahl der Elemente, die wir während der Initialisierung des Arrays deklarieren.

Wenn man das versteht, könnte man sagen: Wenn wir also einen Index verwenden, der auf ein Element zeigt, können wir seinen Wert ändern. Richtig? Ganz genau. Sie könnten sogar einen ähnlichen Code wie den unten gezeigten verwenden.

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. //+------------------------------------------------------------------+

Code 05

Wenn wir Code 05 ausführen, sieht das Ergebnis wie in der folgenden Abbildung dargestellt aus.

Abbildung 05

Mit anderen Worten: Es funktioniert tatsächlich. Wir können jeden beliebigen Wert ändern, solange das Element, auf das wir zugreifen, innerhalb des Arrays existiert. Zeile 18 funktionierte jedoch nur, weil wir in Zeile sechs angegeben hatten, dass das Array 10 Elemente haben sollte.

Also gut, ich glaube, der erste Teil ist klar. Aber müssen wir immer auf diese Weise arbeiten? Müssen wir also bei der Deklaration eines Arrays immer entweder die Anzahl der Elemente oder die Elemente selbst angeben? Dies ist in der Tat eine Frage, die bei Anfängern viele Zweifel hervorruft. Und wieder einmal lautet die Antwort: Das kommt darauf an.

Es ist jedoch wichtig, dass Sie sich dies merken, lieber Leser: Bei einem konstanten Array (d.h. einem Array vom Typ ROM) müssen die Elemente oder die Anzahl der Elemente IMMER zum Zeitpunkt der Erstellung des Arrays angegeben werden. Ein Detail: Die Angabe der Elemente selbst ist in diesem Fall obligatorisch, die Angabe der Anzahl der Elemente ist jedoch optional.

Diese Regel ist streng und lässt keine Änderungen oder Interpretationen zu. Für RAM-Arrays gibt es jedoch keine starre Regel, die befolgt werden muss. Alles hängt von dem Zweck oder Ziel ab, das erreicht werden soll. Die Deklaration von Elementen in einem dynamischen Array (ein Array, das kein ROM-Typ oder eine Konstante ist) ist jedoch nicht üblich. Wie wir in Zeile sechs von Code 04 gesehen haben. Das liegt daran, dass wir auf diese Weise das dynamische Array in ein statisches verwandeln. Dies ähnelt in gewisser Weise dem, was mit dem Array in Zeile sieben desselben Code 04 geschieht.

In Bezug auf die Interpretation des Codes könnte jedoch ein Array, das wie in Zeile sechs von Code 04 deklariert ist, durch etwas wie das unten Gezeigte ersetzt werden.

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. //+------------------------------------------------------------------+

Code 06

Beachten Sie, dass wir in Code 06 jetzt eine statische Array-Deklaration in Zeile sechs haben. Auf diese Weise weisen wir den Compiler an, einen Speicherblock für uns zu reservieren. Diese Zuordnung erfolgt automatisch während der Kompilierung. Der Compiler reserviert also einen Speicherplatz, der groß genug ist, um die angegebene Anzahl von Elementen aufzunehmen. In diesem Fall sind es fünf Elemente. Da der Typ „char“ ist, werden uns fünf Bytes zugewiesen, die wir sofort verwenden können.

Stellen Sie sich diesen zugewiesenen Platz als eine Fünf-Byte-Variable vor. Sie können es so verwenden, wie es Ihren Bedürfnissen entspricht. Wir könnten auch eine beliebige Zahl zuweisen. Wenn wir zum Beispiel statt fünf, wie in Zeile sechs, 100 verwenden, würden wir eine 100-Byte-Variable erstellen. Beachten Sie, dass die derzeit größte Breite 8 Byte oder 64 Bit beträgt. Mit anderen Worten, es passen etwas mehr als 12 dieser 8-Byte-Variablen in dieses 100-Byte-Array.

Wenn Sie Code 06 ausführen, erhalten Sie auf jeden Fall das unten dargestellte Ergebnis.

Abbildung 06

Beachten Sie nun, dass die Initialisierung nicht während der Array-Deklaration, sondern zwischen den Zeilen neun und dreizehn erfolgt ist. Dies funktioniert auf die gleiche Weise wie Zeile 18 in Code 06 und wie in den vorherigen Beispielen.

Allerdings gibt es hier ein kleines Detail, da das Array statisch erstellt wird, können Sie, wenn Sie aus irgendeinem Grund mehr Platz benötigen, keinen zusätzlichen Speicher zuweisen. Diese Einschränkung tritt zur Laufzeit auf. Aus diesem Grund werden statische Arrays in ganz bestimmten Situationen verwendet. Das Gleiche gilt für dynamische Arrays. Aber was wäre, wenn wir, anstatt einen Wert zu definieren, der die Anzahl der Elemente im Array angibt, wie in Zeile 6 von Code 06, die unten gezeigte Zeile verwenden würden?

    char  Ram_01[]

Dies würde dazu führen, dass ein vollständig dynamisches Array deklariert wird. Passen Sie gut auf, lieber Leser. Damit teilen wir dem Compiler mit, dass wir, die Programmierer, für die Verwaltung des Arrays verantwortlich sind, d.h. für die Zuweisung und Freigabe von Speicher nach Bedarf. Sobald jedoch Zeile neun ausgeführt wird, tritt ein Fehler auf. Dies liegt daran, dass Sie versuchen werden, auf Speicher zuzugreifen, der noch nicht zugewiesen wurde. Dieser Fehler ist unten zu sehen.

Abbildung 07

Es ist nicht ungewöhnlich, dass diese Art von Fehler auftritt, insbesondere bei Programmen mit vielen Variablen. Es ist jedoch einfach, das Problem zu beheben. Wir müssen nur den Speicher zuweisen, bevor wir das Array verwenden. Und dann, wenn wir mit dem Array fertig sind, sollten wir den Speicher explizit freigeben. Diese ausdrückliche Freigabe gilt als gute Programmierpraxis.

In einfacheren Programmen oder in weniger professionellem Code wird dieser Schritt der Deallokation häufig übersprungen. Dies ist jedoch nicht empfehlenswert, da der zugewiesene Speicher nicht automatisch freigegeben wird, nur weil Sie diesen Speicherbereich nicht mehr verwenden. Daher ist es am besten, von Anfang an gute Gewohnheiten zu entwickeln. Weisen Sie Speicher zu, wenn er benötigt wird, und geben Sie ihn frei, wenn er nicht mehr benötigt wird, um seltsame Fehler bei der Codeausführung zu vermeiden.

Zum Schluss sehen wir uns an, wie man den Code korrigiert, um das dynamische Array korrekt zu verwenden. Wir ersetzen Code 06 durch Code 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. //+------------------------------------------------------------------+

Code 07

Dieses Codefragment, lieber Leser, verwendet wirklich ein rein dynamisches Array. Beachten Sie, dass er dem Code 05 sehr ähnlich ist. Dies geschieht absichtlich. Diese Ähnlichkeit ist beabsichtigt, um zu zeigen, dass wir das gleiche Ergebnis auf unterschiedliche Weise erreichen können. Das Ergebnis der Ausführung von Code 07 ist unten dargestellt.

Abbildung 08

Achten Sie jedoch genau auf die hervorgehobenen Informationen in Abbildung 08. Dieser Teil ist äußerst wichtig. Normalerweise werden weniger vorsichtige Programmierer oder solche, die Code schreiben, der nicht für kritische Zwecke bestimmt ist, Speicher verwenden, ohne ihn ordnungsgemäß zu initialisieren. Diese Art von Nachlässigkeit führt zu einer absurden Anzahl von schwer zu entdeckenden Fehlern, selbst für sehr erfahrene Entwickler. Hier zeige ich ein solches Problem auf.

Beachten Sie, dass in Zeile 9 von Code 07 Speicher zugewiesen wurde. Zwischen den Zeilen 11 und 15 weisen wir einigen Positionen im zugewiesenen Speicher Werte zu. Wenn wir jedoch aus dem Speicher lesen und versuchen, seinen Inhalt abzurufen, sehen wir dort etwas Seltsames. Diese Informationen werden als Müll bezeichnet, weil sie im Speicher vorhanden sind, obwohl sie dort nicht sein sollten.

Der springende Punkt ist hier Zeile 22. Es sagt uns, dass wir in diesem Moment den Speicher verändern werden. Dies geschieht in Zeile 24. Beim erneuten Lesen des Speichers ist es jedoch so, als könnte das Programm die Zukunft vorhersagen. Es ist, als ob der Wert aus dem Nichts aufgetaucht wäre, bevor wir ihn auf diesen Speicherplatz angewendet haben. Wie von Zauberhand.

Warum ist das passiert? Es handelt sich weder um einen Zaubertrick noch um eine Zeitreise. Die Erklärung liegt darin, dass der zugewiesene Speicher nicht wirklich unter Ihrer Kontrolle ist. Das Betriebssystem kümmert sich um die Speicherzuweisung, und der Inhalt dieses Speichers kann beliebig sein. Dabei kann es sich um Datenreste von anderen Programmen (so genannter „Garbage“, also Müll) oder sogar um eine Reihe von Nullen handeln. Der Inhalt ist immer zufällig. Wenn das Betriebssystem denselben Speicherplatz für zwei aufeinanderfolgende Ausführungen zuweist, bei denen der Speicher zugewiesen, freigegeben und dann erneut zugewiesen wurde, sei es durch denselben oder einen völlig anderen Code, ist die Wahrscheinlichkeit, dass „Garbage“ auftritt, sehr hoch.

Wenn Sie davon ausgehen, dass der Inhalt dieses Speichers auf eine bestimmte Art und Weise ist, können Sie auf ein sehr schwer zu lösendes Problem stoßen. Gehen Sie daher NIEMALS davon aus, dass der Speicher einen bestimmten Wert enthält. NIEMALS. Wenn Sie Speicher zuweisen, sollten Sie den Bereich immer säubern oder ihn zumindest vollständig initialisieren. Es gibt viele Möglichkeiten, dies in MQL5 zu tun. Ich persönlich bevorzuge einen einfachen und effizienten Aufruf aus der Standardbibliothek, um dies zu erledigen. Die berichtigte Fassung von Code 07 ist nachstehend aufgeführt.

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. //+------------------------------------------------------------------+

Code 08

Wenn Sie den Code 08 ausführen, erhalten Sie das unten abgebildete Ergebnis.

Abbildung 09

Beachten Sie, dass der einzige Unterschied zwischen Bild 08 und Bild 09 der spezifische Speicherplatz ist. Beim ersten Durchlauf sind wir an dieser Stelle auf Datenmüll gestoßen. Nach der Hinzufügung von Zeile 11 in Code 08 tritt diese Art von Fehler, bei dem Müll verwendet werden könnte, jedoch nicht mehr auf. So einfach ist das. Allerdings gibt es auch andere Funktionen in MQL5, die demselben Zweck dienen. Es hängt alles von Ihrer Wahl und dem Ziel ab, das Sie erreichen wollen.


Abschließende Überlegungen

In diesem Artikel, der in der Tat die Grundlagen von etwas viel Komplexerem als dem hier Gezeigten berührt, habe ich gezeigt, wie Sie sich Arrays aus einer professionelleren Perspektive nähern können. Auch wenn Sie vielleicht nicht alles verstanden haben, was ich Ihnen zeigen wollte, denn dieses Thema ist viel schwieriger zu erklären als viele andere Programmierkonzepte. Ich glaube jedoch, dass ich einen Teil des Hauptziels erreicht habe. Ich habe erklärt, wie man ROM-ähnliches Verhalten im RAM erzeugt. Wir haben auch den Entscheidungsprozess für die Wahl zwischen dynamischen und statischen Arrays diskutiert.

Dennoch ist es wichtig, einen wichtigen Punkt hervorzuheben: Gehen Sie nie von etwas aus, wenn Sie mit Arrays oder Speicherzugriff zu tun haben. Uninitialisierte Elemente können Müll enthalten. Und die Verwendung solchen Mülls in Ihrem Code kann die erwarteten Ergebnisse ernsthaft beeinträchtigen oder die Lösung von Interaktionen zwischen verschiedenen Programmen erheblich erschweren.

Die beigefügten Unterlagen enthalten auf jeden Fall die hier geprüften Codes. So können Sie lernen und üben. Dies wird Ihnen auch dabei helfen, zu lernen, wie man mit Fehlern umgeht und Konzepte im Zusammenhang mit der Verwendung von Arrays in praktischeren Aufgaben zu erkunden.

Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/15472

Beigefügte Dateien |
Anexo.zip (3.32 KB)
Entwicklung eines Replay-Systems (Teil 70): Das richtige Bestimmen der Zeit (III) Entwicklung eines Replay-Systems (Teil 70): Das richtige Bestimmen der Zeit (III)
In diesem Artikel erfahren Sie, wie Sie die Funktion CustomBookAdd richtig und effektiv nutzen können. Trotz ihrer scheinbaren Einfachheit hat sie viele Nuancen. So können Sie dem Mauszeiger beispielsweise mitteilen, ob ein nutzerdefiniertes Symbol gerade versteigert oder gehandelt wird oder ob der Markt geschlossen ist. Der hier dargestellte Inhalt ist ausschließlich für Bildungszwecke bestimmt. Die Anwendung sollte unter keinen Umständen zu einem anderen Zweck als zum Erlernen und Beherrschen der vorgestellten Konzepte verwendet werden.
Neuronale Netze im Handel: Kontrollierte Segmentierung Neuronale Netze im Handel: Kontrollierte Segmentierung
In diesem Artikel wird eine Methode zur Analyse komplexer multimodaler Interaktionen und zum Verstehen von Merkmalen erörtert.
Neuronale Netze im Handel: Marktanalyse mit Hilfe eines Muster-Transformers Neuronale Netze im Handel: Marktanalyse mit Hilfe eines Muster-Transformers
Wenn wir Modelle zur Analyse der Marktsituation verwenden, konzentrieren wir uns hauptsächlich auf Kerzen. Es ist doch seit langem bekannt, dass Kerzen-Muster bei der Vorhersage künftiger Kursbewegungen helfen können. In diesem Artikel werden wir uns mit einer Methode vertraut machen, die es uns ermöglicht, diese beiden Ansätze zu integrieren.
MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 60): Inferenzlernen (Wasserstein-VAE) mit gleitendem Durchschnitt und stochastischen Oszillatormustern MQL5-Assistenten-Techniken, die Sie kennen sollten (Teil 60): Inferenzlernen (Wasserstein-VAE) mit gleitendem Durchschnitt und stochastischen Oszillatormustern
Wir schließen unsere Betrachtung der komplementären Paarung von MA und stochastischem Oszillator ab, indem wir untersuchen, welche Rolle das Inferenzlernen in einer Situation nach überwachtem Lernen und Verstärkungslernen spielen kann. Es gibt natürlich eine Vielzahl von Möglichkeiten, wie man in diesem Fall das Inferenzlernen angehen kann, unser Ansatz ist jedoch die Verwendung von Variationsautokodierern. Wir untersuchen dies in Python, bevor wir unser trainiertes Modell mit ONNX exportieren, um es in einem von einem Assistenten zusammengestellten Expert Advisor in MetaTrader zu verwenden.