6. Kontroll-Anweisungen

Kontrollanweisungen bestimmen die Reihenfolge, in der Aktionen durchgeführt werden. Vorbild für die Kontrollstrukturen von UniScript war die Programmiersprache C; die for-Anweisung von UniScript weicht jedoch von der C-Version ab. Die try-except-Anweisung ist nur bei wenigen C-Compilern vorhanden; eine Variante existiert jedoch bei den moderneren C++-Compilern. Die switch-Anweisung und die do-while-Anweisung fehlen in UniScript. Diese Anweisungen sind jedoch einfach durch if-else-if-Ketten bzw. die while-Anweisung zu ersetzen. Die goto-Anweisung fehlt ebenfalls, weil es für sie nur sehr wenige vernünftige Verwendungen gibt.

6.1. Die if-else-Anweisung

Die if-else-Anweisung wird für Entscheidungen verwendet. Sie hat die Form

if (bedingung) {
    if-anweisungen
} else {
    else-anweisungen
}

Der Ausdruck bedingung muss reell sein. Die if-Anweisungen werden ausgeführt, wenn die Bedingung wahr ist (d.h. wenn der skalare Ausdruck ungleich 0 ist) und die else-Anweisungen werden ausgeführt, wenn die Bedingung falsch, d.h. 0 ist. Der else-Teil kann entfallen

if (bedingung) {
    if-anweisungen
}

Gehört zu den if- und else-Teilen nur jeweils eine Anweisung, kann auf die geschweiften Klammern verzichtet werden

a = 1;
if (a == 1) print "a ist 1" else print "a ist nicht 1"

Übersichtlicher ist es jedoch, wenn die Anweisungen geklammert werden und eine Anweisung pro Zeile geschrieben wird.

a = 1
if (a == 1) {
    print "a ist 1"
} else {
    print "a ist nicht 1"
}

Die folgende Schreibweise ist in UniScript nicht erlaubt, weil UniScript die zweite geschweifte Klammer als Ende der if-else-Anweisung interpretiert und beim Wort else einen Syntaxfehler produziert.

a = 1;
if (a == 1)
{
    print "a ist 1";
}
else
{
    print "a ist nicht 1";
}

Wer die Anweisungen unbedingt in dieser Form schreiben will, kann sich mit Fortsetzungszeilen behelfen:

a = 1;
if (a == 1)
{
    print "a ist 1";
} ..
else ..
{
    print "a ist nicht 1";
}

Die Bedingung muss, wie bereits bemerkt, ein reeller Skalar sein. Der Ausdruck string == "Hallo" liefert dann einen reellen Skalar, wenn string ein Skalar ist, nämlich 1, wenn in string "Hallo" steht und sonst 0. Falls string jedoch einen Vektor oder eine Matrix von Strings enthält, liefert der Vergleichsausdruck einen reellen Vektor oder eine reelle Matrix. Enthält string z. B. den aus drei Elementen bestehenden Vektor ["Hallo", "HALLO", "hallo"] liefert der Vergleichsausdruck string == "Hallo" den reellen Vektor [1, 0, 0].

Um diesen reellen Vektor in einen reellen Skalar umzuwandeln, existieren die beiden UniScript Funktionen all und any.

Falls all mit einem reellen Vektor aufgerufen wird, liefert all den Skalar 1, wenn alle Elemente den Wert 1 haben und 0, wenn das nicht der Fall ist. Entsprechend liefert any den Wert 1, wenn irgend ein Wert des Eingabevektors den Wert 1 hat. Falls das Argument von all oder any eine reelle Matrix ist, wird die Matrix spaltenweise ausgewertet. Die Funktionen liefern dann einen Zeilen-Vektor. Durch doppeltes Anwenden der Funktionen kann daraus ein Skalar gemacht werden. Das folgende Beispiel führt also Aktion-1 nur aus, wenn alle Elemente von string den Wert "Hallo" haben, unabhängig davon, ob string ein Skalar, ein Vektor oder eine Matrix ist.

if (all(all(string == "Hallo"))) {
    // Aktion-1
} else {
    // Aktion-2
}

Häufig werden if-else-Anweisungen verwendet, um eine Entscheidung aus einer Reihe von Alternativen zu treffen. In diesem Fall werden sogenannte else-if-Ketten verwendet:

if (bedingung-1) {
    anweisung-1
} else if (bedingung-2) {
    anweisung-2
} else if (bedingung-3) {
    anweisung-3
} else if (bedingung-4) {
    anweisung-4
} else {
    anweisung-n
}

Die Bedingungen werden nacheinander geprüft, bis eine Bedingung zutrifft. Die entsprechenden Anweisungen werden ausgeführt und die else-if-Kette wird beendet. Falls keine Bedingung zutrifft, wird der letzte else-Fall ausgeführt.

6.2. Die for-Anweisung

Die for-Anweisung wird dazu verwendet, über alle Elemente eines Vektors zu iterieren.

for (i in vektor) {
    for-anweisungen
}

Hat vektor den Wert [1,3,6,4] nimmt i nacheinander die Werte 1, 3, 6, 4 an. vektor kann auch ein String-Vektor oder ein komplexer Vektor sein.

