Grundlagen - Kapitel 6: Methoden, Schutzklassen & Variablen, die Zweite

  • Guten Tag!


    Ja ? richtig, es gibt schon wieder gleich nach einigen Stunden einen neuen Teil!
    Wir haben im letzten Teil die grundlegenden Variablen, also die Datentypen besprochen. Mir kam nun die Idee noch die Methoden in einem Teil hinterherzugeben.
    Schutzklassen

    Um mit den Funktionen im Script anfangen zu können, benötigen wir aber noch ein kleines bisschen was an Wissen. Dabei ist die Rede von den sog. ?Schutzklassen?. Bitte nicht erschrecken, die Bezeichnung ?Schutzklasse? ist was ganz anderes, als das, was wir bisher als ?Klasse? kennengelernt haben.
    Es sind nämlich einfach bloß eine reihe Schlüsselwörter, die uns den Umgang mit unseren Methoden und Variablen erleichtern.


    Man stelle sich folgendes einmal vor:
    Es ist ja nur logisch, daß irgendetwas von ?Außen? auf unsere Klasse zugreifen muß um dort die Methoden, also die Funktionen ausführen zu können. Da wir aber vielleicht nicht alles ?freigeben? möchten und dies an manchen Stellen auch nicht sollten, gibt es sowohl im Auran Game Script, als auch in der Programmierung allgemein, die Schutzklassen.


    Damit ist der Begriff ?Schutzklasse? doch schon beschrieben:
    Eine Schutzklasse ist dafür zuständig, Variablen (Member) und Funktionen (Methoden) zu schützen, bzw. diese für die Verwendung von Außerhalb der Klasse freizugeben.
    Im Auran Game Script ist das ganz einfach gehalten. Und zwar können wir vor Variablen und Methoden das Wörtchen ?public? setzen (wie bei ?public void Init?). Damit erreichen wir, daß nicht nur von innerhalb der Klasse auf ?Init? zugegriffen werden kann, sondern auch von außen diese Methode freigegeben ist.
    Lassen wir dieses Wörtchen einfach weg, so wie wir das im Teil ?Variablen & Datentypen? getan haben ? dort haben wir vor die Variablen kein ?public? gesetzt ? kann man diese Variablen nicht von Außen manipulieren, noch nicht einmal auslesen. Und das ist gut so! Dafür baut man sich in der Regel Methoden, die Werte holen und Werte setzen können. Damit geht man sicher, daß man beim Scripten nicht ?aus Versehen? durch Vertippen den Wert der Variablen verändert. Man ruft dann eben explizit eine Funktion auf, gibt ihr den neuen Wert für die Variable, der dann gesetzt wird. Wir schaffen es also so, die Variablen, die ?uns nicht gehören? auch wirklich nur dann zu manipulieren, wenn wir es wirklich wollen.


    Der Sinn und Zweck von Schutzklasse scheint vielleicht noch verwirrend zu sein, wird sich aber von selbst erklären, wenn man erst einmal damit arbeitet.


    Achso: Das Gegenteil von ?public? (öffentlich) ist ?private? (privat). Dieses Schlüsselwort gibt es im Auran Game Script nicht. ?Private? wird automatisch angenommen, wenn keine Schutzklasse angegeben ist.


    Nochmal kurz:

    • „public“ erlaubt den Zugriff auch außerhalb der Klasse
    • „private“, also kein public, einfach nichts angegebeben, erlaubt den Zugriff nur innerhalb der Klasse
    • Schutzklassen werden sowohl auf Variablen, als auch auf Funktionen angewendet

    Meldet euch, wenn ihr Fragen habt!


    Funktionen
    Eine Funktion ist dazu zuständig, eine bestimmte Aufgabe zu erfüllen. Es gibt in jeder Klasse in Trainz vordefinierte Funktionen, die wir verwenden können und oder müssen (die Init-Methode ist eine solche). Die Verwendung von Funktionen unterscheidet sich in zwei Arten:

    • Funktionen können überschrieben werden, manchmal müssen sogar Funktionen überschrieben werden (Beispiel: die Init-Methode muss in jedem Trainz-Objekt in der „Hauptklasse“ überschrieben werden)
    • Funktionen können auch nur aufgerufen werden, um Werte zu erhalten, zu schreiben oder um irgendwelche „Dinge“ anzustellen

    Überschreiben von Funktionen
    Funktionen werden überschrieben, um die Arbeitsweise der selben neuzudefinieren. Das ist bei der Init-Methode der Fall. Viele Funktionen in Trainz, dazu gehört die Init-Methode, müssen für das eigene Objekt überschrieben werden. Diese Methoden haben von Hause aus schon eigene Inhalte, die entweder komplett neu geschrieben, oder ergänzt werden. Das Wörtchen ?inherited? ist also hier interessant.


    Wenn wir eine Funktion überschreiben, müssen wir entscheiden, ob wir die Standard-Implemtierung der Funktion erhalten wollen oder diese komplett neu schreiben möchten. In der Init-Methode ist es verboten(!), die Funktion komplett neu zu implementieren. Wir müssen also immer ?inherited? angeben, und dann in den Klammern alle Paramter in der gleichen Reichenfolge, wie in der Deklaration der Funktion, angeben (inherited(pAsset), Paramter = Variablen, die einer Funktion mitgegeben werden).


    Nicht nur mit Parametern kann ?inherited? genutzt werden. Auch Rückgabewerte (s.u.) können damit geholt werden. In diesem Fall macht die Funktion ?inherited? nichts anderes, als die ursprüngliche Funktion aufzurufen und deren Rückgabewert zu erhalten. Beispielsweise macht man das bei ?public string GetDescriptionHTML(void)?. Die Funktion dieser Methode ist an dieser Stelle irrelevant.
    Wichtig ist nur, daß man mit dem Rückgabewert weiterarbeiten kann.
    Bei Rückgabewerten wird eine Variable deklariert, die den Wert ?inherited(PARAMETER)? erhält.
    Beispielsweise bei ?GetDescriptionHTML?: ?string sHTML = inherited();?.


    Dieses ?inherited? wird uns noch öfter begegnen. Mehr gibt es nicht zu sagen, also daß es dafür zuständig ist. Inherited bedeutet ?geerbt?. Aha! Erben. Schon wieder? ? Ja, aber diesmal geht es nicht um Klassen, sondern um Funktionsdeklarationen, die wir annehmen können/müssen, oder auch nicht.


    Aufrufen von Funktionen
    Ein einfaches Beispiel für den Aufruf einer Funktion wäre zum Beispiel die ?Print?-Funktion aus der Interface-Klasse. Gehen wir hin und schreiben in unserem Script in die Init-Methode unter ?inherited(pAsset)? folgende Zeile

    Code
    1. Interface.Print(„Ich bin ein Text!“);

    Stellen wir unser Objekt dann auf die Strecke, laden anschließend den Fahrermodus, sehen wir im Nachrichtenfenster den Text: ?Ich bin ein Text!?
    Man sieht, wir können Funktionen ganz einfach aufrufen und das Spielgeschehen somit beeinflussen.


    Aufbau von Funktionen
    Wie bei Variablen werden Funktionen deklariert. Sie werden nicht initialisiert sondern ?implementiert?.
    Das meint im Grunde genommen das gleiche. Deklaration ist das ?Bekanntmachen? der Funktion, die Implementierung ist das Füllen der Funktion mit Inhalten.


    Ein Unterschied ist doch da:
    Funktionen sollten immer deklariert und später implementiert werden. Das liegt daran, daß das Script von oben nach unten ?gelesen? wird. Basteln wir uns eine Funktion unter die Init-Methode und rufen diese dann in der Init-Methode auf, wird Trainz uns einen Scriptfehler an den Kopf werfen, da er die Funktion noch nicht kennt. Darum sollte man sich angewöhnen, jede Funktion über den ganzen Funktionsimplementierungen zu deklarieren. Dann ist es später egal, an welcher Stelle im Script sich die Funktion befindet, da diese schon bekannt ist.

    Obenstehendes Codebeispiel zeigt sehr schön die Deklaration, die Implementierung und den Aufruf einer Funktion. Gehen wir das doch einmal Schritt für Schritt durch:


    In Zeile 005 findet man die Deklaration der Funktion ?TestFunktion?. Man sieht deutlich, daß ich für diese Funktion die Schutzklasse ?public? gewählt habe. Diese steht ganz voran.
    Doch halt? was ist denn das jetzt? ?void?? Richtig. Wie in der Init-Methode steht dort ?void?.
    Nach der Schutzklasse kommt der sog. ?Rückgabewert?. Ein Rückgabewert ist etwas, das die Funktion einem zurück gibt, eine Variable, also ein Wert. Nach den Erläuterungen zu diesem Beispiel gibt es dazu auch nochmal ein Beispiel. Danach kommt die Funktionsbezeichnung, wie bei den Variablen kann man diese frei wählen. In die runden Klammern kommen dann, durch Kommas getrennt die Paramter der Funktion. Was das ist und wie man diese nutzt, kommt auch in das nächste Beispiel. Hier steht auf jeden Fall ?void?. Das kann ich vorweg nehmen: ?void? bedeutet ?leerer Raum?. Man kann es also durchaus plausibel als ?nichts? definieren. Die Funktion gibt ?nichts? zurück und erwartet dafür auch ?nichts?. Hört sich lustig an, nicht?
    Am Ende der Deklarierung folgt ein Semikolon.


    In den Zeilen 013 bis 016 wird die Funktion ?TestFunktion? dann implementiert. Auch hier muss der gesamte Funktionsname, mit Schutzklasse, Rückegabewerte und Parametern aufgeführt werden. Statt des Semiklons öffnen wir bei der Implementierung einer Funktion einen neuen Block mittels ?{?. Am Ende der Implementierung schließen wir den Block dann wieder mit ?}?. Alles, was nun in den geschweiften Klammern steht, ist das, was abgearbeitet wird, wenn die Funktion aufgerufen wird.
    Die ?TestFunktion?-Funktion macht nichts anderes, als daß sie einen Text in das Nachrichtenfenster im Fahrermodus ausgibt.


    Die Funktion ruft sich nicht von alleine auf. Wir brauchen etwas, daß es für uns macht. Da wir eine eigene Funktion implementiert haben, wird diese nicht von Trainz an der benötigten Stelle aufgerufen.


    Das müssen wir selbst tun. In Zeile 010 haben wir das gemacht. Direkt, wenn das Objekt geladen wird, erscheint der schöne Text im Nachrichtenfenster. Das Script funktioniert so und kann so auch verwendet werden, um mal zu schauen.
    Hier einmal ein Ablauf von dem was passiert:
    Init (Objekt wird initialisiert) -> TestFunktion (die Funktion wird aufgerufen) -> Interface.Print(die TestFunktion schreibt einen Text mittels Interface.Print ins Nachrichtenfenster)


    Parameter und Rückgabewerte
    Paramter und Rückgabewerte sind ganz einfach Variablen. Paramter werden der Funktion übergeben. Diese braucht sie dann, um die gewünschten Prozesse zu verarbeiten. Ein Rückgabewert kann das Resultat eines solchen Prozesses sein.
    Dazu einmal das folgende Script:

    Was für eine Gewalt, was? ? So viel Neues auf einmal.
    Wir machen es langsam:
    Zu Beginn sehen wir die Deklaration der Funktion ?Addition?. Statt ?void? hat sie nun den Rückgabewert des Typs ?int?. Die Funktion gibt also eine ganze Zahl zurück. Als Paramter erwartet sie zwei Variablen des Typs ?int?. Also: Wir geben der Funktion zwei Ganzzahlen mit, und sie gibt uns eine Ganzzahl zurück.
    In der Implementierung der Funktion sehen wir das Wörtchen ?return?. ?Return? bedeutet: Zurückgeben.
    Danach steht in Klammern die Rechnung, die wir ausführen. Wir addieren einfach iA und iB miteinander (zu erkennen durch den Operator: +). Das heißt nichts anderes, als daß die Funktion ?Addition? die Summe aus iA und iB zurückgibt.


    In der Init-Methode gibt es wieder was neues:
    Wir speichern die Summe aus 5 und 10 in die Variable iErgebnis, die den Typ int hat. Aha!
    Der Zusammenhang dürfte klar sein. Hier sehen wir auch wieder: Variablen sind Platzhalter.
    In der Funktion ?Addition? wäre es ja nicht sinnvoll, direkt 5 und 10 zu addieren. Wir möchten ja womöglich auch andere Zahlen miteinander addieren. Darum gibt es Parameter. Der Wert der solchen können individuell für jeden Aufruf der Funktion ?Addition? festgelegt werden. In der Funktion ?Addition? müssen wir dann nur noch mit den Variablen rechnen und benötigen keinen Aufschluss über ihren aktuellen Wert.


    Anschließend lassen wir mittels Interface.Print das Ergebnis in das Nachrichtenfenster ausgeben.
    Dort wird stehen: 15


    Wir können auch bereits vorhandene Variablen an eine Funktion übergeben. Anschaulich im nächsten Beispiel. Wir erweitern das letzte Script ein wenig:

    In Zeile 017 addieren wir das Ergebnis der vorangegangenden Addition auch noch einmal mit 5 und speichern dieses in die Variable iErgebis2. Auch dieses Ergebnis schreiben wir ins Nachrichtenfenster.
    Führt man dieses Script einmal aus, erscheint im Nachrichtenfenster:


    15
    20


    Damit hätte ich ersteinmal alles gesagt. Ich hoffe, ihr habt alles verstanden!


    Bis zum nächsten Mal
    Pascal