Kapitel 7: Benutzerdefinierte Datentypen (UDTs)

Inhaltsverzeichnis

Kapitel 9: Pointer (Zeiger)

8. Datenfelder (Arrays)

Ein Array ist nichts anderes als eine Gruppe gleichartiger Variablen, die im Speicher direkt hintereinander liegen. Sie werden nicht über viele verschiedenen Namen angesprochen, sondern besitzen einen gemeinsamen Namen und unterscheiden sich durch einen Index.

8.1 Deklaration und Zugriff

Stellen Sie sich z. B. vor, Sie haben eine Liste mit den Nachnamen Ihrer Kunden. Jeden Namen in einer eigenen Variablen zu speichern, ist aus mehreren Gründen unpraktisch. Zum einen ist es umständlich, die Variablen zu verwalten, zum anderen ist das Konzept unflexibel, wenn neue Kunden hinzukommen. Stattdessen bietet sich ein Array an, das über einen einzigen Bezeichner angesprochen werden kann.

' STRING-Datenfeld fuer 5 Nachnamen anlegen
DIM AS STRING nachname(1 TO 5)

Die Syntax ist weitgehend aus den vorigen Kapiteln bekannt; sie ist fast identisch mit der Variablendeklaration. Neu ist die Angabe innerhalb der Klammern. Dadurch werden Speicherplätze für fünf String-Variablen angelegt, die von 1 bis 5 durchnummeriert sind. Ebenfalls fünf Speicherplätze, nun aber von 4 bis 8 nummeriert, würden durch folgende Zeile festgelegt werden:

DIM AS STRING nachname(4 TO 8)

In beiden Fällen spricht man von der Array-Länge 5. Die einzelnen Werte können nun über ihren Index angesprochen werden:

Quelltext 8.1: Array-Deklaration
' STRING-Datenfeld fuer 5 Nachnamen anlegen
DIM AS STRING nachname(1 TO 5)
' Array-Werte speichern
nachname(1) = "Huber"
nachname(2) = "Schmid"
' Array-Wert abrufen
PRINT nachname(2)
SLEEP

Zu Beginn, in Zeile 2, werden alle Speicherstellen auf einen leeren Wert gesetzt, also bei Strings auf einen Leerstring, bei Zahlendatentypen auf den Wert 0. Jeder Array-Stelle in einer neuen Zeile einen Wert zuzuweisen kann recht aufwendig werden. Stattdessen gibt es, analog zur Variablendeklaration, eine Kurzschreibweise, mit der direkt bei der Deklaration auch die Wertzuweisung erfolgt.

Quelltext 8.2: Array-Deklaration mit direkter Wertzuweisung
' STRING-Datenfeld fuer 5 Nachnamen anlegen
DIM AS STRING nachname(1 TO 5) = {"Huber", "Schmid", "Mayr", "Mueller", "Schuster"}
' Array-Wert abrufen
PRINT nachname(2)
SLEEP
Ausgabe:
Schmid

Die Liste muss mit geschweiften Klammern umschlossen werden und genauso viele Elemente enthalten wie das Array. Die einzelnen Werte werden durch Komma voneinander getrennt. Natürlich müssen die Elemente auch alle den richtigen Datentyp besitzen.

Warning Achtung:
Wird beim Arrayzugriff ein Index angegeben, der außerhalb der bei der Deklaration festgelegten Grenzen liegt, dann greift das Programm auf einen Speicherbereich zu, der nicht zum Array gehört. Dies führt zu unvorhersagbaren Ergebnissen und muss daher auf jeden Fall vermieden werden. Mehr dazu erfahren Sie in Kapitel 9.3.
Wenn Sie mit der Compileroption -exx compilieren, bricht das Programm mit einer Fehlermeldung ab, sobald auf einen Arrayindex außerhalb der erlaubten Grenzen zugegriffen wird. Dadurch lassen sich falsche Speicherzugriffe deutlich leichter aufspüren.

8.2 Mehrdimensionale Arrays

Während die bisher behandelten eindimensionalen Arrays in etwa einer aneinandergereihten Folge von Datenwerten entsprechen, kann man sich ein zweidimensionales Array ähnlich wie ein Schachbrett vorstellen. Jedes Feld kann eindeutig über die Reihe und Spalte angesprochen werden, z. B. als a4. Der Unterschied besteht nur darin, dass in FreeBASIC keine Buchstaben-Indizes verwendet werden. Das Feld in der ersten Spalte der vierten Reihe könnte also z. B. über feld(1, 4) angesprochen werden.

