English Русский Español Português
preview
Von der Grundstufe bis zur Mittelstufe: Union (I)

Von der Grundstufe bis zur Mittelstufe: Union (I)

MetaTrader 5Beispiele |
53 3
CODE X
CODE X

Einführung

Die hier vorgestellten Materialien sind 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.

Im vorherigen Artikel „Von der Grundstufe zur Mittelstufe: Array (IV)“ haben wir ein sehr cooles und äußerst interessantes Konzept erforscht. Obwohl viele es als ein fortgeschrittenes Thema betrachten, ist es meiner bescheidenen Meinung nach etwas, das jeder Anfänger in der Programmierung wissen sollte. Denn bei richtiger Anwendung kann das im vorigen Artikel vorgestellte Konzept buchstäblich die ganze Welt der Möglichkeiten eröffnen. Mit ihr können wir Dinge tun, die sonst nur sehr schwer oder gar nicht zu erreichen wären.

Darüber hinaus wird derselbe Begriff auch in einem anderen Kontext verwendet, den wir zu einem geeigneteren Zeitpunkt behandeln werden. Um niemanden unnötig zu beunruhigen, hier ein kleiner Tipp: Lernen und üben Sie ausgiebig, was im vorherigen Artikel behandelt wurde. Es ist wichtig, dieses Wissen gründlich zu verstehen. Ohne sie wird von nun an nichts mehr Sinn machen. Alles, was danach kommt, wird wie Magie erscheinen.

Nun, vielleicht ist die Aussage, dass nichts einen Sinn ergibt, wenn man den vorherigen Artikel nicht verstanden hat, ein wenig übertrieben von mir. Das ändert jedoch nichts an der Tatsache, dass der vorherige Artikel der wichtigste ist, der bisher veröffentlicht wurde. Zumindest für diejenigen, die wirklich gute Programmierer werden wollen. Und für diejenigen, die alles mit einer Programmiersprache machen können wollen. Das im vorigen Artikel vorgestellte Konzept ist nicht auf MQL5 beschränkt - es gilt für jede Programmiersprache, insbesondere wenn es um die richtige Nutzung von Rechenressourcen geht.

Bevor wir also beginnen, müssen wir die Voraussetzungen für diesen Artikel erörtern. Auch wenn manche Ausbilder denken, dass ich übertreibe, bin ich der Meinung, dass Sie dem, was wir hier tun werden, kaum folgen können, wenn Sie nicht zumindest oberflächlich verstanden haben, was im vorherigen Artikel dargestellt wurde. Ich sage nicht, dass Sie es nicht verstehen werden. Aber es wird auf jeden Fall viel schwieriger sein, mit den Erklärungen Schritt zu halten.

Der vorangegangene Artikel stellt also eine Art Wendepunkt dar. Auf der einen Seite haben wir alle grundlegenden Programmierungsmaterialien; jetzt springen wir zu etwas fortgeschrittenerem Material. Und wie der Titel des Artikels schon andeutet, werden wir über die UNION sprechen. Aber nicht „Union“ im allgemeinen Sinne des Wortes. Wir beziehen uns hier auf den Begriff UNION, wie er in bestimmten Programmiersprachen vorkommt. Und wie immer beginnen wir mit einem neuen Thema, das den Implementierungs- und Codierungsprozess viel angenehmer und unterhaltsamer macht.


Die Geburt der UNION

Im vorherigen Artikel haben wir den folgenden Code implementiert:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const uint  ui = 0xCADA5169;
09.     ushort      us = 0x43BC;
10.     uchar       uc = B'01011101';
11. 
12.     uchar       Infos[],
13.                 counter = 0;
14.     uint        start,
15.                 number;
16. 
17.     PrintFormat("Translation personal.\n" +
18.                 "FUNCTION: [%s]\n" +
19.                 "ui => 0x%s\n" + 
20.                 "us => 0x%s\n" +
21.                 "uc => B'%s'\n",
22.                 __FUNCTION__,
23.                 ValueToString(ui, FORMAT_HEX),
24.                 ValueToString(us, FORMAT_HEX),
25.                 ValueToString(uc, FORMAT_BINARY)
26.                );
27.     
28.     number = sizeof(ui) + 1;
29.     start = Infos.Size();
30.     ArrayResize(Infos, start + number);
31.     Infos[counter++] = sizeof(ui);
32.     Infos[counter++] = (uchar)(ui >> 24);
33.     Infos[counter++] = (uchar)(ui >> 16);
34.     Infos[counter++] = (uchar)(ui >> 8);
35.     Infos[counter++] = (uchar)(ui & 0xFF);
36. 
37.     number = sizeof(us) + 1;
38.     start = Infos.Size();
39.     ArrayResize(Infos, start + number);
40.     Infos[counter++] = sizeof(us);
41.     Infos[counter++] = (uchar)(us >> 8);
42.     Infos[counter++] = (uchar)(us & 0xFF);
43. 
44.     number = sizeof(uc) + 1;
45.     start = Infos.Size();
46.     ArrayResize(Infos, start + number);
47.     Infos[counter++] = sizeof(uc);
48.     Infos[counter++] = (uc);
49. 
50.     Print("******************");
51.     PrintFormat("The Infos block contains %d bytes.", Infos.Size());
52.     ArrayPrint(Infos);
53.     Print("******************");
54. 
55.     Procedure(Infos);
56. 
57.     ArrayFree(Infos);
58. }
59. //+------------------------------------------------------------------+
60. void Procedure(const uchar &arg[])
61. {
62.     Print("Translation personal.\n" +
63.           "FUNCTION: ", __FUNCTION__);
64. 
65.     ulong value;
66. 
67.     for (uchar c = 0; c < arg.Size(); )
68.     {
69.         value = 0;
70.         for (uchar j = arg[c++], i = 0; (c < arg.Size()) && (i < j); i++, c++)
71.             value = (value << 8) | arg[c];
72.         Print("0x", ValueToString(value, FORMAT_HEX), " B'", ValueToString(value, FORMAT_BINARY), "'");
73.     }
74. }
75. //+------------------------------------------------------------------+

