Von der Grundstufe bis zur Mittelstufe: Structs (II)
Im vorigen Artikel, Von der Grundstufe zur Mittelstufe: Struct (I), haben wir mit der Erörterung eines Themas begonnen, das meines Erachtens sehr wichtig ist, um es so klar wie möglich zu verstehen. Das liegt daran, dass wir damit viele Dinge auf viel einfachere Weise erledigen können. Die wahre Bedeutung des Verständnisses für jedes Detail liegt jedoch in der Tatsache, dass Strukturen auf halbem Weg zwischen der objektorientierten Programmierung und der herkömmlichen Programmierung liegen.
Da dieses Thema erst am Anfang steht, haben wir noch viel zu tun, bevor wir wirklich etwas sagen können: Ja, ich weiß bereits, wie man mit Strukturen arbeitet.
Verwendung von Strukturen in Funktionen und Prozeduren
Eines der Dinge, die viele Anfänger beim Umgang mit Strukturen ratlos zurücklassen, ist die Frage, ob Werte mit Hilfe von Strukturen übergeben werden sollten oder nicht. Das ist in der Tat eine sehr interessante Frage, die manchmal mehr Verwirrung stiftet als alles andere. Im Gegensatz zu dem, was manche denken mögen, liegt der Grund dafür darin, dass wir Variablen per Verweis übergeben können, sei es an eine Funktion oder an eine Prozedur. Und wenn dies der Fall ist, müssen wir bei der Arbeit mit Strukturen in solchen Szenarien vorsichtig sein.
Als jemand der alten Schule habe ich eine Zeit erlebt, in der die Sprache C keine Datenübertragung über Strukturen erlaubte, zumindest nicht direkt. Heute ist dies möglich, aber es gab eine Zeit, in der wir für solche Überweisungen andere Mechanismen verwenden mussten. In diesem Szenario wuchsen die Fehlermöglichkeiten, je mehr Variablen die Struktur enthielt. Aber das ist die Vergangenheit. Heute verfügen wir über sicherere Mechanismen, um dieselbe Art von Übertragung durchzuführen. Dennoch hält Sie nichts davon ab, ältere Implementierungstechniken zu verwenden, bei denen die Verarbeitungsgeschwindigkeit und nicht die Sicherheit im Vordergrund steht.
Obwohl es möglich ist, solche Dinge zu tun, werde ich NICHT ZEIGEN, wie man sie umsetzt, da die Schwierigkeit und das Risiko von Fehlern sehr hoch sind. Deshalb sollten wir lernen, wie wir die Dinge richtig angehen. Zunächst müssen Sie verstehen, dass eine Struktur nicht immer zwischen verschiedenen Routinen weitergegeben werden muss. Mit anderen Worten: Eine Funktion oder Prozedur muss nicht unbedingt wissen, dass sie mit einer Datenstruktur oder mit diskreten Werten arbeitet.
Wenn wir die bereits in anderen Artikeln gezeigten und erläuterten Mechanismen nutzen, können wir den Code wesentlich flexibler und nutzerfreundlicher gestalten. Denn wenn wir erst einmal anfangen, Strukturen zu verwenden, kann es passieren, dass wir bestimmte Aktivitäten einer bestimmten Routine einschränken.
Um dies zu verstehen, schauen wir uns ein einfaches Stück Code an, das unten gezeigt wird.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. MqlDateTime dt; 07. 08. TimeToStruct(TimeLocal(), dt); 09. 10. Print(DateToString(dt.day, dt.mon, dt.year)); 11. Print(DateToString(3, 15, 2024)); 12. } 13. //+------------------------------------------------------------------+ 14. string DateToString(int day, int month, int year) 15. { 16. if ((day < 1) || (day > 31) || (month < 1) || (month > 12)) 17. return "ERROR..."; 18. 19. return StringFormat("%02d/%02d/%d", day, month, year); 20. } 21. //+------------------------------------------------------------------+
Code 01
Wenn dieser Code 01 ausgeführt wird, führt er zu dem unten dargestellten Ergebnis:

