Kapitel 4: Funktionen

4.1 Rueckblick
Es sollte Folgendes bekannt sein: LPC-Objekte beinhalten Funktionen, die
Variablen manipulieren koennen. Funktionen manipulieren Variablen, wenn sie
ausgefuehrt werden, und sie werden durch Aufrufe aus anderen Funktionen
ausgefuehrt.
Genauso wenig ist es relevant in welcher Reihenfolge die Funktionen im
Quelltext stehen.
Innerhalb der Funktion werden Variablen manipuliert.
Diese werden im Arbeitsspeicher in 0'en und 1'en gespeichert, die in
nutzbarer Form verarbeitet werden koennen. Dies nennt man Datatyping.
string-Datentypen teilen dem Driver mit, dass Du die Ein- und Ausgaben in
Form von alphanumerischen Zeichen haben willst.
int-Datentypen sind fuer ganzzahlige Werte reserviert.
Der status-Datentyp beinhaltet Wahrheitswerte, also entweder 0 oder 1.
Der letzte behandelte Datentyp, void beinhaltet keinen Wert, und sollte
nicht fuer Variablen-Datentypen verwendet werden.

4.2 Was ist eine Funktion?
Genau wie mathematische Funktionen benoetigen LPC-Funktionen Eingaben und
geben Ausgaben zurueck (returnieren Ausgaben).
Funktionen werden, genau wie Variablen, mit einem Datentyp, der den
Rueckgabewert (return-Wert) deklariert.
Funktionen, die als void deklariert werden, haben keinen Rueckgabewert.
Die trivialste und typischste korrekte Funktion in LPC sieht folgendermassen
aus:

void ich_bin_sinnlos() { }

Diese Funktion akzeptiert keinen Input (also Eingaben oder Uebergabewerte) noch
produziert sie Output (also Rueckgabewerte).
Sie existiert, ist referenzierbar und bringt keinen Fehler, wenn sie
compiliert werden wuerde.
Jede richtige und vollstaendige Funktion besteht aus folgenden drei Teilen:
1) Die Deklaration
2) Die Definition
3) Der Funktionsblock

Genau wie Variablen muss eine Funktion deklariert werden. Damit weiss der
Driver 1) um was fuer einen Datentyp es sich bei dem return-Wert handelt,
und 2) um welche Input-Datentypen es sich handelt.
Eine Funktionsdeklaration besteht aus Folgendem:
typ name(parameter1, parameter2, ... parametern);
Die Deklaration der Funktion pfluecke_blumen(), die als Input einen string
(der in der Variable str gespeichert ist) und einen return-Wert vom Typ int
liefern soll, sieht folgendermassen aus:

int pfluecke_blumen(string str);

Dabei wird der uebergebende String der (dadurch definierten und initalisierten)
Variablen str uebergeben.
Die Funktionsdefinition ist der Code, der beschreibt, was die Funktion mit
dem Input, den sie uebergeben bekommt, anfaengt.
Der Aufruf des Funktion kann von ueberall aus einem beliebigen Objekt
stattfinden.
Ab sofort werde ich in allen Beispielen Kommentare in die Kommentarzeichen
von LPC einfuegen:
Kommentare sind Hinweise, die vom Compiler ignoriert werden, aber fuer
fremde Leute die Lesbarkeit und Verstaendlichkeit des Codes erhoehen.
// bedeutet, dass der Rest der Zeile als Kommentar dient
/* alles was zwischen diesen Zeichen sind(kann auch mehrere Zeilen umfassen)
   steht, ist als Kommentar zu werten */
Wie man mit den Funktionen spielen kann, zeigt folgendes Beispiel:

/* zuerst die Deklarationen der Funktionen */
void write_vals();
int add(int x, int y);

/* nun folgt die Definition von write_vals() */

void write_vals()
{
 int x;
 x=add(2,2);
 /* hiermit wird x der Rueckgabewert der Funktion add, der die Werte 2 und 2
    uebergeben werden, zugewiesen */
 write(x+"\n");
}

/* nun noch die Definition von add() */
int add(int x, int y)
{
 return (x+y);
}

Gut, ich gebe zu, diese Funktionen sind etwas sinnlos und effektiv in eine
eigene Funktion zu ueberfuehren, jedoch an diesem einfachen Beispiel ist
die Funktion wohl gut sichtbar zu machen.

Niemals sollte man vergessen, dass es egal ist, in welcher Reihenfolge die
Definitionen im Quellcode vorkommen.
Das liegt daran, dass die Funktionen ausgefuehrt werden, wie sie aufgerufen
werden.
Das Einzige, was zu beachten ist (und das ist ein beliebter Fehler), dass
Die Funktion deklariert werden muss, BEVOR! sie definiert wurde, und vor
jeder Definition jeglicher Funktion, die sie aufruft.

4.3 Efuns (externe Funktionen)
Efuns sind extern definierte (deshalb Efun) Funktionen. Um genau zu sein, sind
sie im Driver definiert. Falls Du bereits LPC-Code gesehen haben solltest,
wirst Du ueber Funktionen wie this_player(), write() oder say() gestolpert
sein. Sie sehen auf den ersten Blick aus, als waeren es Funktionen.
Es sind auch Funktionen, jedoch sind sie sehr viel schneller, da sie bereits
compiliert sind, da sie vom Driver zur Verfuegung gestellt werden.

In der Funktion write_vals() wurden zwei Funktionen aufgerufen.
Die erste Funktion (add()) ist eine selbst definierte und deklarierte
Funktion. Die zweite Funktion (write()) ist eine efun. Diese Funktion
wurde bereits vom Driver definiert und deklariert. Du musst sie nur noch
aufrufen.

