Copyright (c) 1994, Jason R. Hulance Ins deutsche Übersetzt Juni/Juli 1994 von Jörg Wach (=JCL_POWER) Ein Einführungskurs zu Amiga E ***************************** Dieser Kurs gibt eine Einleitung zu der Amiga E Programmiersprache und, mit ein paar Abschweifungen, zum programmieren im allgemeinen. Inhalt ------ Teil Eins: Der Anfang  Einleitung zu Amiga E   Das Verständnis für ein einfaches Programm   Variablen und Ausdrücke   Programm-Fluß kontrollieren   Zusammenfassung  Teil Zwei: Die E Sprache  Format und Layout   Funktionen   Konstanten   Typen   Mehr über Statements und Ausdrücke   Eingebaute E Konstanten, Variablen und Funktionen   Module   Exception Handling   Rekursionen  Teil Drei: Beispiele  Einleitung zu den Beispielen   Zeit-Ausdrücke   Argumente Analysieren   Gadgets IDCMP und Graphics   Recursion Beispiel  Teil Vier: Anhang  Gemeinsame Probleme   Neue Funktionen   Syntax Beschreibung   Anderen Informationen  ======================================================================== Einleitung zu Amiga E ********************* Um mit Deinen Amiga zu arbeiten brauchst Du eine Sprache, die er versteht. Glücklicherweise gibt es eine große Auswahl von solchen Sprachen, welche für bestimmte Anwendungen genutzt werden können. Z.B. Basic (in den meisten Fällen) ist einfach und leicht zu lernen und somit Ideal für Anfänger. Assembler, auf der anderen Seite, fordert ein Menge Verständnis und ist langweilig, aber man kann damit die schnellsten Programme erzeugen, so daß Assembler, im allgemeinen, von kommerziellen Programmierern benutzt wird. Diese beiden sind zwei Extreme und mehr allgemein ausgerichtete Programmierer und Universitäten benutzen C oder Pascal/Modula-2, womit versucht wird, ein Gleichgewicht zwischen Einfachheit und Geschindigkeit herzustellen. E-Programme sehen sehr stark Pascal oder Modula-2 Programmen ähnlich, aber E basiert mehr auf dem geschlossenen Aufbau von C. Jemand, der vertraut ist mit C oder Modula-2/Pascal, wird leicht E lernen. Er benötigt nur die Besonderheiten von E-Programmiertechniken und die Unterschiede zu anderen Programmiersprachen. Dieser Kurs wendet sich deshalb an Menschen, die bisher nicht viel programmiert haben und kann deshalb zu einfach für fachkundige Programmierer sein, welche das `E Reference Manual' als angemessen finden. Teil Eins (dieser Teil) geht auf einige der Grundlagen der E Sprache und programmieren, im allgemeinen, ein. Teil Zwei geht tiefer in E, widmet sich mehr den komplexen Bestandteilen und den einmaligen Funktionen von E. Teil Drei stellt ein paar Beispielprogramme dar, welche ein Stück länger sind als die Beispiele in den anderen Teilen. Schließlich behandelt Teil Vier den Anhang, in welchem Du einige andere interessante Information finden wirst. Next Step:  Ein einfaches Programm  ======================================================================== Ein einfaches Programm ====================== Wenn Du dieses immer noch liest, dann bist Du wahrscheinlich verzweifelt dabei, in E zu programmieren, aber du weißt nicht, wie Du starten sollst. Wir werden deshalb in die Tiefe springen mit dem Erfolg, daß wir ein kleines Beispiel programmieren. Zwei Dinge wirst Du vor dem Start allerdings wissen müßen: wie benutze ich einen Text Editor und die Shell/CLI. Next Step:  Der Code   Kompilieren   Ausführung  ======================================================================== Der Code -------- Gebe die folgenden Zeilen von E-Code mit einem Text-Editor ein und speichere es mit dem File-Namen simple.e (Achte darauf, daß Du jede Zeile EXAKT abtipst. Gebe die angezeigten Buchstaben ein und drücke am Ende jeder Zeile die RETURN bzw. ENTER-Taste): PROC main() WriteF('Mein erstes Programm.') ENDPROC Versuche nicht, etwas hiervon abweichendes einzugeben. Und jetzt zu dem Code: die Eingabe als Groß- bzw. Kleinschreibung in jedem Wort ist ENTSCHEIDEND!!! Wenn Du ein echter Anfänger bist wirst Du Schwierigkeiten haben, das ' Zeichen zu finden. Auf meiner deutschen Tastatur mußt Du dazu die -Taste und GLEICHTEITIG die <Ä> Taste drücken. ======================================================================== Kompilieren ----------- Nachdem Du das File gespeichert hast (vorzugsweise in der RAM-Disk, weil es ein kleines Programm ist), kannst Du den E-Compiler benutzen, um es in ein ausführbares Programm umzuwandeln. Alles was Du dazu brauchst ist das File ec in Deinem C:-Verzeichnis oder irgendwo sonst in Deinem Suchpfad. Angenommen, du hast dieses getan und du hast eine Shell/CLI laufen, gebe das folgende ein, nachdem Du zu dem Verzeichnis gewechselt bist, in welchem du dein neues File gespeichert hast: ec simple Wenn alles in Ordnung war wirst Du von dem E Compiler begrüßt. Wenn irgend was falsch gewesen ist dann überprüfe nochmal den Inhalt des Files simple.e bzw. ob Du Dich in dem gleichen Verzeichnis befindest, in welchem das Programm gespeichert ist und daß das Programm ec sich in deinem C: Verzeichnis befindet (oder in dem Suchpfad). ======================================================================== Ausführung ---------- Nachdem alles i.O. war kannst Du dein erstes Programm durch die folgende Eingabe im CLI ausführen: simple Als Hilfe ist hier nochmals die vervollständige Beschreibung der ganzen Kompilation und Ausführung enthalten (der CLI-Prompt, unten, ist der Teil des Textes, der mit 1. beginnt und mit > endet): 1.Workbench3.0:> cd ram: 1.Ram Disk:> ec simple Amiga E Compiler/Assembler/Linker v2.1b (c) 91/92/93 $#%! lexical analysing ... parsing and compiling ... no errors 1.Ram Disk:> simple Mein erstes Programm.Ram Disk:> Der Bildschirm sollte etwas ähnliches zeigen, wenn alles O.K. war. Beachte wie die Ausgabe von dem Programm vor dem Prompt steht (die letzte Zeile). Wir werden dieses noch korrigieren. ======================================================================== Das Verständnis für ein einfaches Programm ****************************************** Um das Beispielprogramm zu verstehen brauchen wir ein paar Dinge. Die Beobachtung, die Du gemacht hast, ist, daß alles, was passierte, die Ausgabe einer Nachricht war und daß diese Nachricht ein Teil einer Zeile gewesen ist, die wir im Programm geschrieben haben. Die erste Sache, die wir nun machen wollen, ist das Verändern dieser Nachricht. Next Step  Verändern der Nachricht   Procedures   Parameter   Zeichenketten   Gestaltung, Übersichtlichkeit und lesbarkeit   Das einfache Programm  ======================================================================== Verändern der Nachricht ======================= Editiere jetzt das File, so daß die Zeile zwischen den beiden '-Zeichen eine andere Nachricht ist (z.B. 'Ich liebe meinen Amiga' oder was immer Dir einfällt). Kompiliere es wieder, indem Du wie zuvor beschrieben vorgehst. Benutze nicht irgendwelche '-Zeichen ausserhalb der Nachricht. Wenn alles in Ordnung war und Du das Programm wieder startest wird es jetzt eine andere Nachricht ausgeben. Wenn etwas schief gegangen ist, dann vergleiche den Inhalt deines File mit dem Original und gehe auf Nummer sicher, daß der einzige Unterschied die Nachricht zwischen den '-Zeichen ist. Next Step:  Gedanken zu unserem Beispiel   Kurzer Überblick  ======================================================================== Gedanken zu unserem Beispiel ---------------------------- Einfaches Nachdenken ist ein guter Weg zum selberlernen und um Dich an diesem einfachen Beispiel zu ermutigen. Strebe nicht zu weit und wenn du anfängst verworren zu sein, dann gehe einfach zu dem Beispiel zurück! ======================================================================== Kurzer Überblick ---------------- Wir werden ausführlich die wichtigsten Teile des Programms in der folgende Sektion aufzählen, aber wir brauchen zuerst einen Gesamtüberblick. Hier ist eine kurze Beschreibung von einigen grundlegenden Sachen: * Procedures: Wir definieren eine Procedure mit dem Namen main und benutzen die (eingebaute) ProcedurWriteF. Eine Procedure kann man sich vorstellen wie ein kleines Programm mit einem Namen. * Parameter: Die Nachricht in Klammern nach WriteF in unserem Programm ist der Parameter von WriteF. Dieses sind die Daten, welche die Procedure ausgeben soll. * Strings: Die Nachricht, die wir an WriteF weitergegeben haben, ist eine Folge von Buchstaben, die in den '-Zeichen eingeschlossen sind. Dieses wird als string bezeichnet. ======================================================================== Proceduren ========== Wie oben erwähnt kann man sich eine Procedure als kleines Programm mit einem Namen vorstellen. Tatsache ist, wenn ein E Programm ausgeführt wird, daß die Prozedure main aufgerufen und gestartet wird. Deshalb, wenn dein E Programm irgend etwas tun soll mußt Du die Procedure main definieren. Andere (eingebaute oder Benutzer-definierte) Proceduren können hiervon gestartet werden (wie wir es mit WriteF im Beispiel getan haben). Z.B., wenn die Procedure fred die Procedure barney aufruft, dann wird der Code (oder das Mini-Programm) von barney ausgeführt. Dieses darf wiederum andere Proceduren aufrufen und wenn die Ausführung dieses Code abgeschlossen ist, dann werden die nächsten Instruktionen von der Procedure fred ausgeführt (und dieses ist, im allgemeinen, die nächste Zeile der Procedure). Wenn das Ende der Procedure main erreicht ist, dann wird das Programm beendet. Wie auch immer, viel kann sich zwischen dem Begin und dem Ende einer Procedure ereignen, und manchmal wird das Programm nie beendet. Alternativ kann das Programm auch kaputt gehen und dann ereignen sich seltsame Dinge in deinem Computer ...... ;-) Next Step:  Procedure Definition   Procedure Ausführung   Wir weiten das Beispiel aus ======================================================================== Procedure Definition -------------------- Proceduren werden mit dem Schlüsselwort PROC definiert, gefolgt von dem neuen Proceduren Namen (in Kleinbuchstaben), einer Beschreibung der Parameter, die sie benötigt (in Klammern), eine Folge von Zeilen, die den Code der Procedure enthalten und dann, als Ende der Procedure, das Schlüsselwort ENDPROC. Schaue wieder auf das Beispiel-Programm um die verschiedenen Teile zu sehen. Siehe auch Der Code. ======================================================================== Procedure Ausführung -------------------- Proceduren können aufgerufen (oder ausgeführt) werden von einer beliebigen Stelle in einer anderen Prozedur. Du tust dies, indem Du den Namen angibst, gefolgt von einige Daten in Klammern. Schaue dir den Aufruf von WriteF im Beispielprogramm an. Siehe auch Der Code. ======================================================================== Wir weiten das Beispiel aus --------------------------- Nun wollen wir mal sehen, wie wir das Beispielprogramm so verändern, daß eine andere Procedure definiert werden kann: PROC main() WriteF('Mein erstes Programm') fred() ENDPROC PROC fred() WriteF('...etwas verbessert') ENDPROC Diese scheint kompliziert zu sein, aber in Wahrheit ist es sehr einfach. Alles, was wir getan haben, ist die Definition einer zweiten Procedure mit dem Namen fred, welche genau das gleiche macht wie unser vorheriges Beispielprogramm --- es gibt eine Nachricht aus. Wir haben diese Prozedure in der main Procedure aufgerufen und zwar genau nach der Zeile, die die originale Nachricht ausgegeben hat. Deshalb wird die Nachricht in fred nach dieser Nachricht ausgegeben. Kompiliere das Programm, wie zuvor beschrieben, und starte es, damit Du meinen Worten glauben schenken kannst. ======================================================================== Parameter ========= Im allgemeinen möchten wir Proceduren, die mit besonders Daten arbeiten. In unserem Beispiel haben wir gewollt, daß die WriteF Procedure eine besondere Nachricht ausgibt. Wir haben die Nachricht als einen Parameter (oder als Argument) an WriteF weitergegeben, indem wir diese zwischen den Klammern (dem ( und )-Zeichen) geschrieben haben. Als wir die fred Procedure aufgerufen haben war es nicht erforderlich, irgendwelche Daten weiterzugeben, so daß wir den Inhalt in den Klammern leer gelassen haben. Falls wir eine Procedure definiert haben, die mit Daten arbeitet, und wir haben diese Daten vom Namen und den Typen definiert, dann rufen wir diese Procedure auf, indem wir die dazugehörigen Daten mitgeben, die diese benutzt. Beachte, daß die Procedure fred (wie die Procedure main) leere Klammern in ihrem Definitionsteil hat. Dieses bedeutet, daß dieser Procedure keine Daten beim Aufruf übergeben werden. Bevor wir unsere eigene Procedure definieren, die Parameter beinhaltet, müssen wir noch etwas über Variable lernen. Wir werden dieses im nächsten Kapitel tun. Siehe auch Globale und lokale Variablen. ======================================================================== Strings ======= Eine Folge von Buchstaben zwischen zwei '-Zeichen wird String genannt. Fast jedes Zeichen kann in einem String benutzt werden, obwohl das \ und das '-Zeichen eine besondere Bedeutung hat. Als Beispiel, ein Zeilenvorschub wird durch die beiden Zeichen \n erreicht. Wir wissen jetzt, wie wir die Nachricht nun so stoppen können, daß diese nicht im Prompt erscheint. Verändere jetzt das Programm wie folgt: PROC main() WriteF('Mein erstes program\n') fred() ENDPROC PROC fred() WriteF('...etwas improvisiert\n') ENDPROC Kompiliere es, wie zuvor beschrieben, und starte es. Du wirst beobachten, daß die Nachrichten jetzt in jeweils einer einzelnen Zeile erscheinen und das die zweite Nachricht von dem Prompt jetzt getrennt ist. Wir haben jetzt also das Zeilenvorschub-Problem geheilt, über das wir vorher gespottet haben (siehe auch Ausführung). ======================================================================== Gestaltung, Übersichtlichkeit und Lesbarkeit ============================================ Das Beispiel ist in zwei Proceduren gewachsen, eine mit dem Namen main und eine mit dem Namen fred. Wie auch immer, wir können das gleiche auch mit nur einer Procedure erreichen: PROC main() WriteF('Mein erstes program\n') WriteF('...etwas improvisiert\n') ENDPROC Was wir getan haben war das ersetzen des Proceduren-Aufrufs von fred durch den Code, den sie darstellt (dieses wird auch inlining der Procedure genannt). Tatsache ist, das fast alle Programme leicht umgeschrieben werden können um alles auszuschalten, mit Ausnahme der main Procedure. Trotzdem, das aufsplitten eines Programmes in mehrere Proceduren resultiert in einem mehr lesbaren Code. Es ist auch hilfreich, die Proceduren so zu benennen, daß ihre Funktion offensichtlich werden, so daß damit unsere Procedure fred umbenannt werden könnte in message oder etwas ähnlichem. Ein gut-geschriebenes Programm in diesem Stil kann wie englisch gelesen werden (oder irgendwelche andere sprechende Sprache). Ein anderer Grund für Proceduren ist leicht zu portierender Code, besser als immer wieder neue Proceduren zu schreiben, wenn Du diese benötigst. Bild Dir ein, Du willst immer die gleiche, lange Nachricht öfters in deinem Programm drucken -- dann mußt Du dieses entweder jedesmal in Deinem Programm schreiben oder du schreibst es einmal in eine Procedure und rufst diese Procedure auf, wann immer du die Nachricht drucken möchtest. Die Benutzung einer Procedure hat auch den Vorteil, daß Du nur eine Kopie dieser Nachricht hast, die Du auch nur ein einziges mal ändern mußt, wenn es den so sein sollte. ======================================================================== Das einfache Programm ===================== Das einfache Programm wird jetzt (hoffentlich) einfach erscheinen. Das einzige, daß noch nicht erklärt worden ist, ist die eingebaute Procedure WriteF. E hat viele eingebaute Proceduren und später werden wir einige im Detail Besprechen. Die erste Sache die wir tun werden, ist das verändern von Daten. Dieses ist das einzige was ein Computer die ganze Zeit tut -- er akzeptiert Daten von einigen Quellen (möglicherweise dem Benutzer), verändert diese auf unterschiedliche Arten (speichert diese, möglicherweise, auch noch irgendwo hin) und gibt neue Daten aus (gewöhnlich auf einem Bildschirm oder Drucker). Das einfache Beispielprogramm hat dieses alles getan, ausgenommen die ersten beiden Stufen, die hierbei sehr simpel gewesen sind. Du erzähltes dem Computer, das er das kompilierte Programm ausführen sollte (dieses war eine Benutzer-Eingabe) und die eigentlichen Daten (die Nachricht, die ausgedruckt werden sollte) wurde vom Programm verarbeitet. Diese Daten wurden durch die Übergabe als ein Parameter an WriteF so verändert, daß dann einige schlaue Sachen auf dem Bildschirm ausgegeben wurden. Um unsere Daten selber verändern zu können müssen wir etwas über Variablen und Ausdrücke lernen. ======================================================================== Variablen und Ausdrücke *********************** Jeder, der schon irgendwelches Schul-Algebra angewendet hat, wird wahrscheinlich wissen, was eine Variable ist -- es ist benanntes Stück von Daten. In Algebra sind diese Daten gewöhnlich Zahlen, aber in E kann es alles mögliche sein (z.B. ein String). Die Veränderung von Daten, wie die Addition von zwei Zahlen, ist bekannt als Ausdruck. Mit vielen kleinen Ausdrücken kann man größere Ausdrücke aufbauen. Als Beispiel, 1+2 ist ein Ausdruck, und damit auch 6-(1+2). Das gute an dieser Sache ist, das Du Variablen an Stelle von Daten in Ausdrücken benutzen kannst. D.h., wenn x die Zahl 1 darstellt und ein stellt 5 dar, dann stellt der Ausdruck ein-x die Zahl 4 dar. In den nächste zwei Sektionen schauen wir uns an, was für Arten von Variablen du definieren kannst und was für unterschiedliche Arten von Ausdrücken es gibt. Next Step:  Variablen   Ausdrücke  ======================================================================== Variablen ========= Variablen in E können viele unterschiedliche Arten von Daten darstellen (genannt Typen). Wie auch immer, bevor eine Variable benutzt werden kann muß sie erstmal definiert werden, und dieses wird Deklaration einer Variable genannt. Eine Variablen-Deklarationen entscheidet auch darüber, ob die Variable im ganzen Programm verfügbar ist oder nur im Code einer Procedure (d.h., ob die Variable global oder lokal ist). Schließlich können die in einer Variablen gespeicherten Daten durch Zuweisungen geändert werden. Die folgenden Sektionen behandeln diese Punkte ausführlicher. Next Step:  Variablen-Typen   Variablen Deklarationen   Zuweisungen   Globale und lokale Variablen   Änderung unseres Beispiels  ======================================================================== Variablen-Typen --------------- In E ist eine Variable eine Speicherstelle für Daten (und dieser Speicher ist ein Teil des Amigas RAM). Unterschiedliche Arten von Daten erfordern unterschiedliche Speichergrößen. Wie auch immer, Daten können in Typen zusammengefasst werden und zwei Stücke von Daten vom gleichen Typ erfordern den gleichen Speicherbedarf. Jede Variable hat einen zugewiesenen Typ und dieser diktiert die erforderliche Speichergröße, die genutzt wird. Um mehr wirtschaftlicher zu sein werden die Variablen in E mit dem Typ LONG gespeichert. Dieser Typ kann Werte zwischen -2.147.483.648 und 2.147.483.647 annehmen, was normalerweise mehr als genügend ist. Es gibt auch andere Typen wie INT und LIST und noch mehr zusammengesetzte Sachen, die man mit Typen machen kann, aber fürs erste ist es genug, etwas über den Type LONG zu erfahren. ======================================================================== Variablen Deklarationen ----------------------- Variablen müssen deklariert werden, bevor Du sie benutzt. Sie werden deklariert, indem Du das DEF-Schlüsselwort, gefolgt von einer (durch ein Komma getrennten) Liste mit den Namen der zu deklarierenden Variablen, benutzt. Diese Variablen sind alle vom Typ LONG (später werden wir sehen, wie Du Variablen mit anderen Typen deklarieren kannst). Einige Beispiele werde hoffentlich manche Dinge erhellen: DEF x DEF a, b, c Die erste Zeile erklärt die einzelne Variable x, die zweite die Variablen a, b und c, alles in einem Schritt. ======================================================================== Zuordnung ---------- Die Daten, die in Variablen gespeichert sind, können normalerweise durch Zuordnungen geändert werden. Eine Zuordnung wird gemacht, indem der Variablenname und ein Ausdruck, der die neuen zu speichernden Daten beschreibt, genutzt wird. Das Symbol := trennt die Variable von dem Ausdruck. Zum Beispiel, der folgende Code speichert die Zahl zwei in die Variable x. Die linke Seite von := enthält den Namen der Variable, die verändert wird (x in diese Fall) und die rechte Seite ist ein Ausdruck, der den neuen Wert beschreibt (eben die Zahl zwei in diese Fall): x := 2 Das folgende, schon komplexere Beispiel, benutzt den Wert, der in der Variable gespeichert ist, vor der Zuordnung als ein Teil des Ausdruck für die neuen Daten. Der Wert des Ausdruck auf der rechten Seite von dem := ist der Wert, der in der der Variable x, plus eins, gespeichert wird. Dieser Wert wird dann in x gespeichert, wobei die alten Daten Überschrieben werden (Damit wird der Effekt erreicht, das x Inkrementiert (=erhöht) wird): x := x + 1 Dieses wird in dem nächsten Beispiel klarer, in welchem nicht der Wert geändert wird, der in x gespeichert ist. Tatsache ist, dieses Stück Code ist eine Verschwendung von CPU Zeit, weil alles, was es tut, ist auf den Wert zu schauen, der in x gespeichert ist um ihn dann wieder zurückzuspeichern! x := x ======================================================================== Globale und lokale Variablen (und Proceduren-Parameter) ------------------------------------------------------- Es gibt zwei Arten von Variablen: globale und lokale. Daten, die in globalen Variablen gespeichert sind, können gelesen und geändert werden von allen Proceduren, aber die Daten, die in lokalen Variablen gespeichert sind, können nur der Procedure zugänglich gemacht werden, in welcher sie lokal sind. Globale Variablen müßen vor der ersten Procedure definiert werden. Lokale Variablen werden in in der Procedure deklariert, in welcher sie lokal sind (also zwischen PROC und ENDPROC). Zum Beispiel, der folgende Code erklärt eine globale Variable w und die lokalen Variablen x und y: DEF w PROC main() DEF x x:=2 w:=1 fred() ENDPROC PROC fred() DEF y y:=3 w:=2 ENDPROC Die Variable x ist lokal zu der Procedure main, und y ist lokal zu fred. Die Proceduren main und fred können den Wert der Variable w lesen und ändern aber fred kann nicht den Wert von x lesen oder ändern (weil diese Variable lokal ist (in main). Also kann main auch nicht die Variable y lesen oder ändern. Die lokalen Variablen einer Procedure sind deshalb vollständig unterschiedlich zu den lokalen Variablen anderer Proceduren. Aus diesem Grund können sie den gleichen Namen erhalten, ohne das es zu einem Durcheinander kommt. Somit, wie in dem obigen Beispiel, kann die lokale Variable y in fred auch mit x deklariert werden und das Programm wird weiterhin exakt das gleiche tun: DEF w PROC main() DEF x x:=2 w:=1 fred() ENDPROC PROC fred() DEF x x:=3 w:=2 ENDPROC Dieses arbeitet, weil das x in der Zuordnung in fred nur auf die lokale Variable x von fred zugreifen kann (das x in main ist lokal zu main, damit ist es fred nicht zugänglich). Wenn eine lokale Variable für eine Procedure den gleichen Name wie eine globale Variable hat, dann wird im restlichen Teil der Procedure nur auf die lokale Variable zugegriffen. Daraus folgt, daß die globale Variable nicht in der Procedure zugänglich gemacht werden kann und dieses wird descoping der globalen Variable genannt. Die Parameter einer Procedure sind lokale Variablen für diese Procedure. Wir haben gesehen, wie Werte als Parameter an eine aufgerufene Procedure übergeben werden (die Benutzung von WriteF im Beispiel), aber bis jetzt waren wir nicht fähig, eine Procedure zu definieren, die Parameter übernimmt. Jetzt wissen wir ein bischen über Variablen und können loslegen: DEF y PROC onemore(x) y:=x+1 ENDPROC Dieses ist nicht ein vollständiges Programm, also versuche nicht, es zu kompilieren. Hauptsächlich haben wir eine Variable y deklariert (welche vom Typ LONG ist) und eine Procedure onemore. Die Procedure wurde mit einem Parameter x definiert und dieses ist eine (lokale) Variablen-Deklarationen. Wenn onemore aufgerufen wird, dann muß ein Parameter geliefert werden und dieser Wert wird in der (lokalen) Variable x vor Ausführung des onemore's Code gespeichert. Der Code speichert den Wert von x plus eins in der (globalen) Variable y. Im folgenden einige Beispiele, wie man onemore aufruft: onemore(120) onemore(52+34) onemore(y) Eine Procedure kann so definiert werden, daß sie irgendwelche Anzahl von Parameter übernimmt. Wie Unten, die Procedure addthem wurde definiert, um zwei Parameter aufzunehmen (a und b), so daß sie immer mit zwei Parametern aufgerufen werden muß. Beachte, daß die Parameterwerte in den Parameter-Variablen (a and b) gespeichert und im Code der Procedure verändert werden können: DEF y PROC addthem(a, b) a:=a+2 y:=a*b ENDPROC Das folgende sind einige Beispiele, wie man addthem aufrufen kann: addthem(120,-20) addthem(52,34) addthem(y,y) ======================================================================== Veränderung unseres Beispiels ----------------------------- Bevor wir unser Beispiel verändern müßen wir etwas über WriteF lernen. Wir wissen schon, daß der Buchstaben \n in einem String einen Zeilenvorschub bewirkt. Wie auch immer, es gibt einige andere wichtige Kombination von Zeichen in einem String und einige sind speziell für Proceduren wie WriteF bestimmt. Eine dieser Kombinationen ist \d, welche leichter zu beschreiben ist, nachdem wir unser geändertes Beispiel gesehen haben: PROC main() WriteF('Mein erstes Programm\n') fred() ENDPROC PROC fred() WriteF('...bringt Dir die Zahl \d\n', 236) ENDPROC Du kannst dazu fähig sein, zu Vermuten, was geschehen wird, aber kompiliere es und probiere es aus. Wenn alles arbeitet wirst du sehen, daß die zweite Nachricht die Zahl ausdruckt, die wir als zweiten Parameter an WriteF weitergegeben haben. Das ist was die \d Kombination tut--es markiert die Stelle im String, an welcher die Zahl gedruckt werden soll. Hier ist die Ausgabe, die das Beispiel erzeugen sollte: Mein erstes Programm ...bringt Dir die Zahl 236 Versuche die nächste Änderung: PROC main() WriteF('Mein erstes Programm\n') fred() ENDPROC PROC fred() WriteF('...der Zahl \d ist ganz Nett\n', 16) ENDPROC Dieses ist sehr ähnlich und zeigt, daß \d wirklich die Stelle markiert, an der die Zahl gedruckt werden soll. Hier ist wieder die Ausgabe, die es erzeugen sollte: Mein erstes Programm ...die Zahl 16 ist ganz Nett Nun werden wir versuchen, zwei Zahlen zu drucken: PROC main() WriteF('Mein erstes Programm\n') fred() ENDPROC PROC fred() WriteF('...bringt Dir die Zahlen \d und \d\n', 16, 236) ENDPROC Weil wir zwei Zahlen gedruckt haben brauchen wir zwei mal \d und wir brauchen zwei Zahlen als Parameter in der Reihenfolge, in welche wir sie drucken möchten. Die Zahl 16 wird deshalb vor dem Wort `und' und vor der Zahl 236 gedruckt. Hier ist die Ausgabe: Mein erstes Programm ...bringt Dir die Zahlen 16 und 236 Wir können jetzt einen großen Schritt vorwärts machen und die Zahlen als Parameter der Procedure fred übergeben. Sieh Dir mal die Unterschiede zwischen diesem nächsten Beispiel und dem vorherigen Beispiel an: PROC main() WriteF('Mein erstes Programm\n') fred(16, 236) ENDPROC PROC fred(a,b) WriteF('...bringt Dir die Zahlen \d und \d\n', a,b) ENDPROC Diesmal haben wir die (lokalen) Variablen a und b an WriteF übergeben. Dieses ist genau das gleiche als wenn wir den Werte direkt übergeben (welches genau das war, was wir im vorhergehenden Beispiel getan haben) und damit ist die Ausgabe die gleiche. Im nächsten Abschnitt verändern wir die Variablen, indem wir ein bischen Arithemetik mit a und b machen. Anschließend benutzen wir WriteF um die Resultate zu drucken. ======================================================================== Ausdrücke ========= Die E Sprache schließt die normalen mathematischen und logischen Operatoren ein. Diese Operatoren können kombiniert werden mit Werten (gewöhnlich aus Variablen) um Ausdrücke mit neuen Werten auszugeben. Der folgende Abschnitt behandelt diesen Punkt ausführlicher. Next Step:  Mathematik   Logik und Vergleiche   Reihenfolge und Gruppierungen  ======================================================================== Mathematik ---------- Alle Standard mathematik Operatoren werden von E unterstützt. Du kannst Addieren, Subtrahieren, Multiplizieren und Dividieren. Andere Funktionen, wie Sinus, Modulus und Wurzelziehen können auch genutzt werden, weil sie ein Teil der Amiga System Libraries sind, aber wir brauchen nur etwas zu wissen über einfache Mathematik, jedenfalls in diesem Moment. Das +-Zeichen wird für Addition, - für Subtraktion, * für Multiplikation (es ist das naheliegensdste, daß du eine Multiplikation ohne Benutzung der x-Taste bekommst) und / für Division (sei vorsichtig, um nicht verwirrt zu werden, daß Du das \ in Zeichenketten benutzt und das / für die Division). Im Folgenden einige Beispiele von Ausdrücken: 1+2+3+4 15-5 5*2 330/33 -10+20 3*3+1 Jeder dieser Ausdrücke ergibt zehn als Resultat. Das letzte Beispiel ist sehr vorsichtig geschrieben um die Reihenfolge richtig zu betrachten (siehe auch Reihenfolge und Gruppierungen). ======================================================================== Logik und Vergleiche -------------------- Logik ist das Herz eines Computer. Sie vermuten, was als nächstes zu tun ist. Betrachten wir den Passwort-Schutz von vielen Spielen. Der Computer muß entscheiden, ob Du die richtige Zahl oder das richtige Wort eingegeben hast, bevor er dich spielen läßt. Wenn du das Spiel spielst gibt es fortwährend Vergleiche: hat Dein Laserstrahl den Alien getroffen, hast Du ein Leben verloren etc. Logik kontrolliert den Ablauf des Programmes. In E stellen die Konstanten TRUE und FALSE die Werte wahr und falsch dar und die Operatoren AND und OR sind die Standard Logik-Operatoren. Die Vergleichsoperatoren sind = (gleich zu), > (größer als), < (kleiner als), >= (größer als oder gleich zu), <= (kleiner als oder gleich zu) und <> (ungleich zu). Alle folgende Ausdrücke sind wahr: TRUE TRUE AND TRUE TRUE OR FALSE 1=1 2>1 3<>0 Und diese sind alle falsch: FALSE TRUE AND FALSE FALSE OR FALSE 0=2 2<1 (2<1) AND (-1=0) Das letzte Beispiel muß Klammern benutzen. Wir werden im nächsten Abschnitt sehen, warum (es hat, wieder, was zu tun mit der Reihenfolge). Die beiden Werte TRUE und FALSE sind eigentlich Zahlen. Hiermit arbeitet das logische System in E. TRUE ist die Zahl -1 und FALSE ist Null. Die logischen Operatoren AND und OR erwarten solche Zahlen als ihre Parameter. Tatsache ist, die AND und OR Operatoren sind in Wirklichkeit Bit-arbeitende Operatoren (siehe auch Bitweises AND und OR ), so das jede nicht-Null-Zahl TRUE wird. Es kann manchmal günstig sein, sich auf diese Kenntnis zu verlassen, denn meistens wird dieses vorgezogen (und mehr lesbar) um eine etwas deutlichere Form zu nutzen. Diese Fakten können Ursache sein für ein paar spitzfindige Probleme wie wir noch im nächsten Abschnitt sehen werden. ======================================================================== Reihenfolge und Gruppierungen ----------------------------- Aus der Schule wissen viele von uns, daß Multiplikation vor Additionen geht. In E ist dieses aber anders--dort gibt es keine solche Reihenfolge. Dieses bedeutet, daß Ausdrücke wie 1+3*3 nicht das selbe Resultat ergeben wie es im Mathematischen erwartet wird. Tatsache ist, 1+3*3 stellt die Zahl 12 in E dar. Diese ist so, weil die Addition, 1+3 vor der Multiplikation gemacht wird! Wenn die Multiplikation vor der Addition geschrieben würde, dann würde diese zuerst ausgeführt werden (wie wir normalerweise erwarten würden). Deshalb, 3*3+1 stellt die Zahl 10 in E dar und in der Schul-Mathematik. Um diesen Unterschied umgehen zu können nutzen wir Klammern, um den Ausdruck zu gruppieren. Wenn wir schreiben 1+(3*3) würde das Resultat 10 sein. Dieses, weil wir E zwingen, erst die Multiplikation zu tun. Diese scheint am Anfang ein Problem zu sein, es ist aber eigentlich viel besser als eine Menge von Regeln zu lernen, um zu entscheiden, welcher Operator zuerst ausgeführt wird (in C kann dieses sehr unangenehm sein und Du gewöhnst Dir an, die Klammern zu schreiben um sicher zu sein!). Das logische Beispiele von oben beinhaltet den Ausdruck: (2<1) AND (-1=0) Dieser Ausdruck ist falsch. Wenn wir die Klammern auslassen würde E es als folgendes ansehen: ((2<1) AND -1) = 0 Nun sollte die Zahl -1 nicht wirklich genutzt werden, um es logisch mit AND zu vergleichen, aber wir wissen, daß TRUE die Zahl -1 ist, damit E es verstehen kann und der E Compiler sich nicht beschwert. Wir werden bald sehen, wie AND und OR wirklich arbeiten (siehe auch Bitweises AND und OR), aber für jetzt reicht es aus, zu wissen, was E für diesen Ausdruck berechnen würde: 1. Zwei ist nicht weniger als eins, damit kann 2<1 ersetzt werden durch FALSE. (FALSE AND -1) = 0 2.TRUE ist -1, damit können wir -1 durch TRUE ersetzen. (FALSE AND TRUE) = 0 3.FALSE UND TRUE ist FALSE. (FALSE) = 0 4.FALSE ist wirklich die Zahl Null, damit können wir es ersetzen mit Null. 0 = 0 5. Null ist gleich Null, damit ist der Ausdruck TRUE. TRUE Somit errechnet E den Ausdruck als wahr. Aber der originale Ausdruck (mit Klammern) war falsch. Klammern sind deshalb sehr wichtig! Es ist also sehr leicht, es richtig anzuwenden. ======================================================================== Programm Fluß-Kontrolle *********************** Ein Computer Programm führt öfters Wiederholungen als eine Serie von Anweisungen aus oder führt unterschiedliche Anweisungen gemäß dem Resultat von Entscheidung aus. Als Beispiel, ein Programm, das alle Zahlen zwischen eins und eintausend ausdruckt würde sehr lange und langweilig zu schreiben sein, wenn jede Drucke-Anweisung jedesmal vorgegeben werden muß--es würde viel besser sein, eine Variable zu nutzen und wiederholt den Wert zu drucken und die Variable zu inkrementieren. Manchmal gehen Dinge auch schief und ein Programm muß entscheiden, ob es weitermachen oder eine Fehlernachricht ausgeben soll und anhalten-- dieses Teil eines Programms wäre ein typisches Beispiel von einem bedingten Block. Next Step:  Bedingter Block   Schleifen  ======================================================================== Bedingter Block =============== Es gibt zwei Arten von bedingten Blöcken: IF und SELECT. Beispiele von diesen Blöcke sind unten als Fragmente eines E Codes wiedergegeben (zur Anmerkung, die Beispiele sind nicht vollständige E Programme). IF x>0 x:=x+1 WriteF('Inkrement: x ist jetzt \d\n', x) ELSEIF x<0 x:=x-1 WriteF('Dekrement: x ist jetzt \d\n', x) ELSE WriteF('Null; x ist 0\n') ENDIF In dem obigen IF Block, der erste Teil prüft ob der Wert von x größer ist als Null, und, wenn er es ist, wird x inkrementiert und der neue Wert wird gedruckt (mit einer Nachricht die sagt, es wird inkrementiert). Das Programm überspringt dann den Rest des Blocks und führt die Anweisungen aus, welche dem ENDIF folgen. Wenn, wie auch immer, x nicht größer als Null ist, dann wird der ELSEIF Teil geprüft. Ist x kleiner als Null wird es dekerementiert und gedruckt und der Rest des Blocks wird übersprungen. Wenn x nicht größer als Null und nicht kleiner als Null ist, dann werden die Anweisungen im ELSE Teil ausgeführt und die Nachricht gedruckt, daß x Null ist. Die IF Bedingung wird ausführlicher unten beschrieben.  IF block   IF Ausdruck  ======================================================================== IF Block ----------------- Der IF Block hat die folgende Form (die Stücke Ausdrücke sind Beschreibungen von Arten des E Codes, welche an diesem Punkt erlaubt sind -- sie sind nicht eigener E Code): IF Ausdruck A Anweisungen A ELSEIF Ausdruck B Anweisungen B ELSE Anweisungen C ENDIF Dieser Block bedeutet: * Wenn der Ausdruck A ist wahr (stellt TRUE oder irgendwelche nicht-Null Zahl dar) der Code, gekennzeichnet durch Anweisungen A, wird ausgeführt. * Wenn Ausdruck A falsch ist ( stellt FALSE oder Null dar) und Ausdruck B ist wahr dann wird Anweisung B ausgeführt. * Wenn beide Ausdruck A und Ausdruck B falsch sind dann wird Anweisung C ausgeführt. Es braucht keinen ELSE Zweig zu geben, aber wenn einer vorhanden ist, dann muß es der letzter Teil sein (sofort vor dem ENDIF). Es können egal wieviele ELSEIF Zweige zwischen dem IF und ELSE Zweig vorhanden sein. Eine alternative zu dieser senkrechten Form (wo jeder Teil eine getrennte Zeile ist) ist die waagerechte Form: IF Ausdruck THEN Anweisung A ELSE Anweisung B Dieses hat den Nachteil keines ELSEIF Zweiges und das alles in einer einzelnen Zeile steht. Beachte die Anwesenheit von dem THEN Schlüsselwort um den Ausdruck und die Anweisung zu trennen. Diese waagerechte Form ist ähnlich zu dem IF-Ausdruck, welcher unten beschrieben ist (siehe auch IF Ausdruck). Um einige Dinge klarer zu machen sind hier sind ein paar E Code Fragmente die die erlaubten IF Blöcke erläutern: IF x>0 THEN x:=x+1 ELSE x:=0 IF x>0 x:=x+1 ELSE x:=0 ENDIF IF x=0 THEN WriteF('x ist Null\n') IF x=0 WriteF('x ist Null\n') ENDIF IF x<0 Write('Negatives x\n') ELSIF x>2000 WriteF('Zu großes x\n') ELSIF (x=2000) OR (x=0) Write('Merkwürdiges x\n') ENDIF IF x>0 IF x>2000 WriteF('Großes x\n') ELSE WriteF('OK x\n') ENDIF ELSE IF x<-800 THEN WriteF('Kleines x\n') ELSE Write('Negatives OK x') ENDIF Im letzten Beispiel sind verschachtelte IF Blöcke vorhanden(ein IF Block in einem IF Block). Dort ist keine zweideutigkeit vorhanden, in welchen ELSE oder ELSEIF Teil welcher IF Block gehört, weil der Beginn und das Ende der IF Blöcke klar gekennzeichnet sind. Zum Beispiel, die erste ELSE Zeile kann nur interpretiert werden als ein Teil des inneren IF Blocks. Bei der Gestaltung der Bedingungen des IF und ELSEIF Teils werden keine überlappungen zugelassen ( an mehr als einer Stelle ist die Bedingungen wahr). Wenn sie es doch tun, wie auch immer, dann wird die erste zuerst ausgeführt. Deshalb, die folgenden zwei Fragmente von E Code tun das gleiche: IF x>0 WriteF('x ist größer als Null\n') ELSEIF x>200 WriteF('x ist größer als 200\n') ELSE WriteF('x ist auch klein\n') ENDIF IF x>0 WriteF('x ist größer als Null\n') ELSE WriteF('x ist auch klein\n') ENDIF Der ELSEIF Teil des ersten Fragments prüft, ob x größer als 200 ist. Aber, wenn es das ist, die Prüfung in dem IF Teil würde wahr werden (x ist nunmal größer als Null wenn es größer als 200 ist) und damit wird nur der Code in dem IF Teil ausgeführt. Der ganze IF Block verhält sich, als wenn ELSEIF nicht vorhanden wäre. ======================================================================== IF Ausdruck -------------------- IF ist ein Mittel, das benutzt werden kann für die Zusammensetzung von IF Ausdrücken. Der IF Block ist eine Anweisung und es kontrolliert, welche Zeilen des Code ausgeführt werden, während der IF Ausdruck ein Ausdruck ist und seinen eigenen Wert kontrolliert. Zum Beispiel, der folgende IF Block IF x>0 y:=x+1 ELSE y:=0 ENDIF kann besser geschrieben werden, indem der IF Ausdruck genutzt wird: y:=(IF x>0 THEN x+1 ELSE 0) Die Klammern sind unwesentlich aber sie helfen, das Beispiel mehr lesbarer zu machen. Weil der IF Block zu wählen hat zwischen den zwei Zuordnungen zu y sind nicht nur die Zeilen des Codes unterschiedlich (sie sind beides Zuordnungen), es ist der Wert, der y zugewiesen wurde und damit den Unterschied ausmacht. Der IF Ausdruck macht dieses wesentlich klarer. Er führt den Wert auf dem gleichen Weg zu, wie der IF Block diesen zugewiesen hat. Wie du sehen kannst, IF Ausdrücke werden geschrieben wie die waagerechte Form der IF Blöcke. Wie auch immer, es muß ein ELSE Teil haben und es darf kein ELSEIF vorhanden sein. Dieses bedeutet, daß der Ausdruck immer einen Wert hat und er ist nicht überhäuft mit einer Anzahl von Wahlmöglichkeiten. Ärgere dich ncht zu viel über IF Ausdrücke, weil sie nur in einigen, wenigen Fällen nützlich sind und immer umgeschrieben werden können in einen IF Block. Laß uns sagen, daß sie sehr elegant und mehr lesbarer sind als der gleiche IF Block. ======================================================================== SELECT --------------- SELECT x CASE 0 WriteF('x ist Null\n') CASE 10 WriteF('x ist zehn\n') CASE -2 WriteF('x ist -2\n') DEFAULT WriteF('x ist nicht Null, zehn oder -2\n') ENDSELECT Der SELECT Block ist ähnlich zu dem IF Block--er unterscheidet sich in der Abhängigkeit des Wertes zu x. Wie auch immer, x wird nur geprüft gegen bestimmte Werte die in der Serie der CASE Anweisungen vorgegeben werden. Wenn es keinen Wert auf diese Weise zuweisen kann, dann wird der DEFAULT Teil ausgeführt. Der SELECT Block wird ausführlich unten beschrieben. Next Step:  SELECT block  ======================================================================== SELECT Block --------------------- Der SELECT Block hat folgende Form: SELECT variable CASE Ausdruck A Anweisungen A CASE Ausdruck B Anweisungen B DEFAULT Anweisungen C ENDSELECT Der Wert der ausgewählten Variable (gekennzeichnet durch variable im SELECT Teil) wird verglichen mit dem Resultat der Ausdrücke in jedem CASE Teil. Wenn einer passt werden die Anweisungen in dem (ersten) passenden CASE Teil ausgeführt. Es kann irgendeine Anzahl von CASE Teilen zwischen dem SELECT und DEFAULT Teil vorhanden sein. Wenn keines paßt, dann wird die Anweisung DEFAULT ausgeführt. Es braucht keinen DEFAULT Teil zu geben, aber wenn einer vorhanden ist, dann muß er der letzter Teil sein (sofort vor ENDSELECT). Es ist klar, daß SELECT Blöcke umgeschrieben werden können in IF Blöcke, wobei die Prüfungen in dem IF und ELSEIF Teil gleich sind. Als Beispiel, die folgenden Code-Fragmente sind gleich: SELECT x CASE 22 WriteF('x ist 22\n') CASE (y+z)/2 WriteF('x ist (y+x)/2\n') DEFAULT WriteF('x ist nichts bestimmtes\n') ENDSELECT IF x=22 WriteF('x ist 22\n') ELSEIF x=(y+z)/2 WriteF('x ist (y+x)/2\n') ELSE WriteF('x ist nichts bestimmtes\n') ENDIF Beachte, daß der IF und ELSEIF Teil von dem CASE Teil kommt, der ELSE Teil kommt von dem DEFAULT Teil und die Ordnung der Teile ist exakt wiedergegeben. Der Vorteil des SELECT Blocks ist, daß es viel leichter zu sehen ist, daß der Wert von x überall getestet wird und wir auch nicht x= in jeder Prüfung schreiben brauchen. ======================================================================== Schleifen ========= Schleifen sind alles was ein Programm braucht, um eine Anzahl von Anweisungen immer wieder auszuführen. Wahrscheinlich ist die einfachste Schleife die FOR Schleife. Es gibt andere Arten von Schleifen, aber sie sind leichter zu verstehen, wenn wir erst mal wissen, wie eine FOR Schleife benutzt wird. Next Step:  FOR Schleife   WHILE Schleife   REPEAT..UNTIL Schleife  ======================================================================== FOR Schleife --------------------- Wenn du ein Programm schreiben möchtest, daß die Zahlen von eins bis 100 ausdruckt, dann kannst Du entweder jede Zahl eingeben und deine Finger beerdigen oder du kannst eine einzelne Variable benutzen und eine kleine FOR Schleife. Versuche dieses E Programm zu kompilieren (das Leerzeichen nach dem \d wird gebraucht, um die zu druckenden Zahlen zu trennen): PROC main() DEF x FOR x:=1 TO 100 WriteF('\d ', x) ENDFOR WriteF('\n) ENDPROC Wenn du dieses startest, dann werden alle Zahlen von eins bis 100 gedruckt, gerade so, wie wir es gewollt haben. Es arbeitet, indem es die (lokale) Variable x benutzt, um die zu druckende Zahl festzuhalten. Die FOR Schleife startet, indem der Wert von x auf eins gesetzt wird (das Stück, daß wie eine Zuordnung aussieht). Dann werden die Anweisungen zwischen der FOR und ENDFOR Zeile ausgeführt (somit der Wert von x gedruckt). Wenn das Programm ENDFOR erreicht, dann inkrementiert es x und prüft, ob es größer als als 100 ist (die Grenze, die wir mit dem TO Teil gesetzt haben). Wenn das der Fall ist, dann wird die Schleife beendet und die Anweisungen nach dem ENDFOR ausgeführt. Wenn, wie auch immer, es nicht größer als 100 war, dann werden die Anweisungen zwischen den FOR und ENDFOR Zeilen immer wieder ausgeführt und x ist um eins größer, weil es inkrementiert wurde. Tatsache ist, dieses Programm tut genau das gleiche, wie das folgende Programm (die ... sind nicht E Code--es steht für 97 anderen WriteF Anweisungen): PROC main() WriteF('\d ', 1) WriteF('\d ', 2) ... ... WriteF('\d ', 100) WriteF('\n') ENDPROC Die allgemeine Form der FOR Schleife ist folgende: FOR var := Ausdruck A TO Ausdruck B STEP Zahl Anweisungen ENDFOR Das var-Stück steht für die Schleifen-Variable (in dem Beispiel oben war diese x). Der Ausdruck A gibt den Startwert für die Schleifen-Variable vor und der Ausdruck B stellt den letzten erlaubten Wert dar. Der STEP Teil erlaubt es den Wert anzugeben (durch eine Zahl), welcher zu der Schleifen-Variable nach jeder Schleife hinzuaddiert wird. Im Unterschied zu den Werten, die für den Start und das Ende vorgegeben werden (welche willkürliche Ausdrücke sein können) muß der STEP Wert eine eindeutige Zahl sein, z.B. eine Konstante (siehe Konstanten). Der STEP Wert ist normalerweise eins, wenn der STEP Teil nicht vorgegeben wird (wie in unserem Beispiel). Negative STEP Werte sind erlaubt, aber in diesem Fall wird am Ende jeder Schleife geprüft, ob der Wert der Schleifen-Variable kleiner als der Wert in dem TO Teil ist. Null ist nicht erlaubt als STEP Wert. Wie beim IF Block gibt es eine waagerecht Form der FOR Schleife: FOR var := Aus.A TO Aus.B STEP Aus.C DO Anweisung ======================================================================== WHILE Schleife ----------------------- Die FOR Schleife benutzt eine Schleifen-Variable und prüft, ob die Variable über die Grenze hinausgegangen ist. Eine WHILE Schleife erlaubt dir, deine eigene Schleifenprüfung anzugeben. Als Beispiel, dieses Programm tut das gleiche wie das Programm in dem vorherigen Abschnitt: PROC main() DEF x x:=1 WHILE x<=100 WriteF('\d ', x) x:=x+1 ENDWHILE WriteF('\n) ENDPROC Wir haben die FOR Schleife ersetzt durch eine Initialisierung von x und einer WHILE Schleife mit einer zusätzlichen Anweisung, um x zu inkrementieren. Wir können jetzt die innere Arbeit der FOR Schleife sehen und, tatsächlich, dieses ist genau das was dieFOR Schleife macht. Es ist wichtig zu wissen, daß unsere Prüfung x<=100 ausgeführt wird, BEVOR die Schleifenanweisungen ausgeführt werden. Dieses bedeutet, daß die Schleifen-Anweisungen kein einziges mal ausgeführt werden müssen. Als Beispiel, wenn wir die Prüfung x>=100 machen wäre es am Anfang der Schleife falsch (x ist initialisiert mit eins in der Anweisung vor der Schleife). Deshalb würde die Schleife sofort abgebrochen werden und die Ausführung würde nach dem ENDWHILE weitergehen. Hier ist ein komplizierteres Beispiel PROC main() DEF x,y x:=1 y:=2 WHILE (x<10) AND (y<10) WriteF('x ist \d und y ist \d\n', x, y) x:=x+2 y:=y+2 ENDWHILE ENDPROC Wir benutzen diesmal zwei (lokale) Variablen. Sobald eine von Ihnen zehn oder mehr ist wird die Schleife abgebrochen. Ein Blick auf den Code zeigt, daß x mit eins initialisiert und um zwei erhöht wird. Es wird deshalb immer eine ungerade Zahl sein. Dagegen wird y immer gerade sein. Die WHILE Prüfung zeigt, daß es keine Zahlen drucken wird, die größer oder gleich zehn sind. Von diesem und der Tatsache, daß x bei eins startet und y bei zwei können wir schließen, daß die letzten Zahlenpaare sieben und acht sein werden. Starte das Programm, um dieses zu bekräftigen. Es wird die folgende Ausgabe erzeugen: x ist 1 und y ist 2 x ist 3 und y ist 4 x ist 5 und y ist 6 x ist 7 und y ist 8 Wie die FOR Schleife gibt es eine waagerechte Form der WHILE Schleife: WHILE Ausdruck DO Anweisung Schleifenabbrüche sind immer ein großes Problem. FOR Schleifen erreichen garantiert ihre Grenze (wenn du nichts mit der Schleifen-Variable tust, sicherlich). Wie auch immer, WHILE Schleifen (und alle anderen Schleifen) starten immer wieder und werden nie beendet. Als Beispiel, wenn die Schleife prüft, ob 1<2 ist, dann würde dieses immer wahr sein und nichts, was die Schleife machen könnte, würde verhinderen, daß es wahr wäre! Du muß deshalb vorsichtig sein, daß deine Schleifen irgendwie beendet werden und dein Programm beendet werden kann. Es gibt einen Ausweg, um Schleifen zu beenden, indem die JUMP Anweisung benutzt, aber wir werden dieses erstmal ignorieren. ======================================================================== REPEAT..UNTIL Schleife ------------------------------- Eine REPEAT..UNTIL Schleife ist sehr ähnlich mit einer WHILE Schleife. Der erste Unterschied ist, wo du die Schleifenprüfung angibst und wann und wie die Prüfung geleistet wird. Um dieses zu Erläutern ist hier das Programm von den vorherigen zwei Abschnitten, umgeschrieben, um eine REPEAT..UNTIL Schleife zu benutzen (versuche, die Unterschiede herauszuarbeiten): PROC main() DEF x x:=1 REPEAT WriteF('\d ', x) x:=x+1 UNTIL x>100 WriteF('\n') ENDPROC Gerade in der WHILE Schleifen Version haben wir eine Initialisierung von x gemacht und eine zusätzliche Anweisung in der Schleife, um x zu inkrementieren. Wie auch immer, diesmal ist die Schleifenprüfung am Ende der Schleife aufgeführt (in dem UNTIL Teil) und die Prüfung wird nur am Ende jeder Schleife ausgeführt. Dieser Unterschied bedeutet, daß der Code in einer REPEAT..UNTIL Schleife IMMER einmal ausgeführt wird während der Code in einer WHILE Schleife evtl. niemals ausgeführt wird. Auch der logische Part wird wie folgt geprüft: eine REPEAT..UNTIL Schleife wird ausgeführt bis die Prüfung wahr ist, wohingegen in der WHILE Schleife das while ausgeführt wird, bis die Prüfung falsch ist. Deshalb, die REPEAT..UNTIL Schleife wird ausgeführt auch wenn die Prüfung falsch ist! Dieses erscheint auf den ersten Blick verwirrend, aber versuche doch einfach, den Code in Englisch zu lesen und Du erhälst die richtige Interpretation. ======================================================================== Zusammenfassung *************** Dieses ist das Ende von Teil Eins, welcher hoffentlich genug war, damit Du starten kannst. Wenn Du das Hauptkonzept verstanden hast, dann bist du in der guten Position, um Teil Zwei anzugreifen, welche die E Sprache ausführlicher erläutern wird. Dieses ist wahrscheinlich ein guter Zeitpunkt, um sich die Unterschiede eines Beispiele aus den vorherigen Abschnitten anzusehen, seitdem wir jetzt ein Stück von E nutzen. Die folgende Untersuchung benutzt das WHILE Schleifen Beispiel. Gerade um Sachen einfacher für das Nachvollziehen zu machen, wurde jede Zeile nummeriert (versuche nicht, es mit den Zeilennummern zu kompilieren!). 1. PROC main() 2. DEF x,y 3. x:=1 4. y:=2 5. WHILE (x<10) AND (y<10) 6. WriteF('x ist \d und y ist \d\n', x, y) 7. x:=x+2 8. y:=y+2 9. ENDWHILE 10. ENDPROC Hoffentlich bist Du in der Lage, alle aufgelisteten Funktionen in der untenstehenden Tabelle zu verstehen. Wenn nicht, dann brauchst Du nur zurückzugehen zu den vorherigen Kapiteln oder finde eine bessere Programmierhilfe als diese! Zeile(n) Übersicht --------------------------------------------------------- 1-10 Die Procedure Definition. 1 Die Deklaration der Procedure main ohne Parameter. 2 Die Deklarationen der lokalen Variablen x und y. 3, 4 Initialisierung von x und y mit zuordnenden Anweisungen. 5-9 Die WHILE Schleife. 5 Die Schleifenprüfung für die WHILE Schleife benutzt den logischen Operator AND, der Vergleichsoperator < und Klammern, um den Ausdruck zu gruppieren. 6 Der Aufruf der (eingebauten) Pocedure WriteF benutzt Parameter. Beachte den String, den Stellenhalter für Zahlen, \d, und den Zeilenvorschub, \n. 7, 8 Zuordnungen zu x und y, addiert zwei zu ihren Werten. 9 Das Kennzeichen für das Ende der WHILE Schleife. 10 Das Kennzeichen für das Ende der Procedure. ======================================================================== Format und Layout ***************** In diesem Kapitel schauen wir auf die Regeln, welche uns das Format und Layout des E Codes zeigen. Im vorherigen Teil sahen wir Beispiele von E Codes, die ganz einfach dargestellt und deren Struktur des Programms leicht ersichtlich war. Dieses war eine Sammlung und die E Sprache zwingt dich nicht dazu, E-Code auf diesem Wege zu schreiben. Wie auch immer, es gibt sichere Regeln die befolgt werden müßen. (Diese Kapitel beziehen sich auf einige Konzepte und Teile der E-Sprache, welche nicht in Teil Eins eingebunden worden sind. Laß dieses nicht aus - diese Dinge werden später in Kapiteln vorausgesetzt und es ist vielleicht eine gute Idee, diese Kapitel wieder zu lesen, wenn sie dar sind.) Next Step:  Bezeichner   Anweisungen   Abstand und Seperatoren   Kommentare  ======================================================================== Bezeichner ========== Ein Bezeichner ist ein Wort, welches der Compiler wörtlich interpretiert. Zum Beispiel, eine Variable ist ein Bezeichner, wie ein Schlüsselwort (z.B. IF), aber irgend etwas in einem String ist es nicht (z.B. fred in 'fred und wilma' ist kein Bezeichner). Bezeichner können aus Groß- oder Kleinbuchstaben, Zahlen und Unterstriche bestehen (das _ Zeichen). Es gibt hierbei zwei Bedingungen: 1. Der erste Buchstabe kann nicht eine Zahl sein (dieses würde Probleme mit numerischen Konstanten ergeben). 2. Die ersten paar Buchstaben eines Bezeichners sind entscheidend. Für Schlüsselwörter (z.B. ENDPROC), Konstanten (z.B. TRUE) und Assembler mnemonics (z.B MOVE.L) müssen die ersten zwei Buchstaben beide Großbuchstaben sein. Für eingebaute E-Funktionen oder Amiga System Proceduren/Funktionen muß der ersten Buchstabe groß und der zweite kleingeschrieben sein. Für alle anderen Bezeichner ( lokale, globale und Procedure-Parameter Variablen, Objekt Namen und Element-Namen, Procedur Namen und Code labels) muß der erste Buchstabe kleingeschrieben sein. Ausgenommen von diesen Regeln kannst Du dich frei entscheiden, wie Du Bezeichner schreiben willst, obwohl es vertretbar ist, alle Kleinbuchstaben für Variablen und alle Großbuchstaben für Schlüsselwörter und Konstanten zu verwenden. ======================================================================== Anweisungen =========== Eine Anweisung ist normalerweise eine einzelne Zeile mit einer Instruktion für den Computer. Jede Anweisung beansprucht normalerweise eine einzelnen Zeile. Soweit waren in unseren Beispiele nur zwei Arten von Anweisungen: die einzelne Zeilenanweisung und die Multi-Zeilen Anweisung. Die Zuordnungen waren einzelne Zeilenanweisungen und die senkrechte Form des IF Blocks ist ein Multi-Zeilenanweisung. Die waagerechte Form von IF Blöcken war eigentlich eine einzelne Zeilenanweisung des IF Block. Beachte, daß Anweisungen von anderen Anweisungen aufgebaut werden können, wie dieses der Fall für IF Blöcke war. Der Codeteil zwischen dem IF, ELSEIF, ELSE und ENDIF Zeilen ist eine Sequenz von Anweisungen. Einzelne Zeilenanweisungen können oft sehr kurz sein und du darfst einige von ihnen in eine einzelnen Zeile eingeben, ohne das die Zeile zu lang wird. Um dieses in E zu tun benutzt Du ein Semikolon (das ; Zeichen), um jede Anweisung in einer Zeile zu trennen. Zum Beispiel sind die folgenden Code-Fragmente gleich: fred(y,z) y:=x x:=z+1 fred(y,z); y:=x; x:=z+1 Auf der anderen Seite kannst Du zu lange Anweisungen Über einige Zeilen aufspalten. Diese ist ein bischen kniffliger, weil der Compiler sehen muß, daß du die Anweisung noch nicht beendet hast, wenn er das Ende der Zeile erreicht. Deshalb kannst Du eine Anweisung nur gezielt plazieren. Die beste gemeinsame Stelle ist nach einem Komma, daß ein Teil der Anweisung ist (wie in einem Procedure-Aufruf mit mehr als einem Paramter), aber du kannst auch eine Zeile nach den meisten binären Operatoren aufteilen. Die folgenden Beispiele sind eigentlich ziemlich dumm, aber zeigen einige erlaubte Plätze. fred(a, b, c, d, e, f) x:=x+ y+ z Die komplette Liste der binären Operatoren, nach der du eine Zeile aufteilen kannst,ist: + - * / = > < <> >= <= AND OR BUT Strings dürfen auch ein ziemliches Stück lang sein. Du kannst sie Über einige Zeilen darstellen, indem du sie in einige getrennte Strings aufteilst und + zwischen ihnen benutzt. Wenn eine Zeile mit einem + endet und das vorherige Ding in der Zeile ein String war, dann nimmt der E Compiler den nächsten String als Fortsetzung an. Die folgenden Aufrufe für WriteF drücken das gleiche aus: WriteF('Dieser lange String kann Über einige Zeilen gespalten werden.\n') WriteF('Dieser lange String ' + 'kann über einige Zeilen gespalten werden.\n') WriteF('Dieser lange' + ' String kann über ' + 'einige Zeilen ' + 'gespalten werden.\n') ======================================================================== Abstand und Seperatoren ======================= Die Beispiele, die wir gesehen haben, waren eine radikalen Sammlung, welches beabsichtigt war um die Struktur der Programme zu beleuchten. Dieses war gerade eine Sammlung und die E Sprache hat keine Schwierigkeiten mit whitespace (Leerzeichen, Tabulatoren und Zeilenvorschübe), die du zwischen Anweisungen stellst. Wie auch immer, in einer Anweisung muß genug Abstand sein um die Anweisung lesbar zu machen. Dieses bedeutet im allgemeinen, daß du whitespaces setzen mußt. Im besonderen zwischen benachbarten Bezeichnern, welche mit einem Buchstaben starten oder Enden, Zahlen oder Unterstriche (damit der Compiler nicht denkt, es ist ein riesiger Bezeichner!). Praktisch bedeutet dieses, daß du ein Leerzeichen nach einem Schlüsselwort setzen mußt, wenn es in eine Variable oder in einen Procedure-Namen hineinläuft. Sehr oft (wie in Ausdrücken) werden Bezeichner getrennt durch Nicht-Bezeichner Zeichen (ein Komma, Klammern oder andere Symbole). ======================================================================== Kommentare ========== Ein Kommentar ist etwas, daß der E Compiler ignoriert und ist nur da, um dem Leser zu helfen. Erinnere dich daran, daß eines Tages in der Zukunft du der Leser sein wirst und es dürfte ganz schön hart sein, Deinen eigenen Code ohne ein paar anständige Kommentare zu verstehen! Kommentare sind deshalb sehr wichtig. Du kann Kommentare irgendwo schreiben, wo du auch whitespaces schreiben kannst, die nicht Teil eines Strings sind. Der Start eines Kommentars wird gekennzeichnet durch /* und das Ende durch */, d.h. du mußt vorsichtig sein, damit Du nicht /* oder */ als einen Teil eines Kommentartextes benutzt, außer als Abgrenzungen in verschachtelten Kommentaren. Praktisch bedeutet dieses, daß Kommentare am besten in einer eigenen Zeile oder nach dem Ende des Codes in einer Zeile zu setzen sind. /* Diese Zeile ist ein Kommentar */ x:=1 /* Diese Zeile beinhaltet eine Anweisung, dann einen Kommentar */ /* y:=2 /* Diese ganze Zeile ist ein Kommentar mit einem geschachtelten Kommentar */*/ ======================================================================== Funktionen ********** Eine Funktion ist eine Procedure, die einen Wert zurückliefert. Dieser Wert kann irgendein Ausdruck sein. Somit ist er abhängig von den Parametern, mit welchen die Funktion aufgerufen wird. Als Beispiel, den Additions-Operator + kann man sich vorstellen wie eine Funktion, welche die Summe der zwei Parameter zurückliefert. Next Step:  Proceduren als Funktionen   Ein-Zeilen Funktionen  ======================================================================== Proceduren als Funktionen ========================= Wir können unsere eigene Additions-Funktion, add, auf einem ähnlichen Weg wie eine Procedure definieren. (Der einzige Unterschied ist, daß eine Funktion ausdrücklich einen Wert zurückgibt.) PROC main() DEF sum sum:=12+79 WriteF('Benutze +, Summe ist \d\n', sum) sum:=add(12,79) WriteF('Benutze add, Summe ist \d\n', sum) ENDPROC PROC add(x, y) DEF s s:=x+y ENDPROC s Dieses sollte die folgende Ausgabe herausbringen: Benutze +, Summe ist 91 Benutze add, Summe ist 91 In der Procedure add wird der Wert s zurückgeliefert, indem die ENDPROC Bezeichnung benutzt wird. Der Wert, der von add zurückgeliefert wird, kann in Ausdrücken genutzt werden, ebenso, wie irgendwelche anderen Werte. Du tust dies, indem Du den Procedure-Aufruf dahin schreibst, wo du den Wert haben möchtest. Im obigen Beispiel wollten wir den Wert zu sum zugewiesen haben, sodaß wir den Aufruf von add auf der rechten Seite der Zuordnung geschrieben haben. Beachte die Unterschiede zwischen der Benutztung von + und add. Im allgemeinen kann add(a,b) an genau den gleichen Plätzen stehen wie a+b (oder mehr pedantisch: es kann dort genutzt werden, wo auch ein (a+b) benutzt wird). Das RETURN Schlüsselwort kann auch dazu benutzt werden, um Werte von einer Procedure zurückzuliefern. Wenn die ENDPROC Methode benutzt wird, dann wird der Wert zurückgeliefert wenn die Procedure ihr Ende des Codes erreicht hat. Wie auch immer, wenn die RETURN Methode benutzt wird, dann wird der Wert sofort an diesem Punkt zurückgeliefert und nichts von dem Procedure-Code wird mehr ausgeführt. Hier ist das gleiche Beispiel mit RETURN: PROC add(x, y) DEF s s:=x+y RETURN s ENDPROC Der einzige Unterschied ist, daß du RETURN irgendwo in dem Code Teil einer Procedure schreiben kannst und es die Ausführung der Procedure an diesem Punkt beendet (viel früher, als wenn das Ende des Codes erreicht wird). Tatsache ist: du kannst RETURN in der main Procedure benutzen um die Ausführung eines Programms zu beenden. Hier ist eine etwas kompliziertere Benutzung von RETURN: PROC limitedadd(x,y) IF x>10000 RETURN 10000 ELSEIF x<-10000 RETURN -10000 ELSE RETURN x+y ENDIF x:=1 IF x=1 THEN RETURN 9999 ELSE RETURN -9999 ENDPROC Diese Funktion prüft x um zu sehn, ob es größer als 10.000 oder niedriger als -10.000 ist und wenn es ein solcher Wert ist, dann wird er zurückgeliefert (welches im allgemeinen nicht die richtige Summe ist!). Wenn x zwischen -10.000 und 10.000 liegt dann wird die richtige Antwort zurückgeliefert. Die Zeilen nach dem ersten IF Block werde nie ausgeführt, weil die Ausführung zwischen einer der RETURN Zeilen beendet wird. Diese Zeilen sind deshalb geradezu eine Verschwendung von Compiler Zeit und können sicher weggelassen werden. Wenn kein Wert dem ENDPROC oder RETURN Schlüsselwort mitgegeben wird, dann wird Null zurückgeliefert. Deshalb sind alle Proceduren eigentlich Funktionen (und die Bezeichnungen Procedure und Funktion neigen zum gegenseitigen Wechsel). Was geschieht mit dem Wert, wenn du eine Procedure schreibst, die sich in einer Zeile selber aufruft, und nicht in einem Ausdruck? Nun, wie wir sehen werden wird der Wert einfach abgelegt (siehe auch Ändern eines Ausdrucks in einer Anweisung). Dieses ist, was in den vorherigen Beispielen geschah, wenn wir die Proceduren fred und WriteF aufgerufen haben. ======================================================================== Ein-Zeilen Funktionen ===================== Gerade der IF Block und die FOR Schleife haben eine waagerechte, einzelne Zeilenform, wie eine Proceduren-Definition. Die allgemeine Form ist: PROC name (arg1, arg2, ...) RETURN Ausdruck Auf den ersten Blick mag dieses sehr unpraktisch erscheinen, aber es ist nützlich für sehr einfache Funktionen und unsere add Funktion in dem vorherigen Abschnitt ist ein gutes Beispiel dafür. Wenn du auf die originale Definition schaust wirst du sehen, daß die lokale Variable s nicht wirklich gebraucht wurde. Hier ist die Ein-Zeilen Definition von add: PROC add(x,y) RETURN x+y ======================================================================== Konstanten ********** Eine Konstante ist ein Wert, der sich nicht verändert. Eine Zahl wie 121 ist ein gutes Beispiel einer Konstante--der Wert ist immer 121. Wir trafen schon auf eine andere Art von Konstanten: String Konstanten (siehe Strings). Wie du zweifellos sehen kannst, sind Konstanten wichtig Dinge. Next Step:  Numerische Konstanten   String Konstanten: besondere Zeichen-Sequenzen   Benannte Konstanten   Aufzählungen   Sets  ======================================================================== Numerische Konstanten ===================== Wir haben eine Menge von Zahlen in den vorherigen Beispielen angetroffen. Technisch ausgesprochen waren dieses numerische Konstanten (Konstanten, weil sie nicht ihren Wert wechseln können wie eine Variable es kann). Sie sind alle dezimale Zahlen, aber du kannst Hexadezimale und binäre Zahlen genausogut benutzen. Es gibt auch einen Weg, wie eine Zahl durch ein Zeichen spezifiziert werden kann. Um eine Hexadezimal Zahl anzugeben benutze ein $ vor den Ziffern (und danach das freigestellte Minus Zeichen - um negative Wert darzustellen). Um binäre Zahlen darzustellen benutzt Du ein % an Stelle von dem $-Zeichen. Die Darstellung von Zahlen durch Buchstaben ist mehr kompliziert, weil die Basis von diesem System 256 ist (die Basis eines Dezimalen ist zehn, einer Hexadezimalen 16 und einer binären zwei). Die Ziffern sind eingeschlossen in Anführungsstriche (das "-Zeichen) und es kann mehr als vier Ziffern geben. Jede Stelle ist ein Zeichen, das den ASCII Wert darstellt. Deshalb, das Zeichen a stellt 65 dar und der Buchstabe 0 (Null) stellt 48 dar. Der Zusammenhang ist, daß das Zeichen a den ASCII Wert "A" in E hat und 0z ("0" * 256) + "z" = (48 * 256) + 122 = 12.410 darstellt. Wie auch immer, du wirst Dich wahrscheinlich nicht zu ärgern brauchen über irgend was anderes als das über den Fall des einzelnen Zeichens, welcher Dir den ASCII Wert des Zeichens gibt. Die folgende Tabelle zeigt die dezimalen Werte einiger numerischen Konstanten. Beachte, daß du Groß- oder Kleinbuchstaben für die Hexadezimalen Konstanten benutzen kannst. Offensichtlich liegt der Fall natürlich anders bei Zeichen-Zahlen. Zahl Dezimal-Wert ---------------------- 21 21 -143 -143 $1a 26 -$B1 -177 %1110 14 -%1010 -10 "z" 122 "Je" 19,045 -"A" -65 ======================================================================== String Konstanten: besondere Zeichen-Sequenzen ============================================== Wir haben gesehen, daß in einem String die Zeichen-Sequenz \n ein Zeilenvorschub bedeutet (siehe Strings). Es gibt einige andere, ähnlich besondere Zeichen-Sequenzen, welche nützliche Buchstaben darstellen, die nicht in einem String eingegeben werden können. Die folgende Tabelle zeigt alle diese Sequenzen. Beachte, daß es einige andere, ähnliche Sequenzen gibt, welche benutzt werden für die Formatierung in eingebaute-Proceduren wie WriteF. Diese sind aufgelistet wo WriteF und ähnliche Proceduren beschrieben sind (siehe Input und Output Funktionen). Sequenz Meint -------------------------------------- \0 Eine Null (ASCII Null) \a Ein Apostroph ' \b Ein Wagenrücklauf (ASCII 13) \e Ein Escape (ASCII 27) \n Ein Zeilenvorschub (ASCII 10) \t Ein Tab (ASCII 9) \\ Ein Backslash \ ======================================================================== Benannte Konstanten =================== Es ist oft schön, Namen für Konstanten zu vergeben. Als Beispiel, wie wir früher sahen, der Wert TRUE stellt eigentlich den Wert -1 und FALSE Null dar (siehe Logik und Vergleich). Diess war unser erstes Beispiel für benannte Konstanten. Um deine eigenen zu definieren benutzt Du das CONST Schlüsselwort wie folgt: CONST ONE=1, LINEFEED=10, BIG_NUM=999999 Dieses hat die Konstante ONE für die Darstellung von eins, LINEFEED für zehn und BIG_NUM für 999.999 definiert. Benannte Konstanten müßen mit zwei großen Buchstaben beginnen, wie zuvor erwähnt wurde (siehe Bezeichner). Du kannst früher definierte Konstanten benutzen, um den Wert einer neuen Konstante vorzugeben, aber in diesem Fall muß die Definition in einer anderen CONST Zeilen geschehen. CONST ZERO=0 CONST ONE=ZERO+1 CONST TWO=ONE+1 Der Ausdruck, der benutzt wird um den Wert einer Konstanten zu definieren, kann nur einfache Operatoren (keine Funktionsaufrufe) und Konstanten nutzen. ======================================================================== Aufzählungen ============ Oft möchtest Du eine ganze Menge von Konstanten definieren und du willst, das sie alle einen unterschiedlichen Wert haben, damit du sie leichter auseinander halten kannst. Als Beispiel, wenn du einige Konstanten für die Darstellung einiger berühmten Städte definieren möchtest und du nur wissen willst, wie Du sie voneinander unterscheiden kannst, dann kannst du eine Aufzählung, wie diese, nutzen: ENUM LONDON, MOSCOW, NEW_YORK, PARIS, ROME, TOKYO Das ENUM Schlüsselwort beginnt die Definitionen (wie es das CONST Schlüsselwort für eine übliche Konstanten-Definition tut). Der eigentlich Werte der Konstanten fängt mit Null an und steigt auf fünf auf. Tatsache ist, dieses ist genau das gleiche, als wenn wir folgendes schreiben würden: CONST LONDON=0, MOSCOW=1, NEW_YORK=2, PARIS=3, ROME=4, TOKYO=5 Die Aufzählungen müßen nicht bei Null starten. Du kannst den Startwert an irgendeinem Punkt wechseln, indem Du einen Wert für eine Aufzählungskonstante wählst. Als Beispiel, die folgende Konstanten-Definitionen sind alle gleich: ENUM APPLE, ORANGE, CAT=55, DOG, GOLDFISH, FRED=-2, BARNEY, WILMA, BETTY CONST APPLE=0, ORANGE=1, CAT=55, DOG=56, GOLDFISH=57, FRED=-2, BARNEY=-1, WILMA=0, BETTY=1 ======================================================================== Sets ==== Eine andere Art der Konstanten-Definition ist die set Definition. Dieses ist nützlich zum definieren von Flag-Sets, d.h., eine Anzahl von Optionen, wobei jede an oder aus sein kann. Die Definition ist wie bei einer einfachen Aufzählung, aber es wird das SET Schlüsselwort genutzt und der Wert startet bei eins und nimmt mit der Potenz von zwei zu (also ist der nächste Wert zwei, der nächste vier, der nächste acht, und so weiter). Deshalb sind die folgenden Definitionen gleich: SET ENGLISH, FRENCH, GERMAN, JAPANESE, RUSSIAN CONST ENGLISH=1, FRENCH=2, GERMAN=4, JAPANESE=8, RUSSIAN=16 Wie auch immer, die Bedeutung der Werte kann am besten gezeigt werden durch die Benutzung von binären Konstanten: CONST ENGLISH=%00001, FRENCH=%00010, GERMAN=%00100, JAPANESE=%01000, RUSSIAN=%10000 Wenn eine Person Englisch spricht, dann benutzen wir die Konstante ENGLISH. Wenn sie auch japanisch spricht, dann brauchen wir für die Darstellung normalerweise eine neue Konstante (etwas wie ENG_JAP). Tatsache ist, daß wir wahrscheinlich eine Konstante für jede Kombination der Sprachen, die eine Person wissen kann, brauchen. Wie auch immer, mit der Set-Definition können die ENGLISH und JAPANESE Werte zusammen OR verknüpft werden, um einen neuen Wert zu bekommen (%01001) und dieser stellt ein Set dar mit ENGLISH und JAPANESE. Auf der anderen Seite, um herauszufinden, ob jemand Französisch spricht, würde wir den Wert für die Sprachen mit %00010 AND-Verknüpfen (oder mit der Konstante FRENCH). (Wie du gesehen hast sind AND und OR wirklich Bit-weise Operatoren, nicht einfache logische Operatoren. Siehe Bitweises AND und OR.) Betrachten wir dieses Programm-Fragment. speak:=GERMAN OR ENGLISH OR RUSSIAN /* Irgendeine Sprache von diesen */ IF speak AND JAPANESE /* Kann japanisch sprechen */ WriteF('Kann japanisch sprechen\n') ELSE /* Kann nicht japanisch sprechen */ WriteF('Kann nicht japanisch sprechen\n') ENDIF IF speak AND (GERMAN OR FRENCH) /* Kann Deutsch oder Französisch sprechen */ WriteF('Kann sowohl Deutsch als auch Französisch sprechen\n') ELSE /* Kann nicht Deutsch oder Französisch sprechen */ WriteF('Kann weder Deutsch noch Französisch sprechen\n') ENDIF Die Zuweisung setzt speak, um zu zeigen, daß die Person Deutsch, Englisch oder Russisch sprechen kann. Der erste IF Block testet, ob die Person japanisch sprechen kann und der zweite testet, ob sie Deutsch oder Französisch sprechen kann. Wenn Du Sets benutzt, dann sei vorsichtig, daß du nicht Werte addierst, sondern sie OR-Verknüpftst. Die Addition zweier unterschiedlicher Konstanten von dem gleichen Set ist das gleiche wie eine OR-Verknüpfung, aber die Addition einer Konstanten ist nicht das selbe. Dieses ist nicht das einzige Mal, daß Additionen nicht die gleiche Antwort geben, aber es ist offensichtlicher. Wenn du Dir angwöhnst OR zu verwenden, dann wirst du keine Problem haben. ======================================================================== Typen ***** Wir haben schon den LONG-Typ getroffen und gemerkt, daß dieser der normale Typ für Variablen ist (siehe Variablen Typen). Der Typ INT und LIST wurde auch erwähnt. Zu lernen, wie man dieses Typen wirksam und lesbar einsetzt, ist sehr wichtig. Der Typ einer Variable (genauso wie ihr Name) kann dem Leser Informationen geben, über das wie oder für was sie benutzt wird. Es gibt noch mehr fundamentale Ursachen, um Typen zu benötigen, z.B. um logische Gruppen mit Objekten herzustellen (siehe OBJECT Typen). Dieses ist ein sehr großes Kapitel und du kannst es langsam angehen. Eines der wichtigsten Dinge, die zu verstehen sind, sind Pointer. Versuche dich auf das Verständnis dieser Pointer zu konzentrieren, weil sie eine große Rolle in jeder Art der System-Programmierung spielen. Next Step:  LONG Typ   PTR Typ   ARRAY Typ   OBJECT Typ   LIST und STRING Typen   Gelinkte Listen  ======================================================================== LONG Typ ======== Der LONG Typ ist der wichtigste Type, weil er der voreingestellte Typ ist und bei weitestem der meiste gemeinsame Typ. Er kann benutzt werden, um eine Vielzahl von Daten zu speichern, einschliesslich Speicher-Adressen, wie wir sehen werden. Next Step:  Voreingestellte Typen   Speicher-Adressen  ======================================================================== Voreingestellte Typen --------------------- LONG ist der voreingestellte Typ einer Variablen. Es ist ein 32-Bit Typ. Gemeint ist, daß 32-Bits des Speichers (RAM) genutzt werden um Daten für jede Variable dieses Types zu speichern und die Daten können (Integer) Werte in dem Bereich -2.147.483.648 bis 2.147.483.647 annehmen. Variablen können ausschließlich als LONG deklariert werden: DEF x:LONG, y PROC fred(p:LONG, q, r:LONG) DEF zed:LONG Anweisungen ENDPROC Die globale Variable x, Procedure-Parameter p und r und die lokale Variable zed wurden alle als LONG Werte deklariert. Die Deklarationen sind sehr ähnlich zu der Art, wie wir es zuvor gesehen haben, ausgenommen, daß die Variablen den Zusatz :LONG nach ihrem Name in der Deklaration haben. Dieses ist der Weg, wie der Typ einer Variablen vergeben wird. Beachte, daß die globale Variable y und die Procedure-Parameter q auch LONG sind, weil sie keine Typenzuweisung bekommen haben und LONG der voreingestellte Typ für Variablen ist. ======================================================================== Speicher Adressen ----------------- Es gibt einen sehr guten Grund, warum LONG der normale Typ ist. Ein 32-Bit (Integer) Wert kann benutzt werden wie eine Speicheradresse. Deshalb können wir die Adresse (oder die Lage) von Daten in einer Variable speichern (die Variable wird dann ein pointer genannt). Die Variable würde dann nicht den Wert der Daten beinhalten, aber einen Weg, um die Daten zu finden. Wenn die Daten-Lage erstmal bekannt ist, dann können Daten gelesen oder sogar geändert werden! Der nächste Abschnitt erklärt Pointer und Adressen ausführlicher. (siehe PTR Type.) ======================================================================== PTR Type ======== Der PTR Type wird benutzt, um Speicher-Adressen zu merken. Variablen, welche vom PTR Type sind, werden pointer genannt (weil sie Speicher- Adressen beinhalten, wie im vorigen Abschnitt erwähnt). Dieser Abschnitt beschreibt, ausführlicher, Adressen, Pointer und den PTR Typ. Next Step:  Adressen   Pointer   Indirekte Typen   Finden von Adressen (Pointer machen)   Daten extrahieren (Nichtreferenz-Pointer)   Procedure Parameter  ======================================================================== Adressen -------- Um Speicher-Adressen zu verstehen ist es vielleicht ein gutes Beispiel, sich den Speicher wie eine Straße vorzustellen, jede Speicherstelle als einen Postkasten an einem Haus und jedes Stück von Daten wie einen Brief. Wenn du ein Postman wärst müßtest Du wissen, wo Du die Briefe abgeben sollst und diese Information wird Dir durch die Adresse des Briefkasten gegeben. Wenn die Zeit vergeht wird jeder Briefkasten gefüllt sein mit unterschiedlichen Briefen. Dieses ist das gleiche, als wenn der Wert in einer Speicherstelle (oder Variable) wechselt. Um die Briefe in dem Briefkasten zu wechseln sagst Du deinen Freunden deine Adresse und sie schicken Briefe. Dieses ist wie das verändern deiner Daten in einem Programm, indem die Adresse der Daten vorgegeben werden. Die nächsten zwei Diagramme erläutert diesen Vergleich. Ein Brief beinhaltet eine Adresse, welche zu einem besonderen Haus zeigt (oder einer Menge von Post) in einer Straße. +-------+ | Brief | |-------| |Adresse+----* +-------+ \ \ \ +--------+ +---\----+ +--------+ +--------+ | Haus | | Haus | | Haus | | Haus | Straße: |+------+| |+------+| |+------+| ... |+------+| || Post || || Post || || Post || || Post || +========+ +========+ +========+ +========+ Ein Pointer beinhaltet eine Adresse, welche zu einer Variablen (oder Daten) im Speicher zeigt. +-------+ |Pointer| |-------| |Adresse+----* +-------+ \ \ \ +---------+ +---\-----+ +---------+ +---------+ |Variable | |Variable | |Variable | |Variable | Speicher: |+-------+| |+-------+| |+-------+| ... |+-------+| || Daten || || Daten || || Daten || || Daten || +=========+ +=========+ +=========+ +=========+ ======================================================================== Pointer ------- Variablen, welche Speicher-Adressen beinhalten, werden pointer genannt. Wie wir im vorherigen Abschnitt gesehen haben, können wir Speicher-Adressen in LONG Variablen ablegen. Wie auch immer, wir kennen nicht den Type der Daten, die wir an diesen Adressen gelagert haben. Wenn es wichtig (oder nützliche) ist, dieses zu wissen, dann kann der PTR Typ (oder einer der vielen PTR Typen) benutzt werden. DEF p:PTR TO LONG, i:PTR TO INT, cptr:PTR TO CHAR, gptr:PTR TO gadget Die Werte, die in p, cptr, i und gptr gespeichert werden, sind LONG, weil sie Speicher-Adressen sind. Wie auch immer, die Daten, die in der Adresse p gespeichert werden, sind LONG (ein 32-Bit Wert), die bei cptr sind CHAR (ein 8-Bit Wert), die bei i sind INT (ein 16-Bit Wert) und die bei gptr sind vom Typ gadget, welcher ein Objekt ist (siehe OBJECT Typen). ======================================================================== Indirekte Typen --------------- Im vorherigen Beispiel sahen wir INT und CHAR, welche als Ziel-Typen eines Pointers genutzt wurden und dieses sind die 16- und 8-Bit Äquivalente (beziehungsweise) vom LONG Typ. Im Gegensatz zu LONG können diese Typen nicht direkt genutzt werden, um global oder lokale Variablen bzw. Procedure Parameter zu deklarieren. Sie können nur in konstruierten Typen genutzt werden (z.B. mit PTR TO). Die folgenden Deklarationen sind deshalb illegal und es kann ganz nützlich sein, zu versuchen, ein kleines Programm mit solchen Deklarationen zu kompilieren, um die Fehler-Nachrichten des E Compilers zu sehen. /* Diese Programm-Fragment beinhaltet illegale Deklarationen */ DEF c:CHAR, i:INT /* Diese Programm-Fragment beinhaltet illegale Deklarartionen */ PROC fred(a:INT, b:CHAR) DEF x:INT Befehle ENDPROC Dieses ist keine große Beschränkung, weil du INT oder CHAR Werte in LONG Variablen speichern kannst, wenn du es wirklich brauchst. Wie auch immer, hiermit ist gemeint, daß es eine schöne, einfache Regel gibt: jeder direkte Wert in E ist eine 32-Bit Menge, entweder LONG oder ein Pointer. Tatsache ist, LONG ist eigentlich eine Kurzschreibung für PTR TO CHAR, so daß Du LONG Werte benutzen kannst, als wenn sie PTR TO CHAR Werte sind. ======================================================================== Finden von Adressen (Pointer machen) ------------------------------------ Wenn ein Programm die Adresse einer Variable kennt kann es geradewegs den Wert lesen oder verändern, der in der Variable gespeichert ist. Um die Adresse einer einfachen Variable zu erlangen benutzt Du { und } um den Variablen-Namen. Die Adressen von nicht-einfachen Variablen (z.B. Objekte und Felder) können viel leichter gefunden werden (siehe den betreffenden Abschnitt) und tatsächlich wirst Du sehr oft {var } benutzen. Wie auch immer, wenn du verstehst, wie Du Pointer mit {var } machen kannst und den Pointer benutzt, um Daten zu bekommen, dann wirst Du sehr schnell verstehen, wie Pointer für die nicht-einfachen Typen benutzt werden. Adressen können in einer Variable gespeichert, weitergegeben werden an eine Procedure oder was auch immer (sie sind 32-Bit Werte). Versuche das folgende Programm: DEF x PROC main() fred(2) ENDPROC PROC fred(y) DEF z WriteF('x ist an Adresse \d\n', {x}) WriteF('y ist an Adresse \d\n', {y}) WriteF('z ist an Adresse \d\n', {z}) WriteF('fred ist an Adresse \d\n', {fred}) ENDPROC Beachte, daß du auch die Adresse einer Prozedur durch { und } finden kannst. Dieses ist die Speicherstelle des Codes, den die Procedure darstellt. Hier ist die Ausgabe nach einer Ausführung dieses Programms: x ist an Adresse 3758280 y ist an Adresse 3758264 z ist an Adresse 3758252 fred ist an Adresse 3732878 Dieses ist ein interessantes Programm, um es einige Male unter unterschiedliche Umständen laufen zu lassen. Du wirst sehen, daß manchmal die Werte der Adressen wechseln. Eine Ausführung des Programms, wenn ein anderes gleichzeitig läuft (und Speicher "frißt"), wird die besten Änderungen erzeugen, wohingegen eine Ausführung alleine (in einem CLI) die kleinsten Veränderungen (wenn überhaupt welche) erzeugt. Dieses gibt dir einen Einblick in die komplexe Speicherverwaltung des Amigas und des E Compilers. ======================================================================== Daten extrahieren (Nichtreferenz-Pointer) ----------------------------------------- Wenn du eine Adresse in eine Variable gespeichert hast (z.B. in einem Pointer), dann kannst du die Daten extrahieren durch den ^ Operator. Diese Art der Extraktion von Daten über einen Pointer wird Nichtreferenz-Pointer genannt. Dieser Operator wird erst wirklich benutzt, wenn {var } benutzt wurde, um eine Adresse zu erlangen. An diesem Ende, LONG Werte werden gelesen und geschrieben, wenn Nichtreferenz-Pointer diesen Weg zeigen. Für Pointer auf nicht-einfache Typen (z.B. Objekte und Felder) ist nichtreferenz der mehr lesbare Weg (sehe den zutreffenden Abschnitt für Details) und dieser Operator wird nicht benutzt. Tatsache ist,  ^var wird selten benutzt in Programmen, aber ist nützlich um zu zeigen, wie Pointer arbeiten, besonders in Verbindung mit {var }. Die Benutzung von Pointern kann die Beschränkungen bei der Benutzung von lokalen Variablen außer Kraft setzen, damit ist gemeint, sie können von außerhalb der Procedure, für welche sie lokal sind, geändert werden. Weil diese Art der Benutzung im allgemeinen nicht favorisiert wird, ist es ein gutes Beispiel, das die Leistung der Pointer zeigt. Z.B., das folgende Programm wechselt den Wert der lokalen Variable x für die Procedure fred in der Procedure barney. PROC main() fred() ENDPROC PROC fred() DEF x, p:PTR TO LONG x:=33 p:={x} barney(p) WriteF('x ist jetzt \d\n', x) ENDPROC PROC barney(ptr:PTR TO LONG) DEF val val:=^ptr ^ptr:=val-6 ENDPROC Hier ist daß, was als Ausgabe zu erwarten ist: x ist jetzt 27 Beachte, daß der ^ Operator (hier nichtreferenz) ganz außerhalb ist. In der ersten Zuweisung in der Procedure barney wurde er benutzt (mit dem Pointer ptr), um den Wert zu bekommen, der in der lokalen Variable x vorhanden war, und zum zweiten wurde er benutzt, um den Variablen Wert zu verändern. Im entsprechenden Fall macht nichtreferenz, daß der Pointer sich genauso verhält, als wenn Du die Variable, für welche der Pointer da ist, geschrieben hättest. Um dieses zu betonen können wir die barney Procedure entfernen, wie wir es oben getan haben (siehe Gestaltung und Lesbarkeit): PROC main() fred() ENDPROC PROC fred() DEF x, p:PTR TO LONG, val x:=33 p:={x} val:=x x:=val-6 WriteF('x ist jetzt \d\n', x) ENDPROC Überall, wo die barney Procedure ^ptr benutzt hatte, haben wir x geschrieben (weil wir jetzt in der Procedure sind, für welche x lokal ist). Wir haben auch die ptr Variable eleminiert (der Parameter für die barney Procedure), weil sie nur mit dem ^ Operator benutzt wurde. Um die Dinge zu verdeutlichen, ist das fred und barney Beispiel absichtlich `simplex'. Die val und p Variablen sind einfach und der Pointer-Type kann zu LONG oder sogar weggelassen werden (siehe LONG Typ). Dieses ist die kompakte Form des Beispiels: PROC main() fred() ENDPROC PROC fred() DEF x x:=33 barney({x}) WriteF('x ist jetzt \d\n', x) ENDPROC PROC barney(ptr) ^ptr:=^ptr-6 ENDPROC Am meisten wird der Pointer für Adressen (oder Referenzen) großer Datenstrukturen benutzt. Es würde riesige Dimensionen annehmen (von der CPU Zeit her) um große Datenblöcke von Procedur zu Procedur zu übertragen, so daß Adressen zu solchen Daten an deren Stelle weitergegeben werden (und, wie wir wissen, sind dieses 32-Bit Werte). Die Amiga System Funktionen (solche wie für die Erzeugung von Fenstern) erfordern ein Menge Datenstrukturen, so wenn du planst, irgendwelche realen Sachen zu programmieren, bist Du gezwungen, Pointer zu verstehen und zu benutzen. ======================================================================== Procedure Parameter ------------------- Nur lokale und globale Variablen bieten den Luxus einer großen Auswahl von Typen. Procedure-Parameter können nur LONG oder PTR TO Typen sein. Dieses ist keine wirkliche Einschränkung wie wir in den späteren Abschnitten sehen werden. ======================================================================== ARRAY Typ ========= Sehr oft müssen die Daten, die von einem Programm gebraucht werden, geordnet sein, hauptsächlich damit diese leicht zugänglich sind. E liefert ein Weg zur Bereitstellung solcher einfacher Ordnung: den ARRAY Typ. Diesen Typ (in seinen verschiedenen Formen) gibt es bei den verschiedensten Computer Sprachen. Next Step:  Datentabellen   Zugriff auf Feld-Daten   Feld-Pointer   Zeiger zu anderen Elementen   Feld-Procedure-Parameter  ======================================================================== Datentabellen ------------- Daten können auf viele unterschiedliche Wege zusammen gruppiert werden, aber wahrscheinlich ist der beste und einfachste Weg, Tabellen daraus zu machen. In einer Tabelle sind die Daten entweder vertikal oder horizontal sortiert, aber das Wichtigste ist die relative Position der Elemente. Die E Betrachtung dieser Art von sortierten Daten ist der ARRAY Typ. Ein Feld(array) ist eine feste Ansammlung von Daten in einer bestimmten Ordnung. Die Größe eines Feldes ist wichtig und diese wird festgelegt, wenn es deklariert wird. Das folgende erläutert Feld-Deklarationen: DEF a[132]:ARRAY, table[21]:ARRAY OF LONG, ints[3]:ARRAY OF INT, objs[54]:ARRAY OF myobject Die Größe des Feldes wird in den eckigen_Klammern vorgegeben ([ und ]). Der Type der Elemente in dem Feld ist voreinstellt auf CHAR, aber dieses kann durch die Vorgabe des OF Schlüsselwortest und des Types geändert werden. Wie auch immer, nur LONG, INT, CHAR und Objekt Typen sind erlaubt (LONG kann Pointer-Werte halten, damit ist dieses keine große Begrenzung). Objekt Typen sind unten beschrieben (siehe OBJECT Typ). Wie oben erwähnt, können Procedure-Parameter nicht Felder sein (siehe Procedure Parameter). Wir werde auf diese Einschränkung bald zurückkommen (siehe Feld-Procedure Parameter). ======================================================================== Zugriff auf Feld-Daten ---------------------- Um auf ein einzelnes Element in einem Feld zuzugreifen, mußt Du eckige Klammer wieder benutzen, wobei diesmal der index (oder die Position) des Elementes, welches du möchtest, spezifiziert wird. Indizes starten bei Null für das erste Element des Feldes, eins für das zweite Element und, im allgemeinen, (n-1) für der n-te Element. Dieses scheint zuerst seltsam zu sein, aber es ist der Weg, wie es die meisten Computer Sprachen tun! Wir werden sehen, warum dieses seinen Grund hat (siehe Array Pointer). DEF a[10]:ARRAY PROC main() DEF i FOR i:=0 TO 9 a[i]:=i*i ENDFOR WriteF('Das 7. Element des Feldes a ist \d\n', a[6]) a[a[2]]:=10 WriteF('Das Feld ist nun:\n') FOR i:=0 TO 9 WriteF(' a[\d] = \d\n', i, a[i]) ENDFOR ENDPROC Dieses scheint alles sehr einfach zu sein, obwohl eine der Zeilen ein bischen kompliziert erscheint. Versuche auszuarbeiten, was mit dem Feld nach der ersten Zuweisung passieren wird, daß sofort dem ersten WriteF folgt. In diese Zuweisung wird der Index, der von einem Wert kommt, in dem Feld selbergespeichert! Sei vorsichtig, wenn Du komplizierte Dinge wie diese machst, deshalb: sei sicher, daß du nicht versuchst, Daten zu lesen oder zu schreiben, die über die Anzahl der Elemente hinausgehen! In unserem Beispiel sind nur zehn Elemente in dem Feld a vorhanden, so daß es notwendig ist, über das elfte Element zu reden. Das Programm kann prüfen, ob der Wert bei a[2] eine Zahl zwischen Null und neun vor dem Zugriff zu dem Feldelemnt ist, aber es war nicht notwendig in diesem Fall. Hier ist die Ausgabe, die dieses Beispiel erzeugen wird: Das 7. Element des Feldes a ist 36 Das Feld ist nun: a[0] = 0 a[1] = 1 a[2] = 4 a[3] = 9 a[4] = 10 a[5] = 25 a[6] = 36 a[7] = 49 a[8] = 64 a[9] = 81 Wenn du versuchst, ein nicht-vorhanden Feldelement zu beschreiben, dann können seltsame Dinge passieren. Dieses kann praktisch unbemerkt bleiben (wie das verändern von anderen Daten), aber wenn Du wirkliche Pech hast, dann stürzt dein Computer ab. Die Moral der Geschichte ist: bleibe in den Grenzen des Feldes. Eine Kurzform für das erste Element eines Feldes (damit ist gemeint, das mit dem Index Null) ist, den Index nicht zu schreiben, sondern nur die eckigen Klammern. Deshalb ist a[] das gleiche wie a[0]. ======================================================================== Feld Pointer ------------ Wenn du ein Feld deklarierst, dann wird die Adresse des (Anfangs des) Feldes vorgegeben durch den Variable-Namen ohne eckige_Klammern. Betrachte das folgende Programm: DEF a[10]:ARRAY OF INT PROC main() DEF ptr:PTR TO INT, i FOR i:=0 TO 9 a[i]:=i ENDFOR ptr:=a ptr++ ptr[]:=22 FOR i:=0 TO 9 WriteF('a[\d] ist \d\n', i, a[i]) ENDFOR ENDPROC Hier ist die Ausgabe von ihm: a[0] ist 0 a[1] ist 22 a[2] ist 2 a[3] ist 3 a[4] ist 4 a[5] ist 5 a[6] ist 6 a[7] ist 7 a[8] ist 8 a[9] ist 9 Du wirst bemerken, daß das zweite Element des Feldes durch die Benutzung des Pointers geändert wurde. Die ptr++ Anweisung Inkrementiert den Pointer ptr, um auf das nächste Element des Feldes zu zeigen. Es ist wichtig, daß ptr deklariert ist als PTR TO INT, weil das Feld ein ARRAY OF INT ist. Die [] wurden benutzt, um den Pointer zu bekommen um beim zweiten mal 22 in dem Element zu speichern. Tatsache ist, ptr kann genauso genutzt werden wie ein Feld, so daß ptr[1] das nächste (oder dritte Element) des Feldes a darstellen würde (nach der ptr++ Anweisung). Auch wenn ptr zu dem zweiten Element von a zeigt, können negative Werte auch als Index benutzt werden und ptr[-1] ist das erste Element von a. Tatsache ist, die folgenden Deklarartionen sind identisch, wobei die erste einen passenden Speicherdarf für das Feld reserviert und die zweite sich darauf verlässt, daß Du es irgenwo im Programm gemacht hast. DEF a[20]:ARRAY OF INT DEF a:PTR TO INT Das folgende Diagramm ist ähnlich zu den früheren Diagrammen (siehe Adressen). Es ist eine Erläuterung eines Feldes, a, welches deklariert wurde als ein Feld mit zwanzig INTs. +--------+ |Variable| | 'a' | |--------| |Adresse +----* +--------+ \ \ \ +---------+ +--\----+ +-------+ +-------+ +---------+ |Unbekannt| | a[0] | | a[1] | | a[19] | |Unbekannt| Speicher: |+-------+| |+-----+| |+-----+| ... |+-----+| |+-------+| || XXX || || INT || || INT || || INT || || XXX || +=========+ +=======+ +=======+ +=======+ +=========+ Wie du sehen kannst, die Variable a ist ein Zeiger zu dem reservierten Speicherblock, welcher die Feldelemente beinhaltet. Teile des Speichers, die nicht zwischen a[0] und a[19] stehen, sind mit `Unbekannt' gekennzeichnet, weil sie nicht Teil des Feldes sind. Dieser Speicher sollte deshalb nicht mit dem Feld a benutzt werden. ======================================================================== Pointer zu anderen Elementen ---------------------------- Wir sahen in dem vorherigen Abschnitt, wie ein Pointer Inkrementiert wurde, damit er zu dem nächsten Element des Feldes zeigte. Dekremtieren eines Zeigers p (damit ist gemeint, er soll zu dem vorherigen Element zeigen) wird auf einem ähnlichen Weg gemacht, indem die p-- Anweisung benutzt wird. Diese arbeitet in der gleiche Weise wie die p++ Anweisung. Tatsache ist, p++ und p-- sind Ausdrücke, welche Pointer-Werte kennzeichnen. p++ kennzeichnet die Adresse in p vor der Inkrementierung und p-- kennzeichnet die Adresse nachdem p dekrementiert worden ist. Deshalb, addr:=p p++ tut das gleiche wie addr:=p++ Und p-- addr:=p tut das gleiche wie addr:=p-- Der Grund, warum ++ und -- für das Inkrementieren und Dekrementieren benutzt werden, ist, daß Werte von unterschiedlichen Typen unterschiedliche Mengen von Bytes benötigen. Tatsache ist, eine einzelne Speicherstelle ist ein Byte, und dieses sind acht Bits. Deshalb, CHAR Werte besetzen ein einzelnes Byte, wohingegen LONG Werte vier Bytes (32 Bits) einnehmen. Wenn p ein Pointer zu CHAR ist und er auf ein Feld (von CHAR) zeigt, dann würde die p+1 Speicherstelle das zweite Element des Feldes beinhalten (und p+2 das dritte, usw..). Aber wenn p ein Zeiger zu einem Feld von LONG ist, dann würde das zweite Element des Feldes bei p+4 liegen (und das dritte bei p+8). Die Stellen p, p+1, p+2 und p+3 beschreiben alle den LONG Wert an der Adresse p. Sich an diese Dinge zu erinnern, ist unangenehm und es ist erheblich weniger lesbar, als wenn Du ++ oder -- benutzt. Wie auch immer, du mußt dich immer daran errinnern,den Zeiger mit dem richtigen Typ zu deklarieren, damit ++ und -- korrekt arbeiten. ======================================================================== Feld-Procedure-Parameter ------------------------ Seit wir jetzt wissen, wie wir die Adresse eines Feldes bekommen, können wir das übergeben eines Feldes als ein Procedure Parameter simulieren, indem wir die Adresse des Feldes übergeben. Z.B., das folgende Programm benutzt eine Procedure um die ersten x Elemente eines Feldes mit ihren Indexwerten zu füllen. DEF a[10]:ARRAY OF INT PROC main() DEF i fillin(a, 10) FOR i:=0 TO 9 WriteF('a[\d] ist \d\n', i, a[i]) ENDFOR ENDPROC PROC fillin(ptr:PTR TO INT, x) DEF i FOR i:=0 TO x-1 ptr[]:=i ptr++ ENDFOR ENDPROC Hier ist die Ausgabe, die es erzeugt: a[0] ist 0 a[1] ist 1 a[2] ist 2 a[3] ist 3 a[4] ist 4 a[5] ist 5 a[6] ist 6 a[7] ist 7 a[8] ist 8 a[9] ist 9 Das Feld a hat nur zehn Elemene, so daß wir nicht mehr als die ersten zehn Elemente füllen dürfen. Darum darf der Aufruf der Procedure fillin nicht eine größere Zahl als zehn im zweiten Parameter haben. Also können wir ptr besser nutzen als ein Feld (und nicht ++ benutzen), aber in diesem Fall ++ zu benutzen ist etwas besser, weil wir jede Element in einem Durchgang zuweisen. Die Alternative Definition von fillin (ohne ++) ist: PROC fillin2(ptr:PTR TO INT, x) DEF i FOR i:=0 TO x-1 ptr[i]:=i ENDFOR ENDPROC Auch kann jetzt eine andere Version von fillin die Ausdrucks-Form von ++ benutzen und die waagerechte Form der FOR Schleife, um eine wirkliche kompakte Definition abzugeben. PROC fillin3(ptr:PTR TO INT, x) DEF i FOR i:=0 TO x-1 DO ptr[]++:=i ENDPROC ======================================================================== OBJECT Typ ========== Objekte sind die E-Äquivalente von C und Assembler Strukturen, oder Pascal Records. Sie sind wie Felder mit der Ausnahme, das die Elemente benannt sind und nicht nummeriert und das die Elemente unterschiedliche Typen sein können. Um ein besonderes Element in einem Objekt zu finden benutzt Du einen Namen an Stelle des Indizes (der Nummer). Next Step:  Beispiel Objekt   Element Auswahl   Element Typen   Amiga System Objekte  ======================================================================== Beispiel Objekt --------------- Wir tauchen gerade ein mit diesem ersten Beispiel und definieren ein Objekt und benutzen es. Objekt Definitionen sind global und müßen vor irgendeiner Procedure Definitionen gemacht werden. OBJECT rec tag, check table[8]:ARRAY data:LONG ENDOBJECT PROC main() DEF a:rec a.tag:=1 a.check:=a a.data:=a.tag+(10000*a.tag) ENDPROC Dieses Programm soll nichts anschauliches zeigen, so daß es keine Schwierigkeit macht, es zu kompilieren. Was es auch tut, wie auch immer, es zeigt, wie ein typisches Objekt definiert wird und Elemente eines Objektes ausgewählt werden. Das Objekt, das in dem Beispiel definiert wurde, ist rec und seine Elemente wurden definiert wie Variablen Deklarationen (aber ohne ein DEF). Es kann egal wie viele Zeilen von Element Definitionen zwischen den OBJECT und ENDOBJECT Zeilen geben und jede Zeile kann irgendeine Anzahl vom Elementen beinhalten, jeweils getrennt durch Kommas. Die Elemente des rec Objektes sind tag und check (welche LONG sind), table (welches ein Array von CHAR mit acht Elementen ist) und data (welches auch LONG ist). Für jede Variable des rec Objekt-Types wird Platz für jedes einzelne Element reserviert. Die Deklarationen der (lokalen) Variable a reserviert deshalb genug Speicher für ein rec Objekt. ======================================================================== Element Auswahl --------------- Um Elemente in einem Objekt obj auszuwählen benutzt Du obj.name, wobei name eines der Element Namen ist. In dem Beispiel, das tag Element des rec Objektes a wird ausgewählt durch a.tag. Die anderen Elemente werden auf dem gleichen Weg ausgewählt. Wie bei einer Feld Deklarationen wird die Adresse eines Objektes obj in der Variable obj gespeichert und irgendein Zeiger des Typs PTR TO Objectname kann wie ein Objekt des Typs objectname genutzt werden. Deshalb ist in dem vorherigen Beispiel a ein PTR TO rec. ======================================================================== Element Typen ------------- Wie uns das Beispiel-Objekt zeigt können die Elemente eines Objektes verschiedene Typen haben. Wie auch immer, diese Typen sind begrenzt auf LONG, INT, CHAR, ARRAY oder einem anderen Objekttyp. Du kannst nichtPTR TO oder ARRAY OF benutzen; wenn du es doch versuchst wirst Du einen Fehler `illegal/inappropriate type' an dem Punkt bekommen, wo dein Objekt definiert ist. Wieder mal ist dieses keine große Begrenzung weil, wie wir wissen, ein LONG eine Speicher Adresse halten kann. Ein Sache an die Du dich erinnern mußt bei ARRAY und Objekttype-Elementen in einem Objekt: wenn du dieses Element auswählst bekommst Du einen Zeiger zu dem Feld oder Objekt. Du kannst diesen Wert in eine entsprechende Pointer-Variable speichern und dann auf das Feld oder das Objektelement zugreifen. Als Beispiel, wenn p ein Pointer zu einem Objekt von unserem Beispiel Objekttyp rec ist kannst Du p.table (ein Pointer zu dem Feld) in einer PTR TO CHAR Variablen speichern und dann über diese Variable darauf zugreifen. Der folgende Code definiert einen neuen Objekttyp, der auf rec aufbaut und zeigt, wie der Zugriff zu ARRAY und den Objekttypen-Elementen erfolgt. OBJECT rec tag, check, table[8]:ARRAY, data:LONG ENDOBJECT OBJECT bigrec subrec:rec bigtable[22]:ARRAY ENDOBJECT PROC main() DEF b:bigrec, p:PTR TO rec, s, t p:=b.subrec p.tag:=1 p.data:=p.tag+(10000*p.tag) s:=b.bigtable s[0]:="A" t:=p.table t[1]:="y" ENDPROC Erinnere dich, daß die Variablen s und t LONG sind (weil sie mit keinem expliziten Typ definiert wurden) und damit PTR TO CHAR sind. Wenn du ein Feld von Objekten hast kannst du ein Element von dem Feld auswählen und dann ein Element von dem Objekt, alles in dem gleichen Ausdruck. Tatsache ist, die erlaubten Ausdrücke (für Objekte) sind: var . obj_element_name var [ Ausdruck ] . obj_element_name var . obj_element_name ++ var [ Ausdruck ] . obj_element_name ++ var . obj_element_name -- var [ Ausdruck ] . obj_element_name -- Der ++ oder -- wird auf den Pointer var angewendet. Hier ist ein Beispiel, welches ein Feld von Objekten benutzt: OBJECT rec tag, check table[8]:ARRAY data:LONG ENDOBJECT PROC main() DEF a[10]:ARRAY OF rec, p:PTR TO rec, i p:=a FOR i:=0 TO 9 a[i].tag:=i p.check++:=i ENDFOR FOR i:=0 TO 9 IF a[i].tag<>a[i].check WriteF('Whoops, a[\d] ist schiefgegangen...\n', i) ENDIF ENDFOR ENDPROC Wenn du lange genug darüber nachdenkst wirst Du sehen, daß a[0].tag das gleiche ist wie a.tag. Deshalb, weil a ein Zeiger zu dem ersten Element des Feldes ist und die Elemente des Feldes Objekte sind. Deshalb ist a ein Pointer zu einem Objekt (das erste Objekt in dem Feld). ======================================================================== Amiga System Objekte -------------------- Es gibt viele verschiedene Amiga System Objekte. Z.B. gibt es eines, welches die Information zum erzeugen eines Gadget beinhaltet (wie das `close' Gadget bei vielen Fenstern) und eines, welches alle Informationen über ein Prozeß oder Task beinhaltet. Diese Objekte sind unheimlich wichtig und werden deshalb mit E geliefert in der Form von `modules'. Jedes Modul ist abgestimmt auf ein spezielles Gebiet des Amiga Systems und beinhaltet Objekt und andere Definitionen. Module werden später ausführlicher behandelt (siehe Module). ======================================================================== LIST und STRING Typen ===================== Felder sind vielen Computer Sprachen gemeinsam. Wie auch immer, sie können ein Stück unangenehm sein, weil du immer sicher sein mußt, daß du nicht außerhalb des Endes eines Feldes bist, wenn Du es beschreibst. Deshalb kommen jetzt die STRING und LIST Typen ins Spiel. STRING ist sehr ähnlich wie ARRAY OF CHAR; und LIST ist ähnlich wie ARRAY OF LONG. Wie auch immer, jede hat einen Satz von E (eingebaute) Funktionen, welche sicher Variablen dieser Typen manipulieren, ohne ihre Begrenzungen zu überspringen. Next Step:  Normale Strings und E-Strings   String Funktionen   Listen und E-Listen   Listen Funktionen   Komplexe Typen   Typ Listen   Statische Daten  ======================================================================== Normal Strings und E-Strings ---------------------------- Normale Strings gibt es in vielen Programmiersprachen. Sie sind einfach ein Feld von Zeichen, wobei das Ende des Strings mit einem Null Zeichen (ASCII Null) gekennzeichnet wird. Wir haben schon mal normale Zeichenketten angetroffen (siehe Strings). Die, die wir benutzt haben, waren Konstante Zeichenketten, eingeschlossen in ' Zeichen, und sie kennzeichneten Pointer zu dem Speicher, wo die String Daten gespeichert waren. Deshalb kannst du eine String Konstante zu einem Zeiger (zu CHAR) zuweisen und wirst ein Feld mit bereits gefüllten Elementen bekommen, d.h., ein initialisiertes Feld. DEF s:PTR TO CHAR s:='This is a string constant' /* s[] ist T und s[2] ist i */ Erinnere dich, daß LONG eigentlich PTR TO CHAR ist, so daß dieser Code das gleiche ist wie: DEF s s:='This is a string constant' Das folgende Diagramm erläutert die obige Zuordnung zu s. Die ersten beiden Zeichen (s[0] und s[1]) sind T und h und das letzte Zeichen (vor der abschließenden Null) ist t. Speicher, der gekennzeichnet ist durch `Unbekannt', ist nicht ein Teil der String Konstanten. +--------+ |Variable| | 's' | |--------| |Adresse +----* +--------+ \ \ \ +---------+ +--\----+ +-------+ +-------+ +-------+ +---------+ |Unbekannt| | s[0] | | s[1] | | s[24] | | s[25] | |Unbekannt| Speicher: |+-------+| |+-----+| |+-----+|...|+-----+| |+-----+| |+-------+| || XXX || || "T" || || "h" || || "t" || || 0 || || XXX || +=========+ +=======+ +=======+ +=======+ +=======+ +=========+ E-Zeichenketten sind sehr ähnlich wie normale Zeichenketten und, tatsächlich, kann ein E-String immer dort genutzt werden, wo auch ein normaler String benutzt werden kann. Wie auch immer, das umgekehrte ist nicht wahr! Wenn etwas einen E-String fordert, kannst du nicht einen normalen String stattdessen verwenden!!! Der Unterschied zwischen einem normalen String und einem E-String wurde in der Einleitung zu diesem Abschnitt getroffen: E-Zeichenketten können geändert werden, ohne daß ihre Begrenzung übersprungen werden. Ein normaler String ist gerade mal ein Feld, so daß du vorsichtig sein mußt, um nicht die Begrenzungen zu überschreiten. Wie auch immer, ein E-String weiß welche Begrenzungen er hat und damit kann jede der String Manipulations-Funktionen zu einer sicheren Änderung führen. Eine E-String (STRING Typ) Variable wird deklariert wie in dem folgenden Beispiel, mit der Maximalen Größe des E-String, so wie bei einer Feld Deklaration. DEF s[30]:STRING Wie bei einer Feld Deklaration, die Variable s ist eigentlich ein Poiner zu den String Daten. Um ein E-String zu initialisieren brauchst du nur die Funktion StrCopy zu benutzen, wie wir sehen werden. ======================================================================== String Funktionen ----------------- Es gibt eine Menge an nützlichen, eingebauten Funktionen, welche Zeichenketten manipulieren. Erinnere dich, daß ein E-String genutzt werden kann, woimmer ein normaler String genutzt wird, aber normale Zeichenketten nicht genutzt werden können, wo ein E-String gefordert wird. Wenn ein Parameter als string gekennzeichnet ist, dann kann ein normaler oder E-String als Parameter übergeben werden, aber wenn es als e-string gekennzeichnet ist, dann kann nur ein E-String benutzt werden. String(maxsize) Fordert Speicher für einen E-String der Größe maxsize an und liefert einen Pointer zu den String Daten zurück. Es wird benutzt, um Platz für ein neuen E-String zu machen, wie eine STRING Deklarationen es tut. Das folgende Code Fragment ist praktisch gleich: DEF s[37]:STRING DEF s:PTR TO CHAR s:=String(37) Der einzige Unterschied ist, daß es nicht genug Speicherplatz geben kann, um den E-String zu halten, wenn die String Funktion ausgeführt wird. In diesem Fall wird der besonderer Wert NIL (eine Konstante) zurückgeliefert. Dein Programm muß prüfen, ob der zurückgelieferte Wert nicht NIL ist, bevor du es wie ein E-String benutzt. Der Speicher für die deklarierende Version wird zugeteilt, wenn das Programm ausgeführt wird, so daß dein Programm nicht ausgeführt wird, wenn es nicht genug Speicher gab. Die String Version wird oft dynamische Zuteilung genannt, weil es nur dann passiert, wenn das Programm läuft; bei der Deklarierenden Version wird die Zuteilung vom E Compiler vorgenommen. StrCmp(string1,string2,length) Vergleicht string1 mit string2 (sie können beide normale oder E-Zeichenketten sein). Liefert TRUE zurück, wenn die ersten length Zeichen der Zeichenketten passten, ansonsten FALSE. Die length kann die besondere Konstante ALL sein, was bedeutet, daß die Zeichenketten bei jedem Zeichen übereinstimmen muß. Z.B. die folgenden Vergleiche liefern alle TRUE zurück: StrCmp('ABC', 'ABC', ALL) StrCmp('ABCd', 'ABC', 3) StrCmp('ABCde','ABCxxjs',3) Und die folgenden FALSE (beachte die Groß-/Kleinschreibung der Zeichen): StrCmp('ABC', 'ABc', ALL) StrCmp('ABCd', 'ABC', ALL) StrCopy(e-string,string,length) Kopiert den Inhalt von string nach e-string. Nur length Zeichen werden kopiert von dem Quellen String, aber die besondere Konstante ALL kann benutzt werden um zu zeigen, daß der ganze Quellen String zu kopieren ist. Erinnere dich, daß E-Zeichenketten sicherer zu manipulieren sind, so daß das folgende Code Fragment zeigt, daß in s Mehr al steht, weil die Maximale Größe (von seiner Deklaration) sieben Zeichen ist. DEF s[7]:STRING StrCopy(s, 'Mehr als sieben Buchstaben', ALL) Eine Deklaration, die STRING (oder ARRAY) benutzt, reserviert einen kleinen Teil des Speichers und packt einen Pointer zu diesem Speicher in die Variable, die dafür deklariert wurde. Um damit Daten in diesen Speicher zu bekommen brauchst du es nur dorthin zu kopieren, indem Du StrCopy benutzt. Wenn Du vertraut bist mit höheren Programmiersprachen wie Basic, dann mußt Du vorsichtig sein, weil du annehmen könntest, daß du einen String zu einem Feld oder einer E-String Variable zuweisen könntest. In E (und Sprachen wie C und Assembler) muß Du ausdrücklich Daten in Felder und E-Zeichenketten kopieren. Du solltest nicht das folgende tun: /* Tue nicht Dinge wie diese! */ DEF s[80]:STRING s:='Dies ist eine String Konstante' Dieses ist sehr unglücklich: es schmeißt den Pointer weg, der zum reservieren von Speicher benötigt wurde und ersetzt ihn durch einen Pointer zu der String Konstanten. s ist dann nicht länger ein E-String und kann nicht dadurch repariert werden, indem StrLen benutzt wird. Wenn du möchtest, daß s weiterhin den obigen String beinhaltet, dann mußt du StrCopy benutzen: DEF s[80]:STRING StrCopy(s,'Dieses ist eine String Konstante', ALL) Die Moral ist: erinnere Dich, wann du Pointer zu Daten benutzt und wann du Daten kopierst. Erinnere dich auch, daß Zuordnungen nicht große Felder von Daten kopieren, es werden nur Pointer zu Daten kopiert, so wenn du einige Daten in einem ARRAY oder STRING Typ Variable speichern möchtest, dann kopiere sie dorthin. StrAdd(e-string,string,length) Dieses tut das gleiche wie StrCopy, aber der Quellen String wird am Ende des E-Zielstring kopiert. Das folgende Code-Fragment bringt in s Dies ist ein String und eine Hälfte. DEF s[30]:STRING StrCopy(s, 'Dies ist ein String', ALL) StrAdd(s, ' und eine Hälfte', ALL) StrLen(string) Liefert die Länge des string zurück. Hierbei wird angenommen, daß der String von einem Null Zeichen abgeschlossen ist (d.h. ASCII Null), welches richtig ist für jede Zeichenkette, die durch E-Zeichenketten und String Konstanten gemacht wurden. Wie auch immer, du kannst eine String Konstante kurz ausschauen lassen, wenn du das Null Zeichen benutzt (die besondere Sequenz \0). Z.B. liefern alle diese Aufrufe drei zurück: StrLen('abc') StrLen('abc\0def') Tatsache ist, viele der String Funktionen nehmen an, daß Zeichenketten mit Null abgeschlossen sind, so daß Du nicht das Null Zeichen in deinen Zeichenketten benutzen sollst, außer du weist wirklich, was Du tust. Für E-Zeichenketten ist StrLen weniger effizient als die EstrLen Funktion. EstrLen(e-string) Liefert die Länge des e-string zurück (erinnere dich, daß kann nur ein E-String sein). Dieses ist viel effizienter als StrLen, weil E-Zeichenketten ihre Länge wissen und deshalb nicht nach einem Null-Zeichen gesucht werden muß. StrMax(e-string) Liefert die maximale Länge des e-string zurück. Dieses ist nicht unbedingt die aktuelle Länge des E-Strings, eher ist es die Größe die in der Deklarationen mit STRING benutzt wurde oder der String Aufruf. RightStr(e-string1,e-string2,length) Diese ist wie StrCopy, aber es kopiert die rechtsstehenden Zeichen von e-string2 in e-string1 und beide Zeichenketten müssen E-Zeichenketten sein. Nur length Zeichen werden kopiert und die besondere Konstante ALL kann nicht benutzt werden (um alles von einem String zu kopieren mußt Du, natürlich, StrCopy benutzen). Als Beispiel, ein Wert von eins für length bedeutet, daß das letzte Zeichen von e-string2 in e-String1 kopiert wird. MidStr(e-string,string,index,length) Kopiert den Inhalt von string, beginnend bei index (welches ein Index wie bei einem Feld ist) nach e-string. Nur length Zeichen werden kopiert und die besondere Konstante ALL kann benutzt werden, wenn alle der restlichen Buchstaben in string kopiert werden sollen. Z.B. ruft das folgende Beipiel zweimal MidStr auf. s enthält dadurch vier: DEF s[30]:STRING MidStr(s, 'Gerade vier', 8, ALL) MidStr(s, 'Gerade vier, Sir', 8, 4) InStr(string1,string2,startindex) Liefert die Stelle zurück, an der string2 in string1 zum ersten mal vorkommt, beginnend bei startindex (in string1). Wenn string2 nicht vorkommt, dann wird -1 zurückgeliefert. TrimStr(string) Liefert die Adresse (d.h., einen Pointer zu) dem ersten Nicht-Whitespace Zeichen in string. Z.B., das folgende Code Fragment gibt in s den Wert 12345 zurück. DEF s:PTR TO CHAR s:=TrimStr(' \n \t 12345') LowerStr(string) Konvertiert alle Großen Buchstaben in string zu kleinen Buchstaben. Dieser Wechsel wird auf der Stelle gemacht, d.h., der Inhalt des Strings wird direkt geändert. UpperStr(string) Konvertiert alle kleinen Buchstaben in string zu großen Buchstaben. Wieder wird diese Änderung auf der Stelle gemacht. SetStr(e-string,length) Setzt die Länge von e-string auf length. E-Zeichenketten wissen wie lang sie sind, so wenn du einen E-String änderst (ohne eine E-String Funktion zu benutzen) und wechselst die Größe, dann brauchst Du diese Funktion, um die Länge zu setzen, bevor du es wieder wie ein E-String benutzen kannst. Z.B., wenn Du einen E-String wie ein Feld benutzt hast (was du tun kannst) und schreibst Zeichen geradewegs in dieses hinein, dann mußt Du die Länge setzen, bevor du ihn als etwas anderes benutzt als ein Feld/String: DEF s[10]:STRING s[0]:="a" /* Errinnere Dich, daß "a" ein Buchstabenwert ist. */ s[1]:="b" s[2]:="c" s[3]:="d" /* An diesen Punkt ist s gerade ein Feld von CHAR. */ SetStr(s, 4) /* Jetzt kann s benutzt werden wie ein E-String.*/ SetStr(s, 2) /* s ist ein Stück kleiner, aber dennoch ein E-String.*/ Beachte, daß diese Funktion zum verkürzen eines E-String benutzt werden kann (aber du kannst ihn nicht auf diesem Weg verlängern). Val(string,address) Was diese Funktion tut ist klar, aber wie du sie benutzen sollst ist ein bischen kompliziert. Hauptsächlich konvertiert es string zu einem LONG Integer. Führende whitespaces werden ignoriert und ein führendes % oder $ bedeutet, daß der String eine binäre oder Hexadezimale Integerzahl kennzeichnet (auf dem selben Weg, wie es bei numerischen Konstanten gemacht wird). Der dekodierte Integer wird zurückgeliefert. Die Anzahl der Zeichen von string, die gelesen wurden, um den Integer zu machen, werden in address gespeichert, welche gewöhnlich eine Variablen-Adresse ist (von {var }). Wenn address die besondere Konstante NIL (oder Null) ist, dann wird die Anzahl nicht gespeichert. Du kannst diese Zahl benutzen, um die Position in dem String zu berechnen, welche nicht ein Teil der Integerzahl in dem String gewesen ist. Wenn ein Integer nicht aus dem String dekodiert werden kann, dann wird Null zurückgeliefert und Null wird in address gespeichert. Folge den Kommentaren in diesem Beispiel und schenke besondere Aufmerksamkeit auf die Benutzung des Pointer p. DEF s[30]:STRING, value, chars, p:PTR TO CHAR StrCopy(s, ' \t \n 10 \t $3F -%0101010', ALL) value:=Val('abcde 10 20', {chars}) /* Nach der obigen Zeile sind value und chars beide Null */ value:=Val(s, {chars}) /* Nun ist value 10, chars ist 7 */ p:=s+chars /* p zeigt jetzt auf das Leerzeichen nach der 10 in s */ value:=Val(p, {chars}) /* value ist nun $3F (63), cahrs ist jetzt 6 */ p:=p+chars /* p zeigt jetzt auf das Leerzeichen nach $3F in s */ value:=Val(p, {chars}) /* value ist nun -%0101010 (-42), chars ist 10 */ Es gibt noch ein paar andere String Funktionen (ReadStr und StringF) welche später behandelt werden (siehe Eingabe und Ausgabe Funktionen). ======================================================================== Listen und E-Listen ------------------- Listen sind eher Zeichenketten mit LONG als CHAR Elementen (damit sind sie sehr ähnlich wie ARRAY OF LONG). Das List Äquivalent eines E-String ist etwas, daß eine E-Liste genannt wird. Es hat den gleichen Zustand wie ein E-String, ausgenommen, daß die Elemente LONG sind (damit können sie Pointer sein). Normalerweise sind Listen eher wie String Konstanten zu betrachten, ausgenommen, daß die Elemente von Variablen aufgebaut werden können und damit nicht Konstanten sein müssen. Gerade weil Zeichenketten nicht echte E-Zeichenketten sein müßen, sind (normale) Listen nicht echte E-Listen. Listen werden geschrieben, indem [ und ] benutzt wird, um Elemente durch Kommas abzutrennen. Wie String Konstanten, eine Liste liefert die Adresse des Speichers zurück, welche das Element beinhaltet. Z.B., das folgende Code Fragment DEF list:PTR TO LONG, number number:=22 list:=[1,2,3,number] ist gleich zu: DEF list[4]:ARRAY OF LONG, number number:=22 list[0]:=1 list[1]:=2 list[2]:=3 list[3]:=number Nun, welche von diesen zwei Versionen würde du lieber schreiben? Wie du sehen kannst, sind Listen sehr nützlich um ein Programm leichter zu schreiben und viel einfacher zu lesen. E-Listen Variablen sind wie E-String Variablen und werden auf dem gleichen Weg deklariert. Das folgende Code Fragment deklariert lt als eine E-Liste mit der maximalen Größe von 30. Wie immer, lt ist ein Pointer (zu LONG), und es zeigt zu dem Speicher, der durch die Deklarationen allokiert wurde. DEF lt[30]:LIST Listen sind sehr nützlich zum schreiben von tag Listen, welche immer mehr in wichtigen Amiga System Funktionen genutzt werden. Eine tag-Liste ist eine Liste, wobei die Element alle als Paare benutzt werden. Das erste Element von einem Paar ist das tag und das zweite enthält einige Daten für das tag. Siehe das `ROM Kernel Reference Manual (Libraries)' für mehr Details. ======================================================================== List Funktionen --------------- Es gibt eine Menge von List Funktionen, welche sehr ähnlich zu den String Funktionen sind (siehe String Funktionen). Erinnere dich, daß E-Listen die List Äquivalente von E-Zeichenketten sind, d.h., sie können sicher verändert und vergrößert werden, ohne ihre Grenzen zu überschreiten. Wie bei E-Zeichenketten, E-Listen sind abwärtskompatibel mit Listen. Deshalb, wenn eine Funktion eine Liste als einen Parameter anfordert, dann kannst Du eine Liste oder eine E-Liste liefern. Aber wenn eine Funktion ein E-Liste anfordert, dann kannst Du nicht eine Liste an Stelle benutzen. List(maxsize) Fordert Speicher für eine E-Liste der maximalen Größe maxsize an und liefert einen Zeiger zu den List Daten zurück. Es wird benutzt um Platz für eine neue E-Liste zu erhalten, wie eine LIST Deklaration dieses auch tut. Die folgenden Code Fragmente sind (wie mit String) praktisch gleich: DEF lt[46]:LIST DEF lt:PTR TO LONG lt:=List(46) Erinnere dich, daß du den zurücklieferten Wert von List prüfen mußt, daß dieser nicht NIL ist, bevor eine E-Liste benutzt wird. ListCmp(list1,list2,length) Vergleicht list1 mit list2 (sie können beide normale oder E-Listen sein). Arbeitet wie StrCmp diese für E-Zeichenketten tut, damit, als Beispiel, die folgenden Vergleiche liefern alle TRUE zurück: ListCmp([1,2,3,4], [1,2,3,4], ALL) ListCmp([1,2,3,4], [1,2,3,7], 3) ListCmp([1,2,3,4,5], [1,2,3], 3) ListCopy(e-list,list,length) Arbeitet gerade wie StrCopy und das folgende Beispiel zeigt, wie eine E-Liste zu initialisieren ist: DEF lt[7]:LIST, x x:=4 ListCopy(lt, [1,2,3,x], ALL) Wie mit StrCopy kann eine E-Liste nicht Überfüllt werden durch die Benutzung von ListCopy. ListAdd(e-list,list,length) Arbeitet gerade wie StrAdd, so daß die nächsten Code Fragmente als Resultat in die E-Liste lt die E-List Version von [1,2,3,4,5,6,7,8] packen. DEF lt[30]:LIST ListCopy(lt, [1,2,3,4], ALL) ListAdd(lt, [5,6,7,8], ALL) ListLen(list) Arbeitet gerade wie StrLen, liefert die Länge der list zurück. Dort gibt es keine E-Listen spezifische Längen-Funktion. ListMax(e-list) Arbeitet gerade wie StrMax, liefert die maximale Länge der e-List zurück. SetList(e-list,length) Arbeitet gerade wie SetStr, setzt die Länge von e-List auf length. ListItem(list,index) Liefert das Element von list an der Stelle index zurück. Z.B., wenn lt eine E-Liste ist, dann ListItem(lt,n) ist das gleiche wie lt[n]. Diese Funktion ist sehr nützlich, wenn die Liste keine E-Liste ist. Z.B., die folgenden zwei Code Fragmente sind gleich: WriteF(ListItem(['Fred','Barney','Wilma','Betty'], name)) DEF lt:PTR TO LONG lt:=['Fred','Barney','Wilma','Betty'] WriteF(lt[name]) ======================================================================== Komplexe Typen -------------- In E werden die STRING und LIST Typen komplexe Typen genannt. Komplexe Typen Variablen können auch erzeugt werden durch die Benutzung der String und List Funktionen, wie wir in den vorherigen Abschnitten gesehen haben. ======================================================================== Typ Listen ---------- Normale Listen beinhalten LONG Elemente, so daß du initialisierte Felder von LONG Elementen schreiben kannst. Was ist mit anderen Arten von Feldern? Nun, daß ist, was Type Listen sind. Du gibst den Typ der Elemente einer Liste an durch die Benutzung von  :Typ nach den schließenden ]. Die erlaubten Typen sind CHAR, INT, LONG und irgendwelche Objekttypen. Es gibt einen spitzfindigen Unterschied zwischen einer normalen, LONG Liste und einer Typ Liste (sogar einer LONG Typ Liste): nur normale Listen können mit den List Funktionen benutzt werden (siehe List Funktionen). Aus diesem Grund, der Term `Liste' tendiert dahin, nur auf normale Listen zu verweisen. Das folgende Code Fragment benutzt das Objekt rec, daß früher definiert wurde (siehe Beispiel Objekt) und gibt ein Paar Beispiele von Typ Listen: DEF ints:PTR TO INT, objects:PTR TO rec, p:PTR TO CHAR ints:=[1,2,3,4]:INT p:='fred' objects:=[1,2,p,4, 300,301,'barney',303]:rec Ist gleich zu DEF ints[4]:ARRAY OF INT, objects[2]:ARRAY OF rec, p:PTR TO CHAR ints[0]:=1 ints[1]:=2 ints[2]:=3 ints[3]:=4 p:='fred' objects[0].tag:=1 objects[0].check:=2 objects[0].table:=p objects[0].data:=4 objects[1].table:='barney' objects[1].tag:=300 objects[1].data:=303 objects[1].check:=301 Die letzte Gruppe der Zuordnungen zu objects[1] wurden absichtlich in der richtigen Ordnung eingegeben, um zu betonen, daß die Ordnung der Elemente in der Definition des Objektes rec entscheidend ist. Jedes der Elemente der Liste korrespondiert zu einem Element in dem Objekt und die Ordnung der Elemente in der Liste korrespondiert zu der Ordnung in der Objekt Definition. In dem Beispiel, die (Objekt) Listen-Zuordnungs-Zeile wurde abgebrochen nach dem Ende des ersten Objektes (das vierte Element) um es ein bischen einfacher lesbar zu machen. Das letzte Objekt in der Liste braucht nicht vollständig definiert zu werden, damit, zum Beispiel, braucht die zweite Zeile der Zuordnung nur die ersten drei Elemente beinhalten. ======================================================================== Statische Daten ------------------ String Konstanten (z.B. fred), Listen (z.B. [1,2,3]) und Typ Listen (z.B. [1,2,3]:INT) sind statische Daten. Das bedeutet, daß die Adresse der (initialisierten) Daten bekannt ist, wenn das Programm startet. Normalerweise brauchst Du dich nicht darüber zu ärgern, aber, zum Beispiel, wenn du eine Serie von Listen als initialisierte Felder hast, kann du nur eine Schleife benutzen: PROC main() DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG FOR i:=0 TO 9 a[i]:=[1, i, i*i] /* Diese Zuordnung ist wahrscheinlich nicht das was du möchtest! */ ENDFOR FOR i:=0 TO 9 p:=a[i] WriteF('a[\d] ist ein Feld an der Adresse \d\n', i, p) WriteF(' und das 2. Element ist \d\n', p[1]) ENDFOR ENDPROC Das Feld a ist ein Feld von Pointern zu initialisierten Feldern (welche alle drei Elemente groß sind). Aber, wie der Kommentar es erklärt und das Programm es zeigt, war dieses wahrscheinlich nicht das, was beabsichtigt war, weil die Liste statisch ist. Daß bedeutet, die Adresse der Liste ist festgeschrieben, so daß jedes Element von a die gleiche Adresse bekommt (d.h, das gleiche Feld). Weil i benutzt wird in der Liste, der Inhalt des Speicherteils variiert etwas an der ersten FOR Schleife, die abgearbeitet wird. Aber nach dieser Schleife ist der Inhalt fest und das zweite Element von jedem der zehn Felder ist immer neun. Dies ist ein Beispiel von der Ausgabe, die erzeugt wird (das ... stellt eine Anzahl ähnlicher Zeilen dar): a[0] ist ein Feld an der Adresse 4021144 und das 2. Element ist 9 a[1] ist ein Feld an der Adresse 4021144 und das 2. Element ist 9 ... a[9] ist ein Feld an der Adresse 4021144 und das 2. Element ist 9 Die Erkenntnis ist, daß die dynamisch Zuteilungs-Funktion List benutzt wird und die normale Liste in die neue E-Liste durch die Benutzung von ListCopy kopiert wird: PROC main() DEF i, a[10]:ARRAY OF LONG, p:PTR TO LONG FOR i:=0 TO 9 a[i]:=List(3) /* Überprüfen, daß die Zuteilung erfolgreich war vor dem kopieren */ IF a[i]<>NIL THEN ListCopy(a[i], [1, i, i*i], ALL) ENDFOR FOR i:=0 TO 9 p:=a[i] IF p=NIL WriteF('Could not allocate memory for a[\d]\n', i) ELSE WriteF('a[\d] ist ein Feld an der Adresse \d\n', i, p) WriteF(' und das 2. Element ist \d\n', p[1]) ENDIF ENDFOR ENDPROC Das Problem ist nicht so dramatisch mit String Konstanten, weil der Inhalt fest ist. Wie auch immer, wenn du den Inhalt deutlich veränderst, wirst Du Sorge dafür tragen müßen, daß Du nicht in das gleiche Problem reinrennst, wie es das nächste Beispiel zeigt. PROC main() DEF i, strings[10]:ARRAY OF LONG, s:PTR TO CHAR FOR i:=0 TO 9 strings[i]:='Hello World\n' /* Diese Zuordnung ist wahrscheinlich nicht das, was du möchtest! */ ENDFOR s:=strings[4] s[5]:="X" FOR i:=0 TO 9 WriteF('strings[\d] ist ', i) WriteF(strings[i]) ENDFOR ENDPROC Dieses ist ein Beispiel der Ausgabe, die erzeugt wird (wieder, das ... stellt eine Anzahl ähnlicher Zeilen dar): strings[0] ist HelloXWorld strings[1] ist HelloXWorld ... strings[9] ist HelloXWorld Wieder ist die Erkenntnis, dynamisch Zuteilung zu benutzen. Die Funktionen String und StrCopy werden auf dem gleichen Weg benutzt wie List und ListCopy, wie oben gezeigt. ======================================================================== Gelinkte Listen =============== E-Listen und E-Zeichenketten haben eine nützliche Erweiterung: sie können benutzt werden, um gelinkte Listen zu machen. Diese sind wie die Listen, die wir gesehen haben, mit der Ausnahme, daß die List Elemente nicht einen angrenzende Block von Speicher besetzen. Stattdessen hat jedes Element ein zusätzliches Stück an Daten: einen Pointer zu dem nächsten Element in der Liste. Diese bedeutet, daß jedes Element irgendwo im Speicher sein kann (Normalerweise ist das nächste Element einer Liste in der nächsten Speicherposition.) Das Ende einer gelinkten Liste wird erreicht, wenn der Zeiger zu dem nächsten Element den besonderer Wert NIL hat (eine Konstante). Du mußt vorsichtig sein und prüfen, daß der Pointer nicht NIL ist, weil, wenn er es ist und du beziehst dich darauf, dann wird Dein Programm definitiv abstürzen. Die Elemente einer gelinkten Liste sind E-Listen oder E-Zeichenketten (d.h., die Elemente sind komplexe Typen). Damit kannst du E-Listen linken, um eine `gelinkte' Liste von E-Listen' zu bekommen (oder, einfacher, eine `Liste von Listen'). Das verbinden von E-Zeichenketten ergibt `gelinkte Listen von E-Zeichenketten' oder eine `Liste von Zeichenketten'. Du mußt Dir nichts bei diesen beiden Arten von gelinkten Listen denken, obwohl: du kannst eine Mischung von E-Listen und E-Zeichenketten in der gleiche gelinkten Liste benutzen. Um ein komplexes Type-Element zu einem anderen zu linken mußt du die Link Funktion benutzen und um nachfolgende Elemente in einer gelinkten Liste zu finden mußt du die Next oder Forward Funktion benutzen. Link(complex1,complex2) Verbindet complex1 zu complex2. Beide müßen eine E-Liste oder ein E-String sein, mit der Ausnahme, daß complex2 die besonderer Konstante NIL sein kann, um zu zeigen, daß complex1 das Ende der gelinkten Liste ist. Der Wert complex1 wird zurückgeliefert von der Funktion, welcher nicht immer nützlich ist, so daß, gewöhnlich, der Aufruf von Link als Anweisung lieber benutzt wird als als Funktion. Der Effekt von Link ist, daß complex1 auf complex2 zeigt, als das nächste Element in der gelinkten Liste ( somit ist complex1 der Kopf der Liste und complex2 ist das Ende). Für E-Listen und E-Zeichenketten ist der Zeiger zu dem nächsten Element normalerweise mit NIL initialisiert, damit du nur Link mit einem NIL Parameter benutzen mußt, wenn du eine gelinkte Liste kleiner machen möchtest (wobei das Ende verloren geht). Next(complex) Liefert den Zeiger zu dem nächsten Element in der gelinkten Liste zurück. Dieser kann die besondere Konstante NIL sein, wenn complex das letzte Element in der gelinkten Liste ist. Sei vorsichtig und prüfe, daß der Wert nicht NIL ist, bevor du darauf zugreifst! Folge den Kommentaren in dem Beispiel unten: DEF s[23]:STRING, t[7]:STRING, lt[41]:LIST, lnk /* Der nächsten zwei Zeilen setzen die gelinkte Liste "lnk" rauf */ lnk:=Link(lt,t) /* lnk Liste startet bei lt und ist lt->t */ lnk:=Link(s,lt) /* Nun startet sie bei s und ist s->lt->t */ /* Der nächste drei Zeilen folgen den Links in "lnk" */ lnk:=Next(lnk) /* Jetzt startet sie bei Lt und ist Lt->t */ lnk:=Next(lnk) /* Jetzt startet sie bei t und ist t */ lnk:=Next(lnk) /* Jetzt ist lnk NIL und damit hat die Liste geendet */ Du kannst sicher Next mit ein NIL Parameter aufrufen, und in diesem Fall wird NIL zurückgeliefert. Forward(complex,expression) Liefert einen Zeiger zu dem Element zurück, welches die Anzahl expression an links unter der gelinkten Liste complex steht. Wenn expression den Wert eins darstellt, dann wird ein Zeiger zu dem nächsten Element zurückgeliefert (gerade wie bei Next). Wenn es zwei ist, dann wird ein Zeiger zu der eins zurückgeliefert. Wenn expression eine Zahl darstellt, welche größer ist, als die Anzahl der links in der Liste, dann wird der besondere Wert NIL zurückgeliefert. Weil der link in einer gelinkten Liste ein Zeiger zu dem nächstem Element ist, kannst du nur über die Liste von Anfang bis zum Ende schauen. Technisch ist dies eine einfache gelinkte Liste (eine doppelte gelinkte Liste würde also auch einen Zeiger zu dem vorherigen Element in der Liste haben, der es ermöglicht, rückwärts in der Liste zu suchen). Gelinkte Listen sind nützlich, um Listen zu bauen, die ganz groß wachsen können. Dieses, weil es viel besser ist, viele kleine Stücke von Speicher zu haben als einen großen Kuchen. Wie auch immer, du brauchst dich nicht zu Ärgern über diese Dinge, wenn Du mit ganz großen Listen spielst (wie ein Buchführer mit Über 100,000 Elementen!). ======================================================================== Mehr über Anweisungen und Ausdrücke *********************************** Dieses Kapitel zeigt verschiedenen E Anweisungen und Ausdrücke im Detail, die nicht im Teil Eins behandelt wurden. Es ergänzt auch einige der teilweisen Beschreibungen, die in Teil Eins vorgegeben wurden. Next Step:  Ein Ausdruck im eine Anweisung ändern   Initialisierte Deklarationen   Zuordnungen   Mehr Ausdrücke   Mehr Anweisungen   Angeführte Ausdrücke   Assembler Anweisungen  ======================================================================== Ein Ausdruck in eine Anweisung ändern ==================================== Der VOID Operator konvertiert einen Ausdruck in eine Anweisung. Er tut dieses durch die Bestimmung des Ausdrucks und schmeißt dann das Resultat weg. Dieses scheint nicht sehr nützlich zu sein, aber Tatsache ist, daß wir es einige Male schon getan haben. Wir haben nicht VOID ausdrücklich benutzt, weil E dieses automatisch tut, wenn es einen Ausdruck findet, der eine Anweisung behandelt (normalerweise wenn es auf der selben Zeile ist). Einige der Ausdrücke, die wir in Anweisungen benutzt haben, waren die Procedure-Aufrufe (zu WriteF und fred) und die Benutzung von ++. Erinnere dich, daß alle Proceduraufrufe Werte zurückliefern, weil sie in Wirklichkeit Funktionen sind, die, voreingestellt, Null zurückliefern (siehe Proceduren als Funktionen). Z.B. sind die folgenden Code Fragmentente alle gleich: VOID WriteF('Hello world\n') VOID x++ WriteF('Hello world\n') x++ Weil E automatisch VOID benutzt, bedeutet es nur ein kleines Stück an Zeit, es hinein zu schreibend, obwohl es Fälle geben kann, wo du es benutzen möchtest, um den Void-Prozeß deutlich zu machen (für den Leser). Das wichtigste Ding ist die Tatsache, daß Ausdrücke wie Anweisungen in E genutzt werden können. ======================================================================== Initialisierte Deklarationen ============================ Einige Variablen können mit Konstanten in ihrer Deklaration initialisiert werden. Die Variablen, die Du auf diese Weise nicht initialisieren kannst, sind Felder und komplexe Typen Variablen (und Procedure-Parameter, offensichtlich). All die anderen Arten können initialisiert werden, egal ob lokal oder global. Eine Initialisierte Deklaration sieht sehr stark wie ein Konstanten Definition aus, wobei der Wert dem Variablenname folgt und ein = Zeichen sie verbindet. Das folgende Beispiel erläutert initialisierte Deklarationen: SET ENGLISH, FRENCH, GERMAN, JAPANESE, RUSSIAN CONST FREDLANGS=ENGLISH OR FRENCH OR GERMAN DEF fredspeak=FREDLANGS, p=NIL:PTR TO LONG, q=0:PTR TO rec PROC fred() DEF x=1, y=88 /* Rest der Procedure */ ENDPROC Beachte, wie die Konstante FREDLANGS definiert wurde im Gegensatz zu der Initialisierten Deklaration von fredspeak zu etwas mittel komplizierten. Beachte auch die Initialisierung der Zeiger p und q und die Position der Typ-Information. Natürlich, wenn du Variablen mit irgend was mehr komplizierten als einer Konstanten initialisieren möchtest, dann kannst Du die Zuordnungen am Anfang des Codes benutzen. Im allgemeinen solltest du immer deine Variablen initialisieren (benutze irgendeine Methode) damit garantiert ist, daß sie einen Wert haben, wenn du sie benutzt. Benutzt Du den Wert einer Variablen, die Du nicht initialisiert hast, kann es wahrscheinlich zu einer Menge Trubel führen, weil der Wert irgendein zufällig Wert sein wird, der dann im Speicher steht. Es gibt Regeln, wie E einige Arten von Variablen initialisiert (siehe das `Reference Manual'), aber es ist weise, sogar diese zu initialisieren, wie .... (aus ! Ende! Genug!) dieses wird dein Programm mehr lesbar machen. ======================================================================== Zuordnungen =========== Wir haben schon einige Zuordnungen gesehen -- dieses waren zugeordnete Anweisungen. Zugeordnete Ausdrücke sind ähnlich, mit der Ausnahme (wie Du vermutest), das sie in Ausdrücken benutzt werden können. Dieses, weil sie auf der rechten Seite der Zuordnung den Wert zurückliefern, also so wie das Ausführen einer Zuordnung. Dieses ist nützliche für das effiziente Prüfen, daß der Wert, der zugewiesen wurde, in Ordnung war. Als Beispiel, die folgenden Code Fragmente sind gleich, aber das erste benutzt einen zuordnenden Ausdruck an Stelle von einer normalen Zuordnungsanweisung. IF (x:=y*z)=0 WriteF('Fehler: y*z ist Null (und x ist Null)\n') ELSE WriteF('OK: y*z ist nicht Null (und x ist y*z)\n') ENDIF x:=y*z IF x=0 WriteF('Fehler: y*z ist Null (und x ist Null)\n') ELSE WriteF('OK: y*z ist nicht Null (und x ist y*z)\n') ENDIF Du kannst leicht den zuordnenden Ausdruck benennen: er ist in Klammern und nicht in einer Zeile von sich selbst. Beachte die Benutzung der Klammern, um den zuordnenden Ausdruck zu gruppieren. Technisch hat der Zuordnungsoperator eine sehr niedrige Rangfolge. Nichttechnisch gesehen wird er so viel wie er kann auf der rechten Seite machen, um den Wert, der zugewiesen wird, zu formen. Somit mußt Du Klammern benutzen, um zu verhindern, daß x den Wert ((y*z)=0) bekommt (welcher TRUE oder FALSE sein wird, d.h., -1 oder Null). Zuordnungsausdrücke, wie auch immer, erlauben nicht die die Zuweisung auf der linken Seite, wie dieses bei Zuordnungsanweisungen erlaubt ist. Das einzige, was erlaubt ist auf der linken Seite eines Zuordnungs-Ausdruck, ist ein Variablen Name, wobei die Anweisungsform folgendes erlaubt: var var [ Ausdruck ] var . obj_element_name var [ Ausdruck ] . obj_element_name ^ var Und jedes von diesen darf enden mit ++ oder --. Deshalb sind die folgenden alles richtige Zuordnungen (die letzten drei benutzen Zuordnungs-Ausdrücke): x:=2 x--:=1 x[a*b]:=rubble x.apple++:=3 x[22].apple:=y*z x[].pear--:=fred(2,4) x:=(y:=2) x[y*z].orange:=(IF (y:=z)=2 THEN 77 ELSE 33) WriteF('x ist jetzt \d\n', x:=1+(y:=(z:=fred(3,5)/2)*8)) Du darfst dich über das wundern, was ++ oder -- betrifft. Gut, es ist sehr einfach: sie berühren nur die var, welche ist x in allen von den obigen Beispielen. Beachte, daß x[].pear-- ist das gleiche wie x.pear--, wie schon früher erwähnt (siehe Element Typen). ======================================================================== Mehr Ausdrücke ============== Diese Abschnitt diskutiert Neben-Effekte, Einzelheit zu zwei neuen Operatoren (BUT und SIZEOF) und komplettiert die Beschreibung der AND und OR Operatoren. Next Step:  Neben-Effekte   BUT Ausdruck   Bitweise AND und OR   SIZEOF Ausdruck  ======================================================================== Neben-Effekte ------------- Wenn in einem Ausdruck bestimmt wird, den Inhalt einer Variablen zu wechseln, dann wird gesagt, der Ausdruck hat einen Neben-Effekt. Ein Zuordnender Ausdruck ist ein einfach Beispiel eines Ausdruck mit Neben-Effekt. Weniger offensichtlich wird eine beteiligte Funktion mit einem Zeiger zu einer Variablen aufgerufen. Im allgemeinen werden Ausdrücke mit Neben-Effekten ausgeführt, außer es ist wirkliche offensichtlich, was passiert. Dieses, weil es schwierig sein kann, Probleme in deinem Programm Code zu finden, wenn Beschreibungen ausarten in komplizierten Ausdrücken. Auf der anderen Seite, Neben-Effekt-Ausdrücke sind bündig und oft sehr elegant. Sie sind auch nützlich zum verschleiern deines Codes (d.h., es schwierig machen, zu verstehen--eine Form des Kopierschutzes!). ======================================================================== BUT Ausdruck --------------------- BUT wird benutzt, um zwei Ausdrucke zu zerlegen. exp1 BUT exp2 beurteilt exp1, aber liefert den Wert von exp2 zurück. Dieses scheint nicht sehr nützlich zu sein, auf dem ersten Blick, aber wenn der erste Ausdruck eine Zuordnung ist, dann erlaubt es einen mehr allgemeinen Zuordnungs-Ausdruck. Z.B. sind die folgenden Code Fragmente gleich: fred((x:=12*3) BUT x+y) x:=12*3 fred(x+y) Beachte, daß Klammern um den Zuordnungs-Ausdruck herum gebraucht werden (in dem ersten Fragment) für die Ursachen, die früher schon gezeigt wurden (siehe Zuordnung). ======================================================================== Bitweises AND und OR ------------------- Wie schon in den früheren Kapiteln beschrieben, die Operatoren AND und OR sind nicht einfache logische Operatoren. Tatsache ist, sie sind beide Bit-weise Operatoren, wobei ein bit eine binäre Zahl ist (d.h., die Nullen oder einsen in der binären Form einer Zahl). Um zu sehen, wie sie arbeiten, werden wir uns anschauen, was mit Nullen und Einsen passiert: x y x OR y x AND y ------------------------ 1 1 1 1 1 0 1 0 0 1 1 0 0 0 0 0 Nun, wenn du zwei Zahlen ANDst oder ORst werden die korrespondierenden Bits (binäre Ziffern) der Zahlen individuell verglichen, gemäß der obigen Tabelle. Wenn x %0111010 ist und y ist %1010010 dann wird x AND y %0010010 sein und x OR y würde %1111010 sein: %0111010 %0111010 AND OR %1010010 %1010010 ------- ------- %0010010 %1111010 Die Zahlen (in binärer Form) sind jede untereinander aufgeführt, gerade so, wie du Additionen mit normalen Zahlen vornimmst (d.h, beginnend mit den Zahlen auf der rechten Seite und, vielleicht, mit Nullen aufgefüllt auf der linken Seite). Die zwei Bits in jeder Spalte wurden geANDed bzw. geORd um das Resultat unter der Summenzeile abzubilden. Wie arbeitet dieses für TRUE und FALSE und logische Oerationen? Nun, FALSE ist die Zahl Null, damit sind alle Bits von FALSE Nullen und TRUE ist -1, womit es bei allen 32 Bits Einsen hat (diese Zahlen sind LONG, damit sind sie 32-Bit Quantitäten). So ergeben geAND-te und geOR-te Zahlen immer Zahlen, welche alle Nuller Bits haben (d.h., FALSE) oder alle Einser Bits (d.h., TRUE). Wie zutreffend. Es geht erst los, wenn Du anfängst Zahlen zu mischen, die nicht Null oder -1 sind, so daß du die Logik aufmischst. Die nicht-Null Zahlen eins und vier sind wirklich TRUE, aber 4 AND 1 ist %100 AND %001, welches Null ist (d.h., FALSE). So wenn du AND als logischen Operator benutzt ist es nicht unbedingt wahr, daß alle nicht-Null Zahlen TRUE darstellen. OR gibt nicht solche Probleme auf, so daß alle nicht-Null Zahlen als TRUE erkannt werden. Führe dieses Beispiel aus, um zu sehen, warum du vorsichtig sein solltest: PROC main() test(TRUE, 'TRUE\t\t') test(FALSE, 'FALSE\t\t') test(1, '1\t\t') test(4, '4\t\t') test(TRUE OR TRUE, 'TRUE OR TRUE\t') test(TRUE AND TRUE, 'TRUE AND TRUE\t') test(1 OR 4, '1 OR 4\t\t') test(1 AND 4, '1 AND 4\t\t') ENDPROC PROC test(x, title) WriteF(title) WriteF(IF x THEN ' ist TRUE\n' ELSE ' ist FALSE\n') ENDPROC Hier ist die Ausgabe, die erzeugt werden sollte: TRUE ist TRUE FALSE ist FALSE 1 ist TRUE 4 ist TRUE TRUE OR TRUE ist TRUE TRUE AND TRUE ist TRUE 1 OR 4 ist TRUE 1 AND 4 ist FALSE So, AND und OR sind hauptsächlich Bit-weise Operatoren und sie können als logische Operatoren in den meisten Fällen genutzt werden, wobei Null FALSE darstellt und alle anderen Zahlen TRUE. Vorsicht mußt Du walten lassen bei der Benutzung des AND mit einigen Paaren von nicht-Null Zahlen, weil das Bit-weise AND bei solchen Zahlen nicht immer ein nicht-Null (oder TRUE) Resultat zurück gibt. ======================================================================== SIZEOF Ausdruck ----------------- SIZEOF liefert die Größe, in Bytes, eines Objektes zurück. Diese kann nützlich sein für die Untersuchung von Speicherbedarf. Z.B. druckt das folgende Code Fragment die Größe des Objektes rec: OBJECT rec tag, check table[8]:ARRAY data:LONG ENDOBJECT PROC main() WriteF('Größe von rec-object ist \d Bytes\n', SIZEOF rec) ENDPROC Du kannst nun denken, daß SIZEOF unhandlich ist, weil du leicht die Größe eines Objekt berechnen kannst, indem du auf die Größe der Elemente schaust. Während dieses im allgemeinen wahr ist (jedenfalls für das rec Objekt), gibt es eine Sache, bei der man vorsichtig sein muß: Ausrichtung. Dieses bedeutet, daß ARRAY, INT, LONG und Objekt Typen Elemente an einer geraden Speicher Adresse beginnen müssen. Normalerweise ist dieses kein Problem, aber wenn du eine ungerade Anzahl von aufeinanderfolgenden CHAR Typ-Elelemten hast oder eine ungerade Größe eines ARRAY, ein zusätzlich, pad Byte, wird im Objekt eingeführt, damit die folgenden Elemente richtig ausgerichtet sind. Dieses pad Byte ist ein echter Bestandteil eines ARRAY. Damit ist gemeint, das Feldgrößen aufgerundet werden zu der nächsten geraden Zahl. Auf der anderen Seite sind pad Bytes ein unüblich Teil eines Objektes und ihre Anwesenheit bedeutet, daß die Objekt Größe nicht ganz das ist, was Du erwarten würdest. Versuche das folgende Programm: OBJECT rec2 tag, check table[7]:ARRAY data:LONG ENDOBJECT PROC main() WriteF('Größe von rec2-object ist \d Bytes\n', SIZEOF rec2) ENDPROC Der einzige Unterschied zwischen dem rec und rec2 Objekten ist, daß die Feldgröße ist sieben in rec2. Wenn du das Programm ausführst wirst du sehen, daß die Größe des Objektes sich nicht geändert hat. Wir können nun auch das table Element etwas größer machen (d.h., es kann acht Elemente haben). ======================================================================== Mehr Anweisungen ================ Dieser Abschnitt zeigt Einzelheiten von vier neuen Anweisungen (INC, DEC, JUMP und LOOP) und beschreibt die Benutzung von Labeln. Next Step:  INC and DEC Anweisungen   Label und die JUMP Anweisung   LOOP Block  ======================================================================== INC und DEC Anweisungen ---------------------- INC x ist das gleiche wie x:=x+1. Wie auch immer, weil es keine Addition macht ist es ein bischen effizienter. DEC x ist das gleiche wie x:=x-1. Der aufmerksame Leser darf denken, daß INC und DEC das gleiche ist wie ++ und --. Aber es gibt einen wichtigen Unterschied: INC x erhöht x immer um eins, wohingegen x++ evtl x um mehr als eins erhöht, in Abhängigkeit zu dem Typ, auf den x zeigt. Z.B., wenn x ein Zeiger zu INT ist, dann würde x++ x um zwei erhöhen (INT ist 16-Bit, welches zwei Bytes ist). ======================================================================== Label und die JUMP Anweisung -------------------------------- Ein Label bezeichnet eine Position in einem Programm und diese Namen sind Global (sie können in jeder Procedure genutzt werden). Die meiste gemeinsame Nutzung von Labeln ist die JUMP Anweisung, aber du kannst auch Labels benutzen um die Position von einigen Daten zu markieren (siehe Assembler Anweisungen). Um ein Label zu definieren schreibst Du einen Namen, gefolgt von einem Doppelpunkt, sofort vor der Position, die du kennzeichnen möchtest. Dieses muß vor dem Beginn einer Anweisung sein, entweder in der vorherigen Zeile (von sich selber) oder am Anfang der gleichen Zeile. Die JUMP Anweisung führt Programme von der Position aus, die durch Label gekennzeichnet wurden. Diese Position muß in der gleichen Procedure sein, aber sie kann, als Beispiel, außerhalb einer Schleife sein (und JUMP wird dann die Schleife abbrechen). Z.B. sind die folgenden Code Fragmente gleich: x:=1 y:=2 JUMP rubble x:=9999 y:=1234 rubble: z:=88 x:=1 y:=2 z:=88 Wie du sehen kannst hat die JUMP Anweisung ein überspringen der zweiten Zuordnungsgruppe mit x und y bewirkt. Ein nützlicheres Beispiel benutzt JUMP um eine Schleife zu beenden: x:=1 y:=2 WHILE x<10 IF y<10 WriteF('x ist \d, y ist \d\n', x, y) ELSE JUMP end ENDIF x:=x+2 y:=y+2 ENDWHILE end: WriteF('Beendet!\n') Diese Schleife endet wenn x nicht kleiner als zehn ist (die WHILE Prüfung) oder wenn y nicht kleiner als zehn ist (das JUMP in dem IF Block). Dieses scheint hübsch vertraut zu sein, weil es praktisch das gleiche wie in einem früheren Beispiel ist (siehe WHILE Schleife). Tatsache ist, es ist gleich zu: x:=1 y:=2 WHILE (x<10) AND (y<10) WriteF('x ist \d, y ist \d\n', x, y) x:=x+2 y:=y+2 ENDWHILE WriteF('Beendet!\n') ======================================================================== LOOP Block ---------- Ein LOOP Block ist eine Multi-Zeilen Anweisung. Es ist die allgemein Form von Schleifen, wie die WHILE Schleife, und es baut eine Schleife mit keinerlei Prüfungen. Somit wird diese Art von Schleife normalerweise nie enden. Wie auch immer, wie wir jetzt wissen, kannst Du einen LOOP Block beenden durch die JUMP Anweisung. Als Beispiel sind die folgenden zwei Code Fragmente gleich: x:=0 LOOP IF x<100 WriteF('x ist \d\n', x++) ELSE JUMP end ENDIF ENDLOOP end: WriteF('Beendet\n') x:=0 WHILE x<100 WriteF('x ist \d\n', x++) ENDWHILE WriteF('Beendet\n') ======================================================================== Quote Ausdrücke =============== Quote Ausdrücke sind eine mächtige Eigenschaft der E Sprache und sie erfordern ein ganzes Stück an vorgerücktem Wissen. Hauptsächlich kannst du quote irgendwelche Ausdrücke, indem Du mit dem `-Zeichen beginnst (sei vorsichtig! Nicht mischen mit dem '-Zeichen, welches mur für Zeichenketten bestimmt ist). Diese Handlung berechnet nicht den Ausdruck, sondern liefert an Stelle dessen die Adresse des Codes für den Ausdruck zurück. Diese Adresse kann benutzt werden wie jede andere Adresse, somit kannst du, als Beispiel, sie in einer Variable speichern und an Procedurem übergeben. Natürlich willst Du an einigen Punkten die Adresse zum ausführen des Codes benutzen und den Wert des Ausdrucks bekommen. Die Idee von solchen Ausdrücken wurde geboren von der Funktionalen Programmiersprache Lisp. Auch wurden einige mächtige Funktionen geboren, welche Listen mit quote-Ausdrücken verbinden um sehr bündige und lesbare Anweisungen zu schaffen. Next Step:  Evaluation   Quotable Ausdrücke   Listen und quote-Ausdrücke  ======================================================================== Evaluation ---------- Wenn du einen Ausdruck ge"quotest" hast, dann hast Du die Adresse des Codes, welcher den Wert des Ausdruck berechnet. Um diesen Ausdruck zu berechnen übergibst Du diese Adresse an die Eval Funktion. Damit haben wir jetzt einen Rundweg bei der Berechnung des Wertes eines Ausdrucks. PROC main() DEF adr, x, y x:=0; y:=0 adr:=`1+(fred(x,1)*8)-y x:=2; y:=7 WriteF('Der Wert ist \d\n', Eval(adr)) x:=1; y:=100 WriteF('Der Wert ist jetzt \d\n', Eval(adr)) ENDPROC PROC fred(a,b) RETURN (a+b)*a+20 Dieses ist die Ausgabe die erzeugt wird: Der Wert ist 202 Der Wert ist jetzt 77 Dieses Beispiel zeigt, wie ein komplizierter Ausdruck ge"quoted" wird. Die Adresse des Ausdrucks wird in der Variable adr gespeichert und der Ausdruck wird berechnet durch die Benutzung von Eval in dem Aufruf zu WriteF. Die Werte der Variablen x und y, wenn der Ausdruck ge"quoted" ist, sind unwesentlich--nur ihre Werte zu jeder Zeit, wenn Eval benutzt wird, sind entscheidend. Deshalb, wenn Eval in dem zweiten Aufruf zu WriteF benutzt wird haben sich die Werte von x und y geändert und damit ist der resultieren Wert unterschiedlich. Wiederholte Evaluationen des gleichen Ausdruck ist die offensichtlich Nutzung von quote Ausdrücken. Eine andere gemeinsame Nutzung ist, wenn du die selbe Sache für eine Vielzahl von unterschiedlichen Ausdrücken tun willst. Z.B. willst du die aufgelaufene Zeit aufdecken, die in Anspruch genommen wird um die Resultate von verschiedenen Ausdrücke zu berechnen. Dann würde es das beste sein, quoted Ausdrücke zu benutzen, etwa wie diese: DEF x,y PROC main() x:=999; y:=173 time(`x+y, 'Addition') time(`x*y, 'Multiplikation') time(`fred(x), 'Procedure-Aufruf') ENDPROC PROC time(exp, message) WriteF(message) /* Finde aktuelles Ergebnis */ Eval(exp) /* Finde neues Ergebnis und berechne den Unterschied, t */ WriteF(': Ergebnis \d\n', t) ENDPROC Dieses ist nur der Umriß eines Programms--es ist nicht vollständige, so laß es bitte nicht laufen. Die vollständige Version wird als erarbeitetes Beispiel später gegeben (siehe Zeit Ausdrücke). ======================================================================== Quotable Ausdrücke ------------------ Es gibt keine Beschränkungen in der Art von Ausdrücken, die du "quoten" kannst, mit der Ausnahme, daß du vorsichtig sein mußt mit variablen Umfängen. Wenn du lokale Variablen in einem "quote"-Ausdruck benutzt kannst Du nur Eval in der gleichen Procedure benutzen (somit sind die Variablen im Umfeld). Wie auch immer, wenn du nur globale Variablen benutzt kannst Du Eval in jeder Procedure benutzen. Deshalb, wenn du daran gehst einen "quote"-Ausdruck an eine Procedure zu übergeben und etwas damit zu tun, dann solltest du nur globale Variablen benutzen. Ein Wort der Warnung: Eval prüft nicht die übergebene Adresse, ob es wirklich eine Adresse eines Ausdruck ist. Du kannst deshalb in eine mißliche Lage kommen, wenn es dir passiert, sagen wir, das die Adresse einer Variable {var } benutzt wird. Du mußt alle Nutzungen von Dingen mit Eval selbst überprüfen, weil der E Compiler dich Dinge schreiben lässt wie Eval(x+9), wobei du wahrscheinlich meintest Eval(`x+9) zu schreiben. Das ist, weil du meintest, daß die Adresse, die an Eval übergeben wird, das Resultat eines komplizierten Ausdruckes ist. Damit meintest Du, daß Du x+9 als Parameter übergeben hast! ======================================================================== Listen und quote Ausdrücke -------------------------- Es gibt einige in E eingebaute Funktionen, welche Listen und "quote" Ausdrücke auf mächtigen Wegen benutzen. Diese Funktionen sind ähnlich zu funktionalen Programm Konstrukten und, hauptsächlich, erlauben sie einen sehr lesbaren Code, welcher sonst iterative Algorithmen erfordern würde (d.h., Schleifen). MapList(address,list,e-list,quoted-exp) Die address ist die Adresse einer Variablen (z.B. {x}), list ist ein Liste oder E-Liste (die Quelle), e-Liste ist ein E-Listen Variable (das Ziel) und quoted-exp ist die Adresse eines Ausdrucks, welcher die adressierte Variable enthält (z.B., `x+2). Der Effekt der Funktion ist, in einem Rutsch, einen Wert von list zu nehmen, ihn an address zu speichern, den "quote"-Ausdruck zu berechnen und das Resultat in der Ziel Liste abzulegen. Als Beispiel: MapList({y}, [1,2,3,a,99,1+c], lt, `y*y) resultiert in lt, daß folgenden Wert annimmt: [1,4,9,a*a,9801,(1+c)*(1+c)] Funktionale Programmierer würden sagen, daß MapList die Funktion zeichnet (der "quote"-Ausdruck) über die (Quellen) Liste. ForAll(address,list,quoted-exp) Arbeitet wie MapList, ausgenommen, daß die resultierende Liste nicht gespeichert wird. Stattdessen, ForAll liefert TRUE zurück wenn jedes Element der resultieren Liste TRUE ist (d.h., nicht-Null) und sonst FALSE. Auf diesem Weg entscheidet es sich, ob der "quote"-Ausdruck TRUE ist für alle Elemente der Quellen Liste. Als Beispiel, die folgenden sind TRUE: ForAll({x}, [1,2,-13,8,0], `x<10) ForAll({x}, [1,2,-13,8,0], `x<=8) ForAll({x}, [1,2,-13,8,0], `x>-20) und diese sind FALSE: ForAll({x}, [1,2,-13,8,0], `x OR x) ForAll({x}, [1,2,-13,8,0], `x=2) ForAll({x}, [1,2,-13,8,0], `x<>2) Exists(address,list,quoted-exp) Arbeitet wie ForAll, ausgenommen, das es TRUE zurückliefert wird wenn der "quote"- Ausdruck TRUE ist (d.h., nicht-Null) für mind. eines der Quellen Listelemente, ansonsten FALSE. Als Beispiel, die folgende sind TRUE: Exists({x}, [1,2,-13,8,0], `x<10) Exists({x}, [1,2,-13,8,0], `x=2) Exists({x}, [1,2,-13,8,0], `x>0) und diese sind FALSE: Exists({x}, [1,2,-13,8,0], `x<-20) Exists({x}, [1,2,-13,8,0], `x=4) Exists({x}, [1,2,-13,8,0], `x>8) ======================================================================== Assembler Anweisungen ===================== Die E Sprache beinhaltet einen Assembler, so daß du Assembler Mnemonics schreiben kannst wie E Anweisungen. Du kannst sogar vollständige Assembler-Programme schreiben und kompilieren mit dem E Compiler. Oder noch stärker kannst du E Variablen als einen Teil der mnemonics benutzen, damit kannst Du wirklich Assembler-Anweisungen mit normalen E Anweisungen mischen. Dieses ist nicht die richtige Stelle um Assembler Programmierung zu diskutieren, so wenn du planst, diese Eigenschaft von E zu nutzen, dann wirst Du ein gutes Buch kaufen müßen, Vorzugsweise für Amiga Assembler. Erinnere dich, daß der Amiga die Motorola 68000 CPU benutzt, somit mußt Du die Assemblersprache für diese CPU lernen. Mächtigere und neuere Amigas benutzen neuere CPUs (solche wie der 68020) welche zusätzliche mnemonics haben. Programme, die nur für 68000 CPU mnemonics geschrieben worden sind, laufen auf allen Amigas. Wenn du nicht die 68000 Assemblersprache kennst, kannst du diesen Abschnitt überspringen und im Verstand behalten, daß E Anweisungen, die Du nicht verstehst, wahrscheinlich Assembler mnemonics sind. Next Step:  Assembler und die E Sprache   Statischer Speicher   Dinge, die zu überwachen sind  ======================================================================== Assembler und die E Sprache --------------------------- Du kannst auf E Variablen einfach zugreifen, indem Du sie in einem Operanden nutzt. Folge den Kommentaren in dem nächsten Beispiel (die Kommentare sind an der gleichen Stelle wie die Assembler mnemonics, die anderen Zeilen sind normale E Anweisungen): PROC main() DEF x x:=1 MOVE.L x, D0 /* Kopiere den Wert in x nach Register D0 */ ADD.L D0, D0 /* verdoppel den Wert in D0 */ MOVE.L D0, x /* Kopiere den Wert in D0 zurück zur Variable x */ WriteF('x ist jetzt \d\n', x) ENDPROC Konstanten können genau so behandelt werden, aber du mußt die Konstante mit einem # kennzeichnen. CONST APPLE=2 PROC main() DEF x MOVE.L #APPLE, D0 /* Kopiere die Konstante APPLE zu Register D0 */ ADD.L D0, D0 /* Verdoppele den Wert in D0 */ MOVE.L D0, x /* Kopiere den Wert in D0 zur Variable x */ WriteF('x ist jetzt \d\n', x) ENDPROC Labels und Proceduren können ähnlich behandelt werden, aber sie sind PC-relativ, so daß du sie nur auf diesem Wege adressieren kannst. Das folgende Beispiel erläutert dieses, aber tut nichts nützliches: PROC main() DEF x LEA main(PC), A0 /* Kopiere die Adresse von main zum Register A0 */ MOVE.L A0, x /* Kopiere den Wert in A0 in die Variable x */ WriteF('x ist jetzt \d\n', x) ENDPROC Du kannst Amiga System Funktionen auf dem gleichen Weg aufrufen, wie du es normalerweise in Assembler machen würdest. Du mußt das Register A6 mit der zutreffenden Library-Basis laden, lade die anderen Register mit entsprechenden Daten und dann ab mit einem JSR in die System Routine. Dieses nächste Beispiel benutzt die E eingebaute Variable intuitionbase und die Intuition Library Routine DisplayBeep. Wenn du es ausführst wird der Bildschirm flackern (oder, mit Workbench 2.1 und obigen, kannst Du einen Beep oder einen gesampelten Sound bekommen, abhängig von deiner Workbench Einstellung). PROC main() MOVE.L #NIL, A0 MOVE.L intuitionbase, A6 JSR DisplayBeep(A6) ENDPROC Dummerweise arbeitet dieses nicht in der Version 2.1b des E Compiler (aber in der Version 3.0); damit mußt Du den Funktions-Offset-Wert benutzen (eine gute Quelle von diesen ist das `ROM Kernel Reference Manual (Includes and Autodocs)', welches diese Offsets in einem kleinen Abschnitt auf der Rückseite auflistet). Nun, DisplayBeep ist an dem Offset -$60 in der Intuition Library, somit wird aus dem obigen Programm: PROC main() MOVE.L #NIL, A0 MOVE.L intuitionbase, A6 JSR -$60(A6) ENDPROC ======================================================================== Statischer Speicher ------------------- Assembler-Programme reservieren statischen Speicher, für Dinge wie Zeichenketten, indem das DC mnemonic benutzt wird. Wie auch immer sind dieses keine echten mnemonics. Sie sind, tatsächlich, Compiler direktiven und sie können von Compiler zu Compiler sehr stark variieren. Die E Version kennt LONG, INT und CHAR, welche ein Liste von Werte annehmen, den betreffenden Teil an statischen Speicher reservieren und diesen Speicher mit den gelieferten Werten füllen. Die CHAR Form erlaubt auch ein Liste von Zeichen, um auch Strings abbilden zu können. In diesem Fall wird der String automatisch auf eine gerade Speicherzelle ausgerichtet, obwohl du dafür verantwortlich bist, ihn mit Null abzuschließen. Du kannst auch ein ganzes File als statische Daten einbinden, indem Du INCBIN benutzt (und das entsprechende File muß existieren, wenn das Programm kompiliert wird). Um an die Daten heranzukommen, mußt Du diese mit einem Label kennzeichnen. Dieses nächste Beispiel ist ein kleines bischen erfunden, aber erläutert statische Daten: PROC main() DEF x:PTR TO CHAR LEA datatable(PC), A0 MOVE.L A0, x WriteF(x) ENDPROC datatable: CHAR 'Hello world\n', 0 moredata: LONG 1,5,-999,0; INT -1,222 INCBIN 'file.data'; CHAR 0,7,-8 Der Assembler Teil, um die Labeladresse zu bekommen, ist nicht wirklich notwendig, damit kann das Beispiel so aussehen: PROC main() WriteF({datatable}) ENDPROC datatable: CHAR 'Hello world\n', 0 ======================================================================== Dinge, die zu beachten sind --------------------------- Es gibt ein paar Dinge, die zu Beachten sind, wenn Assembler mit E benutzt wird: * Alle mnemonics und Register müßem großgeschrieben sein, während, natürlich, E Variablen usw.., den normalen E Regeln folgen. * Die meisten Standardassembler benutzen ;, um den Rest einer Zeile als Kommentar darzustellen. In E benutzt Du die normalen /* und */ Begrenzer. * Register A4 und A5 werden intern von E benutzt und sollten somit nicht benutzt werden, wenn du E und Assembler Code mixt. * E Funktionsaufrufe wie WriteF können Register verändern. Schaue in dem `Reference Manual' für mehr Einzelheiten nach. ======================================================================== Und hier endet der Teil 1 des Kursus. Wem es Spaß gemacht hat und wer neugierig ist auf die Fortsetzung darf sich auf die nächste EPD freuen. Wer Anmerkungen und Kritik hat bzw. einige Punkte nicht versteht, der sollte mich doch mal anschreiben. Jörg