Skyrim:Array

Aus Skript-Wiki
Wechseln zu: Navigation, Suche

Beschreibung

Der Begriff Array wird von Programmierern häufiger benutzt als die deutsche Bezeichnung ‚Feld‘. Deshalb wird im Weiteren dieser Begriff verwendet. Arrays sind eine besondere Art von Variablen, die mehr als einen Wert vom selben Typ aufnehmen können. Man wählt mit einem numerischen Index aus einem Bereich von Null bis zur Länge des Arrays -1, welchen Wert man möchte.


Für Anfänger, die gerne ein weitergehendes Verständnis über Arrays erlangen möchten

Obwohl die kurze Beschreibung als eingeschränkte Definition genutzt werden kann, was ein Array ist, geht das Konzept eines Arrays ein wenig tiefer, ist aber sehr einfach zu begreifen. Viele Leute verwechseln einen „Array-Datentyp“ (oder eine ‚besondere Art einer Variablen‘) mit einer „Array-Datenstruktur“. Die meisten modernen Sprachen implementieren letzteres, nicht wegen der Performanz sondern meisten wegen der grundlegenden Idee, von der es stammt. Zur Erläuterung sagen wir einmal, man möchte eine Menge von 10 Waffen haben. Man könnte dann so vorgehen: „Waffe1, Waffe2 ... Waffe10“ . Ermüdend, nicht wahr? Man stelle sich vor, alle einzelnen Variablen durchzugehen und zu prüfen, ob sie oder ob sie nicht die Bedingungen einer Abfrage genügen. Das ist doch grässlich, oder? Nun, wo man einen üblen Geschmack im Mund bekommen hat, lasst uns erklären, wie dies gelöst werden kann. Beachte, dass sie alle Variablen von „Waffe“ sind und sich nur durch ihren Inhalt (einen Wert irgendeiner Art) unterscheiden und durch die Nummer, die festlegt, welche Waffe welche ist. Nun, lasst uns das zu etwas Nützlichem verbinden. Wir möchten ein Array, eine Menge von Variablen eines bestimmten Datentyps und wir möchten ein Verfahren, um sie zu indexieren.


Eine ziemlich einfache formale Darstellung

<identifier> <index_or_count>

Das ist das Wesentliche, oder? Eine Sammlung, eine Struktur von Daten des selben Typs. Eine Array-Datenstruktur. Betrachte eine Entwicklung der der obigen Syntax, C-Stil: : Waffe waffe 10 -> Waffe waffe [10];. Nun, wo der Index/Zähler vom Identifikator getrennt ist, kann man auf jede Waffe im Array mit dem Index zugreifen: weapon[i]. Das sieht nicht wirklich nach einem Unterschied aus. Was ist denn der Vorteil dieses Ansatzes? Nun kann man verschiedene in der Sprache implementierte Schleifen-Kontrollstrukturen (for, while, do-while loop usw.) nutzen, um iterativ über alle möglichen Indices zu gehen, um zu vergleichen, zu ändern usw., was mit dem originalen Ansatz nicht möglich ist, weil man Identifikationen durch Verbindung des beschreibenden Namens mit dem Änderungsindex zur Laufzeit konstruieren musste.


Gut, dies und die gemachte Unordnung. Nicht zu erwähnen andere Probleme, wie der Mangel an virtueller Speicheradress-Nachbarschaft (die ich erklären werde). Man erkennt, jede Variable, Funktion, Datenstruktur und vieles mehr, auf das man in einer Programmiersprache zeigen kann, muss irgendwie adressiert werden. Man kann dem Computer nicht sagen: „Du, gib mir den Wert dieser Variablen!“ Der Computer erkennt keine Variablen. Variablen verschiedener Datentypen sind nur menschliche Vorstellungen und Interpretationen der Einsen und Nullen, die durch die Adern der Maschine laufen. Für diese Einsen und Nullen (Bits -> binary digits, binäre Zahlen) wurde entschieden, sie in Einheiten von acht Bits, genannt Bytes zu zerlegen. Ein Byte ist die kleinste adressierbare Einheit im Speicher eines Computers und dies ist es, in dass jede Variable (ja, auch Nichtpointertypen ) aufgelöst wird, um einen Wert zu lesen oder zu schreiben, nämlich die Speicheradresse des ersten Bytes der Variablen. Durch Anweisung des Computers, wie groß der Datentyp ist (üblicherweise 4 Bytes) oder wie genau, in Bits, kann er den Platz vergrößern und kurz gesagt eindrucksvolle Dinge tun.