Efuns loesen alltaegliche Probleme, um Ein- und Ausgaben der
Internetverbindungen etc. zu loesen. Diese Sachen waeren mit LPC zwar genauso
moeglich, jedoch durch die Auslagerung auf den Driver wird hier ein
Geschwindigkeitsvorteil erreicht.
Da sie in C programmiert sind und bereits compiliert sind (also in der
Sprache vorliegen, die der Computer spricht), spart das Zeit und auch
fuer den Programmierer Arbeit.
Efuns verhalten sich fuer den Programmierer letztlich wie selbst geschriebene
Funktionen.
Fuer die taegliche Arbeit hat das zwei Konsequenzen:
1) Der return-Typ (s. Datentypen) muss bekannt sein und
2) Welche Parameter muessen uebergeben werden
Frage:
Nenne drei nicht im Text erwaehnte efuns.
(oder Funktionen, die efuns sein koennten)
Zusatzfrage:
Ist es moeglich, einer Funktion eine andere Funktion zu uebergeben, und wenn
ja, was wird endgueltig uebergeben, Beispiele. Wenn nein, warum nicht?

Informationen ueber die Efuns, ganz speziell die Parameter und den Returnwert,
sind zumeist im Hilfesystem fuer Programmierer (manpages, Aufruf mit man)
verfuegbar. Sie sind normalerweise in /doc/efun im MUD einsehbar.
Jede einzelne Efun kann hier nicht aufgefuehrt werden, da das sehr vom Driver
abhaengig ist, welche Funktion wo definiert ist und wie sie heisst.

Wenn man sich die Quellcodes des Drivers ansieht, findet man write
folgendermassen deklariert:

void write(string);

Das bedeutet, der Funktion write wird ein einzelner Parameter vom Typ string
uebergeben und sie liefert keinen return-Wert.

4.4 Eigene Funktionen definieren
Auch wenn es irrelevant ist, wie die Funktionen im Objekt angeordnet sind, ist
es in der Funktion um so wichtiger, in welcher Reihenfolge die Anweisungen
stehen, da eine Funktion von oben nach unten abgearbeitet wird.
Nehmen wir uns wieder unser Beispiel write_vals() von oben vor:

Die Anweisung: x = add(2,2);
muss vor der write()-efun kommen, da write() ja der benoetigte Wert (den ja
die Funktion add() liefert) uebergeben bekommen soll.

Kommen wir noch kurz zum return-Wert. Die return-Werte haben immer denselben
Datentypen wie die Funktionen, in denen sie vorkommen. In unserem obigen
Beispiel hat die Funktion add() folgenden return-Wert:
return (x+y);
Da die Funktion vom Typ int ist, wird das Ergebnis von x+y zuerst berechnet,
und dann zurueckgegeben (returniert).
return beendet die Ausfuehrung der entsprechenden Funktion.
Da void keinen return-Wert hat, wird eine void-Funktion einfach mit return;
gestoppt. Das Allerwichtigste ist, dass return denselben Datentyp _HABEN_MUSS_
wie die Funktion.

4.5 Zusammenfassung
LPC-Objekte bestehen aus Funktionen. Funktionen bestehen aus drei Teilen:
1) Deklaration
2) Definition
3) Aufruf
Funktionsdeklarationen werden normalerweise am Beginn der Datei vor jeglicher
Definition (aber nach den Precompilerdirektiven, aber dazu spaeter)
erscheinen. Ausserdem muss eine Funktion, die andere Funktionen aufruft,
vor dieser Funktion deklariert werden.
Die Reihenfolge der Definition der Funktionen ist dann egal.
Ausserdem kann keine Funktion innerhalb einer anderen Funktion definiert
werden.

Eine Funktion an sich besteht aus folgenden Punkten:
1) Der return-Datentyp
2) Funktionsname
3) oeffnende "(", gefolgt von der Parameterliste und einer schliessenden ")"
4) oeffnende "{" (der Driver weiss, dass ab hier der Funktionsblock/koerper
kommt)
5) Deklaration der lokalen Variablen
6) Anweisungen, Ausdruecke und Funktionsaufrufe in logischer Reihenfolge
7) eine schliessende "}", die dem Driver mitteilt, dass hier der
Funktionsblock zuende ist. Falls kein return angegeben wurde, gibt
diese Funktion der aufrufenden Funktion ein 'fertig' zurueck, als wenn es
angegeben worden waere. Dies ist jedoch nur bei void-Funktionen moeglich.
Alle anderen Funktionen muessen mit einem return abgeschlossen werden, da
sich sonst der Compiler verhaspelt und Fehler folgen.

Eine triviale Funktion wuerde auch so aussehen:

void lausige_funktion() {}

Diese Funktion akzeptiert keine Uebergabewerte, fuehrt keine Anweisung aus und
hat keinen return-Wert.
Jede Funktion, die nicht vom Typ void ist, BENOETIGT einen return-Wert, der
dem Funktionsdatentyp entspricht.

Nicht jeder Driver benoetigt eine Deklaration der Funktion und manche
benoetigen keinen spezifizierten return-Wert. Das mag fuer den Anfaenger
einfacher sein, hat aber folgende Konsequenzen:
1) Wenn jemand den Code lesen (und debuggen) soll, hat er erst einmal zu
tun, zu verstehen, was es machen soll.
Das trifft auch fuer Dich spaeter zu.
Und Datentypprobleme sind ein sehr typischer Fehler. Und ein Fehler, der so
aussieht: "Bad arg 1 to foo() line 32" ist sehr aussagekraeftig und waere
schon von Anfang an umgehbar gewesen...
2) Es ist einfach schlechter Stil
mud.tamedhon.de:4711 ON
mud.tamedhon.de:4712 (TLS) ON
mud.tamedhon.de:4713 (Webclient) ON