Abbildung 01
Beachten Sie, dass wir in Code 01 kaum etwas Komplexes oder schwer zu Verstehendes verwenden. Aber das Ergebnis, das Sie sehen werden, wenn Sie diesen Code ausführen, wird sich sicherlich von dem in Abbildung 01 gezeigten unterscheiden. Der Grund dafür ist einfach: die Verwendung der Funktion TimeLocal. Diese Funktion aus der MQL5-Standardbibliothek erfasst den aktuellen Uhrwert und gibt ihn im Datetime-Format zurück. Dieses Format ist ein Acht-Byte-Typ und enthält Informationen über den aktuellen Tag und die aktuelle Uhrzeit an dem Ort, an dem der MetaTrader 5 ausgeführt wird. Natürlich wird diese Zeit in Sekunden gezählt, aber das ist für unsere Zwecke unerheblich. Derselbe Wert, der von TimeLocal zurückgegeben wird, wird jedoch von der Funktion TimeToStruct verwendet, um die Struktur MqlDateTime zu füllen. Und genau dort wollen wir ankommen. Die MqlDateTime-Struktur wird wie unten gezeigt deklariert:
struct MqlDateTime { int year; int mon; int day; int hour; int min; int sec; int day_of_week; int day_of_year; };
Deklaration von MqlDateTime
Hören Sie gut zu, was ich jetzt erkläre, denn es ist wichtig für das Verständnis dessen, was wir in Code 01 tun. Die Funktion TimeToStruct zerlegt den 64-Bit-Wert, den wir als Datetime-Typ kennen, in Werte und weist jeder Variablen in der obigen Strukturdeklaration einen entsprechenden Wert zu. Es gibt auch andere Möglichkeiten, dies zu tun, aber das ist hier nicht der Punkt. Sobald die Werte zugewiesen sind, können wir sie verwenden, wie in Zeile 10 von Code 01 gezeigt. Beachten Sie jedoch, dass dies nur möglich war, weil wir in der Funktion in Zeile 14 Argumente vom Typ Primitiv verwenden, um die Daten zu empfangen. So können wir unnötige Werte verwerfen und nur das verwenden, was wir tatsächlich brauchen.
Der gleiche Ansatz ermöglicht jedoch auch die Verwendung in Zeile 11. Aufgrund der Prüfung in Zeile 16 gibt die Routine in Zeile 14 jedoch einen Fehler zurück, da es KEIN Jahr mit fünfzehn Monaten geben kann.
Dies ist die Art von Situation, die ich hervorheben wollte. Der gleiche Ansatz, der hier in Code 01 gezeigt wird, könnte auf verschiedene Weise umgesetzt werden, wie in Code 02 unten zu sehen ist.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. MqlDateTime dt; 07. 08. TimeToStruct(TimeLocal(), dt); 09. 10. Print(DateToString(dt)); 11. dt.day = 3; 12. dt.mon = 15; 13. dt.year = 2024; 14. Print(DateToString(dt)); 15. } 16. //+------------------------------------------------------------------+ 17. string DateToString(MqlDateTime &arg) 18. { 19. if ((arg.day < 1) || (arg.day > 31) || (arg.mon < 1) || (arg.mon > 12)) 20. return "ERROR..."; 21. 22. return StringFormat("%02d/%02d/%d", arg.day, arg.mon, arg.year); 23. } 24. //+------------------------------------------------------------------+
Code 02
In diesem Fall hätten wir das gleiche Ergebnis wie in Abbildung 01. Es wäre jedoch viel einfacher, den Grund für den Fehler zu verstehen, denn wenn man sich die Zeile 12 in Code 02 ansieht, kann man deutlich erkennen, was falsch ist. Beachten Sie jedoch, dass in Zeile 17 die Struktur per Referenz übergeben werden musste. Das kann ein Problem sein, aber es ist auch leicht zu umgehen, da wir nur sicherstellen müssen, dass das empfangene Argument als Konstante behandelt wird. Wie man das macht, wurde bereits in anderen Artikeln erklärt. Dennoch bleibt die Tatsache bestehen, dass die Funktion nun weitaus mehr Privilegien hat als zuvor, da sie mehr Daten sehen kann, als für ihr Funktionieren erforderlich sind. Aus diesem Grund gibt es auch keine allgemeingültige Antwort auf die Frage, wie wir Strukturen in Funktionen oder Prozeduren verwenden sollten, da Sie möglicherweise weit mehr Informationen als nötig weitergeben.
Dieses Konzept des minimalen Wissens, das eine Funktion oder Prozedur über andere Daten haben sollte, wird viel häufiger erwähnt, wenn es um Klassen und Objekte geht. Denn in der objektorientierten Programmierung ist dieses Prinzip viel präsenter, als Sie vielleicht denken. Aber das ist ein Thema für die Zukunft. Je weniger eine Routine (sei es eine Funktion oder eine Prozedur) wissen muss, desto besser. Dadurch wird die Menge an Informationen, die wir deklarieren und weitergeben müssen, reduziert, was die Implementierung und zukünftige Fehlersuche und Verbesserungen beschleunigt.
Okay, aber diese beiden Teile des Codes, die wir gerade gesehen haben, beziehen sich auf eine bestimmte Aktivität. Es ist jedoch nicht ungewöhnlich, dass man nutzerdefinierte Strukturen erstellen muss, auch wenn MQL5 bereits zwölf vordefinierte Strukturen bietet. Zu einem späteren Zeitpunkt werden wir auf jede dieser Strukturen eingehen. Eine davon haben wir bereits kurz besprochen: die MqlDateTime. Aber das war nur eine kurze Einführung in das, was wir später tun können und werden.
Jetzt fragen Sie sich vielleicht, lieber Leser: Wie implementiere ich etwas, das eine Struktur verwendet, die ich selbst erstellt habe? Nun, um diese Frage richtig zu beantworten, werden wir ein neues Thema eröffnen, da ich die Themen nicht vermischen möchte. Auch wenn die Antwort etwas mit dem aktuellen Thema zu tun hat. Dennoch möchte ich die Dinge klarstellen.
Arbeiten mit nutzerdefinierten Strukturen
Obwohl die Antwort auf die am Ende des vorherigen Themas gestellte Frage in engem Zusammenhang steht, möchte ich die Dinge trennen, um das Material so didaktisch wie möglich zu gestalten. Denn wenn wir eine der zwölf vordefinierten Strukturen in MQL5 verwenden, können wir die Dinge auf eine bestimmte Weise implementieren. Wenn wir jedoch etwas mit einer von uns selbst geschaffenen Struktur implementieren, müssen wir die Dinge oft anders umsetzen. Dafür gibt es Gründe, die im vorherigen Artikel erläutert wurden. Auf einen weiteren Grund werden wir gleich eingehen.
Aber vorher müssen wir noch etwas verstehen:
Eine Struktur ist ein spezieller Datentyp, der beliebige Informationen enthalten kann.
Es ist sehr wichtig, diese Aussage richtig zu verstehen. Wenn Sie wirklich verstehen, was eine Struktur ist, wird es viel einfacher zu verstehen, wie Sie sie in Ihrem Code implementieren und verwenden können. Verstehen Sie also, lieber Leser, dass sich eine Struktur nicht von einem uchar-, double- oder einem anderen Typ unterscheidet. Genauso wie diese Typen es uns ermöglichen, die in ihnen enthaltenen Werte zu speichern und mit ihnen zu arbeiten, können wir dasselbe mit Strukturen tun. Dies ähnelt in gewisser Weise dem, was wir bei Unions gesehen haben, bei denen es sich nicht wirklich um einen speziellen Datentyp handelt, sondern eher um einen Satz von Daten, die sich einen gemeinsamen Speicherbereich teilen.
Um zu verdeutlichen, dass es sich bei einer Struktur tatsächlich um einen speziellen Datentyp handelt, denken wir an Registrierungsdatensätze. In einem Registrierungsdatensatz können wir verschiedene Informationen wie Vorname, Nachname, Adresse, Beruf, Kontakte usw. haben. Alle diese Daten können so organisiert werden, dass sie das gleiche Basisformat haben. Dies ermöglicht es, Datensätze auf sehr einfache und praktische Weise zu bearbeiten.
Es gibt grundsätzlich zwei Möglichkeiten, dies zu tun. Die erste besteht darin, eine Reihe von Variablen zu erstellen, die die Informationen enthalten, die wir in einem Datensatz speichern wollen. Die gleichen Variablen würden im gesamten Code verstreut sein. Etwa wie unten zu sehen ist. Natürlich ist dies nur ein hypothetisches Beispiel.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string Name, 07. Surname, 08. Address, 09. Position; 10. double Remuneration; 11. datetime Birth, 12. Hiring; 13. . . . Registration routines . . .
Code 03
Wenn Sie nicht gerade eine Anwendung erstellen, deren Zweck darin besteht, einen einzigen Datensatz zu verarbeiten, ist es unwahrscheinlich, dass ein echter Code wie der in Code 03 gezeigte aussieht. Sie könnten jedoch tatsächlich einen Code haben, der Code 03 ähnelt, vorausgesetzt, die Datensätze werden auf der Festplatte gespeichert. Hier ergeben sich jedoch einige Probleme, z. B. die Reihenfolge, in der die Variablen gespeichert werden müssen. Doch dieses Problem ist nicht das, was uns im Moment wirklich interessiert. Das eigentliche Problem ist der Umgang mit Querverweisen oder Beziehungen zwischen Daten in verschiedenen Datensätzen.
Okay, wie könnten Sie das handhaben? Der häufigste Ansatz ist die Erstellung von Arrays. Da wir bereits über Arrays gesprochen haben, könnten Sie sofort daran denken, etwas zu erstellen, wie es unten gezeigt wird:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. #define def_Number_of_Records 10 07. 08. string Name[def_Number_of_Records], 09. Surname[def_Number_of_Records], 10. Address[def_Number_of_Records], 11. Position[def_Number_of_Records]; 12. double Remuneration[def_Number_of_Records]; 13. datetime Birth[def_Number_of_Records], 14. Hiring[def_Number_of_Records]; 15. . . . Registration routines . . .
Code 04
Beachten Sie, dass wir jetzt mit mehr als einem Datensatz gleichzeitig arbeiten können. Diese Technik funktioniert zwar, ist aber etwas ungeschickt. Denn wenn wir jemals ein Feld hinzufügen oder entfernen müssen, bedeutet dies eine Menge Arbeit für eine einfache Änderung. Und ich beziehe mich nur auf die Umsetzung, nicht auf die Verwendung. Aber wenn man sich den Code 04 ansieht und bedenkt, was wir bisher über Strukturen diskutiert haben, fragt man sich vielleicht: Können wir nicht alles aus Code 04 in eine Struktur packen? Und das ist genau der Punkt, an dem die Dinge beginnen, Sinn zu machen. Denn die ganze Komplexität, die im früheren Code zu sehen war, wird zu etwas wie dem, was wir jetzt sehen.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. #define def_Number_of_Records 10 07. 08. struct st_Register 09. { 10. string Name, 11. Surname, 12. Address, 13. Position; 14. double Remuneration; 15. datetime Birth, 16. Hiring; 17. }; 18. 19. st_Register Record[def_Number_of_Records]; 20. . . . Registration routines . . .
Code 05
Beachten Sie, wie natürlich die Idee entstanden ist. Wir brauchten keine komplette Anwendung zu erstellen, damit das Konzept Gestalt annahm und Sinn machte. Aber jetzt – wo es wirklich interessant wird – wird die in Zeile 8 von Code 05 deklarierte Struktur nur innerhalb der OnStart-Prozedur oder wo immer sie deklariert wurde, zugänglich sein. In diesem Fall müssen Sie verstehen, dass die Struktur st_Register nicht dazu verwendet werden kann, Daten an eine Funktion oder Prozedur zu übergeben, wie es in Code 02 der Fall ist. Um die Informationen in der in Zeile 8 von Code 05 angegebenen Struktur zu nutzen, müssen Sie einen ähnlichen Ansatz wie bei Code 01 wählen.
In einigen Fällen ist dies problematisch. In anderen Fällen kann es genau das sein, was wir brauchen. Im Allgemeinen werden Strukturen (im Gegensatz zu Unions) jedoch mit einem globalen Geltungsbereich deklariert. So können wir Daten auf angenehmere und praktischere Weise übertragen.
Jeder, der sich auch nur oberflächlich mit Sprachen wie Python oder JavaScript beschäftigt hat, weiß, dass es sehr üblich ist, etwas wie das unten Gezeigte zu verwenden:
cmd_entry.pack(fill='x', side='left' , expand=1)
In dieser Codezeile, die sowohl in Python als auch in JavaScript gültig ist, haben wir etwas, das für jemanden, der MQL5, C oder C++ verwendet, keinen Sinn ergibt. Denn wenn Sie sehen, dass einer Variablen in diesen Sprachen ein Wert zugewiesen wird, weisen Sie tatsächlich einer im Code vorhandenen Variablen einen Wert zu, unabhängig davon, ob diese global oder lokal ist. Und wenn Sie nach dieser Variablen suchen, finden Sie sie in einem der Bereiche, die in MQL5, C oder C++ gesprochen werden.
In Python oder JavaScript wird einer Variablen jedoch kein Wert zugewiesen. Ich weiß, das klingt völlig unlogisch. Aber wir ordnen etwas, das einer Struktur ähnelt, einen Wert zu. Dadurch können wir jedem Element in beliebiger Reihenfolge Werte zuweisen, was z. B. in MQL5 unmöglich wäre.
Wenn wir jedoch denselben Code in Python oder JavaScript betrachten, als etwas, das einer Struktur in MQL5 entspricht, beginnt alles einen Sinn zu ergeben. Es würde die Übergabe von Werten zwischen Funktionen und Prozeduren viel einfacher und angenehmer machen, besonders wenn viele Argumente übergeben werden müssen.
Achten Sie jetzt genau darauf, denn das kann einen Unterschied machen, wie Sie zukünftige Probleme angehen. In einigen früheren Artikeln haben wir die Tatsache erörtert, dass wir Funktionen und Prozeduren überladen können. Dies ermöglicht es uns, verschiedene Typen oder eine unterschiedliche Anzahl von Argumenten zu behandeln. Wenn wir jedoch in einigen Situationen Strukturen zur Übergabe dieser Werte verwenden würden, wären viele der Überladungen, die sonst notwendig wären, nicht mehr erforderlich. Das liegt daran, dass wir eine beliebige Anzahl von Argumenten übergeben können, indem wir sie einfach in eine Struktur einfügen.
Ziemlich interessant, oder? In der Tat gibt es keinen perfekten, absoluten Weg, Dinge zu tun. Es gibt jedoch Ansätze, die je nach Einzelfall mehr oder weniger interessant sind.
Nehmen wir also Folgendes an: Angenommen, Code 05 verwendet tatsächlich mehrere Funktionen und Prozeduren, die jeweils mehr oder weniger Argumente erfordern. Sie könnten den in Code 01 gezeigten Ansatz verwenden und jedes Argument kontrollieren, was jedoch den Preis hat, dass Sie möglicherweise eine Funktionsüberladung an mehreren Stellen benötigen. Sie könnten auch den Ansatz aus Code 02 übernehmen, aber der Preis dafür wäre das Risiko, dass ein Wert geändert wird, ohne dass Sie es merken. Es gibt jedoch eine Lösung, die in vielen Fällen tatsächlich angenommen wird. Bei dieser Lösung wird eine Funktion verwendet, wenn etwas geändert werden soll, und eine Prozedur, wenn keine Änderung, sondern nur eine Abfrage gewünscht wird.
In jedem Fall müsste die in Zeile 8 von Code 05 deklarierte Struktur aus dem lokalen Bereich in den globalen Bereich verschoben werden, wie im folgenden hypothetischen Code gezeigt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Register 05. { 06. string Name, 07. Surname, 08. Address, 09. Position; 10. double Remuneration; 11. datetime Birth, 12. Hiring; 13. }; 14. //+------------------------------------------------------------------+ 15. void OnStart(void) 16. { 17. st_Register Record[10]; 18. uchar Counter = 0, 19. Index; 20. 21. Index = Counter; 22. Counter += (NewRecord(Record[Counter], "Daniel", "Jose", "Brasil", "1971/03/30") ? 1 : 0); 23. Record[Index] = UpdatePosition(Record[Index], "Chief Programmer"); 24. Index = Counter; 25. Counter += (NewRecord(Record[Counter], "Edimarcos", "Alcantra", "Brasil", "1974/12/07") ? 1 : 0); 26. Record[Index] = UpdatePosition(Record[Index], "Programmer"); 27. Index = Counter; 28. Counter += (NewRecord(Record[Counter], "Carlos", "Almeida", "Brasil", "1985/11/15") ? 1 : 0); 29. Record[Index] = UpdatePosition(Record[Index], "Junior Programmer"); 30. Index = Counter; 31. Counter += (NewRecord(Record[Counter], "Yara", "Alves", "Brasil", "1978/07/25") ? 1 : 0); 32. Record[Index] = UpdatePosition(Record[Index], "Accounting"); 33. 34. Print("Number of records: ", Counter); 35. ViewRecord(Record[3]); 36. } 37. //+------------------------------------------------------------------+ 38. bool NewRecord(st_Register &arg, const string Name, const string Surname, const string Address, const string Birth) 39. { 40. arg.Name = Name; 41. arg.Surname = Surname; 42. arg.Address = Address; 43. arg.Birth = StringToTime(Birth); 44. arg.Hiring = TimeLocal(); 45. 46. return true; 47. } 48. //+------------------------------------------------------------------+ 49. st_Register UpdatePosition(const st_Register &arg, const string NewPosition) 50. { 51. st_Register info = arg; 52. 53. info.Position = NewPosition; 54. 55. return info; 56. } 57. //+------------------------------------------------------------------+ 58. void ViewRecord(const st_Register &arg) 59. { 60. PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s", 61. arg.Surname, 62. arg.Name, 63. arg.Position, 64. TimeToString(arg.Birth, TIME_DATE)); 65. } 66. //+------------------------------------------------------------------+
Code 06
Ich weiß, dass der Code 06 etwas Subjektives ist – etwas, das für viele unwahrscheinlich ist, dass es geschaffen wird. Auch wenn es sich nur um ein hypothetisches Beispiel handelt, so enthält es doch viele Elemente und Probleme, die auch im realen Code vorkommen. Bei der Ausführung ergibt sich folgendes Bild:

Abbildung 03
Wir haben hier mehrere Dinge zu tun. Und obwohl der Kodex manchen kompliziert und schwer verständlich erscheinen mag, ist er für diejenigen, die das in den Artikeln Gezeigte studiert und geübt haben, eigentlich ganz einfach, klar und geradlinig. Da es jedoch einige Elemente gibt, die seltsam erscheinen mögen, wie z. B. die Geburtsdaten, werde ich kurz erklären, was hier geschieht.
In Zeile vier deklarieren wir unsere Datenstruktur. Beachten Sie, dass es im globalen Bereich deklariert ist. Aus diesem Grund kann sie außerhalb des Hauptcodeblocks – in diesem Fall der OnStart-Prozedur – verwendet werden. Innerhalb des Hauptblocks befindet sich die Erklärung in Zeile 17, die darauf abzielt, eine Liste von Informationen zu erstellen. Mit anderen Worten: Unsere Struktur wird tatsächlich als eine besondere Art von Variable behandelt. Danach, zwischen den Zeilen 21 und 32, fügen wir mehrere Datensätze zu unserer Liste hinzu. Denken Sie daran, dass jeder Wert innerhalb des Arrays wie ein völlig eigenständiges Element ist. Da wir jedoch alles innerhalb eines einzigen Blocks strukturieren (die in Zeile 04 deklarierte Struktur), hat man das Gefühl, dass jedes Element physisch mit den anderen verbunden ist und eine Art Protokollbogen bildet.
Bei jedem Aufruf zwischen den Zeilen 21 und 32 versuchen wir also zunächst, mit der Funktion in Zeile 38 einen neuen Datensatz zu unserem Array hinzuzufügen. Beachten Sie, dass wir der Funktion in Zeile 38 die erforderlichen Mindestberechtigungen zugestehen. Wir stellen nur einige Informationen zur Verfügung, während andere Werte direkt von der Routine selbst zugewiesen werden können, wie z. B. in Zeile 44, wo wir das aktuelle Datum nehmen und es verwenden, um das Einstellungsdatum festzulegen. In der Realität wird dies häufig so gehandhabt, da der Zeitpunkt der Erstellung eines Mitarbeiterdatensatzes in der Tat der Zeitpunkt der Einstellung eines Mitarbeiters ist. Aus diesem Grund erlauben wir der Funktion in Zeile 38, der zurückgegebenen Struktur einen Wert zuzuweisen – einen Wert, der nicht als Argument übergeben wird.
In echtem Code würde es natürlich viele Prüfungen geben, um Fehler während der Registrierungsphase zu vermeiden. Da dieser Code jedoch rein didaktisch ist, sind solche Überprüfungen unnötig.
Da jeder Datensatz andere Daten benötigt, die nicht direkt als Argumente an die Funktion NewRecord in Zeile 38 übergeben werden, verwenden wir Zeile 49, um den bereits erstellten Datensatz zu aktualisieren oder ihm einen bestimmten Wert zuzuweisen. In echtem Code gäbe es auch hier viele Prüfungen.
Aber der Zweck der Funktion in Zeile 49 ist es zu zeigen, wie wir einen Wert zurückgeben können, in diesem Fall genau die Struktur, die in der Funktion erstellt wurde. Auf diese Weise können wir besser kontrollieren, wann und wo wir Werte in einer Struktur ändern.
Beachten Sie, dass im Gegensatz zu Zeile 38, wo die Eingabestruktur permanent geändert wird, in Zeile 49 die Struktur geändert und an den Aufrufer zurückgegeben wird. Es ist Sache des Aufrufers, zu bestimmen, wo und wie er den zurückgegebenen Wert verwenden will. Und ja, liebe Leserin, lieber Leser, Sie können diesen Rückgabewert auf vielfältige Weise verwenden, wie wir später zeigen werden.
Schließlich haben wir eine kleine Prozedur, um einen Datensatz anzuzeigen. Dies führt zu dem in Abbildung 03 dargestellten Ergebnis.
Könnten wir denselben Code 06 erstellen, aber auf eine Art und Weise, bei der die Arbeit anders ausgeführt wird? Ja. Und genau das werden wir im folgenden Code sehen:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. struct st_Register 005. { 006. uchar ID; 007. string Name, 008. Surname, 009. Address, 010. Position; 011. double Remuneration; 012. datetime Birth, 013. Hiring; 014. }gl_Record[10]; 015. //+------------------------------------------------------------------+ 016. void OnStart(void) 017. { 018. uchar Index; 019. 020. ZeroMemory(gl_Record); 021. 022. NewCollaborator("Daniel", "Jose", "Brasil", "1971/03/30", "Chief Programmer"); 023. NewCollaborator("Edimarcos", "Alcantra", "Brasil", "1974/12/07", "Programmer"); 024. NewCollaborator("Carlos", "Almeida", "Brasil", "1985/11/15", "Junior Programmer"); 025. 026. Index = GetNumberRecord(); 027. 028. if (NewRecord(gl_Record[Index], "Yara", "Alves", "Brasil", "1978/07/25")) 029. { 030. gl_Record[Index].ID = Index + 1; 031. gl_Record[Index] = UpdatePosition(gl_Record[Index], "Accounting"); 032. } 033. 034. Print("Number of records: ", GetNumberRecord()); 035. Print("--------------------"); 036. 037. for(uchar c = 1; ViewRecord(c); c++) 038. Print("********************"); 039. } 040. //+------------------------------------------------------------------+ 041. bool NewCollaborator(const string Name, const string Surname, const string Address, const string Birth, const string Position) 042. { 043. st_Register info; 044. uchar Index = 0; 045. 046. if (!NewRecord(info, Name, Surname, Address, Birth)) 047. return false; 048. 049. info = UpdatePosition(info, Position); 050. 051. while (gl_Record[Index].ID) 052. { 053. if (Index >= gl_Record.Size()) return false; 054. Index++; 055. } 056. 057. info.ID = Index + 1; 058. gl_Record[Index] = info; 059. 060. return true; 061. } 062. //+------------------------------------------------------------------+ 063. bool NewRecord(st_Register &arg, const string Name, const string Surname, const string Address, const string Birth) 064. { 065. arg.Name = Name; 066. arg.Surname = Surname; 067. arg.Address = Address; 068. arg.Birth = StringToTime(Birth); 069. arg.Hiring = TimeLocal(); 070. 071. return true; 072. } 073. //+------------------------------------------------------------------+ 074. st_Register UpdatePosition(const st_Register &arg, const string NewPosition) 075. { 076. st_Register info = arg; 077. 078. info.Position = NewPosition; 079. 080. return info; 081. } 082. //+------------------------------------------------------------------+ 083. uchar GetNumberRecord(void) 084. { 085. uchar counter = 0; 086. 087. for (uchar c = 0; c < gl_Record.Size(); counter += (gl_Record[c].ID ? 1 : 0), c++); 088. 089. return counter; 090. } 091. //+------------------------------------------------------------------+ 092. bool ViewRecord(const uchar ID) 093. { 094. st_Register info; 095. 096. ZeroMemory(info); 097. 098. for (uchar c = 0; (c < gl_Record.Size()) && (!info.ID); c++) 099. info = (gl_Record[c].ID == ID ? gl_Record[c] : info); 100. 101. if (info.ID) 102. PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s", 103. info.Surname, 104. info.Name, 105. info.Position, 106. TimeToString(info.Birth, TIME_DATE)); 107. else 108. Print("Record ID [", ID ,"] not found."); 109. 110. return (bool) info.ID; 111. } 112. //+------------------------------------------------------------------+
Code 07
Okay, jetzt haben wir wirklich etwas ziemlich Komplexes in der Hand. Das liegt daran, dass Code 07 definitiv komplizierter ist, und ich verstehe nur sehr wenig von dem, was hier gemacht wird (LOL). Nun, im Grunde ist der Code 07 sehr einfach. Um ehrlich zu sein, ist der Code 07 meiner Meinung nach einfacher als der Code 06, auch wenn sie im Wesentlichen das Gleiche tun. Ich verstehe jedoch, dass einige Leser den Code 07 als komplizierter empfinden könnten.
Erstens haben wir jetzt eine globale Variablenerklärung in Zeile 14. Das lässt alles komplexer erscheinen, als es von Natur aus erforderlich wäre. Doch bevor wir im Detail erörtern, was der Code 07 tut, sollten wir uns das Ergebnis der Ausführung ansehen. Dies ist auf dem nachstehenden Bild zu sehen:

