1 Programmiersprachen • Um einen Algorithmus in einer von einer Maschine ausführbaren Form beschreiben zu können, verwenden wir eine formale Sprache. – formale Beschreibung des Aufbaus der ”Worte” und ”Sätze”, die zur Sprache gehören – Anfällig gegenüber Mehrdeutigkeit, Veränderungen und Spontanität • Eine Beschreibung eines Algorithmus in einer formalen (formal beschriebenen) Sprache heisst ein Programm, die formale Sprache eine Programmiersprache 1 Programmiersprache vs. natürliche Sprache Eine natürliche Sprache ist ein komplexes System aus Lauten und Zeichen • Historisch gewachsen • Ständig im Wandel • Robust, Mehrdeutig, Ungenau • Verwendung willkürlicher gesprochener Symbole mit festgelegter Bedeutung (Wörterbücher, Rechtschreibregeln, Trennungsregeln, Grammatikregeln, Ausnahme-Listen, Sprach-”Gefühl”) 2 Eine Programmiersprache soll: • Datenstrukturen anbieten — zum Speichern und effizienten Bearbeitung von Daten • Operationen auf Daten erlauben • Kontrollstrukturen zur Ablaufsteuerung bereitstellen. 3 In dieser Vorlesung diskutieren wir Imperative Programmiersprachen, die eng mit dem von Neumann’schen Maschinenmodell verknüpft sind. • Ein imperatives Programm beschreibt eine Berechnung durch eine Folge von Anweisungen, die den Programmstatus (Speicherzellen, Programmzähler) verändern. • Der Programmzähler der CPU arbeitet Anweisung nach Anweisung sequentiell ab (der Programmierer hat seine Anweisungen explizit aufzureihen und Wiederholungen/Sprünge zu kodieren) • Der Speicher der Maschine dient zur Zustandsprotokollierung (der Zustand eines Algorithmus wird durch Variablenzuweisung bzw. -auslesen explizit kontrolliert) Diese Vorgehensweise ist unterschiedlich zur Programmierung in einer (↑funktionalen Programmiersprache) 4 1.1 Java - eine Programmiersprache 5 Ein erstes Java-Programm: public class Hello { public static void main ( String[] args ) { int zahl, quadrat; zahl = 4; quadrat = zahl * zahl; // offensichtlich eine Operation String text = "Das Quadrat von "; System.out.println(text + zahl + " ist " + quadrat); } } 6 Erläuterungen: • Jedes Programm hat einen Namen (hier: Hello). • Der Name steht hinter dem Schlüsselwort Klasse, was public ist, lernen wir später. • Der Name der Datei, die den Programmtext speichert, muss zum Namen des Programms “passen”, d.h. in diesem Fall Hello.java heißen. • Das Programm ist der Rumpf des Hauptprogramms, d.h. der Funktion main(). • Die Programm-Ausführung eines Java-Programms startet stets mit einem Aufruf von dieser Funktion main(). 7 class (was eine • Jedes Programm sollte Kommentare enthalten, damit man sich selbst später noch darin zurecht findet! • Ein Kommentar in Java hat etwa die Form: // Das ist ein Kommentar!!! • Wenn er sich über mehrere Zeilen erstrecken soll, kann er auch so aussehen: /* Dieser Kommentar geht über mehrere Zeilen! */ 8 Der ”Edit-Compile-Run”-Zyklus: • Erstellen (Editieren) des Programms mit einem Text-Editor z.B. Editor, Emacs, Word, etc. • Speichern des Programms als Textdatei • Übersetzen (Kompilieren) des Programms in eine maschinennnahe Sprache • Ausführen des übersetzten Programms 9 Java Programm Übersetzung: Stufe 1: Übersetzen des Quelltextes duch den Java-Compiler javac in Bytecode c http://www.gailer-net.de/tutorials/java/Notes/chap05/ch05_3.html ⃝ 10 Java Programm Übersetzung: Stufe 2: Der Java-Interpreter java führt den Code aus — Interpreter und CPU bilden eine Java Virtual Machine (JVM) c http://www.gailer-net.de/tutorials/java/Notes/chap05/ch05_4.html ⃝ 11 1.2 Variablen • Um Daten zu speichern und auf gespeicherte Daten zugreifen zu können, stellt Java Variablen zur Verfügung. • Variablen beziehen sich auf Speicherstellen im Hauptspeicher, die Daten eines ganz bestimmten Typs speichern können. • Variablen müssen erst einmal eingeführt, d.h. deklariert werden. 12 Beispiel: int zahl, quadrat; String text = "Das Quadrat von "; Diese Deklaration führt die drei Variablen mit den Namen zahl, quadrat und text ein. 13 Erklärung: • Das Schlüsselwort int besagt, dass diese Variablen ganze Zahlen (”Integers”) — der Typ der Variablen — speichern sollen. • Das Schlüsselwort String besagt, dass diese Variable Zeichenketten speichern soll. • Variablen können dann benutzt werden, um anzugeben, auf welche Daten Operationen angewendet werden sollen. • Variablen in einer Aufzählung sind durch Kommas “,” getrennt. • Am Ende steht ein Semikolon ”;”. 14 Java-Datentypen In Java können unterschiedliche Arten von Datentypen verwendet werden: • Primitive Datentypen • Ganzzahlige numerische Datentypen: short, int, long, ... • Logischer Datentyp: boolean • Rationale numerische Datentypen: float, double, ... • Alphanumerischer Datentyp: char • Vordefinierte Datentypen (z.B. : String, BufferedReader, InputStreamReader) • Selbstdefinierte Datentypen 15 Java-Basistypen Der Typ int gehört zu den Basistypen (primitive Datentypen), die von Java bereitgestellt werden. • Zu jedem Basistyp gibt es eine Menge möglicher Werte. • Jeder Wert eines Basistyps benötigt die gleiche Menge Platz, um ihn im Rechner zu repräsentieren. • Der Platz wird in Bit gemessen. (Wie viele Werte kann man mit n Bit darstellen?) 16 Es gibt vier primitive Datentypen für ganze Zahlen: Typ Platz kleinster Wert größter Wert byte 8 −128 127 short 16 −32 768 32 767 int 32 −2 147 483 648 2 147 483 647 long 64 −9 223 372 036 854 775 808 9 223 372 036 854 775 807 Die Benutzung kleinerer Typen wie byte oder short spart Platz. 17 Es gibt zwei primitive Datentypen für Gleitkommazahlen: Typ Platz kleinster Wert größter Wert float 32 ca. -3.4e+38 ca. 3.4e+38 7 signifikante Stellen double 64 ca. -1.7e+308 ca. 1.7e+308 15 signifikante Stellen • Für die Auswahl des geeigneten Typs sollte die gewünschte Genauigkeit des Ergebnisses berücksichtigt werden. • Eine Konstante gilt als Gleitkommazahl, wenn sie einen Dezimalpunkt oder einen Exponenten (durch E oder e eingeleitet) enthält (z.B.: 2.34, 1.7e+308, 3.2E-2). • Durch Anhängen von f oder F bzw. d oder D werden Gleitkomma-Konstanten als float- bzw. double-Werte kenntlich gemacht (z.B.: 2.34f, 1.7e8D). • Ohne Zusatz werden Konstanten als double interpretiert. 18 ... weitere Basistypen: Typ boolean char Platz Werte 1 true, false 16 alle Unicode-Zeichen Unicode ist ein Zeichensatz, der alle irgendwo auf der Welt gängigen Alphabete umfasst, also zum Beispiel: • die Zeichen unserer Tastatur (inklusive Umlaute); • die chinesischen Schriftzeichen; • die ägyptischen Hieroglyphen ... char-Konstanten schreibt man mit Hochkommas: 'A', ';', '\n'. 19 Weitere Java-Datentypen Der Datentyp String für Wörter ist kein Basistyp, sondern eine Klasse (dazu kommen wir später ...) Hier behandeln wir nur zwei Eigenschaften: • Man kann Wörter in Variablen vom Typ String abspeichern. • String-Konstanten haben die Form "Hello World!"; • Man kann Wörter mithilfe des Operators “+” konkatenieren. 20 Beispiel: String s0 = ""; String s1 = "Hel"; String s2 = "lo Wo"; String s3 = "rld!"; System.out.println(s0 + s1 + s2 + s3); ... schreibt Hello World! 21 auf die Ausgabe 1.3 Operationen Die Operationen sollen es gestatten, die Werte von Variablen zu modifizieren. Operationen auf ganzen Zahlen: • Negation - • Addition, Subtraktion, Multiplikation +, -, * • Ganzzahlige Division /, Modulo (Divisionsrest) % • Gleichheit, Ungleichheit ==, != • Größer, größer gleich, kleiner, kleiner gleich >, >=, <, <= • Bitoperationen (später) 22 Operationen auf Gleitkommazahlen: • Negation - • Addition, Subtraktion, Multiplikation +, -, * • Division /, Modulo (Divisionsrest) % • Gleichheit, Ungleichheit ==, != • Größer, größer gleich, kleiner, kleiner gleich >, >=, <, <= 23 Operationen auf Variablen vom Typ boolean: Kann bei Zuweisungen von logischen Ausdrücken verwendet werden und wird implizit bei Kontrollstrukturen verwendet. Logische Operatoren: • logische Negation: ! • logisches UND: && (a && b) ist nur dann wahr, wenn a und b beide wahr sind. • logisches ODER: || (a || b) ist nur dann falsch, wenn a und b beide falsch sind. Beachte: Das Ergebniss einer Vergleichsoperation auf Ganz- oder Gleitkommazahlen liefert einen booleschen Wert. 24 Eine wichtige Operation ist die Zuweisung. Die Zuweisung weist der Variablen auf der linken Seite den Wert des Ausdrucks auf der rechten Seite zu. Ein Ausdruck ist eine Kombination von • Literalen — Zeichen, die einen Wert darstellen, z.B.: 3.456, • Operatoren — Symbole, wie ”+” oder ”*”, die Operationen auf Operanden darstellen, • Variablen • Klammern — ”(” und ”)”, die verwendet wird, um einen Wert zu berechnen. 25 Beispiele: • x = 7; Die Variable x erhält den Wert 7. • result = x; Der Wert der Variablen x wird ermittelt und der Variablen result zugewiesen. • result = x + 19; Der Wert der Variablen x wird ermittelt, 19 dazu gezählt und dann das Ergebnis der Variablen result zugewiesen. 26 • result = x - 5; Der Wert der Variablen x wird ermittelt, 5 abgezogen und dann das Ergebnis der Variablen result zugewiesen. • result = (x - 5) * (x + 3) + -x; Beachte die richtige Klammerung und die Unterscheidung zwischen unären und binären Operatoren. 27 Inkrementieren und Dekrementieren Die unären Operatoren ++ und - inkrementieren bzw. dekrementieren eine Ganz- oder Gleikommazahl um 1. • ++ und - können vor (prefix) oder nach (postfix) dem Operanden stehen. • Wird die Prefix-Version in einem Ausdruck verwendet, so wird der bereits inkrementierte/dekrementierte Wert verwendet. • Wird die Postfix-Version in einem Ausdruck verwendet, so wird der initiale Wert verwendet. Die Operation wird erst nach der Ausdrucksauswertung durchgeführt. 28 Beispiel class PrePostDemo { public static void main(String[] args){ int i = 3; i++; System.out.println(i); ++i; System.out.println(i); System.out.println(++i); System.out.println(i++); System.out.println(i); } } 29 // "4" // // // // "5" "6" "6" "7" Präzedenz (Auswertereihenfolge) der Operatoren Unäre Operatoren ++, -, +, binäre arithmetische Operatoren *, /, +, Vergleichsoperatoren <, <=, >, >=, == != Logische Operatoren &&, || Zuweisungs-Operator = 30 Achtung: Java warnt nicht vor Überlauf/Unterlauf !! Beispiel: int x; x = 2147483647; // grösstes int x = x+1; ... setzt x auf -2147483648 31 Achtung: • Java bezeichnet die Zuweisung mit “=”anstelle von “:=” (Erbschaft von C ...) • Jede Zuweisung wird mit einem Semikolon “;” beendet. • In der Zuweisung x = x + 1; greift das x auf der rechten Seite auf den Wert vor der Zuweisung zu. • Zuweisung können auch direkt bei der Variablendeklaration durchgeführt werden, z.B.: String name = Hello World!; • Konstanten ändern ihren Wert während der Laufzeit des Programms nicht, z.B.: final float PI = 3.1415; 32 1.4 Typen und Casts Werden die Operatoren +, -, *, /, % auf ein Paar von Argumenten verschiedenen Typs angewendet, wird automatisch vorher der speziellere in den allgemeineren umgewandelt (implizite Typumwandlung oder implicit Type Cast) a a http://en.wikibooks.org/w/index.php?title=Java_Programming/Primitive_Types 33 double float Gleitkomma-Zahlen long int ganze Zahlen short byte char 34 Implizite Typumwandlung: Eine Variable wird einer anderen von anderem, erlaubten Typ zugewiesen oder eine Operation eines solchen Typs auf sie angwandt. Beispiel: short s = 123; int i; int j = 1024; i = s; i = j + s; 35 Beispiel: short xs = 1; int x = 999999999; (x + xs) liefert den int-Wert 1000000000 float xs = 1.0f; int x = 999999999; (x + xs) liefert den float-Wert 36 1.0E9 Achtung: • Das Ergebnis einer Operation auf float kann aus dem Bereich von float herausführen. Dann ergibt sich der Wert Infinity oder -Infinity. Das gleiche gilt für double. • Das Ergebnis einer Operation auf Basistypen, die in int enthalten sind (außer char), liefern ein int. • Wird das Ergebnis einer Variablen zugewiesen, sollte deren Typ dies zulassen • Mithilfe von expliziten Type Casts lässt sich das (evt. unter Verlust von Information) stets bewerkstelligen. • Java lässt keine Konvertierung von boolean zu anderen Datentypen zu. 37 Beispiel: int i, j; double d; ... i = j + d; i = (int) (j+d); // Fehler: i ist int, aber j + d ist double /* Korrekt: aber Verluste bei der Konversion werden in Kauf genommen */ 38 Beispiel: byte x = 1; ... x++; x = x + 1; // Korrekt: x ist nun 2 39 Beispiel: byte x = 1; ... x++; x = x + 1; // Korrekt: x ist nun 2 // Fehler: Auswertung von x+1 liefert int-Wert 40 Beispiele: (float) 1.7e+308 liefert Infinity (long) 1.7e+308 liefert 9223372036854775807 (d.h. den größten long-Wert) (int) 1.7e+308 liefert 2147483647 (d.h. den größten int-Wert) (short) 1.7e+308 liefert -1 (int) 1.0e9 liefert 1000000000 (int) 1.11 liefert 1 (int) -2.11 liefert -2 41 Beachte: • Jeder Wert in Java hat eine Darstellung als String. • Wird der Operator “+” auf einen Wert vom Typ String und einen anderen Wert x angewendet, wird x automatisch in seine String-Darstellung konvertiert ... ==⇒ ... liefert einfache Methode, um float oder double auszugeben !!! Beispiel: double x = -0.55e13; System.out.println("Eine Gleitkommazahl: " + x); ... schreibt Eine Gleitkommazahl: -0.55E13 42 auf die Ausgabe 1.5 Java-IO (Input/Output) Sehr häufig benötigen wir Operationen, um Daten (Zahlen) einlesen bzw. ausgeben zu können. • System.out.println(Meine Ausgabe); Diese Operation schreibt den Text ”Meine Ausgabe” auf die Ausgabe. • System.out.println(42); Diese Operation schreibt 42 auf die Ausgabe. • System.out.println(result); Diese Operation bestimmt den Wert der Variablen result und schreibt dann diesen auf die Ausgabe. 43 Das Einlesen von Daten ist komplizierter! 44 import java.io.*; class ReadMe { public static void main (String[] args) throws IOException { // Catch me if you can BufferedReader stdin = new BufferedReader (new InputStreamReader(System.in)); String inData; inData = stdin.readLine(); // inData in int konvertieren int zahl = Integer.parseInt(inData); } } 45 Online-Tutorial Java IO http://www.gailer-net.de/tutorials/java/Notes/chap10/ch10_1.html 46 Bemerkung Zur Vereinfachung werden wir im Folgenden oft zum Lesen von Daten die Funktion read() und zum Schreiben des Wertes einer Variablen die Funktion write(varname) oder write(Literal)verwenden. Beide Funktionen sind nicht Bestandteil von Java und werden nur zur Vereinfachung eingeführt! 47 1.6 Kontrollstrukturen Sequenz: int x, y, result; x = read(); y = read(); result = x + y; write(result); 48 • Zu jedem Zeitpunkt wird nur eine Anweisung (Statement) ausgeführt. • Jede Anweisung wird genau einmal ausgeführt. Keine wird wiederholt, keine ausgelassen. • Die Reihenfolge, in der die Anweisungen ausgeführt werden, ist die gleiche, in der sie im Programm stehen (d.h. nacheinander). • Mit Beendigung der letzten Anweisung endet die Programm-Ausführung. ==⇒ Sequenz alleine erlaubt nur sehr einfache Programme. 49 Selektion (bedingte Auswahl): Allgemein: if ( cond ) stmt else stmt 50 Selektion (bedingte Auswahl): int x, y, result; x = read(); y = read(); if (x > y) result = x - y; else result = y - x; System.out.println(result); • Zuerst wird die Bedingung ausgewertet. • Ist sie erfüllt, wird die nächste Anweisung ausgeführt. • Ist sie nicht erfüllt, wird die Anweisung nach dem else ausgeführt. 51 Beachte: • Anweisungen können selbst wieder aus Selektionen bestehen: int x; x = read(); if (x == 0) write(0); else if (x < 0) write(-1); else write(+1); 52 • ... oder aus (geklammerten) Folgen von Anweisungen: int x, y; x = read(); if (x != 0) { y = read(); if (x > y) write(x); else write(y); } else write(0); 53 Beachte: ein else bezieht sich immer auf das zuletzt öffnende if im selben Block if (x > 0) if (x < 10) write("aaa"); else write("bbb"); 54 Beachte: durch Klammerung wird ein seprater Block definiert if (x > 0) { if (x < 10) write("aaa"); } else write("bbb"); 55 Iteration (wiederholte Ausführung): Auch mit Sequenz und Selektion kann noch nicht viel berechnet werden ... oder wie sollten sich wiederholende Aktionen mit sich ändernden Werten durchgeführt werden? Allgemein: while ( cond ) stmt 56 Iteration (wiederholte Ausführung): int x, y; x = read(); y = read(); while (x != if (x < y = else x = write(x); y) y) y - x; x - y; 57 • Zuerst wird die Bedingung ausgewertet. • Ist sie erfüllt, wird der Rumpf des while-Statements ausgeführt. • der Rumpf besteht aus einer Anweisung oder aus einer geklammerten Sequenz von Anweisungen • Nach Ausführung des Rumpfs wird das gesamte while-Statement erneut ausgeführt. • Ist die Bedingung nicht erfüllt, fährt die Programm-Ausführung hinter dem while-Statement fort. 58 Jede (partielle) Funktion auf ganzen Zahlen, die überhaupt berechenbar ist, läßt sich mit Selektion, Sequenz, Iteration, berechnen Beweis: ↑ Berechenbarkeitstheorie. Idee: Eine Turing-Maschine kann alles berechnen... Versuche, eine Turing-Maschine zu simulieren! 59 Und nun der Euklidische Algorithmus in Java import java.io.*; public class GGT { public static void main (String[] args) throws IOException { BufferedReader stdin = new BufferedReader (new InputStreamReader(System.in)); } } int x, y; String inData = stdin.readLine(); x = Integer.parseInt(inData); inData = stdin.readLine(); y = Integer.parseInt(inData); while (x != y) if (x < y) y = y - x; else x = x - y; System.out.println(x); 60 2 Mehr Java Oft müssen viele Werte gleichen Typs gespeichert werden. Idee: • Lege sie konsekutiv ab! • Greife auf einzelne Werte über ihren Index zu! Feld: 17 3 -2 9 0 1 Index: 0 2 3 4 5 1 61 Beispiel: Einlesen eines Felds int[] a; // Deklaration int n = read(); a = new int[n]; // Anlegen des Felds int i = 0; while (i < n) { a[i] = read(); i = i+1; } 62 • type [] name ; deklariert eine Variable für ein Feld (array), dessen Elemente vom Typ type sind. • Alternative Schreibweise: type name []; • Das Kommando new legt ein Feld einer gegebenen Größe an und liefert einen Verweis darauf zurück: a a = new int[6]; a 63 • Der Wert einer Feld-Variable ist also ein Verweis. • int[] b = a; kopiert den Verweis der Variablen a in die Variable b: a int[] b = a; a b 64 • Die Elemente eines Felds sind von 0 an durchnumeriert. • Die Anzahl der Elemente des Felds name ist name.length. • Auf das i-te Element des Felds name greift man mittels name[i] zu. • Bei jedem Zugriff wird überprüft, ob der Index erlaubt ist, d.h. im Intervall {0, . . . , name.length-1} liegt. • Liegt der Index außerhalb des Intervalls, wird die ArrayIndexOutofBoundsException ausgelöst (↑Exceptions). 65 Mehrdimensionale Felder • Java unterstützt direkt nur ein-dimensionale Felder. • Ein zwei-dimensionales Feld ist ein Feld von Feldern a a http://chortle.ccsu.edu/java5/Notes/chap49C/ch49C_1.html 66 a a = new int[5][6]; a 67 2.1 Mehr Kontrollstrukturen Typische Form der Iteration über Felder: • Initialisierung des Laufindex; • while-Schleife mit Eintrittsbedingung für den Rumpf; • Modifizierung des Laufindex am Ende des Rumpfs. 68 Beispiel (Forts.): Bestimmung des Minimums int result = a[0]; int i = 1; // Initialisierung while (i < a.length) { if (a[i] < result) result = a[i]; i = i+1; // Modifizierung } write(result); 69 Mithilfe des for-Statements: int result = a[0]; for (int i = 1; i < a.length; ++i) if (a[i] < result) result = a[i]; write(result); 70 Allgemein: for ( init; cond; modify ) stmt ... entspricht: { init ; while ( cond ) { stmt modify ;} } ... wobei ++i äquivalent ist zu i = i+1 71 Warnung: • Die Zuweisung x = x+1 ist in Wahrheit ein Ausdruck. • Der Wert ist der Wert der rechten Seite. • Die Modifizierung der Variable x erfolgt als Seiteneffekt. • Der Semikolon “;” hinter einem Ausdruck wirft nur den Wert weg ... ==⇒ ... fatal für Fehler in Bedingungen ... boolean x = false; if (x = true) write("Sorry! This must be an error ..."); 72 • Die Operatoranwendungen ++x und x++ inkrementieren beide den Wert der Variablen x. • ++x tut das, bevor der Wert des Ausdrucks ermittelt wird (Pre-Increment). • x++ tut das, nachdem der Wert ermittelt wurde (Post-Increment). • a[x++] = 7; entspricht: a[x] = 7; x = x+1; • a[++x] = 7; entspricht: x = x+1; a[x] = 7; 73