Von der Grundstufe bis zur Mittelstufe: Funktionszeiger
Einführung
Im vorherigen Artikel „Von der Grundstufe bis zur Mittelstufe: Objekte (II)“ haben wir uns mit der ersten Art von Ereignis befasst, mit der sich ein im Chart angezeigtes Objekt manipulieren bzw. steuern lässt.
In diesem Artikel wurde jedoch ein Ereignistyp verwendet, den MetaTrader 5 standardmäßig immer auslöst, wenn der Benutzer mit dem Chart interagiert. In diesem Fall geht es um einen Tastendruck. Da sich dieses Ereignis mit dem OnChartEvent-Handler leicht abfangen lässt, denken Sie vielleicht, dass es keine andere Möglichkeit gibt, die Tastatur zu nutzen, als dieses Ereignis abzufangen. Nun, lieber Leser, ganz so ist es nicht. Obwohl Skripte den OnChartEvent-Handler weder direkt verwenden noch unterstützen, können wir Mechanismen zur Steuerung bestimmter Objekteigenschaften über die Tastatur und ein Skript aktivieren, auch wenn dies ein etwas ungewöhnlicher Ansatz ist.
Bevor wir uns also mit der Verarbeitung von Mausereignissen befassen, wollen wir uns zunächst ansehen, wie man Tastaturereignisse bei der Verwendung von Skripten verarbeitet. Obwohl MetaTrader 5 und damit auch MQL5 nicht für diese Art von Anwendungen konzipiert sind, da sie ursprünglich für die Arbeit mit Kurscharts entwickelt wurden, ist es wichtig, dass Sie wissen, was möglich ist und was nicht. Es gibt Einschränkungen, die man beachten muss.
Ein Skript mit Ereignissen?
Zunächst müssen Sie Folgendes verstehen: EIN SKRIPT KANN NICHT EREIGNISGESTEUERT ARBEITEN. Das hindert uns jedoch nicht daran, ein Skript zu erstellen, das Ereignisse von der Tastatur verarbeiten kann. Allerdings – und hier wird es wirklich kompliziert – muss man sich darüber im Klaren sein, dass MQL5 für bestimmte Arten von Implementierungen NICHT VORGESEHEN IST. Wenn man also etwas ausschließlich mit MQL5 programmiert, ergeben sich verschiedene Einschränkungen und Schwierigkeiten.
Aber wie lassen sich Tastaturereignisse innerhalb eines Skripts abfangen und verarbeiten? Die Wahrheit ist: WIR KÖNNEN ES NICHT. Was wir tatsächlich machen können, ist, bestimmte Tasten abzufangen und mithilfe einer Art Filterung so etwas wie einen Tastatur-Ereignis-Handler zu erstellen. In der Praxis werden wir jedoch den OnChartEvent-Handler verwenden.
Um dies zu verdeutlichen, werden wir den Code verwenden, den wir im vorherigen Artikel betrachtet haben. Dadurch wird es viel einfacher, genau zu verstehen, auf welches Problem wir stoßen könnten. Der betreffende Code ist unten aufgeführt.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. int OnInit() 12. { 13. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 14. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 15. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 16. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 17. 18. return INIT_SUCCEEDED; 19. }; 20. //+------------------------------------------------------------------+ 21. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 22. { 23. return rates_total; 24. }; 25. //+------------------------------------------------------------------+ 26. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch(id) 32. { 33. case CHARTEVENT_KEYDOWN: 34. switch ((int)lparam) 35. { 36. case def_KEY_DOWN: 37. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 38. break; 39. case def_KEY_UP: 40. p = (p > 0 ? p - 1 : p); 41. break; 42. default: 43. return; 44. } 45. Comment(StringFormat("Current bar analyzed: %d", p)); 46. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 47. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 48. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 49. break; 50. } 51. ChartRedraw(); 52. }; 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. Comment(""); 57. for (uint c = 0; c < gl_Objs.Size(); c++) 58. ObjectDelete(0, gl_Objs[c]); 59. ChartRedraw(); 60. }; 61. //+------------------------------------------------------------------+
Code 01
Wenn Code 01 ausgeführt wird, ergibt sich folgendes Ergebnis:

Animation 01
Nun stellt sich die Frage: Wie können wir etwas Ähnliches wie Code 01 erstellen, mit dem gleichen Ergebnis wie in Animation 01, aber mithilfe eines Skripts? Gut, genau diese Frage werden wir nun beantworten.
Um dieses Ziel zu erreichen, müssen wir verschiedene Funktionen aus der MQL5-Bibliothek nutzen. Da wir uns jedoch ausschließlich auf MQL5 konzentrieren wollen, werden wir hier keine andere Methodik verwenden. Auch wenn der Einsatz einer auf C- oder C++-Code basierenden Methodik die Aufgabe erheblich vereinfachen könnte, werden wir dies hier in Artikeln, die sich an Anfänger bis Fortgeschrittene richten, nicht tun.
Vielleicht werden wir uns dieses Problem in Zukunft auf einfachere Weise ansehen, falls ich beschließe, zu zeigen, wie man komplexeren Code entwickelt, bei dem Programme zum Einsatz kommen, die in anderen Sprachen geschrieben und kompiliert wurden. Das ist jedoch auch wesentlich komplizierter, denn um die Interaktion zwischen den Sprachen zu gewährleisten und damit die Aufgabe zu lösen, muss man sowohl MQL5 als auch eine andere Programmiersprache gut beherrschen.
Kommen wir also auf unser Hauptproblem zurück. Zunächst einmal: Wir werden hier keinen stark strukturierten Programmaufbau verwenden. Das liegt daran, dass unser Ziel nicht darin besteht, eine Anwendung zu erstellen, sondern zu zeigen, wie sich das Problem lösen lässt. Deshalb müssen wir einen Aktionsplan ausarbeiten. Und dafür besteht der erste Schritt darin, festzulegen, wie das Ausgangsskript aussehen soll.
Wie Sie sehen können, haben wir sowohl im Code als auch in der Animation zwei Objekte: eines ist eine horizontale Linie, das andere eine vertikale. Beide werden mit dem Zeitpunkt der Balkenerstellung und dem Schlusskurs verknüpft. Und wenn das Skript seine Arbeit beendet hat – und dies korrekt getan hat –, müssen wir beide Objekte löschen, die von diesem Skript erstellt wurden.
Ausgezeichnet, jetzt haben wir einen ersten Aktionsplan. Danach können wir mit der Umsetzung des Quellcodes beginnen. Dies ist im Folgenden dargestellt:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. int p = 0; 14. MqlRates rate[1]; 15. 16. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 17. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 18. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 19. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 20. 21. Comment(StringFormat("Current bar analyzed: %d", p)); 22. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 23. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 24. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 25. 26. Comment(""); 27. for (uint c = 0; c < gl_Objs.Size(); c++) 28. ObjectDelete(0, gl_Objs[c]); 29. ChartRedraw(); 30. } 31. //+------------------------------------------------------------------+
Code 02
Code 02 bildet die Grundlage für unseren Ansatz. Wenn wir Code 02 jedoch ausführen, werden wir keine Ergebnisse sehen. Der Grund dafür ist, dass die Objekte erstellt, positioniert und gelöscht werden, bevor wir ihr tatsächliches Erscheinen überhaupt bemerken können.
Liebe Leserinnen und Leser, hier in Code 02 möchte ich Sie auf einen Punkt aufmerksam machen. Wenn Sie sich jede Zeile des Codes 02 genau ansehen, werden Sie feststellen, dass genau dieselben Zeilen auch im Code 01 vorkommen. Aber hier sehen wir nichts. Der Grund dafür ist, dass der Codeabschnitt zwischen den Zeilen 21 und 24, der Teil des in Code 01 gezeigten OnChartEvent-Ereignisbehandlers ist, hier zu schnell ausgeführt wird, sodass wir keine Gelegenheit zur Interaktion haben. Genau an dieser Stelle müssen wir eingreifen, um die Verarbeitung von Tastaturereignissen zu implementieren.
Perfekt. Wir wissen bereits, wie der Quellcode aussehen wird, daher ist es an der Zeit, die Aufgabe in kleinere Teile zu unterteilen. Das Erstellen von monolithischem Code ist eine sehr mühsame Angelegenheit. Somit ändert sich der Code 02 und wird zu Code 03.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. Init(); 14. KeyEvent(def_KEY_DOWN); 15. Deinit(); 16. } 17. //+------------------------------------------------------------------+ 18. void Init(void) 19. { 20. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 21. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 22. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 23. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 24. } 25. //+------------------------------------------------------------------+ 26. void KeyEvent(int lparam) 27. { 28. static int p = 0; 29. MqlRates rate[1]; 30. 31. switch (lparam) 32. { 33. case def_KEY_DOWN: 34. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 35. break; 36. case def_KEY_UP: 37. p = (p > 0 ? p - 1 : p); 38. break; 39. default: 40. return; 41. } 42. Comment(StringFormat("Current bar analyzed: %d", p)); 43. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 44. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 45. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 46. } 47. //+------------------------------------------------------------------+ 48. void Deinit(void) 49. { 50. Comment(""); 51. for (uint c = 0; c < gl_Objs.Size(); c++) 52. ObjectDelete(0, gl_Objs[c]); 53. ChartRedraw(); 54. } 55. //+------------------------------------------------------------------+
Code 03
Jetzt haben wir viel schöneren Code, eine wahre Freude. Das liegt daran, dass wir beim Betrachten von Code 03 erkennen können, was wir tatsächlich tun müssen. Wir benötigen in Zeile 14 eine Schleife, damit wir mit den Objekten interagieren und sie steuern können, wie in Animation 01 gezeigt. Die Frage ist: Wie können wir das bewerkstelligen? Und jetzt kommt der interessanteste Teil. Bevor wir jedoch dazu kommen, möchte ich euch daran erinnern, dass Schleifen gefährlich sind. Dies wurde bereits erläutert, ebenso wie die sichere Beendigung von Schleifen.
Und da jedes Skript dauerhaft aus dem Chart entfernt wird, sobald MetaTrader 5 den Chart neu erstellen muss – beispielsweise, wenn der Benutzer MetaTrader 5 anweist, den Zeitrahmen zu ändern –, wird das Skript in diesem Moment entfernt. Es ist jedoch wichtig zu verstehen, dass es nicht ausreicht, das Skript einfach nur zu entfernen. Die dabei erstellten Objekte müssen ebenfalls entfernt werden. Andernfalls werden diese Objekte wieder im Chart angezeigt, sobald MetaTrader 5 mit der Neuzeichnung beginnt.
Ich weiß, das klingt vielleicht etwas schockierend, aber ich sage Ihnen das, damit Sie bei der Verwendung von Objekten in einem Skript vorsichtig sind. Wenn Sie nicht möchten, dass die vom Skript erstellten Objekte nach Abschluss des Skripts im Chart verbleiben, sollten Sie sie löschen. Andernfalls ignorieren Sie diesen Löschschritt einfach, und das Problem ist damit behoben.
Kommen wir nun also zu den Schleifen. Das Problem besteht hier nicht darin, die Schleife selbst zu erstellen, sondern sicherzustellen, dass sie nicht zu aggressiv ist und nicht zu viele CPU-Ressourcen beansprucht. Da Tastaturereignisse – egal, wie wild der Benutzer auch sein mag – nicht in jedem einzelnen Moment auftreten, können wir den Schleifenrumpf um einige Elemente ergänzen. Das Ergebnis ist ein Skript, das die CPU-Ressourcen deutlich weniger beansprucht. Nach der Analyse dieser Möglichkeiten erhalten wir folgendes Ergebnis:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. Init(); 14. while (!IsStopped()) 15. { 16. if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP)) 17. KeyEvent(def_KEY_UP); 18. if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN)) 19. KeyEvent(def_KEY_DOWN); 20. Sleep(100); 21. } 22. Deinit(); 23. } 24. //+------------------------------------------------------------------+ 25. void Init(void) 26. { 27. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 28. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 29. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 30. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 31. } 32. //+------------------------------------------------------------------+ 33. void KeyEvent(int lparam) 34. { 35. static int p = 0; 36. MqlRates rate[1]; 37. 38. switch (lparam) 39. { 40. case def_KEY_DOWN: 41. p = (p < Bars(_Symbol, _Period) ? p + 1 : p); 42. break; 43. case def_KEY_UP: 44. p = (p > 0 ? p - 1 : p); 45. break; 46. default: 47. return; 48. } 49. Comment(StringFormat("Current bar analyzed: %d", p)); 50. CopyRates(_Symbol, _Period, p, rate.Size(), rate); 51. ObjectMove(0, gl_Objs[0], 0, rate[0].time, rate[0].close); 52. ObjectMove(0, gl_Objs[1], 0, rate[0].time, rate[0].close); 53. } 54. //+------------------------------------------------------------------+ 55. void Deinit(void) 56. { 57. Comment(""); 58. for (uint c = 0; c < gl_Objs.Size(); c++) 59. ObjectDelete(0, gl_Objs[c]); 60. ChartRedraw(); 61. } 62. //+------------------------------------------------------------------+
Code 04
Sehen Sie sich nun die Schleife in Zeile 14 an. Auf diese Weise erhalten wir das Verhalten, das in der folgenden Animation zu sehen ist.

