MIDGARDAudhumbla 0.7

Kapitel 4: Variablen & Konstanten

Variablen sind das Gedächtnis deiner Objekte. In diesem Kapitel lernst du nicht nur, wie man Variablen anlegt, sondern vor allem wo, wie lange und wer darauf zugreifen darf. Wir erklären typische Anfängerfallen, zeigen viele Beispiele und bauen ein solides mentales Modell auf.

Weiter zu Kapitel 5

Als Nächstes: Funktionen richtig schreiben und benutzen.

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.