Tutorial #09: Container & Basics

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.

#include <stdio.h>
#include <vector>

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

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

STL string → String
STL vector → Array
STL (unordered) set/multiset → Index (ähnlich)
STL map/multimap → Map

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:

Array von murl_array.h (NTL Vector)
Index von murl_index.h (NTL Index)
Map von murl_map.h (NTL VectorMap)

Große Strukturen oder Objekte sollten nur in folgenden Objekt-Containern verwendet werden:

ObjectArray von murl_object_array.h (NTL Array)
ObjectIndex von murl_object_index.h (NTL ArrayIndex)
ObjectMap von murl_object_map.h (NTL ArrayMap)

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.

tut0109_container.png
Container

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:

Murl::App::ContainerLogic::PrintDemo(), line 63: Debug Hello World

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:

Debug Hello World

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:

Murl::App::ContainerLogic::PrintDemo(), line 66: Error Hello World

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:

Error Hello World

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:

Hello World 42!

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:
String valString = "Value = ";
UInt32 val = 42;
Debug::Trace("%s %d", valString, val);
Debug::Trace("%s %d", valString.Begin(), val);
Liefert im Debug Mode unter Visual Studio folgendes Ergebnis (Xcode liefert korrekterweise eine Fehlermeldung):
Value = 8
Value = 42
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.

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:

Util::SortStringArray()

Zerlegen von Strings in StringArray oder StringIndex:

Util::SplitString()

Verketten von StringArray oder StringIndex:

Util::JoinStringArray()
Util::JoinStringIndex()

Trimmen von StringArray:

Util::TrimStringArray()

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());
Initial:
objRefA: 0 false 'Super Mario A'
objCopyA: 0 false 'Super Mario A'
objRefB: 0 false 'Super Mario B'
objCopyB: 0 false 'Super Mario B'

Alle Variablen liefern das selbe Ergebnis, zeigen aber auf ganz unterschiedliche Speicherbereiche:

tut0109_array_v1.png
Speicherbereiche nach der Initialisierung

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";
Changed Array Item 1:
objRefA: 0 false 'Giana Sisters A'
objCopyA: 0 false 'Super Mario A'
objRefB: 0 false 'Giana Sisters B'
objCopyB: 0 false 'Super Mario B'

Wie erwartet liefern objRefA und objRefB die geänderten Inhalte, während die Variablen objCopyA und objCopyB die Inhalte der kopierten Elemente liefern.

tut0109_array_v2.png
Speicherbereiche nach der Namensänderung

Als nächstes Löschen wir noch in beiden Arrays das Objekt an Position 0.

arrayNamesA.Remove(0);
arrayNamesB.Remove(0);
Removed Array Item 0:
objRefA: 0 false 'Lara Croft A'
objCopyA: 0 false 'Super Mario A'
objRefB: 0 false 'Giana Sisters B'
objCopyB: 0 false 'Super Mario B'

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.

tut0109_array_v3.png
Speicherbereiche nach dem Löschvorgang

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:

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.

Murl::App::BenchmarkLogic::Bench000(), line 100: CalculateDelaunayTriangulation() time: 7872.019000 ms
Murl::App::BenchmarkLogic::Bench000(), line 104: CalculateConvexHull() time: 5034.857000 ms
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 Methode IAppConfiguration::SetClockAveragingFactor() kann der Glättungsfaktor geändert werden (1 bedeutet keine Glättung). Die Grenzen eines Logic-Ticks können mit der Methode IEngineConfiguration::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:

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()


Copyright © 2011-2024 Spraylight GmbH.