Auch die Initialisierung, also eine Wertzuweisung direkt bei der Deklaration, ist möglich. Dazu müssen die einzelnen Dimensionen extra durch geschweifte Klammern eingeschlossen werden.

Quelltext 8.3: Mehrdimensionales Array
' 2x3-Integerfeld
DIM AS INTEGER array(1 TO 2, 1 TO 3) = { { 1, 2, 3 }, _
                                         { 4, 5, 6 } }
PRINT array(2, 2)
SLEEP
Ausgabe:
 5

Wie Sie sehen, besteht die Wertzuweisungen aus zwei Blöcken mit je drei Einträgen, entsprechend den Länge 2 in der ersten Dimension und der Länge 3 in der zweiten Dimension.
Arrays sind nicht auf zwei Dimensionen begrenzt. Es können bis zu acht Dimensionen genutzt werden.

Tip Für Fortgeschrittene:
Bei mehrdimensionalen Arrays folgen im Speicher die Werte aufeinander, deren erster Index gleich ist. FreeBASIC unterscheidet sich hier von QuickBASIC, welches die Werte aufeinander folgen lässt, deren letzter Index gleich ist.

8.3 Dynamische Arrays

FreeBASIC kennt statische und dynamische Arrays. Bei einem statischen Array werden die Anzahl der Dimensionen und die Länge einmal mit DIM festgelegt und können dann innerhalb seines Gültigkeitsbereichs nicht mehr verändert werden. Ein dynamisches Array dagegen erlaubt eine spätere Größenänderung. Das ist vor allem dann praktisch, wenn während der Laufzeit des Programmes immer wieder neue Werte zum Array hinzugefügt werden müssen und daher zu Beginn die benötigte Länge noch nicht feststeht.

8.3.1 Deklaration und Redimensionierung

Zur Deklaration eines dynamischen Arrays gibt es zwei Möglichkeiten: zum einen wie gewohnt mit DIM, jedoch ohne Angabe der Arraygrenzen (zur Unterscheidung von normalen Variablen müssen jedoch die Klammern angegeben werden). Zum anderen können Sie den Befehl REDIM verwenden. REDIM dient auch zur späteren Änderung der Arraygrenzen. In der ersten Version mit DIM ist das Array zunächst noch undimensioniert, muss also später vor der Verwendung noch mit REDIM auf seine Dimensionen festgelegt werden. Wird gleich zu Beginn REDIM verwendet, dann können sofort die gewünschten Array-Grenzen angegeben werden.

Tip Hinweis:
Wir unterscheiden hier die Deklaration und die Dimensionierung (= Zuweisung der Dimensionen) eines Arrays. Während ein statisches Array bei der Deklaration auch gleich dimensioniert wird und diese Dimensionierung nicht mehr verliert, kann bei einem dynamischen Array Deklaration und Dimensionierung getrennt voneinander stattfinden, und das Array kann später vergrößert oder verkleinert werden.

Dynamische Arrays können nicht gleich bei der Deklaration initialisiert werden; eine Wertzuweisung ist erst nach der Deklaration möglich.

Quelltext 8.4: Dynamische Arrays anlegen
' dynamische Arrays deklarieren
DIM AS INTEGER array1()           ' dynamisches Array mit DIM
REDIM AS INTEGER array2(1 TO 5)   ' dynamisches Array mit REDIM

' dynamische Arrays neu dimensionieren
REDIM array1(1 TO 10), array2(1 TO 10)

Auch bei der ersten Dimensionierung mit REDIM können die Grenzen zunächst weggelassen werden; in diesem Fall muss das Array, genauso wie bei der dynamischen Version von DIM, erst noch festgelegt werden, bevor auf das Array zugegriffen werden kann.

Bei einer Redimensionierung eines bereits deklarierten dynamischen Arrays braucht kein Datentyp mehr angegeben werden, da dieser bereits feststeht. Wird er trotzdem angegeben, muss er mit dem ursprünglichen Datentyp übereinstimmen. Auch die Anzahl der Dimensionen kann, wenn sie einmal festgelegt wurde, nicht mehr verändert werden — veränderbar sind nur die Array-Grenzen innerhalb der Dimensionen.

8.3.2 Werte-Erhalt beim Redimensionieren

Bei der Verwendung von REDIM werden die Array-Werte, genauso wie bei DIM, neu initialisiert, d. h. Zahlenwerte werden auf 0 gesetzt und Zeichenketten auf einen Leerstring. Sie können also davon ausgehen, dass Sie nach jedem REDIM wieder ein „frisches“ Array vor sich haben.