Der Vektor wird nur einmal am Anfang der for-Schleife ausgewertet. Das folgende Beispiel

a = [1,2,3]
for (i in a) {
    a = 0;
    printf("a = %d, i = %d\n", a, i);
}

erzeugt die Ausgabe

a = 0, i = 1
a = 0, i = 2
a = 0, i = 3

Das folgende Beispiel verwendet die DocCreate-Funktion um drei UniPlot-Dateien zu öffnen:

svFilename = ["be_ia.ipw", "fit.ipw", "no_x.ipw"];
svFilename = GetRootDirectory() + "examples/" + ..
             svFilename;
for (ssName in svFilename) {
    DocCreate(ssName);
}

6.3. Die while-Anweisung

Die while-Anweisung wird verwendet, um eine Anweisung oder eine Folge von Anweisungen mehrfach auszuführen. Man bezeichnet dies als Schleife.

while (bedingung) {
    while-anweisungen
}

bedingung muss wie bei der if-Anweisung ein reeller skalarer Ausdruck sein. Die Anweisungen werden wiederholt ausgeführt solange die Bedingung wahr, also ungleich 0 ist.

while (1) {
    print "Hallo"
}

würde also ununterbrochen ausgeführt, da die Bedingung nie den Wert 0 annimmt. Sie kann nur von außen abgebrochen werden, indem man die ESCAPE-Taste (ESC-Taste) betätigt. Man sieht solche while(1)-Schleifen dennoch häufiger. Sie werden dann jedoch durch eine break- oder return-Anweisung abgebrochen.

Diese while-Schleife gibt genau 10 mal das Wort "Hallo" aus und druckt am Ende die Zahl 11.

i = 1;
while (i <= 10) {
    print "Hallo";
    i = i + 1;
}
print i;

6.4. Die break- und die continue-Anweisung

Die break-Anweisung wird in Verbindung mit der if-Anweisung dazu verwendet, eine while- oder for-Anweisung vorzeitig abzubrechen.

Einige Sprachen haben eine Schleife, die am Ende prüft, ob die Anweisungen der Schleife nochmal ausgeführt werden sollen (bei Pascal ist dies die repeat-Schleife, bei C die do-Schleife). Mit der break-Anweisung kann dieses Verhalten in UniScript simuliert werden. Das folgende Beispiel druckt 10 mal "Hallo" und am Ende die Zahl 10.

i = 1;
while (1) {
    print "Hallo";
    if (i == 10) break;
    i = i + 1;
}
print i;

Mit der continue-Anweisung können Anweisungen innerhalb einer for- oder while-Schleife übersprungen werden. Hier ein Beispiel, das alle Dateien aus einem Verzeichnis öffnet, die die Endung .ipw haben.

ssPath = "c:/uniplot/samples/";
svFiles = FindFiles(ssPath + "*.*");
for (ssName in ssPath + svFiles[;1]) {
    if (strupper(SplitPath(ssName)[4]) != ".IPW") {
        continue;
    }
    if (DocCreate(ssName) == 0) break;
}

FindFiles(ssPath + "*.*") liefert eine Stringmatrix mit den Dateien des Verzeichnisses ssPath. In der ersten Spalte der Matrix befinden sich die Dateinamen. In der zweiten Spalte befindet sich die Größe der Dateien und in der dritten Spalte die Dateiattribute. Diese beiden Angaben interessieren hier nicht, weshalb sie mit dem Ausdruck svFiles[;1] herausgefiltert werden.

Die Funktion SplitPath teilt einen vollständigen Dateinamen in seine Bestandteile Laufwerk, Pfad, Name und Erweiterung auf. Der Aufruf SplitPath(ssName)[4] liefert dabei die Dateinamenserweiterung. strupper wandelt sein Argument in Großbuchstaben um, so daß die Bedingung auch zutrifft, wenn SplitPath eine Dateinamenserweiterung in Kleinbuchstaben liefert.

Wenn die Bedingung nicht zutrifft, wird die continue-Anweisung ausgeführt. Dies bewirkt, daß ganz ans Ende, also unmittelbar vor die schließende geschweifte Klammer, gesprungen wird. Falls die Funktion DocCreate die Zahl 0 zurückliefert, konnte das Dokument nicht erzeugt werden. Es wird dann die break-Anweisung ausgeführt, wodurch unmittelbar hinter die for-Schleife gesprungen wird.

Man hätte bei dem Beispiel übrigens auf die continue-Anweisung verzichten können, indem man in FindFiles als Suchmuster "*.ipw" angegeben hätte, also FindFiles(ssPath + "*.ipw").

6.5. Die try-except-Anweisung

Die try-except-Anweisung dient hauptsächlich dazu, Programmierfehler abzufangen und die Fehlerbehandlung zu vereinfachen. Sie hat die Form

try {
    try-anweisungen
} except (bedingung) {
    except-anweisungen
}

Im Gegensatz zur if-else-Anweisung ist der zweite Block nicht optional. Zu jedem try-Block gehört genau ein except-Block. try-except-Blöcke können geschachtelt werden.