Code 01

Wenn wir Code 01 ausführen, erhalten wir das unten dargestellte Ergebnis:

Abbildung 01

In Abbildung 01 ist zu sehen, dass die in den Zeilen 8, 9 und 10 definierten Werte an die Prozedur in Zeile 60 übergeben werden. Wenn wir uns jedoch den von dieser Prozedur erwarteten Datentyp ansehen, stellen wir fest, dass nicht einzelne Werte, sondern ein Array übergeben wird. Auf den ersten Blick scheint dies keinen Sinn zu ergeben. Viele Leute, die sich die Prozedur in Zeile 60 ansehen, würden Operationen erwarten, die ein Array betreffen. Aber das ist nicht ganz das, was hier passiert. Was wir hier sehen, ist eine Transkription oder Übersetzung der im Array enthaltenen Werte, um die übergebenen Werte zu rekonstruieren.

Beachten Sie, dass wir keine Möglichkeit haben, den Namen oder den Typ der Variablen zu erfahren, die beim Aufruf der Prozedur verwendet wird. Das liegt daran, dass es keinen Hinweis auf diese Art von Informationen gibt. Alles, was wir wissen, ist die Anzahl der Elemente und der Wert jedes Elements, der jeder Variablen entsprechen würde.

Viele Programmierer (sogar einige mit guter Erfahrung) halten diese Art von Ansatz für völlig inakzeptabel, da es keine direkte Zuordnung zwischen einem Wert und einer benannten Variablen im Quellcode gibt. Dabei wird jedoch oft vergessen, dass der Name einer Variablen für die CPU irrelevant ist. Die CPU sieht nur eine Folge von Zahlen. Das war's. Sie hat keine Ahnung, wie der Name einer Variablen oder Konstanten lautet. Diese Art von Information ist völlig irrelevant für die Sache.

Daher sieht das Speichermodell, das durch Code 01 erstellt wird, in etwa so aus wie die unten gezeigte Darstellung:

Abbildung 02

Das Konzept, das wir hier erörtern, kann ziemlich verwirrend sein, vor allem, wenn wir später ein anderes Konzept untersuchen, das diesem sehr ähnlich sieht.

In Abbildung 02 sehen wir die in Abbildung 01 hervorgehobenen Werte zusammen mit einigen Markierungen. Diese Markierungen erscheinen in Grün und dienen folgenden Zwecken:

Hier beginnt ein neuer Wert, und er besteht aus so vielen Elementen.

Die blauen Rechtecke stellen die einzelnen Elementblöcke dar. Das heißt, wenn wir kein Array und stattdessen diskrete Variablen verwenden würden, bräuchten wir sechs separate Variablen, da es in Abbildung 02 sechs blaue Rechtecke gibt. Wir haben aber auch rote Rechtecke. Sie stellen jedes Element innerhalb des Arrays dar. Da es zehn Elemente gibt, sehen wir zehn rote Rechtecke. Wir könnten auch fünf rote Rechtecke haben, wenn jedes Element aus zwei Werten bestünde.

Aus Gründen der Fragmentierung (dieses Thema ist meiner Meinung nach zu komplex, um es hier zu erläutern) verwenden wir jedoch eine Mindestgröße für jedes Element. Dies hilft, eine Fragmentierung zu vermeiden, auch wenn es den späteren Dekodierungsprozess etwas verlangsamt. Dieser Vorgang wird von der Schleife in Zeile 70 ausgeführt, obwohl viele denken könnten, dass er von der Schleife in Zeile 67 erledigt wird. Die Dekodierung erfolgt nämlich in der Schleife in Zeile 70.