Wollen Sie allerdings die alten Werte behalten, benötigen Sie das Schlüsselwort PRESERVE. Nehmen wir an, Sie wollen eine Reihe von Messdaten in einem Array speichern und, falls der Platz nicht ausreicht, das Array vergrößern. In diesem Fall wäre ein Zurücksetzen der Array-Werte auf 0 nicht wünschenswert. PRESERVE weist das Programm an, bisherige Daten zu erhalten. Wird das Array vergrößert, werden an sein Ende leere Elemente angefügt (also mit dem Wert 0 oder Leerstring). Bei einer Verkleinerung werden am hinteren Ende Daten abgeschnitten und gehen damit verloren.

Quelltext 8.5: REDIM mit und ohne PRESERVE
REDIM AS INTEGER arr(1 TO 4)
arr(1) = 1 : arr(2) = 2 : arr(3) = 3 : arr(4) = 4
PRINT "Startbelegung des Arrays arr(1 TO 4) mit den Werten:"
PRINT arr(1), arr(2), arr(3), arr(4)
' Array vergroessern
REDIM PRESERVE arr(1 TO 6)
PRINT "REDIM aus arr(1 TO 6); Ausgabe der Werte arr(3), arr(4) und arr(5):"
PRINT arr(3), arr(4), arr(5)
' Grenzen verschieben
REDIM PRESERVE arr(3 TO 9)
PRINT "REDIM auf arr(3 TO 9); Ausgabe der Werte arr(3), arr(4) und arr(5):"
PRINT arr(3), arr(4), arr(5)
' Neudimensionierung ohne PRESERVE
REDIM arr(3 TO 9)
PRINT "REDIM ohne PRESERVE; Ausgabe der Werte arr(3), arr(4) und arr(5):"
PRINT arr(3), arr(4), arr(5)
SLEEP
Ausgabe:
Startbelegung des Arrays arr(1 TO 4) mit den Werten:
 1             2             3             4
REDIM aus arr(1 TO 6); Ausgabe der Werte arr(3), arr(4) und arr(5):
 3             4             0
REDIM auf arr(3 TO 9); Ausgabe der Werte arr(3), arr(4) und arr(5):
 1             2             3
REDIM ohne PRESERVE; Ausgabe der Werte arr(3), arr(4) und arr(5):
 0             0             0

Beachten Sie, dass beim Verschieben der Grenzen die Werte nicht ebenfalls verschoben werden. Zuvor hatte der dritte Eintrag den Wert 3. Nach der Anweisung REDIM PRESERVE arr(3 TO 9) ist jedoch arr(3) der erste Eintrag und arr(5) der dritte — dementsprechend kommt es bei der Ausgabe zu den Werten, die Sie oben sehen können.

Note Achtung:
Das Schlüsselwort PRESERVE kann nur bei eindimensionalen Arrays ohne Probleme verwendet werden, bei mehrdimensionalen Arrays bleibt wegen der Array-Speicherverwaltung nur der erste Index erhalten.

8.4 Weitere Befehle

8.4.1 Grenzen ermitteln: LBOUND() und UBOUND()

Mit den Funktionen LBOUND() (Lower BOUND) und UBOUND() (Upper BOUND) kann die untere bzw. obere Grenze eines Arrays ausgelesen werden. Der Name des Arrays, dessen Grenzen bestimmt werden sollen, wird als Argument übergeben; dabei fallen die zum Array gehörenden Klammern weg. Bei mehrdimensionalen Arrays kann die gewünschte Dimension als zweites Argument übergeben werden.

Interessant ist auch die Rückgabe, die erfolgt, wenn als zweiter Parameter der Wert 0 angegeben wird. Doch dazu sehen wir uns erst einmal den Quellcode an:

Quelltext 8.6: Verwendung von LBOUND und UBOUND
DIM AS INTEGER a(1 TO 5), b(0 TO 9, 4 TO 7)
PRINT "Grenzen von a():", LBOUND(a), UBOUND(a)
PRINT "1. Dim. von b():", LBOUND(b, 1), UBOUND(b, 1)
PRINT "2. Dim. von b():", LBOUND(b, 2), UBOUND(b, 2)
PRINT "LBOUND(b)    = ";  LBOUND(b), "UBOUND(b)    = "; UBOUND(b)
PRINT
PRINT "LBOUND(a, 0) = "; LBOUND(a, 0), "UBOUND(a, 0) = "; UBOUND(a, 0)
PRINT "LBOUND(b, 0) = "; LBOUND(b, 0), "UBOUND(b, 0) = "; UBOUND(b, 0)
SLEEP
Ausgabe:
Grenzen von a():             1             5
1. Dim. von b():             0             9
2. Dim. von b():             4             7
LBOUND(b)    =  0           UBOUND(b)    =  9