Arrays sind speichertechnisch nur ein Haufen von Bytes, die zusammen gestapelt im Speicher zusammen liegen (im virtuellen Speicher, d.h. physikalischer Speicher kann in Unordnung kommen). Von nun an werde ich es einfach Speicher nennen. Zur Erinnerung: die physikalische Aufbau des Speichers nicht immer kontinuierlich ist. Wenn das System den Datentyp der Variablen, den Namen oder Indentifikator und den gewünschten Index kennt, kann es die exakte Speicheradresse des ersten Bytes des gewünschten Elements berechnen. Wie? Nun, wir haben gesagt, dass das Byte die kleinste adressierbare Einheit ist. Es hat eine Adresse. Genauso wie man in der Irgendwostraße 13a in Berlin leben könnte, bekommt ein Byte Speicherplatz eine Nummer angehängt, die der Computer dazu nutzt, es zu verfolgen. Und Speicher ist sequentiell. Um es einfach und bildlich auszudrücken, es ist eine lange Straße mit Häusern, die Bytes genannt werden.


Ein Datentyp kann so einfach sein wie ein Byte ( Bereich (2^8)-1 -> 256 - 1 oder Bereich [0,255] (unsigned)). Beachte, ein Byte wird oft Char (Kurzform für Character, also Zeichen) genannt, weil unser symbolischen System innerhalb der Grenzen von 256 Einträgen (zumindest ein einfaches System. Es gibt viele Möglichkeiten zur internen Repräsentation von Text auf dem Bildschirm). Ein Integer kann eine Genauigkeit auf dem Niveau von 32 Bits oder sogar 64 Bits haben. Das sind 4 Byte bzw. 8 Byte. Sagen wir mal, wir nutzen „klassische“ 32-Bit Integer und möchte das sechste Element eines Arrays mit 10 Elementen und wir wissen, dass der Identifikator des Arrays einArray. ist.


Zur Erinnerung, einArray zeigt auf die Speicheradresse des ersten Bytes des gesamten Array (mit fortgesetzten aneinander angrenzenden Elementen). Und ja, das erste Byte des gesamten Array ist ebenfalls das erste Byte des ersten Elements. Wir wissen, das jedes 32-Bit-Integer genau 4 Byte Speicherplatz belegt. Es sind 10 Elemente, also 10 mal 4 sind 40 Byte. Und nach simpler Logik 40 Speicherplatzadressen, die fortlaufend sind, jede mit einem Inkrement des vorigen. Wenn also jedes Element genau 4 Byte Speicherplatz belegt, welches ist das erste Byte des gewünschten sechsten Elements? Nun, 6 mal 4 ist 24. Wir wollen also das Byte 24, 25, 26 und 27 und die heutige Computerarchitektur kann es einfach über den Abstand (4 Byte), die Basisspeicheradresse (gewonnen über die Variable, die sie enthält) und dem Index des gewünschten Elements zugreifen.


Dies ist viel einfacher als einer zerstückelten „Gruppe“ von Variablen wie waffe1, waffe2, waffe3 usw. hinterher zu jagen, die sich an „verschiedenen Ecken“ der „Speicherstraße“ befinden könnte. Und das Finden einer Adresse der nächsten Variablen ist viel einfacher, wenn man den Abstand zu der Speicheradresse (das erste Byte des Arrays) addiert, als wenn man die Speicheradresse einer Variable für einen Wert ermittelt und dann und dann den Adressen anderer „Elemente“ in der Nähe von Hand verfolgt. Warum? Nun, wenn man z.B. versucht, der Adresse von waffe1 4 hinzu zu fügen (in der Hoffnung, dann auf die Speicheradresse, das erste Byte, von waffe2 zugreifen zu können), könnte man versehentlich auf eine Speicheradresse „sichern“, die eigentlich als erstes und einziges Byte einer Variablen vom Typ Char „gedacht“ ist und das System es dann als Integer anpasst. Und damit auch die 3 Bytes weiter voran in der Speichergasse, die zu einen vollständig anderen Variablen „gehören“ könnten. Der Versuch, diese Variable zu lesen, würde Kauderwelsch ergeben, und das Ändern würde den Speicherplatz der Applikation unumkehrbar beschädigen, was undefinierte Konsequenzen zur Folge hätte, wie z.B. eine Crash. Nun sollte man erkennen, warum darauf hingewiesen wurde, dass der Computer keine Ahnung von kümmerlichen menschlichen Datentyp-Plänen hat. Er tut, was man ihm sagt, unabhängig davon, ob es Sinn macht oder nicht.