GUT. Ich glaube, dass dieser Teil bis zu diesem Punkt nicht allzu schwierig war. Schauen Sie sich nun Abbildung 02 genau an und halten Sie inne, um nachzudenken: Gibt es eine Möglichkeit, die blauen Rechtecke zu lesen, ohne unbedingt durch die roten Rechtecke zu gehen? Oder anders gefragt: Ist es möglich, allein durch Betrachtung des blauen Rechtecks die Werte der roten Rechtecke zu ermitteln? Das würde uns das Leben erheblich erleichtern. Dies würde sowohl den Kodierungsprozess zwischen den Zeilen 28 und 48 beschleunigen als auch die Dekodierung vereinfachen, da wir direkt von den blauen Rechtecken ausgehen würden. Das ist anders als in Code 01, wo wir jedes blaue Rechteck zerlegen, um die roten Elemente zu erhalten. Diese roten Elemente werden in dem Array gespeichert.

In der Tat, liebe Leserin, lieber Leser, ist diese Idee, die aus diesem Konzept entstanden ist, der Grundstein für das, was wir als Union kennen. Wenn wir eine Union verwenden, erstellen wir eine gemeinsame Speicherstruktur, die die blauen Rechtecke in Einheiten unterschiedlicher Größe unterteilt. Der Umfang der einzelnen Einheiten oder Elemente hängt vom Zweck und Ziel des zu entwickelnden Codes oder der Anwendung ab. Sobald jedoch eine Vereinigung definiert ist, kann der Programmierer jeden Teil eines größeren Blocks auf eine völlig individuelle Weise steuern. Dieses Konzept wird häufig in C- und C++-Code verwendet, wo Unions uns helfen, ganze Ketten von Elementen auf sehr einfache, reibungslose und sichere Weise zu manipulieren.

Um zu verstehen, wie eine Vereinigung funktioniert, beginnen wir mit etwas Einfachem, bevor wir uns wieder der Änderung von Code 01 zuwenden. Schauen wir uns also den unten stehenden Code an:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union un_01
07.     {
08.         ulong   u64_bits;
09.         uint    u32_bits[2];
10.     }info;
11. 
12.     uint tmp;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
17. 
18.     tmp = info.u32_bits[0];
19.     info.u32_bits[0] = info.u32_bits[1];
20.     info.u32_bits[1] = tmp;
21. 
22.     PrintFormat("After modification : 0x%I64X", info.u64_bits);
23. }
24. //+------------------------------------------------------------------+

Code 02

Beachten Sie, wie ein Konzept ganz natürlich zu einem anderen führt. Ohne das Verständnis von Arrays, wie zuvor erklärt, werden Sie nicht in der Lage sein zu verstehen, was an dieser Stelle des Codes getan wird. Und ohne den vorherigen Artikel zu verstehen, wird das, was Sie hier sehen werden, überhaupt keinen Sinn ergeben. Aber ich glaube, es ist jetzt der richtige Zeitpunkt, um zu erklären, was eine Union ist und was ihr praktischer Zweck ist.

Wenn Sie Code 02 ausführen, sehen Sie im MetaTrader 5-Terminal das unten gezeigte Bild:

Abbildung 03

Dies ist der Kernpunkt der Unions. Beachten Sie, dass dies fast magisch wirkt. Dies ist nur die erste Stufe dessen, was wir tun können. Aber wir müssen verstehen, was genau im Code 02 passiert. Zunächst muss eine Vereinigung deklariert werden, wie in Zeile sechs zu sehen ist. Mit anderen Worten: Wir verwenden das reservierte Schlüsselwort union, gefolgt von einem Bezeichner, der als Name der Union dient. Hier gibt es einen Unterschied zwischen der Deklaration von Unions in MQL5 und der Deklaration in C oder C++. In MQL5 ist es nicht möglich, eine anonyme Vereinigung zu erstellen. Das bedeutet, dass der Bezeichner, der auf das Schlüsselwort union folgt, existieren muss.

Wenn in Sprachen wie C und C++ der Bezeichner weggelassen wird, erhalten wir eine so genannte anonyme Vereinigung. Der Nachteil einer anonymen Union ist, dass man sie nicht außerhalb ihres Deklarationsblocks referenzieren kann. Ein Beispiel dafür werden wir in Kürze in einem anderen Codefragment sehen. Aber konzentrieren wir uns erst einmal auf den Code 02. Wie Sie sehen, beginnt die Vereinigung nach ihrer Deklaration und Benennung mit einer öffnenden Klammer { und endet mit einer schließenden Klammer }. Alles innerhalb dieses Blocks ist Teil der Vereinigung und teilt sich denselben Speicherbereich.

