Vorwort
Die Murl Engine ist ein Multi-Plattform-Framework. Um diese Multi-Plattform-Fähigkeit zu gewährleisten, sind alle plattformrelevanten Implementierungen abstrahiert worden.
Verschiedene Plattformen verwenden verschiedene C++ Compiler. Diese Compiler unterscheiden sich geringfügig, vor allem im Bereich der Standard Bibliotheken und der STL (Standard Template Library).
Das Einbinden von Standard Bibliotheken wie stdio.h
sollte unbedingt vermieden werden, um mögliche Komplikationen beim Kompilieren der Applikation mit unterschiedlichen Compilern zu vermeiden. Die Murl Engine stellt bereits alle notwendigen Funktionen und Templates direkt zur Verfügung, so dass keine zusätzlichen Bibliotheken benötigt werden. Dies bedeutet, dass im gesamten Applikationscode keine Standard #includes
verwendet werden sollen wie z.B.
Die nachfolgenden Kapitel zeigen wie typische Implementierungen mit dem Framework ohne diese Standard Bibliotheken zu bewerkstelligen sind.
Solange eine Applikationsimplementierung ausschließlich die Interfaces und Funktionen der Murl Header verwendet, ist eine bestmögliche plattformübergreifende Funktionalität durch das Framework gewährleistet.
Gesetzt den Fall, dass es für eine spezielle Aufgabe keine passende Implementierung im Framework gibt, können natürlich auch nicht-Murl Header verwendet werden. In solch einem Fall würden wir uns freuen, wenn ein entsprechender Eintrag im Forum gemacht wird, um allfällige Korrekturen oder Erweiterungen im Framework zu diskutieren.
Dieses Tutorial ist sehr umfangreich und beinhaltet folgende Unterkapitel:
- NTL Container versus STL Container
- Textausgabe
- String Klasse
- Array Klasse
- Index Klasse
- Queue Klasse
- Map Klasse
- Zeit und Datum
- Sortieren
- Mathematische Funktionen
- Data Klasse
- Zeiger
- Hilfsfunktionen
NTL Container versus STL Container
Die am häufigsten verwendeten Container Klassen in C++ sind die Klassen der Standard Template Library (STL).
Leider unterscheiden sich diese Container bei unterschiedlichen Compilern erheblich, vor allem in Bezug auf die Ausführungsgeschwindigkeit des erzeugten Codes und in seltenen Fällen sogar auf das Verhalten der verwendeten Methoden.
Die Murl Engine bietet deshalb eine adaptierte Variante der NTL Container an, siehe www.ultimatepp.org.
Diese Container sind nach kurzer Einarbeitungszeit einfach in der Benutzung und liefern vor allem wesentlich schnellere und zuverlässigere Ausführungsgeschwindigkeiten als die Container der STL.
Zuordnung STL → Murl Container
Das Speicherkonzept der NTL ist zu Gunsten von höherer Performance etwas einfacher als das der STL und benutzt keinen Copy-Konstruktor bei internen Speicherverschiebungen. Der Copy-Konstruktor wird aber sehr wohl beim Hinzufügen von Elementen (z.B. Add()
, Insert()
) verwendet.
Prinzipiell wird zwischen einfachen Containern und Objekt-Containern unterschieden. Die folgenden einfachen Container sollten nur mit elementaren Datentypen (z.B. UInt32
, Real
, etc.) oder einfachen Strukturen verwendet werden:
Große Strukturen oder Objekte sollten nur in folgenden Objekt-Containern verwendet werden:
Die Objekt Container verwenden intern die einfachen Container, speichern dort jedoch nur Zeiger auf die Instanzen der großen Strukturen oder Objekte, welche somit im Speicher nicht bewegt werden. Dies hat den zusätzlichen Vorteil, dass Zeiger auf Objekte in solchen Containern immer ihre Gültigkeit behalten. Eine Beispiel dazu findet sich im Abschnitt Einfache Container versus Objekt-Container.
Beim Hinzufügen oder beim Kopieren von Elementen wird der Copy-Konstruktor natürlich schon aufgerufen – egal ob einfacher Container oder Objekt-Container. Lediglich bei internen Speicherverschiebungen wird auf die Copy-Konstruktoren verzichtet.
Effiziente Container-Klassen sind eine wichtige Voraussetzung für eine gute App-Performance, vor allem bei Multimedia Anwendungen auf mobilen Geräten.
Textausgabe
Die nachfolgenden Abschnitte beschäftigen sich vorwiegend mit Containern und deren Manipulation, somit ist es für Demonstrationszwecke ausreichend die Ergebnisse in Textform auszugeben.
Der Source-Code zum Tutorial beinhaltet alle folgenden Source Code Fragmente in der Datei container_logic.cpp
und benutzt zur Textausgabe in eine Konsole folgenden Header:
#include "murl_system_console.h"
Die Textausgabe Beispiele sind in folgender Methode implementiert:
void App::ContainerLogic::PrintDemo()
Nachfolgend noch ein paar generelle Infos zum Thema Textausgabe mit der Murl Engine:
Einen einfachen Text ausgeben
System::Console::Print("Hello World!\n");
Abhängig von der Plattform findet die Textausgabe wie folgt statt:
- iOS/OSX: Xcode Debug Konsole
- Windows: Konsolenfenster der Applikation
- Android: LogCat mit Priorität
ANDROID_LOG_INFO
Debug Meldungen
Der Header murl_debug_trace.h
beinhaltet Methoden zur Ausgabe von Debug Nachrichten. Diese werden im Release Build nicht ausgegeben.
MURL_TRACE(0, "Debug Hello World");
Schreibt in die Konsole:
Der erste Parameter (0
) gibt den "Log-Level" für diese Meldung an. Debug Nachrichten deren Log-Level über den globalen Log-Level liegen werden unterdrückt. Ein gesetzter globaler Log-Level von 1 zeigt also nur Debug Nachrichten mit einem Log-Level kleiner gleich 1 an. Der globale Log-Level kann übrigens mit Debug::Logger::SetLogLevel
gesetzt und mit Debug::Logger::GetLogLevel
abgefragt werden.
Beachtenswert ist auch, dass der Methodenname und die Zeilennummer der Nachricht vorangestellt werden.
Wenn nur die Nachricht ausgegeben werden soll, gibt es die Alternative:
Debug::Trace("Debug Hello World");
Schreibt in die Konsole:
Auf Android findet die Debug Ausgabe in LogCat mit Priorität ANDROID_LOG_DEBUG
statt.
Fehler Meldungen
Der Header murl_debug_error.h
beinhaltet Methoden zur Ausgabe von Fehler Meldungen. Diese werden auch im Release Build ausgegeben.
MURL_ERROR("Error Hello World");
Schreibt in die Konsole:
Beachtenswert ist dass der Methodenname und die Zeilennummer der Nachricht vorangestellt werden.
Wenn nur die Nachricht ausgegeben werden soll, gibt es die Alternative:
Debug::Error("Error Hello World");
Schreibt in die Konsole:
Auf Android findet die Fehler Ausgabe in LogCat mit Priorität ANDROID_LOG_ERROR
statt.
Konfiguration
Mit den Methoden IAppConfiguration::SetDebugOutputFunctionVerbosity()
und IAppConfiguration::SetDebugTraceLevel()
können MURL_TRACE und MURL_ERROR Meldungen konfiguriert werden.
Variablen
Alle oben angeführten Methoden (Console/Debug/Error) unterstützen natürlich auch die Verwendung von Variablen und zwar in derselben Weise wie bei der bekannten Funktion printf
.
UInt32 what = 42; System::Console::Print("Hello World %d!\n", what);
Schreibt in die Konsole:
Zeilenumbruch
Debug und Error Methoden fügen jeder Nachricht automatisch einen Zeilenumbruch hinzu. Die System Console hingegen gibt nur exakt den angegebenen Text aus, weshalb bei den Print()
Beispielen ein Zeilenumbruch mit \n
explizit angeführt werden muss. Alternativ kann auch die PrintEndline()
Funktion verwendet werden, wobei diese jedoch keine variablen Argumente unterstützt.
String Klasse
Um den seitens der Programmiersprache C/C++ unzulänglichen Umgang mit Zeichenketten zu kompensieren steht die Klasse String
aus dem Header murl_string.h
zur Verfügung.
Alle String
Beispiele sind in folgender Methode implementiert:
void App::ContainerLogic::StringDemo()
String Klasse
Ein String
Objekt kann einfach an beliebiger Stelle instanziert werden:
String myText;
Ein so instanzierter String
ist vorerst einmal leer (IsEmpty()
):
if (myText.IsEmpty()) { System::Console::Print("Text is Empty\n"); }
Im Gegensatz zur STL hat der NTL String immer einen gültigen Zeiger auf die Zeichenkette. Dieser kann mit Begin()
ausgelesen werden (Äquivalent zu c_str()
des STL String):
System::Console::Print("myText contains '%s'\n", myText.Begin());
Zuweisen eines Strings (=
):
myText = "Hello"; System::Console::Print("myText contains '%s'\n", myText.Begin());
Anhängen eines Strings (+=
):
myText += " World!"; System::Console::Print("myText contains '%s'\n", myText.Begin());
Leeren eines Strings (Clear()
):
myText.Clear(); System::Console::Print("myText contains '%s'\n", myText.Begin());
- Zu beachten
- Achtung! Um den Inhalt eines String-Objekts mit
%s
auszugeben, muss immer die.Begin()
Methode verwendet werden. Diese liefert als Rückgabewert den Pointer zum eigentlichen Null-terminierten Character-Array. Wird auf das.Begin()
vergessen, wird das String-Objekt übergeben, was je nach Plattform zu einem fehlerhaften Verhalten oder zu einer Fehlermeldung führen kann. Beispiel:Liefert im Debug Mode unter Visual Studio folgendes Ergebnis (Xcode liefert korrekterweise eine Fehlermeldung):String valString = "Value = "; UInt32 val = 42; Debug::Trace("%s %d", valString, val); Debug::Trace("%s %d", valString.Begin(), val);
Im ersten Fall wird nicht das Null-terminierten Character-Array sondern das String-Objekt auf den Stack gelegt. Die Print Methode verwendet die Daten als würde es sich um einen C-String gefolgt von einem Integer-Wert handeln, weshalb es zur fehlerhaften Ausgabe kommt. Im zweiten Fall stimmt die Ausgabe.Value = 8Value = 42
String Manipulationen
Initialisieren von Strings (String()
, =
):
String murlText("Murl"); String engineText = "Engine";
Verketten von Strings (+
):
System::Console::Print(murlText + "-" + engineText + "\n");
Erzeugen von Character-Zeichen (String()
):
Vorsicht! Hier ist unbedingt das einfache Hochkomma zu verwenden, da es sich um ein einzelnes Zeichen handeln muss.
String text('X', 4); // text contains now: "XXXX" System::Console::Print(text + "\n");
Anhängen von Zeichen (Cat()
):
Vorsicht! Hier ist unbedingt das einfache Hochkomma zu verwenden, da es sich um ein einzelnes Zeichen handeln muss.
text.Cat('Y', 5); // text contains now: "XXXXYYYYY" System::Console::Print(text + "\n");
Ersetzen einzelner Zeichen (Set()
):
Vorsicht! Hier ist unbedingt das einfache Hochkomma zu verwenden, da es sich um ein einzelnes Zeichen handeln muss.
text.Set(7, 'Z'); text.Set(8, 'Z'); // text contains now: "XXXXYYYZZ" System::Console::Print(text + "\n");
Einfügen von Zeichen oder Strings (Insert()
):
text.Insert(0, 'A'); // text contains now: "AXXXXYYYZZ" System::Console::Print(text + "\n"); text.Insert(2, "BB"); // text contains now: "AXBBXXXYYYZZ" System::Console::Print(text + "\n");
Entfernen von Zeichen (Remove()
):
text.Remove(5, 2); // text contains now: "AXBBXYYYZZ" System::Console::Print(text + "\n");
Ersetzen von Teil-Strings (Replace()
): Alle im String vorkommenden Teilstrings die mit dem Suchstrings "YY"
übereinstimmen, werden durch den Replace-String "CCC"
ersetzt.
text.Replace("YY", "CCC"); // text contains now: "AXBBXCCCYZZ" System::Console::Print(text + "\n");
Teilstring Operationen
Entfernen von Leerzeichen (Trim()
, TrimLeft()
, TrimRight()
):
Achtung! Erzeugt einen neuen String (keine Modifikation des Quell-Strings).
String whiteSpace(" white "); System::Console::Print("'" + whiteSpace.Trim() + "'\n"); // prints: 'white' System::Console::Print("'" + whiteSpace.TrimLeft() + "'\n"); // prints: 'white ' System::Console::Print("'" + whiteSpace.TrimRight() + "'\n"); // prints: ' white'
Sub-Strings von Strings erhalten (Left()
, Right()
, Mid()
):
Achtung! Erzeugt einen neuen String (keine Modifikation des Quell-Strings).
String someText("Spraylight"); System::Console::Print(someText.Left(5) + "\n"); // prints: Spray System::Console::Print(someText.Right(5) + "\n"); // prints: light System::Console::Print(someText.Mid(2, 6) + "\n"); // prints: raylig
Anzahl der Zeichen (Byteanzahl) im String (GetLength()
):
Achtung! Die Methode (GetLength()
) liefert die Anzahl der Bytes. Die Anzahl der UTF-Zeichen kann mit der Methode (GetLengthUTF8()
) abgefragt werden.
someText += "ray"; System::Console::Print("Text: %s, Length: %d", someText.Begin(), someText.GetLength()); // prints: Text: Spraylightray, Length: 13
Suchen nach Teil-Strings (Find()
, ReverseFind()
):
Suchergebnisse liefern immer einen Index im Bereich 0 .. (GetLength() – 1)
bzw. einen negativen Index wenn die Suche erfolglos war. Optional kann die Suche auch von einem Index innerhalb des gültigen Bereichs gestartet werden.
System::Console::Print("Position: %d\n", someText.Find("what")); System::Console::Print("Position: %d\n", someText.Find("ray", 1)); System::Console::Print("Position: %d\n", someText.Find("ray", 3)); System::Console::Print("Position: %d\n", someText.ReverseFind("ray"));
Suche nach dem ersten Vorkommen eines Zeichens aus einer Menge von Zeichen (FindFirstOf()
,FindFirstNotOf()
):
System::Console::Print("Position: %d\n", someText.FindFirstOf("igl")); System::Console::Print("Position: %d\n", someText.FindFirstNotOf("prS"));
Überprüfen von String Anfang oder Ende (StartsWith()
, EndsWith()
):
System::Console::Print("Check: %s\n", someText.StartsWith("who") ? "yes" : "no"); System::Console::Print("Check: %s\n", someText.StartsWith("Spra") ? "yes" : "no"); System::Console::Print("Check: %s\n", someText.EndsWith("tray") ? "yes" : "no");
String-Vergleichs-Operatoren (==
, !=
, >
, <
):
System::Console::Print("Check: %s\n", someText == "Spray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText != "ray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText == "Spraylightray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText > "Spray" ? "yes" : "no"); System::Console::Print("Check: %s\n", someText < "light" ? "yes" : "no");
UTF-8 Strings
Grundsätzlich arbeitet das gesamte Murl Engine Framework ausschließlich mit UTF-8 Strings. Die String Klasse besitzt einige spezielle Methoden zur Verarbeitung von UTF-8 Strings.
Um UTF-8 Strings korrekt eingeben zu können muss der Source-Code Editor unbedingt auf UTF-8 Zeichen Kodierung eingestellt sein!
Anmerkung: Die Ausgabe der UTF-8 Zeichen funktioniert in der Windows Konsole leider nicht.
String utf8String("Dürüm Döner"); System::Console::PrintEndline(utf8String); utf8String += "® €2,49 𝄞"; System::Console::PrintEndline(utf8String);
Groß- und Kleinschreibung Konvertierung (ToLowerUTF8()
, ToUpperUTF8()
):
Achtung! Erzeugt einen neuen String (keine Modifikation des Quell-Strings).
System::Console::PrintEndline(utf8String.ToLowerUTF8()); System::Console::PrintEndline(utf8String.ToUpperUTF8());
UTF-8 Zeichenanzahl vs. Byteanzahl (GetLength()
, GetLengthUTF8()
):
Die Byteanzahl einzelner UTF-8 Zeichen ist unterschiedlich. Somit entspricht in einem UTF-8 String die Anzahl der Bytes nicht der Anzahl der UTF-8 Zeichen!
System::Console::Print("Bytes %d != %d Chars", utf8String.GetLength(), utf8String.GetLengthUTF8());
Bearbeiten von UTF-8 Zeichen (GetUTF8Chars()
):
Um einzelne UTF-8 Zeichen zu erhalten, kann ein String
in ein StringArray
umgewandelt werden. Jedes UTF-8 Zeichen wird als separater String im Array gespeichert.
StringArray utf8Chars; utf8Chars = utf8String.GetUTF8Chars();
Die einzelnen Zeichen können dann explizit als Strings verwendet werden:
for (UInt32 i = 0; i < utf8Chars.GetCount(); i++) { System::Console::Print(utf8Chars[i] + " "); } System::Console::Print("\n");
Somit lassen sich beliebige Einzelzeichen Operationen innerhalb des StringArrays
implementieren, z.B. Ersetzen des Euro Zeichens durch das Dollar Zeichen:
for (UInt32 i = 0; i < utf8Chars.GetCount(); i++) { if (utf8Chars[i] == "€") { utf8Chars[i] = "$"; } }
Aus einem StringArray
kann mit Cat()
einfach wieder ein String
erzeugt werden:
String newUtf8; newUtf8.Cat(utf8Chars); System::Console::PrintEndline(newUtf8);
Weitere String Methoden
Die oben gezeigten String
Methoden sind zum Teil noch in anderen Variationen mit unterschiedlichen Parametern verfügbar. Eine vollständige Auflistung aller Methoden kann in der String API-Referenz
bzw. direkt im Header murl_string.h
eingesehen werden.
String Konvertierungen
Zum Verarbeiten von Strings gibt es diverse Hilfsfunktionen im Header murl_util_string_conversion.h
. Eine detaillierte Beschreibung der Funktionen findet sich in der API-Referenz unter String Conversion Functions
.
String in Zahlen Konvertierung:
Util::StringToBool()
Util::StringToUInt64()
Util::StringToSInt64()
Util::StringToUInt32()
Util::StringToSInt32()
Util::StringToDouble()
Util::StringToFloat()
Util::StringToColor()
Util::StringToColorComponent()
Util::HexStringToUInt64()
Util::HexStringToUInt32()
Util::AngleStringToDouble()
Beispiel:
String numString = "12345.678"; Double doubleNumber; if (Util::StringToDouble(numString, doubleNumber)) { System::Console::Print("Double Number %f\n", doubleNumber); } SInt32 intNumber; if (Util::StringToSInt32(numString, intNumber)) { System::Console::Print("SInt32 Number %d\n", intNumber); } if (!Util::StringToDouble("not a number", doubleNumber)) { System::Console::PrintEndline("Not a number!"); }
Array Konvertierung:
Util::StringToBoolArray()
Util::StringToUInt64Array()
Util::StringToSInt64Array()
Util::StringToUInt32Array()
Util::StringToSInt32Array()
Util::StringToDoubleArray()
Util::StringToFloatArray()
Beispiel:
String someNumbers = "17;08;32;42;15;26"; UInt32Array numberArray; Util::StringToUInt32Array(someNumbers, ';', numberArray); for (UInt32 i = 0; i < numberArray.GetCount(); i++) { System::Console::Print("%d ", numberArray[i]); } System::Console::Print("\n");
Zahlen in String Konvertierung:
Util::UInt64ToString()
Util::SInt64ToString()
Util::UInt32ToString()
Util::SInt32ToString()
Util::DoubleToString()
Util::BoolToString()
Beispiel:
UInt64 uint64Value = 123456789; String uint64String = Util::UInt64ToString(uint64Value); System::Console::PrintEndline(uint64String); System::Console::PrintEndline(Util::DoubleToString(765.432, "%5.2f"));
String Hilfsobjekte und -funktionen
Zum Verarbeiten von Strings
gibt es diverse Hilfsfunktionen im Header murl_util_string.h
. Eine detaillierte Beschreibung der Funktionen findet sich in der API-Referenz unter Murl Util String Functions
.
Statische String Objekte:
Util::StaticEmptyString()
Util::StaticWhitespaceString()
Util::StaticEmptyStringArray()
Sortieren von StringArray:
Zerlegen von Strings in StringArray oder StringIndex:
Verketten von StringArray oder StringIndex:
Util::JoinStringArray()
Util::JoinStringIndex()
Trimmen von StringArray:
Parsen von Strings:
Util::GetLine()
Util::GetWord()
Dateipfade und Dateinamen:
Util::GetFilePath()
Util::GetFileName()
Util::GetFileExtension()
Util::StripExtension()
Util::StripPathAndExtension()
Util::JoinPaths()
Util::GetUnixPath()
Util::GetWindowsPath()
Util::GetNormalizedPath()
Util::GetRelativePath()
Util::GetAbsolutePath()
C++ Namen:
Util::HasCppScope()
Util::GetCppScope()
Util::StripCppScope()
Util::HasScope()
Util::GetScope()
Util::StripScope()
Attribute:
Util::StripIndex()
Util::StripCount()
Util::IsIdValid()
Numerisch/Alphanumerisch:
Util::IsNumeric()
Util::IsAlphaNumeric()
Einzelzeichen Überprüfung:
Util::IsDigit()
Util::IsAlpha()
Util::IsAlphaNumeric()
Util::IsPunctuation()
Util::IsSpace()
Util::IsHexDigit()
Util::IsControl()
Array Klasse
Um den seitens der Programmiersprache C/C++ unzulänglichen Umgang mit Arrays zu kompensieren steht die Klasse Array
aus dem Header murl_array.h
zur Verfügung.
Alle Array
Beispiele sind in folgender Methode implementiert:
void App::ContainerLogic::ArrayDemo()
Array Template Klasse
Um das Instanzieren von Arrays
mit häufig verwendeten Datentypen zu vereinfachen, gibt es folgende vordefinierte Array-Typen:
UInt8Array
SInt8Array
UInt16Array
SInt16Array
UInt32Array
SInt32Array
UInt64Array
SInt64Array
BoolArray
StringArray
RealArray
FloatArray
DoubleArray
Die vordefinierten Array
-Typen ersparen die Verwendung der "unansehnlichen" Schreibweise mit den Größer- und Kleiner-Operatoren (z.B. Array<UInt32>
).
Ein Array
Objekt kann einfach an beliebiger Stelle instanziert werden:
UInt32Array myArray;
Ein so instanziertes Array
ist vorerst einmal leer (IsEmpty()
):
if (myArray.IsEmpty()) { System::Console::Print("myArray is Empty\n"); }
Hinzufügen von Elementen am Ende des Array (Add()
):
myArray.Add(17); myArray.Add(42); myArray.Add(54); myArray.Add(33); myArray.Add(22); System::Console::Print("myArray has %d elements\n", myArray.GetCount());
Auslesen von Array Elementen ([]
):
Hinweis: Das Iterieren in NTL Containern ist grundsätzlich immer Index basiert.
for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); } System::Console::Print("\n");
Überschreiben von Array Elementen (Set()
):
myArray.Set(2, 44); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Einfügen von Array Elementen (Insert()
):
myArray.Insert(2, 55); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Entfernen von Array Elementen (Remove()
):
myArray.Remove(3, 2); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Entfernen von mehreren Array Elementen (Remove()
):
Hinweis: Wenn innerhalb einer Schleife mehrere Elemente entfernt werden sollen, dann kann das Entfernen nicht unmittelbar in der Schleife stattfinden, da nach dem Entfernen der Iterator (Schleifenzähler) falsch ist. Eine Lösung hierfür ist die Verwendung eines zusätzlichen Arrays, welches die zu entfernenden Indizes enthält.
Achtung! Die Indizes müssen in aufsteigender Reihenfolge vorliegen.
Das Beispiel entfernt alle ungeraden Zahlen:
SInt32Array indicesToRemove; for (UInt32 i = 0; i < myArray.GetCount(); i++) { if (myArray[i] & 1) { indicesToRemove.Add(i); } } myArray.Remove(indicesToRemove); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Suchen nach Array Elementen (Find()
, FindLast()
):
Hinweis: Suchergebnisse liefern immer einen Index im Bereich 0 .. (GetLength() – 1)
bzw. einen negativen Index, wenn die Suche erfolglos war. Optional kann die Suche auch von einem Index innerhalb des gültigen Bereichs gestartet werden.
myArray.Add(44); myArray.Add(22); SInt32 findIndex; findIndex = myArray.Find(17); System::Console::Print("Find 17 at %d\n", findIndex); findIndex = myArray.Find(22); System::Console::Print("Find 22 at %d\n", findIndex); findIndex = myArray.FindLast(22); System::Console::Print("FindLast 22 at %d\n", findIndex);
Mit der Methode IsIndexValid()
kann geprüft werden, ob ein gegebener Index gültig ist.
UInt32 i=0; System::Console::Print("myArray contains [ "); while (myArray.IsIndexValid(i)) { System::Console::Print("%d ", myArray[i++]); } System::Console::Print("]\n");
Kopieren von Arrays (Array()
, =
):
UInt32Array my2ndArray(myArray); UInt32Array my3rdArray; my3rdArray = myArray;
Arrays aneinander hängen (Add()
):
myArray.Add(my2ndArray); System::Console::Print("myArray has %d elements\n", myArray.GetCount()); for (UInt32 i = 0; i < myArray.GetCount(); i++) { System::Console::Print("Element %d has value %d\n", i, myArray[i]); }
Erstes Array Element auslesen (Bottom()
)::
System::Console::Print("Bottom element is %d\n", myArray.Bottom());
Letztes Array Element auslesen (Top()
):
System::Console::Print("Top element is %d\n", myArray.Top());
Letztes Array Element löschen (Pop()
):
System::Console::Print("Top element was %d\n", myArray.Pop());
Löschen von Arrays (Clear()
, Empty()
):
Hinweis: Clear()
leert das Array und gibt den intern verwendeten Speicher frei. Empty()
hingegen leert das Array behält jedoch den internen Speicher für eine spätere Verwendung. Wenn ein und dasselbe Array immer wieder gelöscht und mit neuen Elementen gefüllt werden muss, kann die Verwendung von Empty()
eine erhebliche Leistungssteigerung erbringen.
my2ndArray.Clear(); my3rdArray.Empty();
Die Anzahl der Array Elemente kann auch mit SetCount()
angegeben werden. Hierzu wird für jedes neue Element der Default-Konstruktor aufgerufen.
Vorsicht! Bei primitiven Datentypen existiert kein Default-Konstruktor, somit sind dann alle neuen Elemente uninitialisiert (z.B. UInt32Array
, FloatArray
etc.). Einzig und alleine StringArray
ist mit leeren Strings initialisiert, da das String
Objekt natürlich einen Default-Konstruktor mit sich bringt.
FloatArray myFloatArray; myFloatArray.SetCount(10); for (UInt32 i = 0; i < myFloatArray.GetCount(); i++) { System::Console::Print("Element %d has value %f\n", i, myFloatArray[i]); }
Nach SetCount()
alle Elemente initialisieren:
myFloatArray.SetCount(10); for (UInt32 i = 0; i < myFloatArray.GetCount(); i++) { myFloatArray[i] = 0; } for (UInt32 i = 0; i < myFloatArray.GetCount(); i++) { System::Console::Print("Element %d has value %f\n", i, myFloatArray[i]); }
Weitere Array Methoden
Die oben gezeigten Array Methoden sind zum Teil noch in anderen Variationen mit unterschiedlichen Parametern verfügbar. Eine vollständige Auflistung aller Methoden kann in der Array API-Referenz unter Murl Container Classes
bzw. direkt im Header murl_array.h
und murl_object_array.h
eingesehen werden.
ObjectArray Template Klasse
Wie eingangs erwähnt, verzichten die NTL Container zu Gunsten von Performance bei internen Speicherverschiebungen auf das Aufrufen der Copy-Konstruktoren. Um große Strukturen oder Objekte zu verwalten, sollte das ObjectArray
von murl_object_array.h
verwendet werden.
class MyObject { public: // Default Constructor MyObject() : mNumber(0) , mStatus(false) { } String GetDescription() const { String description = Util::UInt32ToString(mNumber); description += " " + Util::BoolToString(mStatus); description += " '" + mName + "'"; return description; } UInt32 mNumber; Bool mStatus; String mName; };
Grundsätzlich sind bis auf wenige Ausnahmen alle Methoden von Array
auch in ObjectArray
verfügbar.
ObjectArray<MyObject> myObjectArray; myObjectArray.SetCount(3); System::Console::Print("myObjectArray has %d elements\n", myObjectArray.GetCount()); myObjectArray[0].mName = "Element 0"; myObjectArray[1].mName = "Element 1"; myObjectArray[2].mName = "Element 2"; for (UInt32 i = 0; i < myObjectArray.GetCount(); i++) { System::Console::Print("Element %d is %s\n", i, myObjectArray[i].GetDescription().Begin()); }
Bestehendes Element hinzufügen:
MyObject anotherObject; anotherObject.mNumber = 42; anotherObject.mStatus = true; anotherObject.mName = "New One!"; myObjectArray.Add(anotherObject); System::Console::Print("myObjectArray has %d elements\n", myObjectArray.GetCount()); for (UInt32 i = 0; i < myObjectArray.GetCount(); i++) { System::Console::Print("Element %d is %s\n", i, myObjectArray[i].GetDescription().Begin()); }
Mit SetCount(3)
werden drei Objekte erzeugt und im ObjectArray
abgelegt. Mit Add(anotherObject)
wird eine Kopie vom Objekt anotherObject
ezeugt und diese Kopie wird im ObjectArray
abgelegt.
Mit Remove()
kann ein Objekt aus dem ObjectArray
entfernt und dessen Speicher freigegeben werden. Das ObjectArray
gibt den Speicher für seine Objekte automatisch frei, wenn das ObjectArray
zerstört wird.
Spezielle ObjectArray Methoden
Wenn Objekte einen privaten Copy-Konstruktor haben, dann kann ein bestehendes Objekt nicht wie oben gezeigt mit Add(anotherObject)
hinzugefügt werden. Zu diesem Zweck können auch Zeiger von "new"
allokierten Objekten in das ObjectArray
eingefügt werden, das allokierte Objekt geht dann in den Besitz des ObjectArray
über und wird zu gegebener Zeit vom ObjectArray
mit "delete"
freigegeben.
myObjectArray.Add(new MyObject); myObjectArray.Set(1, new MyObject); myObjectArray.Insert(2, new MyObject);
Ebenso können Objekte aus dem Besitz des ObjectArray
entzogen werden. Diese müssen dann explizit mit "delete"
freigegeben werden (Detach()
, PopDetach()
):
delete myObjectArray.Detach(1); delete myObjectArray.PopDetach();
Einfache Container versus Objekt-Container
Wie im Abschnitt NTL Container versus STL Container erwähnt, speichern Objekt-Container intern nur Zeiger auf die Objekt-Instanzen, welche somit im Speicher nicht bewegt werden müssen. Zur Veranschaulichung konstruieren wir ein kleines Beispiel:
Wir erstellen ein Array
arrayNamesA
und ein ObjectArray
arrayNamesB
und füllen beide mit willkürlichen Daten. Zusätzlich erzeugen wir mit objRefA
/ objRefB
eine Referenz auf ein Objekt im Array und mit objCopyA
bzw. mit objCopyB
eine Kopie von einem Objekt im Array.
Array<MyObject> arrayNamesA; arrayNamesA.SetCount(3); arrayNamesA[0].mName = "Pac Man A"; arrayNamesA[1].mName = "Super Mario A"; arrayNamesA[2].mName = "Lara Croft A"; MyObject& objRefA = arrayNamesA.Get(1); MyObject objCopyA = arrayNamesA.Get(1); ObjectArray<MyObject> arrayNamesB; arrayNamesB.SetCount(3); arrayNamesB[0].mName = "Pac Man B"; arrayNamesB[1].mName = "Super Mario B"; arrayNamesB[2].mName = "Lara Croft B"; MyObject& objRefB = arrayNamesB.Get(1); MyObject objCopyB = arrayNamesB.Get(1);
Mit Debug::Trace
können wir uns die Inhalte ausgeben lassen:
Debug::Trace("Initial:"); Debug::Trace(" objRefA: " + objRefA.GetDescription()); Debug::Trace("objCopyA: " + objCopyA.GetDescription()); Debug::Trace(" objRefB: " + objRefB.GetDescription()); Debug::Trace("objCopyB: " + objCopyB.GetDescription());
Alle Variablen liefern das selbe Ergebnis, zeigen aber auf ganz unterschiedliche Speicherbereiche:
Als nächstes ändern wir die Array-Objekte an Position 1 und sehen uns wieder mit Debug::Trace
die Inhalte an.
arrayNamesA[1].mName = "Giana Sisters A"; arrayNamesB[1].mName = "Giana Sisters B";
Wie erwartet liefern objRefA
und objRefB
die geänderten Inhalte, während die Variablen objCopyA
und objCopyB
die Inhalte der kopierten Elemente liefern.
Als nächstes Löschen wir noch in beiden Arrays das Objekt an Position 0.
arrayNamesA.Remove(0); arrayNamesB.Remove(0);
Die Referenz objRefB
zeigt auf das richtige Objekt im Array. Die Referenz objRefA
zeigt aber immer noch auf das Objekt an der Position 1, obwohl das richtige Objekt wegen des Löschvorgangs nun an Position 0 liegt.
Ein weiterer Vorteil von Objekt-Containern ist, dass beim Löschvorgang oder beim Einfügen von Elementen weniger Speicher umkopiert werden muss, da ja lediglich die Pointer und nicht die gesamten Objekte im Speicher verschoben werden müssen.
Index Klasse
Ein Index
Container kann zum Abspeichern und Auffinden von Elementen verwendet werden. Um das Auffinden der Elemente zu beschleunigen, wird intern ein Hash Algorithmus verwendet. Jedem Element wird ein Indexwert zugeordnet. Die Klassen Index
und ObjectIndex
sind in den Headern murl_index.h
und murl_object_index.h
implementiert, welche die Basisklasse IndexBase
aus dem Header murl_index_base.h
verwenden.
Alle Index
Beispiele sind in folgender Methode implementiert:
void App::ContainerLogic::IndexDemo()
Index Template Klasse
Um das Instanzieren von Index
-Containern mit häufig verwendeten Datentypen zu vereinfachen, gibt es folgende vordefinierte Index
-Typen:
UInt8Index
SInt8Index
UInt16Index
SInt16Index
UInt32Index
SInt32Index
UInt64Index
SInt64Index
StringIndex
RealIndex
FloatIndex
DoubleIndex
Dies erspart die Verwendung der "unansehnlichen" Schreibweise mit den Größer- und Kleiner-Operatoren (z.B. Index<String>
).
Erzeugen eines Index
Objekts:
StringIndex myStringIndex; myStringIndex.Add("null"); myStringIndex.Add("one"); myStringIndex.Add("two"); myStringIndex.Add("three"); myStringIndex.Add("four"); myStringIndex.Add("five"); // "null"->0, "one"->1, "two"->2, "three"->3, "four"->4, "five"->5 System::Console::Print("myStringIndex one is %d\n", myStringIndex.Find("one")); System::Console::Print("myStringIndex two is %d\n", myStringIndex.Find("two")); System::Console::Print("myStringIndex four is %d\n", myStringIndex.Find("four"));
Die Index
Klasse erlaubt auch das mehrmalige Hinzufügen des gleichen Elements:
myStringIndex.Add("two"); // "null"->0, "one"->1, "two"->2, "three"->3, "four"->4, "five"->5, "two"->6 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
Die Methode RemoveKey()
entfernt jedes Vorkommen des angegebenen Elements aus dem Container:
myStringIndex.RemoveKey("two"); // "null"->0, "one"->1, "three"->2, "four"->3, "five"->4 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
Die Methode Remove()
entfernt das Element mit dem angegebenen Index:
myStringIndex.Remove(3); // "null"->0, "one"->1, "three"->2, "five"->3 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
Einen Index
mit eindeutigen Elementen erhält man durch Verwendung der Methode FindAdd()
. Dabei wird der Wert nur dann hinzugefügt, wenn er nicht gefunden wurde. Dadurch können multiple Elemente verhindert werden:
myStringIndex.Pop(); myStringIndex.FindAdd("two"); myStringIndex.FindAdd("three"); // "null"->0, "one"->1, "three"->2, "two"->3 System::Console::Print("myStringIndex has %d elements\n", myStringIndex.GetCount()); for (UInt32 i = 0; i < myStringIndex.GetCount(); i++) { System::Console::Print("Element %s has index %d\n", myStringIndex[i].Begin(), i); }
Index Hash
Wenn Klassen als Index Element verwendet werden, dann müssen diese eine öffentliche Methode GetHashValue()
implementieren und dort einen entsprechenden Hash-Wert berechnen.
z.B. Hash eines Data
Objekts von murl_data.h
:
UInt32 GetHashValue() const { return Util::Hash::GetMemoryHashValue(mData, mByteSize); }
Weitere Index Methoden
Die Index
Methoden sind den Array
Methoden sehr ähnlich und zum Großteil auch in gleicher Weise zu verstehen wie z.B. Set()
, Insert()
, Remove()
, etc.
Multiple Elemente können mit Find()
oder FindLast()
und anschließendem FindNext()
oder FindPrev()
gesucht und verarbeitet werden.
Alle Methoden die den Begriff "Put"
im Namen tragen sind nur relevant wenn mit Unlink()
gearbeitet wird. Die Methode Unlink()
bewirkt, dass diese Elemente von den Suchoperationen ausgeschlossen (ignoriert) werden.
Eine vollständige Auflistung aller Methoden kann in der API-Referenz
bzw. direkt im Header murl_index_base.h
bzw. murl_index.h
und murl_object_index.h
eingesehen werden.
ObjectIndex Template Klasse
Wie eingangs erwähnt, verzichten die NTL Container zu Gunsten von Performance bei internen Speicherverschiebungen auf das Aufrufen der Copy-Konstruktoren. Um große Strukturen oder Objekte zu verwalten, sollte der ObjectIndex
von murl_object_index.h
verwendet werden.
Fast alle Methoden sind gleichbedeutend den Methoden der Klasse Index
.
Queue Klasse
Die Queue
Klasse kann für das Erstellen von FIFO Containern verwendet werden und erlaubt das schnelle Hinzufügen und Entfernen von Elementen am Anfang und am Ende einer Sequenz.
Die Klassen Queue
und ObjectQueue
sind in den Headern murl_queue.h
und murl_object_queue.h
implementiert.
Alle Queue Beispiele sind in folgender Methode implementiert:
void App::ContainerLogic::QueueDemo()
Queue Template Klasse
Erzeugen eines Queue
Objekts.
Queue<String> myStringQueue;
Um das Instanzieren von Queues mit häufig verwendeten Datentypen zu vereinfachen, gibt es folgende vordefinierte Queue-Typen:
UInt8Queue
SInt8Queue
UInt16Queue
SInt16Queue
UInt32Queue
SInt32Queue
UInt64Queue
SInt64Queue
BoolQueue
StringQueue
RealQueue
FloatQueue
DoubleQueue
Die vordefinierten Queue-Typen ersparen die Verwendung der "unansehnlichen" Schreibweise mit den Größer- und Kleiner-Operatoren:
StringQueue myStringQueue;
Hinzufügen von Werten (AddHead()
, AddTail()
):
myStringQueue.AddTail("item 1"); myStringQueue.AddTail("item 2"); myStringQueue.AddTail("item 3"); myStringQueue.AddHead("item 0");
Auslesen des ersten Elements mit einer Referenz (Head()
, Tail()
):
String& head = myStringQueue.Head(); System::Console::PrintEndline(head);
Auslesen eines Elements über die Position ([]
):
String& item1 = myStringQueue[1]; System::Console::PrintEndline(item1);
Löschen des ersten Elements AddTail (DropHead()
, DropTail()
):
myStringQueue.DropHead();
Auslesen und Löschen des ersten Elements (DropGetHead()
, DropGetTail()
) bis die Queue leer ist:
while (!myStringQueue.IsEmpty()) { head = myStringQueue.DropGetHead(); System::Console::PrintEndline(head); }
ObjectQueue Template Klasse
Wie bereits erwähnt verzichten die NTL Container zu Gunsten von Performance bei internen Speicherverschiebungen auf das Aufrufen der Copy-Konstruktoren. Um große Strukturen oder Objekten zu verwalten sollte die ObjectQueue
von murl_object_queue.h
verwendet werden.
Fast alle Methoden sind gleichbedeutend den Methoden der Klasse Queue
.
Map Klasse
Ein Map Objekt kann einem Schlüssel ein oder mehrere Elemente zuordnen. Um das Auffinden des Schlüssels zu beschleunigen, wird intern ein Hash Algorithmus verwendet. Die Klassen Map
und ObjectMap
sind in den Headern murl_map.h
und murl_object_map.h
implementiert, welche die Basisklasse MapBase
aus dem Header murl_map_base.h
verwenden.
Alle Map Beispiele sind in folgender Methode implementiert:
void App::ContainerLogic::MapDemo()
Map Template Klasse
Erzeugen eines Map
Objekts, z.B. eine Zuordnung von String
Schlüssel auf Double
Werte:
Map<String, Double> nameToDouble; nameToDouble.Add("null", 0.0); nameToDouble.Add("pi", Math::PI); nameToDouble.Add("double", 2.0); nameToDouble.Add("halve", 0.5);
Auslesen einer Map (GetKey()
, []
):
System::Console::Print("nameToDouble has %d elements\n", nameToDouble.GetCount()); for (UInt32 i = 0; i < nameToDouble.GetCount(); i++) { System::Console::Print("Key %s ", nameToDouble.GetKey(i).Begin()); System::Console::Print("has value %f\n", nameToDouble[i]); }
Suchen einzelner Werte (Find()
):
Hinweis: Zugriffe auf NTL Container sind immer Indexbasiert.
SInt32 index = nameToDouble.Find("double"); if (index >= 0) { System::Console::Print("double value %f\n", nameToDouble[index]); } index = nameToDouble.Find("something"); if (index < 0) { System::Console::PrintEndline("something not found"); }
Die oben gezeigte Suche nach den Schlüsseln bietet den Vorteil, dass eine entsprechende Fehlerhandhabung gemacht werden kann, wenn der Schlüssel nicht vorhanden ist.
Eine vereinfachte aber auch "gefährliche" Variante ist die Verwendung von Get()
.
Achtung! In diesem Fall wird die Applikation mit ASSERT
beendet, wenn der Schlüssel nicht vorhanden ist!
System::Console::Print("pi is %f", nameToDouble.Get("pi")); System::Console::Print("halve is %f", nameToDouble.Get("halve"));
Es gibt eine "entschärfte" Variante von Get()
, welche einen Vorgabewert verlangt, falls der Schlüssel nicht vorhanden ist. Dieser Vorgabewert wird dann der Map
hinzugefügt:
System::Console::Print("quater is %f\n", nameToDouble.Get("quater", 0.25)); System::Console::Print("nameToDouble has %d elements\n", nameToDouble.GetCount()); for (UInt32 i = 0; i < nameToDouble.GetCount(); i++) { System::Console::Print("Key %s has value %f\n", nameToDouble.GetKey(i).Begin(), nameToDouble[i]); }
Die Map
Klasse kann immer mehrere Werte einem Schlüssel zuordnen:
nameToDouble.Add("odd", 1.0); nameToDouble.Add("odd", 3.0); nameToDouble.Add("odd", 7.0); index = nameToDouble.Find("odd"); while (index >= 0) { System::Console::Print("Found 'odd' with value %f\n", nameToDouble[index]); index = nameToDouble.FindNext(index); }
Eine Map
mit eindeutigen Elementen erhält man durch entsprechende Verwendung der Methode FindAdd()
. Multiple Elemente können wie folgt vermieden werden:
nameToDouble.RemoveKey("odd"); nameToDouble.FindAdd("halve", 0.5); nameToDouble.FindAdd("ten", 10.0); System::Console::Print("nameToDouble has %d elements\n", nameToDouble.GetCount()); for (UInt32 i = 0; i < nameToDouble.GetCount(); i++) { System::Console::Print("Key %s has value %f\n", nameToDouble.GetKey(i).Begin(), nameToDouble[i]); }
Weitere Map Methoden
Die Map
Methoden sind den Array
/ Index
Methoden sehr ähnlich und zum Großteil auch in gleicher Weise zu verstehen wie z.B. Insert()
, Remove()
, etc.
Multiple Elemente können mit Find()
oder FindLast()
und anschließendem FindNext()
oder FindPrev()
verarbeitet werden.
Alle Methoden die den Begriff "Put"
im Namen tragen sind nur relevant, wenn mit Unlink()
gearbeitet wird. Die Methode Unlink()
bewirkt, dass diese Elemente von den Suchoperationen ausgeschlossen (ignoriert) werden.
Eine vollständige Auflistung aller Methoden kann in der API-Referenz
bzw. direkt im Header murl_map_base.h
bzw. murl_map.h
und murl_object_map.h
eingesehen werden.
ObjectMap Template Klasse
Wie eingangs erwähnt verzichten die NTL Container zu Gunsten von Performance bei internen Speicherverschiebungen auf das Aufrufen der Copy-Konstruktoren. Um große Strukturen oder Objekten zu verwalten sollte die ObjectMap
von murl_object_map.h
verwendet werden.
Fast alle Methoden sind gleichbedeutend den Methoden der Klasse Map
.
Zeit und Datum
Die Murl Engine bietet Methoden zum Umgang mit Zeitangaben in murl_system_time.h
an.
Alle Time Beispiele sind in folgender Methode implementiert:
void TimeDemo(const Logic::IState* state)
Die Methode GetNow()
liefert die aktuelle Uhrzeit als System::Time
Objekt:
System::Time currentTime = System::Time::GetNow();
System::Time
Objekte beinhalten die Sekunden und Nanosekunden seit dem 1.1.1970 00:00 Uhr. System::Time
Objekte können einfach in ein lesbares Datum System::DateTime
umgewandelt werden:
System::DateTime currentDate(currentTime); System::Console::Print("Date %4d-%02d-%02d", currentDate.mYear, currentDate.mMonth, currentDate.mDay); System::Console::Print(" Time %02d:%02d:%02d\n", currentDate.mHour, currentDate.mMinute, currentDate.mSecond);
Startzeit des Systems auslesen (GetBootTime()
):
System::Time bootTime = state->GetEngineConfiguration()->GetBootTime(); System::DateTime bootDate(bootTime); System::Console::Print("Boot %4d-%02d-%02d", bootDate.mYear, bootDate.mMonth, bootDate.mDay); System::Console::Print(" Time %02d:%02d:%02d\n", bootDate.mHour, bootDate.mMinute, bootDate.mSecond);
System::Time
Objekte bieten auch Operatoren zum Rechnen:
System::Time upTime = currentTime - bootTime; System::Console::Print("Uptime %llu seconds", upTime.GetSeconds()); System::Console::Print(" %llu milliseconds\n", upTime.GetMilliSeconds());
Beispiel zur Ausgabe der System Laufzeit:
System::DateTime upDate(upTime); System::Console::Print("Uptime %d Months %d Days", upDate.mMonth - 1, upDate.mDay - 1); System::Console::Print(" %02d:%02d:%02d\n", upDate.mHour, upDate.mMinute, upDate.mSecond);
GetTickCount
Mit der Methode GetTickCount()
kann ein Zeitobjekt erstellt werden, das unabhängig von der eingestellten Systemzeit ist. Änderungen der Systemzeit wirken sich auf diese Zeitbasis nicht aus. Bei einem Neustart des Systems wird diese Zeitbasis typischerweise ebenfalls resettet. Für relative Zeitmessungen in der Applikation sollte immer diese Zeitbasis verwendet werden.
Weitere Zeit Methoden
Eine vollständige Auflistung aller Methoden kann in der API-Referenz (System::Time
, System::DateTime
) bzw. direkt im Header murl_system_time.h
eingesehen werden.
System::Time
Objekte können auch verglichen und sortiert werden, siehe auch folgenden Abschnitt Sortieren.
Zeitmessung (Benchmarks)
Die Datei murl_debug_time.h
stellt die folgenden Methoden für eine bequeme Zeitmessung zur Verfügung:
- MURL_TRACE_TIME_BEGIN(key) und MURL_TRACE_TIME_END(key, level, message)
- MURL_ERROR_TIME_BEGIN(key) und MURL_ERROR_TIME_END(key, message)
Mit dem Parameter key
werden BEGIN und END Aufrufe einander zugeordnet. Somit sind auch ineinander verschachtelte Zeitmessungen möglich sind.
MURL_TRACE_TIME_BEGIN(t1); CalculateDelaunayTriangulation(); MURL_TRACE_TIME_END(t1,0, "CalculateDelaunayTriangulation() "); MURL_ERROR_TIME_BEGIN(t2); CalculateConvexHull(); MURL_ERROR_TIME_END(t2, "CalculateConvexHull() ");
Die Meldungen werden als Debug Nachricht bzw. als "Fehler Meldung" in die Konsole ausgegeben.
- Zu beachten
- Oftmals wird auch die Dauer des Logic-Ticks (
GetCurrentTickDuration()
) als Benchmark-Wert herangezogen. Dabei ist zu beachten, dass die Murl Engine standardmäßig die Zeitunterschiede zwischen Logic-Ticks "glättet" und das Limit für die maximale Dauer eines Logic-Ticks auf eine Sekunde eingestellt ist. Mit der MethodeIAppConfiguration::SetClockAveragingFactor()
kann der Glättungsfaktor geändert werden (1
bedeutet keine Glättung). Die Grenzen eines Logic-Ticks können mit der MethodeIEngineConfiguration::SetBoundsForLogicTickDuration()
angepasst werden.
Sortieren
Die Murl Engine bietet Methoden zum Sortieren von Daten in murl_util_sort.h
an.
Elementare Daten sortieren
Zum Sortieren eignen sich am besten Arrays
. Für Arrays mit elementaren Datentypen sind die Sortierfunktionen bereits implementiert (Util::SortArray
):
SInt32Array numbers; numbers.Add(45); numbers.Add(23); numbers.Add(67); numbers.Add(98); numbers.Add(12); Util::SortArray(numbers, true); for (UInt32 i = 0; i < numbers.GetCount(); i++) { System::Console::Print("Number %d has value %d\n", i, numbers[i]); }
Die vorgefertigten Sortierfunktionen sind für folgende Arrays verfügbar:
UInt64Array
, SInt64Array
, UInt32Array
, SInt32Array
, RealArray
, DoubleArray
und StringArray
StringArray names; names.Add("home"); names.Add("friends"); names.Add("all"); names.Add("go"); names.Add("now"); Util::SortArray(names, true); for (UInt32 i = 0; i < names.GetCount(); i++) { System::Console::Print("Name %d has value %s\n", i, names[i].Begin()); }
Benutzerdefinierte Objekte sortieren
Zum Sortieren von benutzerdefinierten Datenfeldern ist es notwendig eine Vergleichsfunktion zu implementieren.
Im nachfolgenden Beispiel implementieren wir eine Klasse welche einen Beleg mit einem Wert und zugehörigem Zeitstempel speichern kann.
Um ein Array mit solchen Beleg-Objekten zu sortieren, benötigen wir eine Vergleichsfunktion welche 2 Instanzen (source1
und source2
) der Klasse vergleichen kann.
Zu diesem Zweck implementieren wir eine Funktion welche die Zeiten vergleichen kann (CompareTime
) und eine weitere welche die Werte vergleicht (CompareValue
).
Eine Vergleichsfunktion muss folgende Rückgabewerte liefern:
Null
wenn source1
gleich source2
ist.
Negativ
wenn source1
kleiner source2
ist.
Positive
wenn source1
größer als source2
ist.
class MyReceipt { public: // Default Constructor MyReceipt() : mValue(0) { } MyReceipt(const System::Time& time, Double value) : mTime(time) , mValue(value) { } static SInt32 CompareTime(const MyReceipt* source1, const MyReceipt* source2) { if (source1->mTime > source2->mTime) { return 1; } if (source1->mTime < source2->mTime) { return -1; } return 0; } static SInt32 CompareValue(const MyReceipt* source1, const MyReceipt* source2) { if (source1->mValue > source2->mValue) { return 1; } if (source1->mValue < source2->mValue) { return -1; } return 0; } String GetDescription() const { System::DateTime date(mTime); String description = Util::DoubleToString(mValue, "% 8.2f"); description += " " + Util::UInt32ToString(date.mYear, "%4d"); description += "-" + Util::UInt32ToString(date.mMonth, "%02d"); description += "-" + Util::UInt32ToString(date.mDay, "%02d"); description += " " + Util::UInt32ToString(date.mHour, "%02d");; description += ":" + Util::UInt32ToString(date.mMinute, "%02d");; description += ":" + Util::UInt32ToString(date.mSecond, "%02d");; return description; } System::Time mTime; Double mValue; };
Belege erstellen:
Array<MyReceipt> myReceipts; System::Time time = System::Time::GetNow(); myReceipts.Add(MyReceipt(time, 100.5)); myReceipts.Add(MyReceipt(time + System::Time::FromSeconds(5000), 203.19)); myReceipts.Add(MyReceipt(time - System::Time::FromSeconds(4000), 1060.75)); myReceipts.Add(MyReceipt(time + System::Time::FromSeconds(2000), 2007.25)); myReceipts.Add(MyReceipt(time - System::Time::FromSeconds(8000), 412.34));
Belege nach Zeit sortieren:
Util::SortArray(myReceipts, MyReceipt::CompareTime); for (UInt32 i = 0; i < myReceipts.GetCount(); i++) { System::Console::Print("Receipt %d is %s\n", i, myReceipts[i].GetDescription().Begin()); }
Belege nach Wert sortieren:
Util::SortArray(myReceipts, MyReceipt::CompareValue); for (UInt32 i = 0; i < myReceipts.GetCount(); i++) { System::Console::Print("Receipt %d is %s\n", i, myReceipts[i].GetDescription().Begin()); }
- Zu beachten
- Tipp: Um eine Sortierung in umgekehrter Reihenfolge zu erreichen, sind einfach weitere Vergleichsfunktionen zu implementieren, welche die Rückgabewerte invertieren.
Weitere Sortiermethoden
Es können auch Datenfelder ohne Array
sortiert werden, siehe Util::QuickSort()
, Util::BubbleSort()
und Util::BinarySearch()
.
SInt32 compare (const SInt32 * a, const SInt32 * b) { return (*a - *b ); } void sort() { SInt32 a[] = {3,4,8,2,1}; Util::QuickSort(a, 5, compare); for (int i=0; i<5; i++) Debug::Trace(Util::SInt32ToString(a[i])); }
Eine vollständige Auflistung aller Sortiermethoden kann in der API-Referenz unter Sort Functions
bzw. direkt im Header murl_util_sort.h
eingesehen werden.
Mathematische Funktionen
Die Murl Engine bietet Funktionen und Konstanten zur Verarbeitung von Zahlen in murl_math.h
, murl_math_types.h
und murl_math_limits.h
an.
Funktionen
Im Header murl_math.h
stehen folgende mathematische Funktionen
zur Verfügung:
- Trigonometrische Funktionen
- Hyperbolische Funktionen
- Exponential- und logarithmische Funktionen
- Potenz Funktionen
- Rundungs- und diverse Funktionen
Konstanten
Im Header murl_math_types.h
stehen folgende Konstanten
zur Verfügung:
- Euler Zahl und Pi mit diversen Teilern.
- Umrechnungsfaktoren zwischen Grad und Radiant.
- Umrechnungsfaktoren zwischen Metrisch und Zoll.
Wertebereiche von Zahlen
Im Header murl_math_limits.h
stehen folgende Limit-Funktionen
zur Verfügung:
Math::Limits::Min()
Math::Limits::Max()
Math::Limits::NaN()
Math::Limits::Infinity()
Math::Limits::Epsilon()
Weitere Mathematik Klassen
Es gibt noch Klassen für Vektorrechnung, Matrizenrechnung, Quaternion und viele mehr. Eine vollständige Auflistung aller Mathematik Klassen kann in der Math API-Referenz
eingesehen werden.
Data Klasse
Einige Interfaces in der Murl Engine liefern oder verlangen Data
Objekte wenn z.B. eine Datei eingelesen wird oder Daten aus dem Netzwerk empfangen werden. Die Data
Klasse erleichtert den Umgang mit Rohdaten und ist im Header murl_data.h
definiert.
Der Einsatz von Data
Objekten vermeidet größtenteils den Einsatz von "unsicheren" Funktionen wie memcpy()
.
Beispiel:
Data myData("Hello World!"); System::Console::PrintHex(myData); myData.ResizeData(30); System::Console::PrintHex(myData); Data otherData("Spraylight"); myData += otherData; System::Console::PrintHex(myData); Data moreData("Murl"); myData.CopyDataFrom(moreData, 15); System::Console::PrintHex(myData);
Weitere Data Klassen und Methoden
Die Data
Klasse ist von MutableData
abgeleitet, welches wiederum von ConstData
abgeleitet ist. Diese Hierarchie ist nützlich, je nachdem woher die Daten bezogen wurden und wer im Besitz der eigentlichen Rohdaten ist.
Die Methode Util::StaticEmptyData()
liefert ein leeres Data Objekt.
Eine vollständige Auflistung aller Data
, MutableData
und ConstData
Methoden kann in der Data API-Referenz
bzw. direkt im Header murl_data.h
eingesehen werden.
Zeiger
In der Regel ist es bei der Implementierung von Applikationen mit Hilfe der Logik Klassen nur ganz selten notwendig mit Zeigern auf allokierte Objekte zu hantieren.
Wenn allokierte Objekte notwendig sind, dann gibt es folgende Klassen zur Untersützung:
AutoPointer
Die AutoPointer
Klasse ist in murl_auto_pointer.h
verfügbar.
Ein AutoPointer
übernimmt bis zu einem gewissen Grad das Speichermanagement für das Objekt, auf das er zeigt. Wird ein AutoPointer
zerstört (freigegeben), so sorgt der AutoPointer
automatisch dafür, dass auch der Speicher seines Objektes freigegeben wird.
Bei der Verwendung von AutoPointer
Klassen ist Vorsicht geboten, da bei Zuweisung von Zeigerobjekten der Besitz des Speichers mit übertragen wird, das heißt nach der Zuweisung ist der Quellzeiger plötzlich null!
Bei der Arbeit mit AutoPointer
gilt die goldene Regel: Es kann nur einen geben! ;-)
SharedPointer / WeakPointer
Aus den schon eingangs erwähnten Kompatibilitätsgründen wird bei der Murl Engine auch auf die Verwendung der C++ Bibliothek Boost verzichtet.
Das Framework stellt aber die beiden Klassen SharedPointer
und WeakPointer
zur Verfügung. Die Verwendung ist genau gleich wie unter Boost.
Die SharedPointer
Klasse ist in murl_shared_pointer.h
verfügbar.
Die WeakPointer
Klasse ist in murl_weak_pointer.h
verfügbar.
Ein SharedPointer
gibt wie der AutoPointer
automatisch den Speicher seines referenzierten Objekts frei, sobald es nicht mehr benötigt wird. Es können aber, im Gegensatz zum AutoPointer
, mehrere SharedPointer
auf das gleiche Objekt zeigen. Dafür wird jedem Objekt ein eigener Referenzzähler zugeordnet. Bei einer Zeigerzuweisung wird dieser Zähler erhöht, bei einer Zeigerdestruktion wird der Zähler erniedrigt. Wenn der Zähler 0 erreicht, wird das Objekt gelöscht.
Ein WeakPointer
ist ein Zeiger auf ein SharedPointer
Objekt, der das Freigeben seines Objekts aber nicht verhindert. Der Referenzzähler wird also für den WeakPointer nicht erhöht. Bei Bedarf kann man sich von einem WeakPointer
einen SharedPointer
geben lassen. Wurde das Objekt bereits vorher freigegeben, ist der erhaltene SharedPointer
0. Existiert das Objekt noch, wird der Referenzzähler erhöht und der erhaltene SharedPointer
zeigt auf das Objekt.
Anwendungsbeispiele sind bitte direkt der Boost Dokumentation zu entnehmen (www.boost.org/doc).
Hilfsfunktionen
Nachfolgend werden einige nützliche Hilfsfunktionen aufgelistet. Eine vollständige Auflistung aller Funktionen findet sich in der API-Referenz in den Abschnitten Murl::Util Functions, Murl::Math Functions und Murl::System::CLib Functions.
Util & Math Funktionen
Viele nützliche Hilfsfunktionen finden sich in Form von Templates in murl_util.h
und murl_math.h
, wie z.B.:
Math::Abs()
Math::Max()
Math::Min()
Math::Clamp()
Math::IsEqual()
Util::RoundToNextPowerOfTwo()
Util::IsPowerOfTwo()
etc.
System::CLib Hilfsfunktionen
Direkte Forwards in die C-Library finden sich in murl_system_clib.h
, wie z.B.:
System::CLib::PrintToString()
System::CLib::ScanString()
System::CLib::StrCmp()
System::CLib::StrLen()
etc.
Speichermanipulation
Wenn die Notwendigkeit direkter Speichermanipulation besteht, dann stehen auch folgende Funktionen zur Verfügung:
Util Speichermanipulation in murl_util.h
:
Util::MemClear()
Util::MemSet()
Util::MemCopy()
Util::MemCopyArray()
Util::MemMove()
Util::MemCompare()
Util::Fill()
Util::FillArray()
System::CLib Speichermanipulation in murl_system_clib.h
(direkte Forwards in die C-Library):
System::CLib::MemSet()
System::CLib::MemCopy()
System::CLib::MemMove()
System::CLib::MemCompare()