Was wir hier gesehen haben, sind nur ein paar sehr grundlegende grobe Vorstellungen hinter dem Konzept von Arrays, einschließlich einigem der dahinter steckenden Logik, sowie eine sehr einfache Einführung in Datenspeicher im Allgemeinen. Dies ist ein sehr umfassendes Thema und der geneigte Leser wird ermuntert, das gerade erworbenen Wissen zu erweitern und vielleicht später zurück zu kehren und einige der hier verwendeten Vereinfachungen zu erkennen.


Deklaration von Arrays

<tesscript>

float[] myFloatArray
ObjectReference[] myObjectArray = new ObjectReference[10]

</tesscript>

myFloatArray spezifiziert ein leeres Array von Floats. Es hat die Länge 0 und ist gleich None. MyObjectArray beginnt ebenso, außer dass es ein Array von ObjectReference-Objekten ist. Dann wird ihm ein neues Array von ObjectReference-Objekten mit einem Inhalt von 10 Elemente (die alle mit dem voreingestellten Wert None belegt sind) zugewiesen. Beachte, der Aufruf von New ist nur innerhalb von Functions (einschließlich Events) möglich. Wenn man mit einer Variablen mit einem leeren Array beginnen möchte, kann man das Array innerhalb eines OnInit-Events machen.


Beachte, dass man kein Array von Arrays haben kann, ebenso wenig wie ein mehrdimensionales Array Man kann solche Anforderungen mittels mehrfacher While-Schleifen simulieren (das Beispiel in „Übliche Anwendungen“).


Man kann natürlich eine Property als Array definieren. Der Editor zeigt eine besondere Nutzerschnittstelle dafür an, die es ermöglicht, das Array innerhalb der Property hinzu zu fügen, zu löschen und umzuorganisieren.


Beispiel: <tesscript> Weapon[] Property MyWeapons Auto </tesscript>


Anmerkung: Man kann keine Variable oder einen Ausdruck nutzen, um die Länge eines Arrays zu spezifizieren. Es muss mit einem Integer Literal deklariert werden.


Defektes Beispiel: <tesscript>

 int NoOfItems = 10
 ObjectReference[] MyItems = new ObjectReference[NoOfItems] ; diese Zeile funktioniert nicht

</tesscript>


Funktionsparameter

Um ein Array als Eingabeparameter zu nutzen, wird die gleiche Syntax wie für die Deklaration eines verwendet.


Beispiel: <tesscript>

Function MyArrayFunction(int[] myArray, bool someOtherParameter)
EndFunction

</tesscript>


Rückgabe von einer Funktion

Zur Rückgabe eines Arrays von einer Funktion nutzt man wiederum die gleiche Syntax.


Beispiel: <tesscript>

int[] Function MyReturnArrayFunction()
  int[] someArray = new int[10]
  return someArray
endFunction

</tesscript>


Erzeugen von Arrays

Um ein Array zu erzeugen, nutzt man das „New“-Schlüsselwort, gefolgt vom Elementtyp des Arrays sowie der Größe des Arrays in eckigen Klammern. Die Array-Größe muss ein Integer zwischen 1 und 128 sein. Sie darf keine Variable sein. Mit anderen Worten muss die Array-Größe zur Compile-Zeit gesetzt sein. Jedes Element im Array wird auf den voreingestellten Wert des Elements gesetzt, ob das nun 0 ist, false, „“ oder None.


Beispiel: <tesscript>

new bool[5]
new Weapon[25]

</tesscript>


Normalerweise wird ihnen eine neue Array-Variable zugewiesen, wenn aber eine Funktion ein Array erwartet, kann man ein neues Array im Funktionsaufruf erzeugen.


Beispiel: <tesscript>

MyArrayFunction(new int[20])

</tesscript>


Erhalten und Setzen von Elementen

Um einen einzelnen Wert von einem Array zu erhalten oder ihn zu setzten nutzt man den Index des Elements, das man möchte, in eckigen Klammern nach dem Varablennamen. Der Index kann eine Integer-Variable sein, eine Integerzahl oder das Ergebnis eines Ausdruckes. Der Gültigkeitsbereich geht von 0 (das erste Element) bis zur Länge des Arrays minus 1.


Beispiel: <tesscript>

myArray[20] = newValue
someRandomValue = myArray[currentIndex]
myArray[i * 2] = newValue

</tesscript>


Wenn die Array-Element andere Scripte sind, kann man genauso auf Properties und Functions zugreifen.


Beispiel: <tesscript>

DoorArray[currentDoor].Lock()
objectXPos = ObjectArray[currentObject].X

</tesscript>


Beachte, dass alle Änderungen eines Array-Elements Auswirkungen auf alle Variablen haben, die auf das Array schauen, da Arrays „By Reference“ zugewiesen und übergeben werden.


Erhalten der Länge