Jetzt kommt der entscheidende Teil, auf den wir gewartet haben. Alles im Speicher muss in Bytes gedacht werden. ABSOLUT ALLES. Um also eine Union korrekt zu definieren, müssen Sie sich überlegen, wie der Speicher in Bytes aufgeteilt werden soll. Schauen Sie sich noch einmal Abbildung 02 an. Dort sind 10 Bytes dargestellt. Die Art und Weise, wie Sie diese gruppieren (wobei „gruppieren“ vielleicht nicht der genaueste Begriff ist), wird Ihnen bei der Schaffung Ihrer Union als Richtschnur dienen. Die in Zeile sechs definierte Union belegt acht Bytes Speicherplatz. Ihr größtes Mitglied ist die Variable u64_bits.

Was ist mit der Variablen u32_bits, die ein Array ist? Würde er nicht auch acht weitere Bytes benötigen, da jedes seiner Elemente vier Bytes belegt? Sollte die Vereinigung also nicht insgesamt 16 Bytes belegen? Nein, lieber Leser. Die Vereinigung wird nur acht Bytes belegen. Das liegt daran, dass sich u64_bits und u32_bits denselben Speicherplatz teilen.

Ich weiß, dass dies auf den ersten Blick sehr verwirrend sein kann. Gehen wir es also langsam an, denn die Dinge werden nur noch komplizierter, wenn wir irgendwelche Schritte in der Erklärung überspringen.

Das Ziel von Code 02 besteht genau darin, einen Tausch zwischen einem Teil des Speichers und einem anderen durchzuführen. Schließlich wollen wir die im Gedächtnis gespeicherten Informationen drehen. Hierfür benötigen wir eine temporäre Variable. Sie wird in Zeile 12 angegeben. Ein wichtiger Hinweis: Diese temporäre Variable muss eine Größe (in Bytes) haben, die gleich oder größer ist als die kleinste Variable in der Vereinigung. In der Regel verwenden wir denselben Typ, sodass die richtige Größe beibehalten wird. Dies ist wichtig, um das angestrebte Ergebnis zu erreichen.

In Zeile 14 wird dann die Vereinigung initialisiert. Passen Sie hier gut auf. Die eigentliche Variable, deren Speicherbereich verwendet wird, wird in Zeile 10 deklariert. Da wir jedoch nicht direkt auf diesen Speicherbereich verweisen können (da er mehrere Variablen enthalten kann), müssen wir dem Compiler mitteilen, auf welche Mitgliedsvariable der Union wir zugreifen wollen. Sie können jedes Mitglied verwenden, das der Union angehört. Wenn Sie dies richtig machen, wird der Compiler den richtigen Wert verstehen und zuweisen und den Speicherbereich entsprechend aktualisieren.

Um dies besser zu verstehen, gehen wir noch einmal zurück zu Abbildung 02. Stellen Sie sich vor, dass das gesamte Bild eine Union ist - und das ist nicht weit von der Wahrheit entfernt. (Auf diese Idee werden wir später noch genauer eingehen). Stellen Sie sich nun vor, dass jedes rote Rechteck einen Namen hat. Wenn Sie auf eines dieser Rechtecke zugreifen wollen, brauchen wir dem Compiler nur den Namen des Rechtecks mitzuteilen, und er wird aus diesem speziellen Teil des Speichers lesen oder in ihn schreiben.

Ziemlich cool, oder? Aber lassen Sie uns einen Schritt nach dem anderen machen. Zunächst müssen Sie sich mit Code 02 befassen. Sobald wir dem Mitglied u64_bits einen Wert zuweisen, enthält der gesamte Speicherbereich namens info nun den in Zeile 14 angegebenen Wert.

Um dies zu bestätigen, verwenden wir Zeile 16, um den Speicherinhalt anzuzeigen. Dies gibt unsere erste Ausgabezeile im Terminal aus. Jetzt kommt der interessanteste Teil: Wir wollen die Werte in diesem Speicher vertauschen, also im Wesentlichen einen neuen Wert im selben Speicherbereich erzeugen. Durch die gemeinsame Nutzung des Speichers, den die Union zur Verfügung stellt, können wir dies schnell und einfach tun.

Der erste Schritt besteht darin, unsere temporäre Variable zum Speichern eines der Werte zu verwenden. Das geschieht in Zeile 18. In Zeile 19 weisen wir den Wert bei Index 1 des Arrays dem Index 0 zu. Zu diesem Zeitpunkt ist unser Speicher ein ziemliches Durcheinander, da er nur einen Teil des ursprünglichen Wertes enthält. Um den Vorgang abzuschließen, verwenden wir Zeile 20, um den ursprünglichen Wert, der sich in Index 0 befand, in Index 1 zu verschieben. Der Tauschvorgang ist nun abgeschlossen, und in Zeile 22 wird das Ergebnis ausgedruckt - dies ist die zweite Zeile in Abbildung 03.

