1552 lines
		
	
	
		
			64 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			1552 lines
		
	
	
		
			64 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Programmierparadigmen
 | ||
| 
 | ||
| ## Was ist ein Paradigma?
 | ||
| - Paradigma – aus dem Altgriechischen Beispiel, Muster; Erzählung mit beispielhaftem Charakter (laut Duden)
 | ||
| - Programmierparadigmen beschreiben grundsätzliche Arten wie Computer-Programme formuliert werden können
 | ||
| - Programmiersprachen können einzelne oder viele Konzepte aufgreifen
 | ||
|   - Keine verbreitete Sprache greift alle behandelten Konzepte auf
 | ||
|   - Betrachtung unterschiedlicher Sprachen
 | ||
| - Ziel der Veranstaltung: Weiten der in Algorithmen und Programmierung eingeführten Sichten hin zu einem Werkzeugkoffer zur Lösung realer Probleme...
 | ||
| 
 | ||
| ## Warum unterschiedliche Paradigmen?
 | ||
| Komplexität von Software schlecht beherrschbar
 | ||
| 
 | ||
| ## Was bedeutet das?
 | ||
| - Programmierer schreiben, testen und dokumentieren zwischen 325 und 750 Codezeilen pro Monat
 | ||
|   - maximal 360.000 Zeilen in 40 Jahren!
 | ||
| - Komplexität muss verborgen werden, z.B. durch
 | ||
|   - Kapselung
 | ||
|   - Spezifische Spachkonstrukte, Domain Specific Languages
 | ||
|   - Ausdrucksstärkere Sprachen
 | ||
| - Entwicklung neuer Programmierparadigmen hilft Grenzen (ein wenig) zu verschieben
 | ||
| - Theoretische Rahmenbedingungen (Turing-Mächtigkeit, Satz von Rice) behalten Gültigkeit!
 | ||
| 
 | ||
| ## Welche Paradigmen existieren?
 | ||
| - Aus Vorlesung AuP:
 | ||
|   - Imperative Algorithmen
 | ||
|   - Applikative Algorithmen
 | ||
|   - Deduktive Algorithmen
 | ||
| - Aber Vielzahl weiterer Formen
 | ||
|   - teilweise ergänzend, unterschiedliche Kategorisierung möglich
 | ||
|   - Bsp: prozedural, deklarativ, objekt-orientiert, datenstromorientiert, parallele & verteilte Programmierung...
 | ||
| - Teilweise unterschiedliche Bezeichnungen
 | ||
|   - Applikativ bzw. Funktional
 | ||
|   - Deduktiv bzw. Logisch
 | ||
| - Aktueller Trend: Multiparadigmen-Sprachen
 | ||
|   - Umsetzung unterschiedlichster Paradigmen in einer Sprache
 | ||
|   - Beispiele: Scala, neuere C++-Standards, ...
 | ||
| 
 | ||
| # Objektorientierung und weiterführende Konzepte am Beispiel Java
 | ||
| - Bekannt:
 | ||
|   - Grundlegendes Verständnis von Java
 | ||
|   - Kapselung durch Klassen und Vererbung
 | ||
| - Ziele:
 | ||
|   - Verständnis der Probleme bei Vererbung und Typersetzbarkeit in objektorientierten Programmiersprachen
 | ||
|   - Kennenlernen der Grundideen generischer und abstrahierender Konzepte in Objekt-orientierter Programmierung (OOP)
 | ||
|   - Praktische Erfahrungen anhand von Java & C++
 | ||
| - Ausdrucksstärke erhöhen, Komplexität verbergen
 | ||
| 
 | ||
| ## Unit Testing
 | ||
| ### Motivation
 | ||
| - Große Software-Systeme entwickeln sich über lange Zeiträume
 | ||
| - Wie können Änderungen an komplexen Code-Basen beherrscht werden?
 | ||
| - Veränderung über Zeit + Komplexität der Software
 | ||
|   - Änderungen führen mglw. zu Auswirkungen, die für Einzelne nicht immer überschaubar sind
 | ||
|   - Software muss nach Änderung von Grund auf durchgetestet werden
 | ||
| - Verbreitetes Vorgehen: zusätzlichen Code schreiben, der eigentlichen Code automatisch “überprüft”
 | ||
|   - Nicht vollständig möglich (z.B. Halteproblem)
 | ||
|   - Eher Heuristik
 | ||
| - Test-Code wird bei Ereignissen oder periodisch ausgeführt
 | ||
|   - Vor Releases, nach Commit in Repository, während der Entwicklung ...
 | ||
| 
 | ||
| ### Eigenschaften von Unit-Tests
 | ||
| - Software schlecht als Ganzes testbar -> Zergliederung von Software in sinnvolle Einheiten
 | ||
| - Individuelle Tests dieser Einheiten
 | ||
| - Dabei: reproduzierbar & vollautomatisierbar
 | ||
|   - Ziel: Wann immer Änderungen in komplexen Programmen vorgenommen werden, möglichst vollständiger Test, da Programmierer nicht mehr alles überblicken
 | ||
| - Messung der Vollständigkeit der Tests schwierig
 | ||
| - Üblich: Messung von Überdeckung (Coverage) in Bezug auf Anzahl Funktionen, Code-Zeilen oder Verzweigungen
 | ||
| - Gute Praxis: Wenn ein Bug beim Testen oder Live-Betrieb auftritt -> Schreiben eines zusätzlichen Tests, um Wiederauftreten zu erkennen
 | ||
| 
 | ||
| ### Unit-Testing in Java
 | ||
| - De facto Standard: JUnit Framework
 | ||
| - „Best Practice” für einfachen Einsatz:
 | ||
|   - Java Code in ein oder mehrere Klassen im Ordner src speichern
 | ||
|   - Im Ordner tests jeweils eine Klasse anlegen, die Funktionen einer Implementierungsklasse prüft
 | ||
|   - Konvention: Testklasse einer Klasse Name heißt NameTest
 | ||
|   - Eigentliche Tests werden in Methoden implementiert, die als Tests annotiert sind
 | ||
|   - Typischer Ansatz: für bekannte Werte ausführen und Ergebnis mit Grundwahrheit (erwartetes Verhalten) vergleichen, bspw. mit assertEquals-Funktion
 | ||
| - Viele weitere Features, z.B. Deaktivieren von Tests, Timeouts, GUI Coverage, Mocks
 | ||
| 
 | ||
| ### Unit Testing – Richtiges Abstraktionsniveau
 | ||
| - Um die Tests auszuführen, müssen jeweils entsprechende Hauptprogramme generiert werden („Test Suites“)
 | ||
| - Hauptschwierigkeiten von Unit-Tests:
 | ||
|   - Richtiges Abstraktionsniveau
 | ||
|   - „Herauslösen“ von zu testendem Code aus Umgebung
 | ||
| - Zwei wesentliche Möglichkeiten:
 | ||
|   1. Individuelles Testen von Klassen:
 | ||
|     - Vernachlässigt Zusammenspiel zwischen Klassen
 | ||
|     - Oft sehr aufwändig, da andere Klassen für Unit-Tests nachgebildet werden müssen (Mocks)
 | ||
|     - Was bei zyklischen Abhängigkeiten?
 | ||
|   2. Gemeinsames Testen von Klassen:
 | ||
|     - Erfordert Eingreifen in gekapselte Funktionalitäten
 | ||
|     - Private & Protected Member-Variablen & Methoden!
 | ||
|     - Eigentlich nicht möglich?!
 | ||
| 
 | ||
| ## Reflections
 | ||
| - Normaler Ablauf: Programm schreiben, compilieren, ausführen
 | ||
|   - Aber was wenn ich ein Programm zur Laufzeit inspizieren oder verändern möchte?
 | ||
| - Unterschiedliche Gründe
 | ||
|   - Testen (um Fehler zu injizieren!)
 | ||
|   - Fehlersuche („Debugging“)
 | ||
|   - Nachladen von Plugins zur Modularisierung von Programmen
 | ||
|   - Serialisierung/Deserialisierung von Code
 | ||
|   - „Patchen“ zur Laufzeit
 | ||
|   - Erkunden der Ablaufumgebung (z.B. OS-/Shared-Library Version)
 | ||
| - Benötigt die Fähigkeit, im Programm Codestruktur zu analysieren und ggf. zu verändern:
 | ||
|   - Typisch: Abruf Klassenhierarchie, Auflisten von Methoden und Parametern, Austausch von Klassen und Methoden
 | ||
|   - Teil von Java, Python, ...
 | ||
| 
 | ||
| API verstreut über verschiedene Packages, z.B. java.lang.Class, java.lang.instrument, java.lang.reflect
 | ||
| 
 | ||
| ```java
 | ||
| Class cls = "test".getClass();
 | ||
| System.out.println("Die Klasse heisst " + cls.getName());
 | ||
| // Die Klasse heisst java.lang.String
 | ||
| ```
 | ||
| ```java
 | ||
| // import java.lang.reflect.Method;
 | ||
| Method[] methods = cls.getMethods();
 | ||
| for (Method m : methods)
 | ||
| System.out.println(m.getName());
 | ||
| ```
 | ||
| ### Annotationen
 | ||
| - Annotationen erlauben Anmerkungen an Klassen & Methoden
 | ||
| - Beginnen mit @
 | ||
| - Einige wenige vordefinierte z.B. @Override
 | ||
| - Aber auch eigene; u.a. durch Reflections abrufbar
 | ||
| - Häufig genutzt, wenn zusätzlicher Code geladen wird (Java EE)
 | ||
| - Oder um Unit-Tests zu markieren...
 | ||
| ```java
 | ||
| class MultiTest {
 | ||
|   @org.junit.jupiter.api.Test
 | ||
|   void mul() {
 | ||
|     ...
 | ||
| ```
 | ||
| 
 | ||
| ### Reflektionen über Reflections
 | ||
| - Reflections sind ein sehr mächtiges Werkzeug, aber Einsatz sollte wohldosiert erfolgen
 | ||
| - Nachteile:
 | ||
|   - Geringe Geschwindigkeit weil Zugriff über Programmcode erfolgt
 | ||
|   - Kapselung kann umgangen werden
 | ||
|     - private, protected und final können entfernt werden
 | ||
|     - Aufruf/Veränderung interner Methoden & Auslesen/Veränderung interner Variablen
 | ||
|     - Synchronisation zwischen externen und internen Komponenten bei Weiterentwicklung?
 | ||
|   - Debugging veränderter Programme?
 | ||
|   - Sicherheit?!
 | ||
| - Verwandte Techniken:
 | ||
|   - Monkey Patching (JavaScript-Umfeld)
 | ||
|   - Method Swizzling (Swift/Objective-C-Umfeld)
 | ||
| 
 | ||
| 
 | ||
| ## Assertions
 | ||
| - Kann man interne Zustände testen, ohne invasive Techniken wie Reflections?
 | ||
| - Einfache Möglichkeit: An sinnvollen Stellen im Programmcode testen, ob Annahmen/Zusicherungen (Assertions) stimmen...
 | ||
| - Tests, die nie falsch sein sollten
 | ||
|   - Erlauben gezielten Programmabbruch, um Folgefehler zu vermeiden
 | ||
|   - Erlauben gezieltes Beheben von Fehlern
 | ||
|   - Gemeinsames Entwickeln von Annahmen und Code
 | ||
| ```java
 | ||
| class Stack {
 | ||
|   public void push(Object o) {
 | ||
|     ...
 | ||
|     if(empty() == true) // es sollte ein Objekt da sein
 | ||
|       System.exit(-1);
 | ||
|   }
 | ||
|   ...
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| Aber: Ausführungsgeschwindigkeit niedriger
 | ||
| - Zeitverlust stark abhängig von Programm/Programmiersprache
 | ||
| - Verbreitetes Vorgehen:
 | ||
|   - Aktivieren der Tests in UnitTests und Debug-Versionen
 | ||
|   - Deaktivieren in Releases
 | ||
|   - Benötigt spezielle „if“-Bedingung: assert
 | ||
| - Aktivierung der Tests über Start mit java -ea
 | ||
| ```java
 | ||