Animation 02
Interessant, nicht wahr? Aber das ist nicht alles. Es gibt noch mehr Material zu zeigen. Das ist in der folgenden Animation zu sehen.

Animation 03
Bitte beachten Sie, liebe Leser: Wenn wir den Zeitrahmen ändern, löscht MetaTrader 5 das Chart und erstellt es sofort neu. Da Skripte in MetaTrader 5 standardmäßig nicht neu geladen werden, endet das Skript und die von ihm erstellten Objekte werden aus dem Chart entfernt – genau das wollten wir mit dem oben gezeigten Code 04 erreichen.
Aber vielleicht fragen Sie sich: Warum werden die Funktionen in den Zeilen 16 und 18 verwendet? Der Grund dafür ist, dass absolut nichts verwendet werden soll, was nicht Teil der Standard-MQL5-Bibliothek ist. Da MQL5 keine Funktionen oder Prozeduren zum direkten Auslesen gedrückter Tasten bereitstellt – weil dafür kein Bedarf besteht –, können wir diese nicht auf eine allgemeinere Weise auslesen. So können wir einen Mechanismus aufbauen, der dem Abfangen von CHARTEVENT_KEYDOWN über OnChartEvent gleichkommt, wie in Code 01 gezeigt.
Das bedeutet jedoch nicht, dass wir diese Art der Tastenklicks nicht nutzen können. Dazu müssten wir Mechanismen nutzen, die nicht Teil von MQL5 sind, weshalb wir sie in diesen Artikeln für Anfänger bis Fortgeschrittene nicht behandeln werden.
Gut, aber vielleicht fällt Ihnen auf, dass wir in Code 04 Konstanten oder eine Enumeration verwenden, wodurch es viel leichter zu erkennen ist, welche Taste erwartet wird. Gleichzeitig sehen wir, dass wir nicht unbedingt alles genauso einrichten müssen, wie es in Code 04 gezeigt wird. Wir können die Situation ein wenig verbessern. Und da wir in diesem Fall keine allzu einfache und uninteressante Änderung zeigen möchten, nutzen wir die Gelegenheit, um eine weitere Funktion von MQL5 zu erläutern. Dies ist eine sehr spezifische Funktion, und wir haben nicht oft Gelegenheit, zu zeigen, wie sie funktioniert; dieser Fall ist einer davon. Um die Themen klar voneinander zu trennen, wenden wir uns nun einem neuen Thema zu.
Funktionszeiger
Dies ist eine der seltenen Gelegenheiten, bei denen wir einen Mechanismus mit einem ganz bestimmten Zweck in MQL5 erläutern können. Da wir im vorigen Abschnitt sozusagen eine alternative Version dessen implementiert haben, was zuvor als Indikator vorgestellt wurde, bietet sich uns nun die Gelegenheit, Funktionszeiger zu erläutern. Das ist ein sehr interessantes Sprachmerkmal, aber es sorgt bei jedem Programmieranfänger für enorme Verwirrung, vor allem, wenn er bestimmte Konzepte nicht genau versteht.
Zunächst einmal: Seit geraumer Zeit haben wir nicht mehr darauf hingewiesen, dass zum Verständnis eines Themas bestimmte Vorkenntnisse erforderlich sind. Das liegt daran, dass die Themen stets mit einem der jüngsten Artikel in Verbindung standen. Dies ist jedoch einer jener Fälle, in denen die von uns verwendeten Begriffe in relativ alten Artikeln vorkommen. In diesem Fall beziehe ich mich auf den Artikel „ Von der Grundstufe bis zur Mittelstufe: Variablen (III)“. Dieser Artikel schließt die Erörterung des Themas im Zusammenhang mit Variablen und Konstanten ab. Ein klares Verständnis des dort vorgestellten Konzepts ist von entscheidender Bedeutung, um zu verstehen, wie wir hier nun weiter vorgehen werden.
Betrachten wir zunächst einmal ein ganz einfaches und auf den ersten Blick lächerliches Beispiel. Es wird uns jedoch dabei helfen zu verstehen, wie wir mit dem Konstrukt umgehen, das wir gleich untersuchen werden. Der Quellcode ist unten zu sehen.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. string Msg_01(const int value) 05. { 06. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 07. } 08. //+------------------------------------------------------------------+ 09. string Msg_02(const int value) 10. { 11. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 12. } 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. Print(Msg_01(171)); 17. Print(Msg_02(-375)); 18. } 19. //+------------------------------------------------------------------+
Code 05
Na gut, Code 05 ist sehr einfach, daher werden wir ihn nicht näher erläutern. Das Ergebnis der Ausführung ist jedoch unten zu sehen.