Wenn Sie dachten, dass dies schon sehr wild war, dann machen Sie sich bereit für ein noch interessanteres Beispiel, bei dem wir den gesamten Inhalt auf einfache und effiziente Weise spiegeln. Werfen Sie einen Blick auf Code 03, wie unten dargestellt:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union un_01
07.     {
08.         ulong   u64_bits;
09.         uchar   u8_bits[sizeof(ulong)];
10.     };
11. 
12.     un_01 info;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("The region is composed of %d bytes", sizeof(info));
17. 
18.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
19. 
20.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
21.     {
22.         tmp = info.u8_bits[i];
23.         info.u8_bits[i] = info.u8_bits[j];
24.         info.u8_bits[j] = tmp;
25.     }
26. 
27.     PrintFormat("After modification : 0x%I64X", info.u64_bits);
28. }
29. //+------------------------------------------------------------------+

Code 03

Dieser Code 03 ist noch interessanter als der vorherige. Wenn Sie es ausführen, sehen Sie im MetaTrader 5 Terminal, was in Abbildung 04 unten gezeigt wird.

Abbildung 04

Mann, was für eine verrückte Sache. Aber wirklich cool und sehr interessant. Ich hatte selbst versucht, so etwas zu machen: Ich hatte zu kämpfen, zu schwitzen und mich durchzukämpfen, nur um es zum Laufen zu bringen. Dann kommt dieser Ansatz, der zeigt, wie wir das auf so einfache, leichte und praktische Weise tun können. Wirklich beeindruckend. Ich habe es geliebt! Aber jetzt habe ich ein paar Fragen.

Lassen Sie uns Schritt für Schritt vorgehen, damit ich es besser verstehen kann. Erste Frage: Was genau ist in Zeile 9 zu sehen? Bei der Arbeit mit Unions ist es üblich, Arrays einzubinden, um den Zugriff auf bestimmte Teile des gemeinsamen Speichers zu erleichtern. Es gibt auch andere Möglichkeiten, dies ohne Arrays zu tun, aber darauf gehen wir später noch ein. In den meisten Fällen möchten Sie auf den gesamten Speicherbereich zugreifen, damit Sie ihn richtig bearbeiten können. Es ist also nicht ungewöhnlich, dass etwas wie in Zeile 9 erscheint. In echtem Code könnte die Erklärung allerdings etwas anders aussehen. Das Prinzip und der Zweck bleiben derselbe: Es soll eine Möglichkeit geschaffen werden, auf jedes Element des Speicherblocks innerhalb der Vereinigung zuzugreifen.

Nächste Frage: „Was ist das für eine seltsame Sache, die in Zeile 12 angegeben wird? Das ergibt für mich keinen Sinn.“ Zeile 12 ist eine perfekte Demonstration dessen, was wir in Code 02 besprochen haben. Erinnern Sie sich, dass anonyme Vereinigungen in MQL5 nicht erlaubt sind? Da wir den Union-Typ in Zeile 6 mit einem Bezeichner un_01 definiert haben, können wir jetzt Variablen dieses Typs deklarieren, wie die in Zeile 12. Sie können mehrere Variablen haben, die jeweils unterschiedliche Werte der Union enthalten und alle auf demselben Bezeichner basieren. Wenn Sie nämlich einen speziellen Typ definieren (und eine Union ist ein spezieller Typ), können Sie seinen Bezeichner im gesamten Code wiederverwenden, genau wie jeden anderen Typ.

Denken Sie daran, dass für diesen Bezeichner die gleichen Regeln für den Geltungsbereich und die Sichtbarkeit gelten wie für jede andere Variable oder Konstante. Dies wurde in früheren Artikeln erörtert. Aber es gibt etwas Wichtiges zu beachten: Eine Vereinigung ist IMMER eine Variable, sie kann keine Konstante sein. Selbst wenn Sie auf einen konstanten Wert verweisen, wird die Vereinigung selbst immer als Variable behandelt. Ein besonderer Variablentyp, ähnlich wie eine Zeichenkette, aber dennoch eine Variable.

Deshalb sind diese Artikel so aufgebaut, dass Sie bestimmte grundlegende Konzepte verstehen, bevor Sie sie anwenden können. Ohne den Unterschied zwischen einer Variablen und einer Konstanten zu verstehen, wäre es schwierig zu erklären, worüber wir hier sprechen. Ein weiterer sehr wichtiger Punkt: ein Array innerhalb einer Union wird unter keinen Umständen vom dynamischen Typ sein. Es wird immer statisch sein.

Daher ist es sinnlos, mit einem dynamischen Array eine massive Vereinigung zu versuchen. Wenn Sie dies versuchen, wird der Compiler nicht verstehen, was Sie zu tun versuchen.