LBOUND(a, 0) =  1           UBOUND(a, 0) =  1
LBOUND(b, 0) =  1           UBOUND(b, 0) =  2

Die ersten drei Ausgabezeilen sind selbsterklärend. In der vierten Zeile fällt auf, dass LBOUND() und UBOUND() ohne zweiten Parameter offenbar die Grenzen in der ersten Dimension zurückgeben — ein ausgelassener Parameter wird also als Wert 1 behandelt. Dieses Verhalten ist auch sinnvoll, da bei eindimensionalen Arrays logischerweise die 1. Dimension interessant ist.

Eine 0 als zweiter Parameter macht dagegen etwas ganz anderes: hier wird die Anzahl der Dimensionen zurückgegeben. LBOUND(array, 0) gibt immer 1 zurück, weil das immer „die untere Dimensionenzahl“ ist. UBOUND(array, 0) liefert bei eindimensionalen Arrays den Wert 1, bei zweidimensionalen den Wert 2 usw. Ist das Array noch nicht dimensioniert (beim Deklarieren dynamischer Arrays durch DIM ohne Angabe der Grenzen), dann gibt UBOUND(array, 0) den Wert 0 zurück.

Wenn Sie die Länge einer Dimension abfragen, die überhaupt nicht existiert (wenn Sie also z. B. LBOUND(array, 4) abfragen, obwohl array() weniger als vier Dimensionen besitzt), gibt LBOUND() 0 und UBOUND() -1 zurück. Auch dieses Verhalten kann dazu genutzt werden, um zu überprüfen, ob ein Array bereits dimensioniert ist.

8.4.2 Nur die Dimensionenzahl festlegen: ANY

Bei dynamischen Arrays kann es vorkommen, dass Sie zwar noch keine konkrete Dimensionierung vornehmen können, aber bereits festschreiben wollen, wie viele Dimensionen es geben soll. Geben Sie dann statt der Grenzen einfach das Schlüsselwort ANY an. Bei mehrdimensionalen Arrays ist allerdings keine Mischung zwischen ANY und Arraygrenzen erlaubt — entweder wird für jede Dimension ANY angegeben oder für keine.

DIM   AS STRING array1(ANY, ANY)      ' dynamisches zweidimensionales Array
REDIM AS STRING array2(ANY, ANY)      ' alternative Schreibweise zur 1. Zeile
REDIM AS STRING array3(ANY, ANY, ANY) ' dynamisches dreidimensionales Array
' REDIM AS STRING array4(1 TO 4, ANY) ' nicht erlaubt!

Beachten Sie, dass in allen drei Fällen die Arrays noch nicht dimensioniert wurden. UBOUND(array1, 0) wird daher 0 ausgeben. Die Deklarationen verhindern lediglich, dass später eine Dimensionierung mit einer anderen als der vorgesehenen Dimensionenzahl stattfindet.

8.4.3 Implizite Grenzen

In der Regel werden von den Programmierern Arrays bevorzugt, die mit dem Index 0 beginnen. Daher kann bei der Dimensionierung die Angabe des Startindex auch entfallen, wenn 0 als Startindex verwendet werden soll.

DIM AS INTEGER array(5)
' ist identisch mit: DIM AS INTEGER array(0 TO 5)

Auf diese Weise wird ein Array mit sechs Elementen erzeugt. Auch die Angabe der oberen Grenze kann in einem besonderen Fall offen gelassen werden, nämlich wenn bei der Dimensionierung gleichzeitig Werte zugewiesen werden. Die obere Grenze wird dann durch drei Punkte, auch Ellipsis (Auslassung) genannt, ersetzt. Die Ellipsis verhindert, dass bei einer Quelltextänderung durch Hinzufügen weiterer Initialwerte zwei verschiedene Stellen angepasst werden müssen.

Quelltext 8.7: Implizite obere Grenze bei Arrays
DIM AS INTEGER a(0 TO ...) = { 1, 2, 3, 4, 5 }
DIM AS INTEGER b(...)      = { 1, 2, 3, 4, 5 }