Bild 01
Obwohl Code 05 sehr einfach ist, enthält er Elemente, die wir verstehen sollten, bevor wir uns komplexeren Themen zuwenden. Die zentrale Frage lautet hier: Welchen Mechanismus könnten wir entwickeln, um die in Zeile 04 definierte Funktion mit der in Zeile 09 definierten Funktion zu verknüpfen? So können wir auswählen, welche Funktion wir verwenden möchten, ohne deren Namen direkt anzugeben.
Na gut, vielleicht denken Sie gerade an Überladung, ein Thema, das in dem Artikel Von der Grundstufe zur Mittelstufe: Überladen behandelt wurde. Dennoch trifft die Überladung hier nicht zu, zumindest nicht in dem Sinne, wie wir sie nutzen wollen. In Wirklichkeit wären Arrays der Mechanismus, der dem Ideal am nächsten kommt. In den Artikeln Von der Grundstufe zur Mittelstufe: Array (IV) haben wir in einfachen Worten erklärt, wie man mit Arrays arbeitet. Dort lag der Schwerpunkt auf der Speicherung diskreter Werte wie Ganzzahlen und Gleitkommazahlen. Arrays können jedoch auch andere Arten von Informationen speichern. In diesem Fall Funktionen oder Prozeduren. „Moment mal, aber wie ist das möglich? Sind Sie verrückt? Wie können Funktionen und Prozeduren in einem Array gespeichert werden? Das ergibt doch überhaupt keinen Sinn.“
Genau deshalb haben wir darauf hingewiesen, dass Sie das Konzept einer Variablen sehr gut verstehen müssen. Es gibt einen speziellen Variablentyp, der als Zeiger bezeichnet wird. Da MQL5 aus Sicherheits- und Einfachheitsgründen keinen Code mit Zeigern implementiert bzw. dem Programmierer nicht erlaubt, solchen Code zu implementieren, werden Sie kaum jemals darauf stoßen. Es gibt jedoch Situationen, in denen Zeiger auch in MQL5 erstellt und verwendet werden. Und da sich Zeiger in MQL5 in der Praxis von Zeigern in C und C++ unterscheiden, hört man hier so gut wie nie etwas von ihnen.
Bevor Sie also verstehen, was wir machen werden, müssen Sie Folgendes begreifen: Jede Funktion oder Prozedur befindet sich im Speicher an einer bestimmten Adresse. Diese Adresse wird in einer speziellen Art von Variablen gespeichert, die als Zeiger bezeichnet wird. Zeiger sind zweifellos das leistungsstärkste Werkzeug in der Programmierung. Gleichzeitig sind sie jedoch auch am verwirrendsten und am schwierigsten zu beherrschen, da wir durch die Manipulation von Zeigern mit dem Inhalt des Computerspeichers arbeiten, unabhängig davon, auf welche Art von Daten wir zugreifen.
Nun, wir werden nicht allzu sehr ins Detail gehen, gerade weil es nicht nötig ist. Sie müssen Folgendes verstehen:
Ein Zeiger ist eine Variable. Diese Variable verweist auf einen Speicherbereich. Wenn dieser Bereich ausführbaren Code enthält, können wir die Programmausführung dorthin verlagern.
Das zu verstehen, reicht für unsere Zwecke und für das, was wir vorhaben, bereits völlig aus. Großartig, jetzt wissen wir, was ein Zeiger ist, aber wie erstellt oder besser gesagt definiert man einen Zeiger? So wird ein solcher Zeiger in MQL5 definiert. Das ist also genau der Moment, in dem es wichtig wird, zu verstehen, wie Vorlagen funktionieren. In den Artikeln Von der Grundstufe zur Mittelstufe: Templates und Typnamen (V) wurde erläutert, wie Templates (Vorlagen) funktionieren und wie man sie zur Erstellung überladener Funktionen und Prozeduren nutzt. Doch genau hier wird es für Anfänger verwirrend: Wir können auch Datentypvorlagen definieren. Ein Zeiger ist genau das: eine Datentypvorlage. In diesem Fall handelt es sich jedoch um einen Datentyp, der eine Funktions- oder Prozedurvorlage darstellt. „Wow, jetzt ist alles ganz schön kompliziert geworden, denn meiner Meinung nach ergibt das überhaupt keinen Sinn.“
Keine Sorge, wir müssen uns nicht beeilen. Mir ist klar, dass das anfangs tatsächlich schwierig ist. Genau aus diesem Grund habe ich dieses Sprachmerkmal erst jetzt angesprochen und werde es nun erläutern. Sie müssen verschiedene Konzepte beherrschen, um wirklich zu verstehen, wie man mit dieser als Zeiger bezeichneten Sprachfunktion umgeht.
Im Code 05 wurden die Funktionen in den Zeilen 04 und 09 bewusst auf diese Weise deklariert. Dies geschieht, um das Konzept der Vorlage, die bei der Definition des Zeigers verwendet wird, besser verständlich zu machen. Wenn man genau hinschaut, wird man feststellen, dass sich im Grunde nur der Funktionsname ändert. Der Inhalt oder die Logik, die die Funktion umsetzt, spielen keine Rolle; entscheidend ist die Deklaration. Da sich die Deklarationen sowohl hinsichtlich des Rückgabetyps als auch hinsichtlich der Anzahl und des Typs der an die Funktionen übergebenen Argumente sehr ähnlich sind, sind die Voraussetzungen für die Definition eines Zeigers gegeben. Dazu fügen wir die folgende Zeile aus dem unten stehenden Code ein. Wir werden Schritt für Schritt zeigen, wie sich Code 05 verändert, damit Sie genau nachvollziehen können, was dabei geschieht.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. Print(Msg_01(171)); 19. Print(Msg_02(-375)); 20. } 21. //+------------------------------------------------------------------+
Code 06
Legen Sie jetzt alles beiseite, womit Sie gerade beschäftigt sind, und alles, was Sie ablenken könnte, und achten Sie jetzt bitte genau auf die folgende Erklärung. Wenn Sie diesen Anfang nicht verstehen, werden Sie völlig den Faden verlieren. Bitte beachten Sie, dass wir in Code 06 Zeile 04 hinzugefügt haben, die in Code 05 nicht enthalten war. In Zeile 04 wird der Zeiger definiert, den wir verwenden werden.
Beachten Sie, dass diese Anweisung sehr ähnlich ist wie die in den Zeilen 06 und 11. Abgesehen davon, dass der Variablenname weggelassen und der Funktionsname durch (FnPtr) ersetzt wurde. Dieser Teil, dieses Element (FnPtr), kann alles sein, was Sie verwenden möchten. Sei jedoch vorsichtig damit, denn wir werden genau dieses Teil in Kürze benötigen.
Gut, der erste Teil ist nun erklärt. Nun werden wir ein Array mit dem in Zeile 04 von Code 06 definierten Typ deklarieren. Denken Sie daran, dass dort ein Zeiger definiert wurde. Der zweite Teil ist im folgenden Code zu sehen.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. Print(Msg_01(171)); 21. Print(Msg_02(-375)); 22. } 23. //+------------------------------------------------------------------+
Code 07
Nun, in Zeile 18, die in diesem Code 07 zu sehen ist, haben wir die Definition eines Arrays für die Verwendung von Zeigern des Typs, der in Zeile 04 definiert wurde. Um die Aufgabe zu vereinfachen, definieren wir ein statisches Array mit zwei Elementen. Falls Sie sich nicht ganz sicher sind, was hier vor sich geht, lesen Sie bitte die vorherigen Artikel noch einmal durch; ich werde nicht näher auf Dinge eingehen, die bereits erläutert wurden. Ausgezeichnet, ich glaube, Sie sind immer noch richtig dabei. Der nächste Schritt ist im folgenden Code zu sehen.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. FnMsg[0] = Msg_01; 21. FnMsg[1] = Msg_02; 22. 23. Print(Msg_01(171)); 24. Print(Msg_02(-375)); 25. } 26. //+------------------------------------------------------------------+
Code 08
„Wow, was für ein Wahnsinn ist das denn? Haben Sie den Verstand verloren? Code 08 lässt sich nicht kompilieren.“
Aber natürlich lässt sich Code 08 kompilieren, lieber Leser. Wie ist das möglich? Haben Sie nicht verstanden, was hier gemacht wurde? Hier definieren wir die Elemente des Arrays. Wenn Ihnen das völlig verrückt vorkam, gehen Sie zum Anfang dieses Themas zurück und versuchen Sie, zu verstehen, was gesagt wurde. In Zeile 04 definieren wir einen Datentyp. In Zeile 18 definieren wir eine Variable, die denselben Datentyp wie in Zeile 04 verwendet. Und in den Zeilen 20 und 21 weisen wir den Elementen der in Zeile 18 deklarierten Variablen Werte zu. Mehr gibt es dazu nicht zu sagen, alles ist ganz einfach und klar. Der schwierigste Teil wird im nächsten Schritt gezeigt. Sehen Sie sich das im unten stehenden Code an.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. typedef string (*FnPtr)(const int); 05. //+------------------------------------------------------------------+ 06. string Msg_01(const int value) 07. { 08. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 09. } 10. //+------------------------------------------------------------------+ 11. string Msg_02(const int value) 12. { 13. return "Example message made in function " + __FUNCTION__ + " with the value " + (string)value; 14. } 15. //+------------------------------------------------------------------+ 16. void OnStart(void) 17. { 18. FnPtr FnMsg[2]; 19. 20. FnMsg[0] = Msg_01; 21. FnMsg[1] = Msg_02; 22. 23. Print(Msg_01(171)); 24. Print(Msg_02(-375)); 25. 26. for (uchar c = 0, i = 15; c < FnMsg.Size(); c++, i += 30) 27. Print(FnMsg[c](i)); 28. } 29. //+------------------------------------------------------------------+
Code 09
Wenn Code 09 ausgeführt wird, erscheinen die folgenden Meldungen auf dem Terminal.