Ein letztes Detail, das Ihnen vielleicht noch nicht aufgefallen ist: Wie ist die Schleife in Zeile 20 in der Lage, den Inhalt des Speicherbereichs zu spiegeln? Damit die Spiegelung funktioniert, müssen Sie einen Zähler verwenden, der nur die Hälfte des Speicherbereichs durchläuft. Da wir immer eine gerade Anzahl von Elementen verwenden, ist dies einfach zu bewerkstelligen. Die Schleife in Zeile 20 kann also tatsächlich jeden beliebigen Wert eines diskreten Typs spiegeln - sofern Sie die erforderlichen Anpassungen vornehmen (nicht an der Schleife selbst, sondern an der Deklaration des Blocks innerhalb der Union in Zeile sechs). Natürlich müssen Sie auch den in Zeile 14 angegebenen Wert anpassen. Abgesehen davon wären jedoch keine weiteren Änderungen des Kodex erforderlich.

Zum Beispiel: Wenn Sie eine Variable des Typs int oder einen beliebigen 32-Bit-Wert spiegeln wollen, müssen Sie nur die Typdeklarationen in den Zeilen acht und neun von ulong in int ändern. Danach aktualisieren Sie einfach den in Zeile 14 angegebenen Wert auf den gewünschten Wert - und das war's. Der Code ist nun in der Lage, einen int anstelle eines ulong zu spiegeln. So einfach ist das.

Es gibt einen noch einfacheren Weg, dies zu erreichen. Ein anderes Konzept, das wir hier in MQL5 verwenden können, habe ich jedoch noch nicht vorgestellt. Die oben erläuterte Methode ist also der einfachste Ansatz, den es derzeit gibt.

Bevor wir diesen Artikel abschließen, wollen wir noch eine letzte Sache untersuchen, die wir mit Unions machen können. Dies hängt mit ihrer Verwendung in Funktionen und Verfahren zusammen. Zur Veranschaulichung nehmen wir Code 03 und ändern ihn so, dass die Schleife zunächst in eine Funktion verschoben wird. Dann wenden wir dieselbe Logik in Form eines Verfahrens an. Wie auch immer, das Ziel ist, dass die Funktion oder Prozedur die Spiegelung für uns durchführt und wir dann das Ergebnis in der Hauptroutine anzeigen.

Ja, es gibt verschiedene Möglichkeiten, dies zu tun. Aber hier werden wir einen didaktischen Ansatz verwenden, da wir zeigen wollen, wie wir Unions in einem breiteren Kontext verwenden können. Beginnen wir mit dem Code, der meiner Meinung nach am einfachsten zu verstehen ist. Das liegt daran, dass wir eine Implementierung verwenden werden, die der in Code 03 sehr ähnlich ist.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. union un_01
05. {
06.     ulong u64_bits;
07.     uchar u8_bits[sizeof(ulong)];
08. };
09. //+------------------------------------------------------------------+
10. void OnStart(void)
11. {
12.     un_01 info;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("The region is composed of %d bytes", sizeof(info));
17. 
18.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
19. 
20.     Swap(info);
21. 
22.     PrintFormat("After modification : 0x%I64X", info.u64_bits);
23. }
24. //+------------------------------------------------------------------+
25. void Swap(un_01 &info)
26. {
27.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
28.     {
29.         tmp = info.u8_bits[i];
30.         info.u8_bits[i] = info.u8_bits[j];
31.         info.u8_bits[j] = tmp;
32.     }
33. }
34. //+------------------------------------------------------------------+

Code 04

Der Code 04 ist recht einfach zu verstehen. Allerdings müssen Sie einige wichtige Details beachten. Der erste ist, dass die Union nicht mehr lokal, sondern global ist. Dies geschah speziell, um der in Zeile 25 deklarierten Prozedur Zugriff auf den in Zeile vier definierten speziellen Typ zu gewähren. Ohne die Union global zu machen, wäre es unmöglich, den speziellen Typ zu verwenden, den wir als un_01 in der Parameterdefinition in Zeile 25 deklariert haben. Beachten Sie, dass wir hier nur den Code, der sich zuvor in der Hauptroutine befand, in eine separate Prozedur verschoben haben. Und anstelle der Schleife in Zeile 20 von Code 03 verwenden wir einen Aufruf unserer neuen Prozedur in derselben Zeile. Im Grunde haben wir gerade einen Codeblock veröffentlicht, der zuvor privat war. Ich glaube wirklich, dass Sie Code 04 ohne Schwierigkeiten verstehen können. Betrachten wir nun ein anderes Szenario, bei dem wir anstelle einer Prozedur eine Funktion verwenden. Dieses Beispiel ist unten dargestellt.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. union un_01
05. {
06.     ulong u64_bits;
07.     uchar u8_bits[sizeof(ulong)];
08. };
09. //+------------------------------------------------------------------+
10. void OnStart(void)
11. {
12.     un_01 info;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("The region is composed of %d bytes", sizeof(info));
17. 
18.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
19. 
20.     PrintFormat("After modification : 0x%I64X", Swap(info).u64_bits);
21. }
22. //+------------------------------------------------------------------+
23. un_01 Swap(const un_01 &arg)
24. {
25.     un_01 info = arg;
26. 
27.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
28.     {
29.         tmp = info.u8_bits[i];
30.         info.u8_bits[i] = info.u8_bits[j];
31.         info.u8_bits[j] = tmp;
32.     }
33. 
34.     return info;
35. }
36. //+------------------------------------------------------------------+