| class Stack {
 | ||
|   public void push(Object o) {
 | ||
|     ...
 | ||
|     assert empty() == false
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### Welche braucht man?
 | ||
| - Woran erkennt man beim Programmieren bzw. (erneutem) Lesen von Code, dass man eine Assertion hinzufügen sollte?
 | ||
| - Eine einfache Heuristik – Die „Eigentlich“-Regel:
 | ||
|   - Wenn einem beim Lesen von Programmcode ein Gedanke der Art „Eigentlich müsste an dieser Stelle XY gelten“ durch den Kopf geht,
 | ||
|   - dann sofort eine entsprechende Assertion formulieren!
 | ||
| 
 | ||
| ### Spezielle Assertions: Pre- & Postconditions
 | ||
| - An welchen Stellen ist es sinnvoll, Annahmen zu prüfen?
 | ||
| - Einfache Antwort: an so vielen Stellen wie möglich
 | ||
| - Komplexere Antwort: Design by contract, ursprünglich Eiffel
 | ||
| - Methoden/Programmabschnitte testen Bedingung vor und nach Ausführung
 | ||
| - Einige Sprachen bieten spezialisierte Befehle: requires und ensures
 | ||
| -> Ziel mancher Sprachen: Formale Aussagen über Korrektheit
 | ||
| 
 | ||
| ```java
 | ||
| class Stack {
 | ||
|   public void push(Object o) {
 | ||
|     assert o != null // precondition
 | ||
|     ...
 | ||
|     assert empty() == false // postcondition
 | ||
|   }
 | ||
|   ...
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### Klasseninvarianten
 | ||
| - Bei OO-Programmierung sind Vor- und Nachbedingungen nur eingeschränkt sinnvoll
 | ||
| - Bedingungen oft besser auf Objekt-Ebene -> interner Zustand
 | ||
| - Invarianten spezifizieren Prüfbedingungen
 | ||
| - In Java nicht nativ unterstützt:
 | ||
|   - Erweiterungen, wie Java Modeling Language
 | ||
|   - Simulation:
 | ||
| ```java
 | ||
| class Stack {
 | ||
|   void isValid() {
 | ||
|     for(Object o : _objs) // Achtung: O(n) Aufwand!
 | ||
|       assert o != null
 | ||
|   }
 | ||
|   public void push(Object o) {
 | ||
|     isValid() // always call invariant
 | ||
|     ...
 | ||
|     isValid() // always call invariant
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ## Exeptions
 | ||
| Signifikantes Element vieler Sprachen: Wie wird mit Fehlern umgegangen?
 | ||
| Fehler können unterschiedliche Gründe haben
 | ||
| Besser für Code-Komplexität: Fehlerprüfungen an zentralerer Stelle
 | ||
| - Abbrechen und Programm-Stack „abbauen“ bis (zentrale) Fehlerbehandlung greift
 | ||
| - Dabei Fehler sinnvoll gruppieren
 | ||
| - Java (und viele mehr): try/catch/throw-Konstrukt
 | ||
| ```java
 | ||
| private void readFile(String f) {
 | ||
|   try {
 | ||
|       Path file = Paths.get("/tmp/file");
 | ||
|       if(Files.exists(file) == false)
 | ||
|         throw new IOException("No such dir");
 | ||
|       array = Files.readAllBytes(file);
 | ||
|   } catch(IOException e) {
 | ||
|       // do something about it
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| throw übergibt ein Objekt vom Typ Throwable an Handler, dabei zwei Unterarten:
 | ||
| - Error: Sollte nicht abgefangen werden z.B. Fehler im Byte-Code, Fehlgeschlagene Assertions
 | ||
| - Exceptions:
 | ||
|   - Checked Exception: Programm muss Exception fangen oder in Methode vermerken
 | ||
|   - Runtime Exceptions: Müssen nicht (aber sollten) explizit behandelt werden, bspw. ArithmeticException oder IndexOutOfBoundsException
 | ||
| 
 | ||
| ### Checked Exceptions
 | ||
| Deklaration einer überprüften Exception:
 | ||
| ```java
 | ||
|   void dangerousFunction() throws IOException {
 | ||
|     ...
 | ||
|     if(onFire)
 | ||
|       throw IOException("Already burns");
 | ||
|     ...
 | ||
| }
 | ||
| ```
 | ||
| Die Deklaration mit "throws IOException" lässt beim build mögliche Fehler durch IOExceptions dieser Funktion zu, diese müssen durch die aufrufende Methode abgefangen werden.
 | ||
| Aufrufe ohne try-catch-Block schlagen fehl!
 | ||
| Sollte man checked oder unchecked Exceptions verwenden?
 | ||
| - Checked sind potenziell sicherer
 | ||
| - Unchecked machen Methoden lesbarer
 | ||
| - Faustregel unchecked, wenn immer auftreten können (zu wenig Speicher, Division durch 0)
 | ||
| 
 | ||
| Abfangen mehrerer unterschiedlicher Exceptions
 | ||
| ```java
 | ||
| try {
 | ||
|   dangerousFunction();
 | ||
| } catch(IOException i) {
 | ||
|   // handle that nasty error
 | ||
| } catch(Exception e) {
 | ||
|   // handle all other exceptions
 | ||
| }
 | ||
| ```
 | ||
| Aufräumen nach einem try-catch-Block: Anweisungen im finally-Block werden immer ausgeführt, d.h. auch bei
 | ||
| return in try- oder catch-Block (oder fehlerloser Ausführung)
 | ||
| ```java
 | ||
| try {
 | ||
|   dangerousFunction();
 | ||
| } catch(Exception e) {
 | ||
|   // handle exceptions
 | ||
|   return;
 | ||
| } finally {
 | ||
|   // release locks etc..
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ## Generizät von Datentypen
 | ||
| (Typ-)Generizität: 
 | ||
| - Anwendung einer Implementierung auf verschiedene Datentypen
 | ||
| - Parametrisierung eines Software-Elementes (Methode, Datenstruktur, Klasse, ...) durch einen oder mehrere Typen
 | ||
| Beispiel:
 | ||
| ```java
 | ||
| int min(int a, int b) {
 | ||
|   return a < b ? a : b;
 | ||
| }
 | ||
| float min(float a, float b) {
 | ||
|   return a < b ? a : b;
 | ||
| }
 | ||
| String min(String a, String b) { // lexikographisch
 | ||
|   return a.compareTo(b) < 0 ? a : b;
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### Grenzen von Typsubstitution
 | ||
| Problem: Für jeden Typ? Wie kann sort implementiert werden?
 | ||
| Möglicher Ausweg: Klassenhierarchie mit zentraler Basisklasse
 | ||
| ```java
 | ||
| void sort(Object[] feld) { ... }            //z.B. java.lang.Object
 | ||
| void sort(java.util.Vector feld) { ... }    //alternativ (nutzt intern Object)
 | ||
| ```
 | ||
| Möglicher Ausweg 2: Nutzung primitiver Datentypen nicht direkt möglich
 | ||
| ```java
 | ||
| Object[] feld = new Object[10];         //Object[] ≠ int[]
 | ||
| feld[0] = new Integer(42);
 | ||
| int i = ((Integer) feld[0]).intValue(); //erfordert Wrapper-Klassen wie java.lang.Integer
 | ||
| ```
 | ||
| 
 | ||
| Weiteres Problem: Typsicherheit<br/>
 | ||
| Typ-Substituierbarkeit: Kann ein Objekt einer Oberklasse (eines Typs) durch ein Objekt seiner Unterklasse (Subtyps) ersetzt werden?
 | ||
| Beispiel (isSubtyp): short $\rightarrow$ int $\rightarrow$ long 
 | ||
| Viele Programmiersprachen ersetzen Typen automatisch, d.h. diese wird auch für shorts und ints verwendet
 | ||
| ```java
 | ||
| long min(long a, long b) {
 | ||
|   return a < b ? a : b;
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| Kreis-Ellipse-Problem: Modellierung von Vererbungsbeziehungen
 | ||
| - „Ist ein Kreis eine Ellipse?“ „Oder eine Ellipse ein Kreis?“
 | ||
| - Annahme: Kreis := Ellipse mit Höhe = Breite
 | ||
| ```java
 | ||
| Circle c = new Circle();  
 | ||
| c.skaliereX(2.0);       //skalieren aus Klasse Circle
 | ||
| c.skaliereY(.5);        //is das noch ein Kreis?
 | ||
| ```
 | ||
| evtl. Reihenfolge in der Klassenhierarchie tauschen (nutzung von Radius)? Was bedeutet das für Ellipse?
 | ||
| Verwandte Probleme: Rechteck-Quadrat, Set-Bag
 | ||
| 
 | ||
| ### Ko- und Kontravarianz
 | ||
| Geg.: Ordnung von Datentypen von spezifisch $\rightarrow$ allgemeiner
 | ||
| - Gleichzeitige Betrachtung einer Klassenhierarchie, die Datentypen verwendet
 | ||
| - Kovarianz: Erhaltung der Ordnung der Typen
 | ||
| - Kontravarianz: Umkehrung der Ordnung
 | ||
| - Invarianz: keines von beiden
 | ||
| - Anwendung für
 | ||
|   - Parameter
 | ||
|   - Rückgabetypen
 | ||
|   - Ausnahmetypen
 | ||
|   - Generische Datenstrukturen
 | ||
| 
 | ||
| Beispiel: Basierend auf Meyer‘s SKIER-Szenario
 | ||
| ```java
 | ||
| class Student {
 | ||
|   String name;
 | ||
|   Student mate;
 | ||
|   void setRoomMate(Student s) { ... }
 | ||
| }
 | ||
| ```
 | ||
| Wie überschreibt man in einer Unterklasse Girl oder Boy die Methode „setRoomMate“ in elternfreundlicher Weise? Von Eltern sicher gewollt - Kovarianz:
 | ||
| ```java
 | ||
| class Boy extends Student {
 | ||
|   void setRoomMate(Boy b) { ... }
 | ||
| }
 | ||
| class Girl extends Student {
 | ||
|   void setRoomMate(Girl g) { ... }
 | ||
| }
 | ||
| ```
 | ||
| Was passiert mit folgendem Code?
 | ||
| ```java
 | ||
| Boy kevin = new Boy("Kevin");
 | ||
| Girl vivian = new Girl("Vivian");
 | ||
| kevin.setRoomMate(vivian);
 | ||
| ```
 | ||
| - Verwendet setRoomMate der Basisklasse
 | ||
| - setRoomMate Methoden der abgeleiteten Klassen überladen nur Spezialfälle $\rightarrow$ gültig
 | ||
| 
 | ||
| - In C++ und Java keine Einschränkung der Typen zur Compile-Zeit
 | ||
| - Kovarianz so nur in wenigen Sprachen implementiert (z.B. Eiffel über redefine); Überprüfung auch nicht immer statisch!
 | ||
| - Auch bekannt als catcall-Problem (cat = changed availablility type)
 | ||
| Ausweg: Laufzeitüberprüfung
 | ||
| ```java
 | ||
| class Girl extends Student {
 | ||
|   ...
 | ||
|   public void setRoomMate(Student s) { //student wird aufgerufen! nicht boy oder girl, dadurch können die methoden der klasse verwendet werden
 | ||
|     if (s instanceof Girl)
 | ||
|       super.setRoomMate(s);
 | ||
|     else
 | ||
|       throw new ParentException("Oh Oh!");
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| Nachteil: Nur zur Laufzeit überprüfung
 | ||
| 
 | ||
| #### Ko- und Kontravarianz für Rückgabewerte
 | ||
| Kovarianz (gängig):
 | ||
| ```java
 | ||
| public class KlasseA {
 | ||
|   KlasseA ich() { return this; }
 | ||
| }
 | ||
| public class KlasseB extends KlasseA {
 | ||
|   KlasseB ich() { return this; }
 | ||
| }
 | ||
| ```
 | ||
| Kontravarianz macht wenig Sinn und kommt (gängig) nicht vor
 | ||
| 
 | ||
| In objektorientierten Programmiersprachen im Allgemeinen
 | ||
| - Kontravarianz: für Eingabeparameter
 | ||
| - Kovarianz: für Rückgabewerte und Ausnahmen
 | ||
| - Invarianz: für Ein- und Ausgabeparameter
 | ||
| 
 | ||
| ### Liskovsches Substitutionsprinzip (LSP)
 | ||
| Barbara Liskov, 1988 bzw. 1993, definiert stärkere Form der Subtyp-Relation, berücksichtigt Verhalten:
 | ||
| > Wenn es für jedes Objekt $o_1$ eines Typs S ein Objekt $o_2$ des Typs T gibt, so dass für alle Programme P, die mit Operationen von T definiert sind, das Verhalten von P unverändert bleibt, wenn $o_2$ durch $o_1$ ersetzt wird, dann ist S ein Subtyp von T.'
 | ||
| Subtyp darf Funktionalität eines Basistyps nur erweitern, aber nicht einschränken. <br>
 | ||
| Beispiel: Kreis-Ellipse $\rightarrow$ Kreis als Unterklasse schränkt Funktionalität ein und verletzt damit LSP
 | ||
| 
 | ||
| ### Generics in Java (Typsicherheit)
 | ||
| Motivation: Parametrisierung von Kollektionen mit Typen
 | ||
| ```java
 | ||
| LinkedList<String> liste = new LinkedList<String>();
 | ||
| liste.add("Generics");
 | ||
| String s = liste.get(0);
 | ||
| ```
 | ||
| auch für Iteratoren nutzbar
 | ||
| ```java
 | ||
| Iterator<String> iter = liste.iterator();
 | ||
|   while(iter.hasNext()) {
 | ||
|     String s = iter.next();
 | ||
|     ...
 | ||
| }
 | ||
| ```
 | ||
| oder mit erweiterter for-Schleife
 | ||
| ```java
 | ||
| for(String s : liste) {
 | ||
|   System.out.println(s);
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| Deklaration: Definition mit Typparameter
 | ||
| ```java
 | ||
| class GMethod {
 | ||
|   static <T> T thisOrThat(T first, T second) {
 | ||
|     return Math.random() > 0.5 ? first : second;
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| - T = Typparameter (oder auch Typvariable) wird wie Typ verwendet, stellt jedoch nur einen Platzhalter dar
 | ||
| - wird bei Instanziierung (Parametrisierung) durch konkreten Typ „ersetzt“
 | ||
| - nur Referenzdatentypen (Klassennamen), keine primitiven Datentypen
 | ||
| Anwendung:
 | ||
| - explizite Angabe des Typparameters
 | ||
|   ```java
 | ||
|   String s = GMethod.<String>thisOrThat("Java", "C++");
 | ||
|   Integer>thisOrThat(new Integer(42), new Integer(23));
 | ||
|   ```
 | ||
| - automatische Typinferenz durch Compiler
 | ||
|   ```java
 | ||
|   String s = GMethod.thisOrThat("Java", "C++");
 | ||
|   Integer i = GMethod.thisOrThat(new Integer(42), new Integer(23));
 | ||
|   ```
 | ||
| 
 | ||
| #### Eingrenzung von Typparametern
 | ||
| Festlegung einer Mindestfunktionalität der einzusetzenden Klasse, z.B. durch Angabe einer Basisklasse
 | ||
| - Instanziierung von T muss von Comparable abgeleitet werden (hier ein Interface, dass wiederum generisch ist, daher Comparable<T>)
 | ||
| - Verletzung wird vom Compiler erkannt
 | ||
| ```java
 | ||
| static<T extends Comparable<T>> T min(T first, T second) {
 | ||
|   return first.compareTo(second) < 0 ? first : second;
 | ||
| }
 | ||
| ```
 | ||
| Angabe des Typparameters bei der Klassendefinition:
 | ||
| ```java
 | ||
| class GArray<T> {
 | ||
|   T[] data;
 | ||
|   int size = 0;
 | ||
|   public GArray(int capacity) { ... }
 | ||
|   public T get(int idx) { return data[idx]; }
 | ||
|   public void add(T obj) { ... }
 | ||
| }
 | ||
| ```
 | ||
| Achtung: new T[n] ist unzulässig! Grund liegt in der Implementierung von Generics:
 | ||
| Es gibt zwei Möglichkeiten der internen Umsetzung generischen Codes:
 | ||
| - Code-Spezialisierung: jede neue Instanziierung generiert neuen Code
 | ||
|   - Array<String> → ArrayString, Array<Integer> → ArrayInteger
 | ||
|   - Problem: Codegröße
 | ||
| - Code-Sharing: gemeinsamer Code für alle Instanziierungen
 | ||
|   - Array<String> → Array<Object>, Array<Integer> → Array<Object>
 | ||
|   - Probleme: keine Unterstützung primitiver Datentypen & keine Anpassung von Algorithmen an Typ
 | ||
| Java: Code-Sharing durch Typlöschung (Type Erasure)
 | ||
| Typen beim Übersetzen geprüft, aber keinen Einfluss auf Code
 | ||
| sichert auch Kompatibilität zu nicht generischem Code (Java-Version < 1.5) Bsp.: ArrayList vs. ArrayList<E>
 | ||
| 
 | ||
| Beispiel: Reflektion (Metaklassen) zur Erzeugung nutzen; danach Konstruktionsaufruf
 | ||
| ```java
 | ||
| public GArray(Class<T> clazz, int capacity) {
 | ||
|   data = (T[]) Array.newInstance(clazz, capacity);
 | ||
| }
 | ||
| 
 | ||
| GArray<String> array = new GArray<String>(String.class, 10);
 | ||
| ```
 | ||
| 
 | ||
| #### Kovarianz generischer Typen
 | ||
| einfache Felder in Java sind kovariant
 | ||
| ```java
 | ||
| Object[] feld = new Object[10];
 | ||
| feld[0] = "String";
 | ||
| feld[1] = new Integer(42);
 | ||
| ```
 | ||
| Instanziierungen mit unterschiedliche Typen sind jedoch inkompatibel
 | ||
| ```java
 | ||
| GArray<String> anArray = new GArray<String>();
 | ||
| GArray<Object> anotherArray = (GArray<Object>) anArray;
 | ||
| ```
 | ||
| 
 | ||
| #### Wildcards
 | ||
| Wildcard „?“ als Typparameter und abstrakter Supertyp für alle Instanziierungen
 | ||
| ```java
 | ||
| GArray<?> aRef;
 | ||
| aRef = new GArray<String>();
 | ||
| aRef = new GArray<Integer>();
 | ||
| ```
 | ||
| aber nicht:
 | ||
| ```java
 | ||
| GArray<?> aRef = new GArray<?>();
 | ||
| ```
 | ||
| hilfreich insbesondere für generische Methoden
 | ||
| ```java
 | ||
| // dieser Methode ist der genaue Typ egal
 | ||
| static void pO(GArray<?> ia) {
 | ||
|   for(Object o : ia) {
 | ||
|     System.out.print(o);
 | ||
|   }
 | ||
| }
 | ||
| // floats wollen wir mit Genauigkeit 2 haben
 | ||
| static void pF(GArray<Float> ia) {
 | ||
|   for(Float f : ia) {
 | ||
|     System.out.printf("%5.2f\n", f);
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| Beschränkte Wildcards 
 | ||
| - nach „unten“ in der Klassenhierarchie $\rightarrow$ Kovarianz
 | ||
|   ```java 
 | ||
|   ? extends Supertyp 
 | ||
|   ```
 | ||
|   Anwendungsbeispiel: Sortieren eines generischen Feldes erfordert Unterstützung der Comparable-Schnittstelle
 | ||
|   ```java
 | ||
|   void sortArray(GArray<? extends Comparable> array) {
 | ||
|     ...
 | ||
|   }
 | ||
|   ```
 | ||
| - nach „oben“ in der Klassenhierarchie $\rightarrow$ Kontravarianz
 | ||
|   ```java
 | ||
|   ? super Subtyp
 | ||
|   ```
 | ||
|   Anwendungsbeispiel: Feld mit ganzen Zahlen und Objekten
 | ||
|   ```java
 | ||
|   GArray<? super Integer> array;
 | ||
|   // Zuweisungskompatibel zu ...
 | ||
|   array = new GArray<Number>();
 | ||
|   array = new GArray<Object>();
 | ||
|   array = new GArray<Integer>();
 | ||
|   // aber nur erlaubt:
 | ||
|   Object obj = array.get(0);
 | ||
|   ```
 | ||
| 
 | ||
| PECS = Producer extends, Consumer super $\rightarrow$ Producer liest nur sachen, Consumer legt daten/Objekte/... ab
 | ||
| 
 | ||
| # Objectorientierung am Beispiel C++
 | ||
| - Ziel von C++: volle Kontrolle über Speicher & Ausführungsreihenfolgen sowie skalierbarere Projekt-Größe
 | ||
| - Kompiliert zu nativem Maschinencode und erlaubt genauere Aussagen über Speicher-, Cache- und Echtzeitverhalten
 | ||
| - Viele Hochsprachenelemente (Wie Java objektorientiert; sogar ähnliche Syntax an viele Stellen (weil Java ursprünglich an C++ angelehnt))
 | ||
| - Jedoch kompromissloser Fokus Ausführungsgeschwindigkeit, d.h.
 | ||
|   - Keine automatische Speicherverwaltung
 | ||
|   - Keine Initialisierung von Variablen (im Allgemeinen)
 | ||
|   - Kein Speicherschutz!
 | ||
|   - Dinge, die Zeit kosten, müssen im Allgemeinen erst durch Schlüsselworte aktiviert werden
 | ||
| - C++ ist zu sehr großen Teilen eine Obermenge von C
 | ||
|   - Fügt Objektorientierung hinzu
 | ||
|   - Versucht fehleranfällige Konstrukte zu kapseln
 | ||
|   - Führt (viele) weitere Sprachkonstrukte ein, die Code kompakter werden lassen
 | ||
| 
 | ||
| ## Vergleich mit Java
 | ||
| ```java
 | ||
| [Hello.java] 
 | ||
| package hello; // say that we are part of a package
 | ||
| public class Hello { // declare a class called Hello:
 | ||
| // declare the function main that takes an array of Strings:
 | ||
|   public static void main(String args[]) {
 | ||
|   // call the static method, println on class System.out with parameter "Hi Welt!":
 | ||
|     System.out.println("Hi Welt!");
 | ||
|   }
 | ||
| } // end of class Hello
 | ||
| ```
 | ||
| ```cpp
 | ||
| [Hello.cpp]
 | ||
| // include declarations for I/O library where cout object is specified in namespace std::
 | ||
| #include <iostream>
 | ||
| // declare the function main that takes an int and array of strings and returns an int as the exit code
 | ||
| int main(int argc, char* argv[]) {
 | ||
| // stream string to cout object flush line with endl
 | ||
|   std::cout << "Hello world!"
 | ||
|   << std::endl;
 | ||
|   return 0;
 | ||
| } // end of main()
 | ||
| ```
 | ||
| - Unterschiede im Aufbau:
 | ||
|   - C++ hat globale Funktionen, also außerhalb von Klassen, wie main
 | ||
|   - #include gibt Dateien mit Klassen- und Funktionsdefinitionen an, die der Compiler einlesen soll
 | ||
|   - Java-Programme werden in packages gegliedert, in C++ gibt es mit modules ein ähnliches Konzept, welches aber (noch) nicht verbreitet ist
 | ||
|   - C++-Programme können (ohne Bezug zu Dateien) in namespaces untergliedert werden, hier std
 | ||
| - Programmargumente:
 | ||
|   - In Java bekommt main ein String-Array übergeben, die Länge kann über .length abgefragt werden
 | ||
|   - C/C++-Programme erhalten ein Array von char* (Details zu Pointern folgen)
 | ||
|   - In C/C++ sind Arrays keine Pseudoobjekte, sondern Speicherbereiche in denen die Daten konsekutiv abgelegt sind $\rightarrow$ argc wird benötigt die Anzahl an Elementen zu kodieren
 | ||
| - Rückgabewerte:
 | ||
|   - In Java keine Rückgabe in der main-Methode
 | ||
|   - In C++ Rückgabe eines exit code
 | ||
|     - 0 gibt an: Programmausführung erfolgreich
 | ||
|     - Andere Werte geben einen Programm-spezifischen Fehlercode zurück
 | ||
| - Primitive Datentypen:
 | ||
|   - Wie in Java einfache Datentypen, die „Zahlen“ enthalten
 | ||
|   - char, short, int, long sind auf 64-bit Maschinen 8 bit, 16 bit, 32 bit und 64 bit breit (char braucht in Java 16 Bit!)
 | ||
|   - long ist auf 32 bit Maschinen 32 Bit breit, long long [sic!] ist immer 64 Bit
 | ||
|   - bool speichert Boolsche Werte (Breite hängt vom Compiler ab!)
 | ||
|   - Ein unsigned vor Ganzahltypen gibt an, dass keine negativen Zahlen in der Variable gespeichert werden (Beispiel: unsigned int) $\rightarrow$ Kann größere Zahlen speichern & zu viel Unsinn führen (beim Vergleich mit vorzeichenbehafteten Zahlen)
 | ||
| 
 | ||
| ## C++ Klassen
 | ||
| Header Foo.hpp deklariert Struktur und Schnittstelle
 | ||
| ```cpp
 | ||
| public:     // Block ohne Zugriffsbeschränkung
 | ||
|   Foo();    // Konstruktor
 | ||
|   ~Foo();   // Destruktor
 | ||
| protected:  // Block von Dingen, auf die auch abgeleitete Klassen zugreifen dürfen
 | ||
|   int num;  // Member-Variable
 | ||
| };          // WICHTIGES Semikolon!
 | ||
| ```
 | ||
| 
 | ||
| Implementierung in getrennter Datei Foo.cpp
 | ||
| ```cpp
 | ||
| #include "Foo.hpp"  // Klassen Deklaration einbinden
 | ||
| #include <iostream> // Einbinden von Funktionen der stdlib
 | ||
| Foo::Foo() :        // Implementierung des Konstuktors von Foo
 | ||
|   num(5) {          // Statische Initialisierung von num, Code in Klammern {} kann auch initialisieren
 | ||
|   std::cout << "c" << std::endl;
 | ||
| }
 | ||
| Foo::~Foo() {
 | ||
|   std::cout << "d" << std::endl;
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| - Reine Implementierung auch im Header möglich, aber Trennung von Implementierung und Deklaration erlaubt schnelleres Kompilieren
 | ||
| - Trennung nicht immer möglich (später mehr Details), aber im Allgemeinen zu bevorzugen
 | ||
| - Der scope-Operator :: wird zum Zugriff auf namespaces und zur Beschreibung der Klassenzugehörigkeit von Methoden verwendet
 | ||
| - Initialisierung von Variablen vor Funktionsrumpf etwas „merkwürdig“ zu lesen, aber erlaubt schnelle Implementierungen...
 | ||
|   - Syntax: nach Konstruktor : dann jeweils Variable(Wert)
 | ||
|   - Variablen durch , getrennt
 | ||
|   - Wichtig: Reihenfolge der Variablen wie in Deklaration der Klasse!
 | ||
| - Schlüsselworte private, protected und public vergleichbar zu Java, werden aber vor ganze Blöcke geschrieben
 | ||
|   - Kapselung nur auf Ebene von Klassen ➞ Klassen sind immer public
 | ||
|   - protected erlaubt nur der Klasse selber und Unterklassen den Zugriff
 | ||
| - Zugriffe außerhalb der Klassenstruktur können durch friend- Deklaration erlaubt werden (teilweise verrufen!)
 | ||
| - Auch *final* ähnlich zu Java $\righarrow$ Verhindert weiteres Ableiten von Klassen
 | ||
| - Schlüsselwort const markiert Methoden, die Objekte nicht verändern $\rightarrow$ Erlauben die Übergabe von Nur-Lesen-Referenzen
 | ||
| - Größere Unterschiede zu Java:
 | ||
|   - Klassen können Destruktoren besitzen
 | ||
|     - Werden aufgerufen wenn Objekt zerstört wird
 | ||
|     - Kann bspw. dafür verwendet werden, um von dem Objekt allozierte Speicherbereiche freizugeben (Achtung: anschließend darf auf diese nicht mehr zugegriffen werden – problematisch wenn anderen Objekte diese Speicherbereiche bekannt gegeben wurden!)
 | ||
|     - Destruktor kann Zerstören eines Objekts aber nicht verhindern
 | ||
|     - Methodensignatur ~Klassenname() – kein Rückgabetyp!
 | ||
|     - Warum gibt es das nicht in Java?
 | ||
|   - Neben dem Standardkonstruktor oder einem expliziten Konstruktor existiert ein Copy-Constructor
 | ||
|     - Methodensignatur Klassenname(const Klassenname& c)
 | ||
|     - Wird aufgerufen wenn Objekt kopiert werden soll
 | ||
|     - Vergleichbar zu Object.clone() in Java
 | ||
| - Überladen von Methoden vergleichbar zu Java
 | ||
|   - Parametertypen (oder const-Markierung) müssen sich unterscheiden!
 | ||
|   - Nur Veränderung des Rückgabewertes nicht ausreichend
 | ||
|     ```cpp
 | ||
|     class Foo {
 | ||
|       public:
 | ||
|         void doMagic(int i);
 | ||
|         void doMagic(std::string s);
 | ||
|     };
 | ||
|     ```
 | ||
| 
 | ||
| ## C++ Präprozessor
 | ||
| C/C++-Code kann vor dem Übersetzen durch einen Präprozessor verändert werden
 | ||
| - Alle Präprozessor-Makros beginnen mit #
 | ||
| - (Haupt-)gründe:
 | ||
|   - Importieren anderer Dateien
 | ||
|   - An- und Ausschalten von Features je nach Compile-Optionen
 | ||
|   - Kapselung von Plattform-spezifischem Code
 | ||
|   - Vermeiden von Redundanzen
 | ||
| - Makros sollten vermieden werden
 | ||
|   - Schwierig zu lesen
 | ||
|   - Keine Namespaces
 | ||
|   - Keine Typsicherheit
 | ||
| - Manchmal jedoch einzige Möglichkeit
 | ||
| 
 | ||
| Beispiele: 
 | ||
| ```cpp
 | ||
| #include "X.hpp" // Datei X.hpp aus Projekt-Ordner
 | ||
| #include <cstdio> // Datei cstdio aus System-Includes
 | ||
| 
 | ||
| #ifdef DEBUG // falls Konstante DEBUG definiert ist
 | ||
| std::cout << "Wichtige Debugausgabe" << std::endl;
 | ||
| #endif
 | ||
| 
 | ||
| #define DEBUG // Konstante setzen
 | ||
| #define VERSION 3.1415 // Konstante auf einen Wert setzen
 | ||
| #define DPRINT(X) std::cout << X << std::endl; // Macro-Fkt.
 | ||
| #undef DEBUG // Konstante löschen, good practice!
 | ||
| 
 | ||
| #ifndef __linux__ // falls nicht für Linux übersetzt
 | ||
| playMinesweeper();
 | ||
| #endif
 | ||
| ```
 | ||
| 
 | ||
| ### Einschub: Include Guards
 | ||
| Eine (oft hässliche) Eigenschaft des #include-Befehls: kein Überprüfen ob eine Datei vorher bereits eingebunden wurde. Problematisches Beispiel:
 | ||
| ```cpp
 | ||
| #include "Bar.hpp" //in "Bar.hpp" ist "Foo.hpp" bereits inkludiert worden
 | ||
| #include "Foo.hpp" //Fehler weil kallse Foo bereits deklariert wurde
 | ||
| ```
 | ||
| Common Practice: Include-Guards um alle Header-Dateien
 | ||
| ```cpp
 | ||
| #ifndef FOO_HPP
 | ||
| #define FOO_HPP
 | ||
| ...
 | ||
| #endif
 | ||
| ```
 | ||
| 
 | ||
| ## Speichermanagement
 | ||
| - Programmspeicher enthält Code und Daten, vom Betriebssystem i.A. auf virtuelle Adressbereiche abgebildet
 | ||
| - Unterschiedliche Varianten von Datenspeicher:
 | ||
|   - Stack hält alle Variablen einer Methode, aller aufrufenden Methoden, Parameter, Rückgabewerte und einige Management-Daten
 | ||
|   - Heap hält Variablen und Objekte, die nicht direkt über Methodenaufrufe übergeben werden
 | ||
|   - Speicher für globale und statische Objekte und Variablen
 | ||
| - Java legt primitive Datentypen im Stack ab und Objekte im Heap
 | ||
| - C++ kann sowohl primitive Datentypen als auch Objekte in Stack und Heap abbilden
 | ||
| - Für den Stack bieten Java und C++ automatisches Speicher-Mgmt.
 | ||
| - Für den Heap bietet nur Java automatisches Speicher-Mgmt.
 | ||
| 
 | ||
| ### Eigenschaften des Stack-Speichers:
 | ||
|   - Variablen/Objekte haben klare Lebensdauer $\rightarrow$ Werden immer gelöscht wenn Funktion verlassen wird  $\rightarrow$ Man kann Speicher nicht „aufheben“
 | ||
|   - In der Regel sehr schnell, weil im Prozessor-Cache
 | ||
|   - In der Größe begrenzt, z.B. 8MB bei aktuellen Linux-Systemen
 | ||
|   - Für flexiblere Speicherung brauchen wir anders organisierten Speicher...
 | ||
|   
 | ||
| ### Heap: Keine klare Struktur
 | ||
|   - Anlegen: in C++ & Java mit new
 | ||
|   - Um angelegten Speicher anzusprechen: Zeiger und Referenzen
 | ||
|     - In Java automatisch Zeiger
 | ||
|     - In C++ Zeiger durch * hinter Typ
 | ||
|     ```cpp
 | ||
|     int main() {
 | ||
|       int* i = new int[3];
 | ||
|       int* j = new int;
 | ||
|       delete [] i;
 | ||
|       delete j;
 | ||
|       return 0;
 | ||
|     }
 | ||
|     ```
 | ||
|   - Löschen von Heap-Speicher: 
 | ||
|     - Java automatisch
 | ||
|     - In C++ nur manuell
 | ||
|       - durch genau einen Aufruf von delete
 | ||
|       - Programmierer ist dafür verantwortlich, dass danach kein Zeiger auf diesen Speicher mehr benutzt wird
 | ||
|   - Warum der Unterschied?
 | ||
|     - Nicht einfach festzustellen, wann letzter Zeiger auf Objekt gelöscht wurde
 | ||
|       - Zeiger können selbst auch im Heap gespeichert sein
 | ||
|       - Zyklische Referenzen!
 | ||
|     - Relativ aufwändiges Scannen, in Java durch regelmäßige Garbage Collection gelöst
 | ||
|       - Führt zu Jitter (Schwankung der Zeitdauer, die bestimmte Programmabschnitte zur Bearbeitung benötigen) & Speicher-Overhead, ...
 | ||
| 
 | ||
| Beispiele
 | ||
|   - Anlegen eines Objects auf dem Heap:
 | ||
|     ```cpp
 | ||
|     std::string* s = new std::string("wiz!");
 | ||
|     delete s;
 | ||
|     ```
 | ||
|   - Allokation von Feldern:
 | ||
|     ```cpp
 | ||
|     int* i = new int[29]; // gültige Indicies 0-28
 | ||
|     i[0] = 23;
 | ||
|     delete [] i; // nicht mit delete i; verwechseln!
 | ||
|     ```
 | ||
|   - Zeiger können durch & auf beliebige Variablen ermittelt werden
 | ||
|     ```cpp
 | ||
|     int i = 0;
 | ||
|     int* j = &i; // &-Operator erzeugt Zeiger; j darf nicht gelöscht werden
 | ||
|     ``` 
 | ||
|   - Zeiger können durch * dereferenziert werden
 | ||
|     ```cpp
 | ||
|     int i = 0;
 | ||
|     int* j = &i; // &-Operator erzeugt Zeiger
 | ||
|     *j = 1; // Zugriff auf Variableninhalt
 | ||
|     ``` 
 | ||
|   - Zugriff auf Methoden/Member Variablen
 | ||
|     ```cpp
 | ||
|     std::string* s = new std::string("wiz");
 | ||
|     (*s).push_back('?'); // manuelles Derefenzieren
 | ||
|     s->push_back('?'); // -> Operator
 | ||
|     delete s;
 | ||
|     ``` 
 | ||
|   - C++ übergibt alles als Kopie
 | ||
|     ```cpp
 | ||
|     void set(std::string s) { s = "foo"; }
 | ||
|     int main() {
 | ||
|     std::string s = "bar";
 | ||
|     set(s);
 | ||
|     std::cout << s; // gibt bar aus
 | ||
|     return 0;
 | ||
|     }
 | ||
|     ```
 | ||
|   - Zeiger können verwendet werden, um schreibend zuzugreifen
 | ||
|     ```cpp
 | ||
|     void set(std::string* s) { *s = "foo"; }
 | ||
|     int main() {
 | ||
|     std::string s = "bar";
 | ||
|     set(&s);
 | ||
|     std::cout << s; // gibt foo aus
 | ||
|     return 0;
 | ||
|     }
 | ||
|     ```
 | ||
|   - Zeiger erlauben syntaktisch sehr viele Dinge mit unvorhersehbaren Nebenwirkungen
 | ||
|     ```cpp
 | ||
|     std::string* magicStr() {
 | ||
|     std::string s("wiz!");
 | ||
|     return &s; // gibt Speicher auf Stack weiter; Tun Sie das nie!
 | ||
|     }
 | ||
|     int main() {
 | ||
|     std::string* s = magicStr();
 | ||
|     std::cout << *s; // Stack ist bereits überschrieben!
 | ||
|     return 0;
 | ||
|     }
 | ||
|     ```
 | ||
| 
 | ||
| Warum wirken sich Speicherfehler so unvorhersehbar aus?
 | ||
| - Speicherfehler entstehen sehr häufig durch Zugriff auf Speicherbereiche nachdem diese freigegeben worden sind
 | ||
| - Ob hierdurch später ein Fehler auftritt, hängt davon ab wie der freigegebene Speicher nach der Freigabe wieder genutzt wird
 | ||
| - Die insgesamte Speichernutzung wird durch die Gesamtheit aller Speicherallokationen und -freigaben beeinflusst
 | ||
| - Das kann dazu führen, dass ein Speicherfehler in Modul X erst lange nach seinem Entstehen Auswirkungen zeigt, nachdem in einem anderen Modul Y eine Änderung eingeführt wurde
 | ||
| - Auch eingebundene dynamische Bibliotheken haben Einfluss
 | ||
| - Das macht es so schwierig, solche Fehler schwierig zu finden
 | ||
| 
 | ||
| ### Bessere Alternative: Referenzen
 | ||
| - Zeigen ebenfalls auf Speicher, Compiler stellt aber sicher, dass Speicher gültig ist (wenn man nicht in Zeiger wandelt etc.)!
 | ||
| - Markiert durch Suffix &
 | ||
| - Beispiel:
 | ||
|   ```cpp
 | ||
|   void set(std::string& s) { s = "foo"; }
 | ||
|     int main() {
 | ||
|     std::string s = "bar";
 | ||
|     set(s);
 | ||
|     std::cout << s; // gibt foo aus
 | ||
|     return 0;
 | ||
|   }
 | ||
|   ```
 | ||
| - Dereferenzierung durch * und -> nicht notwendig
 | ||
| - Referenzen sind toll, haben aber eine Einschränkung:
 | ||
|   ```cpp
 | ||
|     std::string& magicStr() {
 | ||
|     std::string s("wiz!");
 | ||
|     return s; //< FEHLER
 | ||
|     }
 | ||
|   ```
 | ||
|   ```cpp
 | ||
|     std::string& magicStr() {
 | ||
|     static std::string s("wiz!");
 | ||
|     return s; // klappt prima
 | ||
|     }
 | ||
|   ```
 | ||
| - Per Referenz übergebene Rückgabewerte müssen im Speicher noch existieren, wenn Methodenaufruf abgeschlossen ist...
 | ||
|   - OK für globale Variablen, Member-Variablen, statische Variablen...
 | ||
|   - Nicht-OK für Speicher der wirklich dynamisch alloziert werden muss
 | ||
| - Allgemein bleiben nur Zeiger und Heap:
 | ||
|   ```cpp
 | ||
|   std::string* magicStr() {
 | ||
|     std::string* s = new std::string("wiz!");
 | ||
|     return s; // klappt prima, aber: aufpassen wann s gelöscht
 | ||
|     // werden kann und vollständig vergessen wurde!
 | ||
|   }
 | ||
|   ```
 | ||
| 
 | ||
| - Konvertierung von Zeigern zu Referenzen mit „*“-Operator:
 | ||
|   ```cpp
 | ||
|     std::string& s = *magicStr(); // Konvertieren in Referenz; Delete nicht mehr möglich
 | ||
|     std::string s2 = *magicStr(); // Konvertieren in Referenz & Kopie! Delete nicht direkt möglich
 | ||
|   ```
 | ||
| - Konvertierung von Referenzen zu Zeigern mit „&“-Operator:
 | ||
|   ```cpp
 | ||
|     std::string s("bla");
 | ||
|     std::string* sStar = &s; // Konvertieren in Zeiger
 | ||
|   ```
 | ||
| 
 | ||
| - Abschließende Bemerkungen zum Speicher
 | ||
|   - Niemals Speicher doppelt löschen – Niemals Löschen vergessen!
 | ||
|   - Häufige Praxis: Zeiger auf NULL setzen nach dem Löschen (Aber: gibt es danach wirklich keinen anderen Zeiger mehr?)
 | ||
|   - Nur Speicher löschen, der mit „new“ allokiert wurde
 | ||
|   - Speicher der mit „new“ allokiert wurde in jedem möglichen Programmablauf löschen (selbst wenn Exceptions auftreten)...
 | ||
|   - Nie über Feldgrenzen hinweg lesen/schreiben (auch negative Indizes!)
 | ||
|   - Programme ausgiebig testen (dabei Address Sanitizer aktivieren!)
 | ||
|   - Statische Code Analyse nutzen: z.B. http://cppcheck.sourceforge.net
 | ||
|   - malloc/free sind Äquivalente in Sprache C und nicht typsicher!
 | ||
| 
 | ||
| - Verbreitetes Vorgehen in C++ (Pattern): Resource Acquisition Is Initialization (RAII)
 | ||
|   - Speicher (oder Ressourcen im Allgemeinen) wird nur im Konstruktor einer Klasse reserviert
 | ||
|   - Destruktor gibt Speicher frei
 | ||
|   - Sicheres (Exceptions!), nachvollziehbares Konstrukt
 | ||
|   - Beispiel: (Funktioniert leider noch nicht immer)
 | ||
|     ```cpp
 | ||
|       class MagicString {
 | ||
|       std::string* s;
 | ||
|       public:
 | ||
|       MagicString() : s(new std::string("wiz!")) {}
 | ||
|       std::string* magicStr() { return s; }
 | ||
|       ~MagicString() { delete s; }
 | ||
|       };
 | ||
|     ```
 | ||
| 
 | ||
| ## Vererbung
 | ||
| - Vermeiden von Mehrfachimplementierungen
 | ||
| - Vermeiden von Dopplung interner Daten
 | ||
| - Vererbung syntaktisch ebenfalls ähnlich zu Java:
 | ||
|   ```java
 | ||
|   class Foo {
 | ||
|     public:
 | ||
|       int magic() const { return 23; }
 | ||
|       int enchanting() const { return 0xbeef; }
 | ||
|   };
 | ||
|   class FooBar : public Foo {
 | ||
|     public:
 | ||
|       int magic() const { return 42; }
 | ||
|   };
 | ||
|   ```
 | ||
| - Unterschied zu Java: Methoden „liegen“ bei C++ statisch im Speicher
 | ||
|   - D.h. f.magic(); ruft statisch magic-Methode in Klasse Foo auf, weil f eine Referenz vom Typ Foo ist
 | ||
|   - Vermeidet Mehrfachimplementierungen, realisiert aber keine einheitliche Schnittstelle!
 | ||
| - Nach Überschreiben einer Methode wollen wir meist, dass genutzte Methode nicht vom Referenztyp abhängt, sondern vom Objekttyp
 | ||
|   - Idee zu jedem Objekt speichern wir Zeiger auf zu nutzende Methoden
 | ||
|   - Tabelle wird *vtable* bezeichnet
 | ||
|   - Markierung von Methoden, für die ein Zeiger vorgehalten wird, mit Schlüsselwort virtual
 | ||
|   - Funktionierendes Beispiel:
 | ||
|     ```cpp
 | ||
|     class Foo {
 | ||
|       public:
 | ||
|         virtual int magic() const { return 23; }
 | ||
|     };
 | ||
|     class FooBar : public Foo {
 | ||
|       public:
 | ||
|         int magic() const override { return 42; }
 | ||
|     };
 | ||
|     int r(const Foo& f) { return f.magic(); }
 | ||
|     int main() {
 | ||
|       return r(FooBar()); // yeah gibt 42 zurück!
 | ||
|     }
 | ||
|     ```
 | ||
|     - virtual-Markierung genügt in Oberklasse, alle abgeleiteten Methoden ebenfalls „virtuell“
 | ||
|     - override-Markierung optional, aber hätte vor fehlendem virtual gewarnt!
 | ||
| 
 | ||
| ## Mehrfachvererbung
 | ||
| - C++ unterstützt keine Interfaces
 | ||
| - Aber C++ unterstützt Mehrfachvererbung! Pro Interface eine Basisklasse -> mit abstrakten Methoden erstellen
 | ||
| - Gute Praxis: Explizites Überschreiben
 | ||
|   ```cpp
 | ||
|   class NiceFooBar : public Foo, public Bar {
 | ||
|     // erlaube NiceFooBar().magic()
 | ||
|     int magic() const override { return Bar::magic(); }
 | ||
|   };
 | ||
|   ```
 | ||
|   - Wegen Mehrfachvererbung: kein super::
 | ||
|   - Stattdessen immer NameDerBasisKlasse::
 | ||
| - Aber: Diamond Problem
 | ||
|   - Markieren der Ableitung als virtual behebt das Problem
 | ||
| 
 | ||
| Komposition statt Vererbung
 | ||
| - Vererbungshierarchien werden trotzdem häufig als zu unflexibel angesehen
 | ||
| - Ein möglicher Ausweg:
 | ||
|   - Klassen flexiblen aus anderen Objekten zusammensetzen
 | ||
|   - Einzelobjekte modellieren Aspekte des Verhaltens des Gesamtobjekts
 | ||
|   - Werden beim Anlegen des Gesamtobjekts übergeben
 | ||
| - Engl.: Prefer composition over inheritance
 | ||
| ```cpp
 | ||
| class Automatisierungsmodul {
 | ||
|   public:
 | ||
|     void steuere() = 0;
 | ||
| };
 | ||
| class Roboter : public Automatisierungsmodul{
 | ||
|   public:
 | ||
|     void steuere() { /* call HAL */ }
 | ||
| };
 | ||
| class DumbDevice : public Automatisierungsmodul {
 | ||
|   public:
 | ||
|     void steuere() { /* do nothing */ }
 | ||
| };
 | ||
| class Geraet {
 | ||
|   protected:
 | ||
|     Automatisierungsmodul* _a;
 | ||
|     Geraet(Automatisierungsmodul* a, Saeuberungsmodul* s): _a(a), _s(s) {}
 | ||
|   public:
 | ||
|     void steuere() { _a->steuere(); }
 | ||
| };
 | ||
| ```
 | ||
| 
 | ||
| ## Operator Overloading
 | ||
| - In Java: Unterschied zwischen "==" und "equals()" bei String-Vergleich
 | ||
| - In C++: "=="-Operator für String-Vergleich
 | ||
| - Umsetzung: Hinzufügen einer Methode mit Namen *operatorx* wobei für x unter anderem zulässig: $+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=> && || ++ -- , ->* -> () []$
 | ||
| - Vereinfacht Nutzung komplexer Datentypen teilweise sehr stark
 | ||
| - Aber: Erfordert Disziplin beim Schreiben von Code
 | ||
|   - Oft erwartet: Freiheit von Exceptions (Wer gibt Speicher frei, wenn eine Zuweisung fehlgeschlagen ist?)
 | ||
|   - Semantik der Operatoren muss selbsterklärend sein
 | ||
|     - Ist der Operator auf einem multiplikativen Ring + oder * ?
 | ||
|     - Was ist, wenn zwei ungleiche Objekte jeweils kleiner als das andere sind?
 | ||
|     - Ist * bei Vektoren das Skalar- oder das Kreuzprodukt (oder etwas ganz anderes)?
 | ||
| 
 | ||
| ## Templates
 | ||
| - Generische Datentypen werden in C++ mit Templates realsiert
 | ||
| - Häufig ähnlich eingesetzt wie Generics, aber können neben Typen auch Konstanten enthalten
 | ||
| - Zur Compile-Zeit aufgelöst ➞ Deklaration & Implementierung in Header-Dateien
 | ||
| - Einfaches Beispiel (mit Typen, ähnl. zu Generics, primitive Typen ok!):
 | ||
|   ```cpp
 | ||
|     template<typename T> // typename keyword -> deklariert T als Typ
 | ||
|     T max(T a, T b) {
 | ||
|       return (a > b ? a : b);
 | ||
|     }
 | ||
|   ```
 | ||
|   ```cpp
 | ||
|   int i = 10;
 | ||
|   int j = 2;
 | ||
|   int k = max<int>(j, i); // explizit
 | ||
|   int l = max(j, i); // automat. Typinferenz durch Parametertypen
 | ||
|   ```
 | ||
| - Ein wichtiges Grundkonzept von Templates: Substitution failure is not an error (SFINAE) es -> wird solange nach passenden Templates (in lexikogr. Reihenfolge) gesucht bis Parameter passen (sonst Fehler!)
 | ||
| - Sehr häufig verwendetes Konstrukt & mächtiger als es scheint, aber schwer zu beherrschen
 | ||
|   - Wir können alternativ versuchen, durch SFINAE zu verhindern, dass Funktionen doppelt definiert sind
 | ||
|   - Trick: Einführen eines Pseudoparameters, der nicht benutzt wird
 | ||
|     ```cpp
 | ||
|     template<typename T>
 | ||
|     T quadrieren(T i, typename T::Val pseudoParam = 0) {
 | ||
|       T b(i); b *= i; return b;
 | ||
|     }
 | ||
|     ```
 | ||
|   - Trick: Einführen eines Hilfstemplates (sogenannter trait): wenn arithmetic<T>::Cond definiert ist, muss T = int sein
 | ||
|     ```cpp
 | ||
|     template<typename T> struct arithmetic {};
 | ||
|     template<> struct arithmetic<int> { using Cond = void*; };
 | ||
|     ```
 | ||
|   - Definition einer Funktion, die nur für int instanziiert werden kann:
 | ||
|     ```cpp
 | ||
|     template<typename T>
 | ||
|     T quadrieren(T i, typename arithmetic<T>::Cond = nullptr) {
 | ||
|       return i * i;
 | ||
|     }
 | ||
|     ```
 | ||
| 
 | ||
| ## Container
 | ||
| - Templates werden an vielen Stellen der C++ Standard-Bibliothek verwendet
 | ||
| - Container implementieren alle gängigen Datenstrukturen
 | ||
| - Prominente Beispiele:
 | ||
|   ```cpp
 | ||
|   template<typename T> class vector; // dynamisches Array
 | ||
|   template<typename T> class list; // doppelt verkette Liste
 | ||
|   template<typename T> class set; // geordnete Menge basiert auf Baum
 | ||
|   template<typename K, typename V> class map; // Assoziatives Array, geordnet
 | ||
|   // wie oben aber basierend auf Hash-Datenstruktur
 | ||
|   template<typename T> class unordered_set;
 | ||
|   template<typename K, typename V> class unordered_map;
 | ||
|   ```
 | ||
|   - Alle Templates sind stark vereinfacht dargestellt, weitere Parameter haben Standardwerte, die z.B. Speicherverhalten regeln
 | ||
| 
 | ||
| ### Container Enumerieren
 | ||
| - Je nach Struktur unterschiedlicher Zugriff
 | ||
| - Oft über Iteratoren vom Typ Container::iterator, bspw. vector<int>::iterator
 | ||
|     ```cpp
 | ||
|     std::vector<int> v{ 1, 2, 3 }; // Initialisierung über Liste
 | ||
|     // “normale” for-Schleife, Beachte: Überladene Operatoren ++ und *
 | ||
|     for(std::vector<int>::iterator i = v.begin(); i != v.end(); ++i) {
 | ||
|       std::cout << *i << std::endl;
 | ||
|     }
 | ||
|     // auto erlaubt Typinferenz → Code lesbarer, aber fehleranfälliger
 | ||
|     for(auto i = v.begin(); i != v.end(); ++i) {
 | ||
|       std::cout << *i << std::endl;
 | ||
|     }
 | ||
|     // range loop (nutzt intern Iteratoren), komplexe Datentypen nur mit Ref. “&” sonst werden Kopie erzeugt!
 | ||
|     for(int i : v) { // hier ohne “&”, da nur int in v gespeichert
 | ||
|       std::cout << i << std::endl;
 | ||
|     }
 | ||
|     ```
 | ||
| 
 | ||
| ### Container Einfügen
 | ||
| - Unterschiedliche Operationen je nach Container-Typ
 | ||
| - std::vector<T>::push_back() fügt neues Element am Ende ein
 | ||
|   - Allokiert ggf. neuen Speicher
 | ||
|   - Exisitierende Pointer können dadurch invalidiert werden!!!
 | ||
| - std::list<T> zusätzlich push_front() fügt Element am Anfang ein
 | ||
| - std::set, std::map, …
 | ||
|   - insert() fügt Element ein, falls es nicht existiert (Optional mit Hinweis wo ungefähr eingefügt werden soll)
 | ||
|   - operator[] erlaubt Zugriff aber auch Überschreiben alter Elemente
 | ||
|   - emplace() Einfügen, ohne Kopien zu erzeugen (nicht behandelt)
 | ||
| 
 | ||
| ### Container Löschen
 | ||
| - Unterschiedliche Operationen je nach Container-Typ
 | ||
| - Allgemein: erase(Container::iterator) (Vorsicht ggf. werden Iterator/Zeiger auf Objekte dadurch ungültig!)
 | ||
| - std::vector<T>::resize() löscht implizit letzte Elemente bei Verkleinerung
 | ||
| - std::vector<T>::pop_back()entfernt letztes Element
 | ||
| - std::list<T> hat zusätzlich pop_front()
 | ||
| - std::set, std::map, … löschen nur mit erase()
 | ||
| 
 | ||
| 
 | ||
| ## Shared Pointer
 | ||
| - Synonym: Smart Pointer
 | ||
| - Ziel: Sichereres Verwenden von Speicher
 | ||
| - Idee: kleine, schlanke Zeiger-Objekte, die Referenzzähler + Zeiger auf komplexere Objekte enthalten, wird letztes Zeiger-Objekt gelöscht, wird auch das komplexe Objekt gelöscht
 | ||
| - Realisierung mit RAII, Templates, Operator-Überladung
 | ||
| - Beispiel, wie shared_ptr sich verhalten sollten
 | ||
|   ```cpp
 | ||
|   using stringP = shared_ptr<std::string>;
 | ||
|   stringP hello() { // gibt kopie der referenz zurück
 | ||
|     return stringP(new std::string("Hello!"));
 | ||
|   }
 | ||
| 
 | ||
|   int main() {
 | ||
|     stringP x = hello();
 | ||
|     stringP y(x); // Erstellen einer weiteren Referenz
 | ||
|     std::cout << y->length();
 | ||
|     return 0; // Original-String wird gelöscht wenn letzte Ref. weg
 | ||
|   }
 | ||
| 
 | ||
|   template<class T> class shared_ptr { // Vereinfacht!
 | ||
|     T* p; // Zeiger auf eigentliches Objekt
 | ||
|     int* r; // Referenzzähler
 | ||
|   public:
 | ||
|     // neue Referenz auf Objekt erzeugen
 | ||
|     shared_ptr(T* t) : p(t), r(new int) { *r = 1; }
 | ||
|     // Referenz durch andere Referenz erzeugen
 | ||
|     shared_ptr(const shared_ptr<T>& sp) : p(sp.p), r(sp.r) { ++(*r); }
 | ||
|     T* operator->() const { // benutzen wie einen richtigen Zeiger
 | ||
|       return p;
 | ||
|     }
 | ||
|     ~shared_ptr() {
 | ||
|       if(--(*r) == 0) { // Objekt loeschen, wenn letzte Referenz weg
 | ||
|         delete r;
 | ||
|         delete p;
 | ||
|       }
 | ||
|     }
 | ||
|   }; // TODO operator= implementieren!
 | ||
|   ```
 | ||
| 
 | ||
| # Einführung in Funktionale Programmierung
 | ||
| ## Applikaive Algorithmen
 | ||
| Grundidee:
 | ||
| - Definition zusammengesetzter Funktionen durch Terme: $f(x) = 5x + 1$
 | ||
| - Unbestimmte:
 | ||
|   - x, y, z, . . . vom Typ int
 | ||
|   - q, p, r , . . . vom Typ bool
 | ||
| - Terme mit Unbestimmten (z.B. Terme vom Typ int: $x, x - 2, 2x + 1, (x + 1)(y - 1)$)
 | ||
| - Terme vom Typ bool $p, p Λ true, (p V true) ⇒ (q V false)$
 | ||
| 
 | ||
| > Sind $v_1, ..., v_n$ Unbestimmte vom Typ $\tau_1,...,\tau_n$ (bool oder int) und ist $t(v_1, ..., v_n)$ ein Term, so heißt $f(v_1, ..., v_n) = t(v_1, ..., v_n)$ eine Funktionsdefinition vom Typ $\tau$ . $\tau$ ist dabei der Typ des Terms.
 | ||
| 
 | ||
| - Erweiterung der Definition von Termen
 | ||
| - Neu: Aufrufe definierter Funktionen sind Terme
 | ||
| 
 | ||
| > Ein applikativer Algorithmus ist eine Menge von Funktionsdefinitionen. Die erste Funktion $f_1$ wird wie beschrieben ausgewertet und ist die Bedeutung (Semantik) des Algorithmus.
 | ||
| 
 | ||
| - Kategorisierung nach unterschiedlichen Kriterien
 | ||
| - Ordnung der Sprache
 | ||
|   - Erster Ordnung:
 | ||
|     - Funktionen können (nur) definiert und aufgerufen werden
 | ||
|   - Höherer Ordnung:
 | ||
|     - Funktionen können außerdem als Parameter an Funktionen übergeben werden und/oder Ergebnisse von Funktionen sein.
 | ||
|     - Funktionen sind hier auch Werte! -- erstklassige Werte;
 | ||
|     - Erstklassig: Es gibt keine Einschränkungen.
 | ||
|     - Umgekehrt: Wert ist eine Funktion ohne Parameter
 | ||
| - Auswertungsstrategie:
 | ||
|   - Strikte Auswertung:
 | ||
|     - Synonyme: strict evaluation, eager evaluation, call by value, applikative Reduktion
 | ||
|     - Die Argumente einer Funktion werden vor Eintritt in die Funktion berechnet (ausgewertet) – wie z.B. in Pascal oder C.
 | ||
|   - Bedarfsauswertung:
 | ||
|     - Synonyme: Lazy evaluation, call by need
 | ||
|     - Funktionsargumente werden unausgewertet an die Funktion übergeben
 | ||
|     - Erst wenn die Funktion (in ihrem Körper) die Argumente benötigt, werden die eingesetzten Argumentausdrücke berechnet, und dann nur einmal.
 | ||
|     - Realisiert „Sharing“ (im Unterschied zur Normalform-Reduktion – dort werden gleiche Ausdrücke immer wieder erneut berechnet).
 | ||
| - Typisierung:
 | ||
|   - Stark typisiert: Die verbreiteten funktionalen Programmiersprachen sind stark typisiert, d.h. alle Typfehler werden erkannt.
 | ||
|     - Statisch typisiert: Typprüfung wird zur Übersetzungszeit ausgeführt.
 | ||
|     - Dynamisch typisiert: Typprüfung wird zur Laufzeit ausgeführt
 | ||
|   - Untypisiert: Reiner Lambda-Kalkül (später)
 | ||
| 
 | ||
| ## Die funktionale Programmiersprache Erlang
 | ||
| - Entwickelt ab der zweiten Hälfte der 1980er Jahre im Ericsson Computer Science Laboratory (CSLab, Schweden)
 | ||
| - Ziel war, eine einfache, effiziente und nicht zu umfangreiche Sprache, die sich gut zur Programmierung robuster, großer und nebenläufiger Anwendungen für den industriellen Einsatz eignet. 
 | ||
| - Erste Version einer Erlang-Umgebung entstand 1987 auf der Grundlage von Prolog. Später wurden Bytecode-Übersetzer und abstrakte Maschinen geschaffen.
 | ||
| 
 | ||
| ### Arbeiten mit Erlang
 | ||
| - Erlang-Programme werden durch Definition der entsprechenden Funktionen in Modulen erstellt
 | ||
| - Module können in den Erlang-Interpreter geladen und von diesem in Zwischencode übersetzt werden
 | ||
| - Anschließend können Anfragen im Interpreter gestellt werden
 | ||
| 
 | ||
| Modul "fakultaet.erl":
 | ||
| ```erlang
 | ||
|   -module(fakultaet).
 | ||
|   -export([fak/1]).
 | ||
|   fak(0) -> 1;
 | ||
|   fak(N) when N > 0 -> (N) * fak(N-1).
 | ||
| ```
 | ||
| Laden in Interpreter mittels: ```c(fakultaet)```
 | ||
| Testen der Funktion, z.B. mit: ```fakultaet:fak(5)```
 | ||
| 
 | ||
| ### Elemente von Erlang
 | ||
| - Ganzzahlen (Integer):
 | ||
|   - 10
 | ||
|   - -234
 | ||
|   - 16#AB10F
 | ||
|   - 2#110111010
 | ||
|   - $A
 | ||
|   - B#Val erlaubt Zahlendarstellung mit Basis B (mit B ≤ 36).
 | ||
|   - $Char ermöglicht Angabe von Ascii-Zeichen ($A für 65).
 | ||
| - Gleitkommazahlen (Floats):
 | ||
|   - 17.368
 | ||
|   - -56.654
 | ||
|   - 12.34E-10.
 | ||
| - Atome (Atoms):
 | ||
|   - abcef
 | ||
|   - start_with_a_lower_case_letter
 | ||
|   - 'Blanks can be quoted'
 | ||
|   - 'Anything inside quotes \n\012'
 | ||
|   - Erläuterungen:
 | ||
|     - Atome sind Konstanten, die Ihren eigenen Namen als Wert haben
 | ||
|     - Atome beliebiger Länge sind zulässig
 | ||
|     - Jedes Zeichen ist innerhalb eines Atoms erlaubt
 | ||
|     - Einige Atome sind reservierte Schlüsselwörter und können nur in der von den Sprachentwicklern gewünschen Weise verwendet werden als Funktionsbezeichner, Operatoren, Ausdrücke etc. 
 | ||
|     - Reserviert sind: *after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor*
 | ||
| - Tupel (Tuples):
 | ||
|   - {123, bcd} % Ein Tupel aus Ganzzahl und Atom
 | ||
|   - {123, def, abc}
 | ||
|   - {person, 'Joe', 'Armstrong'}
 | ||
|   - {abc, {def, 123}, jkl}
 | ||
|   - {}
 | ||
|   - Erläuterungen:
 | ||
|     - Können eine feste Anzahl von “Dingen” speichern
 | ||
|     - Tupel beliebiger Größe sind zulässig
 | ||
|     - Kommentare werden in Erlang mit % eingeleitet und erstrecken sich dann bis zum Zeilenende
 | ||
| - Listen:
 | ||
|   - [123, xyz]
 | ||
|   - [123, def, abc]
 | ||
|   - [{person, 'Joe', 'Armstrong'}, {person, 'Robert', 'Virding'}, {person, 'Mike', 'Williams'}]
 | ||
|   - "abcdefgh" wird zu [97,98,99,100,101,102,103,104]
 | ||
|   - "" wird zu []
 | ||
|   - Erläuterungen:
 | ||
|     - Listen können eine variable Anzahl von Dingen speichern
 | ||
|     - Die Größe von Listen wird dynamisch bestimmt
 | ||
|     - "..." ist eine Kurzform für die Liste der Ganzzahlen, die die ASCIICodes der Zeichen innerhalb der Anführungszeichen repräsentieren
 | ||
| - Variablen:
 | ||
|   - Abc
 | ||
|   - A_long_variable_name
 | ||
|   - AnObjectOrientatedVariableName
 | ||
|   - Erläuterungen:
 | ||
|     - Fangen grundsätzlich mit einem Großbuchstaben an 
 | ||
|     - Keine „Funny Characters"
 | ||
|     - Variablen werden zu Speicherung von Werten von Datenstrukturen verwendet
 | ||
|     - Variablen können nur einmal gebunden werden!
 | ||
|     - Der Wert einer Variablen kann also nicht mehr verändert werden, nachdem er einmal gesetzt wurde: *N = N + 1 VERBOTEN!*
 | ||
|     - Einzige Ausnahmen: Die anonyme Variable "_" (kein Lesen möglich) und das Löschen einer Variable im Interpreter mit f(N).
 | ||
| - Komplexe Datenstrukturen:
 | ||
|   - [{{person,'Joe', 'Armstrong'}, {telephoneNumber, [3,5,9,7]}, {shoeSize, 42}, {pets, [{cat, tubby},{cat, tiger}]}, {children,[{thomas, 5},{claire,1}]}}, {{person,'Mike','Williams'}, {shoeSize,41}, {likes,[boats, beer]}, ... }]
 | ||
|   - Erläuterungen:
 | ||
|     - Beliebig komplexe Strukturen können erzeugt werden
 | ||
|     - Datenstrukturen können durch einfaches Hinschreiben erzeugt werden (keine explizite Speicherbelegung oder -freigabe)
 | ||
|     - Datenstrukturen können gebundene Variablen enthalten
 | ||
| - Pattern Matching:
 | ||
|   - $A = 10$ erfolgreich, bindet A zu 10 
 | ||
|   - ${B, C, D} = {10, foo, bar}$ erfolgreich, bindet B zu 10, C zu foo and D zu bar
 | ||
|   - ${A, A, B} = {abc, abc, foo}$ erfolgreich, bindet A zu abc, B zu foo
 | ||
|   - ${A, A, B} = {abc, def, 123}$ schlägt fehl (“fails”)
 | ||
|   - $[A,B,C] = [1,2,3]$ erfolgreich, bindet A zu 1, B zu 2, C zu 3
 | ||
|   - $[A,B,C,D] = [1,2,3]$ schlägt fehl
 | ||
|   - $[A,B|C] = [1,2,3,4,5,6,7]$ erfolgreich bindet A zu 1, B zu 2, C zu [3,4,5,6,7]
 | ||
|   - $[H|T] = [1,2,3,4]$ erfolgreich, bindet H zu 1, T zu [2,3,4]
 | ||
|   - $[H|T] = [abc]$ erfolgreich, bindet H zu abc, T zu []
 | ||
|   - $[H|T] = []$ schlägt fehl
 | ||
|   - ${A,_, [B|_],{B}} = {abc,23,[22,x],{22}}$ erfolgreich, bindet A zu abc, B zu 22
 | ||
|   - Erläuterungen: 
 | ||
|     - „Pattern Matching“, zu Deutsch „Mustervergleich“, spielt eine zentrale Rolle bei der Auswahl der „richtigen“ Anweisungsfolge für einen konkreten Funktionsaufruf und dem Binden der Variablen für die Funktionsparameter (siehe spätere Erklärungen)
 | ||
|     - Beachte die Verwendung von "_", der anonymen (“don't care”) Variable (diese Variable kann beliebig oft gebunden, jedoch nie ausgelesen werden, da ihr Inhalt keine Rolle spielt).
 | ||
|     - Im letzten Beispiel wird die Variable B nur einmal an den Wert 22 gebunden (das klappt, da der letzte Wert genau {22} ist)
 | ||
| - Funktionsaufrufe:
 | ||
|   - module:func(Arg1, Arg2, ... Argn)
 | ||
|   - func(Arg1, Arg2, .. Argn)
 | ||
|   - Erläuterungen:
 | ||
|     - Arg1 .. Argn sind beliebige Erlang-Datenstrukturen
 | ||
|     - Die Funktion und die Modulnamen müssen Atome sein (im obigen Beispiel module und func)
 | ||
|     - Eine Funktion darf auch ohne Parameter (Argumente) sein (z.B. date() – gibt das aktuelle Datum zurück)
 | ||
|     - Funktionen werden innerhalb von Modulen definiert
 | ||
|     - Funktionen müssen exportiert werden, bevor sie außerhalb des Moduls, in dem sie definiert sind, verwendet werden
 | ||
|     - Innerhalb ihres Moduls können Funktionen ohne den vorangestellten Modulnamen aufgerufen werden (sonst nur nach einer vorherigen Import-Anweisung)
 | ||
| - Modul-Deklaration:
 | ||
|   ```erlang
 | ||
|     -module(demo).
 | ||
|     -export([double/1]).
 | ||
|     double(X) -> times(X, 2).
 | ||
|     times(X, N) -> X * N.
 | ||
|   ```
 | ||
|   - Erläuterungen:
 | ||
|     - Die Funktion double kann auch außerhalb des Moduls verwendet werden, times ist nur lokal in dem Modul verwendbar
 | ||
|     - Die Bezeichnung double/1 deklariert die Funktion double mit einem Argument 
 | ||
|     - Beachte: double/1 und double/2 bezeichnen zwei unterschiedliche Funktionen
 | ||
| - Eingebaute Funktionen (Built In Functions, BIFs)
 | ||
|   - date()
 | ||
|   - time()
 | ||
|   - length([1,2,3,4,5])
 | ||
|   - size({a,b,c})
 | ||
|   - atom_to_list(an_atom)
 | ||
|   - list_to_tuple([1,2,3,4])
 | ||
|   - integer_to_list(2234)
 | ||
|   - tuple_to_list({})
 | ||
|   - Erläuterungen:
 | ||
|     - Eingebaute Funktionen sind im Modul erlang deklariert
 | ||
|     - Für Aufgaben, die mit normalen Funktionen nicht oder nur sehr schwierig in Erlang realisiert werden können
 | ||
|     - Verändern das Verhalten des Systems
 | ||
|     - Beschrieben im Erlang BIFs Handbuch
 | ||
| - Definition von Funktionen:
 | ||
|   ```erlang
 | ||
|   func(Pattern1, Pattern2, ...) ->
 | ||
|     ... ; % Vor dem ; steht der Rumpf
 | ||
|   func(Pattern1, Pattern2, ...) ->
 | ||
|     ... ; % Das ; kündigt weitere Alternativen an
 | ||
|     ... % Beliebig viele Alternativen möglich
 | ||
|   func(Pattern1, Pattern2, ...) ->
 | ||
|     ... . % Am Ende muss ein Punkt stehen!
 | ||
|   ```
 | ||
|   - Erläuterungen:
 | ||
|     - Funktionen werden als Sequenz von Klauseln definiert 
 | ||
|     - Sequentielles Testen der Klauseln bis das erste Muster erkannt wird (Pattern Matching)
 | ||
|     - Das Pattern Matching bindet alle Variablen im Kopf der Klausel
 | ||
|     - Variablen sind lokal zu jeder Klausel (automatische Speicherverw.)
 | ||
|     - Der entsprechende Anweisungsrumpf wird sequentiell ausgeführt
 | ||
| 
 | ||
| Was passiert wenn wir mathstuff:factorial() mit einem negativen Argument aufrufen? Der Interpreter reagiert nicht mehr?
 | ||
| - Erste Reaktion: rette das Laufzeitsystem durch Eingabe von CTRL-G
 | ||
|   - User switch command
 | ||
|     01.  --> h
 | ||
|     02.  c [nn] - connect to job
 | ||
|     03.  i [nn] - interrupt job
 | ||
|     04.  k [nn] - kill job
 | ||
|     05.  j - list all jobs
 | ||
|     06.  s [shell] - start local shell
 | ||
|     07.  r [node [shell]] - start remote shell
 | ||
|     08.  q - quit erlang
 | ||
|     09.  ? | h - this message
 | ||
|     10. -->
 | ||
|   - Liste durch Eingabe von j alle Jobnummern auf
 | ||
|   - Beende den entsprechenden Shell-Job durch k <jobnr>
 | ||
|   - Starte eine neue Shell durch Eingabe von s
 | ||
|   - Liste durch erneute Eingabe von j die neuen Jobnummern auf
 | ||
|   - Verbinde durch Eingabe von c <shelljobnr> mit neuer Shell
 | ||
| - Zweite Reaktion: Ergänze factorial() um zusätzliche Bedingung:
 | ||
|   - „Beschütze“ die Funktion vor Endlosrekursion durch Ergänzung eines sogenannten Wächters (Guards) bei dem entsprechenden Fallmuster  (Pattern)
 | ||
|   - Erläuterungen:
 | ||
|     - Der Guard wird durch das Atom when und eine Bedingung vor dem Pfeil -> formuliert
 | ||
|     - Vollständig „beschützte“ Klauseln können in beliebiger Reihenfolge angeordnet werden
 | ||
|     - Achtung: Ohne Guard führt diese Reihenfolge zu Endlosschleifen
 | ||
|   - Beispiele für Guards:
 | ||
|     - number(X)       % X is a number
 | ||
|     - integer(X)      % X is an integer
 | ||
|     - float(X)        % X is a float
 | ||
|     - atom(X)         % X is an atom
 | ||
|     - tuple(X)        % X is a tuple
 | ||
|     - list(X)         % X is a list
 | ||
|     - length(X) == 3  % X is a list of length 3
 | ||
|     - size(X) == 2    % X is a tuple of size 2.
 | ||
|     - X > Y + Z       % X is > Y + Z
 | ||
|     - X == Y          % X is equal to Y
 | ||
|     - X =:= Y         % X is exactly equal to Y (i.e. 1 == 1.0 succeeds but 1 =:= 1.0 fails)
 | ||
|   - Alle Variablen in einem Wächter müssen zuvor gebunden werden
 | ||
| 
 | ||
| - Traversieren (“Ablaufen”) von Listen:
 | ||
|   ```
 | ||
|     average(X) -> sum(X) / len(X).
 | ||
|     sum([H|T]) -> H + sum(T); % summiert alle Werte auf
 | ||
|     sum([]) -> 0.
 | ||
|     len([_|T]) -> 1 + len(T); % Wert des Elements
 | ||
|     len([]) -> 0. % interessiert nicht
 | ||
|   ```
 | ||
|   - Die Funktionen sum und len verwenden das gleiche Rekursionsmuster
 | ||
|   - Zwei weitere gebräuchliche Rekursionsmuster:
 | ||
|     ```
 | ||
|     double([H|T]) -> [2*H|double(T)];   % verdoppelt alle
 | ||
|     double([]) -> [].                   % Listenelemente
 | ||
| 
 | ||
|     member(H, [H|_]) -> true;           % prüft auf
 | ||
|     member(H, [_|T]) -> member(H, T);   % Enthaltensein
 | ||
|     member(_, []) -> false.             % in Liste
 | ||
|     ```
 | ||
| - Listen und Akkumulatoren:
 | ||
|   ```
 | ||
|     average(X) -> average(X, 0, 0).
 | ||
|     average([H|T], Length, Sum) -> average(T, Length + 1, Sum + H);
 | ||
|     average([], Length, Sum) -> Sum / Length.
 | ||
|   ```
 | ||
|   - Interessant sind an diesem Beispiel:
 | ||
|     - Die Liste wird nur einmal traversiert 
 | ||
|     - Der Speicheraufwand bei der Ausführung ist konstant, da die Funktion “endrekursiv” ist (nach Rekursion steht Ergebnis fest)
 | ||
|     - Die Variablen Length und Sum spielen die Rolle von Akkumulatoren
 | ||
|     - Bemerkung: average([]) ist nicht definiert, da man nicht den Durchschnitt von 0 Werten berechnen kann (führt zu Laufzeitfehler)
 | ||
| - „Identisch“ benannte Funktionen mit unterschiedlicher Parameterzahl:
 | ||
|   ```
 | ||
|   sum(L) -> sum(L, 0).
 | ||
|   sum([], N) -> N;
 | ||
|   sum([H|T], N) -> sum(T, H+N).
 | ||
|   ```
 | ||
|   - Erläuterungen:
 | ||
|     - Die Funktion sum/1 summiert die Elemente einer als Parameter übergebenen Liste
 | ||
|     - Sie verwendet eine Hilfsfunktion, die mit sum/2 benannt ist
 | ||
|     - Die Hilfsfunktion hätte auch irgendeinen anderen Namen haben können
 | ||
|     - Für Erlang sind sum/1 und sum/2 tatsächlich unterschiedliche Funktionsnamen
 | ||
| - Shell-Kommandos:
 | ||
|   - h() - history . Print the last 20 commands.
 | ||
|   - b() - bindings. See all variable bindings.
 | ||
|   - f() - forget. Forget all variable bindings.
 | ||
|   - f(Var) - forget. Forget the binding of variable X. This can ONLY be used as a command to the shell - NOT in the body of a function!
 | ||
|   - e(n) - evaluate. Evaluate the n:th command in history. 
 | ||
|   - e(-1) Evaluate the previous command.
 | ||
|   - Erläuterungen: Die Kommandozeile kann wie mit dem Editor Emacs editiert werden (werl.exe unterstützt zusätzlich Historie mit Cursortasten)
 | ||
| - Spezielle Funktionen: 
 | ||
|   ``` 
 | ||
|   apply(Func, Args)
 | ||
|   apply(Mod, Func, Args) % old style, deprecated
 | ||
|   ```
 | ||
|   - Erläuterungen:
 | ||
|     - Wendet die Funktion Func (im Modul Mod bei der zweiten Variante) auf die Argumente an, die in der Liste Args enthalten sind
 | ||
|     - Mod und Func müssen Atome sein bzw. Ausdrücke, die zu Atomen evaluiert werden und die eine Funktion bzw. Modul referenzieren 
 | ||
|     - Jeder Erlang-Ausdruck kann für die Formulierung der an die Funktion zu übergebenden Argumente verwendet werden
 | ||
|     - Die Stelligkeit der Funktion ist gleich der Länge der Argumentliste 
 | ||
|     - Beispiel: ```` 1> apply( lists1,min_max,[[4,1,7,3,9,10]]).``` ->  {1, 10}
 | ||
|       - Bemerkung: Die Funktion min_max erhält hier ein (!) Argument
 | ||
| - Anonyme Funktionen:
 | ||
|   ```
 | ||
|   Double = fun(X) -> 2*X end.
 | ||
|   > Double(4).
 | ||
|   > 8
 | ||
|   ```
 | ||
|   - Erläuterung: 
 | ||
|     - Mittels “fun” können anonyme Funktionen deklariert werden
 | ||
|     - Diese können auch einer Variablen (im obigen Beispiel Double) zugewiesen werden
 | ||
|     - Interessant wird diese Art der Funktionsdefinition, da anonyme Funktionen auch als Parameter übergeben bzw. als Ergebniswert zurückgegeben werden können
 | ||
|     - Die Funktionen, die anonyme Funktionen als Parameter akzeptieren bzw. als Ergebnis zurückgeben nennt man Funktionen höherer Ordnung
 | ||
| 
 | ||
| Kap 3a seite 48-84
 | ||
| 
 | ||
| ## Lambda Kalkül
 | ||
| [comment]: <> (Kapitel 3b)
 | ||
| 
 | ||
| # Multithreading & Parallele Programmierung
 | ||
| [comment]: <> (Kapitel 4)
 | ||
| ## Grundlagen
 | ||
| 
 | ||
| ## Parallele Programmierung in Erlang
 | ||
| 
 | ||
| ## Parallele Programmierung in C++
 | ||
| ### Threads
 | ||
| Thread („Faden“) := leichtgewichtige Ausführungseinheit oder Kontrollfluss (Folge von Anweisungen) innerhalb eines sich in Ausführung befindlichen Programms
 | ||
| - Threads teilen sich den Adressraum des ihres Prozesses
 | ||
| - in C++: Instanzen der Klasse std::thread
 | ||
| - führen eine (initiale) Funktion aus
 | ||
| 
 | ||
| ```cpp
 | ||
| #include <thread>
 | ||
| #include <iostream>
 | ||
| 
 | ||
| void say_hello() {
 | ||
|   std::cout << "Hello Concurrent C++\n";
 | ||
| }
 | ||
| 
 | ||
| int main() {
 | ||
|   std::thread t(say_hello);
 | ||
|   t.join();
 | ||
| }
 | ||
| ```
 | ||
| Alternative Erzeugung von Threads über Lamda Ausdruck: 
 | ||
| ```cpp
 | ||
| std::thread t([]() { do_something(); });
 | ||
| ```
 | ||
| oder mit Instanz einer Klasse - erfordert Überladen von operator()
 | ||
| ```cpp
 | ||
| struct my_task {
 | ||
|   void operator()() const { do_something(); }
 | ||
| };
 | ||
| 
 | ||
| my_task tsk;
 | ||
| std::thread t1(tsk); // mit Objekt
 | ||
| std::thread t2{ my_task() }; // über Konstruktor
 | ||
| ```
 | ||
| 
 | ||
| Parameter-Übergabe bei Thread-Erzeugung über zusätzliche Argumente des thread-Konstruktors. Vorsicht bei Übergabe von Referenzen, wenn
 | ||
| Eltern-Thread vor dem erzeugten Thread beendet wird.
 | ||
| ```cpp
 | ||
| void fun(int n, const std::string& s) {
 | ||
|   for (auto i = 0; i < n; i++)
 | ||
|     std::cout << s << " ";
 | ||
|   std::cout << std::endl;
 | ||
| }
 | ||
| std::thread t(fun, 2, "Hello");
 | ||
| t.join();
 | ||
| ```
 | ||
| 
 | ||
| Warten auf Threads
 | ||
| - t.join() wartet auf Beendigung des Threads t
 | ||
| - blockiert aktuellen Thread
 | ||
| - ohne join() keine Garantie, dass t zur Ausführung kommt
 | ||
| - Freigabe der Ressourcen des Threads
 | ||
| ```cpp
 | ||
| std::thread t([]() { do_something(); });
 | ||
| t.join();
 | ||
| ```
 | ||
| 
 | ||
| Hintergrund Threads
 | ||
| - Threads können auch im Hintergrund laufen, ohne dass auf Ende gewartet werden muss
 | ||
| - „abkoppeln“ durch detach()
 | ||
| - Thread läuft danach unter Kontrolle des C++-Laufzeitsystems, join nicht mehr möglich
 | ||
| 
 | ||
| Thread-Identifikation
 | ||
| - Thread-Identifikator vom Typ `std::thread::id`
 | ||
| - Ermittlung über Methode get_id()
 | ||
| ```cpp
 | ||
| void fun() {
 | ||
|   std::cout << "Hello from "
 | ||
|             << std::this_thread::get_id()
 | ||
|             << std::endl;
 | ||
| }
 | ||
| std::thread t(fun);
 | ||
| t.join();
 | ||
| ```
 | ||
| 
 | ||
| 
 | ||
| ### Datenparallele Verarbeitung
 | ||
| ### Kommunikation zwischen Threads
 | ||
| ### Taskparallelität
 | ||
| ... |