Programmieren für Fortgeschrittene

Werbung
Programmieren für Fortgeschrittene
1. Einführung
Werner Struckmann
Technische Universität Braunschweig
Institut für Programmierung und Reaktive Systeme
Wintersemester 2013/2014
1. Einführung
1.1 Sprachen und Paradigmen
1.2 Definition von Programmiersprachen
1.3 Implementierung von Programmiersprachen
1.1 Sprachen und Paradigmen
2/38
Sprachen
• Sprache ist ein sich stets weiterentwickelndes, komplexes
System von Lauten und Zeichen zum Zwecke der
Kommunikation. Jedem Zeichen des Systems wird eine
feststehende Bedeutung zugeordnet.
• Sprache wirkt im Prozess der Kommunikation als Medium
zwischen dem Sender (Sprecher, Schreiber) und Empfänger
(Hörer, Leser).
• Es werden natürliche und künstliche Sprachen unterschieden.
aus Basiswissen Deutsch, Dudenverlag
1.1 Sprachen und Paradigmen
3/38
Natürliche, künstliche und formale Sprachen
• Natürliche Sprachen sind historisch gewachsen. Hierzu
zählen z. B. Deutsch, Englisch und Französisch. Sie sind
Ausdruck menschlichen Denkens, Fühlens und Wollens und
weisen im Unterschied zu künstlichen Sprachen
Mehrdeutigkeiten auf.
• Künstliche Sprachen sind Zeichensysteme, die der
Verständigung in einem eng begrenzten Fachgebiets dienen,
zum Beispiel Programmiersprachen. Sprachen wie Esperanto
sind ebenfalls künstliche Sprachen, die sich durch leichtere
Schreibung und Grammatik gegenüber natürlichen Sprachen
auszeichnen.
• Formale Sprachen sind künstliche Sprachen, die mithilfe
mathematischer Methoden definiert sind.
1.1 Sprachen und Paradigmen
4/38
Sprachklassen der Informatik
Die Sprachen der Informatik werden typischerweise in zwei Klassen
aufgeteilt:
• General Purpose Language (GPL)
• Domain Specific Language (DSL)
Meistens zählt man die Programmiersprachen zu den GPLs und
Sprachen für spezielle Anwendungen zu den DSLs.
Die Klasseneinteilung ist nicht in allen Quellen genau identisch.
Eine mögliche Beispiel-Einteilung finden Sie in einem Material der
Veranstaltung.
1.1 Sprachen und Paradigmen
5/38
Sprachen der Informatik: Beispiele
Um Sachverhalte mit Rechensystemen zu behandeln, müssen sie in
eindeutigen – also künstlichen – Sprachen beschrieben werden.
Einige Beispiele sollen dies verdeutlichen:
• Algorithmen: Programmiersprachen (Java)
• Dokumente: Markup-Sprachen (Html, XML),
Seitenbeschreibungssprachen (Postscript)
• Modelle, Systeme: Modellierungssprachen (UML),
Spezifikationssprachen (Z)
• Datenbanken: Anfragesprachen (SQL)
• Mathematische Objekte: Symbolische Sprachen (Maple)
• Abläufe: Simulationssprachen (GPSS)
1.1 Sprachen und Paradigmen
6/38
Sprachen der Informatik
• In der Informatik hat man es mit einer Vielzahl von
künstlichen Sprachen zu tun.
• Sie alle beschreiben Sachverhalte in einem relativ kleinen
Kontext,
• dafür aber (hoffentlich) präzise, widerspruchsfrei und
vollständig.
In dieser Veranstaltung betrachten wir die grundlegenden Konzepte
und Paradigmen moderner Programmiersprachen. Kenntnisse der
imperativen und objektorientierten Programmierung sowie der
Programmiersprache Java werden vorausgesetzt.
1.1 Sprachen und Paradigmen
7/38
Paradigmen höherer Programmiersprachen
Jeder Programmiersprache liegt (mindestens) eine bestimmte
Vorgehensweise der Formulierung zugrunde. Auf der Basis dieser
Konzepte unterteilt man die höheren Programmiersprachen u. a. in
folgende Kategorien:
• Imperative Programmiersprachen,
• Funktionale (applikative) Programmiersprachen,
• Objektorientierte Programmiersprachen.
• Prädikative (deduktive, logische) Programmiersprachen,
Es gibt weitere Paradigmen, diese vier sind aber die am häufigsten
erwähnten.
1.1 Sprachen und Paradigmen
8/38
Imperative Programmiersprachen
• Bei diesen Sprachen besteht ein Programm aus einer Folge
aus Befehlen an den Rechner.
• Wesentlich ist das Variablenkonzept. Variable können
verschiedene Werte annehmen.
• Die Menge aller Variablen und ihrer Werte sowie der
Programmzähler beschreiben den Zustand zu einem
bestimmten Zeitpunkt.
• Die Ausführung eines Programms bewirkt eine
Zustandstransformation.
1.1 Sprachen und Paradigmen
9/38
Funktionale Programmiersprachen
• Bei diesen Sprachen berechnen Programme Funktionen, die
Eingabedaten auf Ausgabedaten abbilden. Ein funktionales
Programm beschreibt die Beziehungen zwischen Ein- und
Ausgabe mithilfe mathematischer Gleichungen.
• Ausgehend von elementaren Ausdrücken werden die
Beziehungen durch Ausdrücke steigender Komplexität
festgelegt.
• Das wichtigste Konstruktionsprinzip ist hierbei die Rekursion.
• Außerdem spielen Funktionen höherer Ordnung, sogenannte
Funktionale, eine wichtige Rolle.
1.1 Sprachen und Paradigmen
10/38
Objektorientierte Programmiersprachen
• Bei diesen Sprachen werden alle zum Lösen eines Problems
notwendigen Informationen als Objekte aufgefasst. Objekte
besitzen Eigenschaften, die als Attribute bezeichnet werden.
• Objekte können durch Nachrichten an andere Objekte
Informationen austauschen. Dieser Vorgang kann zum Beispiel
durch Aufruf von Methoden realisiert werden.
• Gleichartige Objekte werden durch Klassen beschrieben. Von
jeder Klasse können Objekte gemäß der Beschreibung erstellt
und über die Methoden manipuliert werden.
• Klassen/Objekte können untereinander in vielfältigen
Beziehungen stehen.
1.1 Sprachen und Paradigmen
11/38
Prädikative Programmiersprachen
• Bei diesen Sprachen wird Programmierung als Beweisen in
einem System von Tatsachen und Schlussfolgerungen
aufgefasst.
• Der Anwender gibt eine Menge von Fakten und Regeln vor.
• Die Aufgabe des Rechners ist es festzustellen, ob eine
eingegebene Tatsache zutrifft oder nicht.
• Alternativ können alle Fakten, die bestimmte Kriterien
erfüllen, ermittelt werden.
• Die Fakten werden durch Prädikate formuliert, den
Schlussfolgerungen liegt ein logischer Kalkül zugrunde.
Die prädikative Programmiersprache Prolog können Sie in der
Vorlesung „Logik in der Informatik“ lernen.
1.1 Sprachen und Paradigmen
12/38
Paradigmen höherer Programmiersprachen
Aus einer übergeordneten Sichtweise werden die folgenden
Kategorien unterschieden:
• Prozedurale Programmiersprachen: Es wird exakt angegeben,
wie die Lösung eines Problems ermittelt werden kann.
Imperative Programmiersprachen fallen in diese Kategorie.
• Deklarative Programmiersprachen: Im Gegensatz zum
prozeduralen Paradigma fragt man in der deklarativen
Programmierung danach, was berechnet werden soll. Es wird
also nicht der Lösungsweg programmiert, sondern angegeben,
welches Ergebnis gewünscht ist. Deklarative Paradigmen
beruhen auf mathematischen, rechnerunabhängigen Theorien.
Beispiele hierfür sind prädikative und – bis zu einem gewissen
Grade – auch funktionale Programmiersprachen.
Ein hybrides Paradigma ist die Mischung von Paradigmen.
1.1 Sprachen und Paradigmen
13/38
Entwicklung der Programmiersprachen
Edsger W. Dijkstra:
„Jeder Programmierer weiß, dass es nur eine einzig wahre
Programmiersprache gibt. Jede Woche eine neue.“
Albrecht Weinert: Java für Ingenieure, 2001, Seite 7:
„Die Zahl der Programmiersprachen, die die Informatik in
den letzten fünfzig Jahren hervorgebracht hat, ist Legion.
Ernst zu nehmende Schätzungen sprechen von mehr als
20 000.“
Wenn Weinerts Schätzung zutrifft, sind es 7,7 Sprachen pro
Woche!
1.1 Sprachen und Paradigmen
14/38
Entwicklung der Programmiersprachen
JAVA
1995
93
91
SCHEME−Standard
89
87
C++
85
83
OCCAM
81
ADA
79
LOGO
69
ALGOL68
67
61
59
SIMULA
• Algol
• Algol68
• Modula-2
PL/I
• Scheme
BASIC
COBOL
57
1955
PROLOG
PASCAL
71
63
CSP
SCHEME
C
73
65
SMALLTALK80
MODULA2
77
1975
Programmiersprachen in der
Informatikausbildung
ALGOL
FORTRAN
1.1 Sprachen und Paradigmen
LISP
• Java
15/38
Paradigmen und Programmiersprachen
Einige Programmiersprachen:
imperativ:
funktional:
prädikativ:
objektorientiert:
hybrid:
Algol, Algol68, Pascal, Ada, C, . . .
Lisp, Scheme, ML, Haskell, . . .
Prolog
Smalltalk, Eiffel, . . .
Java, C++, C# (imperativ, oo),
Scala (imperativ, oo, funktional), . . .
In der Regel lassen sich die Sprachen nicht eindeutig einem
bestimmten Paradigma zuordnen. Zum Beispiel gibt es in Scheme
Variable und Zuweisungen, d. h. imperative Konzepte. Java ist als
„imperativ-basierte objektorientierte Programmiersprache“
(hybrides Paradigma) zu bezeichnen. C++ hingegen besitzt
einen vollständigen imperativen Kern, während Smalltalk eine
strikt objektorientierte Programmiersprache ist.
1.1 Sprachen und Paradigmen
16/38
Skriptsprachen
• Bei Skriptsprachen handelt es sich um übergeordnete
Sprachen, um vorhandene Programme oder Prozeduren
kontrolliert ablaufen zu lassen.
• Skriptsprachen haben ihren Ursprung in den
Kommandosprachen (Job Control Language, JCL) von
Betriebssystemen.
• Einfache Skriptsprachen sind die Shell-Skripts von Unix.
Mächtigere Skriptsprachen sind beispielsweise Perl, PHP,
Python oder JavaScript.
• Skriptsprachen werden in der Regel interpretiert, nicht
kompiliert.
1.1 Sprachen und Paradigmen
17/38
Datenstrukturen und Typsysteme
Programmiersprachen bieten die Möglichkeit, aus elementaren
Datenbereichen mithilfe von Konstruktoren komplexe
Datenbereiche aufzubauen. Datenbereiche werden häufig
Datenstrukturen genannt.
• Elementare Datenbereiche
◦ boolean, char, cardinal, integer, real, enumeration, . . .
• Wertebereiche, Operationen
• Konstruktoren
◦ array, record, set, pointer, . . .
• Typäquivalenz, Typanpassung, Typkompabilität, . . .
Alle Aspekte, die die Datenbereiche einer Programmiersprache
betreffen, werden als deren Typsystem bezeichnet.
1.1 Sprachen und Paradigmen
18/38
Paradigmenübergreifende Konzepte (Auswahl)
Die folgende Liste enthält einige paradigmenübergreifende
Konzepte. Nicht jeder Punkt ist für jedes Paradigma relevant.
Beispielsweise benötigt die prädikative Sprache Prolog keine
Ablaufsteuerung.
• Ablaufsteuerung
• Unterprogramme, Module, abstrakte Datentypen
• Ausnahme- und Ereignisbehandlung
• Annotationen
• Programmierung randomisierter Algorithmen
• Programmierung nichtdeterministischer Algorithmen
• Programmierung paralleler und verteilter Algorithmen
• GUI-, Echtzeit-, Netzwerk-, Datenbank-, ... -programmierung
1.1 Sprachen und Paradigmen
19/38
Prinzipien des Sprachentwurfs
• Effizienz
• Allgemeingültigkeit
• Orthogonalität
• Uniformität
• Einfachheit
• Ausdruckskraft
• Genauigkeit
• Maschinenunabhängigkeit
• Sicherheit
• Konsistenz mit anerkannten Konventionen
• Erweiterbarkeit
• Einschränkbarkeit
1.1 Sprachen und Paradigmen
20/38
1. Einführung
1.1 Sprachen und Paradigmen
1.2 Definition von Programmiersprachen
1.3 Implementierung von Programmiersprachen
1.2 Definition von Programmiersprachen
21/38
Definition von Programmiersprachen
Bestandteile einer Programmiersprache:
• Lexik,
• Syntax,
• Semantik.
Die Pragmatik einer Programmiersprache untersucht ihre
Anwendbarkeit und Nützlichkeit. Sie gehört nicht zur Definition
der Sprache.
1.2 Definition von Programmiersprachen
22/38
Lexik
Im Lexikon findet man:
• Lexem (griechisch) das, Sprachwissenschaft: kleinste
semantische Einheit, Träger der lexikalischen Bedeutung; das
Lexem tritt als Einzelwort (z. B. Wald), als Teil eines Wortes
(z. B. wald- in waldig) und als Wortverbindung auf (z. B.
Waldbrand).
• Lexik die, der Wortschatz einer Sprache.
Die Lexik einer Programmiersprache bestimmt die textuellen
Grundbausteine der Programme. Solche Bausteine sind etwa
Schlüsselwörter und Bezeichner. Sie werden z. B. durch Aufzählung
oder reguläre Ausdrücke angegeben. Lexeme einer
Programmiersprache können aus mehr als einem Zeichen bestehen.
In Programmiersprachen werden Lexeme auch Token genannt.
1.2 Definition von Programmiersprachen
23/38
Syntax
• Die Syntax einer Programmiersprache beschreibt, wie aus den
Grundbausteinen vollständige Programme gebildet werden
können.
• In den meisten Fällen wird die Syntax durch eine kontextfreie
Grammatik festgelegt.
• Eine kontextfreie Grammatik G = (VN , VT , P, S) besteht
aus einem Nichtterminalalphabet VN , einem Terminalalphabet
VT , einer Produktionenmenge P und dem Startsymbol S. Mit
L(G) bezeichnen wir die von der kontextfreien Grammatik G
erzeugte Sprache.
• Kontextfreie Grammatiken können durch Syntaxdiagramme
grafisch dargestellt werden.
1.2 Definition von Programmiersprachen
24/38
Syntax
• Programmiersprachen sind i. Allg. kontextsensitiv und nicht
kontextfrei.
• Beispielsweise kann durch eine kontextfreie Grammatik nicht
ausgedrückt werden, dass jeder Bezeichner vor seiner
Benutzung deklariert werden muss.
• Andere Formalismen, zum Beispiel zweischichtige oder
attributierte Grammatiken, ermöglichen es, kontextsensitive
Aspekte in die Definition der Syntax einer
Programmiersprache einzubeziehen.
1.2 Definition von Programmiersprachen
25/38
Semantik
• Die Bedeutung der syntaktisch korrekten Programme ist durch
die Semantik der Sprache gegeben. Sie kann beispielsweise
mithilfe von Zuständen definiert werden. Man spricht dann
von einer operationellen Semantik.
• In der denotationalen Semantik werden den syntaktischen
Einheiten Funktionen zugeordnet. Hier spielen vollständige
Halbordnungen, stetige Abbildungen und Fixpunkte eine
wichtige Rolle.
• Weitere Möglichkeiten sind die axiomatische Semantik
(Programmverifikation, Hoare-Kalkül, Spezifikationssprachen)
und die algebraische Semantik.
Näheres erfahren Sie in der Veranstaltung
„Semantik von Programmiersprachen“.
1.2 Definition von Programmiersprachen
26/38
Beispiel: Lexik
• Eine Programmiersprache enthält endlich viele
Schlüsselwörter. Sie können durch Aufzählung angegeben
werden:
if, then, else, begin, end, while, do, od, ...
• Die Menge der Bezeichner einer Programmiersprache kann
zum Beispiel durch die folgenden regulären Ausdrücke
definiert werden:
Ziffer = {0, 1, ..., 9}
Buchstabe = {a, b, c, ..., y , z}
Bezeichner = Buchstabe · (Buchstabe | Ziffer)∗
1.2 Definition von Programmiersprachen
27/38
Beispiel: Syntax
Die folgenden Zeilen enthalten einige Produktionen einer
kontextfreien Grammatik G zur Beschreibung einer kleinen
Programmiersprache in der sog. Backus-Naur-Form:
<Anweisungsfolge> ::=
<Anweisung> ; <Anweisungsfolge> | <Anweisung>
<Anweisung>
::=
<Zuweisung> | <While-Anweisung> | ...
<Zuweisung>
::=
<Bezeichner> := <arithmetischer Ausdruck>
<While-Anweisung> ::=
while <logischer Ausdruck> do <Anweisungsfolge> od
1.2 Definition von Programmiersprachen
28/38
Beispiel: Operationelle Semantik
• Zunächst werden Zustände z ∈ Z definiert. Hierbei kann es
sich um die Zustände eines abstrakten Automaten handeln. In
unserem Beispiel ist Z = {z | z : V → Z}, wobei V die Menge
aller Variablen ist.
• Die Semantik ordnet jedem syntaktisch korrektem Programm
P ∈ L(G) eine partielle Funktion (Zustandstransformation)
M [P ] : Z −
→Z
p
als seine Bedeutung zu. M hat also die Gestalt
M : L → (Z −
→ Z ).
p
Neben dem operationellen Ansatz gibt es weitere
(s. Vorlesung „Semantik von Programmiersprachen“).
1.2 Definition von Programmiersprachen
29/38
Beispiel: Algorithmus von Euklid
Der folgende in der obigen imperativen Sprache formulierte
Algorithmus von Euklid (ca. 300 v. Chr.)
berechnet den größten gemeinsamen Teiler der Zahlen x , y ∈ N
mit x ≥ 0 und y > 0:
a := x;
b := y;
while b
do r :=
a :=
b :=
od
# 0
a mod b;
b;
r
Nach Ausführung des Programmfragments gilt a = ggT(x , y ).
1.2 Definition von Programmiersprachen
30/38
Beispiel: Algorithmus von Euklid
Es seien x = 36 und y = 52:
Variable
r
a
b
z0
–
–
–
z1
–
36
–
z2
–
36
52
z5
36
52
36
z8
16
36
16
z11
4
16
4
z14
0
4
0
Das Zeichen – bedeutet, dass der Wert dieser Variablen im
betreffenden Zustand irrelevant ist.
ggT(36, 52) = 4
Durchlaufene Zustände:
z0 , z1 , z2 , ... , z14 ∈ Z
Es gilt M [P ](z0 ) = z14 .
1.2 Definition von Programmiersprachen
31/38
Sprachreport
• Die Definition einer Programmiersprache erfolgt oft in Form
eines sog. Sprachreports.
• Dieser enthält in der Regel eine kontextfreie Grammatik zur
Beschreibung der Lexik und der Syntax. Die Produktionen
werden häufig in der Backus-Naur-Form oder einer Varianten
davon angegeben.
• Darüber hinaus werden die Produktionen meistens grafisch in
der Form eines Syntaxdiagramms veranschaulicht.
• Die Semantik wird in der Regel informell, in einigen Fällen
auch halbformal oder sogar formal definiert.
1.2 Definition von Programmiersprachen
32/38
1. Einführung
1.1 Sprachen und Paradigmen
1.2 Definition von Programmiersprachen
1.3 Implementierung von Programmiersprachen
1.3 Implementierung von Programmiersprachen
33/38
Klassifikation der Programmiersprachen
Die Programmiersprachen lassen sich grob in drei Klassen einteilen:
• Maschinensprachen
Bits und Bytes, für den menschlichen Leser kaum verständlich
• Maschinenorientierte Sprachen (Assembler)
stellen die Befehle in einem Mnemo-Code dar
ADDIC 23, R0
STO R0, #12004
• Problemorientierte Sprachen
imperative, funktionale, objektorientierte, logische Sprachen,
Spezialsprachen
Ein Computer versteht nur Maschinensprachen!
1.3 Implementierung von Programmiersprachen
34/38
Implementierung von Programmiersprachen
Compiler übersetzen Quellprogramme aus problemorientierten
Sprachen in äquivalente Zielprogramme in Maschinensprachen:
cc -o prog prog.c
prog input output
Interpreter lesen das Programm zusammen mit den Eingabedaten
ein und führen es aus:
scm prog.scm input output
Mischverfahren übersetzen das Programm zunächst mit einem
Compiler in eine Zwischensprache. Das übersetzte Programm wird
anschließend interpretiert:
javac prog.java
java prog input output
1.3 Implementierung von Programmiersprachen
35/38
Implementierung von Programmiersprachen
Interpreter müssen das Programm bei jedem Lauf erneut
analysieren. Dies bedeutet einen gewissen Effizienzverlust.
Typisch, aber nicht zwingend:
• Compiler: C
• Interpreter: Scheme
• Mischverfahren: Java
• Mehrere Angebote gleichzeitig: z. B. Haskell (ghc, ghci)
Näheres zu Aufbau und Arbeitsweise dieser Programme erfahren
Sie in den Veranstaltungen „Compiler I“, „Compiler II“ sowie im
„Compilerbaupraktikum“.
1.3 Implementierung von Programmiersprachen
36/38
Verarbeitung von Java-Programmen
Java−Quellprogramm
javac
Java−Bytecode
java
VM für Windows
java
VM für Linux
• Zuerst wird ein
Quellprogramm vom
Compiler in Bytecode
übersetzt.
1.3 Implementierung von Programmiersprachen
• Im zweiten Schritt wird der
Bytecode vom Interpreter
ausgeführt. Der Bytecode
kann als Maschinencode
der sogenannten virtuellen
Java-Maschine (JVM)
angesehen werden.
Bytecode ist portabel.
• Der Compiler ist
maschinenunabhängig, der
Interpreter muss für jede
Plattform neu entwickelt
werden.
37/38
Verarbeitung von Java-Programmen
• Interpretierter Code ist langsamer in der Ausführung als
kompilierter Code, selbst wenn dieser als Bytecode vorliegt.
• Prinzipiell können Java-Programme auch in Maschinensprache
übersetzt werden. Dann geht allerdings die Portierbarkeit
verloren.
• Eine Alternativlösung bieten Just-in-Time-Compiler (JIT).
Ein JIT ist ein Programm, das den Bytecode einzelner
Methoden während der Ausführung in Maschinencode der
jeweiligen Plattform übersetzt. So kann die Methode beim
nächsten Aufruf deutlich schneller ausgeführt werden.
Vorteilhaft ist, dass der Bytecode nicht verändert wird und
damit das übersetzte Programm portabel bleibt.
1.3 Implementierung von Programmiersprachen
38/38
Herunterladen