Code 05

Nun, hier sind die Dinge etwas komplizierter, aber nur, weil das Konzept für Sie zu diesem Zeitpunkt neu sein könnte. Beachten Sie, dass der Code sehr ähnlich zu dem ist, den wir zuvor gesehen haben, als wir eine Prozedur verwendet haben. Wenn Sie sich jedoch die Zeile 20 der letzten drei Beispiele genau ansehen, könnte die in Code 05 gezeigte Version für Programmierer mit wenig Erfahrung die schwierigste sein. Aber es gibt keinen Grund zur Panik. Schließlich arbeiten wir einfach mit einer Funktion, die in Zeile 23 deklariert wurde. Lassen Sie uns zunächst etwas Interessantes hervorheben. Sowohl in Code 03 als auch in Code 04 wird der Inhalt der in Zeile 12 deklarierten Variablen zwangsläufig geändert. Das ist eine Tatsache. In Code 05 wird der Inhalt derselben in Zeile 12 deklarierten Variablen jedoch nicht geändert.

Wie kann das sein? Führen wir nicht den Spiegelungsvorgang durch, um das in Abbildung 04 gezeigte Ergebnis zu erzielen? Die Spiegelung findet tatsächlich statt. Und in allen drei letztgenannten Beispielen entspricht das Ergebnis dem in Abbildung 04. Aber wenn ich sage, dass die Variable in Zeile 12 nicht geändert wird, dann meine ich genau das - sie bleibt unverändert. Sie können dies überprüfen, indem Sie feststellen, dass wir in Zeile 23 den Wert als konstante Referenz übergeben.

Jetzt mag alles noch verwirrender erscheinen. Vorhin habe ich erwähnt, dass Unions nicht mit Konstanten verwendet werden können, und jetzt sage ich, dass wir es können. Verwirrend, nicht wahr? Na gut, vielleicht habe ich mich nicht klar ausgedrückt. Oder vielleicht gibt es eine Verwechslung zwischen Zuweisung und Erklärung. Die Deklaration in Zeile 23 definiert den Parameter als konstant. Das bedeutet, dass die übergebene Variable innerhalb der Funktion nicht verändert werden kann. Damit die Funktion funktionieren kann, brauchen wir jedoch noch eine veränderbare Variable. Das ist genau das, was wir in Zeile 25 erstellen. Diese Variable wird geändert, und ihr Ergebnis wird in Zeile 34 zurückgegeben.

Dies ist der Punkt, an dem viele Menschen verwirrt sein könnten. Wenn Swap in Code 05 eine Funktion ist, die eine Variable zurückgibt, sollten wir diesen Rückgabewert dann nicht einer anderen Variablen zuweisen, bevor wir sie verwenden? Es kommt darauf an, lieber Leser. Da eine Funktion jedoch ein Variablentyp ist, wie in einem früheren Artikel erläutert, können wir den in Zeile 4 deklarierten speziellen Typ verwenden, um direkt auf die zurückgegebenen Daten zuzugreifen.

Dies ist nur möglich, weil die Funktion ausdrücklich diesen spezifischen Typ zurückgibt. Wäre der Rückgabewert ein diskreter Typ, wäre die in Zeile 20 gezeigte Implementierung nicht machbar. In diesem Fall bräuchten wir einen anderen Mechanismus, um das gleiche Ergebnis zu erzielen. Aber da dies ein Thema ist, das an dieser Stelle unnötige Verwirrung stiften könnte, werde ich es auf ein anderes Mal verschieben. Es gibt Möglichkeiten, dies zu erreichen.


Abschließende Überlegungen

In diesem Artikel haben wir uns zunächst damit beschäftigt, was eine Union ist. Wir haben mit den ersten praktischen Szenarien experimentiert, in denen eine Union zum Einsatz kommen könnte. Was wir hier gesehen haben, sind jedoch nur die Grundlagen, die Teil eines größeren Pakets von Konzepten und Techniken sind, die wir in zukünftigen Artikeln weiter entwickeln werden. Hier ist also ein goldener Tipp für Sie, liebe Leserin, lieber Leser. Üben und studieren Sie gründlich alles, was Sie hier gesehen haben.

