6.1 Nutzen von Sprachmitteln
Die hier vorgeführten Datenstrukturen (Feld, Liste,
...) sind für sich genommen nutzlos. Erst durch die Kombination mit
wirklichen Nutzdaten aus einer zu lösenden Problemstellung werden die
Strukturen sinnvoll.
Um nicht jede Datenstruktur und die darauf operierenden Algorithmen
für jeden möglichen Typ von Nutzdaten (int, double, Student, ...) neu implementieren zu müssen (FeldInt, FeldDouble, FeldStudent, ..., ListeInt, ...),
strebt man ein möglichst generisches
Programmieren an6.1.
Dieses Ziel kann je nach Programmiersprache und damit je nach
Denkweise auf verschiedenen Wegen erreicht werden. Auch wenn der
Algorithmus selbst davon nicht beeindruckt sein sollte (er ist ja per
Definition von der Implementation unabhängig), ist es doch
ungeschickt, einen Algorithmus so zu definieren, daß er bei der später
stattfindenden Implementation schwierig oder uneffektiv umzusetzen
ist. Auch wenn sich viele Programmiersprachen in einfachen Punkten
sehr ähneln (Schleifen, Vergleiche), haben sie doch individuelle Sprachmittel, die man nutzen
kann - oder eben nicht, wenn man für die Implementation eines
gegebenen Algorithmus die falsche Sprache wählt (falls man die Wahl
überhaupt hat).
Wichtige Beispiele für solche Sprachmittel sind:
- Strukturierung von Daten im Speicher (nicht in Fortran 77; Pascal:
RECORD,
C/C++: struct, Java: class)
- Verwaltung verschiedener Varianten (Kundenversionen,
ausführliche/knappe Ausgaben während der Entwicklung oder danach,
Umgehen von Systemabhängigkeiten, etc.) in einem Quelltext durch
Verwendung eines Präprozessors (C/C++: standardisiert, für ein
Beispiel siehe die MusterlösungC++-Version; andere durch
externe Präprozessoren wie m4 je nach Entwicklungsumgebung möglich)
- Übergabe von Daten an Unterprogramme als Kopien
(call by value,
Fortran: nein, Pascal/C/C++: ja, Java: nur bei
elementaren Datentypen)
- Übergabe von Daten an Unterprogramme als
Referenzen (call by reference,
Fortran/Pascal/C++: ja, Java: nur bei
Objekten), oder als Zeiger (C)
- Möglichkeiten, Daten im Speicher auf verschiedene Arten zu
interpretieren (beispielsweise 32 Bit abwechselnd als ganze Zahl,
als Folge von 32 ja/nein-Werten und als Gleitkommazahl); dies ist
beispielsweise bei genetischen Algorithmen (Genetische
Algorithmen, Evolutionsstrategie) oder beim
Datenaustausch mit fremden Rechnerarchitekturen (Meßtechnik)
hilfreich, aber generell nicht portabel
(Fortran: mit EQUIVALENCE, Pascal: variante RECORDS,
C/C++: mit union oder casts mit Zeigern, Java: nein)
- Unterstützung von streamorientierten Dateien (nicht in
Java-Applets)
- Unterstützung von positionierbaren Dateien
(Direktzugriffsdateien,
random access file,
Pascal: nein, Fortran/C/C++/Java: ja, aber nicht in Java-Applets)
- Unterstützung von Binärdateien
(unformatierte Dateien),
bei denen Daten nicht in ihrer
Textrepräsentation, sondern in der rechnerinternen Darstellung
abgelegt werden
(Pascal: nein, Fortran/C/C++/Java: ja, aber nicht in Java-Applets)
- Festlegen von Feldlängen erst zur Laufzeit (Fortran/Pascal:
nein, C/C++: mithilfe von freiem Speicher, Java: ja)
- Objektorientiertes Programmieren, Überladen von Methoden,
Vererbung (Fortran/Pascal/C: nein,
C++/Java: ja)
- Möglichkeiten, das Anlegen oder Auflösen von Objekten zu
beeinflussen (Konstruktoren, Destruktoren; C++: ja, Java: ja mit
Einschränkungen beim Auflösen)
- Überladen von Operatoren (Fortran/Pascal/C/Java: nein, C++: ja)
- Schablonen/templates
für Unterprogramme und Daten (Fortran/Pascal/Java: nein, C: über
Makros möglich, C++: ja)
- Die Möglichkeit, komplexe Datenstrukturen als Funktionsergebnis
zu liefern (Fortran: nein, Pascal/C/C++/Java: ja)
- Ausnahmebehandlung (mehr oder weniger geordneter
Rücksprung zum Aufrufer im Falle eines
Fehlers oder anderer besonderer Umstände, Fortran/Pascal: nein, C:
bedingt über longjmp(), C++/Java: ja)
- Freies Allokieren von Speicher (Fortran: nein, C/C++: ja,
Pascal/Java: bedingt)
- Automatische Freigabe von allokiertem Speicher
(garbage collection, C/C++/Pascal:
nein, Java: ja)
- Übergabemöglichkeit von Funktionalitäten an Unterprogramme (Funktionen
beziehungsweise ihre Adressen oder Funktionsobjekte; Fortran/Pascal/C/C++/Java: ja)
- Unterstützung von Nebenläufigkeit
(threads, Fortran/Pascal: nein, C/C++: ja, aber
nicht genormt, Java: ja)
- Standardisierte/portable Bibliotheken für Benutzeroberflächen,
IPC, Datenbankzugriff, ...
(Neben den erwähnten Sprachen gibt es natürlich noch eine Unmenge
weiterer, deren Nichtnennung keine Wertung sein soll: Forth, Smalltalk, LISP, Perl,
Pearl, SQL, diverse Assembler, OCCAM, Spezialsprachen wie lex,
yacc, awk, sed, .... Auf alle Sprachen
gerecht vergleichend einzugehen, würde den Rahmen dieses Skripts
völlig sprengen.)
Zusätzlich zu den genannten Ausdrucksmöglichkeiten einer Sprache ist
natürlich auch wichtig, wie die hinter der Sprache stehende Denkweise
zum Problem und zum Algorithmus paßt (prozedural oder
beschreibend/symbolisch, 4GL).
Um diese Unterschiede in den Sprachen und die dadurch zur Verfügung
stehenden Implementationsmöglichkeiten zu veranschaulichen, wird im
Folgenden ein Feld betrachtet, in welchem anhand eines
gegebenen Suchschlüssel ein Feldelement gesucht werden soll.
Jedes Feldelement besteht aus einem Vornamen, einem Nachnamen, und
einer ganzzahligen Kennung (beispielsweise die Matrikelnummer eines
Studenten).
Um den Kern dieses Abschnitts möglichst klar zu zeigen, wird der
einfachste dafür mögliche Algorithmus verwendet, nämlich
lineare Suche: das Feld wird Element für
Element anhand des Suchkriteriums (also Vergleich mit dem
Suchschlüssel) verglichen. Bei einer Übereinstimmung wird die Suche
beendet und als erfolgreich betrachtet. Wurden alle Elemente geprüft,
ohne daß eine Übereinstimmung festgestellt werden konnte, dann wird
die Suche als nicht erfolgreich beendet.
Abbildung 6.1:
Struktogramm lineare Suche
|
TODO: Quelltexte kommentieren und vergleichen!
Unterabschnitte
www.wachtler.de