Normalerweise werden nur die try-Anweisungen ausgeführt, d. h. im Normalfall wird weder bedingung ausgewertet, noch die except-Anweisungen ausgeführt. Es gibt jedoch eine Reihe von Gründen, die dazu führen, daß während der Ausführung der try-Anweisungen ein Problem auftritt.

Beispiele:

  • Es ist nicht genug Speicher vorhanden, um eine der Anweisungen auszuführen.
  • Der Benutzer hat die ESC-Taste gedrückt, während eine der try-Anweisungen ausgeführt wurde.
  • Es wurde auf ein Element eines Vektors zugegriffen, das nicht existiert.
  • Falls die try-Anweisungen Funktionsaufrufe enthalten, kann ein Fehler innerhalb der Funktion aufgetreten sein.
  • Innerhalb einer Funktion kann die error-Funktion aufgerufen worden sein.

Der 1. und der 2. Fall sind Probleme, die vom Programmierer nicht vorhersehbar sind. Der 3. und 4. Fall sind im allgemeinen Programmierfehler. Man bezeichnet die Fälle als Ausnahmen und die Lösung der Probleme als Ausnahme-Behandlung (exception handling). Im 5. Fall (Aufruf der error-Funktion) wird bewußt eine Ausnahme erzeugt.

Beispiel:

def GetTextFile(ssFileName)
{
   fp = fopen(ssFileName, "rt");
   ssText = fread(fp, "char");
   fclose(fp);
   return ssText;
}

fopen öffnet eine Datei zum Lesen, fread liest aus der Datei (bei dieser Form des Aufrufs die gesamte Datei) und fclose schließt die Datei. Es ist wichtig, daß eine Datei nach ihrer Verwendung wieder geschlossen wird, da (je nach Betriebssystem) nur eine kleine Anzahl Dateien gleichzeitig geöffnet werden dürfen.

Ruft man die Funktion mit GetTextFile("d:\\test.txt") auf, dann gibt die Funktion den Inhalt der Datei "d:\\test.txt" als Zeichenkette an den Aufrufer zurück. Voraussetzung ist, daß die Datei existiert, genug Speicher vorhanden ist, um die Datei zu lesen, in den Funktionen (z. B. fopen) kein Fehler ist und der Benutzer nicht während der Funktionsaufrufe die ESC-Taste gedrückt hat. Falls eine dieser Ausnahmen auftritt, wird das Programm mit einer Fehlermeldung abgebrochen. Das Problem ist, daß dieser Fehler auftreten kann, bevor die fclose-Funktion aufgerufen wird. Es gibt einen sogenannten Ressourcen-Verlust.

Das Problem kann gelöst werden, indem man die problematischen Anweisungen in einem try-except-Block schützt.

def GetTextFile(ssFileName)
{
    fp = 0;
    try {
       fp = fopen(ssFileName, "rt");
       ssText = fread(fp, "char");
       if (isreal(ssText)) error();
       fclose(fp);
       return ssText;
    } except (1) {
       if (fp != 0)
           fclose(fp);
       MessageBox("Datei kann nicht geladen werden!");
       return "";
    }
}

Falls irgend eine Ausnahme innerhalb des try-Blocks auftritt, wird in den except-Block verzweigt, in dem die Ausnahme behandelt werden kann. In diesem Beispiel wird die Datei geschlossen (falls sie von fopen erfolgreich geöffnet wurde) und eine Fehlermeldung in einem Meldungsfenster ausgegeben.

Hinter dem Schlüsselwort except steht eine in Klammern eingeschlossene Bedingung. Ist die Bedingung 0, wird die Ausnahme nicht im folgenden except-Block behandelt. Ist die Bedingung ungleich 0 (im Beispiel wurde die Konstante 1 verwendet), wird der except-Block beim Auftreten einer Ausnahme ausgeführt. Innerhalb der Bedingung kann mit der Funktion GetExceptionCode festgestellt werden, welche Ausnahme aufgetreten ist.

Beispiel:

def TestException(a, b)
{
    try {
        for (i in 1:10000) {
            x = a + b;
        }
    } except (GetExceptionCode() == ICERR_INTERRUPT) {
        MessageBox("Benutzer-Abbruch");
    }
}

Wird die Funktion z. B. mit den Argumenten "a" und 1.6 aufgerufen, TestException("a", 1.6), tritt eine Ausnahme auf. Dadurch wird der Ausdruck

GetExceptionCode() == ICERR_INTERRUPT

ausgewertet. GetExceptionCode liefert eine Fehlernummer - in diesem Fall die Fehlernummer mit dem Namen ICERR_OPERATOR_TYPE. Da die Fehlernummer nicht ICERR_INTERRUPT ist, wird der except-Block nicht ausgeführt; die Ausnahme wird in diesem Fall nicht in der Funktion behandelt. Die Ausnahme wird nur behandelt, wenn der Benutzer in der for-Schleife die ESC-Taste betätigt und dadurch die Ausnahme ICERR_INTERRUPT auslöst. Die Definitionen für die UniScript-Fehlernummern befinden sich in der Datei alias.ic.

id-1192040