Im Anhang finden Sie die wichtigsten Code-Beispiele, die in diesem Artikel behandelt werden. Und im nächsten Artikel werden wir noch tiefer in die Grundlagen der MQL5-Programmierung eintauchen. Bis bald!

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

Beigefügte Dateien |
Anexo.zip (1.98 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (3)
Константин Сандалиди
Nichts ist klar! Ich wünschte, jemand würde eine Artikelserie "Vom Nullniveau zum Anfänger" starten! Sie haben einen Anfänger mit 2 Hochschulabschlüssen in Programmierung....
CODE X
CODE X | 3 Juni 2025 in 10:28
Константин Сандалиди # :
Não entendo nada! Ich würde es sehr begrüßen, wenn jemand eine Artikelserie "Do nível zero ao iniciante" einrichten würde! E aí você tem um iniciante com duas formações superiores em programação ....

Aber der Zweck dieses Artikels von mir ist genau das. Eine Person soll bei Null anfangen. Sie sind jedoch in einen Artikel eingestiegen, in dem das Material schon etwas fortgeschrittener ist. Ich schlage vor, dass Sie mit diesem Artikel beginnen:

Grundlagen bis Mittelstufe: Variablen (I)

Detail: Alle Artikel mit fortgeschrittenem Material haben einen Link am Anfang, damit Sie den vorherigen Artikel sehen können. Aber diesen hier empfehle ich. Er ist sogar der erste. Wo ich anfange, die Dinge von Grund auf zu erklären 🙂👍

antar
antar | 3 Juni 2025 in 10:56
Константин Сандалиди #:
Nichts ist klar! Ich wünschte wirklich, jemand würde eine Artikelserie "Vom Nullniveau zum Anfänger" starten! Sie haben einen Anfänger mit zwei Abschlüssen in Programmierung....

Das ist eine Übersetzung aus dem Portugiesischen, und sie ist nicht die beste. 😑

Was im Original auf Russisch geschrieben ist, wird viel klarer sein. Zum Beispiel, dieses Buch.

Entwicklung eines Expert Advisors für mehrere Währungen (Teil 19): In Python implementierte Stufen erstellen Entwicklung eines Expert Advisors für mehrere Währungen (Teil 19): In Python implementierte Stufen erstellen
Bisher haben wir die Automatisierung des Starts von sequentiellen Verfahren zur Optimierung von EAs ausschließlich im Standard-Strategietester betrachtet. Was aber, wenn wir zwischen diesen Starts die gewonnenen Daten mit anderen Mitteln bearbeiten wollen? Wir werden versuchen, die Möglichkeit hinzuzufügen, neue Optimierungsstufen zu erstellen, die von in Python geschriebenen Programmen ausgeführt werden.
Entwicklung eines Replay-Systems (Teil 73): Eine ungewöhnliche Kommunikation (II) Entwicklung eines Replay-Systems (Teil 73): Eine ungewöhnliche Kommunikation (II)
In diesem Artikel werden wir uns ansehen, wie Informationen in Echtzeit zwischen dem Indikator und dem Dienst übertragen werden können, und wir werden auch verstehen, warum bei der Änderung des Zeitrahmens Probleme auftreten können und wie man sie lösen kann. Als Bonus erhalten Sie Zugang zur neuesten Version der Wiedergabe-/Simulations-App.
Optimierungsmethoden der ALGLIB-Bibliothek (Teil I) Optimierungsmethoden der ALGLIB-Bibliothek (Teil I)
In diesem Artikel werden wir uns mit den Optimierungsmethoden der ALGLIB-Bibliothek für MQL5 vertraut machen. Der Artikel enthält einfache und anschauliche Beispiele für die Verwendung von ALGLIB zur Lösung von Optimierungsproblemen, die das Erlernen der Methoden so einfach wie möglich machen. Wir werden uns die Verbindung von Algorithmen wie BLEIC, L-BFGS und NS im Detail ansehen und sie zur Lösung eines einfachen Testproblems verwenden.
Neuronale Netze im Handel: Der Contrastive Muster-Transformer Neuronale Netze im Handel: Der Contrastive Muster-Transformer
Der Contrastive Transformer wurde entwickelt, um Märkte sowohl auf der Ebene einzelner Kerzen als auch auf der Basis ganzer Muster zu analysieren. Dies trägt dazu bei, die Qualität der Modellierung von Markttrends zu verbessern. Darüber hinaus fördert der Einsatz des kontrastiven Lernens zum Abgleich der Darstellungen von Kerzen und Mustern die Selbstregulierung und verbessert die Genauigkeit der Prognosen.