Variablen-Typen und Typbindung der Variablen
Unterstützte Variablen-Typen
Xbase++ kennt in den Speichervariablen keine Typ-Bindung, d.h. einer Variablen kann jeder Wert, unabhängig vom Typ, zugewiesen werden.
Felder in Dateien hingegen sind eindeutig typgebunden, und die Übertragung eines nicht kompatiblen Wertes führt beim Schreiben in die Datei zu einem Laufzeitfehler.
Xbase++ unterstützt die folgenden Variablen-Typen:
ARRAY
Ein Array ist eine Gruppierung von Variablen, die unterschiedliche Typen haben können, die aber über einen gemeinsamen Namen angesprochen werden:
nCount := fCount() aRec := Array(nCount) FOR nI := 1 TO nCount aRec[nI] := FieldGet(nI) NEXT
In diesem Beispiel wird ein Array verwendet, um die Felder eines Datensatzes aufzunehmen. Zu diesem Zweck wird die Feldanzahl über die Funktion fCount() ermittelt und mittels der Funktion Array() ein leeres Array mit nCount Feldern erzeugt. Jedes Element des Arrays hat den Wert NIL. Danach wird in einer Schleife jedes Feld entsprechend seiner Position im Datensatz in das korrespondierende Array-Element übertragen.
Arrays können auch geschachtelt sein, d.h. ein Array-Element kann auch wieder ein weiteres Array enthalten.
Besonderheiten
Arrays werden, wenn sie als Parameter an eine Funktion übergeben werden, immer "per reference" und nicht "per value" übergeben. Das bedeutet, dass die aufgerufene Funktion den Inhalt des Arrays ändern kann.
BLOCK oder Codeblock
Ein Codeblock enthält zur Compile- oder Laufzeit erzeugten Code, der eigenständig im Programmkontext ausführbar ist.
bBlock := {|| Time()} ? Eval(bBlock)
Bei der Ausführung des Codeblocks über die Funktion Eval() wird die aktuelle Zeit des Computers ausgegeben.
Besonderheiten bei der Deklaration
Ein Codeblock ist quasi eine Art "eingefrorene" Funktion, die über Eval() aktiviert wird. Wie jede Funktion, kann auch die im Codeblock hinterlegte Funktion (oder Reihe von Funktionen) Parameter entgegennehmen.
Die Parameter stammen wahlweise aus der Eval()-Anweisung, aus der Funktion, in welcher der Codeblock definiert/erzeugt wird, oder aus beiden.
Parameter, die mittels Eval übergeben werden, müssen zwischen den beiden | Zeichen aufgelistet und so definiert werden. Ihnen wird ein symbolischer Name zugewiesen, der dem Compiler hilft, die Parameter in der richtigen Reihenfolge zu übergeben:
LOCAL oDlg ... oDlg := RootWindow() ... bBlock := {|aEins, aZwei, aDrei| MeineFunktion(aDrei, aZwei, oDlg, aEins)} RunMe(bBlock)
Wird in RunMe() bBlock mittels Eval ausgeführt:
Eval(bBlock, {"A"}, {"B"}, {"C"})
so erhält die Funktion MeineFunktion() folgende Parameter:
{"C"} {"B"} (Rückgabewert der Funktion RootWindow()) {"A"}
Ausführung mehrerer Funktionen in einem Codeblock
Die meisten Beispiele zur Verwendung von Codeblocks legen nahe, dass nur eine Funktion innerhalb eines Codeblocks aufgerufen werden kann. Es besteht jedoch auch die Möglichkeit, mehrere Funktionen nacheinander aufzurufen:
bBlock := {|a, b, c| DoMeFirst(a), DoMeSecond(c), DoMeThird(b)}
In diesem Fall sind die Funktionsaufrufe durch ein Komma zu trennen.
Parameter des Codeblocks
Die zwischen den beiden |-Zeichen angegebenen Werte sind die Parameter, die durch die Eval()-Anweisung an den Codeblock übergeben werden. Sie werden als LOCAL erzeugt und haben die entsprechende Sichtbarkeit. Die Codeblock-Parameter erhalten den Vorzug gegenüber gleich benannten Variablen der Funktion, in welcher der Codeblock erzeugt wird.
Greift der Code des Codeblocks auf weitere Variablen zu, so müssen diese entweder zur Compiler-Zeit (als LOCAL/STATIC in der Funktion, in welcher der Codeblock erzeugt wird, bzw. als STATIC in der Programmquelle) oder zur Laufzeit (als PUBLIC oder PRIVATE Variablen) zur Verfügung stehen.
Erzeugung eines Codeblocks zur Compile- oder Laufzeit
Codeblocks können zur Compilezeit erzeugt werden, oder zur Laufzeit
Erzeugung zur Compilezeit
Die Erzeugung zur Compilzeit geschieht durch die bekannte Deklaration:
bBlock := {|p1, p2, p3| MacheIrgendwas(p1, p2, p3, cName)}
MacheIrgendwas() muss dem Linker bekannt sein. Daher kann es entweder eine STATIC Function in der gleichen Programmquelle, oder eine normale Funktion in einer beliebigen Programmquelle oder Bibliothek sein, die vom Linker verwendet wird.
Existiert in der Funktion, in welcher der Codeblock erzeugt wird, eine LOCAL Variable cName, wird diese referenziert. Ist dies nicht der Fall, existiert aber eine STATIC Variable cName in der Programmquelle, wird diese referenziert. Trifft keiner der beiden Fälle zu, wird zur Laufzeit auf eine vorhandene PUBLIC oder PRIVATE Variable cName zugegriffen. Existiert dann keine solche Variable, kommt es zu einem Laufzeitfehler.
Erzeugung zur Laufzeit
Eine der grossen Stärken der xBase-Sprache seit Clipper 5 ist die Tatsache, dass Codeblocks auch zur Laufzeit erzeugt werden können.
cBlock := "{|p1, p2, p3| MacheIrgendwas(p1, p2, p3, cName)}" bBlock := &cBlock
Im Gegensatz zu Compilezeit erzeugten Codeblocks muss die referenzierte Funktion bekannt sein, es kann also keine STATIC Function verwendet werden, da STATIC Functions zur Laufzeit nur über eine Referenz, aber nicht über ihren Namen angesprochen werden können.
Die Behandlung von cName ist ebenfalls anders. Da zur Laufzeit weder LOCAL noch STATIC über ihren Namen verfügbar sind, muss beim Ausführen von bBlock eine PUBLIC oder PRIVATE Variable cName existieren. Ist dies nicht der Fall, wird bei Ausführen des Codeblocks ein Laufzeitfehler erzeugt.
Focus von in Codeblocks verwendeten Variablen
Unterstellen wir einen Loop dieser Art im Rahmen der Erzeugung eines Browses:
nLen := Quelle->(fCount()) FOR nI := 1 TO nLen ... oCol:dataLink := {|| Quelle->(FieldGet(nI))} ... NEXT
Bei der Ausführung des Programms werden wir eine Überraschung erleben. Beim Versuch, den :dataLink auszuführen, wird ein Fehler erzeugt, weil auf ein nicht existierendes Feld zugegriffen werden soll.
Unterstellen wir, dass die Datei Quelle drei Felder hat. Dann erzeugt die Schleife drei Spaltenobjekte:
oCol(1):dataLink => {|| Quelle->(FieldGet(nI))}
oCol(2):dataLink => {|| Quelle->(FieldGet(nI))}
oCol(3):dataLink => {|| Quelle->(FieldGet(nI))}
Nach dem dritten Durchlauf wird nI auf den Wert 4 geändert und damit der Loop verlassen. Wenn nI jetzt nicht mehr verändert wird, hat die Variable beim Anzeigen des Browse den Wert 4.
Wird der :dataLink des ersten Spaltenobjektes ausgeführt, steht folgender Code an:
Quelle->(FieldGet(4))
weil der Codeblock auf die Variable zurückgreift und nicht auf den Inhalt, den die Variable hatte, als der Codeblock erzeugt wurde. Der Inhalt der Variablen wird erst dann ausgewertet, wenn der Codeblock ausgeführt wird.
Um den gewünschten Effekt zu erzielen, muss mit Detached Locals gearbeitet werden.
CHAR oder Zeichen/String
Hierunter fallen alle alpha- und numerischen Zeichen incl. binärer Werte.
Wenn ein Memo-Feld aus einer Datei einer Speichervariablen zugewiesen wird, hat die Speichervariable den Feldtyp CHAR.
cName := "Felix Hugendabler" cName := Str(27)
DATE oder Datum
Xbase++ unterstützt Datumswerte im Bereich von " . . " (= leer), "01.01.0001" bis "31.12.9999". Datumsfelder können auch in arithmetischen Operationen verwendet werden:
dDate := Date() // weist das aktuelle Datum zu dGestern := dDate - 1 // ermittelt das Datum des Vortages nDauer := dDate - dGestern // ermittelt die Tage zwischen dDate und dGestern
LOGICAL oder logisch
Logische Werte kennen .T. (WAHR) und .F. (FALSCH).
lErgebnis := .T. lErgebnis := 1 > 3
NUM oder Zahlen
Darstellbar am Bildschirm sind Zahlen im Bereich von 16 Vor- und 15 Nachkommastellen, also insgesamt maximal 31 Ziffern. Intern operiert Xbase++ mit IEEE 64 bit format Zahlen.
Zuweisungen können dezimal, hexadezimal oder wissenschaftlich erfolgen, wobei zu berücksichtigen ist, dass für dezimale Werte ein Dezimalpunkt zu verwenden ist:
nWert := -176.33 nWert := 0xAD nWert := 10.1E-5
OBJECT oder Objekt
Ein Objekt ist ein spezieller Datentyp, der als eine Art Container wiederum Variablen und Programmcode enthalten kann. Variablen, die sich auf Objekte beziehen, enthalten immer nur eine Referenz auf das Objekt, nicht jedoch das Objekt selbst.
oXbp := XbpDialog():new()
NIL
Deklarierte, aber nicht initialisierte Variablen haben im Normalfall den Wert NIL.
Übersicht über die Feldtypen, Grössen und Wertebereiche
Typ | Länge von bis | Wertebereich |
---|---|---|
ARRAY | (nicht explizit) | (beliebig) |
BLOCK | (nicht explizit) | n/a |
CHAR | 1 bis 32767 Bytes | alphanumerisch incl. binär |
DATE | 8 bzw. 10 Bytes | leer und gültige Datumswerte von 1.1.1 bis 31.12.9999 |
LOGIC | 1 Byte | WAHR und FALSCH |
NUMERIC | 1 bis 16.15 | 10**-308 bis 10**308 |
OBJECT | (nicht explizit) | n/a |
Besonderheiten
Arrays
Während der Inhalt einer Variablen mittels der Zuweisung einer weiteren Variablen zugewiesen werden kann, ist dies bei Arrays nicht möglich.
dHeute := Date() dDatum := dHeute dHeute := NIL ? dDatum
Hier wird als Ergebnis das Tagesdatum ausgegeben.
aData := Array(1) aData[1] := Date() aWerte := aData aWerte[1] := NIL ? aData
Hier wird als Ergebnis {NIL} ausgegeben.
Die Zuweisung aWerte := aData kopiert nicht das Array, sondern die Referenz auf das Array, so dass beide Variablen auf das gleiche Array zeigen. Die Zuweisung von NIL an ein Element von aWerte betrifft das Array, auf das auch aData Bezug nimmt, ebenso. Beide Variablen verweisen auf das gleiche Array, so dass Änderungen sich in beiden Variablen widerspiegeln.
Um Arrays zu kopieren, muss die Funktion AClone() verwendet werden:
aData := Array(1) aData[1] := Date() aWerte := AClone(aData) aWerte := NIL ? aData
Diesmal wird als Ergebnis {(Tagesdatum)} ausgegeben.
Objekte
Objekte betrachtet man am besten als eine Art Container. Dieser Container kann Felder (Instanzvariablen) und Programm-Code (Methoden) enthalten, sowie in den Instanzvariablen weitere Objekte.
Eine Variable enthält eine Referenz auf ein Objekt, und nicht das Objekt selbst.
Diese Tatsache ist gerade im Umgang mit Xbase-Parts im Anfang etwas verwirrend:
oDlg := XbpDialog():new() ... oDlg:create() oXbp := XbpSLE():new(oDlg:drawingArea, oDlg:drawingArea, aPos, aSize) ... oXbp:create() oXbp := XbpPushButton():new(oDlg:drawingArea, oDlg:drawingArea, aPos, aSize) ... oXbp:create()
Dieser Code erzeugt ein XbpDialog() Fenster. Im nächsten Schritt wird der Variablen oXbp ein neues Objekt der Klasse XbpSLE() zugewiesen, es werden bestimmte Definitionen durchgeführt, und dann wird das Objekt durch Anforderung der Systemressourcen fertiggestellt.
Das Objekt, auf das oXbp verweist, existiert als Child von oDlg, also in dessen Kontext. Wenn nun im nächsten Schritt der gleichen (!) Variablen ein XbpPushButton() Objekt zugewiesen wird, so wird dadurch das XbpSLE() Objekt nicht zerstört! Es wird lediglich die Referenz auf ein Objekt durch die Referenz auf ein anderes Objekt ersetzt.
Als Programmierer haben wir bei dieser Art der Programmierung keinen direkten Zugriff mehr auf das XbpSLE() Objekt, das ist aber auch im Rahmen der Xbase++ Programmierung nicht mehr zwingend erforderlich, da wir später - wenn Ereignisse im Zusammenhang mit diesem Objekt zu bearbeiten sind - immer eine Referenz auf das Objekt erhalten.
Um ein Objekt zu zerstören, ist die Ausführung der Methode destroy() (sofern definiert) erforderlich. Die Überreste werden dann irgendwann vom Garbage Collector entsorgt.