Dietmar Herrmann
C++ für Naturwissenschaftler Beispielorientierte Einführung
Bitte beachten Sie: Der originalen Printversion liegt eine CD-ROM bei. In der vorliegenden elektronischen Version ist die Lieferung einer CD-ROM nicht enthalten. Alle Hinweise und alle Verweise auf die CD-ROM sind ungültig.
An imprint of Pearson Education Deutschland GmbH München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich.
Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
10 9 8 7 6 5 4 3 2 1 04 03 02 01
ISBN 3-8273-1531-X © 2001 by Prentice Hall, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Hommer Design Production, Haar bei München Lektorat: Irmgard Wagner, Taufkirchen,
[email protected] Korrektorat: Petra Kienle, Fürstenfeldbruck Herstellung: TYPisch Müller, Arcevia, Italien,
[email protected] Satz: reemers publishing services gmbh, Krefeld Druck und Verarbeitung: Media Print, Paderborn Printed in Germany
Inhaltsverzeichnis Vorwort
11
1 Einleitung
15
1.1 1.2 1.3 1.4 1.5
Was ist OOP? Warum C++? Die Entwicklung von C++ Die Weiterentwicklung von C Java als C++-Derivat
2 Algorithmen und Compiler 2.1 2.2 2.3 2.4
Wie man ein C++-Programm compiliert Programmfehler Einfache Algorithmen Übungen
3 Lexikalische Elemente und Datentypen 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9
Zeichensatz Schlüsselwörter Kommentare und Separatoren Operatoren Literale Konstanten Definition und Deklaration Einfache Datentypen Der Aufzählungstyp enum Speicherklassen
4 Abgeleitete Datentypen 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9
Konstanten Zeiger Erzeugen dynamischer Variablen Referenzen Typumwandlungen Reihungen Der struct-Typ Strings Übungen
15 19 20 23 24
29 29 35 37 41
43 43 45 46 46 48 49 50 55 56
61 61 61 64 66 67 68 70 74 77
6
Inhaltsverzeichnis
5 Ausdrücke und Anweisungen 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10
Arithmetische Ausdrücke Logische Operatoren Bitoperatoren Zuweisungen Weitere Operatoren Grenzen der Zahlbereiche Typumwandlungen Anweisungen Der Namensraum Übungen
6 Kontrollstrukturen 6.1 6.2 6.3 6.4 6.5 6.6
Verbundanweisung Bedingte Anweisungen Wiederholungsanweisungen Die Transfer-Funktionen Das assert()-Makro Übungen
7 Funktionen 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 7.13 7.14 7.15 7.16 7.17 7.18 7.19
Deklaration und Definition Mathematische Standardfunktionen Selbst definierte Funktionen String-Funktionen Char-Funktionen Rekursive Funktionen Die Funktion main() Funktion mit variabler Parameterliste Call-by-value Call-by-reference Zeiger auf Funktionen Array von Funktionszeigern Funktionen, die einen Zeiger liefern Funktionen, die eine Referenz liefern Default-Argumente Inline-Funktionen Überladen von Funktionen Template-Funktionen Übungen
79 79 80 84 87 89 91 92 94 94 97
99 99 100 102 105 106 107
111 111 113 117 118 121 122 124 126 127 128 130 132 133 134 135 136 136 137 139
Inhaltsverzeichnis
7
8 Klassen
143
8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 8.10 8.11 8.12 8.13 8.14 8.15 8.16 8.17 8.18 8.19
143 145 147 149 149 150 155 157 159 162 162 164 168 171 174 175 177 178 181
Klassen als ADT Definition und Deklaration Elementfunktionen Klasse als Verallgemeinerung der Funktion Eine Klasse Bruch Konstruktoren Destruktoren Beispiel zur Datenkapselung Klasse mit Array Klasse mit dynamischem Array Statische Datenelemente Überladen von Operatoren Überladen von Operatoren 2 Friend-Funktionen Der this-Zeiger Klasse mit Funktionszeiger Klasse mit Invarianten Friend-Klassen Übungen
9 Elemente des Software-Engineerings 9.1 9.2 9.3 9.4 9.5
Die Verifikation von Algorithmen Software-Metrik Stichpunkte der UML Kriterien für Programmiersprachen Forderungen des Software-Engineerings
10 Ausnahmebehandlung 10.1 10.2 10.3 10.4
Ablauf eines Ausnahmefalls Ein Beispiel mit selbst definierten Fehlerklassen Vordefinierte Fehlerfunktionen Die Standardausnahmen
11 Vererbung 11.1 11.2 11.3 11.4 11.5 11.6
Zugriffsmöglichkeiten Konstruktoren und Destruktoren Polymorphismus Virtuelle Funktionen RTTI Abstrakte Basisklassen
183 183 188 192 199 202
205 205 207 208 210
211 212 214 217 218 220 225
8
Inhaltsverzeichnis
11.7 11.8 11.9 11.10 11.11
Beispiel aus der Biologie Beispiel aus der Mathematik Mehrfache Vererbung Virtuelle Basisklassen Übung
12 Ein-/Ausgabestrom 12.1 12.2 12.3 12.4 12.5 12.6 12.7 12.8
Die Basisklasse basic_ios Ausgabe Manipulatoren Eingabe Die Statusfunktionen Streams für Zeichenketten Internationalisierung Dateibehandlung
13 Relationen zwischen Klassen 13.1 13.2 13.3 13.4 13.5 13.6
Die Aggregation Die Komposition Die Uses-A-Relation Wechselseitig enthaltende Objekte Die Vererbung Verschachtelte Klassen
14 Template-Klassen 14.1 14.2 14.3 14.4 14.5
Template-Klasse Queue Template-Klasse Stack Template-Klasse Polynom Template-Klasse Binärbaum Numerische Parameterwerte
15 Datentypen der STL 15.1 15.2 15.3 15.4 15.5 15.6 15.7 15.8 15.9 15.10
Iteratoren Funktionsobjekte Hilfsmittel Der Container vector Der Container deque Der Container list Der Container map Der Container multimap Der Container set Der Container multiset
228 230 232 235 237
243 243 244 249 252 255 257 257 259
265 266 268 269 271 273 274
277 278 280 282 285 287
291 292 294 301 302 305 307 309 311 313 318
Inhaltsverzeichnis
15.11 15.12 15.13 15.14 15.15 15.16 15.17
Der Adapter stack Der Adapter queue Der Adapter priority_queue Die Klasse bitset Die Klasse string Die Klasse valarray Übungen
16 Algorithmen der STL 16.1 16.2 16.3 16.4 16.5 16.6 16.7 16.8 16.9 16.10 16.11 16.12
Nichtmodifizierende Algorithmen Modifizierende Algorithmen Suchen in geordneten Bereichen Sortierähnliche Verfahren Misch-Verfahren Die Mengen-Funktionen Minumum und Maximum Permutationen Heap-Algorithmen Komplexe Arithmetik Numerische Algorithmen Demoprogramme zu den STL Algorithmen
17 Grafik 17.1 17.2 17.3 17.4 17.5 17.6
Ein Beispiel zu X Windows Grafik mit dem C++Builder Hofstadter-Schmetterling Der Ikeda-Attraktor Julia-Menge Orthogonale Trajektorien
Die Binomialverteilung Die Poisson-Verteilung Hypergeometrische Verteilung Normalverteilung Die t-Verteilung Die c2-Verteilung F-Verteilung
19 Numerik-Programme 19.1 19.2
319 320 321 323 325 326 329
331 331 334 348 350 355 357 359 360 365 366 371 374
381
18 Statistik-Programme 18.1 18.2 18.3 18.4 18.5 18.6 18.7
9
Eintauchtiefe einer Kugel Arbeitspunkt einer Diode
381 383 385 387 389 391
395 395 397 399 401 403 407 409
413 413 417
10
Inhaltsverzeichnis
19.3 19.4 19.5 19.6 19.7 19.8 19.9 19.10 19.11 19.12
Die Richmond-Iteration Numerische Differenziation Adaptive Simpson-Formel Integration nach Gauss Integration von Tabellenfunktionen Romberg-Integration Runge-Kutta-Fehlberg Verfahren von Dormand-Prince Adams-Moulton-Verfahren Tschebyschew-Approximation
20 Physik-Programme 20.1 20.2 20.3 20.4 20.5 20.6 20.7 20.8 20.9
Gleichgewichtstemperatur der Erde Energieanteil eines schwarzen Strahlers Abkühlvorgang eines Festkörpers Die Lorenz-Gleichungen Massenformel von Weizsäcker Das Drei-Körper-Problem Dampfdruckkurve Biegeschwingung eines einseitig eingespannten Stabs Zustandsgrößen eines Sterns
21 Literaturverzeichnis C++ Programmiersprachen UML Informatik Astronomie Physik Chaostheorie Mathematik
Index
419 421 423 425 427 430 433 435 437 440
445 445 449 452 453 455 459 463 467 469
475 475 475 475 476 476 476 476 476
477
Vorwort
C++ ist ohne Zweifel die Programmiersprache der neunziger Jahre. Die Gründe für den Erfolg von C++ sind unter anderem: 왘 C++ weist die wesentlichen Merkmale einer objektorientierten Programmierspra-
che auf, erlaubt aber dem Anwender einen Übergang von der prozeduralen zur objektorientierten Programmierweise. 왘 Compiler für C++ sind praktisch an allen Maschinen verfügbar; der Sprachstan-
dard garantiert eine sichere Investition in C++. 왘 C++-Programme sind mit einer Vielzahl von existierenden C-Quellcodes kombi-
nierbar. Außerdem gibt es zahlreiche C++-Bibliotheken für nahezu alle Anwendungen. 왘 C++ ist Ausgangspunkt für die Entwicklung weiterer objektorientierter Program-
miersprachen wie Java. Das Erlernen von C++ liefert eine fundierte Kenntnis der erfolgreichsten Familie von Programmiersprachen, die zur Zeit durch die neue Programmiersprache C# von Microsoft erweitert wird. Viele Bücher über C++ handeln von irgendwelchen abstrakten Klassen oder widmen sich allgemein der objektorientierten Softwareerstellung. Was fehlt, ist eine Darstellung, die das objektorientierte Programmmieren (OOP) aus der Sicht des Naturwissenschaftlers aufzeigt. Hier setzt das vorliegende Buch ein. Es zeigt für Leser mit Grundkenntnissen im Programmieren: 왘 wie die strukturierte und prozedurale Programmierung objektorientiert wird, 왘 wie vielfältig Klassen im naturwissenschaftlichen Bereich eingesetzt werden kön-
nen. Da C++ eine der mächtigsten Programmiersprachen ist, ist sie nicht, wie andere Programmiersprachen, in wenigen Stunden erlernbar. Um den Lernvorgang zu erleichtern, wird die Leserin bzw. der Leser durch konkrete, nachvollziehbare Beispiele und zahlreiche Abbildungen, aber nicht durch abstrakt-formale Beschreibungen in die komplexe Welt der C++-Programmierung eingeführt.
12
Vorwort
Die Grundlage dieses Buchs bildet die Beschreibung von C++ gemäß der ANSI/ISO C++-Norm, die im Juli 1998 von der internationalen Behörde ISO/IEC akzeptiert worden ist. Es ist zu erwarten, dass künftige Compilerversionen weitgehend mit der Norm konform gehen. Um die Bandbreite der C++-Anwendungen zu demonstrieren, behandelt das Buch eine Vielzahl von Themen aus Mathematik, Informatik, Physik, Astronomie und Computergrafik. Es ist natürlich im Rahmen eine solchen Buchs nicht möglich, alle benötigten Grundlagen aus diesen Wissenschaften bereitzustellen. Der Leser wird hier auf die Fachliteratur (im Anhang) verwiesen. Kapitel 1 enthält als Einführung die Geschichte und Entwicklung der Programmiersprache C++ und das Basiswissen über OOP. Kapitel 2 bietet grundlegende Informationen über das Arbeiten mit C++-Compilern, über auftretende Fehlermeldungen und erläutert die Eigenschaften von Algorithmen. Kapitel 3 stellt die Syntax von C++ und die einfachen Datentypen so ausführlich dar, dass das Buch auch ohne C-Kenntnisse gelesen werden kann. Kapitel 4 erläutert alle abgeleiteten Datentypen wie Pointer, Referenzen, dynamische Variablen und Reihungen (Array). Kapitel 5 erklärt die vielfältigen Operatoren, Ausdrücke und Anweisungen sowie den Begriff des Namensraums. Kapitel 6 diskutiert die Kontrollstrukturen; d.h. bedingte Anweisungen, Wiederholungsanweisungen und Transferfunktionen. Kapitel 7 zeigt alles über Funktionen, Rekursivität, Wertübergabe, Default-Argumente, Inline- und Template-Funktionen. Kapitel 8 liefert die Grundlagen für das Programmieren von Klassen, Konstruktoren, Destruktoren, statischen Elementen, Überladen von Operatoren, Funktionszeigern und Friend-Klassen. Kapitel 9 vermittelt einen Einblick in Methoden des Software-Engineerings. Hier findet sich auch eine Einführung in die Unified Modeling Language (UML), die in Zukunft noch größere Bedeutung erlangen wird. Exemplarisch werden die KlassenDiagramme behandelt, die mit Hilfe der UML-Software »Together 4.0« erstellt wurden. Kapitel 10 zeigt die Grundzüge der in C++ eingebauten Ausnahmebehandlung. Kapitel 11 ist der einfachen und mehrfachen Vererbung gewidmet. Es werden die Zugriffsmöglichkeiten der abgeleiteten Klassen, virtuelle Funktionen und abstrakte Basisklassen diskutiert. Kapitel 12 vermittelt die Grundlagen der objektorientierten Ein- und Ausgabe. Kapitel 13 erörtert die vielfältigen Beziehungen von Klassen untereinander, insbesondere das Aggregat und die Komposition. Kapitel 14 erklärt die Programmierung von Template-Klassen an Hand der Datenstrukturen Stack, Warteschlange (Queue) und Polynom. Die Beschäftigung mit diesen Template-Klassen dient als Vorbereitung zum Arbeiten mit der Standard Template Library (STL).
Vorwort
13
Kapitel 15 beschäftigt sich ausführlich mit den generischen Datentypen (Containern) vector, deque, list, map, set und den Adaptern stack, priority_queue und queue. Kapitel 16 stellt umfassend dar, wie die Algorithmen der STL auf den generischen Datentypen der STL operieren können. Durch viele Beispiele wird das Arbeiten mit der Template-Klasse der komplexen Zahlen erläutert. Kapitel 17 liefert einige interessante grafische Anwendungen aus dem Bereich der Chaostheorie, fraktalen Geometrie und der komplexen Abbildungen. Gezeigt wird u. a. eine Julia-Menge und der bekannte Schmetterling von Hofstadter, der die fraktalen Eigenwerte von Bloch-Zellen im homogenen Magnetfeld mit Cantor-Eigenschaften grafisch demonstriert. Kapitel 18 bietet grundlegende Klassen zur Berechnung von Wahrscheinlichkeitsverteilungen und Prüfstatistiken. Kapitel 19 enthält wichtige Klassen zur Lösung von nichtlinearen Gleichungen, numerischer Integration von Integralen und gewöhnlichen Differentialgleichungen. Dabei werden auch neuere und weniger bekannte Algorithmen wie die Integration nach Dormand-Prince, die Richmond-Iteration und die adaptive Simpson-Integration behandelt. Das abschließende Kapitel 20 löst interessante physikalische Problemstellungen, u. a. die Gleichgewichtstemperatur der Erde, Lorenz-Gleichungen, Wärmestrahlung, Dampfdruckkurve, Eigenwertproblem eines eingespannten Stabs und das eingeschränkte Drei-Körper-Problem. Beiliegende CD-ROM enthält alle Musterlösungen der Übungsaufgaben und den Quellcode der im Buch enthaltenen nichttrivialen Programme. Damit Sie die CD-ROM nützen können, müssen Sie über ein entsprechendes Laufwerk verfügen. Der Quellcode der Programme kann mit allen Betriebssystemen wie Windows 95/98/NT, Linux, Solaris oder Apple gelesen werden. Zum Compilieren der C++-Programme wird ein neuerer C++-Compiler benötigt; ausführliche Hinweise zum Arbeiten mit Compilern finden sich zweiten Kapitel. Leser, die über eine Internet-Zugang verfügen, können sich den Borland-Compiler (Version 5.5) in der Kommandozeilenversion downloaden. Ebenfalls kostenlos im Internet verfügbar ist der GNU-C++-Compiler; er wird für alle Rechner mit dem Linux-Betriebsystem empfohlen. Es wird ausdrücklich darauf hingewiesen, dass es derzeit keinen Compiler gibt, der alle im Buch enthaltenen Programme compilieren kann; viele Compiler haben noch nicht alle Neuerungen der C++-Norm implementiert. Dem Verlag Addison-Wesley, nunmehr Teil des Verlags Pearson Education, danke ich für die Herausgabe der zweiten, wesentlich erweiterten Auflage dieses Buch im Rahmen der Serie Scientific Computing. Dadurch war es möglich, die gesamte Darstellung der neuen ISO C++-Norm anzupassen und den Umfang durch ca. 150 Seiten zur Standard Template Library erheblich zu steigern. Besonderen Dank schulde ich Frau Irmgard Wagner, die als Lektorin einen großen Verdienst an dem Zustandekommen des Buchprojekts hat und Frau Petra Kienle für das Korrekturlesen des Buches.
14
Vorwort
Danken möchte ich auch allen, die per E-Mail und Brief geholfen haben, Druckfehler publik zu machen, insbesondere Herrn Tiebel (Uni Potsdam), Herrn Lüer (Uni Hannover), Herrn Braeske (Uni Erlangen) und Herrn Mende (Bochum). Für Hinweise und (humorvolle) Kommentare ist der Autor dankbar. Er ist erreichbar unter der E-Mail-Adresse
[email protected]. Dietmar Herrmann, Oktober 2000
1
Einleitung No turning back. Object-orientation is the future, and the future is here and now. E. Yourdon
Object-oriented programming is an exceptionally bad idea which could only have originated in California. E.W. Dijkstra
1.1
Was ist OOP?
Das objektorientierte Programmieren (abgekürzt OOP) ist nur eine von mehreren Programmiermethoden, die in der Informatik seit der Einführung von FORTRAN entwickelt worden sind. 왘 Der prozedurale Programmierstil ist durch die Programmiersprachen FORTRAN
und ALGOL eingeführt worden und dient hauptsächlich dem Rechnen im technisch-naturwissenschaftlichen Bereich. Ein Programm besteht hier also aus einer bestimmten Zahl von Funktionen und Prozeduren. Ein Algorithmus sorgt dafür, dass diese Funktionen in geeigneter Weise abgearbeitet werden. Bekannte prozedurale Programmiersprachen sind Pascal und C. 왘 Der deklarative Programmierstil wird insbesondere durch die Programmiersprache
PROLOG vertreten. Ein deklaratives Programm besteht aus Fakten und logischen Regeln. Es wird nicht durch einen vorgegebenen Algorithmus gesteuert. Vielmehr versucht der Interpreter bzw. Compiler mit Hilfe des eingebauten Backtracking, eine oder mehrere Lösungen zu finden, die die Wissensbasis und Regeln erfüllen. Dieser Programmierstil ist sehr gut geeignet für die Implementierung von Expertensystemen. 왘 In der funktionalen Programmierung werden, meist mit Hilfe von Rekursion, benut-
zerdefinierte Funktionen auf einfache Listenoperationen zurückgeführt. Da rekursiv definierte Strukturen stets mit Hilfe von mathematischer Induktion charakterisiert werden können, eignen sich besonders mathematische Strukturen und Symbolverarbeitung zur funktionalen Programmierung. Ein Vertreter dieser Richtung ist das bereits Ende der fünfziger Jahre entstandene LISP. Die meisten der Computer-Algebra-Systeme von MUMATH bis MATHEMATICA besitzen einen eingebauten LISP-Interpreter, der die eingegebenen Terme symbolisch abarbeitet. 왘 Die objektorientierte Programmierung trennt nicht mehr Algorithmus und Daten-
struktur, sondern modelliert die Problemstellung durch interagierende Objekte, die bestimmter eingebauter Methoden fähig sind. Alle Aktivitäten gehen dabei von diesen Objekten aus, die wiederum andere Objekte durch Aussenden von geeigne-
16
1 Einleitung
ten Botschaften (messages) zu Aktionen bzw. Interaktionen auffordern. Objekte mit gleichem Verhalten werden in sog. Klassen definiert. Mit Hilfe solcher Objekte können komplexe Modelle der realen Welt nachgebildet werden; die eingesetzten Objekte lassen sich bausteinartig für ähnliche Probleme wiederverwenden (Modularität). Das Konzept der Klassen wurde von der Programmiersprache SIMULA 76 übernommen. Als erste rein objektorientierte Programmiersprache gilt SMALLTALK (1980). Die Programmiersprache C++ ist eine hybride Sprache, da sie neben der Objektorientierung aus Effizienzgründen auch prozedurales Programmieren erlaubt. Programmiermethode
Anwendung
typische Problemklassen
prozedural
Numerik
Berechnungen
funktional
Lambda-Kalkül
Formelmanipulation
deklarativ
automatisches Beweisen
Prädikatenlogik 1.Stufe
objektorientiert
Klassifikation
Objekthierarchien
Tab. 1.1: Hierarchie der Programmiermethoden
Seit der NATO Software-Engineering Konferenz 1968 in Garmisch war die SoftwareKrise in aller Munde. David Gries schrieb darüber in The Science of Programming: Die Informatiker sprachen offen über das Versagen ihrer Software und nicht nur über ihre Erfolge, um auf den Grund der Probleme zu kommen. Zum ersten Mal kam es zur generellen Übereinkunft, dass es wirklich eine Software-Krise gab und dass das Programmieren noch nicht gut verstanden war. Man hatte erkannt, dass die herkömmlichen Programmiersprachen und -methoden nicht mehr ausreichten, um große Software-Projekte in den Griff zu bekommen. Folgende Qualitätsnormen wurden im Software-Engineering angestrebt: 왘 Korrektheit (Software soll exakt die Anforderungspezifikation erfüllen) 왘 Erweiterbarkeit (Maß für die Fähigkeit, Software an erweiterte Spezifikationen anzu-
passen) 왘 Wiederverwendbarkeit (Maß für die Möglichkeit, Software teilweise oder vollständig
für neue Anwendungen wiederzuwenden) 왘 Kompatibilität (Maß für die Fähigkeit, verschiedene Software-Produkte miteinander
zu kombinieren) Es entstanden eine Vielzahl von Entwicklungsumgebungen, die in der Regel objektorientiert konzipiert sind und nun als CASE-Tools (Computer Aided Software Engineering) auf dem Markt erscheinen. Welchen Umfang Software-Projekte haben können, sieht man am Quellcode von Windows 95, der ca. 750.000 Zeilen umfasst. Die Software zur Steuerung der Mondlandung von APOLLO 11 umfasste dagegen nur ca. 300.000 Programmzeilen. Was ist nun ein Objekt genau? Nach Shlaer/Mellor (1988) lassen sich alle Objekte in folgende fünf Kategorien einteilen: 왘 Erfassbare Dinge (z.B. Auto, Bankkonto) 왘 Rollen (z.B. Chauffeur, Kontoinhaber)
1.1 Was ist OOP?
17
왘 Ereignisse (z.B. Fahrt, Bankabrechnung) 왘 Interaktionen (z.B. Leasingvertrag, Auszahlung) 왘 Spezifikationen (z.B. kennzeichnende Eigenschaften).
Das OOP wird in der Literatur durch folgende Schlagworte gekennzeichnet: 왘 Geheimnisprinzip/ Datenkapselung (information hiding/ data encapsulation) 왘 Unterstützung abstrakter Datentypen (data abstraction) 왘 Vererbung (inheritance) 왘 Polymorphismus (polymorphism) 왘 Modularität (modularity) 왘 Wiederverwendbarkeit (reusability)
Abbildung 1.1: Prinzipien (Paradigma) der OOP
Dabei wurden die Konzepte Datenkapselung, Vererbung und Polymorphismus bereits 1965 von O.J. Dahl und K. Nygard für die Programmiersprache SIMULA erdacht, die später dann SIMULA 67 genannt wurde. Zu dieser Zeit war das Schlagwort Objektorientierung aber noch gar nicht geprägt! Unter Datenkapselung versteht man das Abschirmen eines Objekts und seiner Methoden nach außen. Die Kapselung ermöglicht es, Elemente und Funktionen einer Klasse gegen einen Zugriff von außen, z.B. von einem anderen Objekt, zu schützen. Bei der Vererbung können bestimmte Methoden des Objekts ausgeschlossen werden. Die Kapselung ist eine Anwendung des allgemeineren Geheimnisprinzips (information hiding), das 1972 von D. Parnas postuliert wurde: Jedes Programmmodul soll nach diesem Prinzip so wenig wie möglich über seine interne Arbeitsweise aussagen. Unter Vererbung versteht man die Fähigkeit eines Objekts, Eigenschaften und Methoden automatisch von einem anderen Objekt zu erben. Kann ein Objekt nur von einem anderen erben, so spricht man von einfacher Vererbung (single inheritance). Können jedoch Eigenschaften von mindestens zwei Objekten geerbt werden, so handelt es sich
18
1 Einleitung
um mehrfache Vererbung (multiple inheritance). Die vererbende Klasse heißt auch Basisklasse, die erbende Klasse abgeleitet. Unter Polymorphismus versteht man die Fähigkeit, dass verschiedene Objekte auf dieselbe Methode mit unterschiedlichen Aktionen reagieren können. In der Sprache von C++ heißt das, Objekte, die über einen Zeiger auf die Basisklasse angesprochen werden können, agieren so, als wären sie vom Typ der abgeleiteten Klasse (sofern sie vom Typ der abgeleiteten Klasse sind). Diese Zeiger können während eines Programmlaufs mehrere Werte annehmen. Der Compiler kann erst zur Laufzeit entscheiden, zu welchem Objekt die jeweils aufgerufene Methode gehört. Dieser Prozess heißt dynamisches Binden (dynamic binding) oder spätes Binden (late binding). Modularität bedeutet im Allgemeinen Wiederverwendbarkeit (reusability) und Erweiterbarkeit (extensibility). Wiederverwendbarkeit bedeutet, dass Programme aus bereits existierenden Software-Komponenten zusammengesetzt werden können. Mit Erweiterbarkeit ist gemeint, neue Software-Komponenten so zu schreiben, dass sie mit existierenden Programmen ohne Änderung kombiniert werden können. Nach B. Meyer umfasst die Modularität folgende Eigenschaften: 왘 Zerlegbarkeit (Systeme können in einzelne funktionierende Einheiten zerlegt wer-
den) 왘 Zusammenfügbarkeit (Einzelmodule können beliebig kombiniert werden) 왘 Verständlichkeit (Verstehen der Einzelmodule ermöglicht Verstehen des Ganzen) 왘 Kontinuität (kleine Änderungen des Systems bewirken nur eine kleine Änderung
im Verhalten der Software) 왘 Schutz (Ausnahmebehandlungen und Fehler wirken nur im verursachenden
Modul oder in seiner Nähe) Wiederverwendbarkeit ist aber in der Praxis nur schwer zu realisieren. E. Yourdon sagt darüber (1990): Wiederverwendbarkeit ist natürlich eine Pfadfindertugend, wie Treue, Tapferkeit und Mut; aber niemand weiß so richtig, wie man diese in die Tat umsetzen kann. Die prozedurale und objektorientierte Programmierung haben gewisse Ähnlichkeiten. Nach N. Wirth (1990) lassen sich folgende Analogien finden prozedural
objektorientiert
Datentyp
Klasse/Abstrakter Datentyp
Variable
Objekt/Instanz
Funktion/Prozedur
Methode
Funktionsaufruf
Senden einer Botschaft Tab. 1.2: Analogie prozedurales/objektorientiertes Programmieren
Alle Aspekte des prozeduralen Programmierens sind zu einem gewissen Grad auch im OOP vertreten. Anstatt weitgehend vom Datentyp unabhängige Funktionen und Prozeduren zu entwickeln, definiert man geeignete Methoden, die nun direkter Bestandteil der speziellen Objektklassen sind. Während man früher überlegt hat, durch welche Datenstrukturen ein Algorithmus optimal implementiert werden kann, werden Klassen bereits bei der Definition so konzipiert, dass sie der Struktur des Objekts und seines Datenaustausches entsprechen.
1.2 Warum C++?
19
Den Vorteilen des OOP stehen nur wenige Nachteile gegenüber, wie verstärkter Ressourcenbedarf, größere Planungskomplexität und komplizierte Entwurfstechniken. Neuere OOP-Programmierumgebungen mit grafischer Benutzeroberfläche (GUI, englisch Graphical User Interface) erfordern mehr Maschinenressourcen als textorientierte Editoren. Das erste GUI wurde im Xerox PARC (Palo Alto Research Center) entwickelt, von wo auch das SMALLTALK-Interface WIMP (Windows, Icons, Menus, Pointers) stammt. Als Vorstufen des OOP betrachtet man die objektorientierte Analyse (OOA) und das objektorientierte Design (OOD). Hier wurde eine Vielzahl von Entwurfstechniken entwickelt, die nun in der Unified Modelling Language (UML) zusammengefasst worden sind (mehr darüber in 9.3). Compiler und Codierung des OOP sind komplexer und anspruchsvoller geworden. Der Parser von C++ ist doppelt so groß wie der von ADA. C++ kann daher als die komplexeste Programmiersprache angesehen werden. Das Zusammenwirken von Objektklassen muss sorgfältig geplant werden, damit die Vererbung von Methoden und die Wiederverwendbarkeit von Klassen in gewünschter Weise genützt werden können. M. Terrible schreibt (1994): Objektorientierte Methoden versprechen mehr Erfolg für Programmierer und Software-Ingenieure. Lernen und Anwenden von C++ ist nicht dasselbe wie der Entwurf von objektorientierter Software. Ohne grundlegendes Wissen über Planung und Design von objektorientierten Systemen kann C++ nur begrenzten Erfolg haben. Das Programmieren in C++ steigert die Anstrengungen, die Systemspezialisten und Designer erbringen müssen, damit die Analyse stichhaltig, die Architektur überschaubar und das Design begründet ist und nicht zufällig geschieht.
1.2
Warum C++?
Die Grundlagen von C++ (damals noch unter dem Namen C mit Klassen) wurden 1979 bis 1984 von dem aus Dänemark stammenden Informatiker Bjarne Stroustrup bei den Bell Laboratories von AT&T gelegt. Damals, zu Beginn der achtziger Jahre, gab es eine Vielzahl von Bestrebungen, neue rein objektorientierte Sprachen – unabhängig von den bisherigen Programmiersprachen – zu entwickeln. Eine dieser Sprachen ist EIFFEL, das Mitte der achtziger Jahre von Bertrand Meyer konzipiert wurde. Eine Frühform von EIFFEL wurde in Object-Oriented Software Construction (1988) vorgestellt. Der endgültige Entwurf von EIFFEL wurde erst 1992 von Meyer publiziert in EIFFEL: The Language. Dieser Entwurf verfügt über mehr OOP-Konzepte als C++. Vom Standpunkt des reinen OOP gesehen, wäre eine ausschließlich objektorientierte Programmiersprache wie SMALLTALK oder EIFFEL vorzuziehen. Als Stroustrup sah, dass für SMALLTALK (Entwurf Alan Kay) nur ein Interpreter, für EIFFEL nur ein Entwurf existierte, wurde ihm klar, dass diese Sprachen nicht als Basis seines Projekts dienen konnten. Auch von der Vielzahl weiterer Programmiersprachen – darunter MODULA 2, ADA, MESA und CLU –, die Stroustrup in dieser Zeit studierte, konnte keine seinen Anforderungen genügen. So fügte er Teile von SIMULA 76, ALGOL 68 und C zu einem neuen Entwurf zusammen. Eine solche hybride Sprache war neben der Objektorientierung ebenfalls zum prozeduralen Programmieren geeignet.
20
1 Einleitung
Der Schwachpunkte der Programmiersprache C war sich Stroustrup sehr wohl bewusst, er erntete in diesem Zusammenhang viel Kritik, der Erfolg gab ihm aber Recht. Für C sprachen insbesondere folgende Punkte: 왘 Flexibilität 왘 Effizienz 왘 Verfügbarkeit 왘 Portabilität
C war sowohl zur Systemprogrammierung – wie UNIX zeigt – als auch zum technischnaturwissenschaftlichen Rechnen geeignet. Ferner unterstützte es – im Gegensatz etwa zu PASCAL – die Software-Entwicklung durch separates Compilieren. C erlaubte es, durch Bitoperationen teilweise auf Maschinenebene zu programmieren und sich damit jeder Rechnerarchitektur anzupassen. C war wie keine andere Sprache auf allen Betriebssystemebenen vertreten, insbesondere auf Mehrbenutzeranlagen wie UNIX. Außerdem galt C als portabel; durch die Einführung von Bibliotheksfunktionen konnten z.B. alle Ein- und Ausgaben maschinenunabhängig beschrieben werden. Stroustrup schreibt: A programming language serves two related purposes: it provides a vehicle for the programmer to specify the action to be executed and a set of concepts for the programmer to use when thinking about what can be done. Diese beiden Aspekte können auch charakterisiert werden als die Grundphilosophie von C: 왘 Nahe-an-der-Maschine 왘 Nahe-am-Problem
Der Schwachpunkt von C (vor der ANSI C-Norm), die fehlende Typenprüfung, sollte in C++ durch striktes Type-Checking überwunden werden. Da C++ weitgehend abwärtskompatibel ist, erfordert das saubere objektorientierte Programmieren in C++ erhöhte Programmierdisziplin. Für C++ spricht ferner die Möglichkeit, ein reines C-Programm mit einem C++-Compiler zu übersetzen. Davon wird von Programmierern häufig Gebrauch gemacht (nach dem Motto C++ as better C).
1.3
Die Entwicklung von C++
Bjarne Stroustrup kannte durch sein Studium in Cambridge die Programmiersprachen SIMULA 76 und ALGOL 68 gut. Nach seinem Eintritt in die Bell Laboratories in London hatte er sich intensiv mit C zur Systemprogrammierung befasst. Als er im Frühjahr 1979 zu AT&T (New Jersey) wechselte, wurde er mit der Aufgabe betraut, den Kern von UNIX so zu modifizieren, dass das System auch in einem lokalen Netzwerk lief. Als Folge seiner Arbeit erschien 1980 die erste Publikation über C mit Klassen in einem internen Bericht von Bell Labs. Dieser Report sah folgende Eigenschaften vor: 왘 Klassen (mit privatem/öffentlichem Zugriff) 왘 Einfache Vererbung (aber noch keine virtuelle Funktionen)
1.3 Die Entwicklung von C++
21
왘 Konstruktoren/Destruktoren 왘 Friend-Klassen 왘 Konstanten 왘 Überladen von Operatoren 왘 Referenzen
Das Klassenkonzept übernahm Stroustrup aus SIMULA 76, das Operator-Überladen entlehnte er aus ALGOL 68. Der 1982 erschienene SIGPLAN-Report Adding Classes to the C Language: An Exercise in Language Evolution umfasste zusätzlich noch 왘 Inline-Funktionen 왘 Default-Argumente 왘 Überladen des Zuweisungsoperators
1984 erkannte Stroustrup, dass der Erfolg seiner Bemühungen nur eine eigenständige Sprachentwicklung sein konnte und nicht eine Ergänzung einer existierenden Sprache. Diese Entwicklung wurde zunächst C84, später dann, auf Vorschlag seines Kollegen R. Mascitti, C++ genannt. Zur weiteren Verbreitung musste natürlich ein geeigneter (Prä-)Compiler bereitgestellt werden. Die Version 1.0 von Cfront erschien 1985 und umfasste ungefähr 12.000 Programmzeilen C++. Die weiteren Cfront-Versionen bis 3.0 erschienen bis September 1991. Die Version 1.0 war in der 1986 erschienenen Sprachendefinition The C++ Programming Language zugrunde gelegt. Mit der Version 2.0 wurde C++ u.a. um folgende Funktionen erweitert 왘 Mehrfache Vererbung 왘 Abstrakte Klassen 왘 Statische und konstante Elementfunktionen
Die Version 2.1 wurde durch das Referenzwerk: The Annotated C++ Reference Manual (ARM genannt) im April 1990 festgelegt. Hierbei wurde noch folgenden Eigenschaften hinzugefügt 왘 Templates 왘 Virtuelle Funktionen 왘 Ausnahmen (Exceptions) 왘 Verbesserte Ein-/Ausgabe (mittels iostream)
Eine modifizierte Version des Cfront 3.0-Compilers, die im September 1992 von Hewlett-Packard verschickt wurde, unterstützte diese Neuerungen, einschließlich den Ausnahme-Behandlungen (exceptions handling). Diese Version ist praktisch von allen Compilerherstellern übernommen worden, u.a. von Apple, Centerline, Glockenspiel, PARC Place und SUN. Das im Dezember 1989 ins Leben gerufene ANSI C++-Komitee sorgte für eine gute Abstimmung mit der gleichzeitig entstehenden ANSI C-Norm. Für die Zusammenarbeit galt die Devise As Close to C as possible – but not closer. Die Liste der 1990 im ANSI C++-Komitee vertretenen Firmen liest sich wie das Who is Who der Computerindustrie:
22
1 Einleitung
Amdahl, Apple, AT&T, Borland, British Aerospace, CDC, DEC, Fujitsu, Hewlett-Packard, IBM, Los Alamos National Labs, Lucid, Microsoft, NEC, Prime Computer, SAS, Siemens, Silicon Graphics, Sun, Texas Instruments, UniSys, Wang, Zortech u.a. Bereits im Mai 1990 hatte Borland einen C++-Compiler in die MS-DOS-Welt gebracht, von dem bis zur COMDEX 1993 mehr als eine Million Exemplare verkauft wurden (Zortech hatte seit 1988 einen C++-Compiler für MS-DOS angeboten). Microsoft zog erst im März 1992 durch die Übernahme des Glockenspiel-Compilers nach. Auf der UNIX-Seite sind die Compiler der USENIX-Gruppe, GNU C++ (seit Dezember 1987) zu nennen. Für X-Windows Systeme gibt es die komfortablen Versionen von SUN und Centerline. 1993 wurden folgende Erweiterungen vom ANSI/ISO C++-Komitee beschlossen: 왘 Run-Time Type Identification (RTTI) 왘 Namensraum (namespace)
Ein wesentlicher Schritt war auch die Akzeptanz der von Alexander Stepanow und Meng Lee bei Hewlett Packard entwickelten Standard Template Library (STL), die 1994 weitgehend, aber nicht ganz, vom ANSI/ISO-Komitee verfügt wurde. In die kommende Norm wurden schließlich noch neue Operatoren zur Typumwandlung aufgenommen wie 왘 static_cast, dynamic_cast 왘 const_cast, reinterpret_cast
Am 26. September 1995 wurde dann endlich der lang erwartete Normenentwurf Draft ANSI/ISO C++ Standard beschlossen. Nach langen Verhandlungen dauerte es dann noch fast drei Jahre, bis am 27. Juli 1998 die ANSI C++-Norm, international als ISONorm 14882:1998, endgültig beschlossen war. Die Publikation umfasst 774 Seiten und ist elektronisch als *.pdf-File verfügbar. Momentan sammelt das ISO-Komitee über die nationalen Komitees und User-Gruppen Fehler-Reports (defect reports), die über Unklarheiten, Widersprüche oder Auslassungen der Norm Auskunft geben. In etwa zwei Jahren werden dann alle, vom Komitee akzeptierten Reports, als Technical Corrigendum (TC) publiziert. Die Publikation zweier solcher TC ist möglich; erst nach 2003 kann es zu einer letzten Bewertung (final review) der ISO-Norm kommen. Damit war nach fast zehnjähriger Arbeit die umfangreichste internationale Normierung zu einem Schlusspunkt gekommen. Alle Programmierer und Software-Firmen haben nun die Sicherheit, dass ein der Norm entsprechendes C++-Programm nie mehr geändert werden muss! Für viele Informatiker ist der Umfang von C++ bereits zu groß. Stroustrup war stets darauf bedacht, den Umfang von C++ in Grenzen zu halten. Er wollte damit vermeiden, dass C++ nur noch von einer Handvoll Spezialisten verstanden wird und damit das Schicksal von ALGOL 68 teilt. Wie das OOP-Konzept in C++ realisiert wurde, zeigt die Tabelle im Überblick.
1.4 Die Weiterentwicklung von C
OOP-Konzept
Realisation in C++
Klasse
class
Instanz
Variable oder Objekt vom Typ class
Instanzvariable
class-Komponente
23
Klassenvariable
statiche class-Komponente
Methode
class-Komponentenfunktion
Klassenmethode
statische class-Komponentenfunktion
Protokoll
Deklaration aller Methoden
Vererbung
einfache/mehrfache Vererbung
Abstrakte Klasse
Klasse mit rein virtuellen Methoden
Parametrisierte Typen
Templates
Polymorphismus
Überladen von Funktionen/Operatoren, virtuelle Funktionen
Dynamisches Binden
virtuelle Funktionen Tab. 1.3: Realisierung der OOP-Konzepte in C++
1.4
Die Weiterentwicklung von C
Die ANSI/ISO C-Norm war Ende 1989 verabschiedet worden und wurde als ISO/IEC 9899:1900 publiziert. Damit war die Arbeit der ANSI C++-Gruppe keineswegs beendet. Die Bemühungen um eine Internationalisierung führten 1995 zu einer Ergänzung der C-Norm, dem Amendment 1, das zum ersten Mal auch internationale Zeichensätze in C einführte. Dazu erschien eine eigene Norm ISO/IEC 10646, die den Universal Multiple Octet Coded Character Set (UCS) definiert. Auf diese Norm geht die HeaderDatei zurück, die dann auch in C++ übernommen wurde. Java war also keineswegs die erste Programmiersprache, die internationale Zeichensätze unterstützte. Auch ein weiteres internationales Komitee NCEG (Numerical C Extensions Group) beschäftigte sich mit der Weiterentwicklung von C; insbesondere strebte man eine Standardisierung des Parallelrechnens und der Fließpunkt-Arithmetik an. Für das numerische Rechnen wurde ein eigener doppelt-genauer komplexer Zahlentyp long double complex entwickelt. Auch die Diskussionen und Arbeitspapiere des ANSI C++-Komitees wurden von der C-Gruppe zur Kenntnis genommen. Über ein Jahr diskutierte eine Untergruppe, ob und wie man objektorientierte Eigenschaften von C++, wie einfache Vererbung, Zugriffskontrolle und virtuelle Funktionen, in C zulassen sollte. Aus verschiedenen Gründen wurde dies aber später verworfen. Viele (nicht objektorientierte) Sprachelemente von C++ wurden aber als nützlich erkannt, unter anderem die Form der Kommentierung (mittels //), der bool-Typ (Schlüsselwort _BOOL), der sizeof-Operator und anderes mehr. Alle diese Bemühungen mündeten in der Verabschiedung einer neuen Norm ANSI/ ISO 9899:1999 für die nunmehr C99 genannte Programmiersprache. Wichtige Neuerungen von C99 sind:
24
1 Einleitung
왘 Neue Datentypen wie long long, long double complex und long double imaginary. 왘 Erweiterte int-Typen wie int32_t. 왘 Erweiterte Möglichkeiten bei der Fließpunkt-Arithmetik (Rundungsregeln, Aus-
nahmefälle). 왘 Übernahme von C++-Elementen wie const, sizeof und der Blockstruktur der Kon-
trollstrukturen; Letzteres erlaubt nun Definitionen innerhalb der FOR-Schleife. 왘 Arrays mit variabler Länge. 왘 Verkürzte Initialisierung von Strukturen.
1.5
Java als C++-Derivat Java is a new object-oriented programming language developed at Sun Microsystems to solve a number of problems in modern programming practice and to provide a programming language for the Internet. Java Homepage der Fa. SUN
1990 hatte der Software-Ingenieur J. Gosling begonnen, im Auftrag der Fa. SUN unter dem Projektnamen OAK eine neue objektorientierte Sprache zu entwickeln. Die Vorgaben waren 왘 einfache Entwicklungsmöglichkeiten von Applikationen 왘 Stabilität und Zuverlässigkeit von Programmen 왘 Plattformunabhängigkeit
Als Syntaxbasis wurde ein abgespecktes C++ verwendet. Das OAK-Projekt wurde 1994 auf Eis gelegt, da die Medienkonzerne wie Time Warner kein Interesse zeigten. Aus urheberrechtlichen Gründen wurde OAK in Java umbenannt. Als jedoch im Herbst gleichen Jahres P. Naughton und J. Payne einen WWW (World Wide Web)-Browser für SUN entwickelten, konnten sie auf die Grundlagen von OAK zurückgreifen. Im Januar erhielt dieser Browser den Namen HotJava. Die Möglichkeiten, die der Browser bot, waren bahnbrechend, sodass SUN das Java-Projekt als ClientServer-Technologie wieder aufnahm. Um Java populär zu machen, stellte SUN die Programmiersprache im Mai 1995 ins Internet. Dort gewann diese Sprache bei Internet-Surfern und Computerzeitschriften eine ungeheure Popularität, erlaubt Java doch neben separaten Programmen (Applikationen) auch die Ausführung eines so genannten Applets innerhalb einer WWW-Seite. Dieses Applet kann sogar an irgendeiner Internet-Adresse (URL, englisch Uniform Resource Locator) vorliegen. Hinzu kommt, dass nicht einmal ein Compiler notwendig ist; die Interpretation übernimmt ein passender Internet-Browser. Die damit eröffneten Möglichkeiten der Interaktivität und Animation gehen weit über das hinaus, was die Seitenbeschreibungssprache HTML (HyperText Markup Language) vermag. HTML ist ein Derivat der von IBM entwickelten General Markup Language (GML), die später als SGML (Standard General Markup Language) zur ISO-Norm erhoben wurde.
1.5 Java als C++-Derivat
25
Java erhielt von C++ die Syntax, die Kontrollstrukturen (bis auf goto) und die einfachen Datentypen (bis auf char, enum, struct, union). Für den Zeichensatz wurde der 16Bit-Uni(-versal)code gewählt, der aus HTML bekannt ist. Damit wurde das leidige Problem der westeuropäischen Umlaute und der asiatischen Sprachen gelöst. Alle einfachen Datentypen wurden einheitlich als vorzeichenbehaftet (signed) festgelegt, die Bytezahl von int-Typen wurde auf vier fixiert. Ebenfalls übernommen wurden die einfache Vererbung und die Ausnahmefälle (exceptions). C-Funktionen wie malloc(), die einen direkten Speicherzugriff gestatten, wurden aus Sicherheitsgründen außer Kraft gesetzt. Nicht mehr enthalten sind die mehrfache Vererbung, die Template-Klassen und die Möglichkeit, nicht objektorientiert zu programmieren. Das folgende Beispiel eines Applet zeigt die formale Ähnlichkeit mit C++: import java.awt.*; import java.applet.*; public class KurveApp extends Applet // einfache Vererbung von Applet auf KurveApp { int f(double x) // Schwebungsfunktion {return (int)((Math.cos(x/5)+Math.sin(x/7)+2)*50); } public void paint(Graphics g) { for (int x=0; x10-12, gehe nach (3) Rückgabe p
Übung (2.2): Erstellen Sie dazu eine Wertetabelle für x = 20, y = 3 (1) (2) (3) (4) (5) (6) (7) (8)
Eingabe x,y>0 (natürliche Zahlen) Wenn x0 (natürliche Zahl) (2) Setze x = 4 (3) Setze y = 5 (4) Setze z = 1 (5) Wenn x > a, gehe nach (10) (6) Setze x = x + y (7) Setze y = y + 2 (8) Setze z = z + 1 (9) Gehe nach (5) (10) Rückgabe z
Übung (2.4): Erstellen Sie dazu eine Wertetabelle für a = 64 (1) (2) (3) (4) (5) (6) (7) (8)
Eingabe a>0 (natürliche Zahl) Setze x = 0 Setze y = a Wenn y > 1, gehe nach (8) Setze x = x + y Setze y = y / 2 (ganzzahlige Division) Gehe nach (4) Rückgabe x
3
Lexikalische Elemente und Datentypen The C vocabulary was supplementary to others and consisted entirely of scientific and technical terms. Any word could be strengthened by the affix plus –, or, for still greater emphasis, doubleplus. George Orwell (1948) im Roman »1984«
In diesem Kapitel werden die wichtigsten Syntaxregeln und die fundamentalen Datentypen besprochen. Die Sprachbeschreibung von C++ kennt folgende Art von lexikalischen Elementen, Literale genannt: 왘 Bezeichner 왘 Schlüsselwörter 왘 Literale 왘 Operatoren 왘 Separatoren (Trennzeichen) 왘 Kommentare
3.1
Zeichensatz
C++ kennt zwei Arten von Zeichen: 왘 char // ASCII-Zeichen (character) 왘 wchar_t // Multibyte-Zeichen (wide character)
Multibyte-Zeichen sind insbesondere die Zeichen des Uni(-versal)Codes, der in Java und bei Internet-Browsern große Bedeutung gewonnen hat. Unter UNIX wird in der Regel der bekannte ASCII-Zeichensatz (American Standard Code for Information Interchange) verwendet. Da in einem 8-Bit-Code 28=256 Zeichen möglich sind, belegt der ASCII-Code mit den Zeichen Nr. 0 bis Nr. 127 genau die erste Hälfte. Die Verwendung der Zeichen Nr. 128 bis Nr. 255 ist bei Verwendung des ASCII-Codes maschinenabhängig. Die Darstellung hängt vom Betriebssystem und von dem jeweils geladenen Tastaturtreiber ab. Ein typisches Beispiel für ein Durcheinander von Zeichensätzen erhält man, wenn man einen Windows-Text mit Umlauten in einem DOS-Fenster liest. Die ersten 256 Zeichen des Uni(-versal)Code von \u0000' bis '\u00FF', Latin-1 genannt, sind durch die Norm ISO 8859-1 fest gelegt und in den ISO C++-Standard (Anhang E) übernommen worden. Wie Abb. 3.1 zeigt, stimmt die erste Hälfte von Latin-1, Basic Latin genannt, mit dem ASCII-Code überein.
44
3 Lexikalische Elemente und Datentypen
Insgesamt umfasst der UniCode 216 = 2562 = 65.536 Zeichen, von denen bisher 38.885 Zeichen festgelegt wurden, darunter die meisten asiatischen Zeichensätze wie Katakana, Hiragana, Thai, Devanagari und Bengali. Die kyrillischen Buchstaben sind z.B. festgelegt durch ISO 8859-1, die hebräischen Zeichen durch ISO 8859-8. Besonders kompliziert erwies sich das Einordnen aller Han-Zeichen, die von den vier Ländern China, Japan, Vietnam und Korea verwendet werden. Sie werden fixiert auf den Bereich '\u4e00' bis '\u9fff', das sind über 20,000 Zeichen! Ein Problem ist, dass der Zugriff auf diese Zeichensätze unter C++ noch nicht realisiert ist, da die meisten Rechner die asiatischen Zeichensätze nicht darstellen können; dies ist bis jetzt nur bei den asiatischen Windows-Versionen implementiert. Auch die meisten Internet-Browser sind bereits imstande, einige außereuropäische Zeichensätze darzustellen.
Abbildung 3.1: Der UniCode-Zeichensatz Latin-1
3.2 Schlüsselwörter
45
Der ASCII-Code und Latin-1 erfüllen die Bedingung, dass die Kleinbuchstaben 'a' ... 'z' und Großbuchstaben 'A' ... 'Z' in zwei Blöcken jeweils lückenlos aufeinander folgen. Damit lässt sich jedes Zeichen durch Hochzählen von 'a' oder 'A' finden: 'e' = char('a'+4) 'H' = char('A'+7)
Ein Test auf Klein- oder Großbuchstaben ergibt sich damit zu: if ((ch >= 'a') && (ch= 'A') && (ch
++
--
*
=
==
!=
&&
||
*=
/=
%=
+=
-=
=
&=
^=
|=
::
Es gibt jedoch auch Operatoren, die durch Schlüsselwörter definiert sind. Dies sind: new, delete, sizeof
und die Operatoren zur Typumwandlung: const_cast, static_cast, dynamic_cast,reinterpret_cast
Treffen in einem Ausdruck mehrere Operatoren aufeinander, so muss die Priorität (Auswertungsreihenfolge) und die Assoziativität (Zusammenfassen von links oder rechts) festgelegt werden. Siehe hierzu Tabelle 3.1:
3.4 Operatoren
47
Priorität/Assoziativität
Operator
Bedeutung
17 r
::
globaler Geltungsbereich
17 l
::
Klassen-Geltungsbereich
16 l
->,.
Elementauswahl
16 l
[]
Indexoperator
16 l
()
Funktionsaufruf
16 l
sizeof
Größe
16 r
dynamic_cast, const_cast, reinterpret_cast static_cast,typeid
Typumwandlung
15 r
++,--
Inkrement, Dekrement
15 r
~
bitweise Negation
15 r
!
logische Negation
15 r
+,-
Vorzeichen
15 r
*,&
Inhaltsoperator, Adressoperator
15 r
()
Typkonversion (cast)
15 r
new, delete
dynamische Speicherverwaltung
14 l
->,.*
Deferenzierung
13 l
*,/,%
Multiplikative Operatoren
12 l
+,-
Additive Operatoren
11 l
>>,
=,y>0 ein! "; cin >> x >> y; // Vorbedingung (x>0) && (y>0) && (x>y) int r=x,q=0; assert(x == q*y+r); // Zusicherung while(y0) //=> (q = x/y) && (r = x % y) cout =0) && (b>=0) int x=a,y=b,z=0; assert(z + x*y == a*b); // Zusicherung while(x>0) { if (x % 2==1) z += y; y *= 2; x /= 2; assert(z + x*y == a*b);// Invariante(z+x*y=a*b)&&(x>0) } assert(z + x*y == a*b); // Nachbedingung //=> (z + x*y = a*b) && (x=0) //=> (z = a*b) cout > b; while(b>0) { a--; b--; } cout > b; int p=0; while(a>0) { int q=0; while(q ()
2
r,0
2
r = a % b;
=%;
3
r,a,b
3
a = b;
=;
2
a,b
2
b = r;
=;
2
b,r
2
return a;
return ;
2
a
Summe=15
Die Auswertung ergibt: 왘 N1 = 15 왘 N2 = 12 왘 n1 = 8 왘 n2 = 4
1 Summe=12
9.2 Software-Metrik
191
und somit die Halstead-Metrik: 왘 N = N1 + N2 = 27 왘 n = n1 + n2 = 12 왘 V = Nlog2(n) = 96.8 왘 D =(n/2)(N2/n2) = 6*3 = 18 왘 E = 1733.4
Manche Autoren schätzen die Halstead-Länge ab durch die Gleichung N = n1log2(n1)+n2log2(n2) = 8*3+4*2 = 32 Hier erhält man den Näherungswert 32 für den exakten Wert 27.
9.2.3 Der Wartbarkeitsindex Das renommierte Software Engineering Institut (SEI) der Carnegie Mellon-University hat aus den Maßzahlen von McCabe und Halstead einen Wartbarkeitsindex MI (maintainability index) erstellt. Er berücksichtigt noch die Parameter: 왘 LOC = Anzahl der Programmzeilen (lines of code) 왘 CM = Anteil von Kommentarzeilen am Quellcode (comment)
Kennzeichnet man Mittelwerte durch spitze Klammern (), so gilt für den Wartbarkeitsindex des SEI MI = 171 – 5.2log2 -0.23-16.2log2-50sin( 2.4 < CM > ) Diese etwas merkwürdig aussehende Formel ist entstanden durch statistische und empirische Bewertung von Standard-Software, ausgeführt von der Fa. Hewlett Packard. Wählt man die Maßzahlen der ggT()-Funktion als Mittelwert, so erhält man mit 왘 = 27 왘 = 2 왘 = 13 왘 = 0.07
den Wartbarkeitsindex MI = 171-5.2*log2(27)-0.23*2-16.2*log2(13)-50*sin(0.59) = 65 was nur ein mittelmäßiger Wert ist (empfohlen MI > 80).
192
9.3
9 Elemente des Software-Engineerings
Stichpunkte der UML UNIFIED MODELING LANGUAGE
Abbildung 9.2: Das Logo der UML
Die Unified Modeling Language (UML) ist ein von dem internationalen Gremium OMG (Object Management Group) standardisiertes System, das die besten Analyse- und Entwurfsmethoden der Software-Entwicklung zusammenfasst. Die UML ist natürlich zu komplex, um in einem Abschnitt eines Buchs abgehandelt zu werden. Deswegen wird die Darstellung auf einige wesentliche Punkte beschränkt; mit Hilfe der Literatur kann sich der Leser oder die Leserin in das Thema vertiefen.
9.3.1 Was ist die UML? Die UML ist die Standardsprache mit überwiegend grafischer Notation zur Visualisierung, Spezifikation, Konstruktion und Dokumentation von Software-Modellierung. Sie bietet: 왘 Vereinheitlichung von Informationssystemen und der Methodik des Software-
Engineerings 왘 Möglichkeit zur Spezifikation, Visualisierung und Dokumentation beliebiger Soft-
ware-Systeme 왘 Anwendung des objektorientierten Paradigmas 왘 fortgeschrittene Konzepte wie Frameworks und Patterns
Sie kann bei allen Stationen des Software-Entwicklungszyklus eingesetzt werden und ist unabhängig von speziellen Programmiersprachen. Die wichtigsten Ziele der UML sind: 왘 den Anwendern der UML einsatzbereite und wirkungsvolle Hilfsmittel an die
Hand zu geben, damit neue Modelle entwickelt und bestehende verbessert werden können 왘 Mechanismen zu erkennen, die es erlauben, bestehende Konzepte der Software-
Entwicklung zu erweitern 왘 Unabhängigkeit von Entwicklungsumgebungen und Programmiersprachen zu
erlangen 왘 Schaffung einer formalen Basis zum Verständnis einer Modellierungssprache 왘 Entwicklung neuer Werkzeuge für den wachsenden Markt von objektorientierten
Entwicklungs-Tools
9.3 Stichpunkte der UML
193
Die UML enthält auch einige neue Konzepte, wie: 왘 Mechanismen für Erweiterbarkeit (Stereotypen usw.) 왘 Threads und Prozesse 왘 Distributive und parallele Systeme 왘 Muster und Kollaborationsdiagramme 왘 Aktivitätsdiagramme 왘 Interfaces und Komponenten
9.3.2 Entwicklung der UML Viele der Ideen, die in die UML eingegangen sind, stammen aus ganz unterschiedlichen Quellen und aus einer Zeit, als die Objektorientierung noch kein Schlagwort war. Diese Ideen verbinden sich nun zu einem zusammenhängenden Ganzen. Man schätzt, dass es zwischen 1989 und 1994 etwa 50 verschiedene Ansätze zur objektorientierten Modellierung (OOM) gab. Diese Methoden der ersten Generation waren nicht standardisiert und führten zu einem »Krieg der Methoden«. 1994 schloss sich Jim Rumbaugh der Firma Rational Software Corp. an. Gemeinsam mit G. Booch arbeitete Rumbaugh im Oktober 1995 am Entwurf der UML-Version 0.8. Im Herbst 1995 stieg auch Ivar Jacobson bei Rational ein. Die Ansätze der drei Amigos: 왘 Arbeiten von Grady Booch 왘 OMT (Object Modelling Technique) von Jim Rumbaugh 왘 OOSE ( Object Orientated Software Engineering) von Ivar Jacobson.
wurden schließlich vereinheitlicht und führten 1997 zur UML-Version 1.0, die im Zuge der Weiterentwicklung 1999 bis zur Version 1.3 gelangte. Dieser einheitlicher Entwurf zur UML erlaubt nun eine weitgehende objektorientierte Modellierung von Software – ohne dass auch nur eine Zeile Quellcode geschrieben wird.
9.3.3 Was ist die OMG? Die 1989 gegründete Object Management Group (OMG) ist eine internationale Organisation von über 800 Mitgliedern, die die wichtigsten Software- und Hardware-Firmen vertreten. Mitgliedsfirmen sind u.a. Digital Equipment., Hewlett Packard, i-Logix, IntelliCorp, IBM, MCI Systemhouse, Microsoft, Oracle, Rational Software, Texas Instruments und UniSys. Hauptziel der OMG ist die Förderung der Wiederverwendbarkeit, Portabilität und Austauschbarkeit von Software auf verteilten, heterogenen Plattformen und Betriebssystemen.
9.3.4 Das objektorientierte Paradigma Alle Dinge sind entweder materiell oder geistig; erstere existieren auch, ohne dass sie von jemandem wahrgenommen werden. Materielle und geistige Dinge haben folgende gemeinsame Eigenschaften (vgl. Abbildung 9.3):
194
9 Elemente des Software-Engineerings
왘 Strukturelle Eigenschaften bestimmen alle möglichen Zustände, die die Dinge wäh-
rend ihrer Existenz durchlaufen. 왘 Verhaltensmuster bestimmen alle Aktivitäten der Dinge – die von der Umwelt aus-
gelösten Aktionen und Reaktionen. Die Struktur und das Verhalten werden durch die Attribute und Operationen von Objekten dargestellt. Neben den Objekten gibt es noch Links und Szenarios. Links stellen die Verknüpfungen dar, die alle in einer bestimmten Relation stehenden Dinge verbinden. Sie sind jedoch nicht selbst Teil der Dinge. Links sind Instanzen von Assoziationen (allgemeine Relationen zwischen Objekten). Alle Relationen zwischen Objekten sind entweder Assoziationen, Aggregate oder Kompositionen. Die Assoziationen werden in Kapitel 13 noch ausführlicher behandelt. Szenarios stehen stellvertretend für alle Interaktionen mittels Botschaften oder Kommunikation.
Abbildung 9.3: Das objektorientierte Paradigma der UML
9.3.5 Wichtige Diagramme Die wichtigste Aufgabe der UML ist die grafische Darstellung aller möglichen Modellierungskonzepte. Zum gegenwärtigen Zeitpunkt gibt es sieben verschiedene Diagrammtypen, die jeweils eine spezielle Modellierung ermöglichen: 왘 Klassen-/Objektdiagramme (nach Booch) 왘 Interaktionsdiagramme (darunter Sequenz-, Kollaborationsdiagramme) 왘 Paketdiagramme (packet)
9.3 Stichpunkte der UML
195
왘 Zustandsdiagramme (statechart nach David Harel) 왘 Aktivitätsdiagramme (activity) 왘 Verteilungsdiagramme (deployment) 왘 Anwendungsdiagramme (use case nach Rumbaugh)
Zur Erstellung dieser UML-Diagramme kann eine spezielle Software verwendet werden. Abbildung 9.4 zeigt die Benutzeroberfläche der Software »Together Control Center 4.0« der Firma TogetherSoft, die es unter anderem erlaubt, aus einem C++/JavaQuellcode automatisch ein Klassendiagramm zu erzeugen.
Abbildung 9.4: Benutzeroberfläche der Software »Together Control Center 4.0«
Es ist nicht möglich, alle der oben genannten Diagramme in diesem Rahmen darzustellen. Wir beschränken uns auf die drei am häufigsten gebrauchten Diagramme, alle weiteren finden sich in der angegebenen Literatur.
9.3.6 Das Klassendiagramm Für Klassendiagramme gibt es drei grundlegende Sichtweisen: 왘 konzeptionell 왘 spezifizierend 왘 implementierend
196
9 Elemente des Software-Engineerings
Abbildung 9.5: Das Klassendiagramm (aus »UML konzentriert«[14])
Das Klassendiagramm beschreibt alle Typen von Objekten und ihre statischen Beziehungen. Abbildung 9.5 zeigt das Zusammenspiel der Klassen Privatkunde, Firmenkunde, Auftrag und Auftragsposition. Die statischen Beziehungen sind: 왘 Assoziationen werden durch beschriftete Linien dargestellt; hier: Kunde erteilt einen
Auftrag.
9.3 Stichpunkte der UML
197
왘 Abgeleitete Objekte werden durch Vererbung, d.h. durch eine nicht gefüllte Pfeil-
spitze, gekennzeichnet. Hier ist ein Privatkunde ein Ableitungsobjekt von Kunde. 왘 Spezielle Assoziationen sind die Kardinalitäten; sie geben an, wie viele Objekte
beteiligt sind. Die Bezeichnungsweise von Kardinalitäten ist 왘 1 (genau ein Objekt) 왘 * (viele; d.h. Null oder mehr) 왘 0..1 (höchstens eins) 왘 m..n (mindestens m, höchstens n)
Zu jedem Auftrag gehört genau ein Kunde (Kardinalität 1); umgekehrt kann ein Kunde keinen oder mindestens einen Auftrag erteilen (Kardinalität *) (vgl. Abbildung 9.5).
9.3.7 Das Aktivitätsdiagramm Das Aktivitätsdiagramm ist insbesondere nützlich zur Beschreibung von Arbeitsvorgängen, die teilweise auch parallel verlaufen können. Es basiert auf Vorarbeiten von Jim Odell (zustandsbasierte Modelltechnik (SDL)); die Symbolik orientiert sich etwas an den Petri-Netzen. Ein Aktivitätsdiagramm kann die folgenden geometrischen Symbole enthalten: 왘 Ein gefüllter Kreis bedeutet den Eingang. 왘 Zwei konzentrische Kreise, davon der innere gefüllt, bedeuten den Ausgang. 왘 Eine Raute stellt einen Entscheidungsvorgang dar. Der waagrechte Ausgang wird
bei wahrer Bedingung durchlaufen, sonst der senkrechte Ausgang. 왘 Ein dicker waagrechter Balken bedeutet Synchronisation; d.h., alle Aktivitäten, die
den Balken treffen, starten wieder synchron. Wird eine Aktivität in mehrere Teilaktivitäten zerlegt, so dienen die Ein- und Ausgangspunkte als Schnittstelle; d.h., sie können an diesen Punkten aneinander gereiht werden. Werden andauernde (z.B. rund um die Uhr laufende) Aktivitäten beschrieben, so muss das Aktivitätsdiagramm kein Ausgangsymbol haben. Beim obigen Diagramm ist nicht ersichtlich, welches Objekt für eine bestimmte Aktivität zuständig ist. Deswegen hat man die Aktivitätsdiagramme durch Einbeziehung von Verantwortlichkeitsgrenzen (in UML swimlanes genannt) erweitert. Alle Aktivitäten werden durch senkrechte Striche in Zonen eingeteilt, die die Verantwortlichkeitsgrenzen der beteiligten Objekte angeben.
198
9 Elemente des Software-Engineerings
Abbildung 9.6: Das Aktivitätsdiagramm (aus »UML konzentriert"[14])
9.3.8 Das Anwendungsfalldiagramm Das Anwendungsfalldiagramm geht auf die use case-Diagramme von Jacobson zurück, die nun Bestandteil der UML geworden sind. Zentraler Teil eines Anwendungsfalldiagramms sind die Akteure (englisch actors). Abbildung 9.7 stellt einen Finanzhandelsplatz mit den Akteuren Händler, Verkäufer, Buchhalter und Manager dar. Akteure müssen aber nicht unbedingt Menschen sein; es kann auch ein Buchhaltungssystem sein, das jede Nacht die Konten aktualisiert. Da kein System nach außen abgeschlossen ist, ist es nicht immer einfach zu entscheiden, welche externen Akteure in das Diagramm aufgenommen werden sollen. Die Anzahl der aufgenommenen Akteure ist entscheidend für den Programmieraufwand. Jacobson gibt als Faustregel vor, dass 20 Akteure etwa ein 10 Mannjahr-Projekt darstellen.
9.4 Kriterien für Programmiersprachen
199
Abbildung 9.7: Das Anwendungsfalldiagramm (aus »UML konzentriert«[14])
9.4
Kriterien für Programmiersprachen
Language design is compiler construction. (N. Wirth) In diesem Abschnitt werden stichwortartig wichtige Kriterien für Programmiersprachen zusammengestellt und kurz kommentiert. (1) Effizienz (efficiency) 왘 Effizienz beim Übersetzen: Das in der Programmiersprache geschriebene Pro-
gramm soll möglichst wirkungsvoll (effizient) in Maschinensprache übersetzt werden können. Effizienz kann gemessen werden mittels Speicherbedarf oder Laufzeit. 왘 Effizienz beim Programmieren: Der Programmierer soll die Gewissheit haben, dass
er korrekte, präzise und schnelle Programmabläufe (nach dem Übersetzen) erhält. Es sollte möglich sein, komplexe Daten und Abläufe effizient zu formulieren. 왘 Möglichkeit der Code-Optimierung
(2) Allgemeinheit (generality) 왘 Eine Programmiersprache sollte möglichst viele Programmierfälle und Datentypen
unterstützen (das Gegenteil ist Einfachheit). 왘 Gleichartige Operatoren und Konstrukte sollten bei verschiedenen Datentypen
gleichartige Wirkungen hervorrufen.
200
9 Elemente des Software-Engineerings
왘 Umgekehrt dürfen auch verschiedenartige Programmanweisungen nicht gleichar-
tig aussehen. (3) Orthogonalität (orthogonality) 왘 Orthogonalität bedeutet, dass die Kombinationen aller in der Programmiersprache
zugelassenen Konstrukte möglich und sinnvoll ist. 왘 Keine unerwarteten Einschränkungen oder unerwartetes Verhalten (Gegenbeispiel:
Parameterübergabe in C/C++ von Reihungen) (4) Uniformität (uniformity) 왘 Gleichartige Operationen und Konstrukte werden gleichartig implementiert. 왘 Gleichartige Wertzuweisungen für alle Datentypen, nicht nur für einfache 왘 Funktionen liefern alle möglichen Datentypen. 왘 Kompatibilität zwischen Zeichen und Zeichenketten
(5) Einfachheit (simplicity) Everything should be made simple as possible but not simpler. (A. Einstein) 왘 einfach zu lernen 왘 einfach zu schreiben 왘 einfach zu verstehen (es muss möglich sein, selbst erklärende Programme zu
schreiben und durch Kommentare zu dokumentieren) 왘 einfach zu warten 왘 Unterstützung der Modularität 왘 wenige Programmkonstrukte und Schlüsselwörter
Die Anzahl der Schlüsselwörter verschiedener Programmiersprachen zeigt die folgende Tabelle: C
32
PASCAL
35
MODULA-2
40
C++
63
ADA
83
(6) Maschinenunabhängigkeit (machine independence) 왘 Eine Programmiersprache sollte auf mehreren Plattformen verfügbar sein. 왘 Eine Programmiersprache soll plattformunabhängig sein; d.h. die Übertragbarkeit
eines Quellcodes auf ein anderes Betriebssystem bzw. auf einen anderen Rechner sollte gewährleistet sein. 왘 Deklaration geeigneter Konstanten (z.B. größte Ganzzahl, kleinste Maschinenzahl) 왘 Anpassungsmöglichkeit an andere Rechnerarchitekturen (Speicher usw.) 왘 Unterstützung gemeinsamer Ein-/Ausgabeströme und Handling von Dateien
9.4 Kriterien für Programmiersprachen
201
Beispiel: Die Entwickler von ALGOL 60 konnten sich nicht einigen, welche Ein- und Ausgabefunktionen notwendig waren; jedes Institut bzw. jeder Hersteller musste dazu eigene Maschinenroutinen schreiben. (7) Sicherheit, Zuverlässigkeit (security, reliability) Maximize the number of errors that could not be made. C. A. R. Hoare 왘 weitgehende Fehlererkennung beim Erstellen des Quellcodes 왘 Hilfestellung beim Debuggen von Programmen
Möglichkeiten dazu sind: 왘 Explizite Deklaration aller Variablen und Sprungmarken 왘 rigorose Typenprüfung bei Wertzuweisung 왘 striktes Funktionsprototyping 왘 Schutz von Konstanten 왘 Prüfung auf nicht initialisierten Variablen 왘 Prüfung von Array-Indizes 왘 Verbot von Nebeneffekten 왘 keine Unterstützung von Default-Werten 왘 kurze Lebensdauer von Variablen 왘 Eindeutigkeit und Lesbarkeit von Operatoren 왘 Blockstruktur von Funktionen und Klassen 왘 Unterstützung von Selbstdokumentation 왘 Möglichkeit von langen, selbst erklärenden Bezeichnern
Beispiel: Verlust der Mariner 1-Marssonde wegen eines Programmierfehlers in FORTRAN
C
DO 99 I = 1.10
statt
DO 99 I = 1,10
Hier wurde durch ein Versehen ein Komma durch einen Punkt ersetzt. Wegen der automatischen Variablendeklaration in FORTRAN interpretierte der Compiler die DOAnweisung als Wertzuweisung für die reelle Variable D099I; die Schleife mit Zähler I wurde daher nicht ausgeführt. 왘 Als Zuverlässigkeit kann man die Wahrscheinlichkeit definieren, mit der ein Pro-
gramm seine Aufgabe für eine vorgegebene Zahl von Eingabefällen erreicht. 왘 Bei allem Streben nach Sicherheit und Zuverlässigkeit darf die Arbeit des Program-
mierers nicht behindert werden. (8) Präzise Beschreibbarkeit (preciseness) 왘 Genau definierte Syntax, Beschreibung durch BNF (Backus-Naur-Form) 왘 Genau definierter Standard durch internationale Norm
202
9 Elemente des Software-Engineerings
Beispiel: Die Programmiersprache FORTRAN war nur umgangssprachlich definiert. C. A. R. Hoare konnte seinen Algorithmus quicksort erst formulieren, nachdem er ALGOL-W gelernt hatte. (9) Modularität (modularity) 왘 Eine Programmiersprache muss die Möglichkeit bieten, Programme in separaten
Modulen zu schreiben, diese einzeln zu compilieren und später zu einem Projekt zusammenzubinden. (10) Unterstützung von modernen Software-Techniken 왘 Optimierende Compiler 왘 GUI (Grafisches User Interface) 왘 Unterstützung abstrakter Datentypen 왘 Datenkapselung 왘 Wiederverwendbarkeit 왘 Polymorphismus 왘 generische Algorithmen 왘 Vererbung 왘 Schnittstellen 왘 Unterstützung von Ausnahmefällen
Zusammenfassung: Alle diese Kriterien können nicht in gleicher Weise erfüllt werden. Einfachheit und Allgemeinheit widersprechen sich.
9.5
Forderungen des Software-Engineerings
Das Software-Engineering beinhaltet das Ziel, den vollständigen Entwicklungszyklus von Software-Projekten vom anfänglichen Entwurf bis zum Testen und zur Wartung auf eine methodische und handhabbare Weise zu organisieren. Qualität, Leistung und Kosten der Software müssen vorhersehbar sein. Ein angemessener Kompromiss zwischen Kosten und Zuverlässigkeit muss erreicht werden. Weitere Anforderungen an Software sind: (11) Adaptierbarkeit 왘 Eine Software ist adaptierbar, wenn sie auf verschiedene Aufgabenstellungen und
variierende Benutzeranforderungen hin angepasst werden kann. (12) Robustheit 왘 Eine Software ist robust, wenn sie unempfindlich gegen falsche Eingaben ist.
(13) Funktionale Korrektheit 왘 Eine Software heißt funktional korrekt (auch effektiv), wenn sie genau die gege-
bene Aufgabenstellung erfüllt.
9.5 Forderungen des Software-Engineerings
203
(14) Ausfallsicherheit 왘 Eine Software arbeitet ausfallsicher, wenn ein Fehler, eine Störung des Servers oder
der Ausfall von Peripherie-Geräten (in Netzwerken Clients) oder ein Übertragungsfehler bei Ein- und Ausgabegeräten das korrekte Arbeiten der Software nicht unmöglich machen, sondern höchstens behindern. (15) Verfügbarkeit 왘 Eine Software heißt in hohem Maße verfügbar, wenn die Zeitdauer des fehlerfreien
Verlaufs sehr groß ist im Vergleich zu der Zeitraum, in dem das Programm ausgefallen ist oder wieder installiert werden muss. Der Grad der Verfügbarkeit V ist definiert als:
V=
MTBF MTBF + MTR
Dabei bedeutet: 왘 MTBF = Mean Time Between Failures (mittlere Zeit zwischen zwei Ausfällen) 왘 MTR = Mean Time to Repair (mittlere Zeit zur Wiederherstellung)
(16) Benutzerfreundlichkeit 왘 Ein Programm heißt benutzerfreundlich, wenn es auch von einem nicht geschulten
Anwender leicht zu bedienen ist und sich erwartungsgemäß verhält.
10
Ausnahmebehandlung
Die Steuerung von strukturierten Ausnahmebehandlungen (englisch exception handling) ist ein erklärtes Ziel von modernen Programmiersprachen. Die Wahl des Pentagons 1982 fiel deshalb auf die Programmiersprache ADA, da diese (neben anderen Eigenschaften wie Synchronisation) auch eine differenzierte Ausnahmebehandlung erlaubte. Wichtige Ausnahmetypen sind in ADA bereits vordefiniert, wie 왘 constraint_error (Indizes bei Arrays usw.) 왘 numeric_error (Overflow, Division durch Null) 왘 program_error (Fehler bei dynamischer Deklaration) 왘 storage_error (Fehler bei dynamischer Speicherbelegung) 왘 tasking_error (Fehler bei konkurrierenden Prozessen)
Die Ausnahmebehandlung wurde in C++ relativ spät und erst nach langer Diskussion (1984–1989) implementiert. Der Entwurf von Andrew Koenig und B. Stroustrup wurde dann 1990 der USENIX C++-Konferenz vorgelegt und ist nun Bestandteil der ISONorm. Ein üblicher Weg bei der Fehlerbehandlung ist zunächst die Rückgabe eines Wertes, der nicht als Funktionswert auftreten kann und somit einen Fehler anzeigt. Bei der Quadratwurzel kann, z.B. im Fehlerfall, ein negativer Wert wie -1 zurückgegeben werden. Eine andere Möglichkeit ist das Setzen einer globalen Konstanten wie errno in C. Hier kann durch Abfragen des Fehlerwerts errno geprüft werden, ob ein Fehler vorliegt, wie 왘 EDOM (Domain Error – außerhalb des Definitionsbereichs) 왘 ERANGE (Out of range – Overflow)
Die Werte von errno finden sich in der C-Headerdatei <errno.h>. Beide oben erwähnten Fehlermethoden entsprechen nicht dem Prinzip der Objektorientierung.
10.1 Ablauf eines Ausnahmefalls Für den Fall, dass auftretende Fehler vom aufrufenden Programmteil behandelt werden können, stellt C++ folgenden Ausnahmemechanismus auf: 왘 Ein Funktionsaufruf wird versucht (englisch try). 왘 Wird ein Fehler entdeckt, den die Funktion nicht beheben kann, wird eine Aus-
nahme geworfen (englisch throw).
206
10 Ausnahmebehandlung
왘 Die Ausnahme wird von einem anderen Programmteil zur Fehlerbehandlung auf-
gefangen (englisch catch) und kann wiederum einem anderen Programmteil den Fehler »zuwerfen«. Damit ergibt sich folgendes Schema für den Ablauf eines Ausnahmefalls: try { // Funktionsaufruf catch(Typ 1) throw Fehler } catch(Typ 1) { // Fehlerbehandlung vom Typ 1 } catch(Typ 2) { // Fehlerbehandlung vom Typ 2 }
Es gibt auch eine catch()-Anweisung, die jeden Datentyp aufnimmt. Dies ist: catch(...) // genau 3 Punkte (Ellipse) { cerr #include using namespace std; double division(int a,int b) throw (runtime_error) { if (b == 0) throw runtime_error("Nenner Null"); return double(a)/b; } int main() { int x,y; cout > x >> y; try{ cout > y; try{
208
10 Ausnahmebehandlung
if (x < 0) throw Domain(); if (fabs(x) < eps) throw Singularity(); if (y