Vorteile von Templates
Funktionstemplates werden in den Fällen verwendet, wenn man gleiche Operationen mit Daten verschiedener Typen ausführen muss, zum Beispiel, Suche nach dem maximalen Element in einem Array. Der wichtigste Vorteil der Templates besteht darin, dass der Entwickler keine Überladung für jeden Typ separat schreiben muss. Das heißt statt Überladung für jeden Typ zu deklarieren
double ArrayMax(double array[])
{
...
}
int ArrayMax(int array[])
{
...
}
uint ArrayMax(uint array[])
{
...
}
long ArrayMax(long array[])
{
...
}
datetime ArrayMax(datetime array[])
{
...
}
|
reicht es, ein Funktionstemplate zu schreiben
template<typename T>
T ArrayMax(T array[])
{
if(ArraySize()==0)
return(0);
uint max_index=ArrayMaximum(array);
return(array[max_index]);
}
|
und dieses dann im Code zu verwenden:
double high[];
datetime time[];
....
double max_high=ArrayMax(high);
datetime lasttime=ArrayMax(time);
|
Hier gibt es einen formellen Parameter T, der den Datentyp setzt, d.h. der Compiler generiert automatisch eine Funktion für jeden Typ – double, datetime und so weiter. Genauso kann man in MQL5 Klassentemplates erstellen und von allen Vorteilen dieser Methode profitieren.
Klassentemplates
Ein Klassentemplate wird mithilfe des Schlüsselwortes template deklariert, nach welchem spitze Klammern stehen <>, in welchen formelle Parameter mit dem Schlüsselwort typename aufgezählt sind. Dies weist den Compiler darauf hin, dass er mit einer allgemeinen Klasse mit dem formellen T Parameter zu tun hat, welcher den realen Typ der Variablen bei der Implementierung der Klasse setzt. Zum Beispiel, erstellen wir eine Vector-Klasse für das Speichern eines Arrays mit Elementen vom Typ T:
#define TOSTR(x) #x+" " // Makro für die Ausgabe des Objektnamens
//+------------------------------------------------------------------+
//| Vector-Klasse für das Speichern der Elemente vom Typ T |
//+------------------------------------------------------------------+
template <typename T>
class TArray
{
protected:
T m_array[];
public:
//--- der Konstruktor erstellt standardmäßig ein Array für 10 Elemente
void TArray(void){ArrayResize(m_array,10);}
//--- Konstruktor für die Erstellung eines Vectors mit der vorgegebenen Arraygröße
void TArray(int size){ArrayResize(m_array,size);}
//--- gibt Typ und Anzahl der Daten, die im Objekt vom Typ TArray gespeichert werden
string Type(void){return(typename(m_array[0])+":"+(string)ArraySize(m_array));};
};
|
Weiter erstellen wir auf verschiedene Weisen drei TArray Objekte für das Arbeiten mit verschiedenen Typen
void OnStart()
{
TArray<double> double_array; // standardmäßig beträgt die Veсtor-Größe 10
TArray<int> int_array(15); // Verctor-Größe 15
TArray<string> *string_array; // Pointer auf den Vector TArray<string>
//--- erstellen wir ein dynamisches Objekt
string_array=new TArray<string>(20);
//--- geben wir den Objektnamen, Datentyp und die Vector-Größe ins Journal aus
PrintFormat("%s (%s)",TOSTR(double_array),double_array.Type());
PrintFormat("%s (%s)",TOSTR(int_array),int_array.Type());
PrintFormat("%s (%s)",TOSTR(string_array),string_array.Type());
//--- löschen wir das dynamische Objekt vor dem Beenden des Programms
delete(string_array);
}
|
Das Ergebnis der Ausführung des Skriptes:
double_array (double:10)
int_array (int:15)
string_array (string:20)
|
Es wurden insgesamt 3 Vectors mit verschiedenen Datentypen erstellt: double, int und string.
Die Klassentemplates passen gut für die Erstellung von Containers - Objekten, die für die Datenkapselung von Objekten jeglichen Typs vorgesehen sind. Die Objekte von Containers stellen Kollektionen dar, die bereits Objekte eines bestimmten Typs beinhalten. In der Regel wird das Arbeiten mit gespeicherten Daten in den Container eingebaut.
Man kann zum Beispiel ein Klassentemplate erstellen, der den Zugriff auf ein Element außerhalb des Arrays verbietet, und auf diese Weise den kritischen Fehler "out of range" vermeiden.
//+------------------------------------------------------------------+
//| Klasse für sicheren Zugriff auf ein Element des Arrays |
//+------------------------------------------------------------------+
template<typename T>
class TSafeArray
{
protected:
T m_array[];
public:
//--- standardmäßiger Konstruktor
void TSafeArray(void){}
//--- Konstruktor für die Erstellung eines Arrays mit vorgegebener Größe
void TSafeArray(int size){ArrayResize(m_array,size);}
//--- Arraygröße
int Size(void){return(ArraySize(m_array));}
//--- Änderung der Arraygröße
int Resize(int size,int reserve){return(ArrayResize(m_array,size,reserve));}
//--- den Inhalt des Arrays löschen
void Erase(void){ZeroMemory(m_array);}
//--- Operator für das Zugreifen auf ein Element des Arrays nach dem Index
T operator[](int index);
//--- Zuweisungsoperator für das Erhalten aller Elemente aus dem Array
void operator=(const T &array[]); // Array vom Typ T
};
//+------------------------------------------------------------------+
//| Operation des Erhaltens eines Elements nach dem Index |
//+------------------------------------------------------------------+
template<typename T>
T TSafeArray::operator[](int index)
{
static T invalid_value;
//---
int max=ArraySize(m_array)-1;
if(index<0 || index>=ArraySize(m_array))
{
PrintFormat("%s index %d is not in range (0-%d)!",__FUNCTION__,index,max);
return(invalid_value);
}
//---
return(m_array[index]);
}
//+------------------------------------------------------------------+
//| Zuweisungsoperator für das Array |
//+------------------------------------------------------------------+
template<typename T>
void TSafeArray::operator=(const T &array[])
{
int size=ArraySize(array);
ArrayResize(m_array,size);
//--- der T Typ muss den Copy Operator unterstützen
for(int i=0;i<size;i++)
m_array[i]=array[i];
//---
}
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
int copied,size=15;
MqlRates rates[];
//--- kopieren wir das Array der Kurse
if((copied=CopyRates(_Symbol,_Period,0,size,rates))!=size)
{
PrintFormat("CopyRates(%s,%s,0,%d) Fehlercode %d",
_Symbol,EnumToString(_Period),size,GetLastError());
return;
}
//--- erstellen wir einen Container und fügen ihm das Array der MqlRates Werte hinzu
TSafeArray<MqlRates> safe_rates;
safe_rates=rates;
//--- Index innerhalb des Arrays
int index=3;
PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
//--- Index außerhalb des Arrays
index=size;
PrintFormat("Close[%d]=%G",index,safe_rates[index].close);
}
|
Bitte beachten Sie, dass bei der Beschreibung von Methoden außerhalb der Deklaration der Klasse auch bei der Deklaration des Templates verwendet werden muss
template<typename T>
T TSafeArray::operator[](int index)
{
...
}
template<typename T>
void TSafeArray::operator=(const T &array[])
{
...
}
|
Klassen- und Funktionstemplates erlauben es, mehrere formelle Parameter durch Komma getrennt anzugeben, zum Beispiel, die Map Kollektion für das Speichern von Paaren "Schlüssel – Wert":
template<typename Key, template Value>
class TMap
{
...
}
|
Siehe auch
Funktionstempaltes, Überladung