Klaus Schmaranz
Softwareentwicklung in C++ 18. November 2002
Springer-Verlag Berlin Heidelberg NewYork London Paris To...
31 downloads
1403 Views
5MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Klaus Schmaranz
Softwareentwicklung in C++ 18. November 2002
Springer-Verlag Berlin Heidelberg NewYork London Paris Tokyo Hong Kong Barcelona Budapest
Vorwort des Autors
C++ ist aus vielen guten Gr¨ unden derzeit eine der am weitesten verbreiteten objektorientierten Programmiersprachen. Einer der wichtigsten der angesprochenen vielen guten Gr¨ unde f¨ ur diese Verbreitung ist sicherlich die Flexibilit¨at der Sprache, die es erlaubt praktisch von tiefster Maschinenebene bis zu beliebig hohen Abstraktionslevels alle Probleme auf eine saubere und konsistente Art anpacken zu k¨ onnen. Durch seine mittlerweile schon recht lange Geschichte (bezogen auf die schnelllebige Welt der Informatik) hat der Sprachstandard von C++ im Lauf ¨ der Zeit bereits einige kleine und gr¨ oßere Anderungen hinter sich gebracht. Alle diese Anpassungen waren in meinen Augen immer sehr durchdachte Verbesserungen, die dazu gef¨ uhrt haben, dass man gewisse Dinge noch eleganter l¨osen konnte als zuvor. Vor allem hatten die angesprochenen Ver¨anderungen zum allergr¨ oßten Teil den Charakter von Erweiterungen, es wurden also keine k¨ unstlichen Inkompatibilit¨ aten erzeugt, die ein moving Target in der Entwicklung dargestellt h¨ atten. Ich bin selbst in die C++ Entwicklung bereits zu Zeiten der sehr fr¨ uhen Versionen eingestiegen und war begeistert von den M¨oglichkeiten, elegante L¨ osungen f¨ ur Probleme zu konzipieren. Jetzt, viele Jahre sp¨ ater, bin ich von C++ in seiner heutigen Form mehr begeistert denn je. Eines soll allerdings auch nicht verschwiegen werden: Um C++ wirklich zu beherrschen, braucht man viel Erfahrung! Vor allem muss man den Sinn hinter den einzelnen Konstrukten der Sprache wirklich verstanden haben, um sie richtig einsetzen zu k¨ onnen. Die Gefahr, gewisse Features aufgrund von Unverst¨ andnis zu missbrauchen und dadurch unsauberen Code zu erzeugen, ist nicht gering! Leider muss ich sagen, dass gar nicht so wenige Entwickler, die C++ einsetzen, die Sprache nicht in vollem Umfang beherrschen. Jedoch, und das ist genau das Problematische daran, glauben die meisten dieser Entwickler, dass sie gut mit C++ umgehen k¨ onnten. Aus diesem Grund habe ich versucht, die Erfahrungen aus meinen eigenen Fehlern sowie die Erfahrungen mit Fehlern und Unverst¨ andnis Anderer in dieses Buch einzubringen. Das Buch ist nach bestem Wissen und Gewissen so gestaltet, dass C++ und die wichtigen Aspekte, die erst eine saubere Objektorientierung ausmachen, so aufbauend und gut strukturiert wie m¨oglich vermittelt werden.
VI
Vorwort des Autors
Es ist das vorliegende Werk auch zus¨atzlich noch ein Resultat aus seiner Verwendung im Rahmen eines im Studium sehr fr¨ uh angesiedelten Programmierpraktikums an der Technischen Universit¨at Graz. Aus den Reihen der Studierenden, die quasi als “Versuchskaninchen” fungierten kamen sehr viele und sehr fruchtbare Anregungen, was den Aufbau und gewisse Erkl¨arungen betrifft. Das Buch wurde aber nicht nur von Leuten gelesen, die C++ erst lernen mussten, sondern auch von einigen Leuten, die bereits echte C++ Profis sind. Von beiden Lesergruppen gab es durchgehend sehr positive Reaktionen, sowohl was die fachliche Seite als auch was die Strukturierung und die leserische Seite betrifft (der Begriff “literarische” Seite w¨are wohl doch etwas zu hoch gegriffen :-)). Auch die Profis waren angetan davon, dass sie in einigen Punkten durch die Lekt¨ ure dieses Buchs ein tieferes Verst¨andnis f¨ ur gewisse Dinge vermittelt bekamen und entsprechend etwas dazugelernt haben. Obwohl es in diesem Buch naturgegebenermaßen sehr stark um technische Aspekte geht, habe ich versucht, es so angenehm lesbar wie m¨oglich zu machen. Ich bin einfach der Ansicht, dass Fachb¨ ucher nicht nur trocken irgendwelche Fakten vermitteln sollen, sondern dass man auch Spaß am Lesen haben muss. Auf diese Art hat man gleich viel mehr Motivation, das Gelernte auch wirklich in die Tat umzusetzen. Einen Aspekt zum Umgang mit diesem Buch, der im Prinzip f¨ ur alle Fachb¨ ucher G¨ ultigkeit besitzt, m¨ ochte ich hier noch kurz ansprechen: Es steckt eine riesige Menge an Information auf den einzelnen Seiten. Je nach Wissensstand der Leser ist es im Normalfall praktisch nicht m¨oglich, diese gesamte Information auch wirklich bei einmaligem Durchlesen zu verinnerlichen. Deshalb ist es sehr geistreich, nach einiger Zeit das Buch erneut zur Hand zu nehmen und es noch einmal durchzugehen. Einer meiner Studierenden hat dies mit folgender Aussage auf den Punkt gebracht: Ich habe das Buch gelesen und mich mit allen darin beschriebenen Dingen gespielt, bis ich geglaubt habe, sie zu verstehen. Danach habe ich das Buch als Nachschlagewerk verwendet. Nach einiger Zeit dachte ich mir, ich lese es einfach zum Spaß noch einmal von vorne bis hinten durch. Und bei diesem zweiten Mal sind mir erst viele Dinge aufgefallen, die bisher spurlos an mir vor¨ uber gegangen sind. Genau diese Dinge aber waren die wirklich Wichtigen f¨ ur die Praxis. In diesem Sinne w¨ unsche ich allen Lesern viel Spaß bei der Lekt¨ ure und einen guten Einstieg in die Welt von C++. Klaus Schmaranz
Graz im August 2002
Vorwort des Autors
VII
Danksagung Ein Buch zu schreiben ist ganz grunds¨atzlich einmal sehr viel Arbeit. Vor allem ist es Arbeit, die man nicht durchgehend allein im stillen K¨ammerlein erledigen kann, denn einerseits wird man betriebsblind und andererseits w¨ urde man zwischendurch mehr als nur einmal die Nerven nach einer durchtippten Nacht wegwerfen. Aus diesem Grund gilt mein Dank in der Folge nicht nur den Personen, die in Form von Diskussionen und Kritik den Inhalt direkt und nachhaltig beeinflusst haben und den Personen, die das Buch lektoriert haben. Mein Dank gilt ebenso allen Personen, die f¨ ur mich immer ansprechbar waren, wenn mein Nervenkost¨ um wieder einmal nicht im allerbesten Zustand war. Einige der nachstehend angef¨ uhrten Leute haben in diesem ganzen Prozess auch mehr als nur eine Rolle u ¨bernommen. Ich kann und m¨ ochte keine Reihung der Wichtigkeit der einzelnen Beteiligten vornehmen, denn dies ist ein Ding der Unm¨oglichkeit. Jede einzelne Person war ein wichtiger Teil in einem komplizierten Puzzle, das nach seiner Zusammensetzung das fertige Buch ergeben hat. Wie es bei Puzzles u ¨blich ist, hinterl¨ asst jedes fehlende St¨ uck ein Loch und jedes einzelne Loch st¨ort das Gesamtbild enorm. Es wird Zeit, zum Wesentlichen zu kommen: Mein Dank gilt meinem Institutsleiter Hermann Maurer, der mehr als einfach nur “der Chef” ist. Auch als guter Freund steht er immer mit aufmunternden Worten, gutem Rat und Unterst¨ utzung zur Seite. Wie bereits bei meinem ersten Buch Softwareentwicklung in C war auch hier wieder Hermann Engesser mein direkter Ansprechpartner beim Verlag und in gewohnter Weise unb¨ urokratisch und sehr kooperativ. Meine Arbeitskollegen, Informatik-Gurus und außerdem guten Freunde Karl Bl¨ umlinger, Christof Dallermassl und Dieter Freismuth waren freiwillige Leser des Manuskripts und durchk¨ammten dieses auf fachliche Ungereimtheiten und sonstige Fehler. Christof Rabel, seines Zeichens enorm kompetenter C++ Spezialist, war durch seine mannigfaltigen Kommentare und seine Diskussionsbereitschaft eine sehr große Hilfe, wenn ich wieder einmal mit Betriebsblindheit geschlagen war. Ebenso wichtig waren viele Diskussionen mit meinem guten Freund und Informatik-Guru Dirk Schwartmann, der mir viele Denkanst¨oße zur Aufbereitung des Inhalts aus seinem reichen Erfahrungsschatz gab. Da schon sehr viel von guten Freunden die Rede war, geht es gleich mit solchen weiter: Mein herzlichster Dank geb¨ uhrt Monika Tragner und Anita Lang, die aufgrund ihrer germanistischen Kompetenz ehrenamtlich und kurz entschlossen das Lektorat f¨ ur dieses Buch u bernommen haben. Neben den wichtigen Personen in meinem Leben, die ¨ direkt an der Arbeit beteiligt waren, gibt es auch solche, die “nur” indirekt ihren Teil zur Entstehung des Buchs beigetragen haben, indem sie einfach f¨ ur mich da waren. Dies bedeutet aber nicht, dass ihr Beitrag deshalb geringer gewesen w¨ are. Hierbei w¨ aren vor allem Manuela Burger und meine Eltern zu nennen, die mir in vielerlei Hinsicht immer eine ganz große Hilfe waren und sind.
VIII
Vorwort des Autors
Last, but not least, geht auch mein besonderer Dank an die Studierenden der TU-Graz, die im Sommersemester 2002 das Programmierpraktikum besucht haben, sowie an Harald Krottmaier und an die Tutoren und Tutorinnen, die bei der Durchf¨ uhrung desselben geholfen haben.
Aus besonders traurigem Anlass m¨ ochte ich diese Danksagung zum Zeitpunkt der Drucklegung an dieser Stelle noch erg¨anzen und widme dieses Buch meinem Vater, dessen Tod mich heute v¨ollig unerwartet, aber daf¨ ur umso tiefer getroffen hat. Klaus Schmaranz
Graz am 17. November 2002
Inhaltsverzeichnis
1.
Ziel 1.1 1.2 1.3 1.4
und Inhalt dieses Buchs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zum Inhalt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die beiliegende CD-ROM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 3 6 8 9
Teil I: Low-Level Konzepte von C++ . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.
Datentypen und Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 Primitive Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Deklaration, Definition und Initialisierung . . . . . . . . . . . . . . . . . 2.3 Das erste C++ Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Zusammengesetzte Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.2 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.3 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5 Scope und Lifetime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6 Symbolische Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.7 Eigene Typdefinitionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13 13 20 25 31 31 37 40 46 47 49
3.
Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¨ 3.1 Uberblick und Reihenfolge der Auswertung . . . . . . . . . . . . . . . . 3.2 Arithmetische Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Logische- und Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . 3.4 Bitoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5 Zuweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6 Datentypabfragen und explizite Typecasts . . . . . . . . . . . . . . . . . 3.6.1 Type Identification und Run-Time-Type-Information . 3.6.2 Unchecked Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.3 Compiletime-checked Cast . . . . . . . . . . . . . . . . . . . . . . . . 3.6.4 Runtime-checked Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.5 Remove-const Cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.6 C-Style Casts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
53 53 57 59 60 61 61 62 67 67 69 70 70
X
Inhaltsverzeichnis
4.
Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1 Selection Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Das unselige goto Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5.
Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
6.
Pointer und References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Pointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Pointer und Adressen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.2 Dynamische Memory Verwaltung . . . . . . . . . . . . . . . . . . 6.2.3 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.4 Funktionspointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.5 Besondere Aspekte von Pointern . . . . . . . . . . . . . . . . . . Call-by-reference auf Pointer . . . . . . . . . . . . . . . . . . . . . . Mehrfachpointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pointer und Typecasts . . . . . . . . . . . . . . . . . . . . . . . . . . .
103 103 114 115 119 125 125 126 126 128 131
7.
Der 7.1 7.2 7.3
135 136 137 138
Preprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Include Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¨ Bedingte Ubersetzung ................................... Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73 74 79 82
Teil II: Objektorientierte Konzepte von C++ . . . . . . . . . . . . . . . . . 141 8.
Objektorientierung Allgemein . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1 Module und Abl¨ aufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.1 Der Weg zum Arbeitsplatz – ein kleines Beispiel . . . . . 8.2 Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3 Richtige Verwendung der OO Mechanismen . . . . . . . . . . . . . . . .
143 146 146 155 161
9.
Klassen in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.1 Besonderheiten von Structures in C++ . . . . . . . . . . . . . . . . . . . . 9.2 Einfache Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2.1 Konstruktor und Destruktor genauer beleuchtet . . . . . 9.2.2 Der Copy Konstruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.2.3 Initialisierung vs. Zuweisung . . . . . . . . . . . . . . . . . . . . . . 9.2.4 Deklarieren von Konstruktoren als explicit . . . . . . . . 9.2.5 Object- und Class-Members . . . . . . . . . . . . . . . . . . . . . . . 9.3 Abgeleitete Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3.1 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.3.2 Konstruktoren und Destruktoren . . . . . . . . . . . . . . . . . . 9.4 Weitere wichtige technische Aspekte . . . . . . . . . . . . . . . . . . . . . . 9.4.1 Static und Dynamic Binding . . . . . . . . . . . . . . . . . . . . . .
167 168 171 178 184 188 189 192 196 205 215 219 219
Inhaltsverzeichnis
9.4.2 9.4.3 9.4.4 9.4.5 9.4.6 9.4.7 9.4.8
XI
Abstrakte Methoden und Klassen . . . . . . . . . . . . . . . . . . Virtuelle Ableitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Downcasts von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . Friends von Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Overloading von const und non-const Methoden . . . Besonderheiten bei der Initialisierung . . . . . . . . . . . . . . Tempor¨ are Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
232 235 241 247 250 251 253
10. Memory – ein kleines Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1 Das ADD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1.1 Identifikation der Grobmodule . . . . . . . . . . . . . . . . . . . . 10.1.2 Weitere Zerlegung der einzelnen Grobmodule . . . . . . . Input Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Spielsteuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Output Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Memory Spielfeld . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Memory Karte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Commandline Handling . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2 Das DDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.1 Klassendiagramm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.2 Klassendeklarationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.3 Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.4 ObjectDeletor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.5 Konkrete Deletors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.6 ArgumentHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.7 MemoryCommandlineArgumentHandler . . . . . . . . . . . . . 10.2.8 CommandlineHandling . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.9 SimpleOutputHandling . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.10 Displayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.11 OutputContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.12 TextOutputContext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.13 GameCard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.14 MemoryGameCard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.15 MemoryGameboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.16 IntDisplayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.17 TextDisplayable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.18 Event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.19 WordEvent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.20 SimpleInputHandling . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.21 SimpleEventHandling . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.22 MemoryGameControl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.23 MemoryCardSymbolGenerator . . . . . . . . . . . . . . . . . . . . . 10.2.24 MemoryCardpair . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3 Ausz¨ uge aus der Implementation . . . . . . . . . . . . . . . . . . . . . . . . .
257 257 258 259 259 259 260 260 260 260 260 261 261 263 266 267 271 272 273 275 278 279 279 281 282 284 289 290 292 293 294 296 296 300 302 304
XII
Inhaltsverzeichnis
11. Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 12. Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1 Grundprinzipien des Operator Overloadings . . . . . . . . . . . . . . . . 12.2 Typumwandlungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.3 Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.3.1 Einfaches new und delete . . . . . . . . . . . . . . . . . . . . . . . . 12.3.2 Array new und delete . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.3.3 Placement Operator new . . . . . . . . . . . . . . . . . . . . . . . . . 12.3.4 delete mit zwei Parametern . . . . . . . . . . . . . . . . . . . . . . 12.3.5 Globale new und delete Operatoren . . . . . . . . . . . . . . . 12.3.6 Weitere Aspekte der eigenen Speicherverwaltung . . . . Vererbung von new und delete . . . . . . . . . . . . . . . . . . . Verhalten bei “Ausgehen” des Speichers . . . . . . . . . . . . 12.4 Abschließendes zu overloadable Operators . . . . . . . . . . . . . . . . .
335 335 354 363 363 371 374 381 385 391 391 397 400
13. Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1 Function Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 Overloading Aspekte von Function Templates . . . . . . . . . . . . . . 13.3 Class Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.4 Ableiten von Class Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.5 Explizite Spezialisierungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.6 Verschiedenes zu Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.7 Source Code Organisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
405 407 414 419 428 431 444 447
14. Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453 15. Verschiedenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.1 mutable Member Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2 Unions im OO Kontext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.3 Funktionspointer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.4 Besondere Keywords, Diagraphs und Trigraphs . . . . . . . . . . . . . 15.5 volatile Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.6 RTTI und dynamic cast im OO Kontext . . . . . . . . . . . . . . . . . . 15.7 Weiterf¨ uhrendes zu Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . .
463 463 465 470 475 477 477 482
Teil III: Ausgesuchte Teile aus der C++ Standard Library . . . 491 16. Die 16.1 16.2
C++ Standard Library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¨ Ubersicht .............................................. Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.1 Vektoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.2 Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.3 Double-Ended Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.4 Standard Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
493 493 497 497 499 501 502
Inhaltsverzeichnis
XIII
16.2.5 Priority Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.6 Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.7 Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.8 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.2.9 Zusammenfassung der Container-Operationen . . . . . . . Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Allocators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Numerik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Algorithmen und Funktionsobjekte . . . . . . . . . . . . . . . . . . . . . . .
503 505 506 507 510 513 517 518 521 531 534
A. Coding-Standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.1 Generelle Regeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.2 Coding-Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3 Design Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
537 537 538 541
B. Vollst¨ andige Implementation des Memory Spiels . . . . . . . . . . B.1 Implementationen der einzelnen Klassen . . . . . . . . . . . . . . . . . . . B.1.1 Das Hauptprogramm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B.1.2 Implementation von Vector . . . . . . . . . . . . . . . . . . . . . . B.1.3 Implementation von CommandlineHandling . . . . . . . . . B.1.4 Implementation von SimpleOutputHandling . . . . . . . . B.1.5 Implementation von GameCard . . . . . . . . . . . . . . . . . . . . B.1.6 Implementation von MemoryGameCard . . . . . . . . . . . . . . B.1.7 Implementation von MemoryGameboard . . . . . . . . . . . . . B.1.8 Implementation von SimpleInputHandling . . . . . . . . . B.1.9 Implementation von MemoryGameControl . . . . . . . . . . . B.1.10 Implementation von MemoryCardSymbolGenerator . . . B.1.11 Implementation von MemoryCardpair . . . . . . . . . . . . . . B.1.12 Variablen f¨ ur die konkreten Deletors . . . . . . . . . . . . . . . B.1.13 Das MemoryMakefile . . . . . . . . . . . . . . . . . . . . . . . . . . . .
543 543 543 545 546 547 547 547 548 552 552 554 556 557 557
16.3 16.4 16.5 16.6 16.7 16.8
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 561 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 563
1. Ziel und Inhalt dieses Buchs
Dieses Buch richtet sich an alle, die gerne die Programmiersprache C++ von Grund auf erlernen wollen oder ihre Kenntnisse vertiefen wollen. Es wird hierbei ein gewisses Grundverst¨ andnis f¨ ur die Softwareentwicklung im Allgemeinen vorausgesetzt. Kenntnisse der Programmiersprache C sind nat¨ urlich auch nicht von Nachteil. C++ hat sich, wie der Name schon sagt, aus C entwickelt und ist dessen objektorientiertes Pendant. Im Prinzip ist mit sehr wenigen Ausnahmen alles, was f¨ ur C gilt, auch f¨ ur C++ g¨ ultig, allerdings ist ++ der Sprachumfang von C durch die Objektorientierung bedeutend gr¨oßer. Lesern, die noch keine Erfahrung mit der Programmiersprache C haben, oder vielleicht noch u ¨berhaupt keine Erfahrung mit der Softwareentwicklung im Allgemeinen, sei als Grundlage, vor dem Durcharbeiten dieses Buchs, das Buch Softwareentwicklung in C ans Herz gelegt. Um nicht missverstanden zu werden: Ich m¨ochte mit diesen einleitenden S¨atzen keineswegs den Verkauf des oben genannten Buchs ankurbeln (eine elektronische Version desselben liegt ohnehin gratis der mitgelieferten CDROM bei). Es ist auch im vorliegenden Buch alles an Information enthalten, was man zum Erlernen von C++ braucht, auch wenn man noch kein C kann. Nur ist das grundlegende C-Wissen im zuvor genannten Buch ungleich detaillierter enthalten und daher f¨ ur Einsteiger bei weitem leichter genießbar. Eine derartig detaillierte Beschreibung der grundlegenden Dinge im hier vorliegenden Buch u urde dessen Rahmen bei weitem sprengen. ¨ber C++ w¨ Die Intention hinter der Empfehlung, zuerst C zu lernen, hat gute Gr¨ unde, die ich noch kurz n¨ aher erl¨ autern m¨ ochte: Um C++ wirklich gut zu beherrschen, muss man unbedingt tieferes Verst¨andnis in zwei verschiedenen Wissensgebieten erlangen: 1. Man muss das Konzept der objektorientierten (kurz: OO) Programmierung verinnerlicht haben, um ein sauber durchdachtes und schl¨ ussiges Softwaredesign erstellen zu k¨ onnen. 2. Man muss ein gewisses Verst¨ andnis f¨ ur die interne Arbeitsweise eines Computers entwickelt haben, um das sauber durchdachte Design auch vern¨ unftig umsetzen zu k¨ onnen. Die Erfahrung zeigt, dass man viel Zeit und Nerven spart, wenn man nicht beide Dinge zugleich lernt. In vielen B¨ uchern und in noch mehr Kursen wird
2
1. Ziel und Inhalt dieses Buchs
versucht, Softwareentwicklung gleich anhand einer objektorientierten Sprache zu lehren. Das Argument f¨ ur diese Vorgehensweise klingt auf den ersten Blick sehr schl¨ ussig: OO-Sprachen wurden entwickelt, weil den Menschen die OO-Denkweise besser liegt als die imperative. Also stellen OO-Sprachen, im Vergleich zu imperativen Sprachen, eine deutliche Verbesserung dar und sind deswegen auch leichter zu erlernen. Leider wurden bei diesem Argument ein paar Kleinigkeiten u ¨bersehen: Erstens haben Probleml¨ osungen, egal ob mit OO-Sprachen oder mit imperativen Sprachen, immer einen imperativen Anteil. Man muss ja einem Computer “genau sagen, was er tun soll”, zumindest im Kleinen. Das geht ohne Verst¨andnis f¨ ur die interne Arbeitsweise nicht wirklich. Zweitens existiert sehr wohl ein Unterschied zwischen der saloppen Denkweise von Menschen und den absolut exakten Formulierungen, die einer Programmiersprache zugrunde liegen. Drittens wurden OO-Sprachen u ¨ber lange Zeit hinweg immer weiterentwickelt, um noch eleganter und besser einsetzbar zu sein. Dementsprechend fanden viele Konstrukte in OO-Sprachen Eingang, die erst dann zu verstehen sind, wenn man die notwendige Erfahrung hat, um deren Sinn nachvollziehen zu k¨ onnen. W¨ahlt man also f¨ ur die ersten Schritte in der Softwareentwicklung gleich eine OO-Sprache, so ist man gezwungen, einige Interna zu begreifen, obwohl das notwendige Handwerkszeug und die notwendige Erfahrung dazu fehlen. Meine Beobachtungen von C++ Neulingen zeigen folgendes Bild: • Die meisten Entwickler, die zuvor entweder C oder eine andere imperative Sprache beherrschen, finden sehr schnell Gefallen an C++, da sie endlich ein Problem elegant l¨ osen k¨ onnen. Es ist mit der n¨otigen Erfahrung bei richtiger Anwendung des OO-Ansatzes (und nur dann!) m¨oglich, Probleme elegant in kleine, in sich abgeschlossene Teilprobleme zu zerlegen. Damit kann man sich immer um das Wesentliche des entsprechenden Teilproblems k¨ ummern, ohne durchgehend das große Ganze akribisch im Auge behalten zu m¨ ussen. Nat¨ urlich geht dies nur, nachdem das große Ganze einmal vern¨ unftig auf h¨ oherer Abstraktionsebene entworfen wurde. • Fast alle Entwickler, die C++ (oder auch Java) als Einstiegssprache lernen, ohne zuvor mit einer imperativen Sprache gearbeitet zu haben, finden C++ unglaublich kompliziert und sehen den Wald vor lauter B¨aumen nicht mehr. Das liegt daran, dass sie vor vielen Problemen, die durch das OO-Konzept gel¨ost werden, u ¨berhaupt noch nie gestanden sind! Wie soll man auch eine Probleml¨ osung verstehen und den damit verbundenen Overhead gerne in Kauf nehmen, wenn man das Problem nicht kennt bzw. nicht verstanden hat? Lesern, die trotz dieser Argumente keine Lust haben, zuerst C zu lernen, sondern sich gleich C++ aneignen wollen, m¨ochte ich zumindest die beiliegende elektronische Version von Softwareentwicklung in C als Nachschlagewerk f¨ ur Zwischendurch empfehlen. Es schadet in jedem Fall nicht, wenn man
1.1 Zum Inhalt
3
z.B. bei Abhandlungen u ¨ber Pointer einmal einen kurzen Blick in das entsprechende Kapitel des anderen Buchs wirft oder dieses vielleicht sogar im Schnellverfahren einmal von vorne bis hinten querliest. Auf einen Punkt sei noch ganz besonders hingewiesen: C++ als OOSprache unterst¨ utzt sehr elegant alle Eigenschaften, die immer mit OOSoftwareentwicklung in Verbindung gebracht werden. Die wichtigsten Schlagworte hierbei sind sicherlich die Modularit¨ at und die Wiederverwendbarkeit von Code. Ganz bewusst habe ich das Wort unterst¨ utzt besonders hervorgehoben, denn es herrscht der allgemeine Irrglaube, dass gewisse Eigenschaften, wie z.B. Wiederverwendbarkeit, allein durch das Verwenden einer OOSprache automatisch existieren bzw. erzwungen werden. Dies ist definitiv falsch. Kritisch betrachtet ist sogar das Gegenteil der Fall! Bei Verwendung von OO-Sprachen kann man Fehler im Design viel l¨anger hinter abenteuerlichen Objektkonstruktionen verstecken, als es bei Verwendung von imperativen Sprachen jemals m¨ oglich w¨ are. Wenn man dann allerdings den Punkt erreicht, an dem “nichts mehr geht”, dann ist die Katastrophe bei weitem gr¨oßer, als sie sonst jemals werden k¨onnte. Bei imperativen Sprachen muss man viel fr¨ uher das Handtuch werfen, weil man bei weitem schneller den Punkt erreicht, an dem man nichts mehr an einem Teil eines Programms ¨andern kann, ohne gleich an einer ganz anderen Ecke im Programm eine Katastrophe hervorzurufen. Bei OO-Sprachen kann man sich bewusst noch eine Zeit lang an diesen Problemen vorbei schwindeln, indem man Objektkapselungsmechanismen missbraucht. Das Ziel bei jeder Softwareentwicklung ist immer, dass das Endprodukt sauber und robust ist. Daher wird in diesem Buch besonderer Wert darauf gelegt, OO-Softwareentwicklung richtig zu betreiben, denn nur dann gelangt man zum gew¨ unschten Ziel. Dazu ist sehr viel Erfahrung vonn¨oten, vor allem, wenn es um saubere, elegante und allgemein einsetzbare Probleml¨osungsans¨atze geht. Wo auch immer auf dem Weg zum erkl¨arten Ziel typische Stolpersteine liegen, an denen sich C++ Neulinge erfahrungsgem¨aß stoßen k¨onnen, wird besonders auf sie hingewiesen.
1.1 Zum Inhalt Das Buch f¨ uhrt in kleinen Schritten von den Prinzipien der OO-Softwareentwicklung u ¨ber die Grundlagen der Sprache (z.B. primitive Datentypen, wie sie auch in C existieren) und u ¨ber die ersten objektorientierten Gehversuche zu zum Teil sehr speziellen Konstrukten von C++. Es wird immer besonderer Wert darauf gelegt, das Warum zu verstehen, anstatt nur ein Kochbuch mit einfachen Rezepten anzubieten. Nur zu oft sieht man C++ Code, der aus purem Unverst¨ andnis der Hintergr¨ unde unglaublich kompliziert ist, obwohl die Sprache an und f¨ ur sich eine elegante L¨osungen erm¨oglichen w¨ urde.
4
1. Ziel und Inhalt dieses Buchs
Zu allen Sprachkonstrukten werden entsprechende Beispiele angef¨ uhrt und ausf¨ uhrlich erkl¨ art. Diese Beispiele sind durchgehend mit kleinen Zeilennummern am linken Rand versehen, um ein Referenzieren im beschreibenden Text einfacher zu machen. Nat¨ urlich sind diese Zeilennummern nicht Teil des C++ Codes und d¨ urfen dementsprechend nicht in Programmen vorkommen! Alle Beispiele, die im Buch angef¨ uhrt sind, wurden so nahe wie m¨oglich an sinnvolle Konstrukte angelehnt, die auch in der t¨aglichen Praxis vorkommen. Auch die Herangehensweise an Probleml¨osungen, die in diesem Buch vermittelt wird, hat sich u urlich bedeutet dies nicht, ¨ber lange Zeit bew¨ahrt. Nat¨ dass die angef¨ uhrten Beispiele die einzig m¨ogliche L¨osung zu einem Problem darstellen. Ich m¨ ochte unbedingt dazu anregen, u ¨ber alternative L¨osungen nachzudenken und diese mit den vorgeschlagenen zu vergleichen. Dies ist die beste Methode, den Umgang mit verschiedensten Aspekten der Softwareentwicklung zu u ¨ben und die Kenntnisse u ¨ber eine Programmiersprache zu vertiefen. Um das Buch so gut wie m¨ oglich zu strukturieren, wurde es in drei Hauptteile untergliedert: Teil I – Low-Level Konzepte von C++: Im ersten Teil des Buchs werden alle Konzepte von C++ besprochen, die noch nichts mit Objektorientierung zu tun haben. Im Prinzip sind dies einerseits Konzepte, die C++ von Cu ¨bernommen hat, andererseits einige Features, die entweder im Vergleich zu C sinnvoll erweitert wurden bzw. v¨ollig neu sind. Wo auch immer Unterschiede zwischen C und C++ zu finden sind, sind diese explizit angemerkt. Teil II – Objektorientierte Konzepte von C++: Im zweiten Teil des Buchs werden die Features von C++ besprochen, die direkt mit OO-Softwareentwicklung zu tun haben und die es in C vom Konzept her u ¨berhaupt nicht gibt. Teil III – Ausgesuchte Teile aus der C++ Standard Library: Zu C++ gibt es eine sehr große Standard-Klassenbibliothek, die vieles enth¨alt, was man im Programmieralltag gut gebrauchen kann. Der Vorteil der StandardLibrary ist, dass man nicht f¨ ur die allt¨aglichen Bed¨ urfnisse das Rad neu erfinden muss und auch nicht auf Code von Drittherstellern angewiesen ist. Die Standard-Library hat, wie der Name schon sagt, den Vorteil, dass ihr Inhalt erstens standardisiert ist und dass sie zweitens standardm¨aßig bei jedem (ok, fast jedem :-)) C++ Compiler mitgeliefert wird. Um nun diese Standard-Library umfassend mit erkl¨arenden Codebeispielen zu behandeln, m¨ usste dieses Buch mindestens den doppelten Umfang haben. Aus diesem Grund wird im dritten Teil des Buchs vor allem der ¨ prinzipielle Umfang dieser Library vorgestellt, um einen Uberblick zu vermitteln. Umfassendere Beschreibungen gibt es nur zu den Teilen, die man im “¨ ublichen” Alltag sehr h¨ aufig braucht. Durch diese Art der Beschreibung werden die Konzepte klar genug beleuchtet, dass man die Ein-
1.1 Zum Inhalt
5
stiegspunkte hat, die man braucht, um mit der Online-Dokumentation problemlos zu Rande zu kommen. Um besondere Hinweise im Buch auf einen Blick erkennen zu k¨onnen, sind diese extra hervorgehoben und besitzen eine besondere Kennzeichnung am Rand: • Das Zeichen
signalisiert einen Hinweis auf einen Stolperstein.
• Das Zeichen signalisiert einen Hinweis auf einen wichtigen Unterschied zwischen C und C++. • Das Zeichen signalisiert, dass es als Zusatzinformation zu einem bestimmten Thema eine ausf¨ uhrliche Abhandlung im Buch Softwareentwicklung in C gibt. Eine elektronische Version dieses Buchs ist auch auf der beiliegenden CD-ROM vorhanden. Diese speziellen Hinweise auf C-Konstrukte wurden f¨ ur Leser eingef¨ uhrt, die im Umgang mit der Programmiersprache C noch ein wenig unsicher sind oder u ¨berhaupt noch keine C-Erfahrung haben. Ein paar Worte m¨ ochte ich noch u ¨ber das Thema Entwicklungsumgebung verlieren: C++ Entwicklungsumgebungen gibt es wie Sand am Meer. Je nachdem, um welche Entwicklungsumgebung es sich handelt und f¨ ur welche Zielplattform diese gedacht ist, ist sowohl der Funktionsumfang als auch der Umfang zus¨ atzlicher plattformabh¨ angiger Libraries sehr verschieden. Nun ist es nicht im Sinne des Erfinders, C++ als allgemein einsetzbare Programmiersprache an einer bestimmten Umgebung festzumachen. Aus diesem Grund wurde folgende Wahl getroffen: Die empfohlene Entwicklungsumgebung, auf der auch prinzipiell das gesamte Buch beruht, ist eine Umgebung unter Linux mit dem GNU C++ Compiler und GNU make. Diese Entscheidung ist darin begr¨ undet, dass dies eine frei verf¨ ugbare Open-Source Umgebung ist. Sie ist auch eine der stabilsten Entwicklungsumgebungen, die derzeit zur Verf¨ ugung stehen. Es soll sich nun allerdings niemand abschrecken lassen, der lieber unter MS-Windows mit einer anderen Entwicklungsumgebung arbeiten will. Das Wissen, das in diesem Buch vermittelt wird, ist nat¨ urlich auch daf¨ ur g¨ ultig. Es steht also jedem frei, eine beliebige Umgebung zu verwenden, ohne dass dadurch Einschr¨ ankungen zu erwarten sind. Jedoch m¨ochte ich jedem Softwareentwickler gerne ans Herz legen, es zumindest interessehalber einmal mit Linux zu probieren. Nach einer kurzen Eingew¨ohnungsphase lernt man doch gewisse Vorteile sch¨ atzen. Eine kurze Warnung m¨ ochte ich jedoch an dieser Stelle noch aussprechen: Einige Compiler entsprechen definitiv in einigen “Feinheiten” nicht dem C++ Standard und einige Compiler haben auch nicht alle Features implementiert, die im Standard enthalten sind. Dies betrifft auch die sehr weit verbreiteten Compiler gewisser Firmen, die den PC Markt beherrschen. Um zu erfahren, wo diese Compiler vom Standard abweichen, wirft man am besten einen Blick
6
1. Ziel und Inhalt dieses Buchs
auf die entsprechenden Web-Pages der Compilerhersteller. Dort existiert u ¨blicherweise eine Liste, die Auskunft u ¨ber die Abweichungen vom Standard gibt. Sollten Leser also auf Beispiele im Buch stoßen, die mit ihrem Compiler nicht u ochte ich empfehlen, einen Blick auf die Web¨bersetzbar sind, so m¨ Page des Herstellers zu werfen. Sollten dann immer noch Ungereimtheiten existieren, so ist das Feedback-Forum zum Buch (siehe Abschnitt 1.3) der beste Ort, eine Frage anzubringen. Ich werde dann so schnell wie m¨oglich versuchen, Klarheit zu schaffen. Leser, die sich gar nicht sicher sind, ob sie nun Linux ausprobieren sollen oder nicht, k¨ onnen auch einen Zwischenweg w¨ahlen: Alle n¨ utzlichen Tools (GNU C++ Compiler, Emacs als Editor und viele kleine Helferlein, wie z.B. make oder tar) sind auch als MS-Windows Portierungen frei im Internet verf¨ ugbar. Kurze Beschreibungen, die den Einstieg in die Arbeit mit diesen Tools erleichtern, sind auch im Buch Softwareentwicklung in C, das bereits zu Beginn dieses Kapitels genannt wurde, enthalten.
1.2 Motivation Softwareentwickler stehen in der Industrie im Regelfall unter starkem Druck. Es wird von ihnen nur zu oft verlangt, dass sie unrealistische Termine einhalten, dass sie jederzeit auf Wunsch mitten in der Entwicklung neue Features in die Software einbauen oder auch, dass sie einfach w¨ahrend der Entwicklung Konzepte ¨ andern. Das Allerschlimmste, das allerdings passieren kann, ist, dass ein Kunde oder Vorgesetzter fr¨ uhzeitig “schnell einmal etwas sehen will”. Leider bedeutet hier der Begriff etwas sehen, dass man einen Teil eines lauff¨ ahigen Programms demonstrieren muss. Kann man dies nicht, bzw. versucht man, ein erstelltes Design zu zeigen, dann sieht man sich sehr oft Zweifeln ausgesetzt, “ob man denn u ¨berhaupt etwas weiterbringt”. Nun funktioniert aber saubere Softwareentwicklung nicht so, dass man sich einfach zum Computer setzt und schnell ein Programm schreibt. Abgesehen davon, dass das sowieso die schlechtest m¨ogliche Herangehensweise ist, ist die Komplexit¨ at heutiger Softwarepakete so hoch, dass dieser Ansatz niemals funktionieren kann. Leider herrscht immer noch die Meinung vor, dass die tats¨ achliche Arbeit bei der Softwareentwicklung die Implementation der Software ist. Dies ist aber vollkommen falsch! Der gr¨oßte Teil der Arbeit im Rahmen einer sauberen (!!) Entwicklung findet in der Designphase statt! Das Codieren selbst nimmt im Rahmen eines Entwicklungszyklus nur einen sehr geringen Teil der Zeit in Anspruch. Einen weiteren großen Teil im Zyklus nehmen die laufenden Tests ein, die die Software von den ersten Designschritten bis zum Endprodukt begleiten m¨ ussen. Sieht man sich den Projektfortschritt bei einer sauberen Entwicklung an, so stellt man Folgendes fest: Lange Zeit scheint die Arbeit nur schleppend voranzugehen. Es gibt viele Diskussionsphasen im Team und das Ergebnis dieser Diskussionsphasen sind Entw¨ urfe auf Papier. Im Regelfall wird bis
1.2 Motivation
7
u alfte der Gesamtentwicklungszeit hinaus keine Zeile Code produ¨ber die H¨ ziert. Danach allerdings scheint sich die Arbeit von außen gesehen enorm zu beschleunigen. In sehr kurzer Zeit wird sehr viel implementiert und die Implementation ist u ¨blicherweise auch relativ fehlerfrei. Wenn man im Gegensatz dazu den Projektfortschritt bei einer Hackl¨ osung ansieht, bei der die Arbeit gleich von Beginn an am Computer stattfindet, ohne zuerst ein vollst¨ andiges (!!) und schl¨ ussiges (!!) Design zu erstellen, dann beobachtet man als Außenstehender im Prinzip das Gegenteil: Zu Beginn passiert in kurzer Zeit sehr viel und es gibt sehr schnell einen Prototypen zu sehen, der einen Teil der geforderten Funktionalit¨at mehr oder weniger gut implementiert. Gewisse Fehler werden nat¨ urlich toleriert, denn man steckt ja noch mitten in der Entwicklung. Mit jedem zus¨atzlichen Feature, das die Software dem Endprodukt n¨ aher bringt, geht die Arbeit schleppender voran. Nach einer gewissen Zeit bedeuten auch die kleinsten Erweiterungen des halbfertigen Pakets bereits immensen Implementationsaufwand. Außerdem steigt die Fehlerh¨ aufigkeit auf ein unertr¨aglich hohes Maß. Das Beheben von ¨ Fehlern wird immer mehr zum Spießrutenlauf, denn mit jeder Anderung in einem Teil der Software passieren ungeahnte Dinge in anderen Teilen der Software. Mit Gl¨ uck ist das geforderte Produkt vom Funktionsumfang her klein genug, dass man dessen Fertigstellung noch irgendwie mehr schlecht als recht u ¨ber die Runden bringt. Sehr oft allerdings muss man den Kunden erkl¨aren, dass gewisse Features “aus technischen Gr¨ unden gar nicht realisierbar sind” und hoffen, dass diese das akzeptieren. ¨ Noch viel dramatischer wird das Bild, wenn Erweiterungen oder Anderungen eines existenten Softwarepakets gefordert sind. Bei der sauberen Entwicklung ist die Erweiterbarkeit ein Teil des Konzepts und im Regelfall sind neue Features relativ einfach realisierbar. Sollte durch neue Features das Ursprungskonzept ver¨ andert werden m¨ ussen, so ist auch das im Normalfall m¨oglich, wenn auch mit einigem Aufwand verbunden. Das Endprodukt bleibt trotzdem sauber. Ganz im Gegensatz dazu steht die Erweiterbarkeit der Hackl¨osung. Schon ¨ die kleinsten Anderungen dauern enorm lang und destabilisieren die gesamte Software. Sehr oft beobachtet man den Fall, dass in sogenannten BugfixReleases zwar einige bekannte schwere Fehler behoben wurden, daf¨ ur aber mindestens genau so viele neue Fehler eingebaut wurden, da die Auswirkungen von Programm¨ anderungen nicht mehr u ¨berschaubar sind. Vollkommen katastrophal wird es, wenn man die Menge an Source-Code eines sauberen Projekts mit der einer Hackl¨osung vergleicht. Im Normalfall ist die Hackl¨ osung bei gleicher Funktionalit¨at gleich x-Mal so groß wie die saubere L¨ osung. Nicht nur, dass die Codemenge ungleich gr¨oßer ist, auch die Komplexit¨ at des Codes ist durch Seiteneffekte enorm. Es gibt einen ganz bestimmten Grund, warum ich mich u ¨ber saubere und Hackl¨ osungen so ausf¨ uhrlich auslasse: Zur Zeit, zu der OO-Sprachen, insbesondere C++, ihren Eingang in die Industrie fanden, war die Euphorie
8
1. Ziel und Inhalt dieses Buchs
u ¨ber die diesen Sprachen angedichteten Eigenschaften riesig. Pl¨otzlich wurde u ¨berall von Wiederverwendung gesprochen und davon, dass sich dadurch die Entwicklungszyklen drastisch verk¨ urzen. Entsprechend wurden sofort die Entwicklungszeiten k¨ urzer angesetzt als zuvor, allerdings bei einer h¨oheren Anzahl von Features. Man arbeitet ja objektorientiert, und damit geht das alles viel besser und schneller. Leider wurde bei dieser Euphorie etwas u ¨bersehen: OO-Entwicklung will erst richtig gelernt sein und es braucht einige Erfahrung, um ein tragf¨ahiges Konzept zu erstellen! Aber genau die Zeit, die Entwickler brauchen, um die Denkweise vollst¨ andig zu verinnerlichen und die Konzepte hinter dieser Denkweise in vern¨ unftige Softwarekonstrukte umzusetzen, wurde und wird ihnen nicht ausreichend gegeben. Es ist f¨ ur erfahrene C-Entwickler ohne große Probleme m¨ oglich, C++ als Sprache in einigen Tagen, bis zu wenigen Wochen, zu lernen. Es ist allerdings vollkommen unm¨oglich, die Denkmuster innerhalb dieser kurzen Zeit umzustellen. Imperativ zu programmieren ist eben einmal ein ganz anderer Ansatz als objektorientiert zu entwickeln. Vor allem ver¨ andert sich das Design der Software drastisch! Die Syntax von C++ zu beherrschen bedeutet noch lange nicht, objektorientiert C++ zu programmieren. Es ist leicht, C++ zu vergewaltigen, also Klassen und andere Konstrukte zu verwenden und trotzdem im Prinzip rein imperativ zu programmieren. Genau bei einer solchen Arbeitsweise kauft man den gesamten Overhead von C++ gegen¨ uber C ein, ohne die damit verbundenen zus¨atzlichen M¨oglichkeiten zum Vorteil zu n¨ utzen. Polemisch festgestellt: Wenn man mit einem Kleinwagen gut umgehen kann, dann bedeutet dies noch lange nicht, dass man sich einfach in ein Rennauto setzen kann und damit schneller ans Ziel kommt. Die Gefahr, durch das Rennauto u ¨berfordert zu werden, seine Grenzen nicht zu kennen und deshalb gleich einen Unfall zu haben, ist sehr groß. Deshalb kann ich abschließend nur sagen, dass alle Leser dieses Buchs sich so viel Zeit wie m¨ oglich zum Spielen und Probieren nehmen sollten, um C++ und die dahinter liegenden Konzepte zu verstehen, bevor sie sich an C++ im Rahmen einer kommerziellen Entwicklung versuchen.
1.3 Feedback Software wird niemals fertig. Kaum wird eine Version freigegeben, kommen auch schon die n¨ achsten W¨ unsche. Genau dasselbe passiert bei B¨ uchern, die Wissen vermitteln sollen. Es gibt kein Buch, das man nicht noch verbessern k¨onnte und das gilt nat¨ urlich auch (hoffentlich nicht ganz besonders) f¨ ur dieses Buch. Aus diesem Grund gibt es ein Feedback-Forum, u ¨ber das W¨ unsche, Anregungen, Beschwerden, Lob und Tadel an den Autor u ¨bermittelt werden k¨ onnen. Dieses Feedback Forum ist online erreichbar unter der Web-Page zum Buch: http://courses.iicm.edu/SWEntwicklungInCplusplus
1.4 Die beiliegende CD-ROM
9
Ich w¨ urde mich freuen, wenn die eine oder andere Leserin bzw. der eine oder andere Leser dazu beitr¨ agt, dieses Buch zu verbessern, indem sie/er entsprechende Vorschl¨ age macht. Das Feedback-Forum ist aber nicht nur dazu gedacht, Lesern die M¨oglichkeit zu geben, ihre Meinung zum Buch mitzuteilen. Ich sehe das Forum vielmehr als Leserforum, in dem auch eigene Beispiele, Alternativen zu vorgeschlagenen L¨ osungen, etc. ver¨ offentlicht werden k¨onnen und diverse Aspekte der Softwareentwicklung diskutiert werden k¨onnen. Es w¨are sch¨on, wenn das Forum eine Eigendynamik in diese Richtung entwickeln w¨ urde, denn das kommt sicher vielen Leuten zugute.
1.4 Die beiliegende CD-ROM Dem Buch liegt eine CD-ROM bei. Weil der Inhalt einer CD-ROM im Gegensatz zu einem Buch sehr kurzlebig ist, m¨ochte ich an dieser Stelle nur so viel erw¨ahnen: Es sind auf der CD-ROM alle im Buch abgedruckten Programme vorhanden. Was sonst noch auf der CD-ROM zu finden ist und wie man damit arbeitet, kann man erfahren, indem man die Datei index.html mit einem der g¨ angigen Internet-Browser ansieht. Eventuelle Zus¨atze, die sich im Lauf der Zeit nach Auslieferung der CD-ROM als n¨ utzliche Add-ons herausstellen, sind immer aktuell u ¨ber die zuvor erw¨ahnte Web-Page zum Buch abrufbar.
Teil I
Low-Level Konzepte von C++
2. Datentypen und Variablen
Die Verwendung von Variablen ist eines der Grundkonzepte von OO-Sprachen, gleich wie bei imperativen und im Gegensatz zu funktionalen Programmiersprachen. Prinzipiell ist eine Variable ein Datenobjekt, das u ¨ber einen symbolischen Namen (=Identifier ) angesprochen werden kann und dessen Inhalt vom Programm manipuliert werden kann. Zus¨atzlich zum Identifier besitzen Variablen in C++ auch noch einen Datentyp, der u ¨ber ihre Natur Auskunft gibt. Damit wird dem Compiler mitgeteilt, welchen Speicherbedarf eine Variable hat und welche Operationen auf ihr ausf¨ uhrbar sind, bzw. wie gewisse Operatoren in diesem Kontext interpretiert werden m¨ ussen.
2.1 Primitive Datentypen Als sogenannte primitive Datentypen werden die Typen bezeichnet, mit denen ein Computer im Prinzip “von sich aus” umgehen kann. Dies sind verschiedene Arten von Ganzzahlen (=integrale Typen), verschiedene Arten von Gleitkommazahlen (=floating-point Typen) und darstellbare Zeichen (=Characters). In C++ stehen alle primitiven Datentypen zur Verf¨ ugung, die bereits in C existieren. Wie man in der folgenden Tabelle sehen kann, gibt es in C++ auch einen boolschen Datentyp, den man in C vermisst. Ebenso ist wchar_t ein eingebauter Datentyp und nicht, wie in C, ein simples typedef. Typ char wchar_t bool int float double
Bedeutung Ein Character, nimmt ein (¨ ublicherweise) 8 Bit Zeichen auf. Ein wide Character, nimmt ein mindestens (!) 16 Bit Zeichen auf. Ein boolscher Wert, kann einen der Werte true oder false annehmen Ein ganzzahliger Wert in der f¨ ur die jeweilige Maschine “nat¨ urlichen” Gr¨ oße. Eine Gleitkommazahl mit einfacher Genauigkeit. Eine Gleitkommazahl mit doppelter Genauigkeit.
Zum Thema 8 Bit Zeichen in einem char m¨ochte ich noch eine kurze Erg¨anzung liefern: Per Definition ist ein char mindestens 8 Bit lang. Aller-
14
2. Datentypen und Variablen
dings ist auf allen gebr¨ auchlichen Plattformen ein char wirklich genau 8 Bit lang und deshalb werde ich diese Spitzfindigkeit in der Folge nicht mehr weiter beachten. Wie in C gibt es auch noch die folgenden Qualifiers, mit denen man die Eigenschaften bestimmter Grunddatentypen steuern kann: Qualifier signed unsigned short long
Bedeutung vorzeichenbehaftet (normalerweise nicht explizit angegeben) nicht vorzeichenbehaftet “kurz” “lang”
anwendbar auf char, int char, int int int, double
Der Qualifier signed wurde hier nur aus Gr¨ unden der Vollst¨andigkeit angegeben. Im Normalfall wird dieser nicht explizit in Programmen verwendet, da als Default immer angenommen wird, dass es sich um eine vorzeichenbehaftete Zahl handelt. Wenn man die erste Tabelle mit den Grunddatentypen n¨aher betrachtet, dann f¨ allt auf, dass C++ leider ein ganz großes Manko von C geerbt hat: Den Datentypen liegt die “nat¨ urliche” Gr¨oße auf verschiedenen Zielplattformen zugrunde! Man kann nun viel u ¨ber Vor- und Nachteile dieser Spezifikation diskutieren. Tatsache ist, dass oft sogar von erfahrenen Entwicklern immer wieder falsche Annahmen u ¨ber die Gr¨oße von Datentypen getroffen werden. Dies f¨ uhrt dann in manchen F¨allen zu v¨ollig unerkl¨arbaren Fehlern mit manchmal katastrophalen Auswirkungen. Die m¨ oglichen Kombinationen der Grunddatentypen mit entsprechenden Qualifiers ergibt folgendes Gesamtbild: char: Eine Variable vom Typ char kann genau ein Zeichen aus dem g¨ ultigen (¨ ublicherweise) 8 Bit-Zeichensatz der jeweiligen Zielplattform halten. Vorsicht ist geboten, denn es wird keine bindende Aussage getroffen, welcher Zeichensatz auf der Zielplattform g¨ ultig ist. Hier gibt es massive Unterschiede, denn außer Plattformabh¨angigkeiten gibt es auch Abh¨angigkeiten von der nat¨ urliche Sprache (z.B. Deutsch, Englisch), auf die ein Zeichensatz ausgelegt ist. Im Prinzip ist das Einzige, was man einigermaßen sicher sagen kann, dass im Normalfall ein Zeichensatz die Buchstaben a–z, A–Z, die Zahlen 0–9 und die wichtigsten Satzzeichen, wie Punkt, Komma, etc. enth¨ alt. Man kann jedoch nicht davon ausgehen, dass diese Zeichen in verschiedenen Zeichens¨atzen denselben Character-Code besitzen. Man kann nicht einmal davon ausgehen, dass alle m¨oglichen 256 Zeichen tats¨ achlich in einem Zeichensatz belegt sind, dass also ein echter 8 Bit Zeichensatz vorliegt. Oft findet man einen 7 Bit Zeichensatz auf einer Maschine vor, bei dem Characters mit einem Code gr¨oßer als 127 einfach “leer” sind.
2.1 Primitive Datentypen
15
Wenn man sich kurz u ¨berlegt, dass in einem char einfach nur ein Code in Form einer Zahl gespeichert wird, der dann erst bei der Darstellung des entsprechenden Zeichens in einer Zeichensatztabelle “nachgeschlagen” wird, dann erkennt man leicht, was es mit einem char eigentlich im Grunde auf sich hat: F¨ ur den Computer ist er einfach eine (zumeist) 8 Bit lange Ganzzahl. Daher kann man auch mit einem char rechnen wie mit allen anderen integralen Datentypen. Hier ist allerdings allergr¨ oßte Vorsicht geboten: Es gibt keine bindende Definition, ob ein Compiler einen char nun prinzipiell als signed oder unsigned behandelt! Will man also einen char als kleine vorzeichenbehaftete Ganzzahl verwenden, so ist der einzig sichere Weg, den voll ausgeschriebenen Datentyp signed char zu verwenden. In diesem Fall ist dann das Fassungsverm¨ ogen mit einem Wertebereich von -128–127 nicht gerade berauschend, aber je nach Anwendung ist dies ausreichend und wird aus Gr¨ unden der Speicher-Ersparnis verwendet. signed char: Wenn schon bei char nicht gesagt ist, ob dieser auf einer Plattform nun vorzeichenbehaftet ist oder nicht, dann muss man einen vorzeichenbehafteten char eben erzwingen, wenn man unbedingt einen solchen ben¨otigt. Genau dies ist dann der hier vorgestellte signed char mit einem Wertebereich von -128–127. unsigned char: Wo man einen signed char erzwingen kann, gibt es nat¨ urlich auch ein (sicher-)nicht-vorzeichenbehaftetes Pendant dazu: den unsigned char mit dem resultierenden Wertebereich von 0–255. Im Zusammenhang mit der Darstellung von Zeichen aus dem Zeichensatz ist es egal, ob man mit char, signed char oder unsigned char arbeitet. Zum “Nachschlagen” in der Zeichensatztabelle wird er sowieso als nichtvorzeichenbehaftet betrachtet (oder hat schon jemand negative ASCIICodes gesehen? :-)). wchar_t: F¨ ur einen wchar_t gilt dasselbe, was schon u ¨ber char gesagt wurde. Der einzige Unterschied ist, dass es sich hier um einen “Wide” Character handelt, der f¨ ur Unicode und ¨ ahnliche Zeichens¨atze eingesetzt wird und als solcher mindestens 16 Bit lang ist. Sich den Kopf u ¨ber signed und unsigned Varianten desselben zu zerbrechen hat auch nicht u ¨bertrieben viel Sinn, denn zum Rechnen stehen in diesem Gr¨oßenbereich bereits die “echten” Ganzzahlen zur Verf¨ ugung (z.B. short). bool: Der Datentyp bool ist ein primitiver Typ, der in C++ erst vor wenigen Jahren eingef¨ uhrt und zum Standard erkl¨art wurde. In C und in fr¨ uhen C++ Implementationen gab es diesen Datentyp noch nicht, allerdings gab es auch damals schon starke Bestrebungen, einen solchen einzuf¨ uhren. Aus diesem Grund findet man in vielen a¨lteren Standard
16
2. Datentypen und Variablen
Libraries noch verschiedenste Typdefinitionen f¨ ur einen boolschen Wert, die von boolean u urlich ¨ber Boolean und BOOL bis zu bool reichen. Nat¨ gab es auch die entsprechenden Definitionen der Wahrheitswerte dazu, die ebenfalls nicht standardisiert waren. Beliebige Schreibweisen wie z.B. TRUE/FALSE f¨ ur die Wahrheitswerte existierten. Um diesem Wildwuchs Einhalt zu gebieten, wurde der Datentyp bool mit den dazugeh¨ origen Wahrheitswerten true und false in den C++ Standard aufgenommen. Wo auch immer man also explizit einen boolschen Wert braucht, wird unbedingt die Verwendung dieses Typs empfohlen. Rekapituliert man kurz die Definition von C, die auch C++ vollinhaltlich zugrunde liegt, wann etwas als wahr bzw. falsch interpretiert wird, dann kommt man schnell auf des Pudels Kern, wie ein bool intern funktioniert: • Ein Ganzzahlenwert von 0 wird als false interpretiert. • Jeder Ganzzahlenwert ungleich 0 wird als true interpretiert. Der Datentyp bool ist in Wirklichkeit hinter den Kulissen ein Ganzzahlendatentyp. Es ist f¨ ur ihn keine bestimmte Gr¨oße vorgeschrieben. ¨ Ublicherweise wird ein bool intern durch einen char oder auch einen short (siehe unten) repr¨ asentiert. Per Definition ist false die Ganzzahl 0. Weil nun f¨ ur true das ganze Spektrum aller m¨ oglichen Zahlen ungleich 0 zur Verf¨ ugung steht, einigte man sich darauf, true durch die Ganzzahl 1 zu repr¨asentieren. Wo auch immer Variablen vom Typ bool in Rechenoperationen verwendet werden (ja, das funktioniert, bool ist ja eine Ganzzahl!), werden sie implizit zu int (siehe unten) umgewandelt. Eine Zuweisung von einer Ganzzahl auf einen bool f¨ uhrt dazu, dass ein Wert von 0 zu false wird (was sonst, false ist ja auch 0 :-)) und jeder Wert ungleich 0 wird konvertiert zu true (also einfach zu 1). Verwirrend? Keine Sorge, wir werden in der Folge noch ein Beispiel betrachten, in dem diese Zusammenh¨ange genau demonstriert werden. Das Einzige, was es mit bool, true und false auf sich hat, ist, dass hier ein eigener Datentyp eingef¨ uhrt wurde und die Wahrheitswerte im Endeffekt genau definiert auf 0 und 1 abgebildet werden, anstatt auf 0 und “irgendwas ungleich 0”, um dem existierenden Wildwuchs ein Ende zu setzen. int: Der Datentyp int (oder ganz genau signed int, diese Schreibweise ist aber nicht u asentiert den vorzeichenbehafteten Standard¨blich) repr¨ Ganzzahlentyp auf der jeweiligen Zielplattform. Es gibt keine Regel, wie groß denn nun ein int tats¨ achlich ist. Heute u ¨blich ist eine Gr¨oße von 32 Bit, verlassen darf man sich darauf allerdings niemals! Bis vor nicht allzu langer Zeit waren 16 Bit f¨ ur einen int auf PC-Systemen durchaus u ¨blich. Was passiert, wenn man sich darauf verl¨asst, dass 32 Bit zur Verf¨ ugung stehen (Wertebereich ca. -2 Mrd.–2 Mrd.) und pl¨otzlich
2.1 Primitive Datentypen
17
hat man aber nur noch 16 Bits zum Speichern einer Zahl (Wertebereich -32768–32767), das kann man sich sicherlich ausmalen. unsigned int bzw. unsigned: Der Datentyp unsigned (=¨ ubliche Kurzform f¨ ur unsigned int) unterliegt genau denselben Gesetzen wie sein vorzeichenbehaftetes Pendant: Sein Fassungsverm¨ ogen ist maschinenabh¨angig. Allerdings ist ein unsigned auf einer Zielplattform garantiert immer gleich lang wie ein int, nur die Interpretation ist verschieden: Ein unsigned wird immer als positive Ganzzahl betrachtet, also ohne Vorzeichenbit. Damit kann er auf Kosten der wegfallenden negativen Zahlen “doppelt so große” positive Zahlen speichern (0 gilt auch als positive Zahl!) wie sein vorzeichenbehafteter Bruder. short int bzw. short: Der Datentyp short (=¨ ubliche Kurzform f¨ ur short int) bezeichnet einen “kurzen” int. Tolle und unglaublich genaue Aussage, oder :-)? Leider kann wirklich nichts allzu Genaues dar¨ uber gesagt werden. Das Einzige, was per Definition garantiert ist, ist Folgendes: Ein short ist eine vorzeichenbehaftete Ganzzahl, deren Gr¨ oße kleiner oder gleich der Gr¨ oße eines int ist. Zum Gl¨ uck kann man die Aussage zumindest ein kleines Bisschen pr¨azisieren: Es wird auch garantiert, dass ein short mindestens 16 Bit lang ist. unsigned short int bzw. unsigned short: Genauso toll, wie die Aussage u ¨ber short ausgefallen ist, f¨allt sie auch f¨ ur unsigned short (=¨ ubliche Kurzform f¨ ur unsigned short int) aus: Ein unsigned short bezeichnet einen “kurzen” unsigned. Garantieren kann man wieder nur: Ein unsigned short ist eine positive Ganzzahl, deren Gr¨ oße kleiner oder gleich der Gr¨ oße eines unsigned ist. Auch hier gilt nat¨ urlich wieder, dass die L¨ ange mindestens 16 Bit betr¨ agt. long int bzw. long: Weil wir gerade bei den epochalen Erkenntnissen waren, machen wir damit gleich weiter: Ein long (=¨ ubliche Kurzform f¨ ur long int) bezeichnet einen “langen” int, f¨ ur den Folgendes garantiert ist: Ein long ist eine vorzeichenbehaftete Ganzzahl, deren Fassungsverm¨ ogen gr¨ oßer oder gleich dem eines int ist. Zum Gl¨ uck kann man auch diese Aussage zumindest ein kleines Bisschen pr¨azisieren: Es wird weiters garantiert, dass ein long mindestens 32 Bit lang ist. unsigned long int bzw. unsigned long: Dass nun f¨ ur einen unsigned long (=¨ ubliche Kurzform f¨ ur unsigned long int) Folgendes gilt, l¨aßt sich leicht erraten: Ein unsigned long ist eine positive Ganzzahl, deren Fassungsverm¨ ogen gr¨ oßer oder gleich dem eines unsigned ist. Die Garantie f¨ ur die Mindestl¨ ange von 32 Bit gilt nat¨ urlich hier ebenfalls. long long int bzw. long long:
18
2. Datentypen und Variablen
Je nach Plattform gibt es auch einen “besonders langen” long Wert. Seine Existenz ist allerdings vom Compiler abh¨angig. Hier hilft leider nur ausprobieren. Wenn er definiert ist, so kann man Folgendes garantiert sagen: Ein long long ist eine vorzeichenbehaftete Ganzzahl, deren Fassungsverm¨ ogen gr¨ oßer oder gleich dem eines long ist. unsigned long long int bzw. unsigned long long: Bez¨ uglich der Existenz eines unsigned long long gilt dasselbe, wie f¨ ur den long long Datentyp zuvor. Wenn er definiert ist, so ist Folgendes garantiert: Ein unsigned long long ist eine positive Ganzzahl, deren Fassungsverm¨ ogen gr¨ oßer oder gleich dem eines unsigned long ist. float: Wie die Ganzzahl-Datentypen, so sind auch die Gleitkomma-Datentypen von C++ maschinenabh¨ angig: float repr¨asentiert eine vorzeichenbehaftete Gleitkommazahl mit einfacher Genauigkeit (=single Precision), was auch immer das heißt. Ich m¨ ochte es einfach so formulieren: Arbeitet man in einem Programm mit Gleitkommazahlen, deren Gr¨oße sich in einem Bereich von in etwa ±1015 –±1035 bewegt und bei denen Rundungsfehler keine besondere Rolle spielen, dann ist die Verwendung von float normalerweise ausreichend. Will man ganz genau u ¨ber das Fassungsverm¨ ogen, die Genauigkeit und andere Eigenschaften eines float und auch anderer Datentypen Bescheid wissen, so kann man dies u ¨ber die sogenannten numeric_limits tun. Da es sich hierbei um eine objektorientierte Implementation handelt, wird die Besprechung auf Abschnitt 16.7 verschoben. Nicht-vorzeichenbehaftete Gleitkommazahlen (also unsigned float) gibt es nicht. Aufgrund der Natur dieser Zahlen w¨aren diese auch nicht besonders sinnvoll. double: Ein double repr¨ asentiert eine vorzeichenbehaftete Gleitkommazahl mit doppelter Genauigkeit (=double Precision), was auch immer man darunter nun versteht. Als kleinen Anhaltspunkt m¨ochte ich eigentlich nur erw¨ ahnen, dass double u ¨blicherweise vom Wertebereich (ca. ±10300 ) als auch von der Anf¨ alligkeit gegen¨ uber Rundungsfehlern her f¨ ur g¨angige Gleitkomma-Anwendungen brauchbar ist. Auch hier sind, wie bei float, alle Eigenschaften u ¨ber die numeric_limits herausfindbar. long double: Wo auch immer double nicht ausreichend ist, kann man auf long double zur¨ uckgreifen, der eine Gleitkommazahl mit erweiterter Genauigkeit (=extended Precision) bezeichnet. Hier wird es allerdings gleich noch etwas schwammiger als bei float und double zuvor, denn Genaues u ¨ber seine Natur und sein Fassungsverm¨ogen erf¨ahrt man wirklich nur noch durch die entsprechenden Abfragen von numeric_limits.
2.1 Primitive Datentypen
19
Leser, die ihr Grundwissen u ¨ber primitive Datentypen (auch Gleitkommazahlen), Besonderheiten derselben und die interne Repr¨asentation derselben kurz durch eine viel ausf¨ uhrlichere Erkl¨arung auffrischen m¨ochten, finden eine genaue Behandlung dieses Themas in Kapitel 4 und auch in Anhang A des Buchs Softwareentwicklung in C. Dort wird auch anhand von Beispielen die besondere Gefahr beim Mischen von Datentypen sowie von signed und unsigned-Varianten desselben Typs aufgezeigt. Alle diese Betrachtungen sind nicht nur f¨ ur C sondern auch f¨ ur C++ zu 100% g¨ ultig! Leider findet man immer wieder dieselben Fehler beim Umgang mit Variablen verschiedener Datentypen, egal, ob sie nun von Neulingen begangen werden oder ob es sich um erfahrenere Entwickler handelt. Die Ursache vieler Fehler ist sicherlich darin zu finden, dass Datentypen einfach einmal nach dem Motto “es gibt sie und sie funktionieren” betrachtet werden. Die genauen Interna werden als “das weiß sowieso der Computer” abgetan. Ich m¨ochte wirklich allen Lesern ans Herz legen, sich zumindest einmal mit den Interna genau auseinanderzusetzen, um das notwendige Verst¨andnis f¨ ur potentielle Fehlerquellen zu entwickeln. Aus diesem Grund m¨ochte ich dieses Kapitel auch mit ein paar Hinweisen auf m¨ ogliche Fallen schließen: Vorsicht Falle: Es d¨ urfen niemals Annahmen u ¨ber die Gr¨oße und das damit verbundene Fassungsverm¨ ogen von Datentypen getroffen werden. Insbesondere ist das Einzige, was man in Bezug auf die Gr¨oße von Ganzzahlendatentypen garantieren kann, Folgendes: sizeof(short)