Man kann die Länge jedes Arrays leicht erhalten, wenn man die Längen-Property darauf aufruft. Wenn man None einem Array zuweist, ist die Länge 0.


Beispiel: <tesscript>

int ArrayLength = myArray.Length

</tesscript>

Zuweisen und Übergeben von Arrays

Das Zuweisen eines Array an ein anderes oder das Übergeben eines Arrays an eine Function wird genauso wie bei anderen Variablen gemacht. Man muss jedoch beachten, dass die Zuweisung/Übergabe „By Reference“ geschieht, wie es auch mit Objekten gemacht wird. Mit anderen Worten, wenn man ein Array einem anderen zuweist, betrachten beide das selbe Array (d.h. beide Variablen greifen auf den selben Speicherplatz zu). Änderungen an einer Array-Variable werden deshalb in der anderen widergespiegelt.


Beispiel: <tesscript>

int[] Array1 = new int[5]
int[] Array2 = Array1     ; Array1 und Array2 betrachten nun das selbe Array!
Array1[0] = 10
Debug.Trace(Array2[0])    ; zeigt "10", obwohl Array1 modifiziert wurde

</tesscript>


Sobald ein Array nicht mehr referenziert ist, kümmert sich der Garbage-Collector darum (es wird zerstört)


Beispiel: <tesscript>

int[] Array1 = new int[5]
int[] Array2 = new int[10]
Array2 = Array1     ; Array1 und Array2 betrachten nun das selbe Array mit den 5 Elementen und das ursprüngliche Array mit den 10 Elementen von Array2 wird zerstört. 

</tesscript>


Casten von Arrays

Arrays können nur zu Strings oder Bools gecastet werden. Wenn man ein Array zu einem String castet, werden alle Elemente des Arrays einzeln in eckige Klammern gepackt und durch Kommata getrennt. Wenn das Array besonders lang ist, wird der String ein wenig früher abgeschnitten und mit Auslassungspunkten am Ende versehen. Wenn man eine Array zu einem Bool castet, wird es true, wenn die Länge ungleich Null ist und false, wenn die Länge Null ist. Man kann kein Array eines Types zu einem Array eines anderen Typen casten, selbst wenn die einzelnen Elemente erfolgreich gecastet werden könnten.


Beispiel: <tesscript>

Debug.Trace(MyArray)    ; Zeigt "[element1, element2, element3]" oder "[element1, element2, ...]" wenn das Array zu groß ist 
if (MyArray)
  Debug.Trace("Array hat mindestens ein Element!")
else
  Debug.Trace("Array hat kein Element!")")
endIf

</tesscript>


Übliche Anwendungen

Etwas mit jedem Element machen

Manchmal möchte man etwas mit jedem Element im Array machen. Das ist mit einer While-Schleife mit einem Zähler einfach zu erledigen, indem dann etwas mit jedem Element gemacht wird wie z.B. so:


Beispiel: <tesscript>

Function DisableAll(ObjectReference[] objects)
  int currentElement = 0
  while (currentElement < objects.Length)
    objects[currentElement].Disable()
    currentElement += 1
  endWhile
EndFunction

</tesscript>


Beachte, dass die While-Schleife läuft, solange „currentElement“ kleiner als die Länge des Arrays ist. Dies ist deshalb so, weil die Elemente von Null bis „Länge minus eins“ indexiert sind. Wenn man eine Schleife mit „<=“ macht, gibt es einen Fehler beim letzten Element, weil man dann versucht, auf ein Element zuzugreifen, was eins außerhalb des Endes des Arrays liegt.


Simulieren eines Mehrdimensionalen Arrays

Angenommen, man hat 4 Int-Property-Arrays mit Elementen 0-9, die jeweils den Wert 0-9 haben. Das folgende Beispiel gibt 0000 bis 9999 schrittweise auf dem Schirm aus.

<tesscript> Int Array1Element = 0 While (Array1Element < Array1.Length)

 Int Array2Element = 0
 While (Array2Element < Array2.Length)
   Int Array3Element = 0
   While (Array3Element < Array3.Length)
     Int Array4Element = 0
     While (Array4Element < Array4.Length)
       debug.notification(Array1[Array1Element] + Array2[Array2Element] + Array3[Array3Element] + Array4[Array4Element])
       Array4Element += 1
     endWhile
     Array3Element += 1
    endWhile
    Array2Element += 1
 endWhile
 Array1Element += 1

endWhile </tesscript>


FormLists

FormLists können genutzt werden, um ein Array von Propertys/Objekten für ein Script zu erzeugen.

FormList Script Beispiel: Einfaches Disablen/Enablen einer großen Anzahl von Objekten


Links