Bild 02
„Was für ein Wahnsinn ist das denn? Was ist hier los?“ Nun, lieber Leser, an dieser Stelle sind wir nun beim wirklich schwierigen Teil angelangt. In gewisser Weise habe ich es mit diesem Code sogar ein bisschen übertrieben. Denn wenn Sie die vorherigen Schritte überspringen und versuchen, Code 09 von Anfang an zu verstehen, werden Sie höchstwahrscheinlich verwirrter sein als je zuvor in Ihrem Leben. Das liegt daran, dass Sie die ersten beiden Zeilen, die auf Bild 02 zu sehen sind, zwar verstehen würden, aber woher kommen die beiden anderen, auf die ich hinweise? Und genau darum geht es. Dennoch sind Zeiger in MQL5 eigentlich sehr einfach zu erklären und zu verstehen. Aber selbst dann können sie jemanden verwirren, der Code sieht, in dem sie verwendet werden, ohne genau zu verstehen, was dort eigentlich umgesetzt wurde.
Bitte beachten Sie, dass die beiden hervorgehobenen Zeilen in Abbildung 02 in Zeile 27 von Code 09 genau definiert sind. Aber wie? Genau hier entsteht die Verwirrung. Da die Deklaration in Zeile 18 einen Typ verwendet, bei dem es sich um einen Zeiger handelt, könnte man bei Zeile 27 erwarten, dass man eine bestimmte Art von Informationen erhält. Der Compiler hat jedoch erkannt, dass es sich hierbei um einen Funktionsaufruf handelt. Deshalb ist diese Deklaration genauso formuliert. Und deshalb sieht das Ergebnis so aus, wie Sie auf Bild 02 sehen können. Bitte beachten Sie, dass der Name der Funktion keine Rolle spielt. In Zeile 27 wird genau festgelegt, welche Funktion aufgerufen werden soll, da wir in den Zeilen 20 und 21 einem Array-Element eine Funktion zugewiesen haben.
Vielleicht denken Sie: „Aber warum sollte man sich das Leben so schwer machen?“ Gibt es davon im Leben nicht schon genug? Brauchten wir so etwas wirklich, auch wenn es technisch möglich ist? Meine Güte! „Programmierer sind seltsame Leute; sie sind alle völlig verrückt.“
Vielleicht dreht tatsächlich ein beträchtlicher Teil der Programmierer irgendwann durch und wird zu sehr seltsamen Menschen. (GELÄCHTER). Aber insgesamt sind wir alle sehr nette Menschen, nur ein bisschen exzentrisch. Allerdings können wir bereits jetzt über etwas nachdenken, was bisher unmöglich war. Das liegt daran, dass Sie noch nicht gesehen hast, was in diesem Thema gezeigt wurde. Nun werden wir aber den Code 04 aus dem vorherigen Abschnitt so anpassen, dass er das nutzt, was wir in diesem Abschnitt behandelt haben. So erhalten wir folgenden Code:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_KEY_UP 38 05. #define def_KEY_DOWN 40 06. //+----------------+ 07. #define macro_NameObject "Demo" + (string)ObjectsTotal(0) 08. //+------------------------------------------------------------------+ 09. string gl_Objs[2]; 10. //+------------------------------------------------------------------+ 11. typedef void (*ProcPtr)(void); 12. typedef void (*KeyEvent)(int &); 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. ProcPtr proc[3]; 17. 18. proc[0] = Init; 19. proc[1] = Loop; 20. proc[2] = Deinit; 21. 22. for (uint c = 0; c < proc.Size(); c++) 23. proc[c](); 24. } 25. //+------------------------------------------------------------------+ 26. void Init(void) 27. { 28. ObjectCreate(0, gl_Objs[0] = macro_NameObject, OBJ_VLINE, 0, 0, 0); 29. ObjectSetInteger(0, gl_Objs[0], OBJPROP_COLOR, clrRoyalBlue); 30. ObjectCreate(0, gl_Objs[1] = macro_NameObject, OBJ_HLINE, 0, 0, 0); 31. ObjectSetInteger(0, gl_Objs[1], OBJPROP_COLOR, clrPurple); 32. } 33. //+------------------------------------------------------------------+ 34. void Loop(void) 35. { 36. KeyEvent key[3]; 37. int pos = 0; 38. 39. key[0] = Bar_NONE; 40. key[1] = Bar_Next; 41. key[2] = Bar_Prev; 42. 43. while (!IsStopped()) 44. { 45. key[TerminalInfoInteger(TERMINAL_KEYSTATE_UP) ? 1 : (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN) ? 2 : 0)](pos); 46. Sleep(100); 47. } 48. } 49. //+------------------------------------------------------------------+ 50. void Event(const int arg) 51. { 52. MqlRates rate[1]; 53. 54. Comment(StringFormat("Current bar analyzed: %d", arg)); 55. CopyRates(_Symbol, _Period, arg, rate.Size(), rate); 56. for (uint c = 0; c < gl_Objs.Size(); c++) 57. ObjectMove(0, gl_Objs[c], 0, rate[0].time, rate[0].close); 58. } 59. //+------------------------------------------------------------------+ 60. void Deinit(void) 61. { 62. Comment(""); 63. for (uint c = 0; c < gl_Objs.Size(); c++) 64. ObjectDelete(0, gl_Objs[c]); 65. ChartRedraw(); 66. } 67. //+------------------------------------------------------------------+ 68. void Bar_Prev(int &arg) 69. { 70. arg = (arg < Bars(_Symbol, _Period) ? arg + 1 : arg); 71. Event(arg); 72. } 73. //+------------------------------------------------------------------+ 74. void Bar_Next(int &arg) 75. { 76. arg = (arg > 0 ? arg - 1 : arg); 77. Event(arg); 78. } 79. //+------------------------------------------------------------------+ 80. void Bar_NONE(int &arg) 81. {} 82. //+------------------------------------------------------------------+
Code 10
Das Ergebnis der Ausführung von Code 10 wird genau dem entsprechen, was in Animation 01 gezeigt wird. Deshalb werden wir es hier nicht wiederholen. In Code 10 nehme ich mir jedoch eine kleine Freiheit. Ich mache das, damit Sie verstehen, dass wir etwas nicht immer auf nur eine einzige Art und Weise tun müssen. Es gibt viele Wege, um zum gleichen Ergebnis zu gelangen. Einige davon sind einfacher, verlangsamen die Erweiterung des Codes jedoch etwas, während andere, die etwas komplexer sind, eine sehr schnelle Erweiterung ermöglichen.
Meiner Meinung nach enthält Code 10 jedenfalls keine Elemente, die so schwer zu verstehen wären. Zumal mir der Stoff derzeit noch recht frisch im Gedächtnis ist. Allerdings enthält Code 10 einige Punkte, die in der vorliegenden Form vielleicht nicht ganz logisch erscheinen. Lassen Sie uns diese Punkte kurz erläutern.
Die erste davon befindet sich in der Prozedur OnStart. Hier sehen wir, dass in Zeile 16 ein Array von Prozeduren deklariert wird. Unmittelbar danach, in den Zeilen 18, 19 und 20, definieren wir die Prozeduren, die für jedes Array-Element ausgeführt werden. Dann treten wir in eine Schleife ein, um jede dieser Prozeduren in einer bestimmten Reihenfolge auszuführen. Mir ist klar, dass dies auf den ersten Blick unlogisch erscheinen mag, aber Sie müssen verstehen, dass das Ziel dieser Artikel darin besteht, Ihnen beizubringen, wie ein Programmierer zu denken.
Stellen Sie sich nun eine Abfolge kleiner, ständig wiederholter Schritte vor, die in einer bestimmten Reihenfolge ausgeführt werden müssen. Wenn man jede auszuführende Prozedur Zeile für Zeile schreiben müsste, würde dies nicht nur viel Zeit in Anspruch nehmen, sondern auch das Ändern oder Korrigieren der Ausführungsreihenfolge zu einer mühsamen Aufgabe machen. Wenn sich die Indizes im Array jedoch anhand eines mathematischen Ausdrucks berechnen lassen, ist es durchaus möglich, Prozeduren als Elemente desselben Arrays zu definieren. Auf diese Weise könnten Sie eine Schleife verwenden, um die Array-Elemente in einer bestimmten Reihenfolge auszuführen, was sonst nicht möglich wäre.
Was die Prozedur „Loop“ betrifft, die in Zeile 34 steht, so hat sie einen sehr ähnlichen Zweck. Allerdings funktioniert der Mechanismus dort etwas anders. In diesem Fall definieren wir zwischen den Zeilen 39 und 41 einige Prozeduren, die je nach gedrückter Taste verwendet werden. Und in Zeile 45 rufen wir mithilfe des ternären Operators die entsprechende Prozedur auf.
Dieser Ansatz – oder diese Denkweise bei der Lösung eines bestimmten Problems – kann eine Aufgabe, die sonst sehr mühsam wäre, manchmal zu etwas viel Einfacherem und Schnellerem machen. Denn wenn etwas geändert werden müsste, müsste man nur ein Minimum an Änderungen vornehmen, wodurch jede Lösung sehr schnell umgesetzt werden könnte.
Abschließende Überlegungen
Auch wenn wir weit mehr tun können, als hier gezeigt wurde, bin ich mir sicher, dass das, was wir in diesem Artikel behandelt haben, für viele Menschen etwas völlig Neues ist und daher gründlich studiert werden sollte. Es ist wichtig, jeden in diesem Artikel behandelten Punkt zu verstehen.
Im Anhang finden Sie die wichtigsten Code-Beispiele aus diesem Artikel. Nutzen Sie daher diese Zeit, um alles zu üben, was hier gezeigt wurde. Versuchen Sie auch, über das nachzudenken, was in früheren Artikeln erläutert wurde, und darüber, wie sich dieselben Fakten und Konzepte mit dem hier Gesagten kombinieren lassen. Versuche, etwas umzusetzen, bei dem Sie all das bisher erworbene Wissen anwenden können. Im nächsten Artikel werden wir uns erneut mit Ereignissen befassen, die mit Objekten zusammenhängen. Diesmal wird jedoch der Schwerpunkt auf der Verwendung der Maus liegen, da der Teil zur Tastatur bereits gründlich behandelt wurde.
Übersetzt aus dem Portugiesischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/pt/articles/15995
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.
Gaußsche Prozesse im maschinellen Lernen: Regressionsmodellierung in MQL5
Eine Einführung in die Untersuchung fraktaler Marktstrukturen mithilfe von maschinellem Lernen
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Analyse von Kurs-Zeit-Lücken in MQL5 (Teil I): Entwicklung eines einfachen Indikators
- 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.