4.1 Was ist eine Variable? (wirklich grundlegend)
Eine Variable ist ein benannter Speicherplatz für einen Wert.
Stell dir eine Variable wie eine beschriftete Kiste vor: Der Name ist das Etikett,
der Inhalt ist der Wert. Über das Etikett kannst du die Kiste jederzeit wieder finden,
ohne zu wissen, wo genau sie steht. Der Driver kümmert sich um die „Adresse“ im Speicher,
du arbeitest nur mit dem Namen.
Wichtig ist: Der Datentyp bestimmt, was in die Kiste darf.
Eine int-Kiste darf nur ganze Zahlen enthalten, eine string-Kiste
nur Text. Wenn du versuchst, einen String in eine int-Kiste zu legen, beschwert
sich der Compiler — vorausgesetzt, du hast #pragma strict_types oben in deiner
Datei stehen (was im Midgard die Empfehlung ist).
int hp; // Kiste für eine Zahl
string name; // Kiste für Text
object target; // Kiste für ein Objekt
int *flags; // Kiste für eine Liste von Zahlen
mapping props; // Kiste für ein Mapping
Solange du nichts hineinlegst, ist die Kiste „leer“ — aber sie existiert bereits.
In LPC bedeutet „leer“: der Wert ist 0. Das gilt für alle Typen:
Ein neu deklariertes int ist 0, ein neuer string ist 0
(nicht ""!), ein object ist 0, ein Array ist 0.
Variablennamen dürfen aus Buchstaben, Ziffern und Unterstrichen bestehen, müssen aber
mit einem Buchstaben oder Unterstrich beginnen. Die Großschreibung wird beachtet
(hp und Hp sind verschiedene Variablen). Konvention im Midgard:
hp ist eine lokale/globale Variable, P_HP ein Property-Define
(Großbuchstaben), SetHp() eine Funktion in Pascal-Case.
4.2 Deklarieren vs. Initialisieren
Es gibt zwei Phasen, wenn eine Variable ins Leben tritt:
- Deklarieren: der Variable einen Namen und Typ geben.
Die Kiste wird gebaut und beschriftet — aber sie ist erstmal leer (Wert:
0).
- Initialisieren: ihr sofort einen Startwert geben.
Die Kiste wird gebaut, beschriftet und bereits gefüllt.
// nur deklarieren
int hp; // hp ist 0
// deklarieren + initialisieren
int hp = 100; // hp ist 100
Beides ist erlaubt. Für Anfänger ist Initialisieren oft besser, weil du dadurch
immer weißt, welchen Wert die Variable nach der Definitionszeile hat. Bei rein
deklarierten Variablen gilt zwar „Wert ist 0“ — wenn du in der Logik dann vergisst,
sie zu setzen, kannst du dich aber wundern, warum nichts passiert.
Mehrere Variablen in einer Zeile
Du kannst mehrere Variablen desselben Typs in einer Zeile deklarieren — getrennt mit
Komma:
int a, b, c; // alle drei sind 0
int x = 1, y = 2, z; // x=1, y=2, z=0
Lokale Variablen am Anfang ODER unterwegs?
Früher musste man in LPC alle lokalen Variablen am Anfang einer Funktion deklarieren.
Heute kannst du sie auch unterwegs deklarieren, wo sie zum ersten Mal gebraucht
werden. Das ist oft lesbarer:
void demo() {
// Alter Stil:
int a, b;
string s;
a = 5;
b = 10;
s = "Hallo";
// Moderner Stil — wo sie gebraucht werden:
int a = 5;
int b = 10;
string s = "Hallo";
}
Konvention im Midgard MUD: lokale Variablen so deklarieren, wo sie zum ersten Mal
gebraucht werden. Das hält Definition und Verwendung dicht beieinander.
4.3 Globale Variablen (Objekt-Variablen)
Globale Variablen stehen außerhalb von Funktionen, also direkt
auf der „Datei-Ebene“ deines Objekts. Sie gehören dem Objekt selbst — nicht einer
einzelnen Funktion — und existieren so lange, wie das Objekt im Speicher existiert.
Wenn das Objekt zerstört wird (destruct()), verschwinden auch alle seine
globalen Variablen mit.
Du kannst dir globale Variablen als das Gedächtnis eines Objekts
vorstellen. Was sich das Objekt zwischen verschiedenen Funktionsaufrufen merken soll,
gehört in eine globale Variable.
// Diese Variablen sind global - sie gehören dem ganzen Objekt:
private int hp;
private string name;
private int berserker_modus;
protected void create()
{
::create();
hp = 100;
name = "Ein alter Baum";
berserker_modus = 0;
}
public int query_hp() {
return hp; // hp ist hier zugänglich
}
public void heal(int amount) {
hp += amount; // wird hier verändert
}
Diese Variablen kannst du in allen Funktionen des Objekts benutzen.
Sie sind ideal für Zustände wie:
- Lebenspunkte, Stamina, Mana
- Status-Flags: vergiftet? unsichtbar? im Kampf?
- Verweise auf andere Objekte: aktueller Gegner, letzter Spieler
- Counter: wie oft wurde der Hebel gezogen?
- Cache: zuletzt berechnete Werte, gefundene Objekte
Sichtbarkeit globaler Variablen
Globale Variablen sollten fast immer als private markiert werden,
damit andere Objekte nicht direkt darauf zugreifen können. Das ist Kapselung —
einer der Grundpfeiler von OOP. Mehr dazu in Abschnitt 4.6.
// Schlecht: jeder kann hp einfach so verändern
int hp;
// Besser: hp ist intern, nur das Objekt selbst
private int hp;
// Querymethoden bieten geregelten Zugriff:
public int query_hp() { return hp; }
public void set_hp(int new_hp) {
if (new_hp < 0) new_hp = 0;
hp = new_hp;
}
4.4 Lokale Variablen (Funktions-Variablen)
Lokale Variablen werden innerhalb einer Funktion oder eines Blocks
definiert. Sie existieren nur während der Ausführung dieser Funktion bzw. dieses
Blocks. Nach dem Ende der Funktion ist die Variable weg — der Speicher wird vom
Driver freigegeben.
Lokale Variablen sind dazu da, Zwischenergebnisse zu halten oder einer Berechnung
einen Namen zu geben. Sie machen Code lesbar, weil du komplizierte Ausdrücke in
sinnvolle Schritte aufteilen kannst.
void heal()
{
int amount = 10; // lokale Variable, lebt nur in heal()
hp = hp + amount;
// Sobald heal() zu Ende ist, ist amount weg.
}
int berechne_schaden(object enemy)
{
int base_dmg = 10;
int level = enemy->QueryProp(P_LEVEL);
int multi = level / 5 + 1;
int total = base_dmg * multi;
return total;
}
Sobald die Funktion endet, verschwinden alle lokalen Variablen.
Sie sind außerhalb der Funktion unbekannt — versuchst du außerhalb auf sie zuzugreifen,
ist die Variable für den Compiler nicht definiert.
Block-Scope: noch kürzere Lebensdauer
Eine lokale Variable lebt nur in dem Block (zwischen { und }),
in dem sie definiert wurde. Das ist wichtig, wenn du in einer Schleife Variablen anlegst:
void demo()
{
if (something()) {
int x = 5; // x existiert nur innerhalb dieses if-Blocks
write(x + "\n");
}
// Hier ist x bereits wieder weg!
for (int i = 0; i < 10; i++) {
int doppelt = i * 2; // doppelt lebt nur pro Schleifendurchlauf
write(doppelt + "\n");
}
}
Merksatz: Lokale Variablen sind kurzlebig, globale Variablen langlebig.
Variablen sollten so kurzlebig wie möglich sein — das hilft, die Übersicht zu behalten.
Eine globale Variable ist nur dann gerechtfertigt, wenn sich das Objekt etwas
zwischen Funktionsaufrufen merken muss.
4.5 Der häufigste Anfängerfehler: Versteckte Variablen
Dieser Fehler passiert extrem oft:
int hp = 100;
void damage()
{
int hp = 10;
}
Hier gibt es zwei verschiedene Variablen mit dem Namen hp.
Die lokale Variable in damage() verdeckt die globale.
Ergebnis: Die globalen Lebenspunkte ändern sich nicht – sehr verwirrend für Anfänger.
Regel: Verwende unterschiedliche Namen oder ändere bewusst die globale Variable.
4.6 Sichtbarkeit: public, protected, private (warum das wichtig ist)
Sichtbarkeit regelt, wer auf Variablen und Funktionen zugreifen darf.
Das schützt dein Objekt vor falscher Benutzung.
public string name;
protected int hp;
private int secret;
- public – jeder darf zugreifen (Standard)
- protected – nur dieses Objekt und erbende Objekte
- private – nur dieses Objekt selbst
Anfänger-Tipp:
Mach Variablen lieber protected oder private
und greife über Funktionen darauf zu.
4.7 Getter und Setter – sicherer Zugriff
Statt Variablen direkt zugänglich zu machen, nutzt man oft
sogenannte Getter- und Setter-Funktionen.
protected int hp;
int query_hp()
{
return hp;
}
void set_hp(int new_hp)
{
hp = new_hp;
}
Vorteil: Du kannst später Regeln einbauen
(z.B. kein negativer Wert, Maximalgrenze).
4.8 Lebensdauer von Variablen (Speicher verstehen)
Variablen leben nicht ewig – ihre Lebensdauer hängt davon ab,
wo sie definiert sind.
- Lokale Variablen → nur während der Funktion
- Globale Variablen → solange das Objekt existiert
Wird ein Objekt zerstört (destruct()),
verschwinden alle seine Variablen.
4.9 Initialisierung in create() (Best Practice)
Im Midgard MUD ist create() der Standard-Ort,
um Objekt-Variablen zu initialisieren.
void create()
{
hp = 100;
name = "Ein Übungsobjekt";
}
So ist garantiert, dass jede neue Instanz
(Master oder Clone) sauber startet.
Konvention im Midgard: Beginne create() oft mit einem
::create();-Aufruf, damit auch die geerbte Klasse (z.B. /std/thing)
ihren Initialisierungsteil erledigen kann. Das vergisst man als Anfänger gerne –
und wundert sich dann, warum SetProp/QueryProp nicht funktionieren.
4.9a Properties statt globaler Variablen (das Midgard-Idiom)
Im Midgard MUD verwendest du für „aussen sichtbare“ Eigenschaften eines Objekts
normalerweise keine globalen Variablen, sondern Properties.
Diese werden mit SetProp(P_NAME, wert) gesetzt und mit
QueryProp(P_NAME) gelesen. Die Property-Defines kommen aus
<properties.h>.
#include <properties.h>
inherit "/std/thing";
protected void create() {
::create();
SetProp(P_NAME, "Stein");
SetProp(P_SHORT, "Ein kleiner Stein");
SetProp(P_LONG, "Ein unscheinbarer grauer Stein.\n");
SetProp(P_WEIGHT, 200); // Gramm
SetProp(P_GENDER, MALE);
}
Property-Modi mit Set()
Properties haben neben einem Wert auch einen Modus. Mit Set() kannst
du diesen Modus steuern. Wichtige Flags aus <thing/properties.h>:
SAVE – Property wird in save_object() mitgespeichert
PROTECTED – Änderung von aussen blockiert (nur eigenes Objekt darf setzen)
SECURED – einmal gesetzt, nie wieder änderbar (auch nicht intern)
NOSETMETHOD – umgeht die Setmethode beim Setzen
// Property nur intern änderbar machen:
Set(P_LEVEL, PROTECTED, F_MODE_AS); // Flag setzen
Set-/Query-Methoden statt einfacher Variablen
Wenn beim Setzen oder Abfragen einer Property Logik laufen soll (Validierung,
Begrenzung, Berechnung), bindest du eine Closure als Set- oder Querymethode:
private int _set_hp(int new_hp) {
// Werte begrenzen
if (new_hp < 0) new_hp = 0;
if (new_hp > QueryProp(P_MAX_HP)) new_hp = QueryProp(P_MAX_HP);
Set(P_HP, new_hp); // direkt in den Property-Datenteil
return new_hp;
}
protected void create() {
::create();
Set(P_HP, #'_set_hp, F_SET_METHOD);
SetProp(P_MAX_HP, 100);
SetProp(P_HP, 100);
}
So entscheidet immer das Objekt selbst, wie sein Zustand verändert wird – nicht der Aufrufer.
Mehr dazu unter /doc/concepts/properties und /doc/lfun/Set.
4.10 Konstanten mit #define
Konstanten sind feste Werte, die sich nie ändern sollen.
Sie machen Code lesbarer und wartbarer.
#define MAX_HP 100
void create()
{
hp = MAX_HP;
}
Vorteil: Änderst du MAX_HP,
passt sich der ganze Code an.
4.11 Typische Anfängerfehler (Checkliste)
- Lokale und globale Variablen verwechseln
- Variablen ohne Initialwert benutzen
- Alles
public machen
- Unklare oder kurze Namen (
x, tmp)
Wenn etwas „komisch“ wirkt: Prüfe zuerst die Variablen.
In 50% der Fälle liegt der Fehler genau hier.
4.12 Zusammenfassung
- Variablen sind Speicher für Werte
- Ort der Definition ist entscheidend
- Lokale Variablen sind kurzlebig
- Globale Variablen gehören zum Objekt
- Sichtbarkeit schützt vor Chaos
Wenn du dieses Kapitel wirklich verstanden hast,
wirst du viele spätere Probleme automatisch vermeiden.
Ausblick
In Kapitel 5 lernst du Funktionen im Detail:
Parameter, Rückgabewerte, Aufrufe, typische Muster
und wie Funktionen im Midgard MUD sauber eingesetzt werden.
Weiter zu Kapitel 5
Funktionen – das Herz von LPC.