admin 管理员组文章数量: 1086019
2024年1月24日发(作者:form表单提交方法)
Programmiersprachen
•
•
Programm = Text mit Anweisungen für einen Computer;
Widersprüchliche Anforderungen an Formulierung eines Programms: Soll einerseits
für den Menschen lesbar sein, andererseits von einer Maschine verarbeitbar;
Anforderungen für Lesbarkeit durch Menschen:
o
nahe an gewohnter Umgangssprache (z.B. Englisch),
o
übersichtliche Struktur,
o
kompakte Notation,
o
etc.
•
•
Anforderungen für Ausführbarkeit durch Maschinen:
o
präzise und eindeutig,
o
leicht in Strukturen der Maschine umsetzbar,
o
etc.
•
Ergebnis: Programmiersprachen mit folgenden Eigenschaften
o
Umgangssprachliche Komponenten (Wörter wie "if", "while" und "main"),
o
Notation teilweise angelehnt an mathematische Schreibweise,
o
Gliederung in Zeilen mit Einrückung,
o
formale Syntax, d.h. kein Auslegungsspielraum
o
maschinen-nahe Grundbausteine (ganze Zahlen)
2.1 Generationen
Programmiersprachen haben sich über mehrere Generationen hinweg entwickelt:
Maschinensprachen (1. Generation)
Die Sprachen der 1. Generation sind Maschinensprachen, deren Elemente direkt aus dem
Befehlsvorrat bestimmter Computer abgeleitet sind. Maschinensprache ist spezifisch für
genau einen Prozessor und ein Betriebssystem und damit in höchstem Maße unportabel.
Maschinenprogramme werden heute nicht mehr direkt geschrieben.
Assemblersprachen (2. Generation)
Assemblersprachen, in denen Maschinenbefehle durch leichter lesbare Abkürzungen ersetzt
sind. Immer noch diktiert die Zielmaschine die Struktur der Sprache. Assemblerprogramme
werden heute nur noch dann geschrieben, wenn es aus zwingenden Gründen notwendig ist.
Höhere Programmiersprachen (3. Generation)
Sprachen der 3. Generation (auch genannt "höhere Programmiersprachen" oder
"problemorientierte Programmiersprachen") orientieren sich an den zu lösenden Problemen
und nicht mehr an den benutzten Maschinen. Die meisten zeitgemäßen
Programmiersprachen fallen in diese Kategorie. Dazu zählen "C" und "C++", die
Programmiersprachen, die in dieser Vorlesung gelehrt wird.
Spezialsprachen (4., 5. Generation?)
Schließlich werden manche Sprachen einer 4. oder 5. Generation zugeordnet. Diese
Sprachen sind auf bestimmte Anwendungen und Problemklassen zugeschnitten und können
nicht mehr als "general purpose languages" bezeichnet werden.
2.2 Eigenschaften von Programmiersprachen
Eigenschaften von Programmiersprachen werden auf verschiedenen Ebenen festgelegt;
Syntax
Die Syntax regelt die "Rechtschreibung" einer Programmiersprache. Sie läßt sich formal
festlegen, z.B. mit Syntaxdiagrammen oder in einer Grammatik.
Semantik
Die Semantik regelt die Bedeutung einzelner Sprachelemente und ihr Zusammenspiel. Sie
läßt sich viel schwieriger exakt festhalten. Die meisten Versuche, Semantik formal zu fixieren,
mündeten bisher in schwer lesbaren oder sehr umfangreichen Beschreibungen. Unabhängig
von der Beschreibung ist das Thema Semantik noch wesentlich komplexer als hier
angedeutet. So gibt es z.B. eine statische und eine dynamische Semantik, von denen erstere
bis zu einem gewissen Grad automatisch überprüft werden kann, während letztere erst zur
Laufzeit zum Tragen kommt.
Pragmatik
Die Pragmatik beschreibt Einschränkungen und Randbedingungen, die sich aus konkreten
Implementierungen heraus ergeben.
Die Grenzen zwischen Syntax und Semantik, sowie zwischen Semantik und Pragmatik sind
verschwommen und lassen sich nicht eindeutig ziehen.
2.3 Formalismen zur Beschreibung der Syntax
Zur Definition der exakten Syntax von Programmiersprachen kommen verschiedene
Hilfsmittel zum Einsatz. Die beiden wichtigsten (Syntaxdiagrammen und Grammatiken)
werden hier gezeigt.
2.3.1 Syntaxdiagramme
Syntaxdiagramme sind halbgraphische Dastellungen, die gegenüber Grammatiken
übersichtlicher und leichter lesbar sind.
2.3.1.1 Aufbau
Einzeldiagramme
Um die Syntax einer Sprache mit einem Syntaxdiagramm zu beschreiben, werden mehrere
Einzeldiagramme verwendet. Das dient der Übersichtlichkeit, ist aber aus rein formaler Sicht
nicht zwingend notwendig. Alle Einzeldiagramme zusammen bilden ein komplettes
Syntaxdiagramm. Jedes Einzeldiagramm hat einen eindeutigen Namen.
Pfeile
Jedes Einzeldiagramm enthält Knoten, die mit Pfeilen verbunden sind. Die Pfeile regeln die
Abfolge der Knoten. Pfeile können sich innerhalb eines Einzeldiagramms gabeln und
zusammengeführt werden.
Terminale Knoten
Knoten werden als Kästen gezeichnet. Es gibt zwei Arten von Knoten: terminale und
nichtterminale Knoten. Terminale Knoten enthalten wörtlich zu nehmende Texte. Sie
werden durch Kreise, Ovale oder Kästen mit abgerundeten Ecken dargestellt.
Nichtterminale Knoten
Nichtterminale Knoten benennen ein anderes Einzeldiagramm, das an dieser Stelle
durchlaufen werden muß. Nichtterminale Knoten werden durch Kästen mit normalen Ecken
dargestellt. Man kann sich vorstellen, daß das benannte Einzeldiagramm anstelle des
nichtterminalen Knotens eingehängt wird.
2.3.1.2 Anwendung
Ein Text ist (im Sinne des Syntaxdiagrammes) korrekt, wenn er mit einem kompletten
Durchlauf des Syntaxdiagrammes vollständig nachvollzogen werden kann. Dabei darf am
Ende weder ein Rest des Textes noch ein Rest des Syntaxdiagrammes unverarbeitet
übrigbleiben.
Es kommt nur darauf an, daß es irgendeinen Weg durch das Syntaxdiagramm gibt, der zum
Text paßt. Es spielt keine Rolle, welche Abzweigungen dabei genommen werden.
2.3.1.3 Beispiel
Das folgende Beispiel zeigt das Syntaxdiagramm zur Beschreibung der Syntax von ganzen
Zahlen in "C":
Die Zeichenfolge +230859 entspricht z.B. dem Syntaxdiagramm, die Zeichenfolge 3.141592
dagegen nicht.
2.3.2 Grammatik in "EBNF"
Grammatiken werden in unterschiedlichen Notationen formuliert. Eine sehr verbreitete
Notation ist die "EBNF" (= "Erweiterte Backus-Naur Form"). Die EBNF ist gegenüber
Syntaxdiagrammen kompakter und als reine Textdarstellung leichter maschinell zu
verarbeiten.
Seltener verwendet wird die ursprüngliche, einfache "BNF" (= "Backus-Naur Form"). Dieser
fehlen gegenüber der EBNF ein paar bequeme, abkürzende Schreibweisen; mit beiden
Formen lassen sich aber dieselben Grammatiken wiedergeben.
2.3.2.1 Zusammenhang mit Syntaxdiagrammen
Mit einer Grammatik in EBNF wird die korrekte Syntax einer formalen Sprache beschrieben,
genauso wie mit Syntaxdiagrammen.
Im Gegensatz zu Syntaxdiagrammen erlaubt eine EBNF nicht alle Verbindungen und erzwingt
damit strengere Auflagen. Das Ergebnis sind zwangsläufig besser strukturierte Grammatiken.
Demnach kann zwar jede EBNF als Syntaxdiagramm formuliert werden, das Umgekehrte gilt
nicht in jedem Fall.
2.3.2.2 Aufbau und Metasyntax
Produktionen
Eine EBNF besteht aus einer Liste von Produktionen. Jede Produktion beschreibt die Syntax
eines bestimmten Grammatikfragmentes. Produktionen werden als eine Art Gleichungen
geschrieben. Auf der linken Seite steht ein Name für das definierte Grammatikfragment; auf
der rechten Seite steht eine Folge von Symbolen, die den Aufbau des Grammatikfragmentes
festlegen. Zwischen linker und rechter Seite wird das Trennzeichen ::= gesetzt. Das Ende
einer Produktion wird mit einem Punkt gekennzeichnet, also schematisch:
linke Seite ::= rechte Seite .
Terminale und Nichtterminale
Von den Symbolen, die auf der rechten Seite einer Produktion vorkommen, gibt es zwei
Arten:
•
•
Die Terminale stehen für sich selbst, sie sind wörtlich zu nehmen.
Die Nichtterminale benennen eine andere Produktion, die an dieser Stelle
einzusetzen ist.
Zur Unterscheidung werden Terminale oft in Gänsefüßchen gesetzt, Nichtterminale
unterstrichen oder einfach überhaupt nicht markiert.
Optionale Symbolfolgen
Um eine Reihe von Symbolen auf der rechten Seite einer Produktion als optional zu
kennzeichnen, wird diese in eckige Klammern gesetzt.
Wiederholbare Symbolfolgen
Eine Symbolfolge wird in geschweifte Klammern gesetzt, wenn sie beliebig oft wiederholt
werden darf. Das schließt Weglassen, d.h. null-maliges Wiederholen, mit ein. Das Beispiel für
Syntaxdiagramme als EBNF:
integer ::= [sign] digit {digit}.
In diesem Zusammenhang ist eine alternative Notation üblich, die in der EBNF eigentlich
nicht vorgesehen ist: Hinter die geschweifte Klammer kann auch ein Plus-Zeichen gesetzt
werden, um ein- oder mehrmalige Wiederholung auszudrücken. Das obige Beispiel würde
dann kürzer lauten:
integer ::= [sign] {digit}+.
Um die ursprüngliche, beliebige Wiederholung davon klar abzuheben, wird diese dann
ausdrücklich mit einem Stern nach der geschweiften Klammer markiert. Die folgende
Schreibweise ist umständlicher, aber inhaltlich gleichwertig mit der vorhergehenden:
integer ::= [sign] digit {digit}*.
Alternativen
Wenn mehrere Symbolfolgen auf der rechten Seite einer Produktion zur Auswahl stehen,
werden die Möglichkeiten nacheinander aufgeführt und mit senkrechten Strichen getrennt.
Das folgende Beispiel zeigt die komplette EBNF für eine integer-Konstante in "C"-Syntax:
integer ::= [sign] digit {digit}.
sign ::= "+" | "-".
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
Gruppierung
Symbolfolgen können mit einfachen runden Klammern gruppiert werden, um Alternativen
einzugrenzen. Auch die anderen Klammer-Arten (eckige und geschweifte) können so benutzt
werden. Das folgende Beispiel ist zwar ungeschickt, illustriert aber diese Möglichkeit:
integer ::= [sign]
("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")
{digit}.
sign ::= "+" | "-".
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
Metasyntax
Die EBNF folgt einer eigenen Syntax, die mit der durch die EBNF beschriebenen Syntax nichts
zu tun hat. Man nennt die EBNF-eigene Syntax ihre "Metasyntax". Die Metasyntax der EBNF
kann fast in ihrem eigenen Formalismus beschrieben werden:
ebnf ::= {production}.
production ::= leftside "::=" rightside ".".
leftside ::= nonterminal.
rightside ::= alternative {"|" alternative}.
alternative ::= {sequence}.
sequence ::= "{" rightside "}" |
"[" rightside "]" |
"(" rightside ")" |
symbol.
symbol ::= terminal | nonterminal.
terminal ::= doublequote text doublequote.
nonterminal ::= identifier.
identifier ::= letter {letter | digit}.
Die folgenden Nichtterminale sind nicht mehr aufgelöst:
•
•
•
•
letter steht für einen Buchstaben;
digit steht für eine Ziffer;
text steht für eine Folge von beliebigen Zeichen außer Gänsefüßchen;
doublequote steht für das Terminal mit Gänsefüßchen als Inhalt;
2.3.2.3 Beispiel Dateispezifikation
Die folgende EBNF beschreibt die Syntax eines Pfadnamens einer Datei, wie er von vielen
Unix-Kommandos erwartet wird:
filespec ::= [root] {dirspec} name.
root ::= "/".
dirspec ::= (name | "." | "..") "/".
name ::= {char}+.
char ::= letter | digit | "_" | "+" | "-" | ":".
Anmerkung: Die konkrete Auswahl der in Dateinamen zulässigen Sonderzeichen hängt von
der Shell und deren Konfiguration ab.
2.3.2.4 Syntax von "C"
Die komplette Syntax von "C" in EBNF-Notation finden Sie z.B. in [KR].
2.4 "C" und "C++"
2.4.1 Historische Entwicklung
Diese Vorlesung führt die Programmiersprachen "C" und "C++" ein, die beide zu den
höheren Programmiersprachen (im Sinne der obigen Klassifizierung) zählen.
"C" hat sich ursprünglich einen Namen als Sprache zur Systemprogrammierung gemacht
(Implementierung von "Unix"). Ein erster Meilenstein in der Entwicklung von "C" war 1983
die Veröffentlichung des Buches "The C Programming Language" (Kernighan/Ritchie), das
für Jahre eine Art Pseudo-Standard festlegte. Mit "ANSI-C" wurde die Sprache 1988 offiziell
standardisiert, so daß heute praktisch alle "C"-Compiler den gleichen Sprachumfang
beherrschen.
"C" ist seiner Art nach eine Programmiersprache aus der Ära der "strukturierten
Programmierung", wie auch Pascal, Modula, u.a..
"C++" wurde als Nachfolger von "C" geschaffen mit dem erklärten Ziel objektorientierte
Programmierung zu erschließen. Wie bei "C" machte "C++" einige Entwicklungsstufen durch,
von denen eine erste 1990 mit der Veröffentlichung des Buches "The Annotated C++
Reference Manual" (Ellis/Stroustrup) ("the ARM") markiert wird. 1995 wurde der Draft-Standard für "ANSI-C++" verabschiedet. Dieses Dokument ist zwar noch keine endgültige
Sprachnorm, tiefgreifende Änderungen sind aber nicht mehr zu erwarten.
"C++" ist eine objektorientierte Programmiersprache, die weitgehend aufwärtskompatibel
zu "C" bleibt (d.h. daß "C"-Programme mit wenigen Modifikationen auch von "C++"-Compilern akzeptiert werden).
2.4.2 Einsatz in der Vorlesung
Die ersten Themen der Vorlesung sind neutral gegenüber "C" und "C++".
Mit "I/O", homogenen und heterogenen zusammengesetzten Datenstrukturen (Strings,
Vektoren und Strukturen bzw. Klassen) werden aber Themen erreicht, die sich in "C++"
wesentlich sicherer und eleganter behandeln lassen. Ab hier orientiert sich die Vorlesung an
"C++".
Erst in der Nachfolgeveranstaltung "Programmieren II" kehren wir zu "C" zurück. Zu diesem
Zeitpunkt haben Sie die notwendigen Vorkenntnisse, um die inneren Abläufe von "C"
nachvollziehen zu können. Diese sind letztlich auch für "C++" maßgeblich, werden dort aber
gut abgeschirmt.
Alle Ausführungen beziehen sich zunächst auf ANSI-C. Sofern Spracheigenschaften
besprochen werden, die nur in ANSI-C++ (aber nicht in ANSI-C) erlaubt sind, weist diese
Markierung darauf hin.
Während die Syntax der Sprachen nicht allzu schwer zu erlernen ist, sind die Kenntnis der
Semantik und Pragmatik von C(++) eines der wesentlichen Lernziele dieser Vorlesung.
Die Syntax von C(++) läßt sich in einer Grammatik festschreiben, die z.B. in der EBNF verfaßt
sein kann.
Die Semantik ist meist umgangssprachlich beschrieben und macht den größten Teil des
Inhaltes der weitverbreiteten Literatur aus. Ein Beispiel für die Semantik ist z.B. die
Forderung, daß jeder Bezeichner definiert bzw. deklariert sein muß, bevor er verwendet
werden kann.
Die Pragmatik der Sprachen findet sich oft in der Dokumentation von einzelnen
Implementierungen, wie z.B. Compiler-Handbüchern oder "Release Notes". Ein Beispiel für
die Pragmatik ist die Begrenzung des Wertebereiches des Datentyps int, die von bestimmten
Rechnerarchitekturen auferlegt wird.
2.5 Übersetzen und Ausführen von Programmen
Ungeachtet aller höheren Programmiersprachen können Computer nur
Maschinenprogramme ausführen. Alle anderen Sprachen müssen erst in ein
Maschinenprogramm umgeformt ("übersetzt") werden, bevor sie ausgeführt werden
können.
Diese Übersetzung ist bei Sprachen wie "C" oder "C++" verhältnismäßig aufwendig. Sie läßt
sich nicht mit vertretbarem Aufwand von Hand erledigen. Statt dessen werden die Sprachen
mit spezialisierten Hilfsprogrammen, sogenannten "Übersetzern" oder "Compilern",
automatisch in Maschinensprache transformiert. Compiler sind selbst sehr komplexe
Programme, deren Konstruktion ein eigenes Lehrgebiet ausmacht.
Programm-Text wird "Quelltext" genannt (engl. source) und ist, für sich gesehen, nichts
anderes als lesbarer Klartext. Das vom Compiler aus dem Quelltext automatisch produzierte
und dann ausführbare Maschinenprogramm heißt "übersetztes Programm" oder
"Binärprogramm" (engl. binary oder executable). Der Begriff des "Programms" wird sowohl
für Quelltext, wie auch für übersetzte Programme benutzt. Ein Binärprogramm ist nicht
mehr lesbar und i.d.R. wesentlich umfangreicher als der ursprüngliche Quelltext. Soweit der
Compiler fehlerfrei arbeitet, verhält sich das Binärprogramm genau so, wie es im Quelltext
beschrieben steht. (Daß sich das nicht immer mit den Vorstellungen des Programmierers
deckt, ist eine andere Sache.)
Compiler erwarten kompletten und korrekten Quelltext, bevor sie ein Binärprogramm
produzieren können. Bei umfangreichen Quelltexten wird die für die Übersetzung
aufzuwendende Zeit spürbar, so daß "Interpreter" entwickelt wurden. Interpreter verlangen
keinen kompletten Quelltext, sondern lesen Quelltext Schritt für Schritt und führen jeden
gelesenen Schritt sofort aus. Damit kann z.B. der Anfang eines Programms schon ausprobiert
werden, bevor das Ende überhaupt fertig ist. Der Preis für diese gewonnene Flexibilität ist
gegenüber einem Binärprogramm reduzierte Ablaufgeschwindigkeit, weil Compiler
Informationen aus dem gesamten Programm für Optimierungen verwenden können.
Darüber hinaus eignen sich nicht alle Programmiersprachen (zu ihnen zählen auch "C" und
"C++") von ihrem Aufbau her für schrittweise Ausführung, weil sich z.B. Programmteile an
weit auseinander liegenden Stellen gegenseitig beeinflußen.
Diese Skizze zeigt schematisch die Entwicklung eines Programmes:
(Schraffierte Kästen sind ausführbare Programme, Kästen ohne Schraffur sind Daten;)
2.6 Programm-Quelltext
Der Quelltext eines C(++)-Programm wird zunächst als Text geschrieben. Dabei sind einige
allgemeine Regeln zu beachten:
2.6.1 Zwischenräume und Leerzeilen
Unter dem Begriff "Zwischenraum" werden üblicherweise Folgen bestimmter
Kontrollzeichen verstanden:
•
•
•
Leerzeichen ("Space" oder "Blank"),
Tabulator ("Tab")
Zeilenwechsel ("Return" oder "Enter")
Zwischenräume werden benutzt, um Quelltext optisch (d.h. für den menschlichen Leser
übersichtlich) zu gliedern. Das Gesamt-Arrangement (Wort-Abstand, Einrückung, Leerzeilen)
wird unter dem Begriff "Layout" zusammengefaßt.
Notwendig aus der Sicht von C(++) ist Zwischenraum nur an wenigen, seltenen Stellen.
Überspitzt formuliert: Der Compiler ignoriert das Layout (weitgehend).
Es spielt keine Rolle, wie viel Zwischenraum eingeschoben wird. Ein einzelnes Leerzeichen ist
gleichwertig mit eintausend Leerzeilen.
Die Entwicklung eines persönlichen Programmierstils schlägt sich zu einem guten Teil in
einem einheitlichen Layout nieder. Dabei wird allgemein anerkannt, daß die konkrete Art
des persönlichen Stils eine untergeordnete Rolle spielt, wenn er nur konsequent
durchgehalten wird.
2.6.2 Groß- und Kleinschreibung
Große und kleine Buchstaben werden von C(++) . als unterschiedlich betrachtet.
2.6.3 Umlaute und Sonderzeichen
Auf vielen Tastaturen finden sich nationale Sonderzeichen. Aber längst nicht alle Compiler
können mit Sonderzeichen umgehen. Man sollte sich daher auf die Zeichen beschränken, die
auf US-amerikanischen Tastaturen zu finden sind.
Insbesondere mögen deutsche Umlaute oder etwa das Paragraphenzeichen zwar von einem
Compiler "geschluckt" werden, der nächste Compiler kann aber vollkommen unberechenbar
darauf reagieren.
2.6.4 Kommentare
Ebenfalls der besseren Lesbarkeit dienen Kommentare, d.h. Klartext-Fragmente, die vom
Compiler ignoriert werden.
Kommentare können auf zwei Arten eingefügt werden:
Längere Passagen
werden durch die Zeichenfolge /* eingeleitet und mit */ abgeschlossen. Art und Umfang des
eingebetteten Textes sind beliebig, nur kann die Zeichenfolge */ nicht Inhalt des
Kommentartextes sein. Ein Beispiel:
/* dieser Text
wird vom Compiler ignoriert.
*/
Derartige Kommentare können nicht geschachtelt werden, wie etwa im folgenden
(fehlerhaften) Versuch:
/* Anfang eines Kommentars
/* Fortsetzung */
kein Kommentartext mehr!
*/
Kurze Passagen
werden mit der Zeichenfolge // begonnen und enden automatisch mit der Zeile. (Dies ist
einer der wenigen Fälle, in denen ein bestimmtes Zwischenraumzeichen signifikant ist, d.h.
Einfluß auf die Bedeutung des Programms trägt.) Ein Beispiel:
int a; // Zweck dieser Variablen
Beide Kommentararten dürfen gemischt werden. Die Art des Kommentar-Anfangs regelt die
Art des Endes.
Über Regeln für sinnvolle Inhalt von Kommentaren gibt es lange und fruchtlose Debatten.
Ich persönlich möchte mich den folgenden Vorschlägen anschließen:
Comments are good, but there is also a danger of over-commenting. NEVER try to explain
HOW your code works in a comment: it's much better to write the code so that the working
is obvious, and it's a waste of time to explain badly written code.
Generally, you want your comments to tell WHAT your code does, not HOW. Also, try to
avoid putting comments inside a function body: if the function is so complex that you need
to separately comment parts of it, you should probably go back to chapter 4 for a while. You
can make small comments to note or warn about something particularly clever (or ugly), but
try to avoid excess. Instead, put the comments at the head of the function, telling people
what it does, and possibly WHY it does it.
(Auszug aus: Documentation/CodingStyle, Dokumentation der Linux-Kernelquellen)
FH München, FB 07 Informatik/Mathematik
Prof. Dr. R. Schiedermeier - Vorlesung "Programmieren I"
版权声明:本文标题:编程语言的德语介绍 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://roclinux.cn/b/1706093629a501664.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论