Abbildung 04
Beachten Sie, dass alle Datensätze, die wir im Hauptblock erstellen, in diesem Bild erscheinen. Das ist perfekt, denn es zeigt, dass der Code 07 seine Aufgabe erfüllt. Aber wie wurde dies erreicht, insbesondere angesichts der neuen Elemente, die hier auftauchen? Nun, liebe Leserin, lieber Leser, wie im vorigen Artikel erwähnt, stellen Strukturen einen Schritt zwischen konventioneller Programmierung und objektorientierter Programmierung dar. Wir stehen jedoch noch ganz am Anfang, wenn es darum geht, zu erforschen, was mit Strukturen wirklich gemacht werden kann.
Beachten Sie nun, dass die Zeilen 22 bis 32 dazu dienen, neue Datensätze in das System einzufügen. Und das ist vielleicht der entscheidende Punkt: Die Art und Weise, wie Datensätze zwischen den Zeilen 22 und 24 erstellt werden, ist auf den ersten Blick relativ einfach zu verstehen, im Gegensatz zu dem, was zwischen den Zeilen 26 und 32 geschieht. Hier führen wir die gleiche Arbeit wie in Zeile 41 aus, allerdings vollständig manuell. Allein die Tatsache, dass man es auf diese Weise macht, macht den Code etwas risikoreicher. Selbst ein kleiner Fehler kann Stunden der Fehlersuche kosten.
Ich möchte Sie ermutigen, den Code 07 sorgfältig zu studieren, um dies praktisch zu verstehen. Vergleichen Sie die Schwierigkeit, ein Ergebnis mit Code 06 zu erzielen, mit der Schwierigkeit, das gleiche Ergebnis mit Code 07 zu erreichen. Wenn Sie dies verstehen, wird es Ihnen leichter fallen, den Ausführungen im nächsten Artikel zu folgen.
Der vielleicht komplexeste Teil von Code 07 ist jedoch die Funktion in Zeile 92. Analysieren wir, wie das funktioniert. Zunächst deklarieren wir in Zeile 94 eine temporäre Struktur. In Zeile 96 wird dann der Speicherbereich, in dem die Struktur abgelegt wird, gelöscht. Dadurch können wir die Tests in der Schleife in Zeile 98 verwenden, um festzustellen, wann die gewünschte ID gefunden wurde.
Die Schleife wird dann beendet, sodass in Zeile 101 geprüft werden kann, ob im Terminal des MetaTrader 5 etwas zu drucken ist. Wenn die ID gefunden wurde, drucken wir den Inhalt der Datensatzstruktur aus. Andernfalls wird die in Zeile 108 angezeigte Meldung ausgegeben.
Da der Rückgabetyp dieser Funktion boolesch sein muss und die ID ein numerischer Wert ist, teilen wir dem Compiler mit, dass wir uns dieser Tatsache bewusst sind. Daher führen wir in Zeile 110 eine explizite Typumwandlung durch, um dem Aufrufer mitzuteilen, ob die Suche nach der angeforderten ID erfolgreich war. Diese Rückgabe ist wichtig, damit die Schleife in Zeile 37 weiß, wann sie aufhören muss.
Abschließende Überlegungen
In diesem Artikel haben wir gesehen, wie man Strukturen in relativ einfachem Code verwenden kann. Obwohl alles hier in erster Linie didaktisch ist, konnten wir etwas aufbauen, das einer Datenbank sehr ähnlich ist. Es ist richtig, dass eine echte Datenbank zahlreiche Tests erfordert, um Fehler zu vermeiden. Ich glaube jedoch, dass das Hauptziel erreicht worden ist: das Konzept der Strukturen zu demonstrieren.
Mit anderen Worten, eine Struktur ist in der Tat eine besondere Art von Variable. Nehmen Sie sich also die Zeit, den in den Anhängen enthaltenen Code zu studieren und zu üben. Eine zeilenweise Erklärung jedes einzelnen Codes wurde zwar nicht gegeben, ist aber meiner Meinung nach auch nicht unbedingt notwendig, da der Inhalt für jeden verständlich ist, der das in den Artikeln vorgestellte Material studiert und geübt hat.
Im nächsten Artikel werden wir untersuchen, wie Strukturen auf eine noch praktischere und interessantere Weise verwendet werden können, wodurch der Kodierungsprozess erheblich vereinfacht wird.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/15731
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Marktsimulation (Teil 08): Sockets (II)
Chaos Game Optimization (CGO)
Entwicklung eines Expert Advisors für mehrere Währungen (Teil 22): Beginn des Übergangs zum Hot-Swapping von Einstellungen
Marktsimulation (Teil 07): Sockets (I)
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.