Beide Codezeilen definieren ein Array mit unterer Grenze 0 und oberer Grenze 4.

Die Ellipsis kann auch bei mehrdimensionalen Arrays angewendet werden; jede obere Grenze kann durch drei Punkte ersetzt werden. Wichtig ist nur, dass bei der Dimensionierung auch gleich die Wertzuweisung erfolgt, da ja die Anzahl der Werte entscheidend für die Länge des Arrays ist. Außerdem funktioniert das Vorgehen nur bei statischen Arrays — einem dynamischen Array können, wie oben bereits erwähnt, bei der Dimensionierung keine Startwerte übergeben werden.

Tip Unterschiede zu QuickBASIC:
In QuickBASIC kann mit OPTION BASE die standardmäßig verwendete untere Grenze zwischen 0 und 1 umgestellt werden. In FreeBASIC steht OPTION BASE nicht zur Verfügung. Wenn Sie eine andere untere Grenze als 0 verwenden wollen, müssen Sie sie explizit angeben.

8.4.4 Löschen mit ERASE

Arrays können auch wieder gelöscht werden, wobei unter dem Löschen eines statischen Arrays etwas anderes verstanden wird als unter dem Löschen eines dynamischen Arrays. Mit ERASE werden die Werte eines statischen Arrays auf den leeren Wert zurückgesetzt (also auf 0 bei Zahlenwerten und auf einen Leerstring bei Zeichenketten). Ein dynamisches Array wird dagegen tatsächlich aus dem Speicher gelöscht und anschließend als uninitialisiertes Array behandelt. Mit LBOUND() und UBOUND() lässt sich das leicht veranschaulichen.

Auch bei ERASE werden keine Array-Klammern angegeben. Um mehrere Arrays gleichzeitig zu löschen, listen Sie diese durch Komma getrennt hintereinander auf.

Quelltext 8.8: Arrays löschen
DIM   AS INTEGER a(9)  ' statisches  Array a
REDIM AS INTEGER b(9)  ' dynamisches Array b
a(0) = 1338            ' Testwert zuweisen

PRINT "vor dem ERASE:"
PRINT "Grenzen von a:", LBOUND(a), UBOUND(a)
PRINT "Grenzen von b:", LBOUND(b), UBOUND(b)
PRINT "a(0) besitzt den Wert"; a(0)
PRINT
ERASE a, b
PRINT "nach dem ERASE:"
PRINT "Grenzen von a:", LBOUND(a), UBOUND(a)
PRINT "Grenzen von b:", LBOUND(b), UBOUND(b)
PRINT "a(0) besitzt den Wert"; a(0)
SLEEP
Ausgabe:
vor dem ERASE:
Grenzen von a:               0             9
Grenzen von b:               0             9
a(0) besitzt den Wert 1338

nach dem ERASE:
Grenzen von a:               0             9
Grenzen von b:               0            -1
a(0) besitzt den Wert 0

Die Länge des statischen Arrays bleibt erhalten, es ist also nach wie vor im Speicher vorhanden, nur seine Werte werden zurückgesetzt. Anhand der Ausgabe zum zweiten Array können Sie dagegen sehen, dass hier seine Dimensionierung nicht mehr existiert und das Array gelöscht wurde. Nichtsdestotrotz — wenn Sie das Array neu dimensionieren wollen, muss es sowohl im Datentyp als auch in der Anzahl der Dimensionen mit der ursprünglichen Deklaration übereinstimmen.

Warning Achtung:
Wird ein statisches Array als Parameter an eine Prozedur übergeben, so wird es dort als dynamisches Array angesehen. Die Verwendung von ERASE führt dann zu einem Speicherzugriffsfehler.

8.5 Fragen zum Kapitel

  1. Wie wird ein statisches Array deklariert, und welche Unterschiede bestehen zur Deklaration einer Variablen?

  2. Wie wird ein dynamisches Array deklariert?

  3. Was sind die Unterschiede zwischen statischen und dynamischen Arrays?

  4. Wie viele Dimensionen kann ein Array maximal haben?

  5. Ein dynamisches Array wurde im Laufe des Programmes mit Werten gefüllt und soll nun vergrößert werden. Worauf ist zu achten?

  6. Wie kann bei einem dynamischen Array festgestellt werden, ob es dimensioniert wurde?


Kapitel 7: Benutzerdefinierte Datentypen (UDTs)

Inhaltsverzeichnis

Kapitel 9: Pointer (Zeiger)