Sie sind auf Seite 1von 932

Andr Willms

Visual C++ 2010


Das umfassende Handbuch

Liebe Leserin, lieber Leser,


ich freue mich, Ihnen dieses Kompendium zu Visual C++ 2010 vorstellen zu drfen. Eine solche umfassende Behandlung des Themas, wie es dieses Buch bietet, ist einfach notwendig, denn dank Visual C++ 2010 knnen Sie die Vorteile von C++ auch unter .NET nutzen. Einer davon springt besonders ins Auge: C++ ist nicht an .NET gebunden. Es knnen auch C++-Programme auf anderen Systemen geschrieben werden. Der erste Teil des Buches, ANSI C++, richtet sich vor allem an Programmierer ohne C++-Kenntnisse: Sie erhalten eine Einfhrung in die Sprache, angefangen bei den Variablen ber Klassen und Vererbung bis hin zu den Templates. Aber auch wenn Sie schon Erfahrung mit C++ haben, mchten Sie vielleicht das ein oder andere zu den Grundlagen noch einmal nachlesen. Wenn Sie schon mit C++ programmiert haben, werden Sie sich vor allem fr die Erweiterungen und nderungen der Sprache interessieren, die unter .NET notwendig sind. Dieses Thema wird im zweiten Teil des Buches, C++/CLI, behandelt. Hier wird auf die Auswirkungen auf die Klassen und die Vererbung eingegangen und es werden u.a. die Dateiverwaltung, Delegaten und Ereignisse sowie Collections behandelt. Im dritten Teil wird ausfhrlich auf die Programmierung grafischer Oberflchen eingegangen. Hier finden Sie alles Wichtige zu Windows Forms, zur Entwicklung von Steuerelementen, Menleisten und Kontextmens und zum Zeichnen mit GDI+. Auerdem wird gezeigt, wie Sie eine oder mehrere Seiten drucken, und auch die Datenbankanbindung kommt nicht zu kurz. Dieses Buch wurde mit groer Sorgfalt begutachtet, lektoriert und produziert. Sollten sich dennoch Fehler eingeschlichen haben oder Fragen auftreten, zgern Sie nicht, mit uns Kontakt aufzunehmen. Sagen Sie uns, was wir noch besser machen knnen. Ihre Anregungen und Fragen sind uns jederzeit willkommen.

Ihre Christine Siedle Lektorat Galileo Computing

christine.siedle@galileo-press.de www.galileocomputing.de Galileo Press Rheinwerkallee 4 53227 Bonn

Auf einen Blick


TEIL I ANSI C++ 1 2 3 4 5 6 7 Grundlagen von ANSI C++ ................................................... Kontrollstrukturen ................................................................ Komplexere Datentypen ...................................................... Klassen ................................................................................ Fortgeschrittene Sprachelemente ......................................... Die STL ................................................................................ Praxis Adressbuch .............................................................. 29 77 117 143 193 227 375

TEIL II C++/CLI 8 9 Grundlagen von C++/CLI ...................................................... Klassendefinition unter .NET ................................................ 409 433 485 541 567

10 Ntzliche .NET-Klassen ........................................................ 11 Dateiverwaltung ................................................................... 12 Praxis objektorientiertes Telefonbuch ................................ TEIL III .NET-Klassenbibliothek 13 Einfhrung in Windows Forms ............................................. 14 Ntzliche Windows-Forms-Klassen ...................................... 15 Steuerelemente I .................................................................. 16 Steuerelemente II ................................................................. 17 Mens & Leisten .................................................................. 18 GDI+ .................................................................................... 19 Drucken ............................................................................... 20 Datenbankanbindung ...........................................................

591 639 677 715 751 765 785 809

Der Name Galileo Press geht auf den italienischen Mathematiker und Philosophen Galileo Galilei (15641642) zurck. Er gilt als Grndungsfigur der neuzeitlichen Wissenschaft und wurde berhmt als Verfechter des modernen, heliozentrischen Weltbilds. Legendr ist sein Ausspruch Eppur si muove (Und sie bewegt sich doch). Das Emblem von Galileo Press ist der Jupiter, umkreist von den vier Galileischen Monden. Galilei entdeckte die nach ihm benannten Monde 1610. Lektorat Christine Siedle, Anne Scheibe Korrektorat Annette Lennartz, Bonn Einbandgestaltung Barbara Thoben, Kln Titelbilder oben links: fuxart, fotolia.com, oben rechts: Jorge Figueiredo, fotolia.com, unten rechts: Galileo Press Typografie und Layout Vera Brauner Herstellung Maxi Beithe Satz Typographie & Computer, Krefeld Druck und Bindung Bercker Graphischer Betrieb, Kevelaer Dieses Buch wurde gesetzt aus der Linotype Syntax Serif (9,75/14 pt) in FrameMaker.

Gerne stehen wir Ihnen mit Rat und Tat zur Seite: christine.siedle@galileo-press.de bei Fragen und Anmerkungen zum Inhalt des Buches service@galileo-press.de fr versandkostenfreie Bestellungen und Reklamationen britta.behrens@galileo-press.de fr Rezensions- und Schulungsexemplare

Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet ber http://dnb.d-nb.de abrufbar. ISBN 978-3-8362-1639-5

Galileo Press, Bonn 2011 1. Auflage 2011


Das vorliegende Werk ist in all seinen Teilen urheberrechtlich geschtzt. Alle Rechte vorbehalten, insbesondere das Recht der bersetzung, des Vortrags, der Reproduktion, der Vervielfltigung auf fotomechanischem oder anderen Wegen und der Speicherung in elektronischen Medien. Ungeachtet der Sorgfalt, die auf die Erstellung von Text, Abbildungen und Programmen verwendet wurde, knnen weder Verlag noch Autor, Herausgeber oder bersetzer fr mgliche Fehler und deren Folgen eine juristische Verantwortung oder irgendeine Haftung bernehmen. Die in diesem Werk wiedergegebenen Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. knnen auch ohne besondere Kennzeichnung Marken sein und als solche den gesetzlichen Bestimmungen unterliegen.

Inhalt
Vorwort .......................................................................................................... 23

TEIL I ANSI C++ 1 Grundlagen von ANSI C++ ....................................................... 29


1.1 Die Win32-Konsolenanwendung ................................................. 1.1.1 Eine C++-Datei hinzufgen ............................................. 1.1.2 Das Projekt kompilieren ................................................. 1.1.3 Das Programm starten .................................................... 1.1.4 Die Fehlerkorrektur ........................................................ Die Hauptfunktion ....................................................................... 1.2.1 Der Funktionskopf ......................................................... Die Ausgabe ................................................................................ 1.3.1 cout ............................................................................... 1.3.2 endl ............................................................................... 1.3.3 Escape-Sequenzen ......................................................... 1.3.4 Manipulatoren ............................................................... Die include-Direktive ................................................................... using ........................................................................................... 1.5.1 Bezugsrahmenoperator .................................................. Kommentare ................................................................................ 1.6.1 Einzeilige Kommentare .................................................. 1.6.2 Mehrzeilige Kommentare ............................................... 1.6.3 Kommentare verschachteln ............................................ 1.6.4 Kommentare in der Entwicklungsumgebung .................. Variablen ..................................................................................... 1.7.1 Bezeichner ..................................................................... 1.7.2 Eingabe .......................................................................... 1.7.3 Literale .......................................................................... 1.7.4 Konstanten .................................................................... Datentypen ................................................................................. 1.8.1 Elementare Datentypen .................................................. 1.8.2 Zusammengesetzte Datentypen ...................................... Operatoren .................................................................................. 1.9.1 Zuweisungsoperatoren ................................................... 1.9.2 Arithmetische Operatoren .............................................. 1.9.3 Inkrement und Dekrement ............................................. 29 30 32 33 33 34 35 36 37 37 38 38 40 40 41 41 42 42 42 43 43 45 46 47 48 49 50 53 54 54 56 57

1.2 1.3

1.4 1.5 1.6

1.7

1.8

1.9

Inhalt

1.10

1.9.4 Bitweise Operatoren ...................................................... 1.9.5 Vergleichsoperatoren ..................................................... 1.9.6 Logische Operatoren ...................................................... 1.9.7 Spezielle Operatoren ...................................................... 1.9.8 Operanden unterschiedlichen Datentyps ........................ 1.9.9 Bindungsstrke ............................................................... Die cmath-Funktionen .................................................................

58 62 63 66 66 69 71

Kontrollstrukturen ................................................................... 77
2.1 Verzweigungen ............................................................................ 2.1.1 Bedingungen & bool ...................................................... 2.1.2 if .................................................................................... 2.1.3 else ................................................................................ 2.1.4 Einsatz logischer Operatoren .......................................... 2.1.5 ?:-Operator .................................................................... 2.1.6 switch & case ................................................................. Schleifen ...................................................................................... 2.2.1 while .............................................................................. 2.2.2 do while ......................................................................... 2.2.3 for .................................................................................. 2.2.4 Wann welche Schleife? ................................................... 2.2.5 break ............................................................................. 2.2.6 continue ........................................................................ Funktionen .................................................................................. 2.3.1 Lokale Variablen ............................................................ 2.3.2 Funktionen mit Parametern ............................................ 2.3.3 Mehrere Parameter ........................................................ 2.3.4 Rckgabewerte .............................................................. 2.3.5 Standardwerte fr Parameter ......................................... 2.3.6 Funktionsdeklarationen .................................................. Module ....................................................................................... 2.4.1 Definition auslagern ....................................................... 2.4.2 Deklaration auslagern .................................................... 2.4.3 Kompilationsvorgang ..................................................... 2.4.4 Mehrfachdeklarationen vermeiden ................................. 77 78 78 80 83 84 85 91 92 94 96 98 98 99 100 102 104 106 107 109 110 111 111 112 113 115

2.2

2.3

2.4

Komplexere Datentypen .......................................................... 117


3.1 Arrays .......................................................................................... 117 3.1.1 Variabler Index ............................................................... 118 3.1.2 Mehrdimensionale Arrays ............................................... 119

Inhalt

3.2

3.3

3.4

3.5

3.1.3 Einschrnkungen von Arrays ........................................... C-Strings ...................................................................................... 3.2.1 char ............................................................................... 3.2.2 cctype-Funktionen ......................................................... 3.2.3 Aufbau von C-Strings ..................................................... 3.2.4 Texteingabe ................................................................... 3.2.5 cstring-Funktionen ......................................................... Strukturen ................................................................................... 3.3.1 Definition einer Struktur ................................................ 3.3.2 Definition der Strukturelemente ..................................... 3.3.3 Zugriff auf die Struktur-Elemente ................................... 3.3.4 Strukturobjekt als Einheit ............................................... Zeiger .......................................................................................... 3.4.1 Adressoperator .............................................................. 3.4.2 Definition eines Zeigers .................................................. 3.4.3 Dereferenzierungsoperator ............................................. 3.4.4 Zeiger als Funktionsparameter ........................................ 3.4.5 Zeiger auf Struktur- und Klassenobjekte ......................... 3.4.6 Zeiger auf Arrays ............................................................ 3.4.7 Zeigerarithmetik ............................................................. Referenzen ..................................................................................

120 120 120 121 122 123 125 130 130 131 132 132 133 133 134 135 136 138 139 140 141

Klassen ..................................................................................... 143


4.1 4.2 4.3 4.4 Definition einer Klasse ................................................................. 4.1.1 Erstellen einer Klasse mit Visual C++ .............................. Attribute ...................................................................................... Zugriffsrechte ............................................................................... Methoden ................................................................................... 4.4.1 Externe Definition .......................................................... 4.4.2 this ................................................................................ Konstruktoren ............................................................................. 4.5.1 Externe Definition .......................................................... 4.5.2 Private Attribute ............................................................ 4.5.3 Elementinitialisierungsliste ............................................. 4.5.4 Explizite Konstruktoren .................................................. 4.5.5 Standardkonstruktor ...................................................... 4.5.6 Kopierkonstruktor .......................................................... Konstanzwahrende Methoden ..................................................... 4.6.1 Vernderliche Attribute .................................................. berladen von Methoden ............................................................ Statische Klassenelemente ........................................................... 143 144 147 148 149 150 152 153 155 155 156 157 158 159 160 161 161 163 7

4.5

4.6 4.7 4.8

Inhalt

4.9 4.10 4.11

4.12 4.13 4.14 4.15 4.16 4.17 4.18 4.19

4.20

4.8.1 Statische Methoden ....................................................... 4.8.2 Statische Attribute ......................................................... typedef ........................................................................................ Verschachtelte Klassen ................................................................ Vererbung ................................................................................... 4.11.1 Das Wesen der Vererbung .............................................. 4.11.2 Die Syntax der Vererbung .............................................. Konstruktoren und Vererbung ..................................................... Erweitern durch Vererbung .......................................................... Methoden berschreiben ............................................................. Geschtzte Attribute .................................................................... Polymorphie ................................................................................ Virtuelle Methoden ..................................................................... UML ............................................................................................ Schnittstellen ............................................................................... 4.19.1 Rein virtuelle Methoden ................................................ 4.19.2 Rein abstrakte Klassen ................................................... Downcasts ...................................................................................

163 166 167 169 171 172 173 173 176 177 178 180 182 183 185 188 191 192

Fortgeschrittene Sprachelemente ............................................ 193


5.1 Namensbereiche .......................................................................... 5.1.1 using namespace ............................................................ 5.1.2 Darstellung in der UML .................................................. 5.1.3 Verschachtelte Namensbereiche ..................................... 5.1.4 Synonyme definieren ...................................................... Dynamische Speicherverwaltung .................................................. 5.2.1 Erzeugen von Objekten .................................................. 5.2.2 Erzeugen von Arrays ....................................................... 5.2.3 Destruktoren .................................................................. 5.2.4 Wenn new fehlschlgt ................................................. Ausnahmen ................................................................................. 5.3.1 Ausnahmen werfen ........................................................ 5.3.2 Ausnahmen fangen ........................................................ 5.3.3 Unterschiedliche Ausnahmen auffangen ......................... 5.3.4 Ausnahmen weiterwerfen .............................................. 5.3.5 Eigene Ausnahme-Klassen .............................................. Templates .................................................................................... 5.4.1 Funktionstemplates ........................................................ 5.4.2 Klassentemplates ........................................................... 193 195 196 196 197 197 198 199 199 201 202 202 206 207 208 209 210 211 213

5.2

5.3

5.4

Inhalt

5.5

Operatoren berladen ................................................................. 5.5.1 Zuweisungsoperatoren ................................................... 5.5.2 Arithmetische Operatoren .............................................. 5.5.3 Vergleichsoperatoren ..................................................... 5.5.4 Indexoperator ................................................................ 5.5.5 Dereferenzierungsoperator ............................................. 5.5.6 Inkrement- und Dekrementoperator ..............................

214 217 220 223 223 224 225

Die STL ..................................................................................... 227


6.1 Die Philosophie der STL ............................................................... 6.1.1 Container ....................................................................... 6.1.2 Iteratoren ....................................................................... 6.1.3 Algorithmen ................................................................... 6.1.4 Allokatoren .................................................................... Grundlagen .................................................................................. 6.2.1 Zeitkomplexitt .............................................................. 6.2.2 Funktionsobjekte ........................................................... 6.2.3 Paare ............................................................................. 6.2.4 Vergleichsoperatoren ..................................................... 6.2.5 Bereichsangaben ............................................................ Vektoren ..................................................................................... 6.3.1 Konstruktoren ................................................................ 6.3.2 Operatoren .................................................................... 6.3.3 Methoden ...................................................................... 6.3.4 Vektoren im Einsatz ....................................................... Deque ......................................................................................... 6.4.1 Aufbau einer Deque ....................................................... 6.4.2 Definition einer Deque ................................................... 6.4.3 Methoden ...................................................................... Listen .......................................................................................... 6.5.1 Datentypen .................................................................... 6.5.2 Konstruktoren ................................................................ 6.5.3 Operatoren .................................................................... 6.5.4 Methoden ...................................................................... Sets ............................................................................................. 6.6.1 Definition ...................................................................... 6.6.2 Konstruktoren ................................................................ 6.6.3 Operatoren .................................................................... 6.6.4 Methoden ...................................................................... Maps ........................................................................................... 227 228 229 233 234 234 234 237 242 243 244 244 246 247 248 255 256 256 259 259 260 262 263 263 264 272 276 277 277 278 283

6.2

6.3

6.4

6.5

6.6

6.7

Inhalt

6.8

6.9

6.10

6.11

6.12

6.7.1 Konstruktoren ................................................................ 6.7.2 Operatoren .................................................................... 6.7.3 Methoden ...................................................................... Strings ......................................................................................... 6.8.1 char_traits ...................................................................... 6.8.2 basic_string .................................................................... 6.8.3 Konstruktoren ................................................................ 6.8.4 Operatoren .................................................................... 6.8.5 Methoden ...................................................................... Adapter ....................................................................................... 6.9.1 Stacks ............................................................................ 6.9.2 Konstruktoren ................................................................ 6.9.3 Operatoren .................................................................... 6.9.4 Methoden ...................................................................... 6.9.5 Queues .......................................................................... 6.9.6 Konstruktoren ................................................................ 6.9.7 Operatoren .................................................................... 6.9.8 Methoden ...................................................................... 6.9.9 Priority-Queues .............................................................. 6.9.10 Konstruktoren ................................................................ 6.9.11 Methoden ...................................................................... Iteratoren .................................................................................... 6.10.1 Iteratorkategorien .......................................................... 6.10.2 Iterator-Tags .................................................................. 6.10.3 Iterator-Traits ................................................................. Reverse-Iteratoren ....................................................................... 6.11.1 Insert-Iteratoren ............................................................. 6.11.2 Back-Insert-Iterator ........................................................ 6.11.3 Insert-Iterator ................................................................ 6.11.4 Stream-Iteratoren ........................................................... 6.11.5 Methoden fr Iteratoren ................................................ Algorithmen ................................................................................ 6.12.1 Sequenzlngen ............................................................... 6.12.2 Iteratorzugriffe ............................................................... 6.12.3 Ein alphabetischer berblick .......................................... 6.12.4 Nicht-modifizierende Algorithmen ................................. 6.12.5 Elemente suchen ............................................................ 6.12.6 Elemente zhlen ............................................................. 6.12.7 Minimum und Maximum ............................................... 6.12.8 Sequenzen suchen ..........................................................

285 285 286 288 289 292 293 294 295 310 310 312 312 313 314 315 316 316 317 319 319 320 321 322 323 326 328 330 330 331 333 334 334 335 336 339 340 342 342 344

10

Inhalt

6.12.9 6.12.10 6.12.11 6.12.12 6.12.13 6.12.14 6.12.15 6.12.16 6.12.17 6.12.18 6.12.19 6.12.20

Sequenzen vergleichen ................................................... for_each ......................................................................... Modifizierende Algorithmen .......................................... Sequenzen kopieren ....................................................... Elemente ersetzen .......................................................... Elemente lschen ........................................................... Elementreihenfolge verndern ........................................ transform Elemente umwandeln .................................. Sortierende Algorithmen ................................................ Algorithmen fr sortierte Sequenzen .............................. Algorithmen fr Mengen ................................................ Heap-Algorithmen .........................................................

346 348 348 349 351 353 356 359 359 364 369 372

Praxis Adressbuch ................................................................. 375


7.1 Die eigene Liste ........................................................................... 7.1.1 Konstruktoren und Destruktoren .................................... 7.1.2 Hilfsmethoden ............................................................... 7.1.3 ffentliche Methoden .................................................... 7.1.4 Iteratoren ....................................................................... Die Klasse Kontakt ................................................................... 7.2.1 Die Klassendefinition ..................................................... 7.2.2 Die Methoden ............................................................... Die Klasse Kontaktliste ............................................................. 7.3.1 Die Klassendefinition ..................................................... 7.3.2 Die Methoden ............................................................... Die Hauptfunktion ....................................................................... 375 377 379 384 391 394 394 396 398 398 399 404

7.2

7.3

7.4

TEIL II C++/CLI 8 Grundlagen von C++/CLI .......................................................... 409


8.1 8.2 C++/CLI ....................................................................................... .NET ............................................................................................ 8.2.1 Common Language Runtime (CLR) ................................. 8.2.2 Common Language Specification (CLS) ........................... 8.2.3 Common Type System (CTS) ........................................... 8.2.4 Klassenbibliothek ........................................................... 8.2.5 WPF ............................................................................... CLR-Konsolenanwendung ............................................................ 8.3.1 Die Projekt-Dateien ....................................................... 409 410 411 413 414 416 417 418 419

8.3

11

Inhalt

8.4

8.5 8.6 8.7 8.8

8.9 8.10 8.11 8.12

Das Beispielprogramm ................................................................. 8.4.1 stdafx ............................................................................. 8.4.2 Namensbereich System ............................................... 8.4.3 main .............................................................................. 8.4.4 WriteLine ....................................................................... Trackinghandle ............................................................................ Trackingreferenz .......................................................................... Ausgabe ...................................................................................... 8.7.1 Formatierte Ausgabe ...................................................... Arrays .......................................................................................... 8.8.1 Arrays initialisieren ......................................................... 8.8.2 Mehrdimensionale Arrays ............................................... 8.8.3 for each ......................................................................... Eingabe ....................................................................................... Typumwandlung .......................................................................... Ausnahmen ................................................................................. 8.11.1 finally ............................................................................. STL/CLR .......................................................................................

419 420 420 420 420 421 422 423 424 425 426 426 426 427 427 428 431 431

Klassendefinition unter .NET ................................................... 433


9.1 9.2 9.3 Eine verwaltete Klasse erstellen ................................................... 9.1.1 Zugriffsrecht auf die Klasse ............................................. Die Ausgabe ................................................................................ Eigenschaften .............................................................................. 9.3.1 Externe Definition .......................................................... 9.3.2 Unterschiedliche Zugriffsrechte ...................................... 9.3.3 Eigenschaften ohne Attribute ......................................... 9.3.4 Virtuelle und statische Eigenschaften ............................. Indexer ........................................................................................ 9.4.1 Eigenschaften-Indexer .................................................... 9.4.2 Klassen-Indexer .............................................................. 9.4.3 Mehrdimensionale Indexer ............................................. Ressourcenfreigabe ...................................................................... 9.5.1 Deterministische Freigabe verwalteter Ressourcen ......... 9.5.2 Freigabe nicht verwalteter Ressourcen ........................... 9.5.3 Deterministische Freigabe nicht verwalteter Ressourcen .................................................. 9.5.4 Zusammenfassung .......................................................... Wertklassen ................................................................................. Operatoren berladen ................................................................. 433 435 436 437 440 441 442 442 442 443 444 445 445 446 448 449 450 450 452

9.4

9.5

9.6 9.7

12

Inhalt

9.8 9.9

9.10 9.11 9.12 9.13 9.14

9.15 9.16

9.7.1 Operatoren fr Werttypen ............................................. 9.7.2 Operatoren fr Verweistypen ......................................... 9.7.3 Vergleichsoperatoren ..................................................... 9.7.4 Umwandlungsoperatoren ............................................... Literale ........................................................................................ Aufzhlungen .............................................................................. 9.9.1 Explizite Typangabe ....................................................... 9.9.2 Flags .............................................................................. Vererbung ................................................................................... 9.10.1 override versus new ................................................. Abstrakte Methoden und Klassen ................................................ Versiegelte Methoden ................................................................. Versiegelte Klassen ...................................................................... Schnittstellen ............................................................................... 9.14.1 Schnittstellen und Mehrfachvererbung ........................... 9.14.2 Identische Methoden ..................................................... Delegaten .................................................................................... 9.15.1 Multicast-Delegaten ....................................................... Ereignisse .................................................................................... 9.16.1 Die Klasse Becher mit Ereignissen ...............................

454 454 455 456 457 458 460 461 463 466 467 469 469 471 473 474 475 478 479 481

10 Ntzliche .NET-Klassen ........................................................... 485


10.1 CultureInfo .................................................................................. 10.1.1 DateTimeFormatInfo ...................................................... 10.1.2 NumberFormatInfo ........................................................ String ........................................................................................... 10.2.1 String-Inhalte ansprechen .............................................. 10.2.2 String-Inhalte verndern ................................................ 10.2.3 String-Inhalte hinzufgen ............................................... 10.2.4 String-Inhalte lschen .................................................... 10.2.5 Strings vergleichen ......................................................... 10.2.6 Suchen in Strings ............................................................ 10.2.7 Strings formatieren ......................................................... StringBuilder ................................................................................ 10.3.1 String-Inhalte ansprechen .............................................. 10.3.2 String-Inhalte hinzufgen ............................................... 10.3.3 String-Inhalte verndern ................................................ 10.3.4 String-Inhalte lschen .................................................... 10.3.5 StringBuilder-Objekte vergleichen .................................. Char ............................................................................................ 485 486 489 489 490 491 491 492 493 494 495 496 496 496 497 498 498 498

10.2

10.3

10.4

13

Inhalt

10.5 10.6 10.7 10.8 10.9 10.10

10.11 10.12

10.13 10.14

10.15 10.16

10.17

10.18

10.19 10.20

Collections ................................................................................... IComparer ................................................................................... IComparable ................................................................................ Collection-Schnittstellen .............................................................. IEnumerable ................................................................................ ICollection ................................................................................... 10.10.1 Queue ............................................................................ 10.10.2 Stack .............................................................................. IList ............................................................................................. 10.11.1 ArrayList ........................................................................ IDictionary ................................................................................... 10.12.1 Hashtable ....................................................................... 10.12.2 SortedList ....................................................................... Generische Collections ................................................................. 10.13.1 Die generischen Schnittstellen ....................................... Anwendungsbeispiele .................................................................. 10.14.1 Personen verwalten mit List ........................................... 10.14.2 Eine Collection mit int-Werten absteigend sortieren ...... Random Zufallszahlen ............................................................... Math mathematische Funktionen .............................................. 10.16.1 Methoden ...................................................................... 10.16.2 Konstanten .................................................................... Console die Konsole ................................................................. 10.17.1 Eigenschaften ................................................................. 10.17.2 Methoden ...................................................................... Environment die Umgebung ...................................................... 10.18.1 Eigenschaften ................................................................. 10.18.2 Methoden ...................................................................... GC der Garbage-Collector ......................................................... Timer der Taktgeber .................................................................. 10.20.1 Ein Beispiel ....................................................................

499 500 500 501 502 503 503 505 507 508 511 512 513 515 517 518 518 521 522 523 524 528 528 528 530 534 534 535 537 539 540

11 Dateiverwaltung ...................................................................... 541


11.1 DateTime ..................................................................................... 11.1.1 ffentliche Eigenschaften ............................................... 11.1.2 ffentliche Methoden .................................................... Laufwerke .................................................................................... Verzeichnisse ............................................................................... 11.3.1 Directory ........................................................................ 11.3.2 FileSystemInfo ................................................................ 541 542 543 545 546 547 550

11.2 11.3

14

Inhalt

11.4

11.5 11.6

11.7

11.8 11.9

11.3.3 DirectoryInfo ................................................................. Dateien ....................................................................................... 11.4.1 File ................................................................................ 11.4.2 FileInfo .......................................................................... Dateistrme ................................................................................. 11.5.1 FileStream ...................................................................... Binrstrme ................................................................................. 11.6.1 BinaryWriter .................................................................. 11.6.2 BinaryReader .................................................................. Zeichenstrme ............................................................................. 11.7.1 StreamWriter ................................................................. 11.7.2 StreamReader ................................................................ Serialisierung ............................................................................... 11.8.1 Das SerializableAttribute ................................................ Praktische Anwendung ................................................................ 11.9.1 Einlesen einer Textdatei ................................................. 11.9.2 Beschreiben einer Log-Datei .......................................... 11.9.3 Datei byteweise kopieren ...............................................

551 551 552 554 554 555 557 557 559 560 560 561 562 563 564 565 566 566

12 Praxis objektorientiertes Telefonbuch .................................. 567


12.1 12.2 Die Schnittstelle IKontakt ............................................................ Die Klasse Kontakt ................................................................... 12.2.1 Klassendefinition ............................................................ 12.2.2 Die externen Definitionen .............................................. Die Klasse KontaktTxt ............................................................... 12.3.1 Die Klassendefinition ..................................................... 12.3.2 Die externen Definitionen .............................................. Die Schnittstelle ITelefonbuch .................................................. Die Klasse Telefonbuch ............................................................. 12.5.1 Die Klassendefinition ..................................................... 12.5.2 Die externen Definitionen .............................................. Die Klasse TelefonbuchTxt ........................................................ 12.6.1 Die Klassendefinition ..................................................... 12.6.2 Die externen Definitionen .............................................. Das Hauptprogramm ................................................................... Datei-IO ...................................................................................... 12.8.1 Die angepasste IKontakt-Schnittstelle ............................ 12.8.2 Die angepasste Kontakt-Klasse ....................................... 12.8.3 Die angepasste ITelefonbuch-Schnittstelle ...................... 12.8.4 Die abstrakte Fabrik ....................................................... 567 568 569 570 571 571 571 572 573 573 575 577 578 579 580 582 582 582 583 583

12.3

12.4 12.5

12.6

12.7 12.8

15

Inhalt

12.8.5 12.8.6 12.8.7 12.8.8

Die Schnittstelle IKontaktFabrik .................................. Die Klasse KontaktFabrikTxt ........................................ Die angepasste Klasse Telefonbuch ............................. Die angepasste Klasse TelefonbuchTxt ........................

584 584 584 586

TEIL III .NET-Klassenbibliothek 13 Einfhrung in Windows Forms ................................................ 591


13.1 13.2 13.3 13.4 13.5 Das Hauptprogramm ................................................................... Die Form-Datei ............................................................................ Das Eigenschaftenfenster des Designers ....................................... Component ................................................................................. Control Basis aller Steuerlemente .............................................. 13.5.1 ffentliche Eigenschaften ............................................... 13.5.2 ffentliche Methoden .................................................... 13.5.3 ffentliche Ereignisse ..................................................... ScrollableControl scrollbare Container ....................................... 13.6.1 ffentliche Eigenschaften ............................................... 13.6.2 ffentliche Methoden .................................................... 13.6.3 ffentliche Ereignisse ..................................................... Form die Formularklasse ........................................................... 13.7.1 ffentliche Eigenschaften ............................................... 13.7.2 ffentliche Methoden .................................................... 13.7.3 ffentliche Ereignisse ..................................................... Ereignisse im Designer ................................................................. 13.8.1 Handler hinzufgen ........................................................ 13.8.2 Handler entfernen .......................................................... 592 594 596 597 598 599 610 614 622 623 626 626 627 627 634 635 637 637 638

13.6

13.7

13.8

14 Ntzliche Windows-Forms-Klassen ........................................ 639


14.1 14.2 Assembly-Verweise hinzufgen .................................................... Size die Grenangabe .............................................................. 14.2.1 Eigenschaften ................................................................. 14.2.2 Methoden ...................................................................... Point die Positionsangabe ......................................................... 14.3.1 Eigenschaften ................................................................. 14.3.2 Methoden ...................................................................... Rectangle ein rechteckiger Bereich ............................................ 14.4.1 Eigenschaften ................................................................. 14.4.2 Methoden ...................................................................... 639 642 642 642 643 643 643 644 644 645

14.3

14.4

16

Inhalt

14.5

14.6

14.7 14.8

14.9 14.10

14.11

14.12

14.13

14.14

14.15

Color Farbangaben .................................................................... 14.5.1 Eigenschaften ................................................................. 14.5.2 Methoden ...................................................................... 14.5.3 Farben im Designer ........................................................ Font die Schriftart ..................................................................... 14.6.1 Eigenschaften ................................................................. 14.6.2 Fonts im Designer .......................................................... MessageBox ein Nachrichtenfenster .......................................... Image die Grundlage der Bilder ................................................. 14.8.1 Eigenschaften ................................................................. 14.8.2 Methoden ...................................................................... Bitmap die Klasse fr konkrete Bilder ........................................ 14.9.1 Methoden ...................................................................... Icon die kleinen Bilder .............................................................. 14.10.1 Konstruktoren ................................................................ 14.10.2 Eigenschaften ................................................................. 14.10.3 Methoden ...................................................................... 14.10.4 SystemIcons die Klasse mit den System-Icons .............. ImageList die Bilderliste ............................................................ 14.11.1 Eigenschaften ................................................................. 14.11.2 Methoden ...................................................................... Cursor die Mauszeiger ............................................................... 14.12.1 Konstruktoren ................................................................ 14.12.2 Eigenschaften ................................................................. 14.12.3 Methoden ...................................................................... 14.12.4 Cursors ein Mauszeiger fr jede Situation .................... Padding Abstnde und Rnder .................................................. 14.13.1 Konstruktoren ................................................................ 14.13.2 Eigenschaften ................................................................. 14.13.3 Methoden ...................................................................... Standarddialoge ........................................................................... 14.14.1 FileDialog ...................................................................... 14.14.2 OpenFileDialog .............................................................. 14.14.3 SaveFileDialog ............................................................... 14.14.4 FolderBrowserDialog die Suche nach Verzeichnissen ... 14.14.5 ColorDialog bringt Farbe ins Leben .............................. 14.14.6 FontDialog die Auswahl fr Schriftarten ...................... Type Kern der Reflection-API .................................................... 14.15.1 Eigenschaften ................................................................. 14.15.2 Methoden ......................................................................

648 648 650 651 653 653 654 654 657 657 657 658 659 659 660 660 660 661 661 661 662 662 663 663 663 664 664 664 664 665 665 665 669 670 671 672 673 675 675 676

17

Inhalt

15 Steuerelemente I ...................................................................... 677


15.1 Label Beschriftungen ................................................................. 15.1.1 Eigenschaften ................................................................. 15.1.2 LinkLabel anklickbare Beschriftungen .......................... GroupBox Gruppierungen ......................................................... ButtonBase die Basis der Buttons .............................................. 15.3.1 Eigenschaften ................................................................. Button Schaltflchen ................................................................. CheckBox Elemente zum Abhaken ............................................ 15.5.1 Eigenschaften ................................................................. 15.5.2 Ereignisse ....................................................................... RadioButton Optionen zur Auswahl .......................................... 15.6.1 Eigenschaften ................................................................. 15.6.2 Methoden ...................................................................... 15.6.3 Ereignisse ....................................................................... PictureBox ein Bilderrahmen ..................................................... 15.7.1 Eigenschaften ................................................................. 15.7.2 Methoden ...................................................................... 15.7.3 Ereignisse ....................................................................... TextBoxBase die Basis der Texteingabefelder ............................. 15.8.1 Eigenschaften ................................................................. 15.8.2 Methoden ...................................................................... 15.8.3 Ereignisse ....................................................................... TextBox ein einfaches Texteingabefeld ...................................... 15.9.1 Eigenschaften ................................................................. MaskedTextBox Eingabe nach Vorschrift ................................... 15.10.1 Eigenschaften ................................................................. 15.10.2 Methoden ...................................................................... 15.10.3 Ereignisse ....................................................................... RichTextBox die kleine Textverarbeitung ................................... ListControl die Basis aller Listenelemente .................................. 15.12.1 Eigenschaften ................................................................. ListBox eine einfache Auflistung ................................................ 15.13.1 Eigenschaften ................................................................. 15.13.2 Methoden ...................................................................... 15.13.3 Ereignisse ....................................................................... 15.13.4 ComboBox ..................................................................... ProgressBar der Fortschrittsbalken ............................................ 15.14.1 Eigenschaften ................................................................. 15.14.2 Methoden ...................................................................... 677 678 680 680 681 681 682 683 684 685 686 686 687 687 687 688 690 690 691 691 693 696 696 697 700 700 705 705 706 707 707 708 708 711 712 712 713 713 714

15.2 15.3 15.4 15.5

15.6

15.7

15.8

15.9 15.10

15.11 15.12 15.13

15.14

18

Inhalt

16 Steuerelemente II ..................................................................... 715


16.1 16.2 16.3 16.4 Panel die Basis komplexerer Gruppierungen .............................. 16.1.1 Eigenschaften ................................................................. FlowLayoutPanel Gruppierung wie Flietext .............................. 16.2.1 Eigenschaften ................................................................. TableLayoutPanel Gruppierung zu Tabellenform ........................ 16.3.1 Eigenschaften ................................................................. SplitContainer eine grenvernderbare Aufteilung ................... 16.4.1 Eigenschaften ................................................................. 16.4.2 Ereignisse ....................................................................... TabControl Gruppierung ber Registerkarten ............................ 16.5.1 Eigenschaften ................................................................. 16.5.2 Ereignisse ....................................................................... 16.5.3 TabPage die Registerkarte ........................................... ListView zweidimensionale Listen ............................................. 16.6.1 Eigenschaften ................................................................. 16.6.2 Methoden ...................................................................... 16.6.3 Ereignisse ....................................................................... 16.6.4 ColumnHeader die berschriften der Liste ................... 16.6.5 ListViewGroup die Gruppen der Liste .......................... 16.6.6 ListViewItem die Elemente der Liste ............................ 16.6.7 ListViewSubItem die Unterelemente der Liste ............. TreeView die Baumdarstellung .................................................. 16.7.1 Eigenschaften ................................................................. 16.7.2 Methoden ...................................................................... 16.7.3 Ereignisse ....................................................................... 16.7.4 TreeNode die Knoten des Baums ................................. 715 716 716 717 717 718 719 719 721 722 723 724 726 726 728 734 735 737 738 738 740 741 742 744 745 747

16.5

16.6

16.7

17 Mens & Leisten ...................................................................... 751


17.1 17.2 17.3 17.4 17.5 ToolStrip die Symbolleiste ......................................................... 17.1.1 Eigenschaften ................................................................. MenuStrip die Menleiste ......................................................... StatusStrip die Statusleiste ........................................................ ContextMenuStrip das Kontextmen ......................................... Die ToolStrip-Elemente ................................................................ 17.5.1 ToolStripItem die Basis der ToolStrip-Elemente ........... 17.5.2 ToolStripButton ein einfacher Button .......................... 17.5.3 ToolStripComboBox eine Combobox fr ToolStrips ...... 17.5.4 ToolStripDropDownButton eine aufklappbare Schaltflche ............................................... 751 752 753 754 755 755 755 757 758 758 19

Inhalt

17.6

ToolStripLabel, ToolStripStatusLabel eine Leistenbeschriftung ........................................................ 17.5.6 ToolStripMenuItem ein Menpunkt ............................ 17.5.7 ToolStripProgressBar der Fortschrittsbalken in der Leiste ................................................................... 17.5.8 ToolStripSeparator der Spalter unter den Elementen ... 17.5.9 ToolStripSplitButton die Kombischaltflche ................. 17.5.10 ToolStripTextBox die Textbox in der Leiste .................. ToolStripContainer die Spielwiese fr Leisten ............................

17.5.5

759 760 761 761 762 762 763

18 GDI+ ......................................................................................... 765


18.1 Brush die Pinsel ........................................................................ 18.1.1 SolidBrush eine solide Fllung ..................................... 18.1.2 TextureBrush der Stempel ........................................... 18.1.3 LinearGradientBrush der Farbverlauf ............................ 18.1.4 HatchBrush der Pinsel mit Muster ............................... 18.1.5 Brushes ein Pinsel fr alle Flle .................................... Pen der Stift .............................................................................. Graphics das Zeichenbrett ......................................................... 18.3.1 Eigenschaften ................................................................. 18.3.2 Methoden ...................................................................... StringFormat das Aussehen der Strings ...................................... 18.4.1 Konstruktoren ................................................................ 18.4.2 Eigenschaften ................................................................. Zeichnen ber Paint .................................................................. 18.5.1 Partielles Neuzeichnen ................................................... 18.5.2 Double Buffering ............................................................ 766 766 767 768 769 770 770 771 771 773 778 778 779 781 781 782

18.2 18.3

18.4

18.5

19 Drucken .................................................................................... 785


19.1 PrintDocument die Kernklasse zum Drucken ............................. 19.1.1 ffentliche Eigenschaften ............................................... 19.1.2 Methoden ...................................................................... 19.1.3 Ereignisse ....................................................................... PrinterSettings Wer druckt? ...................................................... 19.2.1 Eigenschaften ................................................................. 19.2.2 Methoden ...................................................................... PrintDialog die Druckerauswahl ................................................ 19.3.1 Eigenschaften ................................................................. PageSettings die Seiteneigenschaften ........................................ 785 785 786 786 788 788 795 795 796 797

19.2

19.3 19.4

20

Inhalt

19.5 19.6 19.7 19.8

19.4.1 Eigenschaften ................................................................. PageSetupDialog die Seite einrichten ........................................ 19.5.1 Eigenschaften ................................................................. PrintPreviewDialog die Druckvorschau ...................................... PrintPreviewControl das Steuerelement .................................... 19.7.1 Eigenschaften ................................................................. Drucken in der Praxis ................................................................... 19.8.1 Drucken dreier Seiten ....................................................

797 799 800 801 802 802 803 803

20 Datenbankanbindung .............................................................. 809


20.1 20.2 Das Zusammenspiel der Klassen ................................................... OleDbConnection die Datenbankverbindung ............................ 20.2.1 Konstruktoren ................................................................ 20.2.2 Eigenschaften ................................................................. 20.2.3 Methoden ...................................................................... 20.2.4 Die Verbindungszeichenfolge ......................................... 20.2.5 Verbindungspooling ....................................................... 20.2.6 Ein Beispiel .................................................................... OleDbCommand der SQL-Befehl ............................................... 20.3.1 Konstruktoren ................................................................ 20.3.2 Eigenschaften ................................................................. 20.3.3 Methoden ...................................................................... 20.3.4 Beispiele ........................................................................ OleDbParameter die Befehlsparameter ...................................... 20.4.1 Konstruktoren ................................................................ 20.4.2 Eigenschaften ................................................................. 20.4.3 Methoden ...................................................................... OleDbDataReader der Datensatzleser ....................................... 20.5.1 Eigenschaften ................................................................. 20.5.2 Methoden ...................................................................... 20.5.3 Beispiel .......................................................................... OleDbTransaction die Transaktion ............................................. 20.6.1 Eigenschaften ................................................................. 20.6.2 Methoden ...................................................................... 20.6.3 Beispiel .......................................................................... DataTable die Tabelle im Speicher ............................................ 20.7.1 Konstruktoren ................................................................ 20.7.2 Eigenschaften ................................................................. 20.7.3 Methoden ...................................................................... 20.7.4 Ereignisse ....................................................................... 810 811 811 812 813 814 816 817 819 820 820 822 823 826 827 828 829 830 830 831 832 833 835 836 836 838 839 840 840 842

20.3

20.4

20.5

20.6

20.7

21

Inhalt

20.7.5 DataColumn die Tabellenspalte ................................... 20.7.6 DataRow die Tabellenzeile .......................................... 20.7.7 Beispiel .......................................................................... 20.8 DataSet die Tabellensammlung ................................................. 20.8.1 Konstruktoren ................................................................ 20.8.2 Eigenschaften ................................................................. 20.8.3 Methoden ...................................................................... 20.9 DataGridView die sichtbare Tabelle ........................................... 20.9.1 Eigenschaften ................................................................. 20.9.2 Methoden ...................................................................... 20.9.3 DataGridViewColumn die sichtbare Tabellenspalte ...... 20.9.4 DataGridViewButtonColumn die Button-Spalte ........... 20.9.5 DataGridViewCheckBoxColumn die Checkbox-Spalte ...................................................... 20.9.6 DataGridViewComboBoxColumn die Combobox-Spalte ..................................................... 20.9.7 DataGridViewImageColumn die Bilder-Spalte .............. 20.9.8 DataGridViewLinkColumn die Link-Spalte ................... 20.9.9 DataGridViewTextBoxColumn die Textbox-Spalte ........ 20.9.10 DataGridViewRow die sichtbare Zeile .......................... 20.9.11 DataGridViewCell die Tabellenzelle ............................. 20.10 OleDbDataAdapter automatisierte Kommunikation .................. 20.10.1 Eigenschaften ................................................................. 20.10.2 Methoden ...................................................................... 20.10.3 Beispiel ..........................................................................

844 846 849 851 852 852 853 854 855 859 860 863 863 865 868 869 870 870 872 874 874 875 875

Anhang ........................................................................................... 881


A Arbeiten mit der Entwicklungsumgebung ............................................... A.1 Der Debugger .............................................................................. A.1.1 Haltepunkte ................................................................... A.1.2 Schrittweise Abarbeitung ............................................... A.1.3 Komplexere Haltepunkte ................................................ A.2 Variablen berwachen ................................................................. A.3 Die Klassenansicht ....................................................................... A.3.1 Der Objektbrowser ........................................................ A.3.2 Die Aufrufhierarchie ....................................................... A.4 Der Klassendesigner .................................................................... A.4.1 Ein Klassendiagramm erstellen ....................................... 883 883 885 887 888 890 891 894 894 895 896

Index ............................................................................................................ 899

22

Vorwort

VSchon wieder eine neue Version von Visual C++, aber Entwicklung ist das beste Anzeichen einer lebendigen Sprache. Zugegeben, ein Groteil der Entwicklung fand im Bereich des .NET-Frameworks statt, welches nun in der Version 4 vorliegt. Aber auch C++ hat einen Schritt nach vorn getan, der hauptschlich in der Behebung von Fehlern der Vorgngerversion besteht. Im Vergleich zur Programmiersprache C#, die bei jeder neuen Version des Visual Studios mit zahlreichen Erweiterungen aufwartet, wird C++ scheinbar stiefmtterlich behandelt. Diese Betrachtung lsst allerdings die Entwicklungsgeschichte dieser beiden Sprachen auer Acht. Whrend C# speziell fr die .NET-Programmierung entworfen wurde und deshalb naturgem auch mit dem .NET-Framework wchst und erweitert wird, ist C++ eine Sprache, deren Haupteinsatzgebiet auerhalb von .NET liegt und deren Standard auch nicht von Microsoft vorgeschrieben wird. Es ist daher sinnvoll, die unter C++/CLI notwendigen Ergnzungen fr die .NET-Kompatibilitt so gering wie mglich zu halten und stattdessen dem C++-Programmierer seine gewohnten Hilfsmittel bereitzustellen, wie z. B. die STL/CLR, das .NET-Pendant der C++-eigenen STL, welche in der nun vorliegenden Version besser denn je einsetzbar ist.
Wozu C++?

Sie kennen wahrscheinlich das geflgelte Wort, dass man keiner Statistik trauen sollte, die man nicht selbst geflscht hat. Trotzdem mchte ich hier einige Zahlen ber die momentane Verbreitung (Stand November 2010) der Programmiersprachen nennen. Exemplarisch mchte ich hier den TIOBEIndex (zu finden unter www.tiobe.com) anbringen. Die prozentualen Werte anderer Rankings sind leicht abweichend, das globale Bild bleibt aber dasselbe. Demnach steht Java auf Platz 1 (18,5 %), Platz 2 belegt C (16,7 %), gefolgt von C++ (9,5 %) auf dem dritten Platz. C# findet sich mit 5,7 % auf Platz 5. Visual Basic .NET als dritte Programmiersprache des Visual Studios liegt mit 0,32 % weit abgeschlagen auf Rang 41. C und C++ gemeinsam betrachtet decken

23

Vorwort

mehr als ein Viertel der eingesetzten Programmiersprachen weltweit ab. Ein guter Grund zum Erlernen von C++. Oft entscheidet aber auch der Einsatzbereich. Whrend sich Java im Internet breitgemacht hat und auch in Handys und Bluray-Playern Verwendung findet, kommt C/C++ berwiegend dort zum Einsatz, wo es schnell gehen muss. Von Gertetreibern ber Signalverarbeitung in Echtzeit bis hin zu High-End 3D-Spielen. Auch sagt man, dass es einem C++-Programmierer leichter fllt, Java zu lernen als umgekehrt. Aber das mag auch nur ein Gercht sein.
Wozu C++/CLI?

Mit Erscheinen von Visual Studio 2010 und der darin enthaltenen Version von C++/CLI ist diese Frage klrungsbedrftiger denn je. Grundstzlich ist C++/CLI ein C++-Dialekt, der notwendige Ergnzungen fr die Kompatibilitt mit dem .NET-Framework enthlt. Manche behaupten, C++ und C++/CLI seien zwei vllig unterschiedliche Programmiersprachen, die nichts miteinander zu tun htten. Technisch gesehen ist das korrekt, da das resultierende Kompilat in unterschiedliche Sprachen fr unterschiedliche Laufzeitumgebungen bersetzt wird. Syntaktisch und semantisch sind diese Sprachen aber nahezu identisch, so dass es einem C++-Programmierer keine Mhe macht, C++/CLI zu erlernen. Aber die Frage bleibt: Wozu C++/CLI? Microsoft macht ganz klar, dass fr die Programmierung von .NET-Applikationen die hauseigene Sprache C# bevorzugt werden sollte, welch Wunder. Die Tatsache, dass Intellisense, ein den Programmierer untersttzender Mechanismus, in der Version 2010 nur noch bei nativem C++ und nicht mehr bei C++/CLI vorhanden ist, scheint diese Empfehlung noch zu untersttzen. Hintergrund ist allerdings ein Redesign des Intellisense-Systems. Und weil fr diese Version der Entwicklungsschwerpunkt auf anderen Dingen lag, gibt es Intellisense nur fr natives C++. Ob es in einem Service Pack oder in einer zuknftigen Version auch wieder fr C++/CLI bereitstehen wird, bleibt abzuwarten. Trotzdem kann in C++/CLI jedwede .NET-Applikation programmiert werden, auch wenn der Programmierer dabei nicht so tatkrftig von der Entwicklungsumgebung untersttzt wird, wie bei C# oder Visual Basic .NET. Dass eine .NET-Entwicklung auch fr Microsoft nicht vllig abwegig ist, zeigen die Ergnzungen in C++/CLI, die speziell darauf abzielen, dass sich auch C++-Programmierer unter .NET wohlfhlen, wie z. B. die STL/CLR. Unschlagbar ist C++/CLI auf jeden Fall als sogenannte Interop-Sprache, als Mittler zwi-

24

Vorwort

schen nativem Programmcode und dem verwalteten Programmcode von .NET. Ein typisches Anwendungsbeispiel wre das Kapseln einer in nativem C++ geschriebenen Klasse in eine .NET-Klasse, so dass sie z. B. in C# weiterverwendet werden kann. Um als Programmierer mglichst breit aufgestellt zu sein, bietet es sich daher an, zuerst C/C++ zu lernen, anschlieend die .NET-Welt mit C++/CLI zu erkunden, und sich bei exzessiver Entwicklung von .NET-Applikationen noch C# anzueignen, eine C++ sehr hnliche Sprache.
Der Aufbau des Buchs

Das Buch ist im Wesentlichen in drei Teile gegliedert: Der erste Teil erklrt natives C++ ohne .NET-Untersttzung. Er sollte von Programmieranfngern auf jeden Fall durchgearbeitet werden und dient erfahreneren C++-Programmierern als Nachschlagemglichkeit. Im zweiten Teil werden die Erweiterungen von C++/CLI besprochen. Erfahrende C++-Programmierer knnen hier bequem in die .NET-Programmierung einsteigen. Der dritte Teil schlielich beschftigt sich mit der Klassenbibliothek von .NET, allen voran der Oberflchenprogrammierung. Die Klassenbibliothek steht allen .NET-Programmiersprachen zur Verfgung. Die hier vorgestellten Klassen knnten Sie daher auch unter C# oder Visual Basic .NET mit der entsprechenden Syntax verwenden.
Die Beispiele in diesem Buch

Das Buch beinhaltet eine Reihe von Beispielen und praktischen Anwendungen, die Sie als Projekte von Visual C++ 2010 Express auf der DVD finden. Fr jedes Kapitel, das Beispiele enthlt, existiert eine Projektmappe, die wiederum die einzelnen Listings enthlt. Oft werden die Beispiele in kleinen Schritten erweitert, so dass es keinen Sinn macht, fr jeden Schritt ein eigenes Projekt anzulegen. Wenn also bei den Projekten vermeintliche Lcken vorhanden sind, dann beinhaltet das Folgeprojekt immer alle Ergnzungen und nderungen, die bis dahin gemacht wurden. Manche Projekte, die in sich geschlossenen sind, wie beispielsweise das Telefonbuch, finden Sie unter ihrem Namen in der Projektmappe.

25

TEIL I ANSI C++

Dieses Kapitel fhrt Sie in die Grundstruktur eines C++-Programms ein. Es zeigt die Ein- und Ausgabe und stellt die in C++ verfgbaren Datentypen und Operatoren vor.

1
1.1

Grundlagen von ANSI C++


Die Win32-Konsolenanwendung

Die ersten Schritte in C++ werden wir als Win32-Konsolenanwendung vornehmen. Dazu whlen Sie in der Entwicklungsumgebung ber das Men den Punkt Datei Neu Projekt. Sie erhalten das in Abbildung 1.1 dargestellte Fenster.

Abbildung 1.1

Neues Projekt anlegen

Je nach Edition des Visual Studios kann der Umfang der angebotenen Projektvorlagen variieren. Die in diesem Buch relevanten Projektgruppen sind CLR fr .NET-gesttzte Projekte und die nun thematisierte Projektart Win32. In dieser Gruppe finden Sie die Win32-Konsolenanwendung, die Sie jetzt whlen. Unter Name geben Sie den Namen des Projekts an, in unserem Beispiel wurde dort HelloWorld gewhlt. Der Punkt Ort bieten Ihnen die Mglichkeit, den Speicherort des Projekts zu bestimmten.

29

Grundlagen von ANSI C++

In Visual Studio werden Projekte in einer Projektmappe verwaltet. Soll fr diese Projektmappe physikalisch ein eigener Ordner angelegt werden, dann mssen Sie einen Haken vor Verzeichnis fr Lsung erstellen setzen und einen Projektmappennamen angegeben. Anschlieend ffnen Sie ber OK das Einstellungsfenster fr die Win32-Konsolenanwendung, zu sehen in Abbildung 1.2.

Abbildung 1.2 Die Anwendungseinstellungen fr das Win32-Projekt

Dort setzen Sie einen Haken vor Leeres Projekt. ber die Schaltflche Fertig stellen wird das Projekt angelegt und in der Entwicklungsumgebung angezeigt.

1.1.1

Eine C++-Datei hinzufgen

Um das erste Programm zu schreiben, bentigen wir noch eine entsprechende Quellcodedatei. Dazu klicken Sie im Projektmappen-Explorer mit rechts auf den Projektnamen und whlen den Punkt Hinzufgen Neues Element aus. Es erscheint das in Abbildung 1.3 dargestellte Fenster. Zur Einschrnkung der Auswahl sollten Sie in der linken Spalte auf Code klicken. Von den verbleibenden Dateitypen whlen Sie C++-Datei, geben dieser unter Name einen Namen (die fr diesen Dateityp verwendete Endung .cpp wird automatisch angehngt) und klicken auf Hinzufgen. In C++ gibt es keinerlei syntaktischen Bezug zwischen dem Namen einer Datei und ihrem Inhalt. Zur besseren bersicht sollten Sie aber mglichst

30

Die Win32-Konsolenanwendung

1.1

aussagekrftige Namen vergeben. Die C++-Datei mit der spteren Hauptfunktion knnen Sie beispielsweise immer main nennen.

Abbildung 1.3 Neues Element hinzufgen

Um an dieser Stelle den vollstndigen Entwicklungszyklus eines Programms zu besprechen, schreiben Sie bitte das kleine Programm aus Listing 1.1 in die C++-Datei. Abbildung 1.4 zeigt das Ergebnis.
int main() { }
Listing 1.1 Das erste Programm

Abbildung 1.4 Das erste Programm in der Entwicklungsumgebung

31

Grundlagen von ANSI C++

Der Stern hinter dem Dateinamen zeigt nderungen an, die noch nicht gespeichert wurden. Entweder knnen Sie ber die blichen Punkte Speichern oder Alle speichern die nderungen manuell sichern, oder Sie starten die Kompilation des Programms, vor der automatisch alle nderungen gespeichert werden.

1.1.2

Das Projekt kompilieren

C++-Projekte mssen kompiliert, also vom Compiler in Maschinensprache bersetzt werden.Unter dem Menpunkt Erstellen finden Sie die dafr notwendigen Funkionen. Die erste Gruppe von Menpunkten betrifft die gesamte Projektmappe, also alle in der Projektmappe enthaltenen Projekte. Die zweite Gruppe bezieht sich nur auf das aktuelle Startprojekt. Das Startprojekt ist im ProjektmappenExplorer daran zu erkennen, dass es fett geschrieben ist (um bei mehreren Projekten das Startprojekt zu wechseln, klicken Sie im Projektmappen-Explorer das gewnschte Projekt mit rechts an und whlen den Punkt Als Startprojekt festlegen). Fr beide Gruppen existieren folgende Mglichkeiten: Erstellen: Alle noch nicht kompilierten Bestandteile werden kompiliert, und ein ausfhrbares Programm wird erstellt. Neu erstellen: Alles wird neu kompiliert, egal, ob es bereits kompiliert war oder nicht, und dann wird ein ausfhrbares Programm daraus erstellt. Bereinigen: Alle fr das Projekt unwesentlichen Dateien werden gelscht (etwa die kompilierten Dateien des Projekts, die nicht wichtig sind, da sie immer wieder aus den Quellcodedateien erzeugt werden knnen). Dieser Punkt sollte ausgefhrt werden, bevor ein Projekt beispielsweise auf CD gebrannt wird, um unntigen Speicherverbrauch zu vermeiden. Fr den normalen Kompilationsvorgang reicht der Punkt Projektmappe erstellen, der auch bequem ber (F7) aufgerufen werden kann. Der Kompilationsvorgang umfasst je nach gewhltem Punkt die Kompilation aller oder nur der genderten Dateien, die Erstellung eines Manifests und die abschlieende Verknpfung aller erstellten Dateien des Projekts zu einem ausfhrbaren Programm. Die letzte Zeile im Ausgabefenster gibt eine kurze Zusammenfassung der Projektzustnde:

32

Die Win32-Konsolenanwendung

1.1

erfolgreich: Anzahl der erfolgreich kompilierten Projekte Fehler bei: Anzahl der Projekte mit Fehlern (aus einem fehlerhaften Pro-

jekt wird keine ausfhrbare Datei erzeugt; sollte das Projekt vorher einmal fehlerfrei kompiliert worden sein, dann entspricht die ausfhrbare Datei diesem Stand)
aktuell: Anzahl der Projekte, an denen nichts gendert wurde und die

deswegen auch nicht kompiliert werden mussten


bersprungen: Anzahl der Projekte, die von der Kompilation ausgenom-

men sind.

1.1.3

Das Programm starten

Unter dem Menpunkt Debuggen finden Sie Mglichkeiten des Programmstarts ber die Entwicklungsumgebung. Damit das Ausgabefenster nicht direkt nach Beendigung des Programms wieder geschlossen wird, sollten Sie das Programm ber den Punkt Starten ohne Debuggen oder (Strg) + (F5) starten. Bedenken Sie: Nur das als Startprojekt festgelegte Projekt wird ausgefhrt. Wie ein Programm im Debug-Modus gestartet wird, erfahren Sie in Abschnitt A.1, Der Debugger.

1.1.4

Die Fehlerkorrektur

Nicht selten stellt der Compiler whrend der Kompilation syntaktische Fehler fest. Zum Beispiel knnen Sie das Wort int einmal mit zwei n schreiben (innt) und dann das Programm nochmals kompilieren. Das Ausgabefenster (zu sehen in Abbildung 1.5) listet die gefundenen Fehler auf und merkt in der Statuszeile am Ende Fehler bei 1 an.

Abbildung 1.5 Der Fehler im Ausgabefenster

33

Grundlagen von ANSI C++

Eine strukturiertere Auflistung der gefundenen Fehler bietet die Fehlerliste, die Sie entweder im Men unter Ansicht Fehlerliste oder ber die Tastenkombination (Strg) + (^) und (Strg) + (E) ffnen knnen (Abbildung 1.6).

Abbildung 1.6 Die Fehlerliste

In dieser Fehlerliste knnen Sie sich die aufgetretenen Fehler, Warnungen und Meldungen ansehen. Um die Darstellung einer bestimmten Kategorie zu aktivieren/deaktivieren, klicken Sie auf den Namen der Kategorie. In Abbildung 1.6 wurde nur die Darstellung der Fehler aktiviert. Um einen Fehler schnell im Projekt zu finden, klicken Sie doppelt darauf. Der Editor ffnet automatisch die fehlerhafte Datei und markiert den Fehler mit einem kleinen Pfeil links vor der betreffenden Zeile. Zustzlich kann der Fehler noch durch eine rote Linie unter der betreffenden Zeile gekennzeichnet sein. Sollte die Fehlerliste mehrere Fehler beinhalten, dann sollten Sie immer zuerst den ersten Fehler beheben, da die weiteren Fehler hufig nur Folgefehler des ersten sind.

1.2

Die Hauptfunktion

So flexibel die Sprache C++ auch ist, etwas haben alle Programme gemeinsam: den durch die Hauptfunktion definierten Startpunkt des Programms. Die Hauptfunktion eines C++-Programms besitzt den Namen main und ist wie folgt aufgebaut:
int main() { }

34

Die Hauptfunktion

1.2

Jedes C++-Programm bentigt genau eine main-Funktion; sie wird beim Programmstart aufgerufen, und mit ihr wird das Programm beendet.
Gro- und Kleinschreibung In C++ muss penibel auf die Gro- und Kleinschreibung geachtet werden, weil diese ein Unterscheidungsmerkmal von Bezeichnern, also der im Programm gewhlten Namen fr Variablen, Objekte und Klassen, ist. Die Schreibweise von beispielsweise int und Int bezeichnet in C++ zwei unterschiedliche Dinge.

1.2.1

Der Funktionskopf

Die erste Zeile einer Funktion, in der die Parameter und der Rckgabetyp der Funktion definiert werden (int main()), wird als Funktionskopf bezeichnet. Wie eigene Funktionen definiert werden, behandelt Abschnitt 2.3. Hinter dem Funktionskopf steht der Anweisungsblock, gebildet von einem Paar geschweifter Klammern. Die Anweisungen dieses Anweisungsblocks werden beim Aufruf der Funktion ausgefhrt, im Falle der main-Funktion also beim Programmstart. C++ ist bei den Mglichkeiten der Quellcodeformatierung sehr flexibel. Das bisherige Beispiel wre auch wie in Listing 1.2 oder Listing 1.3 formatiert problemlos kompiliert worden:
int main(){}
Listing 1.2 Alles in einer Zeile

int main( ){ }
Listing 1.3 Wilde Formatierung

Allerdings ist eine berschaubare Formatierung ratsam. blicherweise schreibt man den Funktionskopf in eine eigene Zeile (mit der ffnenden geschweiften Klammer aus Platzgrnden dahinter) und rckt die Anweisungen innerhalb des Anweisungsblocks der Optik wegen etwas ein. Die schlieende geschweifte Klammer steht vertikal bndig mit der Zeile der dazugehrenden ffnenden Klammer.

35

Grundlagen von ANSI C++

1.3

Die Ausgabe

Um weitere Grundlagen von C++ vorzustellen, wird das vorige Programm um einige Anweisungen erweitert:
#include <iostream> using namespace std; int main() { cout << "Hello World" << endl; }
Listing 1.4 Eine Ausgabe in C++

Tipp Sollte das Ausgabefenster nach Beendigung des Programms sofort wieder geschlossen werden, dann bietet sich als letzte Anweisung des Programms
system("pause");

an. Dadurch wird das Windows-eigene pause-Kommando aufgerufen, das auf einen Tastendruck zum Fortfahren wartet. Soll das Fenster nur geffnet bleiben, wenn das Programm ber die Entwicklungsumgebung gestartet wird, dann bietet sich folgende Vorgehensweise an: 1. Klicken Sie im Projektmappen-Explorer mit rechts auf den Projektnamen, und whlen Sie Eigenschaften. 2. Whlen Sie dort in der linken Spalte den Punkt Konfigurationseigenschaften Linker System aus. 3. Setzen Sie rechts die Option Subsystem auf Konsole. 4. Schlieen Sie das Eigenschaftenfenster mit Ok.

Das hier zu betrachtende Beispiel besitzt im Anweisungsblock der main-Funktion nur eine einzige Anweisung:
cout << "Hello World" << endl;

Ein Hinweis kurz vorweg: In C++ wird jede Ausdrucksanweisung mit einem Semikolon abgeschlossen. Eine Ausdrucksanweisung ist eine Anweisung, die einen Ausdruck bildet. Abgesehen von den Operatoren mit einem oder drei Operanden ist die typische Form eines Ausdrucks Operand Operator Operand, wobei jeder Operand selbst wieder ein Ausdruck sein kann. Demnach ist 3+4 ebenso ein Ausdruck wie a-b, x=5 oder cout << "x".

36

Die Ausgabe

1.3

Fr konstante Zeichenfolgen gibt es auch eine syntaktische Regel: Konstante Zeichenfolgen stehen immer in doppelten Anfhrungszeichen.

1.3.1

cout

Der Befehl zur Ausgabe heit cout. Das ist die Abkrzung fr console out bzw. character out, beide Begriffe werden in gleichem Mae verwendet. Es handelt sich hierbei um ein Objekt mit der Fhigkeit, Zeichenketten und elementare Datentypen (darunter versteht man die in C++ enthaltenen Datentypen, die nicht aus anderen Elementen zusammengesetzt sind) auf dem Standard-Ausgabegert (im Normalfall der Konsole) auszugeben. Diese Ausgabe geschieht mithilfe des Operators <<, hinter dem das auszugebende Element steht. Ausgegeben wird in diesem Beispiel zunchst der Text Hello World.

1.3.2

endl

Hinter der Zeichenkette steht nochmals der Operator << und dahinter endl. Diese Schreibweise ist mglich, weil der <<-Operator verkettet werden kann. Alternativ htten wir auch zwei Anweisungen schreiben knnen:
cout << "Hello World"; cout << endl; endl beendet die Zeile, indem es ein New-Line-Zeichen (NL) ausgibt und

anschlieend einen Flush ausfhrt. Dazu muss man wissen, dass es sich bei der Ausgabe ber cout um eine gepufferte Ausgabe handelt. Die Ausgabe wird zuerst in einen Puffer geschrieben und anschlieend in einem Gang auf den Bildschirm gebracht. Es sind also Situationen denkbar, in denen sich ausgegebener Text im Puffer befindet, der noch nicht auf dem Bildschirm zu sehen ist. An dieser Stelle kann ein Flush, das erzwungene Ausgeben des Puffers auf die Konsole, sinnvoll sein. Der Ausgabepuffer wird aber bei Programmende und vor einer Eingabe automatisch geleert, von daher kommt ein isoliertes Flush nur selten zum Einsatz. Interessant an endl ist fr uns die Ausgabe des New Line. Zu Testzwecken knnen Sie einmal das endl mitsamt dem vorhergehenden <<-Operator entfernen, und Sie werden sehen, der vom Compiler ausgegebene Text Drcken Sie steht direkt hinter dem Hello World und nicht mehr in einer eigenen Zeile.

37

Grundlagen von ANSI C++

1.3.3

Escape-Sequenzen

Unter Escape-Sequenzen versteht man die Codierung besonderer Zeichen, die sonst in einer Zeichenkette nicht ausgegeben werden knnten. Nehmen wir als Beispiel das doppelte Anfhrungszeichen. Es wird in C++ verwendet, um Zeichenketten einzugrenzen. Schreiben wir ein Anfhrungszeichen in eine Zeichenkette, wird es nicht ausgegeben, sondern als Ende der Zeichenkette interpretiert. Mit einer Escape-Sequenz ist die Ausgabe eines Anfhrungszeichens kein Problem. Eine Escape-Sequenz beginnt mit dem Backslash \ und einem Zeichen, im Fall der doppelten Anfhrungszeichen dem ". Soll der Text Hello World auch bei der Ausgabe in Anfhrungszeichen stehen, schreiben wir das demnach so:
cout << "\"Hello World\"" << endl;

Die verfgbaren Escape-Sequenzen sehen Sie in Tabelle 1.1 aufgefhrt.


Escape Sequenz
\a \b \f \n \r

Zeichen
BEL (bell): Gibt ein akustisches Warnsignal. BS (backspace): Der Cursor geht eine Position nach links. FF (formfeed): Lst einen Seitenvorschub aus. NL (new line), der Cursor springt zum Anfang der nchsten Zeile. CR (carriage return): Der Cursor springt zum Anfang der aktuellen Zeile. HT (horizontal tab): Der Cursor springt zur nchsten horizontalen Tabulatorposition. VT (vertical tab): Der Cursor springt zur nchsten vertikalen Tabulatorposition.

\t

\v

\" \' \? \\

Das Zeichen " wird ausgegeben. Das Zeichen ' wird ausgegeben. Das Zeichen ? wird ausgegeben. Das Zeichen \ wird ausgegeben.

Tabelle 1.1 Die Escape-Sequenzen

1.3.4

Manipulatoren

Bei der Ausgabe und spter zum Teil auch bei der Eingabe existieren sogenannte Manipulatoren, die in den Ausgabestrom geschoben werden knnen und bestimme Vernderungen der Ausgabe bewirken.

38

Die Ausgabe

1.3

Manipulator
boolalpha dec fixed hex left

Beschreibung Gibt Boolesche Werte als Wrter (true und false) aus. Gibt ganzzahlige Werte dezimal aus. Stellt Fliekommawerte in der normalen Schreibweise dar. Gibt ganzzahlige Werte hexadezimal aus. Ordnet Ausgaben linksbndig an. Eventuelle Fllzeichen werden rechts angehngt. Macht boolalpha rckgngig. Macht showbase rckgngig. Macht showpoint rckgngig. Macht showpos rckgngig. Macht skipws rckgngig. Macht unitbuf rckgngig. Gibt ganzzahlige Werte oktal aus. Ordnet Ausgaben rechtsbndig an. Eventuelle Fllzeichen werden links angehngt. Stellt Fliekommawerte in der Exponentialschreibweise dar. Stellt bei Ausgaben im Oktal- oder Hexadezimalsystem den fr C++ typischen Prfix voran (0 fr oktal und 0x fr hexadezimal). Zeigt alle Ziffern nach dem Komma, auch wenn diese 0 sind. Setzt bei positiven Werten ein + davor. berliest bei der Eingabe Whitespaces. Puffert die Ausgabe nicht, sondern gibt sie sofort aus.

noboolalpha noshowbase noshowpoint noshowpos noskipws nounitbuf oct right

scientific showbase

showpoint showpos skipws unitbuf

Tabelle 1.2

Die Standard-Manipulatoren

Im folgenden Beispiel wird mit boolalpha die Ausgabe von false anstelle von 0 bewirkt:
bool b=false; cout << boolalpha << b << endl;

Zustzlich sind in der Header-Datei iomanip noch zwei interessante Manipulatoren definiert:
setfill(charT c) setzt das Fllzeichen auf das Zeichen c. setprecision(int p) setzt die Ausgabe bei Fliekommawerten auf insge-

samt b Ziffern, den Dezimalpunkt nicht mitgezhlt. setprecision definiert

39

Grundlagen von ANSI C++

nicht die Anzahl der Nachkommastellen, sondern wirklich die Anzahl der gesamten Ziffern, sowohl vor als auch nach dem Komma.
setw(int b) definiert die minimale Breite einer Werteausgabe auf b Zei-

chen. Nicht bentigte Stellen werden mit einem Fllzeichen (standardmig das Leerzeichen, kann aber mit setfill gendert werden) gefllt. Sollte die Ausgabe mehr Zeichen bentigen, als mit setw definiert, dann wird die Ausgabe trotzdem vollstndig durchgefhrt.

1.4

Die include-Direktive

Nachdem die Inhalte der Hauptfunktion nun ausreichend erklrt sind, fehlen noch die beiden Anweisungen vor der main-Funktion. Zum Ersten ist da die Include-Direktive:
#include<iostream>

Bei diesem Befehl handelt es sich um eine sogenannte Prprozessordirektive. Der Prprozessor durchluft eine Quellcodedatei vor der Kompilation und sucht nach an ihn gerichteten Befehlen. Das Ergebnis seiner Textbearbeitung wird dann dem Compiler zur Kompilation vorgesetzt. Zu erkennen ist eine Prprozessordirektive an dem einleitenden #. Die Direktive in unserem Fall heit include und ersetzt die Direktive temporr fr die Kompilation mit der dahinter angegebenen Datei. Die spitzen Klammern besagen, dass die angegebene Datei an von der Entwicklungsumgebung vorgegebenen Orten gesucht wird. Auf diese Weise werden im Normalfall die HeaderDateien der Standardbibliothek eingebunden. Wie Sie eigene Header-Dateien einbinden knnen, lesen Sie in Abschnitt 2.4, Module. In unserem Programm wird die Header-Datei iostream eingebunden, welche die Definition der Input- und Outputstreams, unter anderem auch cout und endl, beinhaltet.

1.5

using

Die zweite noch zu besprechende Anweisung ist die using namespace-Anweisung. Ihre Syntax lautet:
using namespace Namensbereich;

40

Kommentare

1.6

Elemente der C++-Standardbibliothek und spter auch des .NET-Frameworks sind in sogenannte Namensbereiche gruppiert, die einen eigenen Bezugsrahmen bilden und damit Doppeldeutigkeiten von Bezeichnern vermeiden. Zwei Elemente mit demselben Namen drfen in unterschiedlichen Namensbereichen stehen, weil sie jeweils in ihrem Namensbereich eindeutig sind und nach auen hin ber den sie umgebenden Namensbereich angesprochen werden mssen. Die Elemente der C++-Standardbibliothek stehen allesamt im Namensbereich std. Die using namespace-Anweisung sagt dem Compiler, in welchen Namensbereichen er automatisch suchen soll, wenn ein Bezeichner im aktuellen Bezugsrahmen nicht gefunden wird. Entfernen Sie die using namespace-Anweisung aus dem Programm, hagelt es Fehlermeldungen, weil die im Namensbereich std befindlichen Elemente cout und endl nicht mehr gefunden werden.

1.5.1

Bezugsrahmenoperator

Um Elemente eines Namensbereichs anzusprechen, wird der Bezugsrahmenoperator :: verwendet:


#include <iostream> int main() { std::cout << "Hello World" << std::endl; }
Listing 1.5 Die Verwendung des Bezugsrahmenoperators

Die Anweisung std::cout heit demnach: im Namensbereich std das Element cout. Weil diese vollstndige Qualifizierung (d. h., die Nennung des Bezugsrahmens bei der Namensnennung, std::cout; im Gegensatz zu einem unqualifizierten Namen, cout) bei regelmigem Gebrauch eines Elements lstig wird, gibt es die using namespace-Anweisung, die Abhilfe schafft. Wie Sie eigene Namensbereiche definieren knnen, behandelt Abschnitt 5.1.

1.6

Kommentare

Auch wenn die meisten Programmierer ihre Wichtigkeit ungerne zugeben und sich hufig mit Hnden und Fen dagegen wehren, sie zu schreiben, sollten Kommentare ein elementarer Bestandteil eines jeden Programms sein. Wenn sie auch keinerlei Aussagekraft fr den Compiler besitzen, sind

41

Grundlagen von ANSI C++

sie eine wertvolle Informationsquelle fr jeden, der den Versuch unternimmt, das Programm zu verstehen. Natrlich immer unter der Voraussetzung, dass der Verfasser der Kommentare ernsthafte Inhalte geschrieben hat.

1.6.1

Einzeilige Kommentare

Die einfachste Form von Kommentar ist der einzeilige Kommentar, der mit // beginnt und mit dem Ende der Zeile endet:
cout << "Hello World"; // Textausgabe cout << endl;
Listing 1.6 Ein einzeiliger Kommentar

Die zweite Zeile gehrt nicht mehr zum Kommentar, weil er sich nur bis zum Ende der ersten Zeile erstreckt. Soll der Kommentar aus mehreren Zeilen bestehen, dann bleibt mit dieser Variante keine andere Mglichkeit, als jede Zeile mit // beginnen zu lassen.

1.6.2

Mehrzeilige Kommentare

Glcklicherweise sind in C++ auch mehrzeilige Kommentare mglich. Sie beginnen mit /* und enden mit */:
/* Ein mehrzeiliger Kommentar */
Listing 1.7 Ein mehrzeiliger Kommentar

Der Kommentartext muss nicht unbedingt in derselben Zeile wie /* beginnen, theoretisch kann direkt vor und hinter dem mehrzeiligen Kommentar sogar noch Code stehen, auch wenn das nicht gerade die Lesbarkeit erhht:
cout << "Hello World"; /* Ein mehrzeiliger Kommentar */ cout << endl;

1.6.3

Kommentare verschachteln

Es ist in bestimmten Grenzen mglich, Kommentare zu verschachteln, so z. B. einzeilige Kommentare:


//cout << endl; // Neue Zeile

42

Variablen

1.7

Oder einzeilige Kommentare innerhalb eines mehrzeiligen Kommentars:


/* cout << "Hello World"; // Textausgabe cout << endl; // Neue Zeile */

Nicht untersttzt wird jedoch die Verschachtelung von mehrzeiligen Kommentaren.

1.6.4

Kommentare in der Entwicklungsumgebung

Das Markieren von Kommentaren wird in Visual C++ ber zwei Schaltflchen untersttzt, wie Abbildung 1.7 zeigt.

Kommentar entfernen Kommentar hinzufgen


Abbildung 1.7 Die Schaltflchen fr Kommentare

Die Entwicklungsumgebung verwendet ausschlielich einzeilige Kommentare.

1.7

Variablen

Unter einer Variablen versteht man in einer Hochsprache die Bereitstellung von Speicherplatz, um den Wert eines bestimmten Datentyps speichern zu knnen. Eingesetzt werden Variablen immer dann, wenn das Programm Daten manipulieren soll. Ob das Programm diese Daten beispielsweise ber eine Tastatureingabe, das Einlesen einer Datei oder eine Datenbankabfrage ermittelt spielt dabei keine Rolle. Zum Zeitpunkt der Datenverarbeitung mssen die Daten im Arbeitsspeicher des Computers vorliegen, und zwar blicherweise in Form von Variablen oder Objekten. Syntaktisch wird eine Variable in C++ wie folgt definiert:

43

Grundlagen von ANSI C++

Datentyp Variablenname [= Initialisierungswert] ;

Die Teile in den eckigen Klammern sind optional, die Angabe eines Initialisierungswerts ist bei der Definition also nicht zwingend. Welche Datentypen in C++ zur Verfgung stehen, behandelt Abschnitt 1.8. Im Folgenden werden die int-Variablen x und y definiert und x zustzlich noch mit dem Wert 42 initialisiert:
int x=42; int y;

Die elementaren Datentypen knnen problemlos mit cout ausgegeben werden:


cout << x << endl; cout << y << endl;

Allerdings besitzen uninitialisierte Variablen (in diesem Fall y) keinen vorhersagbaren Wert. Das ist grundstzlich kein Problem, aber meist ungewollt und damit ein Sicherheitsrisiko. Aus diesem Grund wird im Debug-Modus ein Fehler zur Laufzeit gemeldet. Abbildung 1.8 zeigt ihn.

Abbildung 1.8 Fehlermeldung der Laufzeitumgebung

In der Release-Konfiguration wird der zufllige Wert allerdings fehlerfrei ausgegeben. Abbildung 1.9 zeigt, wie die Projektkonfiguration zwischen Debug und Release gewechselt werden kann.

Projektkonfiguration wechseln
Abbildung 1.9 Wechseln der Projektkonfiguration

44

Variablen

1.7

1.7.1

Bezeichner

Immer wenn in C++ etwas angelegt wird, sei es eine Variable, sei es ein Objekt, eine Funktion, eine Klasse etc., dann wird ein Bezeichner bentigt. In C++ wird ein Bezeichner nach dem folgenden Regeln gebildet: Das erste Zeichen eines Bezeichners muss ein umgebungsspezifischer Buchstabe oder ein Unterstrich sein. Jedes weitere Zeichen darf ein umgebungsspezifischer Buchstabe, ein Unterstrich oder eine Zahl sein. Reservierte Wrter der Sprache drfen nicht als Bezeichner verwendet werden. Es wird zwischen Gro- und Kleinschreibung unterschieden. Tabelle 1.3 bietet eine bersicht ber die reservierten Wrter in C++.
alignof bitand catch compl decltype dynamic_cast extern goto mutable nullptr protected short static_cast thread_local typeid virtual xor and bitor char const default else false if namespace operator public signed struct throw typename void xor_eq and_eq bool char16_t const_cast delete enum float inline new or register sizeof switch true union volatile asm break char32_t constexpr do explicit for int not or_eq reinterpret_cast static template try unsigned wchar_t auto case class continue double export friend long not_eq private return static_assert this typedef using while

Tabelle 1.3 Reservierte Wrter in C++

45

Grundlagen von ANSI C++

1.7.2

Eingabe

Analog zur Ausgabe mit cout existiert das Objekt cin, welches die Mglichkeit der Eingabe von Werten ber die Konsole bietet. cin steht fr console in oder character in. Die Eingabe ber die Konsole erfolgt so:
cin >> variablenbezeichner;

Listing 1.8 zeigt ein Beispiel fr cin. Dort wird ein int-Wert eingelesen und anschlieend wieder ausgegeben:
#include <iostream> using namespace std; int main() { cout << "Bitte geben Sie einen ganzzahligen Wert ein:"; int x; cin >> x; cout << "Der Wert lautet " << x << endl; }
Listing 1.8 Die Eingabe mit cin

Genau wie der Ausgabe-Operator << kann auch der Eingabe-Operator >> verkettet werden:
#include <iostream> using namespace std; int main() { cout << "Bitte geben Sie zwei Ganzzahlen ein:"; int x,y; cin >> x >> y; cout << "Sie haben "<<x<<" und "<<y<<" eingegeben." << endl; }
Listing 1.9 Verketteter Eingabe-Operator

Die konkreten Eingaben trennen Sie entweder ber die ()-Taste oder mit einem Leerzeichen.

46

Variablen

1.7

1.7.3

Literale

Unter Literalen versteht man konstante Werte, die direkt im Programmcode stehen. In der Anweisung
int a = 42;

ist 42 ein Literal. Literale werden oft auch als Konstanten bezeichnet. Um aber eine begriffliche Unterscheidung zu konstanten Objekten oder Elementen zu ermglichen, wird im C++-Standard der Begriff Literal verwendet. In einer typisierten Programmiersprache wie C++ besitzen auch Literale einen Typ. Dieser ist fr ganze Zahlen int, fr Fliekommazahlen double und fr Zeichen char. Um Literale anderer Datentypen zu erzeugen, werden Suffixe verwendet. Diese werden hinter das Literal gesetzt. Dabei spielt die Gro- und Kleinschreibung (z. B. ll, Ll, lL oder LL) sowie die Reihenfolge (ull oder llu) keine Rolle.
Ganzzahlige Literale

Tabelle 1.4 zeigt die Datentypen fr ganzzahlige Literale an.


Suffix
l oder L ll oder LL u oder U

Datentyp
Long int Long long int Unsigned

Tabelle 1.4 Die Suffixe fr ganzzahlige Literale

int i=20; unsigned int ui = 20u; long l = 20l; unsigned long ul = 20ul; long long ll = 20ll; unsigned long long ull = 20ull;
Listing 1.10 Beispiele fr ganzzahlige Suffixe

Ganzzahlige Literale knnen oktal, dezimal oder hexadezimal angegeben werden. Tabelle 1.5 zeigt die Syntax und Listing 1.11 einige Beispiele dazu.

47

Grundlagen von ANSI C++

Erste Zeichen
0 0x oder 0X

Zahlensystem oktal hexadezimal dezimal

alles andere

Tabelle 1.5 Zahlensysteme fr ganze Zahlen

int i; i = 42; // dezimal 42 i = 052; // oktal 42 i= 0x2a; // hexadezimal 42


Listing 1.11 Literale in unterschiedlichen Zahlensystemen

Fliekommaliterale

Bei Fliekommavariablen stehen die in Tabelle 1.6 aufgefhrten Suffixe zur Verfgung.
Suffix
f oder F l oder L

Datentyp
float Long double

Tabelle 1.6

Suffixe fr Fliekommavariablen

Hinweis In C++ wird als Dezimaltrennzeichen der Punkt verwendet, nicht wie in Deutschland blich das Komma.

Darber hinaus erlauben Fliekommaliterale auch die Exponentialschreibweise. Listing 1.12 zeigt zwei Beispiele:
double d; d = 314e-2; // 3.14 d = 42e3; // 42000
Listing 1.12 Exponentialschreibweise bei Fliekommaliteralen

1.7.4

Konstanten

Konstanten sind im Vergleich zu den Literalen mit ihrer direkten Wertangabe (z. B. 3,14) Bezeichner mit einem konstanten Wert. Diese Konstanz wird

48

Datentypen

1.8

mit dem Schlsselwort const eingeleitet. Die Definition einer Konstanten erfolgt ber:
const Datentyp Variablenname = Initialisierungswert;

Das folgende Beispiel berechnet anhand eines fest vorgegebenen Mehrwertsteuersatzes den Bruttowert aus dem Nettowert:
#include<iostream> using namespace std; const float MWST=1.19f; int main() { cout << "Bitte Netto-Wert eingeben:"; float wert; cin >> wert; cout << "Brutto: " << wert*MWST << endl; system("pause"); }
Listing 1.13 Brutto-Berechnung

Mit einer Konstanten mssen Sie den tatschlichen Wert nur ein einziges Mal im Programm angeben. Dadurch lsst sich dieser schnell und ohne Fehlerpotenzial verndern. Manche fordern, abgesehen vom Wert 0 alle anderen Werte nur ber Konstanten im Programm zu verwenden. Das mag in bestimmten Anwendungsfllen durchaus sinnvoll sein, aber wie fast berall knnen allzu starre Dogmen auch ins Gegenteil umschlagen.

1.8

Datentypen

blicherweise dienen Programme der Manipulation oder Verarbeitung von Daten. Diese Daten, von wo auch immer sie stammen, mssen sich zum Zeitpunkt der Verarbeitung im Arbeitsspeicher des Computers befinden. C++ ist eine typisierte Programmiersprache, deswegen haben alle Daten eines C++-Programms auch einen Datentyp.

49

Grundlagen von ANSI C++

1.8.1

Elementare Datentypen

Die elementaren Datentypen von C++ sind in Tabelle 1.7 aufgefhrt.


Name
bool char wchar_t short int int long int long short

Abkrzung

Bytes 1 1 2 2 4 4 8 4 8 8

Wertebereich
true, false

128 bis 127 0 bis 65.535 32.768 bis 32.767 2.147.483.648 bis 2.147.483.647 2.147.483.648 bis 2.147.483.647 9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807 3.4E +/ bis 38 (sieben Stellen) 1.7E +/ bis 308 (15 Stellen) 1.7E +/ bis 308 (15 Stellen)

long long int long long

float double long double

Tabelle 1.7 Die elementaren Datentypen von C++

Der Datentyp bool kann nur die beiden Werte true oder false speichern, die Schlsselwrter der Sprache sind. Obwohl in Tabelle 1.7 bei allen Datentypen die Gre in Bytes und deren Wertebereich angegeben ist, gilt diese nur fr Visual C++. Der C++-Standard selbst definiert keine festen Gren. Vielmehr richten sich diese nach der Plattform des Compilers. Der Datentyp char ist dabei so gro, dass alle Zeichen der Compiler-Implementierung unterschieden werden knnen. Um alle auf der Plattform verwendeten Zeichen darstellen zu knnen, existiert wchar_t. Es knnte beispielsweise ein englischer Compiler auf einem japanischen Betriebssystem verwendet werden. Die Gren der ganzzahligen Datentypen char, short int, int, long int und long long int stehen lediglich in Relation zueinander: char short int long long long

Dabei sollte der Datentyp int die auf dem System bliche Gre besitzen (auf einem 32Bit-System also 4 Bytes). Es hngt von der Implementierung des Compilers ab, ob der Datentyp char vorzeichenbehaftet (signed) oder nicht vorzeichenbehaftet (unsigned) ist. Die

50

Datentypen

1.8

Datentypen short int, int, long int und long long int sind implizit vorzeichenbehaftet, das Schlsselwort signed kann aber explizit davor gesetzt werden (z. B. signed int). Um vorzeichenlose Datentypen zu erhalten, muss unsigned davor gesetzt werden, z. B. unsigned long long int. Die Wertebereiche der vorzeichenlosen Datentypen verdoppeln sich im positiven Bereich. Die Fliekommatypen float, double und long double existieren nur vorzeichenbehaftet. Auch hier ist die Gre im C++-Standard nur relativ definiert: float double long double

Um den tatschlichen Wertebereich eines elementaren Datentyps zu ermitteln, existieren zwei Mglichkeiten. Zum einen knnen Sie auf die in den Headerdateien climits und cfloat definierten Konstanten zurckgreifen, die in Tabelle 1.8 und Tabelle 1.9 aufgefhrt sind. Das Beispiel in Listing 1.14 gibt den grtmglichen double-Wert aus:
#include <iostream> #include <cfloat> using namespace std; int main() { cout << DBL_MAX << endl; }
Listing 1.14 Ausgabe des grten double-Werts

Konstante
CHAR_BIT CHAR_MAX CHAR_MIN INT_MAX INT_MIN LLONG_MAX LLONG_MIN LONG_MAX LONG_MIN

Bedeutung Anzahl der Bits eines char grter Wert eines char kleinster Wert eines char grter Wert eines int kleinster Wert eines int grter Wert eines long long int kleinster Wert eines long long int grter Wert eines long int kleinster Wert eines long int

Tabelle 1.8 Die Konstanten von climits

51

Grundlagen von ANSI C++

Konstante
SCHAR_MAX SCHAR_MIN SHRT_MAX SHRT_MIN UCHAR_MAX UINT_MAX ULLONG_MAX ULONG_MAX USHRT_MAX

Bedeutung grter Wert eines signed char kleinster Wert eines signed char grter Wert eines short int kleinster Wert eines short int grter Wert eines unsigned char grter Wert eines unsigned int grter Wert eines unsigned long long int grter Wert eines unsigned long int grter Wert eines unsigned short int

Tabelle 1.8 Die Konstanten von climits (Forts.)

Konstante
DBL_DIG DBL_EPSILON DBL_MANT_DIG DBL_MAX DBL_MAX_10_EXP DBL_MAX_EXP DBL_MIN DBL_MIN_10_EXP DBL_MIN_EXP FLT_DIG FLT_ FLT_MANT_DIG FLT_MAX FLT_MAX_10_EXP FLT_MAX_EXP FLT_MIN FLT_MIN_10_EXP FLT_MIN_EXP LDBL_DIG

Bedeutung Anzahl der Dezimalstellen von double kleinster double-Wert ungleich 0 Bitgre der double-Mantisse grter Wert eines double grter Exponent zur Basis 10 eines double grter Exponent zur Basis 2 eines double kleinster positiver Wert eines double kleinster Exponent zur Basis 10 eines double kleinster Exponent zur Basis 2 eines double Anzahl der Dezimalstellen von float kleinster float-Wert ungleich 0 Bitgre der float-Mantisse grter Wert eines float grter Exponent zur Basis 10 eines float grter Exponent zur Basis 2 eines float kleinster positiver Wert eines float kleinster Exponent zur Basis 10 eines float kleinster Exponent zur Basis 2 eines float Anzahl der Dezimalstellen von long double

Tabelle 1.9 Die Konstanten von cfloat

52

Datentypen

1.8

Konstante
LDBL_EPSILON LDBL_MANT_DIG LDBL_MAX LDBL_MAX_10_EXP LDBL_MAX_EXP LDBL_MIN LDBL_MIN_10_EXP LDBL_MIN_EXP

Bedeutung kleinster long double-Wert ungleich 0 Bitgre der long double-Mantisse grter Wert eines long double grter Exponent zur Basis 10 eines long double grter Exponent zur Basis 2 eines long double kleinster positiver Wert eines long double kleinster Exponent zur Basis 10 eines long double kleinster Exponent zur Basis 2 eines long double

Tabelle 1.9 Die Konstanten von cfloat (Forts.)

Die andere Mglichkeit ist der Einsatz des sizeof-Operators, der die Gre eines Objekts oder Datentyps in Bytes ermittelt. Das Beispiel in Listing 1.15 gibt die Gre der Variablen x und des Datentyps long long int in Bytes aus:
#include <iostream> using namespace std; int main() { int x; cout << sizeof(x) << endl; cout << sizeof(long long) << endl; }
Listing 1.15 Einsatz des sizeof-Operators

1.8.2

Zusammengesetzte Datentypen

Zu den zusammengesetzten Datentypen gehren folgende Typen, die in spteren Kapiteln detailliert beschrieben werden: Zeiger Referenzen Aufzhlungen Unions Strukturen Klassen

53

Grundlagen von ANSI C++

1.9

Operatoren

Operatoren dienen dazu, Ausdrcke zu formulieren. Das Ergebnis des Ausdrucks hngt dabei von den Operanden und dem verwendeten Operator ab. C++ kennt Operatoren mit einem, zwei und sogar drei Operanden. Im folgenden Beispiel werden zwei Variablen x und y definiert und x mit dem Wert 5 initialisiert. Anschlieend wird der Ausdruck y=3*x gebildet. Wegen der unterschiedlichen Bindungsstrke (Abschnitt 1.9.9) der Multiplikation und der Zuweisung wird zunchst der Ausdruck 3*x berechnet, und danach dann die Zuweisung an y vorgenommen.
int x=5; int y; y=3*x;

1.9.1

Zuweisungsoperatoren

Zuweisungsoperatoren sind die mit Abstand am meisten eingesetzten Operatoren. Denn sieht man einmal von den Inkrement- und Dekrementoperatoren ab, sind sie die einzigen, die eine Wertnderung einer Variablen oder eine Zustandsnderung eines Objekts bewirken. Die einfachste Form des Zuweisungsoperators sieht so aus:
x=y;

Die Abarbeitung erfolgt von rechts nach links, der Wert von y wird x zugewiesen. Wegen dieser Abarbeitungsreihenfolge wrde ein Ausdruck auf der rechten Seite vor der Zuweisung abgearbeitet. x=y+5 ist gleichbedeutend mit x=(y+5). Eine Sonderform des Zuweisungsoperators sind die sogenannten kombinierten Zuweisungsoperatoren, die eine Zuweisung mit einer arithmetischen Verknpfung kombinieren, im folgenden Fall die Zuweisung mit der Addition:
x+=y;

Diese kombinierten Zuweisungsoperatoren haben zwei wesentliche Vorteile. Zum einen verkrzen sie den Quellcode. Diese Einsparung wird umso deutlicher, je ausfhrlicher die Variablenbezeichnung vorgenommen wurde. Zum Beispiel ist
MaximaleBetriebsdauer=MaximaleBetriebsdauer+1;

weitaus aufwndiger zu schreiben als

54

Operatoren

1.9

MaximaleBetriebsdauer+=1;

Des Weiteren sind kombinierte Zuweisungsoperatoren performanter, weil beispielsweise im obigen Fall der Wert 1 direkt auf die bestehende Variable addiert wird. Bei dem normalen Zuweisungsoperator muss zuerst eine Summe (MaximaleBetriebsdauer+1) als eigenstndiges temporres Objekt gebildet werden, bevor die Variable mit dem neuen Wert berschrieben werden kann.
Tipp Kombinierte Zuweisungsoperatoren sind performanter und krzer zu schreiben als eine von der Zuweisung getrennte Operation.

C++ bietet folgende kombinierte Zuweisungsoperatoren:


Operator
+= -= *= /= %=

Beschreibung
a+=b weist a die Summe a+b zu. a-=b weist a die Differenz a-b zu. a*=b weist a das Produkt a*b zu. a/=b weist a den Quotienten a/b zu. a%=b weist a den Rest der Division von a/b zu. Ist nur auf ganzzahlige Datentypen anwendbar. a<<=b weist a das Ergehnis von a<<b zu. a>>=b weist a das Ergebnis von a>>=b zu. a&=b weist a das Ergebnis von a&b zu. a|=b weist a das Ergebnis von a|=b zu. a~=b weist a das Ergebnis von a~b zu.

<<= >>= &= |= ~=

Tabelle 1.10

Die kombinierten Zuweisungsoperatoren

Zu beachten ist, dass durch den kombinierten Zuweisungsoperator die Prioritt der verknpften arithmetischen Operation berdeckt wird. Whrend
x=x*3+5;

wegen der hheren Prioritt von * zuerst den Wert von x mit 3 multipliziert und dann die 5 hinzuaddiert wird, bildet
x*=3+5;

zuerst die Summe 3+5 und verwendet den resultierenden Wert 8 dann als Faktor mit x.

55

Grundlagen von ANSI C++

Im Zusammenhang mit Sprachelementen, die nderungen an Variablen oder Objekten vornehmen (hier die Zuweisungsoperatoren), spricht man auch von L-Werten und R-Werten (engl. lvalue und rvalue). Ein L-Wert ist eine Variable oder ein nicht konstantes Objekt, welches sowohl auf der linken als auch auf der rechten Seite eines Zuweisungsoperators stehen kann. Ein RWert ist nicht vernderbar (Konstante, Literal oder konstantes Objekt) und kann nur auf der rechten Seite eines Zuweisungsoperators stehen.

1.9.2

Arithmetische Operatoren

Die Gruppe der arithmetischen Operatoren umfasst primr die typischen Grundrechenarten Addition, Subtraktion, Multiplikation, Division und Restwertbildung (Modulo).
Operator
* / %

Beschreibung
a*b bildet das Produkt a*b. a/b bildet den Quotienten a/b. a%b bildet bei ganzzahligen Datentypen den Rest der Division von a/b.

Die binre Variante (a+b) bildet die Summe a+b. Die unre Schreibweise (+5) dient als Vorzeichen. Die binre Variante (a-b) bildet die Differenz a-b. Die unre Schreibweise (-5) dient als Vorzeichen.
Die arithmetischen Operatoren

Tabelle 1.11

Achtung Die unren Operatoren + und auf Variablen angewandt verhalten sich nicht wie bei den Konstanten. Der Ausdruck +x bildet nicht den positiven Wert von x, sondern lsst ihn wie bei einer Multiplikation mit +1 unverndert. Der Ausdruck -x ndert immer das Vorzeichen des Wertes von x, wie bei einer Multiplikation mit 1. Der in x gespeicherte Wert bleibt dabei natrlich unverndert, solange keine Zuweisung stattfindet.

Ein kleines Beispiel:


#include<iostream> using namespace std; int main() {

56

Operatoren

1.9

cout << "Bitte Zahl 1 eingeben:"; int x; cin >> x; cout << "Bitte Zahl 2 eingeben:"; int y; cin >> y; cout << "Die Summe ist: " << x+y << endl; cout << "Die negierte Summe ist: " << -(x+y) << endl; }
Listing 1.16 Der Einsatz des +-Operators

1.9.3

Inkrement und Dekrement

Die Inkrementoperator ++ und der Dekrementoperator kann als rein arithmetischer Operator und in der sogenannten Zeigerarithmetik eingesetzt werden. In der Funktion als arithmetische Operatoren erhhen bzw. vermindern sie den Wert einer Variablen um 1. Dabei wird zwischen der Prfix- und der Postfix-Schreibweise unterschieden. In der Prfix-Schreibweise steht der Ausdruck fr den neuen Wert der Variablen. Der Ausdruck
++x;

erhht den Wert von x um 1, und der Ausdruck steht fr den neuen, bereits erhhten Wert von x. Deshalb ist bei
int x=42; int y = ++x;

der Wert von x und y jeweils 43. Anders verhlt es sich bei der PostfixSchreibweise. Der Ausdruck
x++;

erhht x zwar auch um 1, steht aber fr den alten, noch nicht erhhten Wert von x. Demnach ergibt
int x=42; int y = x++;

fr x den Wert 43 (x wird durch das Inkrement um 1 erhht), fr y aber den Wert 42 (Der Postinkrement-Ausdruck steht fr den alten Wert von x). Das gleiche Verhalten zeigt der Dekrementoperator.

57

Grundlagen von ANSI C++

Hinweis Der Unterschied zwischen der Prfix- und Postfix-Schreibweise zeigt sich nur, wenn das Ergebnis des Ausdrucks in eine weitere Verarbeitung einfliet (Zuweisung oder Bestandteil eines komplexeren Ausdrucks). Bei einer isolierten Anwendung von Inkrement oder Dekrement gibt es zwischen Prfix- und Postfixschreibweise keinen Unterschied.
++x;

und
x++;

erhhen beide ohne Unterschied den Wert von x um 1.

Zeigerarithmetik

Im Zuge der Zeigerarithmetik erhhen und vermindern die Inkrement- und Dekrementoperatoren den Wert des Zeigers nicht um 1, sondern addieren bzw. subtrahieren die Gre des Datentyps. Dadurch zeigt bei einem Inkrement der Zeiger auf das nchste Element, bei einem Dekrement auf das Element davor. Detailliertere Informationen finden Sie in Abschnitt 3.4.7, Zeigerarithmetik.
Tipp Da bei der Postfix-Schreibweise der alte Wert der Variablen temporr gespeichert werden muss, um nach den Inkrement- oder Dekrementoperatoren noch zur Verfgung zu stehen, sind die Prfix-Schreibweisen im Normalfall performanter als die Postfix-Schreibweisen. Wenn Sie die Wahl haben, sollten Sie daher immer auf die Prfix-Schreibweise setzen.

1.9.4

Bitweise Operatoren

Die bitweisen Operatoren verknpfen ihre Operanden auf Bitebene. Es heit ja immer, der Computer versteht nur 0 und 1, und das ist auch wahr. Diese Einheit, die entweder nur 0 oder 1 sein kann, heit Bit. Ein Bit kann demnach zwei verschiedene Werte annehmen, 0 und 1. Nehmen wir zwei Bits, dann sind bereits vier verschiedene Kombinationen mglich (00, 01, 10 und 11). Wenn wir das Bit links mit 2 multiplizieren, dann entstehen aus diesen vier Kombinationen die Werte von 03 (0*2+0=0, 0*2+1=1, 1*2+0=2 und 1*2+1=3). Kommt noch ein drittes Bit hinzu und wird dieses mit 4 multipliziert, dann haben wir bereits die Zahlen von 07. Das vierte Bit mit dem Faktor 8 versehen liefert uns die Mglichkeit, die Zahlen von 015 darzustellen.

58

Operatoren

1.9

Betrachten wir die Faktoren der Bits genauer, dann stellen wir fest, dass es sich um Zweierpotenzen handelt, 2=21, 4=22 und 8=23. Zur Verallgemeinerung multiplizieren wir das rechte Bit mit 1, das ist 20. Diese Kombination von vier Bit wird Nibble genannt. Abbildung 1.10 zeigt den Zusammenhang.
23 22 21 20

Abbildung 1.10 Bitweise Darstellung eines Nibbles

Ein Nibble immer mit vier binren Ziffern zu schreiben, z. B. 12 als 1100, ist aufwndig. Deswegen wird an dieser Stelle oft das Hexadezimalsystem verwendet, welches die Basis 16 besitzt und daher optimal zu einem Nibble passt, welches ja auch genau 16 Kombinationen abdeckt (die Werte von 0 15). Dabei werden im Hexadezimalsystem die Werte von 09 auch mit den Ziffern 09 bezeichnet. Die Werte 1015 werden mit den Buchstaben AF oder af zum Ausdruck gebracht. Tabelle 1.12 zeigt die Zuordnungen in den verschiedenen Zahlensystemen.
Dezimal 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Tabelle 1.12

Hexadezimal 0 1 2 3 4 5 6 7 8 9 A B C D E F

Binr 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

Der Zusammenhang zwischen Dezimal-, Hexadezimal- und Binrsystem

59

Grundlagen von ANSI C++

C++ bietet leider keine Mglichkeit, Werte binr einzugeben, aber mit dem Prfix 0x sind hexadezimale Werte erlaubt:
int x = 0xa; // Weist x den Wert 10 zu

Zwei solcher Nibbles, also 8 Bit, werden zu einem Byte zusammengefasst und das bietet die Mglichkeit, Zahlen von 0255 darzustellen. Im Hexadezimalsystem sind dazu zwei Stellen ntig (eine pro Nibble). Abbildung 1.11 zeigt die Zusammensetzung eines Bytes.
27 26 25 24 23 22 21 20

0 0
161

0 0
160

Abbildung 1.11

Ein Byte

Diese Bytes knnen jetzt wiederum verknpft werden zu noch greren Datentypen mit noch grerem Wertebereich. Der Datentyp short z. B. besteht aus zwei Bytes und kann in seiner vorzeichenlosen Variante die Werte von 065.535 abdecken. Abbildung 1.12 zeigt die Zahl 2010 als short in der binren und hexadezimalen Schreibweise.
215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20

0 0
16
3

1 7
16
2

1 D
16
1

0 A
160

Abbildung 1.12

Die Zahl 2010

Der bitweisen Darstellung einer Zahl komm dann Bedeutung zu, wenn bitweise Operatoren auf sie angewendet werden. Nehmen wir als Beispiel das bitweise Und (&). Die Ausgabe
cout << (2010&1990) << endl;

gibt 1986 auf dem Bildschirm aus. Die Klammerung der beiden Zahlen ist notwendig, weil << eine hhere Prioritt als & hat. Ohne Klammern wrde der Ausdruck wie (cout << 2010) & (1990 << endl) ausgewertet. Abbildung 1.13 zeigt, wie es zu dem Ergebnis kommt.

60

Operatoren

1.9

215 214 213 212 211 210 2010

29

28

27

26

25

24

23

22

21

20

1
&

1990

1
=

1986

Abbildung 1.13 Bitweise Und-Verknpfung von 2010 und 1990

Die Bits der beiden Zahlen 2010 und 1990 mit dem gleichen Stellenwert werden mit Und verknpft. Das Ergebnis-Bit ist also nur dann 1, wenn beide verknpften Bits 1 sind. In allen anderen Fllen ist das Ergebnis-Bit 0. Die so erhaltene Binrzahl ergibt dezimal 1986. Tabelle 1.13 listet die bitweisen Operatoren von C++ auf und erklrt ihre Funktionsweise.
Operator
~

Beschreibung Bitweise Negation. Jedes Bit wird einzeln negiert:


~0 = 1 ~1 = 0

<<

Bitweise Linksverschiebung. a<<b schiebt die Bits von a um b Stellen nach links. Die links hinausgeschobenen Bits gehen verloren. Dafr rcken rechts b 0-Bits nach. Bitweise Rechtsverschiebung. a>>b schiebt die Bits von a um b Stellen nach rechts. Die rechts hinausgeschobenen Bits gehen verloren. Dafr rcken links b 0-Bits nach. Bitweises Und. Das Ergebnis-Bit ist nur dann 1, wenn beide Operanden-Bits 1 sind:
0 0 1 1 & & & & 0 1 0 1 = = = = 0 0 0 0

>>

&

Bitweises exklusives Oder. Das Ergebnis-Bit ist nur dann 1, wenn genau ein Operanden-Bit eins ist:
0 0 1 1 ^ ^ ^ ^ 0 1 0 1 = = = = 0 1 1 0

Tabelle 1.13

Die bitweisen Operatoren

61

Grundlagen von ANSI C++

Operator
|

Beschreibung Bitweises inklusives Oder. Das Ergebnis-Bit ist nur dann 1, wenn mindesten ein Operanden-Bit 1 ist:
0 0 1 1 | | | | 0 1 0 1 = = = = 0 1 1 1

Tabelle 1.13

Die bitweisen Operatoren (Forts.)

1.9.5

Vergleichsoperatoren

Vergleichsoperatoren dienen, wie der Name schon sagt, dem Vergleich von Werten. Das Ergebnis dieses Vergleichs ist ein Boolescher Wert (true oder false). Tabelle 1.14 listet alle Vergleichsoperatoren auf.
Operator
< <=

Beschreibung
a<b liefert true, wenn a kleiner b ist, andernfalls liefert es false. a<=b liefert true, wenn a kleiner oder gleich b ist, andernfalls liefert es false. a==b liefert true, wenn a gleich b ist, andernfalls liefert es false. a!=b liefert true, wenn a ungleich b ist, andernfalls liefert es false. a>=b liefert true, wenn a grer oder gleich b ist, andernfalls liefert es false. a>b liefert true, wenn a grer b ist, andernfalls liefert es false.

== !=

>=

>

Tabelle 1.14

Die Vergleichsoperatoren

Sehen wir uns dazu folgendes Fragment an:


int x=10; bool a = (x>=10); bool b = (x<5); cout << boolalpha << a << endl; cout << b << endl;
Listing 1.17 Der Einsatz von Vergleichsoperatoren

Die Ergebnisse zweier Vergleiche werden zwei bool-Variablen zugewiesen und anschlieend ausgegeben.

62

Operatoren

1.9

1.9.6

Logische Operatoren

Logische Operatoren dienen dazu, Boolesche Werte zu verknpfen. Sie funktionieren analog zu den bitweisen Operatoren, nur dass sie eben keine Bits, sondern Boolesche Werte verknpfen. Tabelle 1.15 listet die verfgbaren logischen Operatoren auf.
Operator
!

Beschreibung Logische Negation. Der Wahrheitsgehalt wird negiert:


!false = true !true = false

&&

Logisches Und. Die Verknpfung ist wahr, wenn alle Aussagen wahr sind:
false && false = false false && true = false true && false = false true && true = true

||

Logisches inklusives Oder. Die Verknpfung ist wahr, wenn midestens eine Aussage wahr ist:
false || false = false false || true = true; true || false = true; true || true = true

Tabelle 1.15

Die logischen Operatoren

Logisches exklusives Oder

C++ bietet keinen Operator fr das logische exklusive Oder, er lsst sich aber leicht mit den anderen Operatoren simulieren: (a && !b) || (!a && b).
Besonderheiten von C++

In C++ gibt es zwar den Datentyp bool speziell fr Boolesche Werte, er ist aber nicht wirklich ein so eigenstndiger Datentyp, wie man vermuten knnte. Genau genommen, ist bool nichts anderes als ein weiterer ganzzahliger Datentyp, der nur deshalb existiert, um speziell fr diese Art von Werten Funktionen berladen zu knnen. Deshalb sind true und false auch lediglich Konstanten fr die Werte 1 und 0, die auch stellvertretend verwendet werden knnen:
bool b=1; int i=false;

63

Grundlagen von ANSI C++

C++ geht sogar noch einen Schritt weiter und definiert, dass alle Werte auer der 0 wahr sind. Daher knnen wir so etwas problemlos schreiben:
int i=5; if(i) { /* ... */ }

Da jeder Wert auer der Null wahr ist, liee sich die Bedingung ausfhrlich auch i!=0 schreiben. In anderen Sprachen wie Java ist das auch Pflicht. In C++ nicht. Diese Vereinfachung birgt auch Tcken wie die folgende:
int i=5; if(i=10) { /* ... */ }

Als erfahrener C++-Programmierer sieht man gleich, dass dort jemand versucht hat, einen Vergleich auf i gleich 10 zu schreiben, aber versehentlich nicht den Gleichheitsoperator ==, sondern den Zuweisungsoperator = verwendet hat. Der Compiler sieht das aber nicht. Er fhrt die Zuweisung durch, i ist danach 10, und prft dann, ob 10 wahr ist. Und das ist es fr C++.
Tipp Dieser Fehler lsst sich vom Compiler berprfen, wenn man 10==i schreibt. Sollte jetzt versehentlich der Zuweisungsoperator verwendet werden, dann wrde einer Konstanten ein neuer Wert zugewiesen, und das geht selbst in C++ nicht.

Kurzschlusseigenschaft

Die logischen Operatoren && und || besitzen noch ein besonderes Verhalten, welches Kurzschlusseigenschaft genannt wird. Schauen wir uns folgendes konstruierte Beispiel an:
int x=20, y=20, z=1; if(--x==0 && --y==0) z--; cout << "x = " << x << ", y = " << y << endl;

Wenn sowohl x als auch y gleich 0 sind, wird z um eins vermindert. Die interessante Frage lautet aber: Welche Werte werden in der letzten Zeile fr x und y ausgegeben? Vllig unabhngig davon, ob das Dekrement nun Pr oder Post ist, bis zur letzten Zeile ist es auf jeden Fall ausgefhrt. Daher sollte fr x und y jeweils 19 ausgegeben werden. Aber betrachten wir den Code etwas genauer. Da --x den neuen Wert von x liefert, prft der erste Vergleich den Wert 19 auf 0, was natrlich false lie-

64

Operatoren

1.9

fert. Und jetzt kommt die Kurzschlusseigenschaft ins Spiel: Bei einer UndVerknpfung kommt nur dann true heraus, wenn beide verknpften Bedingungen wahr sind. Wenn aber die erste Bedingung bereits als falsch identifiziert wurde und das ist bei uns der Fall , dann kann der Und-Operator nur noch false liefern, vllig unabhngig von dem Ergebnis der zweiten Bedingung. Es ist also eigentlich berhaupt nicht mehr notwendig, ja sogar Zeitverschwendung, die zweite Bedingung zu prfen, weil das Gesamtergebnis lngst feststeht. Und das sieht C++ auch so. Die zweite Bedingung wird nicht mehr ausgewertet und damit auch das Dekrement fr y nicht ausgefhrt. Das Ergebnis der Ausgabe ist daher 19 fr x und 20 fr y. Mal abgesehen davon, dass bei dem Einsatz der logischen Operatoren wegen der Kurschlusseigenschaft hllisch aufgepasst werden muss, wo knnte dieser Sachverhalt von Vorteil sein? Dazu wieder einmal ein kleines Beispiel. Stellen Sie sich vor, Sie wollten berprfen, ob der Wert 10 durch die Variable x geteilt grer 2 ist. Was mssen Sie dann auf jeden Fall sicherstellen? Genau, dass x nicht 0 ist, denn durch 0 teilen macht keinen guten Eindruck:
int x=20; if(x!=0) if(10.0/x > 2) cout << "groesser 2" << endl;

Es wurde hier 10.0 geschrieben, weil es eine double-Konstante ist und deshalb das Ergebnis der Division ebenfalls vom Typ double sein wird. Das obere Beispiel ist mit dem Wissen um die Kurzschlusseigenschaft der logischen Operatoren nun einfacher zu implementieren:
int x=20; if(x!=0 && 10.0/x>2) cout << "groesser 2" << endl;

Bitte beachten Sie, dass ein Vertauschen der Operanden von && wegen der Kurzschlusseigenschaft eine vllig andere Bedeutung ergeben wrde.
Achtung In C++ sind die logischen Operatoren nicht kommutativ.

Dies luft entgegen dem Verhalten, wie es vielleicht aus der Booleschen Algebra bekannt ist.

65

Grundlagen von ANSI C++

Die Kurzschlusseigenschaft in Kombination mit der Tatsache, dass jeder Wert auer 0 wahr ist, erlaubt auch solche Konstruktionen:
int i=8; i>5 && cout << "i groesser 5" << endl;

Sollte i nicht grer 5 sein, dann ist der linke Ausdruck von && falsch, der rechte Ausdruck wrde damit nicht ausgewertet und die Ausgabe nicht durchgefhrt. Solche Formulierungen erhhen jedoch nicht die bersichtlichkeit und bringen keinerlei Vorteile.

1.9.7

Spezielle Operatoren

Zu der Gruppe der speziellen Operatoren zhle ich die, deren Verstndnis eine eingehendere Beschftigung mit der Thematik erfordert, in deren Zusammenhang sie eingesetzt werden. Daher werden sie an dieser Stelle nur aufgefhrt und spter eingehend besprochen.
Operator
? :

Beschreibung Operator zur Formulierung einer Bedingung als Ausdruck, siehe Abschnitt 2.1.5, ?:-Operator statische Typumwandlung zur Kompilationszeit, siehe folgenden Abschnitt 1.9.8 dynamische Typberprfung, siehe Abschnitt 4.20, Downcasts Indexoperator, siehe Abschnitt 3.1, Arrays Elementoperator, siehe Abschnitt 3.3, Strukturen Zeigeroperator, siehe Abschnitt 3.4.5, Zeiger auf Strukturund Klassenobjekte
Weitere Operatoren

static_cast

dynamic_cast

[ ] . ->

Tabelle 1.16

1.9.8

Operanden unterschiedlichen Datentyps

Betrachten wir folgenden Codeschnipsel:


int x=10; int y=4; double z = x / y; cout << z << endl;
Listing 1.18 Division zweier ganzzahliger Variablen

66

Operatoren

1.9

Welcher Wert wird auf dem Bildschirm ausgegeben? Die Herleitung knnte folgendermaen lauten: Die Variable z ist vom Typ double und kann damit Fliekommazahlen speichern. Die Division von 10 durch 4 ergibt 2,5, also wird genau das ausgegeben. Dieser Gedankengang bercksichtigt eine entscheidende Tatsache von C++ nicht. Denn der Datentyp eines Ausdrucks hngt salopp gesprochen vom Operanden mit dem greren Wertebereich ab. Die Operanden des Ausdrucks (x und y) sind aber beide int, damit ist auch das Ergebnis int. Und weil bei einer Division von int-Werten die Nachkommastellen immer wegfallen, liefert der Ausdruck den Wert 2. Dabei spielt es keine Rolle, dass die 2 nachtrglich einer double-Variablen zugewiesen wird. Denn die Ganzzahl 2, in eine Fliekommazahl umgewandelt, ist 2,0. Nun soll eine der beiden Variablen double sein:
int x=10; double y=4; double z = x / y; cout << z << endl;
Listing 1.19 Division von int und double

Der Divisionsoperator besitzt jetzt als Operanden einen int- und einen double-Wert (dabei spielt es keine Rolle, welcher der beiden Operanden vom Typ double ist). Der grere Wertebereich ist double und demnach auch der Typ des Ergebnisses. Also erhalten wir 2,5, welches in z gespeichert als 2,5 erhalten bleibt. Wre z im oberen Beispiel vom Typ int, dann wrde das Ergebnis des Ausdrucks auf 2 abgerundet und als Ganzzahl in z gespeichert. Bei dieser Umwandlung kann der Compiler eine Warnung melden, weil der Wertebereich eingeschrnkt wird und dadurch Informationen verloren gehen knnen (in unserem Fall konkret die Nachkommastelle).
Explizite Typumwandlung

Um ein vernnftiges Ergebnis zu erhalten, mussten wir im oberen Abschnitt den Typ einer der beiden Variablen abndern. Diese Vorgehensweise ist nicht immer mglich. Beispielsweise knnten die beiden Werte von Programmteilen geliefert werden, die nicht von uns programmiert wurden und deshalb nicht nderbar sind. Eine Variante wre das Hinzufgen einer zustzlichen Variablen mit dem gewnschten Typ, die dann in die Rechnung einfliet:

67

Grundlagen von ANSI C++

int x=10; int y=4; double tmp = y; double z = x / tmp; cout << z << endl;

Auf diese Weise erhalten wir das gewnschte Ergebnis, ohne die Datentypen der Variablen x und y zu verndern. Obwohl sich diese Vorgehensweise in manchen Lsungen anbietet, wre es mhsam, immer zu diesem Schritt gezwungen zu sein. Deshalb gibt es die Mglichkeit der expliziten Typumwandlung. Im oberen Beispiel wurde nichts anderes gemacht, als mithilfe der Variablen z den Typ des in y gespeicherten Wertes implizit also ohne konkret formuliert zu werden umzuwandeln. War der Wert in y noch die Ganzzahl 4, so ist er in z die Fliekommazahl 4,0. Der Typ von y und der darin enthaltene Wert bleiben natrlich weiterhin int. Der Typ einer Variablen kann nachtrglich nicht mehr gendert werden. Die explizite Typumwandlung versetzt uns in die Lage, den Typ eines Wertes umzuwandeln, ohne ihn dazu in einer anderen Variablen zwischenspeichern zu mssen. Der dazu notwendige Befehl heit static_cast und hat folgende Syntax:
static_cast<Zieltyp>(UmzuwandelderWert)

Der obere Ausdruck steht fr den Wert UmzuwandelnderWert, der in den Typ Zieltyp umgewandelt wurde. Schauen wir uns das einmal praktisch angewendet an unserem vorigen Beispiel an:
int x=10; int y=4; double z = x / static_cast<double>(y); cout << z << endl;
Listing 1.20 Division mit expliziter Typumwandlung

Der rechte Operand des Divisonsoperators ist nun der mit static_cast in double umgewandelte Wert von y. Auch hier gilt es zu beachten, dass sich durch diese Umwandlung der Typ von y nicht verndert hat.

68

Operatoren

1.9

Es reicht aus, einen der beiden an der Division beteiligten Werte in double umzuwandeln, weil sich der Ergebnistyp immer nach dem Operandentyp mit dem greren Wertebereich richtet. Dabei spielt es auch keine Rolle, welcher der beiden Operanden umgewandelt wird. Theoretisch knnten wir auch beide Operanden umwandeln, das fllt jedoch unter die Rubrik unntige Mhe, weil der Compiler den verbliebenen int-Wert automatisch in double umwandeln muss, um eine vernnftige Division durchfhren zu knnen.

1.9.9

Bindungsstrke

Einige Operatoren haben gegenber anderen Vorrang die Tabelle 1.17 bietet einen berblick der Operatoren einschlielich ihrer Bindungsstrke.
Operator
:: ++ -( ) [ ] . -> typeid static_cast dynamic_cast reinterpret_cast const_cast + ++ -! ~ &

Bedeutung Bezugsrahmenoperator Postinkrement Postdekrement Parameterliste Indexoperator Elementoperator Zeigeroperator Typbestimmung statische Typumwandlung dynamische Typumwandlung neu interpretierende Typumwandlung const wegcasten unres Plus unres Minus Prinkrement Prdekrement logische Negation bitweise Negation Adressoperator
Operatoren und ihre Bindungsstrken

Tabelle 1.17

69

Grundlagen von ANSI C++

Operator
* sizeof

Bedeutung Dereferenzierungsoperator Grenbestimmung Typumwandlung C-Stil Speicherplatzreservierung Speicherplatzfreigabe Verweis auf Klassenelement ber Objekt Verweis auf Klassenelement ber Zeiger Multiplikation Division Restwertbestimmung Addition Subtraktion bitweise Linksverschiebung bitweise Rechtsverschiebung kleiner kleiner gleich grer gleich grer gleich ungleich bitweises Und bitweises exklusives Oder bitweises inklusives Oder logisches Und logisches inklusives Oder Ausdruck mit Bedingung Zuweisung Additionszuweisung Subtraktionszuweisung Multiplikationszuweisung
Operatoren und ihre Bindungsstrken (Forts.)

(typ)
new delete .* ->* * / % + << >> < <= >= > == != & ^ | && || ? : = += -= *=

Tabelle 1.17

70

Die cmath-Funktionen

1.10

Operator
/= %= <<= >>= &= |= ~=

Bedeutung Divisionszuweisung Restwertzuweisung Linksverschiebungszuweisung Rechtsverschiebungszuweisung bitweise Und-Zuweisung bitweise Oder-Zuweisung bitweise Negationszuweisung
Operatoren und ihre Bindungsstrken (Forts.)

Tabelle 1.17

Die in Tabelle 1.17 aufgefhrten Operatoren sind ihrer Bindungsstrke entsprechend absteigend gruppiert. Demnach bindet * seine Operanden strker als +. Der Ausdruck w+x*y+z wird deshalb von der Operatorreihenfolge her wie w+(x*z)+z abgearbeitet. Die Bindungsstrke wird auch als Prioritt der Operatoren oder als Vorrangregel bezeichnet. Alle unren und Zuweisungsoperatoren werden von rechts nach links abgearbeitet, die brigen von links nach rechts. Der Ausdruck x+y+z wird wie (x+y)+z und x+=y+=z wird wie x+=(y+=z) umgesetzt.
Achtung, berladung C++ erlaubt fr eigene Datentypen das berladen von Operatoren. Diese berladungen knnen den Operatoren komplett andere Funktionalitten mitgeben, als von den Standarddatentypen her bekannt. Deshalb sind die folgenden Beschreibungen nur bei den elementaren Datentypen garantiert. Eigene Datentypen sollten ihr Verhalten zwar optimalerweise dem der elementaren Datentypen anpassen, eine Garantie gibt es aber nicht. Prominentes Beispiel sind die bitweisen Verschiebeoperatoren << und >>, die bei cout und cin eine vllig andere Funktionalitt besitzen.

1.10

Die cmath-Funktionen

Ergnzend zu den Operatoren gibt es noch eine Gruppe mathematischer Funktionen, die in der Header-Datei cmath zu finden sind. Es ist Ihnen vielleicht aufgefallen, dass der Modulo-Operator nur auf ganzzahlige Werte anwendbar ist. Das Pendant fr die Fliekommawerte finden Sie in dieser Funktionsgruppe. Die Funktionen sind exemplarisch fr den Datentyp double aufgelistet, existieren aber auch noch parallel fr die ande-

71

Grundlagen von ANSI C++

ren Fliekommatypen. Diese Mglichkeit, mehrere Funktionen mit demselben Namen zu definieren, wird berladen genannt (mehr zu diesem Thema finden Sie in Abschnitt 4.7, berladen von Methoden).
Funktion
acos asin atan, atan2 ceil cos cosh exp fabs floor fmod frexp HUGE_VAL ldexp log log10 modf pow sin sinh sqrt tan tanh

Beschreibung Umkehrfunktion des Kosinus Umkehrfunktion des Sinus Umkehrfunktionen des Tangens Aufrunden Kosinus Kosinus hyperbolicus Exponentialfunktion Absolutwert Abrunden Modulo Aufspaltung einerZahl a in a=f*2i Rckgabewert bei Bereichsberschreitung Umkehrung zu frexp natrlicher Logarithmus dekadischer Logarithmus Aufspaltung einer Zahl a in a=f+i Potenz Sinus Sinus hyperbolicus Quadratwurzel Tangens Tangens hyperbolicus
Die Funktionen von cmath

Tabelle 1.18

acos Arkus Kosinus


double acos(double a);

Der Arkus Kosinus ist die Umkehrfunktion des Kosinus fr a im Bogenma im Bereich [0,pi].

72

Die cmath-Funktionen

1.10

asin Arkus Sinus


double asin(double a);

Der Arkus Sinus ist die Umkehrfunktion des Sinus fr a im Bogenma im Bereich [-pi/2, pi/2].
atan Arkus Tangens
double atan(double a);

Der Arkus Tangens ist die Umkehrfunktion des Tangens fr a im Bogenma im Bereich [-pi/2, pi/2].
atan2 Arkus Tangens
double atan2(double a, double b);

Der Arkus Tangens ist die Umkehrfunktion des Tangens fr a/b im Bogenma im Bereich [-pi, pi].
ceil Aufrunden
double ceil(double a);

Liefert die nchsthhere Ganzzahl von a.


cos Kosinus
double cos(double a);

Liefert den Kosinus von a im Bogenma.


cosh Kosinus hyberbolicus
double cosh(double a);

Liefert den Kosinus hyperbolicus von a im Bogenma.


exp Exponentialwert
double exp(double a);

Liefert den Exponentialwert von a (ea).


fabs Absolutwert
double fabs(double a);

Liefert den Betrag von a ( |a| ).

73

Grundlagen von ANSI C++

floor Abrunden
double floor(double a);

Liefert die nchstniedrige Ganzzahl von a.


fmod Modulo
double fmod(double a, double b);

Liefert den Modulowert der Division a/b.


frexp
double frexp(double a, int *b);

Spaltet a in einen ganzzahligen Wert i und einen Fliekommawert f im Bereich [0.5, 1] auf, so dass gilt: a=f*2i. i wird in b gespeichert und f von der Funktion zurckgegeben.
HUGE_VAL der Overflow-Wert

Reprsentiert den Wert, den die cmath-Funktionen bei einer Wertberschreitung (overflow) zurckliefern.
ldexp
double ldexp(double a, int b);

Liefert a*2b und bildet damit die Umkehrfunktion von frexp.


log natrlicher Logarithmus
double log(double a);

Liefert den natrlichen Logarithmus (Logarithmus naturalis) von a zur Basis e.


log10 dekadischer Logarithmus
double log10(double a);

Liefert den Logarithmus von a zur Basis 10 (dekadischer Logarithmus).


modf
double modf(double a, int *b);

Spaltet die Fliekommazahl a in einen ganzzahligen Wert i und einen die Nachkommastellen enthaltenden Fliekommawert f auf, so dass gilt: a=i+f. i wird in b gespeichert und f von der Funktion zurckgegeben.

74

Die cmath-Funktionen

1.10

pow Potenz
double pow(double a, double b);

Liefert ab.
sin Sinus
double sin(double a);

Liefert den Sinus von a im Bogenma.


sinh Sinus hyperbolicus
double sinh(double a);

Liefert den Sinus hyperbolicus von a im Bogenma.


sqrt Quadratwurzel
double sqrt(double a);

Liefert die Quadratwurzel von a.


tan Tangens
double tan(double a);

Liefert den Tangens von a im Bogenma.


tanh Tangens hyperbolicus
double tanh(double a);

Liefert den Tangens hyperbolicus von a im Bogenma.

75

Dieses Kapitel behandelt die Kontrollstrukturen von C++, wie Verzweigungen, Schleifen etc.

Kontrollstrukturen

Das vorige Kapitel war zugegebenermaen etwas trocken. Selbst bei den Grundlagen gibt es in C++ bereits viele Detailinformationen, fr die sich Ihnen vielleicht nicht auf Anhieb ein Einsatzbereich erschliet. Dieses Kapitel beschftigt sich mit den Mglichkeiten, den Programmfluss zu beeinflussen und erffnet Ihnen damit erste Anwendungsgebiete fr das im ersten Kapitel erworbene Wissen.

2.1

Verzweigungen

Bisher liefen unsere Programme schnurstracks vom Anfang zum Ende. Jeder Befehl wurde immer ausgefhrt, keiner ausgelassen. Hufig ist es aber sinnvoll, die Ausfhrung eines Anweisungsbocks von einer Situation abhngig zu machen. Nehmen wir ein Beispiel aus der Praxis. Sie haben soeben ein Ticket fr eine Achterbahnfahrt erworben und wollen nun einsteigen. Als Brillentrger wre jetzt vielleicht ein guter Zeitpunkt, die Brille abzunehmen. Wollten wir diesen Sachverhalt mit unserem bisherigen Wissen programmieren, htten wir ein Problem. Denn entweder msste mit unseren bisherigen Mitteln jeder die Brille abnehmen, was eine Hrde fr Personen ohne Brille darstellen knnte, oder niemand msste die Brille abnehmen. Wir bruchten die in Abbildung 2.1 dargestellte Mglichkeit, die Brille nur dann abnehmen zu lassen, wenn die Person auch tatschlich eine Brille trgt. Die Abbildung zeigt, dass jemand ohne Brille gleich Platz nehmen kann, whrend Brillentrger zunchst die Brille abnehmen mssen. Und genau das ist eine Verzweigung. Anhand einer Bedingung (Brillentrger oder nicht) wird ein Programmteil abgearbeitet oder nicht.

77

Kontrollstrukturen

[Brillentrger] [sonst] Brille abnehmen

Hinsetzen

Abbildung 2.1

Eine Verzweigung im realen Leben

2.1.1

Bedingungen & bool

Wie am oberen Beispiel schn zu erkennen ist, hngt die Ausfhrung einer Aktion von einer Bedingung ab. In der Programmierung knnen solche Bedingungen entweder wahr oder falsch sein. Dazwischen gibt es nichts. Fr diese beiden Zustnde existieren in C++ die bereits vorgestellten Schlsselwrter true und false, sowie der zur Speicherung dieser Werte verwendete Datentyp bool.
bool a = true;

Ich mchte hier noch einmal darauf hinweisen, dass es in C++ zwischen bool und int keine strikte Typunterscheidung gibt, wie in Abschnitt 1.9.6, Logische Operatoren, bereits gezeigt wurde.
Hinweis In C++ ist jeder Wert auer 0 wahr.

2.1.2

if

Jetzt fehlt nur noch der Befehl, mit dem auf den Wahrheitsgehalt einer Bedingung reagiert werden kann. Und dazu dient die if-Anweisung:
if( Ausdruck ) Anweisung

78

Verzweigungen

2.1

Eine Anweisung kann eine einzelne, mit Semikolon abgeschlossene Anweisung oder ein mit { } definierter Block von Anweisungen sein. Abgearbeitet wird die Anweisung, wie in Abbildung 2.2 dargestellt.

[Ausdruck wahr] [Ausdruck falsch] Anweisung

Abbildung 2.2 Die Funktionsweise von if

Sollte der Ausdruck innerhalb der runden Klammen von if wahr sein, dann wird die hinter if stehende Anweisung (oder der Anweisungsblock) ausgefhrt. Andernfalls wird sie einfach bersprungen. Wir haben es hier mit einer Wenn-Dann-Verzweigung zu tun. In den runden Klammern von if kann eine Boolesche Variable stehen:
bool b=true; if(b) { // Anweisungen }

Auch die Angabe eines Vergleichs ist mglich, weil diese ebenfalls einen Booleschen Wert liefert (Vergleiche werden mit den Vergleichsoperatoren aus Abschnitt 1.9.5, Vergleichsoperatoren, formuliert):
int i=23; if(i>50) { // Anweisungen }

Bis hierhin geht C++ konform mit anderen Sprachen wie C# oder Java. Weil in C++ aber alle Werte auer 0 wahr sind, ist auch so etwas erlaubt:
double d=3.14; if(d) {

79

Kontrollstrukturen

// Anweisungen }

Im oberen Fall wird der Anweisungsblock hinter if abgearbeitet, weil 3,14 definitiv nicht 0 ist. In Listing 2.1 sehen Sie ein komplettes Beispiel fr die Anwendung von if:
#include <iostream> using namespace std; int main() { cout << "Bitte Wert eingeben:"; int x; cin >> x; if(x<0) { cout << "Sie haben einen negativen Wert eingegeben!" << endl; } cout << "Der Wert lautet " << x << endl; }
Listing 2.1 Ein Beispiel fr if

Das Programm gibt den eingegebenen Wert aus, quittiert jedoch zustzlich die Eingabe einer negativen Zahl. Die Verwendung von geschweiften Klammern hinter if ist in diesem Fall unntig, weil der Anweisungsblock nur aus einer Anweisung besteht. Da sich die geschweiften Klammern aber nicht negativ auf die Laufzeit auswirken, stattdessen aber die bersichtlichkeit erhhen, knnen sie nicht schaden. Die Formatierung des Beispiels zeigt die bliche Vorgehensweise, die ffnende Klammer des Anweisungsblocks direkt hinter die vorhergehende Anweisung zu schreiben.

2.1.3

else

Hufig mchte man aber nicht nur eine Aktion ausfhren, wenn eine Bedingung wahr ist, sondern auch noch Programmcode abarbeiten lassen, wenn die Bedingung wahr ist. Wir haben es dann mit einer Entweder-Oder-Verzweigung zu tun.

80

Verzweigungen

2.1

Bevor wir fr die oben beschriebene Achterbahnfahrt Platz nehmen knnen, mssen wir zum Beispiel ein Ticket erwerben. Das knnte wie in Abbildung 2.3 skizziert vonstatten gehen.

[sonst] [lter als 12]

5 bezahlen

3 bezahlen

Ticket einstecken

Abbildung 2.3 Ticketverkauf als Wenn-Dann-Verzweigung

Ist der Mitfahrer lter als zwlf Jahre, muss er 5 bezahlen, ansonsten 3 . Er muss auf jeden Fall einen der beiden Preise bezahlen, entweder den einen oder den anderen, aber niemals beide oder keinen. Ein numerisches Beispiel ist das Prfen auf 0. Wenn eine Variable den Wert 0 hat, soll ein entsprechender Text erscheinen. Ist sie ungleich 0, dann soll dies ebenfalls ber eine Ausgabe mitgeteilt werden. Auch hier haben wir wieder eine Entweder-Oder-Verzweigung. Entweder die Zahl ist 0 oder nicht. Eins von beiden muss zutreffen, niemals beides und niemals keines. Mit unserem bisherigen Kenntnisstand knnen wir das Problem bereits lsen, indem wir zwei if-Verzweigungen hintereinandersetzen; die erste prft auf gleich 0, die zweite auf ungleich 0:
int x=1; if(x==0) { cout << "Der Wert ist null." << endl; } if(x!=0) { cout << "Der Wert ist ungleich null." << endl; }
Listing 2.2 Zweimal if fr entweder-oder

81

Kontrollstrukturen

Diese Lsung wirkt aber irgendwie doppelt gemoppelt, denn letztlich ist die zweite Bedingung nichts anderes als die negierte erste Bedingung. Weil eine solche Konstruktion hufiger vorkommt, gibt es das Schlsselwort else, welches bezogen auf einen vorangehenden if-Anweisungsblock einen zweiten Anweisungsblock definiert, der immer dann ausgefhrt wird, wenn die Bedingung bei if falsch ist:
if( Ausdruck ) Anweisung1 else Anweisung2

Anweisung1 und Anweisung2 drfen mit { } definierte Anweisungsblcke sein. Anweisung1 wird wie gehabt ausgefhrt, wenn der Ausdruck in den runden Klammern von if wahr ist. Sollte der Ausdruck aber falsch sein, dann wird Anweisung2 hinter else ausgefhrt. Das vorige Beispiel der Prfung auf 0 kann dann wie folgt umgeschrieben werden:
int x=1; if(x==0) { cout << "Der Wert ist null." << endl; } else { cout << "Der Wert ist ungleich null." << endl; }
Listing 2.3 Entweder-oder mit if und else

Vereinfachungen

Fr Anweisungsblcke gibt es die angenehme Vereinfachung, dass die geschweiften Klammern weglassen werden knnen, wenn der Anweisungsblock aus nur einer Anweisung besteht. Damit lsst sich das Beispiel aus Listing 2.3 wie folgt verkrzen:
int x=1;if(x==0) cout << "Der Wert ist null." << endl;else cout << "Der Wert ist ungleich null." << endl;

Jedoch kann insbesondere bei verschachtelten Anweisungsblcken durch diese Vereinfachung ein erhebliches Ma an bersichtlichkeit verloren gehen. Deswegen sollten die Klammern im Zweifel lieber einmal zu viel als einmal zu wenig gesetzt werden.

82

Verzweigungen

2.1

2.1.4

Einsatz logischer Operatoren

Stellen Sie sich vor, aus einem an dieser Stelle irrelevanten Anlass ist eine Zahl gltig, wenn sie grer 0 und kleiner 100 ist. Mit unserem bisherigen Wissen mssten wir fr diese Abfrage zwei if-Anweisungen verschachteln:
int x=1; if(x>0) if(x<100) cout << "Der Wert ist gueltig." << endl;
Listing 2.4 Und-Verknpfung mit zwei verschachtelten if-Blcken

Weil eine if-Anweisung samt Anweisungsblock (und ihrem vielleicht vorhandenen else-Block) als eine Anweisung gilt, knnen wir auch bei der ueren if-Anweisung auf die geschweiften Klammern verzichten. Wenn aber auch ausgegeben werden soll, dass ein Wert auerhalb des gltigen Bereichs ungltig ist, wo mssten wir diese Ausgabe im oberen Beispiel unterbringen? Die Lsung mag auf den ersten Blick verwirren:
int x=1; if(x>0) if(x<100) cout << "Der Wert ist gueltig." << endl; else cout << "Der Wert ist ungueltig." << endl; else cout << "Der Wert ist ungueltig." << endl;
Listing 2.5 Zustzliche Ausgabe bei ungltigem Wert

Die gewnschte Information wird an zwei Stellen ausgegeben. Bei genauerem Hinschauen ist das auch logisch, denn sowohl die erste, als auch die zweite if-Anweisung knnten feststellen, dass der Wert ungltig ist, daher muss auch jede if-Anweisung diese Information mit ihrem eigenen elseBlock mitteilen. In der Schreibweise ohne expliziten Anweisungsblock ist optisch eigentlich nur durch das Einrcken einigermaen ersichtlich, welches else zu welchem if gehrt. Im Zweifel sollten Sie daher die Schreibweise mit geschweiften Klammern verwenden:
int x=1; if(x>0) {

83

Kontrollstrukturen

if(x<100) { cout << "Der Wert ist gueltig." << endl; } else { cout << "Der Wert ist ungueltig." << endl; } } else { cout << "Der Wert ist ungueltig." << endl; }
Listing 2.6 Die ausfhrliche Fassung von Listing 2.5

Da die Verknpfung von Bedingungen bei der Programmierung eher der Regelfall ist, werden die logischen Operatoren (siehe Abschnitt 1.9.6, Logische Operatoren) verwendet. Der Und-Operator liefert genau dann true, wenn beide durch ihn verknpften Bedingungen wahr sind. Ist nur eine der Bedingungen wahr oder keine, dann liefert er false. Er ist damit der ideale Kandidat, um das vorige Beispiel zu vereinfachen:
int x=1; if(x>0 && x<100) cout << "Der Wert ist gueltig." << endl; else cout << "Der Wert ist ungueltig." << endl;
Listing 2.7 Die Und-Verknpfung mit &&-Operator

2.1.5

?:-Operator

An manchen Stellen kann in C++ keine Anweisung stehen, wohl aber ein Ausdruck. Ein Beispiel ist die Elementinitialisierungsliste eines Konstruktors. Aus diesem Grund gibt es einen Operator, der die Funktionalitt eines ifelse-Konstrukts besitzt. Betrachten wir das folgende Programmfragment, welches der Variablen erg den greren der beiden Werte von a und b zuweist:
int a=15, b=30, erg; if(a>b) erg=a; else erg=b;

84

Verzweigungen

2.1

Der Wert von erg hngt im oberen Beispiel von einer Bedingung ab; ist a>b wahr, dann wird erg der Wert von a zugewiesen, andernfalls der Wert von b. Dies sieht mit dem ?:-Operator so aus:
int a=15, b=30, erg; erg = (a>b)?a:b;
Listing 2.8 Der Einsatz des ?:-Operators

Die Bedeutung der einzelnen Teile ist in Abbildung 2.4 dargestellt.

Bedingung

Wert, den der Ausdruck annimmt, wenn die Bedingung false ist

erg = (a>b)?a:b ;
Wert, den der Ausdruck annimmt, wenn die Bedingung true ist

Abbildung 2.4 Funktionsweise des ?:-Operators

Vor dem Fragezeichen steht die Bedingung, die den Wert des Ausdrucks bestimmt. Ist diese Bedingung wahr, dann nimmt der ?:-Operator den hinter dem Fragezeichen stehenden Wert an. Ist die Bedingung falsch, dann steht der ?:-Operator fr den Wert hinter dem Doppelpunkt. Weil der ?:-Operator auch viel krzer ist und weniger Schreibarbeit erfordert, wird er gerne anstelle eines if-Konstrukts verwendet, auch wenn if problemlos einsetzbar wre.

2.1.6

switch & case

Stellen Sie sich folgende Situation vor: Es wird eine Zahl eingelesen, und wenn diese Zahl 3, 5 oder 7 ist, dann soll die Zahl als Wort (drei, fnf, sieben) ausgegeben werden. Bisher knnen wir dies nur mit drei if-Anweisungen bewerkstelligen:
cout << "Zahl eingeben:"; int x; cin >> x;

85

Kontrollstrukturen

if(x==3) cout << "drei" << endl; if(x==5) cout << "fuenf" << endl; if(x==7) cout << "sieben" << endl;
Listing 2.9 Fallunterscheidung mit if

Im konkreten Fall schlieen sich die drei Bedingungen logisch aus (Der Wert kann zum Beispiel nicht gleichzeitig 3 und 5 sein). Theoretisch und praktisch allemal knnte aber in einem if-Anweisungsblock etwas geschehen, wodurch die nchste Bedingung wahr wird. Wir simulieren das einmal ganz plump, indem der Variablen der entsprechende Wert zugewiesen wird:
if(x==3) { cout << "drei" << endl; x=7; } if(x==5) cout << "fuenf" << endl; if(x==7) cout << "sieben" << endl;

Sollte x zu Beginn nun 3 sein, dann wird drei und sieben ausgegeben. Obwohl dieses Verhalten in seltenen Fllen gewnscht sein mag, versucht man berwiegend, das Phnomen einer mehrfachen Ausgabe zu vermeiden. Doch wie? Am einfachsten geht das, indem Sie das folgende if-Konstrukt immer in den else-Teil des vorhergehenden if packen. Im Folgenden sehen Sie das Ergebnis ausfhrlich mit Klammern und korrekter Einrckung (das Setzen von x auf 7 wurde wieder entfernt):
if(x==3) { cout << "drei" << endl; } else { if(x==5) { cout << "fuenf" << endl; }

86

Verzweigungen

2.1

else { if(x==7) { cout << "sieben" << endl; } } }


Listing 2.10 Simulation eines else if (ausfhrlich)

Wir haben auf diese Weise ein in C++ nicht vorhandenes else-if-Konstrukt simuliert. Um dies zu verdeutlichen, wird der Code formatiert, als gbe es ein else-if:
if(x==3) { cout << "drei" << endl; } else if(x==5) { cout << "fuenf" << endl; } else if(x==7) { cout << "sieben" << endl; }
Listing 2.11 Simulation eines else if (kompakt)

Worauf das Beispiel aber hinauslaufen soll: Fr die Unterscheidung verschiedener Flle anhand von Werten gibt es die Fallunterscheidung. Das obere Beispiel sieht mit der Fallunterscheidung so aus:
switch(x) { case 3: cout << "drei" << endl; break; case 5: cout << "fuenf" << endl; break; case 7: cout << "sieben" << endl; break; }
Listing 2.12 Die Funktionsweise von switch

Die Funktionsweise ist in Abbildung 2.5 noch einmal grafisch dargestellt.

87

Kontrollstrukturen

Die switch-Syntax lautet:


switch( Ausdruck ) { case GanzzahligeKonstante: // Anweisungen case GanzzahligeKonstante: // Anweisungen ... }

[x==3] "drei" ausgeben [sonst] [x==5] "fuenf" ausgeben [sonst] [x==7] "sieben" ausgeben [sonst]

Abbildung 2.5 Die Funktionsweise von switch mit break

In den runden Klammern hinter switch wird angegeben, fr welche Variable die Fallunterscheidung getroffen werden soll. Erlaubt sind nur ganzzahlige Variablen sowie die Variablentypen bool und char, die technisch auch zu den ganzzahligen Typen gezhlt werden. Hinter case steht eine ganze Zahl (es knnen auch die Schlsselwrter true oder false sein oder ein Zeichen in einfachen Anfhrungszeichen), die den Wert definiert, bei dem der caseBlock abgearbeitet wird. Die Angabe von Bereichen ist nicht mglich. Das break zum Abschluss jeden case-Blocks ist wichtig, damit nach der Ausfhrung des Blocks der switch-Block verlassen wird. Es ist syntaktisch auch erlaubt, auf die break-Anweisungen zu verzichten:
switch(x) { case 3: cout << "drei" << endl;

88

Verzweigungen

2.1

case 5: cout << "fuenf" << endl; case 7: cout << "sieben" << endl; }

Allerdings ist das Verhalten etwas gewhnungsbedrftig. Abbildung 2.6 zeigt den Ablauf.

[x==3] "drei" ausgeben [sonst] [x==5] "fuenf" ausgeben [sonst] [x==7] "sieben" ausgeben [sonst]

Abbildung 2.6 switch ohne break

Der Programmfluss zeigt sehr schn, dass die case-Anweisungen eigentlich nur Sprungmarken innerhalb des switch-Blocks sind. Der Wert 5 hat zum Beispiel die Ausgabe von fuenf und sieben zur Folge. Ohne das break wird die entsprechende case-Marke angesprungen und das Programm von dort aus bis zum Ende des switch-Blocks durchlaufen. Es stellt sich vielleicht die Frage, warum dieses Verhalten existiert und nicht automatisch nach Ende eines case-Blocks der gesamte switch-Block verlassen wird. Die Antwort ist einfach: Auf diese Weise knnen Codeverdopplungen vermieden werden. Es kommt nmlich hufiger vor, dass fr mehrere Flle derselbe Programmcode abgearbeitet werden soll. Dies wre mit switch nicht mglich, wenn ein case-Block automatisch beendet wrde. Nehmen wir als Beispiel ein Programm, welches nach einem Monat als Zahl fragt (1=Januar, 2=Februar etc.) und daraufhin die Anzahl der Tage dieses Monats

89

Kontrollstrukturen

ausgibt. Schaltjahre sollen dabei unbercksichtigt bleiben. Mit switch knnte die Lsung so aussehen:
#include <iostream> using namespace std; int main() { cout << "Bitte Monat eingeben (1-12):"; int monat; cin >> monat; switch(monat) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: cout << "Der Monat hat 31 Tage" << endl; break; case 4: case 6: case 9: case 11: cout << "Der Monat hat 30 Tage" << endl; break; case 2: cout << "Der Monat hat 28 Tage" << endl; break; } }
Listing 2.13 Bestimmung der Tage eines Monats

Aus Platzgrnden wurden einige case-Anweisungen nebeneinandergeschrieben, was durchaus erlaubt und in diesem Fall auch sinnvoll ist.
default

Fr switch gibt es noch einen besonderen Fall, nmlich denjenigen, der alle nicht behandelten Flle abdeckt. Immer wenn die bei switch angegebene Variable einen Wert besitzt, fr den es kein case gibt, dann wird falls vorhanden die default-Marke angesprungen. Die default-Marke knnen wir bei der Bestimmung der Tage eines Monats in Listing 2.13 verwenden, um ungltige Monatsangaben abzufangen. Hier der aktualisierte switch-Block:
switch(monat) { case 1: case 3: case 5: case 7: case 8: case 10: case 12:

90

Schleifen

2.2

cout << "Der Monat hat 31 Tage" << endl; break; case 4: case 6: case 9: case 11: cout << "Der Monat hat 30 Tage" << endl; break; case 2: cout << "Der Monat hat 28 Tage" << endl; break; default: cout << "Ungueltige Monatsangabe!" << endl; break; }
Listing 2.14 switch-Block mit default-Marke

Die default-Marke muss nicht zwingend die letzte Marke im switch-Block sein. Hier die Syntax:
switch( Ausdruck ) { case GanzzahligeKonstante: // Anweisungen case GanzzahligeKonstante: // Anweisungen ... default: // Anweisungen }

2.2

Schleifen

Nachdem unsere Programme nun aufgrund einer Bedingung verzweigen knnen, wollen wir nun klren, wie Programmabschnitte wiederholt werden knnen, ohne sie mehrfach ins Programm schreiben zu mssen. Das ist immer dann sinnvoll, wenn hnliche Programmschritte wiederholt werden sollen. Stellen Sie sich vor, Sie mssten zehn Zahlen einlesen und diese aufaddieren. Dann macht es einen Unterschied, ob Sie die Eingabe fr jede Zahl einzeln also zehnmal programmieren, so wie wir es mit dem jetzigen

91

Kontrollstrukturen

Stand machen mssten, oder ob die Eingabe einmal programmiert und dann zehn Mal wiederholt wird.

2.2.1

while

Die einfachste Form der Wiederholung ist die while-Schleife:


while( Ausdruck ) Anweisung

Der Programmablauf unterscheidet sich aber wesentlich, wie Abbildung 2.7 zeigt.

[Ausdruck wahr] [Ausdruck falsch] Anweisung

Abbildung 2.7 Die while-Schleife

Genau wie bei der if-Anweisung wird die Anweisung bzw. der Anweisungsblock nur ausgefhrt, wenn der in den runden Klammern angegebene Ausdruck true ist. Nach der Ausfhrung des Anweisungsblocks springt das Programm aber wieder zurck zum Schleifenkopf und prft den Ausdruck erneut. Sollte er immer noch true sein, dann wird der Anweisungsblock nochmals ausgefhrt usw. Erst wenn der Boolesche Wert false ist, wird die Schleife beendet, und das Programm fhrt hinter der Schleife fort. Die simpelste Form der Schleife sieht so aus:
while(true) { cout << "Ich bin eine Schleife" << endl; }
Listing 2.15 Eine Endlosschleife

92

Schleifen

2.2

Diese Schleife gibt wiederholt einen Text aus. Als Ausdruck wurde true angegeben. Da true den Wert true hat und sich das auch nicht so schnell ndert, wird die Schleife niemals enden. Wir haben eine Endlosschleife konstruiert. In den meisten Fllen ist eine solche Schleife unerwnscht. Sie ist nur dann sinnvoll, wenn es innerhalb der Schleife einen anderen Mechanismus zum Schleifenabbruch gibt, wie zum Beispiel break oder return. An dieser Stelle wollen wir den Fokus aber auf eine Programmierung lenken, bei der die Schleife sauber beendet wird. Dies geschieht nur, wenn der Ausdruck bei while irgendwann einmal false wird. Das schliet den Einsatz von Konstanten schon mal aus. Wir brauchen also eine Variable, die eine Zeitlang true liefert und dann zu false wechselt. Am einfachsten erreichen wir dies durch die Formulierung einer Bedingung:
int a=1; while(a<=10) { cout << a << endl; ++a; }
Listing 2.16 Eine Schleife, die von 110 zhlt

Die Variable a wird zu Beginn mit dem Wert 1 initialisiert. Die Bedingung der while-Schleife (a<=10) ist damit true und der Anweisungsblock der Schleife wird ausgefhrt. Darin wird die Variable ausgegeben und um 1 erhht. Am Ende des Anweisungsblocks springt das Programm wieder zum Schleifenkopf und prft die Bedingung erneut. Nun ist a gleich 2 und die Bedingung immer noch true; der Anweisungsblock wird wieder ausgefhrt. So luft die Schleife, bis a den Wert 11 erreicht und die Bedingung damit false wird. Die Schleife bricht ab. Wenn wir uns den Anweisungsblock genauer ansehen, stellen wir fest, dass die Ausgabe a ausgibt, bevor es inkrementiert wird. Gewissermaen wird der alte Wert von a ausgegeben. Nun sollte es klingeln. Alter Wert da war doch was. Genau, Postinkrement. Wenn sowieso der alte Wert ausgegeben wird, dann knnen wir gleich bei der Ausgabe ein Postinkrement ttigen und haben so den Anweisungsblock auf eine Anweisung verkleinert. Und bei einer Anweisung im Anweisungsblock knnen wir die Klammern weglassen:
int a=1; while(a<=10) cout << a++ << endl;
Listing 2.17 Die Schleife in kompakter Form

93

Kontrollstrukturen

Um sich noch ein wenig mit der Schleifenmechanik zu beschftigen, knnen Sie darber nachdenken, wie die Schleife noch verndert werden msste, wenn anstelle von a++ das Prinkrement ++a verwendet wird. Die Schleife soll dann natrlich weiterhin die Zahlen von 1 bis 10 ausgeben. Die Lsung sieht so aus (die vernderten Stellen sind hervorgehoben):
int a=0; while(a<10) cout << ++a << endl;
Listing 2.18 Mit Prinkrement von 110 zhlen

Rein aus Grnden der Lesbarkeit sollten Sie die erste Variante bevorzugen.

2.2.2

do while

Der zweite Schleifentyp in C++ ist die do-while-Schleife:


do { Anweisung } while( Ausdruck );

Bitte beachten Sie das abschlieende Semikolon hinter while (weil kein Anweisungsblock folgt). Im Vergleich zur while-Schleife steht das while nun am Ende des Schleifenblocks, was sich auch in der Schleifenlogik niederschlgt, die in Abbildung 2.8 dargestellt ist.

Anweisung [Ausdruck wahr]

[Ausdruck falsch]

Abbildung 2.8 Die do-while-Schleife

94

Schleifen

2.2

Wie die Abbildung klar hervorhebt, wird der Anweisungsblock beim Eintritt in die Schleife auf jeden Fall einmal ausgefhrt, vllig unabhngig davon, ob der Ausdruck true oder false ist. Wir erinnern uns: Bei der while-Schleife ist es theoretisch auch mglich, dass der Schleifenblock berhaupt nicht ausgefhrt wird, nmlich wenn der Ausdruck zu Beginn bereits false ist. Die do-while-Schleife fhrt ihren Schleifenblock aber definitiv einmal aus. Wozu kann das ntzlich sein? Immer dann, wenn die Bedingung der Schleife von Anweisungen im Schleifenblock abhngig ist. Nehmen wir als Beispiel ein Programm, welches den Anwender nach einer Zahl fragt. Liegt diese Zahl nicht im gltigen Bereich, muss der Anwender die Zahl erneut eingeben, bis er eine gltige Zahl eingegeben hat. Bei dieser Aufgabe hngt die Bedingung der Schleife (Laufe, solange eingegebene Zahl ungltig) von der Eingabe im Schleifenblock ab. Der Schleifenblock muss demnach erst einmal ausgefhrt werden, bevor die Schleifenbedingung sinnvoll geprft werden kann. Schauen wir uns das dazugehrige Programm an, welches von einem gltigen Bereich der Zahl zwischen einschlielich 1 und 100 ausgeht:
int a; do { cout << "Zahl eingeben [1,100]:"; cin >> a; } while(a<1 || a>100);
Listing 2.19 Prfen auf eine gltige Zahl mit do while

Es ist bei diesem Beispiel ausgesprochen wichtig, dass die Variable a auerhalb des Schleifenblocks definiert wird. Warum das so ist, erklrt Abschnitt 2.3.1, Lokale Variablen. Die im Schleifenblock eingelesene Variable wird am Schleifenende in while berprft. Wenn der Wert nicht im gltigen Bereich liegt, wird nochmals der Schleifenblock durchlaufen und der Wert eingelesen. Dieses Problem htten wir auch mit einer while-Schleife lsen knnen:
int a=0; while(a<1 || a>100) { cout << "Zahl eingeben [1,100]:"; cin >> a; }
Listing 2.20 Prfen auf gltige Zahl mit while

95

Kontrollstrukturen

Der Ansatz mit der while-Schleife hat aber einen Schnheitsfehler: Weil die Bedingung bei while schon vor der Abarbeitung des Anweisungsblocks geprft wird, mssen wir dafr Sorge tragen, dass die Schleife mit Sicherheit auch betreten wird. Die obere Lsung erreicht dies, indem die Variable a mit einem ungltigen Wert vorinitialisiert wird. Diese Lsung ist aber weitaus fehleranflliger, weil bei einem Wechsel des gltigen Bereichs (z. B. [-100,100]) der Initialisierungswert von a pltzlich gltig sein knnte. Er msste deshalb ebenfalls angepasst werden. Bei einem kleinen Programm wie unserem Beispiel ist das noch berschaubar, aber in komplexeren Programmen kann diese zustzliche nderung leicht bersehen werden. Der Ansatz mit der do-while-Schleife ist in diesem Fall der bessere.

2.2.3

for

Der komplexeste Schleifentyp in C++ ist die for-Schleife. Nehmen wir zum besseren Verstndnis die folgende, von 1 bis 10 zhlende while-Schleife zu Hilfe:
int a=1; while(a<=10) { cout << a << endl; a++; }
Listing 2.21 Zhlen von 110

Wirklich als ntzliche Anweisung wiederholt wird eigentlich nur die Ausgabe. Alle anderen Befehle sind technische Manahmen, um berhaupt eine kontrollierte Wiederholung zustande zu bringen. Schauen wir uns diese Teile einmal im Detail an:
int a=1;

Dieser Teil initialisiert die Zhlvariable mit dem Wert 1.


while(a<=10)

Hier wird die Laufbedingung der Schleife formuliert (laufe, solange der Wert der Zhlvariablen kleiner gleich 10 ist). Der dritte und letzte Teil dient dazu, die Schleife in den nchsten Schritt zu berfhren, indem die Zhlvariable um 1 erhht wird:
a++;

96

Schleifen

2.2

Man nennt diesen Schritt bei einer Schleife auch Iteration. Die for-Schleife fasst diese drei Aspekte syntaktisch komprimiert zusammen:
for(Initialisierung; Bedingung; Iteration) Anweisung

Die von 1 bis 10 zhlende Schleife, mit for umgesetzt, sieht so aus:
for(int a=1; a<=10; a++) cout << a << endl;

Abbildung 2.9 hebt die wichtigen Bestandteile hervor.


Initialisierung Bedingung Iteration

for(int a=1; a<=10; a++) { cout << a << endl; }


Abbildung 2.9 Die Bestandteile der for-Schleife

Die tatschliche Reihenfolge der einzelnen Anweisungen ist der Syntax nur bedingt zu entnehmen. Sie ist in Abbildung 2.10 dargestellt.

Initialisierung

[Bedingung wahr] [Bedingung falsch]

Anweisungsblock

Iteration

Abbildung 2.10

Der Ablauf einer for-Schleife

Am Anfang wird einmalig der Initialisierungsteil ausgefhrt. Danach wird wie bei einer while-Schleife die Bedingung geprft, noch vor der Ausfhrung des Anweisungsblocks. Ebenso wie bei der while-Schleife ist es demnach

97

Kontrollstrukturen

mglich, dass bei einer ursprnglich falschen Bedingung der Anweisungsblock nie zur Ausfhrung kommt. Sollte die Bedingung wahr sein, werden der Anweisungsblock und daran anschlieend der Iterationsteil abgearbeitet. Obwohl also der Iterationsteil mit im Schleifenkopf steht, wird er erst nach dem Anweisungsblock ausgefhrt. Nach der Iteration wird wieder die Bedingung geprft und gegebenenfalls eine weitere Wiederholung eingeleitet.

2.2.4

Wann welche Schleife?

Die Frage Wann welche Schleife? wird hufig gestellt, eine eindeutige Antwort existiert jedoch nicht. Prinzipiell gibt es kein Problem, dass nicht mit allen drei Schleifenkonstrukten gelst werden kann. Die Praxis zeigt lediglich, dass bei bestimmten Problemen der eine oder der andere Schleifentyp eine syntaktisch elegantere Formulierung erlaubt oder eine geringere Fehleranflligkeit mit sich bringt. In diesem Zusammenhang sei auf das Beispiel mit dem Einlesen einer Zahl und anschlieendem Prfen auf Gltigkeit verwiesen (Listing 2.19 in Abschnitt 2.2.2, do while). Unsere Betrachtungen haben gezeigt, dass hier ganz klar die do-while-Schleife die Nase vorn hat. Folgende Tipps sollen als grobe, unverbindliche Richtlinien gelten: Verwenden Sie eine while-Schleife, wenn das Ende der Schleife im Vorfeld nicht bekannt ist, wie zum Beispiel beim Auslesen einer Datei, dem Herunterladen eines HTML-Dokuments aus dem Internet etc. Benutzen Sie eine do-while-Schleife, wenn die Schleifenbedingung von Ergebnissen abhngig ist, die erst im Schleifenblock ermittelt werden. Ein Beispiel ist das Abfragen einer Zahl in Abschnitt 2.2.2, do while. Eine for-Schleife sollten Sie einsetzen, wenn etwas durchlaufen werden soll, dessen Anfang und Ende bekannt ist, beispielsweise ein Feld, ein Datencontainer, ein Suchergebnis etc. Es ist klar, dass es fr diese Anregungen bestimmt genauso viele Gegenbeispiele wie Besttigungen gibt, deswegen betrachten Sie sie bitte wirklich nur als Denkansto.

2.2.5

break

Der Befehl break wurde bereits bei der Fallunterscheidung in Abschnitt 2.1.6, switch & case, eingesetzt. Dort sorgte er fr ein Verlassen des switchBlocks. Innerhalb einer Schleife leistet er hnliche Dienste; er beendet die innerste Schleife.

98

Schleifen

2.2

Mit seiner Hilfe ist es mglich, eine von der Bedingung her Endlosschleife zu programmieren, die praktisch jedoch ein Ende hat:
int a=1; while(true) { if(a>10) break; cout << a++ << endl; }
Listing 2.22 Schleifenabbruch mit break

Alleine von der while-Konstruktion haben wir eine Endlosschleife, wre in ihrem Block nicht die if-Anweisung, die ein break ausfhrt, sobald a grer 10 ist. Um zu zeigen, dass nur die innerste Schleife beendet wird, lassen wir den oberen Code mit einer weiteren Schleife zweimal ausfhren. Der Abwechslung halber nehmen wir eine for-Schleife, eine while- oder dowhile-Schleife htte natrlich denselben Zweck erfllt:
for(int b=1; b<=2; b++) { int a=1; while(true) { if(a>10) break; cout << a++ << endl; } }
Listing 2.23 break bei verschachtelten Schleifen

Die uere Schleife versieht zuverlssig ihren Dienst, whrend die innere Schleife wie gehabt von break beendet wird.

2.2.6

continue

Ein weiterer Befehl zur Kontrolle des Schleifenflusses ist continue. Er bricht nicht wie break die gesamte Schleife ab, sondern nur die Abarbeitung des Schleifenanweisungsblocks, um zum nchsten Iterationsschritt zu gelangen. Nehmen wir einmal folgendes sinnlose Beispiel:
for(int w=1; w<=10; w++) { if(w==6) continue; cout << w << endl; }

99

Kontrollstrukturen

Wenn Sie dieses Codefragment kompilieren und starten, werden Sie sehen, dass die 6 nicht ausgegeben wird. Denn mit w gleich 6 wird der Befehl continue ausgefhrt, der an das Ende des Anweisungsblocks springt und damit die Ausgabe berspringt und das Programm mit dem Iterationsschritt fortsetzt. Bedenken Sie, dass bei einer while- oder do while-Schleife die Erhhung/Verminderung der Variablen zum Anweisungsblock gehrt und daher mit continue ebenfalls bersprungen werden knnte.

2.3

Funktionen

Mit Schleifen knnen Codeabschnitte wiederholt werden. Was aber, wenn diese Codeabschnitte an verschiedenen Stellen im Programm liegen? Nehmen wir als Arbeitsgrundlage folgendes Codefragment:
cout << "Hopphopphopp" << endl; cout << "------------" << endl; cout << "Hopphopphopp" << endl;

Die Ausgabe von Hopphopphopp, die hier stellvertretend fr komplexere Codeabschnitte steht, erfolgt in identischer Art und Weise an zwei verschiedenen Stellen im Code. Obwohl zweimal dasselbe geschieht, steht der dazu notwendige Code zweimal im Programm. Nicht nur, dass das Programm dadurch lnger wird, bei eventuellen nderungen an den Codeabschnitten mssen auch alle Abschnitte gendert werden. Die Gefahr, dass nicht alle in gleichem Mae gendert werden oder ein Abschnitt bersehen wird, ist gro. Es wre doch viel praktischer, wenn die Ausgabe von Hopphopphopp nur einmal im Programm vorkme und wir nur noch darauf zu verweisen bruchten. Die Lsung dieses Problems lautet Funktionen. Mithilfe einer Funktion haben wir die Mglichkeit, Programmcode in einer unabhngigen Einheit zu kapseln. Hier die Syntax der Funktionsdefinition:
Rckgabetyp Funktionsname(Parameterliste) { // Anweisungen }

Unsere Funktion soll keinen Rckgabewert besitzen (wir wissen augenblicklich ja noch nicht einmal, was das ist), deswegen muss vor dem Funktionsna-

100

Funktionen

2.3

men als Rckgabetyp void stehen. Der Funktionsname sollte mglichst aussagekrftig sein, damit der Anwender schnell erfassen kann, welchem Zweck die Funktion dient. Passend wre hier hoppausgabe. Hinter dem Funktionsnamen stehen Funktionsparameter. Da noch nicht besprochen wurde, was damit gemacht wird, verzichten wir bis auf Weiteres auf sie und lassen die runden Klammern leer. Im Anweisungsblock hinter dem Funktionskopf steht der zur Funktion gehrende Programmcode, in diesem Fall die Ausgabe von Hopphopphopp. Daraus folgt die nachstehende Funktion:
void hoppausgabe() { cout << "Hopphopphopp" << endl; }
Listing 2.24 Die Funktion hoppausgabe

Fr unsere ersten Ausflge in die Programmierung von Funktionen ist es ausgesprochen wichtig, dass die neue Funktion vor main steht. Spter werden wir in dieser Hinsicht noch flexibler. Einige Punkte sind bei der Definition einer Funktion zu beachten: Auch wenn die Funktion keine Parameter besitzt, muss das leere runde Klammerpaar vorhanden sein. Die Vereinfachungsregel aus Abschnitt 2.1, Verzweigungen, ist bei den geschweiften Klammern des Funktionsanweisungsblocks nicht anwendbar; sie mssen geschrieben werden, auch wenn der Funktionsblock nur aus einer Anweisung besteht. Die Angabe von void bei nicht erwnschtem Rckgabewert ist zwingend. Aufgerufen wird die Funktion durch Angabe des Namens, gefolgt von der Parameterliste (den runden Klammern), die bei unserer Funktion leer bleibt:
blahausgabe();

Sehen wir uns nun das vollstndige Programm an:


#include <iostream> using namespace std; void hoppausgabe() { cout << "Hopphopphopp" << endl; }

101

Kontrollstrukturen

int main() { hoppausgabe(); cout << "------------" << endl; hoppausgabe(); }


Listing 2.25 Definition und Aufruf einer Funktion

Jedes Mal, wenn die Funktion hoppausgabe aufgerufen wird, springt das Programm zur Funktion und merkt sich den Punkt, von dem aus es zur Funktion gesprungen ist. Nach Beendigung der Funktion springt das Programm dann an die Stelle zurck, an der die Funktion aufgerufen wurde, und fhrt dort mit der Ausfhrung fort.

2.3.1

Lokale Variablen

Bevor wir uns mit Funktionsparametern befassen, mssen wir noch Klarheit darber schaffen, was lokale Variablen sind. Nehmen wir dazu Listing 2.19 in Abschnitt 2.2.2, do while, und wandeln es leicht ab:
do { cout << "Zahl eingeben [1,100]:"; int a; cin >> a; } while(a<1 || a>100); // Fehler
Listing 2.26 Eine Schleife mit lokaler Variablen

Anders als im Original wird die Variable a jetzt innerhalb des Schleifenblocks definiert. Erstaunlicherweise meldet der Compiler in der letzten Zeile einen Fehler. Angeblich sei die Variable a berhaupt nicht deklariert. Und das Schlimme ist: Er hat Recht. Ohne dass wir es bisher wussten, arbeiten wir momentan ausschlielich mit lokalen Variablen.
Hinweis Lokale Variablen werden innerhalb eines Anweisungsblocks definiert und sind nur in ihm (und in darin eingeschlossenen Blcken) zugnglich.

Und genau deshalb ist a in der letzten Zeile unseres Programms nicht mehr existent, weil while auerhalb des Anweisungsblocks liegt.

102

Funktionen

2.3

In diesem Fall haben wir die Variable zu spt definiert, denn damit sie hinter dem Schleifenblock noch existiert, muss sie vor dem Schleifenblock definiert werden. Trotzdem hat die spte Definition von Variablen ihre Vorteile. Zur Demonstration wird der Fehler im vorigen Codeabschnitt behoben und eine Ergnzung vorgenommen:
cout << "Wollen Sie eine Zahl eingeben? (1=ja, 0=nein):"; int x; cin >> x; if(x==1) { int a; do { cout << "Zahl eingeben [1,100]:"; cin >> a; } while(a<1 || a>100); cout << "Eingegebene Zahl: " << a << endl; }
Listing 2.27 Sinnvoller Einsatz einer spten Definition

Nun wird der Anwender vorher gefragt, ob er berhaupt eine Zahl eingeben soll. Die Variable a wurde jetzt innerhalb des if-Blocks, aber auerhalb des Schleifenblocks definiert. Aber wir htten ja auch gleich Ngel mit Kpfen machen und a noch vor dem if-Block definieren knnen. Htten wir, aber die jetzige Variante ist performanter. Und zwar aus einem einfachen Grund: Wozu wird die Variable a bentigt, wenn der Anwender gar keine Zahl eingeben mchte? Fr nichts. Warum sie dann also definieren? Durch die Definition im if-Block wird a nur dann angelegt, wenn der Anwender auch wirklich eine Zahl eingeben mchte. Wenn nicht, bleibt dem Rechner das Reservieren des fr a ntigen Speichers erspart. Das Programm luft schneller und bentigt weniger Speicher. Zugegeben, bei den heutigen Rechnern fllt diese Einsparung so gut wie berhaupt nicht ins Gewicht, aber in einer anderen Situation knnte solch ein Codeabschnitt in einer sich millionenfach wiederholenden Schleife stecken, und, glauben Sie mir, dann macht sich die Optimierung auf jedem Rechner bemerkbar.
Lokale Variablen und for

Eine Besonderheit haben die lokalen Variablen im Zusammenhang mit for. Nehmen wir folgende Schleife:

103

Kontrollstrukturen

for(int x=0; x<5; ++x) cout << "-"; cout << endl; cout << "Anzahl an Strichen:" << x << endl;

Was wird ausgegeben? Oder wird berhaupt etwas ausgegeben? Alles luft auf die Frage hinaus, in welchem Anweisungsblock das x definiert wurde, im Anweisungsblock der Schleife oder im die Schleife umschlieenden Anweisungsblock? Auf diese Frage gibt es zwei Antworten. Nach der neuen Norm ist eine im for-Kopf definierte Variable eine lokale Variable des Schleifenblocks. Demnach ist sie nach dem Verlassen des Schleifenblocks nicht mehr existent und kann daher hinter der Schleife auch nicht ausgegeben werden. Nach der alten Norm gehrt x zu dem die Schleife umschlieenden Block und kann daher hinter der Schleife ausgegeben werden. Da die Schleife solange luft, wie x kleiner 5 ist, bricht sie bei x gleich 5 ab. Auf dem Bildschirm erscheint 5. Visual C++ 2010 hlt sich an die neue Norm. Das obere Beispiel wird daher nicht einwandfrei kompiliert.

2.3.2

Funktionen mit Parametern

Kommen wir zu den Funktionen zurck. Funktionsparameter sind immer dann ntzlich, wenn die Funktion zur Erledigung ihrer Arbeit noch zustzliche Informationen braucht. Wenden wir uns dazu noch einmal der Ausgabe von Hopphopphopp zu. Zwischen den beiden Ausgaben wurde ein aus Bindestrichen bestehender horizontaler Strich ausgegeben. Zwecks Wiederverwendung wre es doch schn, eine Funktion strich zu besitzen, die einen Strich variabler Lnge ausgibt. Diese variable Lnge soll ber einen Funktionsparameter mitgeteilt werden. Ein Funktionsparameter wird innerhalb der runden Klammern wie eine typische Variable definiert, mit Typ und Namen:
void strich(int x) { for(int a=1; a<=x; ++a) cout << "-"; cout << endl; }
Listing 2.28 Die Funktion strich

104

Funktionen

2.3

Wenn nun ein Strich aus 15 Bindestrichen gezeichnet werden soll, dann sieht der Aufruf so aus:
strich(15);

Es ist auch mglich, anstelle der Konstanten eine Variable anzugeben:


cout << "Wie lang soll der Strich sein:"; int s; cin >> s; strich(s);

Hinweis Parameter sind lokale Variablen der Funktion und nur in ihr gltig.

Ein Funktionsparameter ist technisch nichts anderes als eine lokale Variable der Funktion. Das bedeutet, er kann innerhalb der Funktion wie eine herkmmliche Variable verwendet werden. Mit diesem Wissen im Hinterkopf knnen wir die Funktion strich so optimieren, dass sie ohne die Variable a auskommt:
void strich(int x) { for(; x>=1; --x) cout << "-"; cout << endl; }
Listing 2.29 Die optimierte Funktion strich

Der Funktionsparameter x wird in der Schleife heruntergezhlt, solange er noch grer gleich 1 ist. Da x in der Schleife nicht initialisiert werden muss denn x wurde bereits automatisch mit dem an die Funktion bergebenen Wert initialisiert , bleibt der Initialisierungsteil leer. Wir bleiben noch kurz bei den Parametern und ihrer Bedeutung als lokale Variablen und schauen uns folgenden Code an:
void aendern(int x) { x=50; } int main() { int x=20; cout << x << endl;

105

Kontrollstrukturen

aendern(x); cout << x << endl; }

Welche Werte werden ausgegeben? Die Versuchung ist gro, bei der zweiten Ausgabe auf 50 zu tippen. Aber bleiben Sie standhaft. Wir haben gelernt, dass Funktionsparameter lokale Variablen sind. Das gilt demnach auch fr das x in aendern. Das x in main ist ebenfalls eine lokale Variable. Und lokale Variablen sind nur innerhalb des sie umschlieenden Anweisungsblocks gltig. Fr Funktionsparameter ist das der Funktionsanweisungsblock, auch wenn das optisch nicht zu erkennen ist. Wenn also das x in aendern nur in aendern gltig ist und das x in main nur in main existiert, dann muss es sich bei diesen beiden Variablen um unterschiedliche Variablen handeln. Jede Funktion besitzt ihr eigenes x. Von daher hat eine Zuweisung von 50 in aendern nur Auswirkung auf das x in aendern, nicht aber auf das x in main.

2.3.3

Mehrere Parameter

Eine Funktion kann beliebig viele Parameter besitzen. Zur Demonstration folgt eine Funktion rechteck, die ein aus Doppelkreuzen gezeichnetes Rechteck ausgibt:
void rechteck(int x, int y) { for(int a=1; a<=y; ++a) { for(int b=1; b<=x; ++b) cout << "#"; cout << endl; } }
Listing 2.30 Die Funktion rechteck

Hinweis Fr jeden Funktionsparameter muss der Datentyp explizit angegeben werden.

Die uere Schleife kann brigens nicht auf die geschweiften Klammern verzichten, weil sie zwei Anweisungen enthlt: die innere Schleife und die Ausgabe von endl.

106

Funktionen

2.3

2.3.4

Rckgabewerte

Die Funktion hoppausgabe soll derart verbessert werden, dass die Anzahl der ausgegebenen Hopps ber einen Funktionsparameter variabel gehalten wird:
void hoppausgabe(int hopps) { if(hopps>=1) { cout << "Hopp"; for(; hopps>=2; --hopps) cout << "hopp"; cout << endl; } }
Listing 2.31 hoppausgabe mit variabler Anzahl an Hopps

Die Funktion wird wegen der if-Anweisung erst dann richtig aktiv, wenn die Anzahl der auszugebenden Hopps grer gleich 1 ist. Die Ausgabe des ersten Hopps ist wegen des groen H ein Sonderfall, deshalb wird sie auerhalb der Schleife gettigt. Innerhalb der Schleife muss nun ein Hopp weniger ausgegeben werden, daher die Laufbedingung hopps>=2. Damit uns diese Funktion zur Demonstration des Rckgabewertes auch ein sinnvolles Ergebnis liefern kann, soll sie bestimmen, aus wie vielen Zeichen das von ihr erzeugte Hopphopphopp besteht. Um einen Wert zurckzugeben, wird der Befehl return verwendet. Der Befehl sorgt fr eine sofortige Beendigung der Funktion und liefert den hinter ihm angegebenen Wert zurck. Damit es der Funktion aber berhaupt erst erlaubt ist, einen Wert zurckzugeben, muss das void im Funktionskopf durch den Datentyp des Rckgabewerts ersetzt werden:
01 02 03 04 05 06 07 08 09 10 11 12 13 int hoppausgabe(int hopps) { int anz=0; if(hopps>=1) { cout << "Hopp"; anz=4; for(; hopps>=2; --hopps) { cout << "hopp"; anz+=4; } cout << endl; } return(anz); }
Die Funktion hoppausgabe mit Rckgabewert

Listing 2.32

107

Kontrollstrukturen

Die Variable anz ist vom Typ int. Damit ihr Wert am Ende der Funktion zurckgegeben werden kann, muss der Rckgabetyp der Funktion ebenfalls int sein (und nicht mehr void). Bei einer Funktion mit Rckgabewert steht der Funktionsaufruf fr den zurckgegebenen Wert. Um den Rckgabewert beispielsweise in einer Variablen zu speichern, muss der Funktionsaufruf einfach einer Variablen zugewiesen werden:
int c=hoppausgabe(3); cout << "Ausgegebene Zeichen:" << c << endl;

Der Funktionsaufruf kann berall dort stehen, wo auch eine Konstante erlaubt ist. Im Gegensatz zu anderen Sprachen muss das return nicht zwangslufig am Ende der Funktion stehen und darf auch mehrfach vorkommen:
int maximum(int x, int y) { if(x>y) return(x); else return(y); }
Listing 2.33 Die Funktion maximum

Achtung Bei einer Funktion, die einen Wert zurckgibt, muss jeder Programmpfad einen Wert zurckliefern.

Ein Beispiel:
int fehlerhaft(int x) { if(x>=10) return(1); }
Listing 2.34 Fehlerhafter Einsatz von return

Die obere Funktion liefert nur dann einen Wert zurck, wenn x grer gleich 10 ist. Obwohl der Compiler bei dieser Funktion nur eine Warnung ausgibt, sind die Auswirkungen auf den Rckgabewert bei x kleiner 10 fatal, weil dann ein unvorhersehbarer Wert zurckgegeben wird. Ein solches Funktionsverhalten muss unter allen Umstnden vermieden werden.

108

Funktionen

2.3

Tipp Eine Funktion ohne Rckgabewert kann ebenfalls an jeder beliebigen Stelle mit return beendet werden. Hinter return darf dann nur kein Wert stehen.

2.3.5

Standardwerte fr Parameter

C++ erlaubt es, fr Funktionsparameter Standardwerte zu definieren. Im folgenden Beispiel bekommt die Funktion hoppausgabe einen Standardwert:
int hoppausgabe(int hopps=3) { // Anweisungen der Funktion }
Listing 2.35 Die Funktion hoppausgabe mit Standardwerten

Sollte die Funktion jetzt ohne Angabe eines Parameters aufgerufen werden, also so
hoppausgabe();

dann wird der Funktionsparameter hopps automatisch mit dem Wert 3 initialisiert. Bei der Verwendung von Standardwerten mssen zwei Regeln beachtet werden. Wenn eine Funktion Standardwerte besitzt, dann muss der letzte Parameter auf jeden Fall einen Standardwert haben, und es drfen zwischen Parametern mit Standardwert keine Parameter ohne Standardwert liegen. Das heit, die Vergabe der Standardparameter beginnt mit dem letzten Funktionsparameter und hangelt sich dann bis maximal zum ersten durch.
Achtung Durch den Einsatz von Standardwerten drfen keine Zweideutigkeiten mit berladungen entstehen.

Betrachten wir dazu die beiden folgenden Funktionen, deren Anweisungsblcke leer bleiben:
void test(int x) {} void test(int x, int y=5) {}

Wrde test mit nur einem Parameter aufgerufen (test(8);), dann wsste der Compiler nicht, ob er die einparametrige Funktion oder die zweipara-

109

Kontrollstrukturen

metrige Funktion mit Standardwert nehmen soll. Deshalb wrde das obere Beispiel nicht kompiliert.
Hinweis Als Vorgriff auf den nchsten Abschnitt: Standardwerte werden nur bei der Funktionsdeklaration angegeben, nicht bei der Funktionsdefinition.

2.3.6

Funktionsdeklarationen

Um die Bedeutung von Funktionsdeklarationen nachvollziehen zu knnen, wollen wir die Funktionsdefinition von maximum hinter die main-Funktion setzen:
int main() { cout << maximum(10,20) << endl; } int maximum(int x, int y) { if(x>y) return(x); else return(y); }
Listing 2.36 Funktionsdefinition hinter Funktionsaufruf

Durch diese Umpositionierung wird das Programm nicht mehr korrekt kompiliert: Der Compiler kompiliert eine Datei ordentlich von oben nach unten. An der Stelle des Aufrufs von maximum ist dem Compiler die Funktion aber noch nicht bekannt. Allerdings reicht es dem Compiler, wenn die Funktion zum Zeitpunkt des Aufrufs nur deklariert ist. Die Deklaration einer Funktion gibt lediglich Auskunft darber, von welchem Typ eventuelle Funktionsparameter oder Rckgabewerte sind. Fr maximum sieht die Funktionsdeklaration so aus:
int maximum(int, int);

Fr den Fall, dass die Namen der Funktionsparameter aussagekrftig gewhlt wurden, bietet sich ihre Angabe ebenfalls an:
int maximum(int x, int y);

Im Falle von maximum ist die Aussagekraft der Parameternamen eher zweifelhaft, aber dafr sagt der Funktionsname eigentlich alles.

110

Module

2.4

Tipp Dem Compiler reicht fr den Aufruf einer Funktion deren Deklaration.

Die hnlichkeit der Funktionsdeklaration mit dem Funktionskopf ist frappierend. Eigentlich ist die Funktionsdeklaration nichts anderes, als der mit Semikolon abgeschlossene Funktionskopf ohne Anweisungsblock. Eine Frage wird jetzt bestimmt aufkommen: Warum sollte die Funktionsdefinition hinter den Funktionsaufruf gesetzt werden, nur um dann vor dem Aufruf eine Funktionsdeklaration zu platzieren? Htte dann nicht gleich die Funktionsdefinition vor dem Aufruf stehen knnen? Die Antwort gibt der nchste Abschnitt.

2.4

Module

Stellen Sie sich vor, Sie htten ein Programm mit 500 Funktionen geschrieben, die, wie bisher, alle in einer Datei stehen. Wenn Sie nun eine klitzekleine nderung an einer dieser Funktionen vornehmen, muss die gesamte Datei mit allen 500 Funktionen neu kompiliert werden. Wre es nicht besser, wenn nur die eine genderte Funktion neu kompiliert wrde? Ein C++-Compiler kompiliert grundstzlich immer dateiweise. Wenn etwas von einer nderung nicht betroffen sein soll, dann muss es in einer anderen Datei stehen. Je feiner diese Aufteilung ist, desto weniger muss unntig kompiliert werden.

2.4.1

Definition auslagern

Wir wollen daher exemplarisch die maximum-Funktion in einer eigenen Datei unterbringen. Dazu muss im aktuellen Projekt eine neue Datei mit dem Namen funktionen.cpp angelegt werden. Der Name der Datei ist fr den Compiler unwesentlich, sollte aber aussagekrftig gewhlt werden, um schnell ihren Inhalt einschtzen zu knnen (wie eine neue Datei im aktuellen Projekt angelegt wird, erklrt Abschnitt 1.1.1, C++-Datei hinzufgen). In diese Datei wird nun die Funktionsdefinition verschoben. Die Deklaration von maximum sollte an ihrem alten Platz bleiben. Eine nderung an einer der beiden Dateien hat nun keine Neukompilation der anderen Datei mehr zur Folge. Die Deklaration muss auf jeden Fall erhalten bleiben, denn der Compiler kompiliert jede Datei fr sich getrennt. Wenn er die Datei mit der main-Funk-

111

Kontrollstrukturen

tion kompiliert, dann wei er nicht, ob er funktionen.cpp bereits kompiliert hat oder berhaupt kompilieren wird. Die Deklaration ist unser Versprechen an den Compiler, dass die Funktion in einer anderen Datei definiert ist. Ob dieses Versprechen eingehalten wird, stellt nach der Kompilation der Linker fest, der alle einzeln kompilierten Dateien zu einem ausfhrbaren Programm zusammenbindet.

2.4.2

Deklaration auslagern

Eine Deklaration immer dorthin zu schreiben, wo sie bentigt wird, ist nicht sehr effizient, denn bei einem komplexeren Projekt werden Funktionen in vielen Dateien bentigt, die dann alle ihre eigene Deklaration beinhalten mssten. Aus diesem Grund wird auch die Deklaration ausgelagert und berall dort eingebunden, wo sie gebraucht wird. Funktionsdeklarationen werden in Header-Dateien mit der Endung .h gespeichert. Um eine einfachere Zuordnung herzustellen, sollte die Header-Datei so benannt werden wie die dazugehrige cpp-Datei. Diese Namensgleichheit ist nicht vorgeschrieben, sondern nur eine stillschweigende Vereinbarung der C++-Programmierer, um die Dateistruktur eines Projekts besser erfassen zu knnen. Abbildung 2.11 fasst die aktuelle Projektaufteilung noch einmal zusammen.
funktionen.cpp int maximum(int x, int y) { if(x>y) return(x); else return(y); } funktionen.h int maximum(int, int); main.cpp #include <iostream> #include "funktionen.h" using namespace std; int main() { cout << maximum(10,20) << endl; }

Abbildung 2.11

Die Aufteilung des Projekts

Hinweis Eine syntaktische Besonderheit ist an der Einbindung der eigenen Header-Datei zu erkennen: Der Einsatz spitzer Klammern < > hinter include bewirkt eine Suche der Datei in den Standardverzeichnissen des Compilers. Die Verwendung von Anfhrungszeichen lsst den Compiler im aktuellen Projektverzeichnis suchen.

Im Gegensatz zu iostream, wo der Name in spitzen Klammern steht, findet sich die eigene Header-Datei in Anfhrungszeichen wieder. Spitze Klammern teilen dem Prprozessor mit, die einzubindende Datei in den Standardver-

112

Module

2.4

zeichnissen des Compilers zu suchen. Aus diesem Grund werden HeaderDateien der Standardbibliothek immer mit spitzen Klammern eingebunden. Die Anfhrungszeichen besagen, dass die Datei ausgehend vom aktuellen Projektverzeichnis gesucht werden soll. Es sind auch relative Ordnerangaben mglich:
#include "meineincludes/datei.h";

Die obere Zeile veranlasst den Prprozessor, die Datei datei.h im Ordner meineincludes zu suchen, der sich im Projektverzeichnis befindet.
#include "../datei.h";

Diese Anweisung sucht nach der Datei in dem dem Projektverzeichnis bergeordneten Verzeichnis.

2.4.3

Kompilationsvorgang

Die aktuelle Projektkonfiguration erlaubt es uns, einige grundlegende Mechanismen der Kompilation eines Projektes zu betrachten, die so auch bei den komplexesten Projekten anzutreffen ist. Als Grundlage dieser Betrachtung soll Abbildung 2.12 dienen.
Projektverwaltung Prprozessor
funktionen.h iostream

Prprozessor
funktionen.cpp

inclu in

de clude

main.cpp

Compiler

Compiler

main.obj

funktionen.obj

Linker

Projektname.exe

Abbildung 2.12

Der Kompilationsvorgang im Detail

113

Kontrollstrukturen

Der steuernde Kern ist die Projektverwaltung. Sie prft, welche Dateien wegen nderung neu kompiliert werden mssen. Dabei werden auch Abhngigkeiten bercksichtigt. In unserem Projekt muss beispielsweise die Datei main.cpp neu kompiliert werden, wenn funktionen.h gendert wurde, denn sie wird von main.cpp eingebunden. Bevor die Quellcode-Dateien (.cpp) dem Compiler zum Fra vorgeworfen werden, durchluft sie der Prprozessor und sucht nach Anweisungen fr sich. Das Einbinden der Header-Dateien (.h) geschieht zum Beispiel durch den Prprozessor.
Hinweis Der Compiler kompiliert nur Quellcode-Dateien.

Das vom Prprozessor erzeugte Zwischenergebnis wird nicht in einer Datei zwischengespeichert, sondern direkt zum Compiler durchgereicht. Die Abbildung zeigt sehr schn, dass der Compiler und damit auch der Prprozessor nur Quellcode-Dateien bearbeitet. Header-Dateien werden vom Compiler nur bercksichtigt, wenn sie zuvor mit #include in eine QuellcodeDatei eingebunden wurden. Wichtig ist auch zu wissen, dass der Compiler jede Quellcode-Datei isoliert kompiliert. Whrend der Kompilation eines Projekts wei er nicht, welche Dateien er bereits kompiliert hat und welche er noch kompilieren wird. Er kennt nur die Datei, die er gerade kompiliert. Daher kann der Compiler auch nicht feststellen, ob fr eine eingebundene Funktionsdeklaration eine passende Definition tatschlich in einer anderen Datei steht. Der Compiler speichert die kompilierten Quellcodedateien in sogenannten Objektdateien, die bei Visual C++ die Endung .obj haben, bei anderen Compilern aber durchaus andere Endungen (wie .o) besitzen knnen. Funktionsaufrufe bestehen in diesen Dateien nur in Form von Verweisen, weil die tatschlichen Aufrufziele in den meisten Fllen nicht ausgemacht werden knnen (da sie hufig in anderen Dateien stehen). Die vom Compiler erzeugten Objektdateien werden nun alle zusammen dem Linker bergeben. Er ist es, der dadurch den kompletten berblick ber alle Funktionsaufrufe und alle kompilierten Funktionen besitzt und die Aufrufe den Funktionen zuordnen kann. Er merkt auch, wenn eine Funktion deklariert, aber nicht definiert wurde. Die meisten der vom Linker gemeldeten

114

Module

2.4

Fehler beziehen sich auf Funktions- oder Objektverweise, zu denen keine Funktion oder kein Objekt passt. Ist das Zusammenfgen fehlerfrei vonstatten gegangen, dann speichert der Linker das Ergebnis als ausfhrbare Datei (.exe) mit dem Namen des Projekts.

2.4.4

Mehrfachdeklarationen vermeiden

Es ist durchaus mglich, dass innerhalb einer Header-Datei mittels #include eine andere Header-Datei eingebunden wird. In der Praxis kommt es daher hufiger vor, dass eine Quellcode-Datei wegen einer Verkettung von #include eine Header-Datei mehrfach einbindet und Funktionen oder Klassen dadurch mehrfach deklariert werden. Dies ist in C++ nicht erlaubt, weswegen Schutzmanahmen ergriffen werden mssen, die dies verhindern. Wenn Sie in Ihrem Projekt in der Zeile
#include <iostream>

mit der rechten Maustaste auf iostream klicken und dann ber Dokument <iostream> ffnen die Datei ffnen, dann sehen Sie als erste Prprozessordirektive
#pragma once

Der Befehl #pragma dient dazu, Befehle anzusprechen, die an die Entwicklungsumgebung gebunden sind. Diese Befehle sind daher von Natur aus nicht unter jeder Entwicklungsumgebung verwendbar.
Tipp Mit
#pragma once

als erster Anweisung in einer Header-Datei wird eine mgliche Mehrfachdeklaration verhindert.

Die Direktive #pragma once ist ein Befehl speziell von Visual C++ und teilt dem Prprozessor mit, dass er die Header-Datei, die diese Direktive beinhaltet, nur einmal pro Quellcode-Datei einzubinden hat, auch wenn die Datei durch Verkettung eigentlich mehrfach eingebunden werden msste. Soll das Programm auf ein anderes System portiert werden, auf dem #pragma once nicht existiert, dann muss ein anderer, standardkonformer Weg gegangen werden. Dieser Weg macht sich die Prprozessorfhigkeit der bedingten Kompilierung zunutze. Sehen wir uns dazu folgendes Beispiel an:

115

Kontrollstrukturen

#ifndef FUNKTIONEN_H #define FUNKTIONEN_H // Hier steht der Inhalt der Datei #endif
Listing 2.37 Bedingte Kompilation ber den Prprozessor

Der erste Befehl #ifndef besagt, dass der Programmcode bis zum #endif nur dann an den Compiler weitergeleitet wird, wenn die Konstante hinter #ifndef nicht definiert ist. Wurde sichergestellt, dass die verwendete Konstante nur einmal im Projekt vorkommt, dann ist dies beim ersten Einbinden der Datei der Fall. Die Einzigartigkeit der verwendeten Konstante ist im Normalfall gewhrleistet, wenn als Bezeichner der Dateiname verwendet wird. Aus syntaktischen Grnden der Name einer Prprozessorkonstante darf keine Punkte enthalten muss der Punkt durch einen Unterstrich ersetzt werden. Direkt hinter #ifndef wird nun die besagte Konstante definiert. Sollte diese Datei nochmals eingebunden werden, dann ist beim zweiten Mal die Konstante definiert und der Bereich zwischen #ifndef und #endif wird nicht an den Compiler weitergeleitet. Ein mehrmaliges Deklarieren wird so vermieden. Dieses bedingte Kompilieren kann natrlich auch dazu eingesetzt werden, verschiedene Versionen des Programms zu erstellen. Beispielsweise knnten Ausgaben, die nur bei der Programmentwicklung gettigt werden sollen, in einen entsprechenden if-Block des Prprozessors gepackt werden:
#ifdef TESTVERSION cout << "Programm luft noch" << endl; #endif

Die Direktive #ifdef ist das Gegenstck zu #ifndef; der ihr folgende Code wird dann an den Compiler weitergeleitet, wenn die Konstante definiert ist.

116

In diesem Kapitel besprechen wir verschiedene Mglichkeiten, wie mehrere Werte gespeichert werden knnen.

Komplexere Datentypen

Die bisherigen elementaren Datentypen knnen immer nur jeweils einen Wert speichern. Wenn Sie mit dieser Technik ein Programm schreiben mssten, welches 100 Werte einliest, aufaddiert und daraus einen Durchschnittswert berechnet, dann wren Sie eine Weile beschftigt. Aus diesem und vielen anderen Grnden gibt es in C++ komplexere Datentypen, die aus den elementaren Datentypen zusammengesetzt sind.

3.1

Arrays

Sie erinnern sich vielleicht noch an die Indexschreibweise aus der Mathematik. Da hatte man es mit x2 oder a6 zu tun und konnte auf diese Weise mit einem Buchstaben verschiedene Werte abdecken, die durch den Index eindeutig benannt waren. Es ging sogar so weit, dass der Index selbst wieder eine Variable sein konnte, wie zum Beispiel xn oder am-1. Dieser Schreibweise kann eine gewisse Eleganz nicht abgesprochen werden. Grund genug, so etwas auch in C++ haben zu wollen. Und genau das gibt es in Form der Arrays. Rekapitulieren wir kurz die Definition einer Variablen eines beliebigen Typs, im folgenden exemplarisch int:
int x;

Unter den Namen x ist jetzt ein einziger Wert ansprechbar. Die Syntax eines Arrays lautet:
Datentyp Arrayname[Elementanzahl];

Wenn wir mehrere Werte haben wollen, dann verwenden wir bei der Definition den Indexoperator, der in C++ durch eckige Klammern dargestellt wird, und schreiben die Anzahl der gewnschten Werte hinein:

117

Komplexere Datentypen

int f[10];

Wir haben damit ein Array erstellt, dessen einzelne Werte wiederum ber den Indexoperator angesprochen werden. Dabei gibt es eine wichtige Regel zu beachten:
Achtung Das erste Element eines Arrays hat immer den Index 0.

Das erzeugte Array hat damit den in Abbildung 3.1 dargestellten Aufbau:
f 0 1 2 3 4 5 6 7 8 9

Abbildung 3.1

Der interne Aufbau des Arrays

Soll das sechste Element den Wert 15 zugewiesen bekommen, she die entsprechende Anweisung so aus:
f[5] = 15;

Es ist vielleicht gewhnungsbedrftig, das sechste Element mit Index 5 anzusprechen, aber eine direkte Konsequenz aus der Tatsache, dass das erste Element den Index 0 hat. Achten Sie darauf, keinen Index auerhalb des gltigen Bereichs zu verwenden, da dies whrend der Kompilation nicht erkannt wird und whrend der Laufzeit unangenehme Probleme mit sich bringen kann, die nicht immer direkt der tatschlichen Fehlerquelle zuzuordnen sind.

3.1.1

Variabler Index

Wie in der Mathematik erlauben die Arrays, als Index eine ganzzahlige Variable zu verwenden:
int f[10]; for(int i=0; i<10; ++i) f[i]=0;
Listing 3.1 Feldelemente mit 0 initialisieren

Als komplettes Beispiel soll hier die zu Beginn des Kapitels angesprochene Durchschnittsberechnung demonstriert werden, allerdings nur fr zehn Werte, um das Testen des Programms nicht allzu sehr in die Lnge zu ziehen:

118

Arrays

3.1

#include <iostream> using namespace std; int main() { const int ELEM_ANZ=10; double f[ELEM_ANZ]; double durchschnitt=0.0; for(int i=0; i<ELEM_ANZ; ++i) { cout << "Bitte Wert " << i+1 << " angeben:"; cin >> f[i]; durchschnitt+=f[i]; } durchschnitt/=ELEM_ANZ; cout << "Durchschnitt: " << durchschnitt << endl; }
Listing 3.2 Durchschnittsberechnung

Um das Programm einfacher an eine andere Anzahl von Werten anpassen zu knnen, wurde die Wertanzahl innerhalb des Programms als Konstante definiert. Das ndern der Anzahl betrifft nun nur noch zentral eine Stelle im Programm, wodurch die Fehleranflligkeit dramatisch reduziert wird. Denn je mehr Stellen von einer nderung betroffen sind, desto grer ist die Wahrscheinlichkeit, eine zu bersehen.

3.1.2

Mehrdimensionale Arrays

Mithilfe des Indexoperators ist es auf einfache Weise mglich, mehrdimensionale Felder zu definieren. Fr jede Dimension wird bei der Definition ein eigener Index angegeben:
int g[4][20];

Analog dazu mssen auch bei der Bestimmung eines Elements beide Indexoperatoren angegeben werden:
g[0][0] = 4;

Diese Syntax ist beliebig auf weitere Dimensionen erweiterbar. Dabei ist zu bercksichtigen, dass bei der Definition der Speicher fr alle Elemente auch

119

Komplexere Datentypen

der unbelegten reserviert wird und der Bedarf bei mehreren Dimensionen schnell explodieren kann.

3.1.3

Einschrnkungen von Arrays

Im Vergleich zu Datentypen wie den Vektoren besitzen Arrays einige Einschrnkungen: Die Gre von Arrays ist fest. Ein Array kann keinem anderen Array direkt zugewiesen werden. Es gibt keine direkte Mglichkeit, die Anzahl der Elemente in einem Array zu bestimmen. Arrays knnen nicht ohne besondere Vorkehrungen als Funktionsparameter verwendet werden.

3.2

C-Strings

Die Arrays setzen wir nun ein, um C-Strings zu erstellen. Anschlieend schauen wir uns den Klassentyp string an.

3.2.1

char

Ein Zeichen kann in C++ relativ einfach gespeichert werden. Wir bentigen dazu den bereits in Abschnitt 1.8, Datentypen, kurz vorgestellten Datentyp char:
char c;

Im Gegensatz zu Zeichenketten, die in doppelten Anfhrungszeichen stehen, werden einzelne Zeichen in einfache Anfhrungszeichen gesetzt:
c = 'X';

Der Datentyp char wird von cin und cout untersttzt:


cout << "Zeichen eingeben:"; char c; cin >> c; cout << "Zeichen:" << c << endl;

Intern ist char als ganzzahliger Wert organisiert, eine Zuweisung an einen anderen numerischen Datentyp ist daher mglich:

120

C-Strings

3.2

char c='A'; int i=c; double d=c; cout << i << endl;

Die Ausgabe am Ende des Codefragments liefert den fr das Zeichen A stehenden Wert. Wegen dieser Doppeldeutigkeit von char sind auch Rechenoperatoren anwendbar:
char x='A'+4; cout << x << endl;

Auf dem Bildschirm erscheint ein E.

3.2.2

cctype-Funktionen

Eine wichtige Bibliothek im Zusammenhang mit Zeichen ist cctype. Es lohnt ein Blick hinein, den Tabelle 3.1 gewhrt.
Funktion
int isalnum(int c);

Beschreibung Liefert einen wahren Wert, wenn es sich bei dem Zeichen um einen Buchstaben oder eine Ziffer handelt. Liefert einen wahren Wert, wenn es sich bei dem Zeichen um einen Buchstaben handelt. Liefert einen wahren Wert, wenn es sich bei dem Zeichen um eine Ziffer handelt. Liefert einen wahren Wert, wenn es sich bei dem Zeichen um einen Kleinbuchstaben handelt. Liefert einen wahren Wert, wenn es sich bei dem Zeichen um ein Leerzeichen, FF (Form Feed), NL (New Line), CR (Carriage Return), HT (Horizontal Tab) oder VT (Vertical Tab) handelt. Liefert einen wahren Wert, wenn es sich bei dem Zeichen um einen Grobuchstaben handelt. Liefert einen Kleinbuchstaben zurck, wenn das bergebene Zeichen ein Grobuchstabe ist. Andernfalls wird das bergebene Zeichen zurckgegeben. Liefert einen Grobuchstaben zurck, wenn das bergebene Zeichen ein Kleinbuchstabe ist. Andernfalls wird das bergebene Zeichen zurckgegeben.

int isalpha(int c);

int isdigit(int c);

int islower(int c);

int isspace(int c);

int isupper(int c);

int tolower(int c);

int toupper(int c);

Tabelle 3.1 Die Funktionen von cctype

121

Komplexere Datentypen

Dabei ist fr die oberen Funktionen die Formulierung einen wahren Wert nicht als das Boolesche true zu verstehen, sondern als irgendeine Ganzzahl ungleich 0. Das nachstehende Beispiel zeigt exemplarisch den Einsatz von isalpha:
cout << "Zeichen:"; char z; cin >> z; if(isalpha(z)) cout << "Ein Buchstabe" << endl; else cout << "Kein Buchstabe" << endl;
Listing 3.3 Eingabe auf Buchstaben prfen

Es ist wichtig, zu bercksichtigen, dass die cctype-Funktionen nur auf den ASCII-Zeichensatz anwendbar sind. Umlaute beispielsweise sind mit den Funktionen nicht zu verarbeiten. Eine Lokalisierung wie man die Anpassung des Programms an eine spezielle Menschensprache nennt ist in reinem C++ etwas aufwndiger und soll hier nicht besprochen werden, da .NET dies bereits beherrscht.

3.2.3

Aufbau von C-Strings

Eine Zeichenkette ist eigentlich nichts anderes als eine Aneinanderreihung von Zeichen. Diesen Ansatz verfolgen die C-Strings, die ihren Namen der Tatsache verdanken, bereits unter C eingesetzt worden zu sein. Zeichen knnen mit der gleichen Technik aneinandergereiht werden wie andere Datentypen, nmlich mit einem Array:
char s[20];

Das Array knnen Sie nun ber den Indexoperator mit Zeichen bestcken:
s[0]='C'; s[1]='+'; s[2]='+';

Die Ausgabe von C-Strings wird von cout untersttzt:


cout << s << endl;

Allerdings wird die Ausgabe etwas berraschen, denn auer den drei von uns abgelegten Zeichen werden wahrscheinlich noch andere, ungewollte Zeichen erscheinen. Wir hatten ja bereits in Abschnitt 3.1, Arrays, erfahren,

122

C-Strings

3.2

dass es keine direkte Mglichkeit zur Bestimmung der Elementanzahl im Array gibt. Es muss daher eine knstliche Kennung geschaffen werden, anhand derer die Anzahl der wesentlichen Elemente im C-String ermittelt werden kann. Die Entwickler von C++ haben deshalb fr C-Strings eine Endekennung eingefhrt, die den Wert 0 hat. Wenn also ein C-String ausgegeben wird, dann werden so lange Zeichen ausgegeben, bis ein Zeichen mit dem Wert 0 (nicht dem Zeichen 0) erreicht wird. Wir brauchen demnach nur hinter unserem C-String eine Endekennung zu setzen
s[3]=0;

und schon wird die Ausgabe wie gewnscht ausgefhrt. Genau genommen, haben wir schon frher mit C-Strings gearbeitet, ohne es vielleicht zu wissen. Folgende Ausgabe zum Beispiel gibt einen C-String aus:
cout << "Visual" << endl;

Denn tatschlich ist die obere Zeichenkette Visual im Speicher abgelegt, wie in Abbildung 3.2 dargestellt.
'V' 0 'i' 1 's' 2 'u' 3 'a' 4 'l' 5 0 6

Abbildung 3.2

Interner Aufbau des C-Strings Visual

Achtung Ein C-String braucht wegen der notwendigen Endekennung immer ein Zeichen mehr an Speicherplatz als der zu speichernde String an Zeichen lang ist.

3.2.4

Texteingabe

Ein String kann auch mit cin eingelesen werden:


cout << "Text eingeben:"; char s[50]; cin >> s; cout << "Eingegebener Text: " << s << endl;
Listing 3.4 Einen String mit cin einlesen

123

Komplexere Datentypen

Dieser Ansatz hat leider ein kleines Manko. Wenn Sie einmal Text mit Leerzeichen eingeben, dann werden Sie sehen, dass nur der Text bis zum ersten Leerzeichen eingelesen wurde. Das liegt an der Eigenschaft von cin, auch ein Leerzeichen als Ende einer Eingabe zu betrachten. Abhilfe schafft die Methode getline von cin:
cin.getline(s,50);

Die Methode bekommt als Parameter das zu fllende char-Feld sowie dessen Gre bergeben. Dabei wird die Notwendigkeit der Endekennung bercksichtigt. Aus diesem Grund werden ber die obere Anweisung nur maximal 49 Zeichen eingelesen. Leider tritt jetzt ein anderer unangenehmer Effekt im Zusammenspiel mit der herkmmlichen Eingabe ber cin auf. Sehen wir uns dazu folgendes Beispiel an:
int i; cin >> i; cout << "Text eingeben:"; char s[50]; cin.getline(s,50); cout << "Eingegebener Text: " << s << endl;
Listing 3.5 Unerwnschte Zeichen im Eingabepuffer

Fhren Sie das obere Codefragment aus, und Sie werden sehen, dass die Eingabe des Textes einfach bersprungen wird. Der Grund ist einfach: Die Eingabe der Zahl wird vom Anwender mit der Eingabetaste beendet. Dieses Drcken der Eingabetaste wird ebenfalls als Zeichen reprsentiert und bleibt im Eingabepuffer. Die Texteingabe liest als Erstes die Eingabetaste, folgert, die Eingabe ist beendet und bricht ab. Um einen reibungslosen Ablauf zu erhalten, mssen wir die Eingabetaste aus dem Eingabepuffer entfernen. Und das geschieht, indem wir das Zeichen einfach ignorieren:
int i; cin >> i; cin.ignore(); cout << "Text eingeben:"; char s[50]; cin.getline(s,50); cout << "Eingegebener Text: " << s << endl;
Listing 3.6 Der Einsatz von ignore

Nun klappt es auch mit der Texteingabe.

124

C-Strings

3.2

3.2.5

cstring-Funktionen

Es ist lstig, einen C-String immer durch Beschreiben der einzelnen Zeichen zu erzeugen. Daher gibt es in der Header-Datei cstring eine Funktion strcpy, mit der Ein C-String in einen anderen kopiert werden kann:
char s[40]; strcpy(s,"Andre Willms"); cout << s << endl;

Dabei kann der zweite C-String eine konstante Zeichenkette oder auch ein char-Feld sein. Wichtig ist nur, dass das aufnehmende Feld gro genug ist. Die Header-Datei cstring enthlt noch andere ntzliche Funktionen, die in Tabelle 3.2 aufgefhrt werden.
Funktion
memchr memcmp memcpy memmove memset NULL strcat strchr strcmp strcoll strcpy strcspn strerror strlen strncat strncmp strncpy strpbrk strrchr strspn

Beschreibung Zeichen in einem Speicherblock suchen Speicherblcke vergleichen Speicherblcke kopieren sicheres Kopieren von Speicherblcken Speicherblock initalisieren der Nullzeiger C-String an einen anderen hngen Zeichen vom Stringanfang aus im C-String suchen zwei C-Strings vergleichen zwei C-Strings umgebungsabhngig vergleichen C-String kopieren Zeichen aus Menge suchen Liefert die Textbeschreibung zu einer Fehlernummer. Lnge des C-Strings ermitteln Teil eines C-Strings an einen anderen hngen Teile von zwei C-Strings vergleichen Teile eines C-Strings kopieren Zeichen aus Zeichenmenge suchen Zeichen vom Stringende aus im C-String suchen nicht vorkommende Zeichen suchen

Tabelle 3.2 Weitere Funktionen von cstring

125

Komplexere Datentypen

Funktion
strstr strxfrm

Beschreibung Prft, ob ein C-String in anderen C-Strings vorkommt. Umwandlung der ersten Zeichen eines Strings

Tabelle 3.2 Weitere Funktionen von cstring (Forts.)

Es folgt die detaillierte Beschreibung der Funktionen:


memchr im Speicherblock suchen
void* memchr(const void* feldadr, int z, size_t groesse);

Sucht in einem Speicherblock der Gre groesse, beginnend bei feldadr nach dem ersten Vorkommen des Zeichens z. Bei erfolgreicher Suche wird die Adresse des Zeichens zurckgegeben, andernfalls 0.
memcmp Speicherblcke vergleichen
int memcmp(const void* feldadr1, const void* feldadr2, size_t groesse);

Vergleicht die beiden ab feldadr1 und feldadr2 liegenden Speicherblcke der Gre groesse. Die Funktion liefert einen der folgenden Werte: <0, wenn das erste unterschiedliche Zeichen in Feld 1 kleiner ist als in Feld 2 0 bei Gleichheit der beiden Felder >0, wenn das erste unterschiedliche Zeichen in Feld 1 grer ist als in Feld 2
memcpy Speicherblock kopieren
void* memcpy(void* feldadr1, const void* feldadr2, size_t anzahl);

Kopiert anzahl Elemente des Speicherblocks beginnend an Adresse feldadr2 in den Speicherblock beginnend an Adresse feldadr1. Zurckgegeben wird feldadr1.
memmove Speicherblock sicher kopieren
void* memmove(void* feldadr1, const void* feldadr2, size_t anzahl);

Wie memcpy, nur dass bei einer teilweisen berlappung der Felder ein Verlorengehen von Informationen ausgeschlossen ist.

126

C-Strings

3.2

memset Speicherblock initialisieren


void* memset(void* feldadr, int z, size_t anzahl);

Fllt anzahl Elemente des an Adresse feldadr beginnenden Speicherblocks mit dem Zeichen z.
NULL der Nullzeiger

Eine Konstante, die als Nullzeiger benutzt werden kann und den Wert 0 oder (void*)0 hat.
strcat C-String anhngen
char* strcat(char* ziel, const char* quelle);

Kopiert den C-String quelle mitsamt der Endekennung hinter den C-String ziel, wobei die Endekennung von ziel mit dem ersten Zeichen von quelle berschrieben wird. Es wird ziel zurckgegeben.
strchr nach Zeichen suchen
char* strchr(char* s, int z); const char* strchr(const char* s, int z);

Liefert die Adresse des ersten in s vorkommenden Zeichens z, wobei die Endekennung ebenfalls bercksichtigt wird. Bei erfolgloser Suche wird 0 zurckgegeben.
strcmp C-Strings vergleichen
int strcmp(const char* s1, const char* s2);

Vergleicht die beiden C-Strings s1 und s2. Die Funktion liefert einen der folgenden Werte: <0, wenn das erste unterschiedliche Zeichen in s1 kleiner ist als in s2 0 bei Gleichheit der beiden C-Strings >0, wenn das erste unterschiedliche Zeichen in s1 grer ist als in s2
strcoll C-Strings umgebungsabhngig vergleichen
int strcoll(const char* s1, const char* s2);

Gleiche Funktionsweise wie strcmp, nur dass die Zeichen umgebungsabhngig verglichen werden.
strcpy C-String kopieren
char* strcpy(char* ziel, const char* quelle);

127

Komplexere Datentypen

Kopiert den C-String quelle mitsamt der Endekennung in den C-String ziel. Es wird ziel zurckgegeben.
strcspn nach Zeichen aus Menge suchen
size_t strcspn(const char* s, const char* suchstr);

Liefert den Index des ersten Zeichens von s, welches mit einem der Zeichen von suchstr bereinstimmt, wobei die Endekennung mit einbezogen wird.
strerror Fehlertext ermitteln
char* strerror(int fehlernummer);

Liefert die Adresse eines Strings, der den durch fehlernummer definierten Fehler als Text beschreibt.
strlen Lnge eines C-Strings ermitteln
size_t strlen(const char* s);

Liefert die Lnge des C-Strings s ohne Endekennung zurck.


strncat Teil eines C-Strings anhngen
char* strncat(char* ziel, const char* quelle, size_t n);

Kopiert die ersten n Zeichen des C-Strings quelle mit Hinzufgen einer Endekennung hinter den C-String ziel, wobei die Endekennung von ziel mit dem ersten Zeichen von quelle berschrieben wird. Es wird ziel zurckgegeben. Sollte quelle kleiner als n Zeichen sein, werden strlen(quelle) Zeichen plus Endekennung kopiert.
strncmp Teilstring vergleichen
int strncmp(const char* s1, const char* s2, size_t n);

Vergleicht die ersten n Zeichen der C-Strings s1 und s2 und liefert einen der folgenden Werte: <0, wenn das erste unterschiedliche Zeichen in s1 kleiner ist als in s2 0 bei Gleichheit der zu vergleichenden Stringteile >0, wenn das erste unterschiedliche Zeichen in s1 grer ist als in s2
strncpy Teilstring kopieren
char* strncpy(char* ziel, const char* quelle,size_t n);

128

C-Strings

3.2

Kopiert die ersten n Zeichen des C-Strings quelle in den C-String ziel. Es wird ziel zurckgegeben. Sollte quelle weniger als n Zeichen haben, werden strlen(quelle) Zeichen kopiert und die restlichen n-strlen(quelle) Zeichen mit 0 aufgefllt.
strpbrk nach Zeichen aus Zeichenmenge suchen
char* strpbrk(char* s, const char* suchstr); const char* strpbrk(const char* s, const char* suchstr);

Liefert die Adresse des ersten Zeichens von s, welches mit einem der Zeichen von suchstr bereinstimmt, wobei die Endekennung mit einbezogen wird. Falls kein Zeichen bereinstimmt, wird 0 zurckgegeben.
strrchr Zeichen vom Stringende aus suchen
char* strrchr(const char* s, int z);

Liefert die Adresse des letzten in s vorkommenden Zeichens z, wobei die Endekennung ebenfalls bercksichtigt wird. Bei erfolgloser Suche wird 0 zurckgegeben.
strspn nicht vorkommende Zeichen suchen
size_t strspn(const char* s, const char* suchstr);

Liefert den Index des ersten Zeichens von s, welches mit keinem der Zeichen von suchstr bereinstimmt, wobei die Endekennung nur fr s mit einbezogen wird.
strstr Teilstring suchen
char* strstr(char* s, const char* suchstr); const char* strstr(const char* s, const char* suchstr);

Liefert die Adresse der ersten Zeichenfolge von s, welche mit suchstr ohne Endekennung bereinstimmt. Bei keiner bereinstimmung wird 0 zurckgegeben.
strxfrm Umwandlung der ersten Zeichen eines Strings
size_t strxfrm(char *ziel, const char *quelle, size_t n);

Wandelt die ersten n Zeichen des Strings quelle nach einer umgebungsspezifischen Regel um und speichert sie in ziel. Die Umwandlung erfolgt so, dass strcoll(a1,a2) gleich mit strcmp(b1,b2) ist, wobei b1 das Ergebnis der Umwandlung von a1 und b2 das Ergebnis der Umwandlung von a2 ist.

129

Komplexere Datentypen

3.3

Strukturen

Mit den Arrays gibt es die Mglichkeit, mehrere Werte oder Daten ber einen Bezeichner in Kombination mit dem Indexoperator anzusprechen und zu verwalten. Arrays haben aber je nach Anwendungsbereich einen entscheidenden Nachteil: Die Elemente eines Arrays haben immer denselben Datentyp.

3.3.1

Definition einer Struktur

Hier schaffen Strukturen Abhilfe. Strukturen definieren einen neuen Datentyp, der aus Elementen anderer Datentypen zusammengesetzt ist. Eine Struktur steht blicherweise in einer eigenen Header-Datei (Endung .h), welche den Namen der Struktur trgt. Die Namensgleichheit von Datei und Struktur ist keine Pflicht wie in Java, sondern lediglich eine Konvention, um einen klaren Bezug zwischen der Datei und ihrem Inhalt herzustellen. Hier die Syntax einer Struktur:
struct Strukturname { // Definition der Strukturelemente } [Objektname];

Als Beispiel wollen wir eine Struktur Farbe entwerfen, die in der Lage ist, den Namen der Farbe sowie deren Rot-, Grn- und Blaukomponente zu speichern. Die reine Strukturdefinition sieht so aus:
struct Farbe { };
Listing 3.7 Definition der Struktur Farbe

Wie in der Syntaxbeschreibung einer Strukturdefinition zu erkennen, ist die Angabe eines Objektnamens optional und fehlt hier. Aus Grnden der Abwrtskompatibilitt erlaubt C++ die Definition eines Objekts direkt bei der Definition der Struktur:
struct Farbe { } einefarbe;
Listing 3.8 Objektdefinition bei Strukturdefinition

Diese Art der Objektdefinition ist in anderen Sprachen nicht mglich und sollte aus Grnden der Wiederverwendbarkeit nicht eingesetzt werden,

130

Strukturen

3.3

denn jeder, der die Headerdatei mit der Klassendefinition einbindet, wrde dann automatisch auch die Objektdefinition von einefarbe einbinden.
Tipp Objekte sollten nicht zusammen mit der Struktur definiert werden.

Die Headerdatei mit der Strukturdefinition kann jetzt in eine Quellcodedatei eingebunden werden und dort Objekte der Struktur definieren:
#include "Farbe.h" int main() { Farbe farbe1; }
Listing 3.9 Definition eines Strukturobjekts in main

3.3.2

Definition der Strukturelemente

Innerhalb des Anweisungsblocks der Struktur knnen die Elemente der Struktur wie herkmmliche Variablen definiert werden:
#pragma once #include <string> struct Farbe { std::string name; int rot; int gruen; int blau; };
Listing 3.10 Der aktuelle Stand der Datei Farbe.h

Um das Beispiel dichter an gngiger Praxis zu halten, greifen wir etwas vor und verwenden den Datentyp string, der in Abschnitt 6.8, Strings, genauer beschrieben wird. An dieser Stelle ist nur wichtig zu wissen, dass Objekte dieses Datentyps in der Lage sind, eine Zeichenkette zu speichern und dass die Definition von string in der Headerdatei string steht, die eingebunden werden muss.

131

Komplexere Datentypen

Tipp In einer Headerdatei sollten Sie nie using namespace-Anweisungen verwenden, weil diese dann mit der Klassendefinition in Quellcodedateien eingebunden wrden.

Weil wir auf eine typische using namespace std-Anweisung in der Headerdatei verzichten, mssen wir Elemente der Standardbibliothek (hier string) mit ihrem Namensbereich angeben, deshalb std::string.
Hinweis Die Elemente einer Struktur besitzen die Struktur als Bezugsrahmen, sind also lokal in ihr definiert. Ein Namenskonflikt mit gleichnamigen Elementen anderer Strukturen ist daher ausgeschlossen.

3.3.3

Zugriff auf die Struktur-Elemente

Ein Objekt vom Typ Farbe besitzt jetzt automatisch die in der Struktur definierten Strukturelemente, die ber den Elementoperator . ansprechbar sind:
Farbe farbe1; farbe1.name="Rot"; farbe1.rot=255; farbe1.gruen=0; farbe1.blau=0;

3.3.4

Strukturobjekt als Einheit

Objekte einer Struktur gelten als Einheit und knnen daher problemlos zugewiesen werden. Nehmen wir die einfache Struktur IntContainer, die maximal zehn int-Werte speichern kann und ber anzahl festhlt, wie viele Werte bisher gespeichert sind:
struct IntContainer { int werte[10]; int anzahl; };
Listing 3.11 Die Struktur IntContainer

Wird ein Objekt dieser Struktur einem anderen Objekt zugewiesen, dann werden alle Elemente der Struktur kopiert, selbst das Feld, das eigentlich durch reine Zuweisung nicht kopiert werden konnte:

132

Zeiger

3.4

IntContainer c1; c1.werte[0]=22; c1.werte[1]=33; c1.anzahl=2; IntContainer c2=c1; cout << c2.werte[0] << endl;
Listing 3.12 Kopieren eines Struktur-Objekts

3.4

Zeiger

Arrays knnen normalerweise nicht einfach als Funktionsparameter verwendet werden. Zeiger bieten eine Mglichkeit, dies trotzdem zu tun. Denn mit ihrer Hilfe werden Variablen oder Objekte nicht als Kopie, sondern als Verweis bergeben. Das Verstndnis von Zeigern bildet eine wichtige Grundlage fr die C++-Programmierung, sowohl fr ANSI C++ als auch fr C++/CLI.

3.4.1

Adressoperator

Wir verwenden Variablen schon seit einigen Kapiteln und haben im vorigen Abschnitt auch schon Strukturelemente erzeugt. All diese Elemente haben eines gemeinsam: Sie mssen irgendwo im Arbeitsspeicher des Computers gespeichert sein. Wo genau im Computerspeicher war uns bisher egal, weil wir ohne Schwierigkeiten ber den Bezeichner auf das Objekt oder die Variable zugreifen knnen. Nun ist der Punkt erreicht, etwas mehr ber das Wo zu erfahren. Der Arbeitsspeicher des Computers besteht als kleinster, nicht mehr teilbarer Einheit aus Bits. Von diesen Bits werden jeweils acht zu einem Byte zusammengefasst. Diese Bytes sind durchnummeriert, man spricht auch von ihrer Adresse. Wenn eine Variable definiert wird, sucht das Programm einen freien Speicherbereich fr die Variable und legt ihren Inhalt dort ab:
int x=22;

Diesen Umstand zeigt Abbildung 3.3.

133

Komplexere Datentypen

. . .
92E0 92E1 92E2 92E3 92E4 92E5 92E6 92E7 92E8 92E9 92EA

x
22

. . .
Variablen im Arbeitsspeicher

Abbildung 3.3

Die Adresse eines Bytes wird blicherweise im Hexadezimalsystem angegeben. In der Abbildung wurden die vier Bytes ab Adresse 92E2 fr x reserviert und der zugewiesene Wert darin gespeichert. Heutige Rechnersysteme haben einen so groen Speicher, dass die Adressen dort achtstellig sind; der Einfachheit halber fhren wir die Betrachtung aber mit vier Stellen durch. Wie kommen wir aber an die Adresse heran, ab der die Variable gespeichert ist? Dazu dient der Adressoperator &. Er wird einfach vor die Variable oder das Objekt geschrieben:
cout << &x << endl;

3.4.2

Definition eines Zeigers

Auf die Dauer wird es langweilig, die Adressen von Variablen einfach nur auszugeben. Um wirklich etwas Sinnvolles mit ihnen anstellen zu knnen, mssen wir sie speichern. Und genau dazu ist der Zeiger da.
Hinweis Variablen speichern Werte, Zeiger speichern Adressen.

C++ ist eine typisierte Programmiersprache. Bezogen auf Zeiger bedeutet das, ein Zeiger kann nur die Adresse eines bestimmten Typs speichern. Soll die Adresse einer int-Variablen gespeichert werden, dann wird ein Zeiger vom Typ Zeiger auf int, auch int* geschrieben, bentigt. Deklariert und definiert wird ein Zeiger, indem vor den Bezeichner ein * gesetzt wird:

134

Zeiger

3.4

int *p;

Der Zeiger p ist nun bereit, eine Adresse aufzunehmen:


p=&x;

Abbildung 3.4 zeigt den Inhalt des Zeigers grafisch umgesetzt. In der Abbildung liegt der Speicherbereich des Zeigers direkt hinter der Variablen. Das muss nicht zwangslufig so sein. Der Inhalt eines Zeigers die in ihm gespeicherte Adresse kann wie der Inhalt einer Variablen mit cout ausgegeben werden:
cout << p << endl;

. . .
92E0 92E1 92E2 92E3 92E4 92E5 92E6 92E7 92E8 92E9 92EA

x
22

p
92E2

. . .

Abbildung 3.4 Der Inhalt eines Zeigers

3.4.3

Dereferenzierungsoperator

Wirklich interessant werden Zeiger aber wegen der Fhigkeit, auf den Inhalt der Variablen zugreifen zu knnen, deren Adresse im Zeiger gespeichert ist. Dieser Vorgang wird Dereferenzierung genannt. Durchgefhrt wird sie mit dem Dereferenzierungsoperator *. Der Dereferenzierungsoperator wird vor den Zeiger geschrieben:
cout << *p << endl;

In p ist die Adresse von x gespeichert. Die Dereferenzierung von p liefert damit den Inhalt von x, also 22. Abbildung 3.5 zeigt die Zusammenhnge.

135

Komplexere Datentypen

. . .
92E0 92E1 92E2 92E3 92E4 92E5 92E6 92E7 92E8 92E9 92EA

x
22

p
92E2

. . .

Abbildung 3.5 Der Vorgang der Dereferenzierung

3.4.4

Zeiger als Funktionsparameter

Ihre Praxistauglichkeit erhalten die Zeiger als Funktionsparameter. Es besteht nmlich die Mglichkeit, ber sie die an die Funktion bergebene Variable zu verndern:
void aendern(int* x) { *x=20; } int main() { int a=30; cout << "a = " << a << endl; aendern(&a); cout << "a = " << a << endl; }
Listing 3.13 Mit einem Zeiger auf die bergebene Variable zugreifen

Die Funktion aendern besitzt als einzigen Funktionsparameter einen Zeiger vom Typ int*. Es spielt keine Rolle, ob der Stern neben dem Typ steht (int* x) oder neben der Variablen (int *x). Die Schreibweise mit dem Stern am Typ spiegelt den Sachverhalt Zeiger auf int aber eher wider und wird hier deshalb bevorzugt. Dass die Funktion einen Parameter vom Typ int* besitzt, hat einige Konsequenzen. Bei dem Funktionsaufruf darf nun nicht mehr eine bloe Variable angegeben werden, weil sonst ein Wert an die Funktion bergeben wrde

136

Zeiger

3.4

Zeiger speichern aber Adressen. Stattdessen wird die Adresse der Variablen a bergeben. In der Funktion knnen wir nicht einfach den Namen des Zeigers verwenden, denn damit wrde der Inhalt die in ihm gespeicherte Adresse angesprochen. Wir greifen daher mithilfe des Dereferenzierungsoperators auf den Wert der Variablen zu, deren Adresse im Zeiger gespeichert ist, und ndern damit den Wert der Variablen a aus main auf 20. Besprechen wir noch ein Beispiel aus der Praxis des Programmierens. Listing 2.33 in Abschnitt 2.3.4, Rckgabewerte, zeigt eine Funktion maximum, die den greren von zwei bergebenen Werten zurckliefert. Hufig ist aber nicht interessant, welches der grere Wert ist, sondern welche Variable den greren Wert beinhaltet. Auf eine Variable verweisen wir mit einem Zeiger, weshalb die Funktion einen Zeiger zurckliefern muss. Um einen Zeiger auf die Variable mit dem greren Wert zurckliefern zu knnen, muss die Funktion jedoch zunchst Verweise auf die beiden potenziellen Kandidaten besitzen. Daher mssen die Adressen der beiden zu vergleichenden Variablen an die Funktion bergeben werden. Um diese Adressen aufnehmen zu knnen, muss es sich bei den Parametern um Zeiger handeln. Die Funktion bekommt also zwei Adressen bergeben (die in Zeigern gespeichert werden) und liefert eine Adresse zurck:
int* maximum(int* a, int* b) { if(*a>*b) return(a); else return(b); }
Listing 3.14 Die Funktion maximum mit Zeigern

Bei dem Vergleich der Variablen muss dereferenziert werden, weil nicht die in den Zeigern gespeicherten Adressen verglichen werden sollen, sondern die Werte der Variablen, deren Adressen in den Zeigern gespeichert sind. Die return-Anweisungen wiederum besitzen den jeweiligen Zeiger als Argument, weil die Funktion die im Zeiger gespeicherte Adresse zurckliefern soll. Ein simples Anwendungsbeispiel der Funktion knnte so aussehen:
int x=30, y=40; int *p=maximum(&x, &y); cout << "Groessere Variable: " << *p << endl;

137

Komplexere Datentypen

Die Funktion erwartet Adressen, deswegen der Einsatz des Adressoperators. Die Funktion liefert eine Adresse zurck, daher muss das Ergebnis in einem Zeiger gespeichert werden. Nach dem Aufruf beinhaltet p die Adresse der Variablen, die den greren Wert enthlt. In der letzten Anweisung wird dieser Wert ber den Dereferenzierungsoperator ausgegeben.

3.4.5

Zeiger auf Struktur- und Klassenobjekte

Zeiger knnen nicht nur auf die elementaren Datentypen zeigen, sondern auch auf komplexe Typen wie Strukturen oder spter auch Klassen. Analog zur bisherigen Zeigerdefinition wird der Zeiger definiert wie ein Objekt der Struktur oder Klasse, nur dass dem Bezeichner ein * vorgesetzt wird. Nehmen wir als Beispiel die Struktur Farbe aus Abschnitt 3.3. Erzeugen wir ein Objekt (beim Ausprobieren das Einbinden der Header-Datei mit der Strukturdefinition nicht vergessen!):
Farbe f;

Dementsprechend sieht die Definition eines Zeigers, der auf f zeigen kann, so aus:
Farbe *p = &f;

Ebenfalls aus dem Abschnitt ber Strukturen wissen wir, dass wir die Elemente eines Strukturobjekts ber den Elementzugriffsoperator . ansprechen knnen. Wollten wir beispielsweise wissen, welchen Wert die Rot-Komponente der Farbe hat, ginge dies so:
cout << f.rot << endl;

Natrlich knnen wir das Element rot auch ber den Zeiger aufrufen. Um an das Objekt zu kommen, dessen Adresse im Zeiger gespeichert ist, mssen wir dereferenzieren. Auf das so ermittelte Objekt kann dann der Elementzugriffsoperator angewendet werden. Dazu muss wegen der Bindungsstrke der Operatoren geklammert werden:
cout << (*p).rot << endl;

Diese Schreibweise ist aufwndig. ber Zeiger wird jedoch hufiger auf Klassenelemente zugegriffen, deswegen existiert fr diesen Fall ein besonderer Operator, der Zeigeroperator ->. Angewendet wird er so:
cout << p->rot << endl;

138

Zeiger

3.4

3.4.6

Zeiger auf Arrays

Zeiger auf Arrays stellen keine Besonderheit dar, weil beispielsweise ein Zeiger vom Typ int* sowohl auf eine einzelne Variable als auch auf ein intArray zeigen kann.
Hinweis Die Adressermittlung eines Arrays bentigt keinen Adressoperator. Der Arrayname steht fr die Adresse des Arrays.

Ein Beispiel:
int f[10]; int *p = f;

Hinweis Die Adresse eines Arrays entspricht der Adresse des ersten Arrayelements.

Demnach htte der Zeiger auch so initialisiert werden knnen:


int *p = &f[0];

Um ber den Zeiger die Arrayelemente anzusprechen, wird auf ihn der Indexoperator angewendet:
p[3] = 935; // 4. Element von f beschreiben

Wir knnen mit diesem Wissen eine Funktion schreiben, die uns die Anzahl von Zeichen eines C-Strings liefert:
int eigenes_strlen(const char* s) { int i=0; while(s[i]!=0) i++; return(i); }
Listing 3.15 Die Funktion eigenes_strlen

Der verwendete Zeiger ist vom Typ const char*, weil C-Strings in charArrays gespeichert werden. Durch das davorgesetzte const kann ber den Zeiger das Feld nur gelesen, aber nicht verndert werden. Das ist notwendig, um auch konstante Zeichenketten bearbeiten zu knnen, wie das folgende Beispiel zeigt:

139

Komplexere Datentypen

cout << eigenes_strlen("andre") << endl;

Das Ergebnis ist 5. Etwas ist zu beachten: Arrays speichern ihre Gre nicht explizit. Deswegen kann die Gre eines Arrays nicht ber einen Zeiger ermittelt werden. Dazu mssen andere Techniken angewendet werden, wie z. B. die Endekennung eines C-Strings oder ein zustzlicher Funktionsparameter, dem die Gre des Arrays bergeben wird.

3.4.7

Zeigerarithmetik

Zeiger besitzen die Fhigkeit einer einfachen Arithmetik. Gehen wir von folgender Situation aus:
int f[20]; int *p = f;

Wir wissen, dass p nun auf das erste Element des Arrays zeigt. Es ist jetzt mglich, ber p durch Addition die Adresse des fnften Elements zu bestimmen. In der folgenden Anweisung wird diese Adresse dem Zeiger q zugewiesen:
int *q = p+4;

Das Ergebnis der Addition muss nicht unbedingt gespeichert werden, um es verwenden zu knnen. Die Summe kann auch direkt dereferenziert werden:
cout << *(p+4) << endl;

Die Klammerung ist wichtig, weil ansonsten auf das dereferenzierte p der Wert 4 addiert wrde. Um einen Offset von vier Elementen zu erzeugen, muss zwangslufig bekannt sein, wie viel Speicher ein Element belegt. Beispielsweise muss bei vier 2 Byte groen Elementen der Offset 8 Byte betragen, wohingegen er bei vier 4 Byte groen Elementen 16 Byte gro sein muss. Glcklicherweise geschieht diese Bercksichtigung der Elementgre bei der Zeigerarithmetik vollautomatisch. Die Anwendung der Inkrement- und Dekrementoperatoren auf Zeiger funktioniert ebenso. Der Zeiger zeigt danach auf das nchste oder vorherige Element. Ob es ein solches Element gibt, muss vom Programmierer sichergestellt werden. Grundstzlich kann ein Zeiger auch eine Adresse enthalten, die keiner passenden Variablen entspricht. Problematisch wird es erst, wenn

140

Referenzen

3.5

dann dereferenziert und schreibend darauf zugegriffen wird. Zu Demonstrationszwecken soll hier die Funktion eigenes_strlen aus Listing 3.15 im letzten Abschnitt mit Zeigerarithmetik implementiert werden:
int eigenes_strlen(const char* s) { int i=0; while(*(s++)!=0) i++; return(i); }
Listing 3.16 Die Funktion eigenes_strlen mit Zeigerarithmetik

3.5

Referenzen

Referenzen sind den Zeigern sehr hnlich, besitzen aber durch Einschrnkung der Fhigkeiten eine einfachere Syntax. Eine Referenz ist, wie der Name bereits zum Ausdruck bringt, ein Verweis auf etwas. Im Gegensatz zu einem Zeiger kann eine Referenz aber nur auf ein einziges Element verweisen. Der Verweis selbst kann nicht mehr gendert werden. Aus diesem Grund muss eine Referenz bei ihrer Definition initialisiert werden. Bei ihrer Definition wird eine Referenz durch ein & gekennzeichnet:
int w=3; int &r = w;

Da die Referenz von nun an immer auf die Variable w verweist und dieser Verweis nicht abgendert werden kann, braucht syntaktisch nicht mehr unterschieden zu werden zwischen Inhalt und Dereferenzierung. Die Referenz r ist nun ein Synonym fr die Variable w:
r=11; cout << w << endl;

Die Referenz hat gegenber dem Zeiger zwei syntaktische Vereinfachungen: Bei der Initialisierung der Referenz muss von der Variablen, auf die verwiesen wird, nicht explizit die Adresse ermittelt werden. Die Dereferenzierung erfolgt implizit und kommt daher ohne speziellen Operator aus.

141

Komplexere Datentypen

Als Beispiel soll die Funktion maximum, die in Listing 3.14 in Abschnitt 3.4.4, Zeiger als Funktionsparameter, mit Zeigern realisiert wurde, nun mit Referenzen implementiert werden:
int& maximum(int& a, int& b) { if(a>b) return(a); else return(b); }
Listing 3.17 Die Funktion maximum mit Referenzen

Im Vergleich zu der Lsung mit Zeigern knnen der Funktion nun die Parameter so bergeben werden, als wrden bloe Kopien erwartet:
int x=30, y=40; int &r=maximum(x, y);

142

Dieses Kapitel fhrt Sie in das Klassenkonzept ein, einen der wesentlichen Pfeiler der objektorientierten Programmierung.

Klassen

Zunchst werden wir Klassen als eine Mglichkeit kennenlernen, Variablen unterschiedlicher Datentypen zu einer Einheit zusammenzufassen. Anschlieend werden die von Klassen untersttzten Mechanismen der objektorientierten Programmierung betrachtet.

4.1

Definition einer Klasse

Definiert wird eine Klasse mit dem Schlsselwort class, gefolgt von dem Namen der Klasse und einem Paar geschweifter Klammern, die mit einem Semikolon abgeschlossen werden! Dieses Semikolon ist analog zu den Strukturen ein Erbe von C und ein beliebter Fehler. Die Syntax einer Klassendefinition lautet:
class Klassenname { // Elemente der Klasse } [Objektname];

Hinweis Wie die Syntax zeigt, gibt es auch hier die Mglichkeit einer Objektdefinition hinter der Klassendefinition. Darauf sollten Sie aber verzichten, weil es nicht mehr zeitgem ist.

Nehmen wir als Beispiel eine Klasse namens Becher, die spter als Komponenten den Inhalt als Text, das Fassungsvermgen in Millilitern und die Fllhhe in Prozent beinhalten soll:
class Becher { };
Listing 4.1 Die nackte Klasse Becher

143

Klassen

blicherweise steht die Definition einer Klasse wie die einer Struktur in einer eigenen Header-Datei mit dem Namen der Klasse. Die Header-Datei fr die Klasse Becher heit daher Becher.h.

4.1.1

Erstellen einer Klasse mit Visual C++

Genau wie die Dateien bisher knnen Sie die Header-Datei Becher.h manuell erstellen. Visual C++ bietet jedoch einen Assistenten, mit dem eine Klasse mitsamt der bentigten Dateien erstellt werden kann. Klicken Sie dazu, wie in Abbildung 4.1 gezeigt, im Projektmappen-Explorer mit der rechten Maustaste auf den Projektnamen, whlen Sie dort Hinzufgen und im darauf erscheinenden Untermen Klasse.

Abbildung 4.1

Erstellen einer neuen Klasse

Daraufhin ffnet sich das in Abbildung 4.2 gezeigte Fenster. Als Kategorie whlen Sie C++, und dort die einzige Vorlage C++-Klasse. Besttigen Sie durch Anklicken von Hinzufgen. Nachdem Sie die Vorlage ausgewhlt haben, erscheint ein auf den ersten Blick reichhaltiger Dialog, mit dem die Klasse dann tatschlich erstellt wird (Abbildung 4.3).

144

Definition einer Klasse

4.1

Abbildung 4.2

Klassentyp whlen

Abbildung 4.3 Klasseneinstellungen

Unter Klassenname geben Sie den Namen der zuknftigen Klasse an. Die Namen fr die Header- und die Quellcode-Datei werden dem Klassennamen und der blichen Benennung entsprechend vorgegeben, knnen bei Bedarf aber nachtrglich beliebig verndert werden. Die Punkte Basisklasse, Zugriff und Virtueller Destruktor kommen erst im weiteren Verlauf des Buchs bei der Vererbung zum Tragen und werden hier noch ignoriert. In bestimmten Situationen, die spter noch besprochen werden, macht es Sinn, die Klasse nicht in Header- und Quellcode-Datei aufzuteilen, sondern stattdessen die gesamte Klasse in die Header-Datei zu schreiben. In diesem Fall haken Sie den Punkt Inline ab, und die Erstellung einer Quellcode-Datei wird unterbunden. Durch Anklicken von Fertig stellen wird die Klasse

145

Klassen

erzeugt. Im Projektmappen-Explorer erscheinen die Header- und die Quellcode-Datei. Sie knnen sie durch Doppelklick auf den Dateinamen im Editor ffnen. Das folgende Listing zeigt die erstellte Klasse in Becher.h:
#pragma once class Becher { public: Becher(void); ~Becher(void); };
Listing 4.2 Der Inhalt von Becher.h

Der erste Befehl dient der Vermeidung einer potenziellen Mehrfachdeklaration, wie wir in Abschnitt 2.4.4, Mehrfachdeklarationen vermeiden, erfahren haben. Die Klasse selbst besitzt schon einige Elemente einen Konstruktor und den Destruktor. Wir kennen beide noch nicht (sie werden erst in den Abschnitten 4.5 und 5.2.3 behandelt), daher lschen wir sie hier, so dass die geschweiften Klammern wie in Listing 4.1 leer bleiben. Im Folgenden ist der Inhalt der Datei Becher.cpp aufgelistet:
#include "Becher.h" Becher::Becher(void) { } Becher::~Becher(void) { }
Listing 4.3 Der Inhalt von Becher.cpp

Als erste Amtshandlung wird die Datei Becher.h eingebunden. Das ist wichtig, weil in Becher.h beschrieben ist, aus welchen Elementen die Klasse besteht. Diese Informationen mssen dem Compiler vorliegen, bevor die Klassenelemente in der Quellcode-Datei genauer ausgefhrt werden. Alles andere sind die Definitionen des in Becher.h deklarierten Konstruktors und Destruktors. Da wir deren Deklarationen in Becher.h bereits entfernt haben, mssen die Definitionen das gleiche Schicksal erleiden und werden aus der

146

Attribute

4.2

Datei gelscht. Das Projekt sollte sich nun immer noch fehlerfrei kompilieren lassen. Auch Objekte der Klasse lassen sich bereits problemlos erzeugen:
#include "Becher.h" int main() { Becher b; }
Listing 4.4 Erzeugen eines Becher-Objekts

Um ein Klassenelement definieren zu knnen, muss die dazugehrige Klassendefinition verfgbar sein. Im oberen Beispiel wird dies durch Einbinden von Becher.h gewhrleistet. Weder wird iostream eingebunden noch der Namensbereich std verfgbar gemacht, weil kein Element der Standardbibliothek bentigt wird.

4.2

Attribute

Nun soll die ursprnglich geplante Struktur der Klasse Becher weiter umgesetzt werden. Gewnscht war der Inhalt des Bechers als Text, das Fassungsvermgen in Millilitern und die Fllhhe des Bechers in Prozent. Solche Datenelemente der Klasse werden Attribute genannt. Die Attribute werden genau wie die Elemente einer Struktur in die geschweiften Klammern der Klasse hineingeschrieben. Das folgende Listing zeigt den aktuellen Inhalt von Becher.h:
#pragma once #include <string> class Becher { std::string inhalt; int fassungsvermoegen; float fuellhoehe; };
Listing 4.5 Die Klasse Becher mit Attributen

Damit uns der Datentyp string zur Verfgung steht, muss die Header-Datei string eingebunden werden. In einer Header-Datei sollten Sie keine using namespace-Anweisungen verwenden, weil Header-Dateien in Quellcode-

147

Klassen

Dateien eingebunden werden und die einbindende Quellcode-Datei eine in der Header-Datei enthaltene using namespace-Anweisung mit einbinden wrde, vllig egal, ob gewollt oder nicht. Deshalb muss bei der Definition von inhalt der Datentyp string mitsamt seinem Namensbereich angegeben werden.

4.3

Zugriffsrechte

Die Klasse Becher hat nun drei Datenelemente. Wir wissen bereits von den Strukturen (siehe Abschnitt 3.3, Strukturen), wie auf die Elemente einer Klasse zugegriffen werden kann: mit dem Elementzugriffsoperator. Mit dessen Hilfe knnte ein erzeugter Becher zur Hlfte mit Milch gefllt werden:
Becher b; b.inhalt="Milch"; b.fassungsvermoegen=300; b.fuellhoehe=50;

Der Compiler ist mit diesen Anweisungen jedoch nicht einverstanden. Er sagt unter anderem Becher::inhalt: Kein Zugriff auf private Member Die gleiche Fehlermeldung wird auch fr die anderen beiden Attribute gemeldet. Diese Fehlermeldungen liefern zwei wesentliche Informationen: 1. Unsere Attribute sind privat. 2. Von der main-Funktion aus haben wir keinen Zugriff darauf. Das wiederum wirft drei Fragen auf: 1. Warum sind die Attribute privat? 2. Was bedeutet privat? 3. Warum hat die main-Funktion keinen Zugriff darauf? Beginnen wir von vorne. Auf jedes Element einer Klasse besteht ein sogenanntes Zugriffsrecht. Dieses Zugriffsrecht bestimmt, von wo aus auf das Element zugegriffen werden kann. In C++ werden drei Zugriffsrechte unterschieden: 1. Privat (private): Auf private Elemente drfen nur Elemente der eigenen Klasse und Freunde der Klasse zugreifen. 2. Geschtzt (protected): Wie privat, nur dass auch Elemente abgeleiteter Klassen (siehe Abschnitt 4.11) darauf zugreifen drfen.

148

Methoden

4.4

3. ffentlich (public): Keine Beschrnkung des Zugriffs. Von berall kann auf das Element zugegriffen werden. Wird kein Zugriffsrecht angegeben, dann besitzen die Klassenelemente privates Zugriffsrecht. Das erklrt auch, warum auf die Attribute nicht zugegriffen werden konnte, denn die main-Funktion gehrt definitiv nicht zur Klasse. Die zur Spezifikation des Zugriffsrechts verwendeten Schlsselwrter (private, protected und public) werden in C++ als Zugriffsspezifizierer (access specifier) bezeichnet. Obwohl es in der objektorientierten Programmierung ein Sakrileg ist, fr Attribute ffentliches Zugriffsrecht zu vergeben, wollen wir genau dies in den folgenden Abschnitten zu Anschauungszwecken tun. Ein Zugriffsrecht wird spezifiziert, indem es irgendwo in der Klasse mit Doppelpunkt angegeben wird. Das so spezifizierte Zugriffsrecht gilt so lange, bis ein anderes Zugriffsrecht spezifiziert wird. Um die Attribute von Becher als ffentlich zu spezifizieren, schreiben wir vor das erste Attribut public:, und schon wird der halbvolle Becher Milch kompiliert:
class Becher { public: std::string inhalt; int fassungsvermoegen; float fuellhoehe; };
Listing 4.6 ffentliche Attribute

Zugriffsrechte knnen in einer Klasse beliebig oft spezifiziert werden.


Unterschied zwischen Klasse und Struktur In C++ liegt der einzige Unterschied zwischen Klasse und Struktur darin, dass bei einer Klasse die Elemente per Voreinstellung privat sind. Bei Strukturen sind die Elemente ffentlich.

4.4

Methoden

Eine weitere Art von Klassenelement sind die Methoden. Sie bilden den dynamischen Teil einer Klasse und sind technisch nichts anderes als zur Klasse gehrende Funktionen. Deswegen werden sie in C++ auch gerne Elementfunktion genannt.

149

Klassen

Wir knnten unsere Klasse Becher mit der Fhigkeit ausstatten, auf dem Bildschirm ausgeben zu knnen, mit welchem Inhalt der Becher gefllt ist. Die notwendigen Ergnzungen sind schnell vorgenommen:
#pragma once #include <string> #include <iostream> class Becher { public: std::string inhalt; int fassungsvermoegen; float fuellhoehe; void ausgabe() { std::cout << "Becher mit " << inhalt << std::endl; } };
Listing 4.7 Die Methode ausgabe

Die Methode besitzt weder Rckgabewert noch Parameter und ist syntaktisch identisch mit einer Funktion. Zur Methode wird sie, weil sie innerhalb der Klasse steht. Als Methode und damit Element der Klasse kann sie auf alle Elemente von Becher zugreifen, ohne deren Zugriffsrechte bercksichtigen zu mssen. Sie selbst besitzt hier ffentliches Zugriffsrecht und kann von berall aus aufgerufen werden. Und der Aufruf luft wieder ber den Elementzugriffsoperator:
b.ausgabe();

Eine herkmmliche Methode muss immer ber ein Klassenobjekt aufgerufen werden. Wird die Methode wie in der oberen Anweisung ber das Objekt b aufgerufen (dieser Aufruf ist in main mglich, weil das Zugriffsrecht auf ausgabe public ist), dann hat sie fr diesen Aufruf Zugriff auf die Attribute von b. Der Zugriff auf inhalt in ausgabe ist fr diesen Fall das Attribut inhalt von b.

4.4.1

Externe Definition

Die Definition der Methode ausgabe steht innerhalb der Klassendefinition. Man nennt eine solche Methode inline. Der Compiler versucht, den Aufruf

150

Methoden

4.4

einer Inline-Methode durch ihren Programmcode zu ersetzen. Dadurch wird das Programm zwar lnger, weil der Programmcode der Methode so oft im Programm vorkommt, wie sie aufgerufen wurde. Aber das Programm gewinnt auch an Geschwindigkeit, weil beim Aufruf nicht zum Methodencode hin- und am Methodenende wieder zurckgesprungen werden muss. Bei lngeren Methoden ist der Nachteil durch Codevervielfltigungen aber weitaus hher als der Geschwindigkeitsvorteil, deswegen werden auch nur kleine Methoden vom Compiler als inline behandelt.
Hinweis Inline-Methoden werden nicht aufgerufen, sondern deren Quellcode ersetzt jeden ihrer Aufrufe. Die Folge sind schnellere, aber lngere Programme sowie eine erhhte Kompilationszeit.

Inline-Methoden haben aber einen Geschwindigkeitsnachteil zur Kompilationszeit, denn ihr Programmcode steht in einer Header-Datei, die unter Umstnden in vielen anderen Dateien eingebunden wird. Der Methodencode wird dadurch mehrfach kompiliert, obwohl einmal vllig ausreichen wrde. Fr die Kompilation wre es daher von Vorteil, wenn der Methodencode in eine eigene Datei ausgelagert werden knnte, die nur einmal kompiliert wird. Es wird keine berraschung sein, dass genau dies mglich ist. Das Prinzip ist identisch mit der Aufteilung einer Funktion in Deklaration und Definition (siehe Abschnitt 2.4, Module). Innerhalb der Klassendefinition in Becher.h steht jetzt nur noch die Deklaration der Methode:
#pragma once #include <string> class Becher { public: std::string inhalt; int fassungsvermoegen; float fuellhoehe; void ausgabe(); };
Listing 4.8 Die Deklaration von ausgabe

151

Klassen

In Becher.h findet keine Ausgabe mehr statt, das Einbinden von iostream ist deshalb nicht mehr notwendig. Doch wo wandert die Methodendefinition hin? Wie bei Funktionen in die dazugehrige .cpp-Datei, die im Folgenden aufgefhrt ist:
#include "Becher.h" #include <iostream> using namespace std; void Becher::ausgabe() { cout << "Becher mit " << inhalt << endl; }
Listing 4.9 Die Definition von ausgabe

Eine Methode gehrt bekanntermaen zu einer Klasse. Aber zu welcher? Als Die Methodendefinition noch innerhalb der Klassendefinition stand, war die Zuordnung klar. Nun aber wird die Definition ausgelagert und der direkte Klassenbezug fehlt. Schlimmer noch: Mehrere Methoden aus unterschiedlichen Klassen knnten denselben Namen besitzen. Um dem Compiler mitzuteilen, zu welcher Klasse eine extern definierte Methode gehrt, wird vor den Methodennamen der Klassenname, und dazwischen der Bezugsrahmenoperator gesetzt. Weil die Methode ausgabe zur Klasse Becher gehrt, steht im Methodenkopf Becher::ausgabe. Zu lesen als von der Klasse Becher das Element ausgabe. Soll eine extern definierte Methode trotzdem als inline betrachtet werden, dann muss vor die Deklaration das Schlsselwort inline geschrieben werden. Inline ist immer nur eine Empfehlung. Unabhngig davon, ob die Methode explizit ber das Schlsselwort inline oder implizit durch Definition innerhalb der Klasse als inline gekennzeichnet wurde, der Compiler entscheidet, ob die Methode tatschlich inline wird oder nicht.

4.4.2

this

Manchmal ist es innerhalb einer Methode sinnvoll, zu wissen, ber welches Objekt die Methode aufgerufen wurde. Dazu stellt jede Methode einen Zeiger namens this bereit, ber den das aufrufende Objekt angesprochen werden kann. Die Methode ausgabe von Becher knnten wir damit auch, wie unten aufgefhrt, implementieren:

152

Konstruktoren

4.5

void Becher::ausgabe() { cout << "Becher mit " << this->inhalt << endl; }

Mit this->inhalt wird vom aufrufenden Objekt das Attribut inhalt angesprochen. Die bloe Angabe des Attributnamens spricht ebenfalls das Attribut des aufrufenden Objekts an, insofern ist this hier unntig. Es wird aber klar, dass ber this das aufrufende Objekt verfgbar ist und dieses bei Bedarf darber an Funktionen oder Methoden anderer Klassen bergeben werden kann.

4.5

Konstruktoren

Die Erzeugung eines Bechers luft bisher so ab: 1. Definition eines Klassenobjekts 2. Zuweisen von Werten an die Attribute Aber wer zwingt uns, den zweiten Schritt korrekt oder berhaupt auszufhren? Es ist kein Problem, einen Becher mit undefiniertem Inhalt oder negativem Fassungsvermgen zu erstellen. Auch wenn Letzteres vielleicht manches Physikerherz hher schlagen lsst, sind diese Mglichkeiten nicht unbedingt praxistauglich. Um sicherzustellen, dass sptere Programmteile nicht mit Bechern aus einem Paralleluniversum konfrontiert werden, muss gewhrleistet sein, dass alle Attribute einen korrekten Inhalt besitzen. Und genau dazu dienen Konstruktoren. Ein Konstruktor ist eine besondere Form von Methode, die den Ersteller eines Klassenobjekts zwingt, bestimmte Informationen zur Initialisierung des Objekts anzugeben. Wir knnten einen Nutzer der Klasse Becher bei der Objektdefinition zwingen, Inhalt, Fassungsvermgen und Fllhhe anzugeben. Der Konstruktor bentigt dazu drei Parameter, die an ihn bergeben werden mssen und deren Typen mit den zu initialisierenden Attributen bereinstimmen sollten. Konstruktoren besitzen den Namen ihrer Klasse und knnen keine Werte zurckliefern. Aus diesem Grund darf nicht einmal void angegeben werden. Zunchst schreiben wir den Konstruktor inline in die Klassendefinition, wie im Folgenden zu sehen ist.
class Becher { public:

153

Klassen

std::string inhalt; int fassungsvermoegen; float fuellhoehe; Becher(std::string i, int fa, float fu) { inhalt=i; fassungsvermoegen=fa; fuellhoehe=fu; } void ausgabe(); };
Listing 4.10 Die Klasse Becher mit Inline-Konstruktor

Der Konstruktor macht nichts anderes, als die an ihn bergebenen Parameter zur Initialisierung der Attribute zu verwenden. Wenn Sie nun behaupten, dass immer noch missratene Objekte erstellt werden knnen, weil fr das Fassungsvermgen schlicht ein negativer Wert an den Konstruktor bergeben werden knnte, dann haben Sie Recht. Aber: Es kann kein Objekt mehr erzeugt werden, ohne dass der Konstruktor abgearbeitet wird. Von daher bruchte im Konstruktor nur entsprechender Code untergebracht zu werden, der alle ungltigen Konstellationen abfngt. Wie auf solche entdeckten Fehler reagiert werden kann, wird bei den Ausnahmen in Abschnitt 5.3, Ausnahmen, besprochen. Uns soll an dieser Stelle die Gewissheit reichen, Missbrauch verhindern zu knnen, wenn wir es wollten. Um nun ein Objekt zu erzeugen, mssen bei der Definition die vom Konstruktor geforderten drei Argumente bergeben werden. Das knnte so aussehen:
Becher b("Milch", 300, 50);

Die ursprngliche Schreibweise


Becher b;

funktioniert nicht mehr, weil die Klasse dazu einen Konstruktor ohne Parameter besitzen msste, den man Standardkonstruktor nennt.

154

Konstruktoren

4.5

4.5.1

Externe Definition

Nachdem die Grundlagen eines Konstruktors besprochen sind, soll auch an ihm die Aufteilung von Deklaration und Definition vollzogen werden. In der Klassendefinition steht nur noch die Konstruktordeklaration:
Becher(std::string i, int fa, float fu);

Die Definition in der .cpp-Datei sieht so aus:


Becher::Becher(string i, int fa, float fu) { inhalt=i; fassungsvermoegen=fa; fuellhoehe=fu; }
Listing 4.11 Der ausgelagerte Konstruktor von Becher

Die Schreibweise Becher::Becher mag etwas merkwrdig aussehen, aber sie gehorcht der bekannten Regel Klassenname::Methodenname.

4.5.2

Private Attribute

Nachdem wir nun mit den Konstruktoren Klassenobjekte initialisieren knnen und mit den Methoden die Mglichkeit haben, jegliche Funktionalitt zur Verfgung zu stellen, gibt es keinen sinnvollen Grund mehr, die Attribute weiterhin ffentlich zu lassen. Wir werden sie wie es sich fr die Datenkapselung gehrt mit privatem Zugriffsrecht ausstatten:
class Becher { private: std::string inhalt; int fassungsvermoegen; float fuellhoehe; public: Becher(std::string i, int fa, float fu); void ausgabe(); };
Listing 4.12 Die Klasse Becher mit privaten Attributen

Die explizite Angabe von private: am Anfang der Klasse ist nicht zwingend notwendig, weil Klassenelemente ohne explizites Zugriffsrecht automatisch privat sind.

155

Klassen

4.5.3

Elementinitialisierungsliste

Technisch gesehen wurden bereits alle Attribute eines Objekts von ihren Standardkonstruktoren initialisiert, bevor der Anweisungsblock des entsprechenden Konstruktors der Klasse abgearbeitet wird.
Hinweis Vor der Konstruktorausfhrung werden alle Attribute mit ihrem Standardkonstruktor initialisiert.

Fr die Klasse Becher heit das, die Attribute inhalt, fassungsvermoegen und fuellhoehe sind mit ihren Standardwerten initialisiert, bevor sie im BecherKonstruktor mit den an ihn bergebenen Argumenten beschrieben werden; Der String beinhaltet eine leere Zeichenkette, die numerischen Typen wurden mit 0 initialisiert. Das erscheint irgendwie unntig, denn warum die Attribute zuerst mit Standardwerten versehen, wenn sie anschlieend mit den wirklichen Initialisierungswerten beschrieben werden? Diese Initialisierung vor der Abarbeitung des Konstruktor-Anweisungsblocks lsst sich nicht vermeiden, wohl aber angeben, womit die Attribute initialisiert werden sollen. Und genau dazu dient die Elementinitialisierungsliste. Sie steht in der Konstruktordefinition zwischen Konstruktorkopf und Anweisungsblock und ist vom Kopf mit einem Doppelpunkt getrennt. Die einzelnen Initialisierungen werden mit Kommata getrennt:
Becher::Becher(string i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { }
Listing 4.13 Konstruktor mit Elementinitialisierungsliste

Die Initialisierung der einzelnen Attribute in der Elementinitialisierungsliste sieht aus wie die Definition von Klassenobjekten mithilfe eines Konstruktors. Und technisch gesehen ist es auch so. Die Zuweisungen innerhalb des Anweisungsblocks sind nun berflssig. Im Falle des Becher-Konstruktors hat der Einsatz der Elementinitialisierungsliste lediglich eine Verbesserung der Performanz zur Folge, er ist aber nicht zwingend.

156

Konstruktoren

4.5

Achtung Es gibt zwei Flle, die eine Elementinitialisierungsliste notwendig machen: 1. Das Attribut wurde als konstant deklariert. 2. Das Attribut wurde als Referenz deklariert.

Das klingt einleuchtend, denn sowohl eine Referenz als auch eine Konstante mssen bei ihrer Definition initialisiert werden und knnen nicht nachtrglich einen anderen Wert zugewiesen bekommen.

4.5.4

Explizite Konstruktoren

Betrachten wir folgende simple Klasse Person, die bewusst nur ein Attribut zum Speichern des Alters und einen passenden Konstruktor besitzt:
class Person { int alter; public: Person(int a) : alter(a) { } };
Listing 4.14 Die Klasse Person

Ein Objekt dieser Klasse ist leicht erzeugt:


Person p(33);

Eine berraschung ist jedoch folgende Zuweisung:


p=20;

Wieso kann einem Objekt des Datentyps Person ein Wert des Typs int zugewiesen werden? Weil der Compiler einen Konstruktor der Klasse zur Typumwandlung verwendet.
Hinweis Konstruktoren, die nur mit einem Parameter aufgerufen werden knnen entweder weil sie nur einen Parameter besitzen oder weil die anderen Parameter Standardwerte besitzen , werden vom Compiler verwendet, um den Datentyp des Konstruktorparameters in den Datentyp der Klasse des Konstruktors umzuwandeln.

157

Klassen

Der Compiler verwendet also den Konstruktor von Person, um aus dem Parametertyp int ein Person-Objekt zu erzeugen, und weist dieses dann dem Person-Objekt auf der linken Seite des Zuweisungsoperators zu. Das ist oft sehr praktisch, manchmal aber auch lstig, speziell dann, wenn nicht wirklich klar ist, was genau die Zuweisung eigentlich bewirkt. Wenn Sie nicht mchten, dass der Compiler einen solchen Konstruktor zur impliziten Typumwandlung heranzieht, dann mssen Sie ihn als explizit deklarieren:
class Person { int alter; public: explicit Person(int a) : alter(a) { } };
Listing 4.15 Die Klasse Person mit explizitem Konstruktor

Tipp Es knnen beliebig viele Konstruktoren als explizit deklariert werden.

4.5.5

Standardkonstruktor

Eine wichtige Variante der Konstruktoren ist der Standardkonstruktor.


Hinweis Als Standardkonstruktor wird der Konstruktor bezeichnet, der ohne Parameter aufgerufen wird (entweder weil er keine Parameter besitzt oder weil er fr alle Parameter Standardwerte definiert).

Oft besteht bei der Definition eines Standardkonstruktors die Schwierigkeit darin, sinnvolle Standardwerte fr die Attribute zu finden. Was wre zum Beispiel das Standard-Alter von Objekten der Klasse Person aus dem vorigen Abschnitt? Der Standardkonstruktor, der das Alter auf 0 setzt, she so aus:
Person() : alter(0) { }
Listing 4.16 Der Standardkonstruktor von Person

158

Konstruktoren

4.5

Tipp Numerische Objekte sollten als Standardwert ihre Reprsentation von 0 whlen (bei einer Bruch-Klasse, deren Objekte aus Zhler und Nenner bestehen, beispielsweise Zhler gleich 0 und Nenner gleich 1).

Besitzt eine Klasse einen Standardkonstruktor, dann knnen von ihren Objekten Arrays erzeugt werden:
Person personen[10];

Achtung Nur von Klassen mit Standardkonstruktor lassen sich Arrays erzeugen.

4.5.6

Kopierkonstruktor

Eine weitere Sonderform von Konstruktoren ist der Kopierkonstruktor, der ein Objekt derselben Klasse bergeben bekommt und von diesem eine Kopie anfertigt. Die Syntax des Kopierkonstruktors lautet:
Klassenname(const Klassenname& objektname) { }

Kopierkonstruktoren verwenden immer eine Referenz auf ein konstantes Objekt. Fr die Klasse Person she dieser Kopierkonstruktor so aus:
Person(const Person& p) : alter(p.alter) { }
Listing 4.17 Der Kopierkonstruktor von Person

Hinweis Wenn kein Kopierkonstruktor definiert wird, dann fgt der Compiler implizit einen Kopierkonstruktor hinzu. Dieser kopiert das Objekt aber nur attributweise (flache Kopie). Belegen die Objekte einer Klasse dynamisch Ressourcen, dann ist ein eigener Kopierkonstruktor erforderlich, der auch die Ressourcen kopiert (tiefe Kopie).

159

Klassen

4.6

Konstanzwahrende Methoden

Kommen wir zurck zu den Bechern. Wir knnen mit dem Schlsselwort const auch konstante Klassenelemente erzeugen:
const Becher c("Kaffee", 200, 100);

Dieser Becher gefllt mit Kaffee kann niemals ausgetrunken werden und eignet sich hchstens als Anschauungsmaterial. Dazu msste aber die Methode ausgabe aufgerufen werden:
c.ausgabe(); // Fehler

Erstaunlicherweise meldet der Compiler einen Fehler: this-Zeiger kann nicht von 'const Becher' in 'Becher &' konvertiert werden. Der Compiler mchte damit auf seine ihm eigene Art sagen, dass die Methode ausgabe fr variable Klassenobjekte gedacht ist und daher nicht fr ein konstantes Klassenobjekt aufgerufen werden kann. Die Frage ist nur, warum nicht? In ausgabe werden keine nderungen an den Attributen vorgenommen, insofern bleibt das Objekt unverndert. Der Compiler mchte dies aber explizit mitgeteilt bekommen.
Hinweis Damit eine Methode ber ein konstantes Klassenobjekt aufgerufen werden kann, muss die Methode als konstanzwahrend deklariert sein.

Eine Methode muss sowohl bei der Deklaration als auch bei der Definition als konstanzwahrend deklariert werden. Dazu wird hinter dem Methodenkopf das Schlsselwort const geschrieben:
void ausgabe() const;
Listing 4.18 Deklaration einer konstanzwahrenden Methode

Und noch die Definition:


void Becher::ausgabe() const { cout << "Becher mit " << inhalt << endl; }
Listing 4.19 Definition einer konstanzwahrenden Methode

Nun kann ausgabe auch fr eine Konstante aufgerufen werden.

160

berladen von Methoden

4.7

Hinweis Eines ist noch zu beachten: Nur Methoden, die wirklich keine nderungen an den Attributen vornehmen, knnen als konstanzwahrend deklariert werden. Der Compiler prft das nach!

4.6.1

Vernderliche Attribute

Wenn Sie gefragt werden, was die Aussage ein Objekt ist konstant bedeutet, was antworten Sie? Wahrscheinlich etwas wie das Objekt kann nicht verndert werden. Damit liegen Sie nicht falsch, nur, was haben wir uns darunter vorzustellen, dass ein Objekt nicht verndert werden kann? Ist ein Objekt nur dann konstant, wenn sich wirklich nichts an ihm ndern lsst? Oder reicht es schon aus, wenn der von auen sichtbare Zustand sich nicht ndern lsst? Denn eine nderung, die der Benutzer des Objekts nicht merkt, ist fr ihn auch keine nderung. Vielleicht kommt Ihnen das wie Erbsenzhlerei vor, aber es ist von entscheidender Bedeutung. Wenn Sie an den Hften ein Kilo abgenommen haben, dafr aber am Bauch ein Kilo zugelegt, ist Ihr Gewicht dann konstant? In der objektorientierten Programmierung gilt die Regel, dass ein Objekt konstant ist, solange der uere Zustand sich nicht ndert, beziehungsweise sich nicht ndern lsst. Es gibt aber Situationen, in denen aus verwaltungstechnischen Grnden Attribute eines konstanten Objekts gendert werden mssen. Dazu knnen Sie vor das Attribut das Schlsselwort mutable schreiben. Diese so gekennzeichneten Attribute sind auch in konstanten Objekten vernderbar. Damit ist eine Methode, die ausschlielich mutable-Attribute verndert, immer noch konstanzwahrend.

4.7

berladen von Methoden

Wir wissen jetzt, wie Methoden implementiert werden, und knnten die Klasse Becher etwas erweitern. Fr den Anwender ist es vielleicht wichtig, zu wissen, ob eine bestimmte Menge noch in den Becher hineinpassen wrde oder nicht. Wir schreiben dazu eine Methode reichtKapazitaet, die die fragliche Menge in Millilitern bergeben bekommt und als Booleschen Wert zurckliefert, ob diese Menge noch in den Becher passt oder nicht:

161

Klassen

bool Becher::reichtKapazitaet(int ml) const { float platz = fassungsvermoegen/100.0F*(100-fuellhoehe); return(platz >= ml); }
Listing 4.20 Die Methode reichtKapazitaet von Becher

Der Ausdruck 100-fuellhoehe liefert die noch freien Kapazitten des Bechers in Prozent, die dann in einen absoluten Wert in Millilitern umgerechnet werden. Das F hinter 100.0 zeigt an, dass es sich um eine Konstante vom Typ float handelt (siehe Abschnitt 1.7.3, Literale). Die beiden Anweisungen des Anweisungsblocks htten auch zu einer zusammengefasst werden knnen:
return(fassungsvermoegen/100.0F*(100-fuellhoehe) >= ml);

Als nchsten Schritt wollen wir eine Methode schreiben, die ein BecherObjekt bergeben bekommt und ermittelt, ob der Inhalt dieses Bechers in den aufrufenden Becher hineinpasst. Aber wie sollen wir die Methode nennen? Der Name reichtKapazitaet ist bereits vergeben. Die Problematik des gleichen Namens tritt nur auf, wenn die gleichnamigen Elemente sich im selben Bezugsrahmen befinden. Sind die Elemente in unterschiedlichen Klassen, Anweisungsblcken oder Namensbereichen definiert, ist ein gleicher Name kein Problem. Glcklicherweise erlaubt C++ unter bestimmten Bedingungen auch Methoden im selben Bezugsrahmen, denselben Namen zu besitzen. Existieren unter einem Namen mehrere Methoden im selben Bezugsrahmen, dann ist dieser Name berladen. Ein Methodenname darf berladen sein, wenn die Methoden sich in der Anzahl ihrer Parameter unterscheiden oder sich Methoden mit gleicher Parameteranzahl in den Typen mindestens einer ihrer Parameter unterscheiden. Die neu zu programmierende Methode reichtKapazitaet bekommt als Parameter ein Objekt des Typs Becher bergeben In reinem C++ wre es blich, das Objekt als Referenz zu bergeben; zur Einstimmung auf C++ unter .NET wird hier ein Zeiger verwendet. Damit unterscheiden sich die Parametertypen, und das berladen ist gltig:
bool Becher::reichtKapazitaet(const Becher* b) const { if(inhalt!=b->inhalt) return(false); return(reichtKapazitaet(

162

Statische Klassenelemente

4.8

static_cast<int>(b->fassungsvermoegen/100.0F* b->fuellhoehe))); }
Listing 4.21 Die zweite Methode reichtKapazitt

Haben Sie eine Vorstellung, warum die Methode als Parameter einen Zeiger auf eine Konstante und nicht auf eine Variable erwartet? Fr die Methode reicht es aus, mit einem konstanten Parameter zu arbeiten, weil die Attribute des bergebenen Objekts nur ausgelesen werden. Der Vorteil liegt aber darin, dass der Methode auch eine Variable bergeben werden knnte. Denn es strt die Variable herzlich wenig, wenn sie als Konstante behandelt wird. Anders herum wre es schon schwieriger. Wrde die Methode eine Variable erwarten, aber eine Konstante bergeben bekommen, dann knnte die Methode die Konstante ndern. Weil das nicht sein darf, meldet der Compiler in solch einem Fall einen Fehler. Schauen wir uns die Implementierung etwas genauer an. Die Objekte der Klasse Becher sollen keine Mischungen enthalten, deswegen wird in der Methode zuerst geprft, ob die beiden Becher dasselbe Produkt beinhalten. Anschlieend wird berechnet, wie viel Milliliter Inhalt der bergebene Becher besitzt und damit dann die erste reichtKapazitaet-Methode aufgerufen. Weil diese Methode einen int-Wert erwartet, das Ergebnis der Berechnung aber float ist, mssen wir dieses vorher mit einem static_cast (siehe Abschnitt Explizite Typumwandlung in Abschnitt 1.9.8) in int umgewandeln. Die dazugehrige Deklaration in der Klassendefinition drfte kein Problem mehr darstellen.

4.8

Statische Klassenelemente

Statische Klassenelemente sind nicht an ein Objekt, sondern an die Klasse gebunden. Was das genau bedeutet und wo es ntzlich sein kann, zeigen die nchsten Abschnitte.

4.8.1

Statische Methoden

Die bisherige Implementierung der Klasse bentigt des fteren eine Umrechnung von prozentualen in absolute Werte. Eine reizvolle Gelegenheit, dafr eine kleine Hilfsmethode berechneAbsolutwert zu programmieren. Weil nicht nur der Absolutwert des Becherinhalts, sondern auch der Absolutwert

163

Klassen

der noch verfgbaren Becherkapazitt bentigt wird, soll die Methode nicht die Attribute des Objekts auslesen, sondern die beiden zur Berechnung notwendigen Werte als Parameter bergeben bekommen:
float Becher::berechneAbsolutwert(float gw, float ps) const { return(gw*ps/100.0F); }
Listing 4.22 Die Methode berechneAbsolutwert

Die Methode bekommt den Grundwert und den Prozentsatz bergeben und liefert den Prozentwert zurck. Um ihren Einsatzbereich mglichst gro zu halten, arbeitet sie nur mit float-Werten. Die beiden reichtKapazitaet-Methoden knnen die neue Methode gut verwenden. Die Methode berechneAbsolutwert wird nur von anderen Methoden derselben Klasse aufgerufen. Sie knnte daher problemlos privates Zugriffsrecht besitzen:
bool Becher::reichtKapazitaet(int ml) const { float platz = berechneAbsolutwert( static_cast<float>(fassungsvermoegen), 100-fuellhoehe); return(platz >= ml); } bool Becher::reichtKapazitaet(const Becher* b) const { if(inhalt!=b->inhalt) return(false); return(reichtKapazitaet( static_cast<int>(berechneAbsolutwert( static_cast<float>(b->fassungsvermoegen), b->fuellhoehe)))); }
Listing 4.23 reichtKapazitaet mit Verwendung von berechneAbsolutwert

Sie berlegen noch, ob der Einsatz dieser Methode wirklich so sinnvoll war, mussten doch zwecks Typkompatibilitt einige Casts hinzugefgt werden, kommen aber zu dem Schluss, dass eine Codeverdopplung vermieden wurde, was in komplexeren Projekten unbedingt erstrebenswert ist, da flattert ein Werbeprospekt Ihrer Bank ins Haus. Es verspricht Ihnen sagenhafte 1,85 % auf Ihr Erspartes. Sie wollen kurz berschlagen, wie viel das im Jahr

164

Statische Klassenelemente

4.8

fr Ihre 3581,44 bringt. Noch ganz in der Euphorie der gelungenen BecherKlasse schwelgend, fllt Ihnen ein, dass dort doch eine Methode programmiert wurde, die genau das berechnen kann. Aber wie rufen Sie die Methode am geschicktesten auf? Methoden mssen immer ber ein Objekt aufgerufen werden, insofern mssen Sie zunchst einen beliebigen Becher konstruieren, der mit Ihrem eigentlichen Problem nichts zu tun hat, um dann die gewnschte Methode aufzurufen. Der Methodenaufruf funktioniert natrlich nur dann, wenn die Methode ffentliches Zugriffsrecht besitzt.
Becher b("BeliebigerInhalt", 300, 0); cout << b.berechneAbsolutwert(1.85F, 3581.44F) << endl;

Auch wenn Sie bei der Betrachtung des Ergebnisses vielleicht froh wren, einen Becher mit entsprechendem Inhalt danebenstehen zu haben, ist es aus Sicht der Programmierung etwas befremdlich, dass zur Berechnung der Sparbuchzinsen ein Becher konstruiert werden muss. Zumal es auch berhaupt keinen Sinn macht, berecheAbsolutwert ber ein Objekt aufzurufen, denn die Methode greift auf keines der Objektattribute zu. In ANSI C++ haben wir eine einfache Mglichkeit: Statt einer Methode berechneAbsolutwert schreiben wir eine entsprechende Funktion, die von Natur aus ohne Objekt aufgerufen werden kann. Nur wollen wir spter unter .NET programmieren, wo es keine Funktionen mehr gibt. Die Lsung lautet, wie der Titel dieses Abschnitts: statische Methoden. Statische Methoden sind nicht an ein Objekt gebunden, sondern an die Klasse. Deswegen werden sie auch Klassenmethoden genannt. Um eine Methode als statisch zu deklarieren, wird vor die Methodendeklaration das Schlsselwort static geschrieben:
static float berechneAbsolutwert(float gw, float ps);

Das const zur Deklaration der Methode als konstanzwahrend ist verschwunden, weil die Methode nicht mehr an ein Objekt gebunden ist und es ihr demnach egal sein kann, ob ein Objekt konstant oder variabel ist. Nun kann die Methode ohne ein Objekt und nur ber den Klassennamen aufgerufen werden:
cout << Becher::berechneAbsolutwert(1.85F, 3581.44F) << endl;

165

Klassen

Hinweis Auch eine statische Methode kann auerhalb der eigenen Klasse nur aufgerufen werden, wenn sie ffentliches Zugriffsrecht besitzt.

Der Aufruf ber ein Objekt ist weiterhin mglich. Wichtig ist nur, dass eine statische Methode nicht auf Attribute ihrer Klasse direkt zugreift, eben weil sie nicht mehr zwingend ber ein Objekt aufgerufen wird.
Tipp Statische Methoden drfen nicht direkt auf Objektdaten der eigenen Klasse zugreifen. Ein Zugriff selbst auf private Elemente anderer Objekte derselben Klasse ist allerdings erlaubt.

4.8.2

Statische Attribute

Analog zu den statischen Methoden sind statische Attribute (auch Klassenattribute genannt) nicht mehr an ein Objekt gebunden. Whrend die bisherigen Attribute fr jedes Objekt einen eigenen Wert annehmen knnen, ist der Wert eines statischen Attributs fr jedes Objekt gleich. Wir knnten zum Beispiel sagen, dass ein Becher, dessen Fassungsvermgen ein gewisses Ma berschreitet, eher zur Kategorie Eimer zhlt und daher nicht mehr von der Klasse Becher abgedeckt werden sollte. Diese Grenze gilt fr alle Objekte der Klasse. Es bietet sich an, hierfr ein Attribut zu verwenden, welches fr alle Objekte denselben Wert hat: ein statisches Attribut. Genau wie statische Methoden wird ein statisches Attribut durch Voranstellen des Schlsselworts static deklariert:
static int maxmenge;

Wie alle anderen Attribute auch wird es im private-Bereich der Klasse untergebracht. Zu klren ist noch, wo das statische Attribut mit einem Wert initialisiert wird. Zusammen mit den anderen Attributen im Konstruktor ist der falsche Ort, denn das statische Attribut wird von allen Objekten geteilt und muss nur einmal initialisiert werden. Die Klasse besitzt bereits einen Platz fr Elemente, die nur einmal bearbeitet werden sollen: die cpp-Datei. Dort hinein setzen wir die Initialisierung des statischen Attributs:
int Becher::maxmenge=1000;

166

typedef

4.9

Durch die erneute Angabe des Datentyps von maxmenge wird dem Compiler mitgeteilt, dass es sich um die Initialisierung handelt. Bese das Attribut ffentliches Zugriffsrecht, dann knnte es auerhalb der Klasse mit Becher::maxmenge angesprochen werden. Im Falle des statischen Attributs maxmenge mssen wir die Frage klren, ob das Attribut berhaupt whrend der Laufzeit gendert werden knnen soll oder ob der Wert einmal zur Kompilationszeit festgelegt wird und dann fr die gesamte Laufzeit gleich bleibt. Um letzteren Fall zu garantieren, knnen Sie das statische Attribut zustzlich noch mit const als konstant deklarieren:
static const int maxmenge;

Solche statischen, konstanten Attribute drfen direkt bei der Definition initialisiert werden, wenn es sich um ganzzahlige Typen handelt:
static const int maxmenge=1000;

4.9

typedef

Mithilfe des Befehls typedef besteht die Mglichkeit, ein Synonym fr einen Datentyp zu erstellen. Die Motivation, ein solches Synonym zu erstellen, ist die gleiche wie die, derentwegen Konstanten verwendet werden. Wir erinnern uns: Der Vorteil einer Konstanten liegt an ihrer zentralen Wertzuweisung:
const float mwst = 1.19F;

Wenn nun im Programm nur noch mwst verwendet wird, dann kann durch eine nderung der oberen Zeile der Mehrwertsteuersatz fr das gesamte Programm verndert werden. Ein typedef funktioniert hnlich nur eben fr Datentypen:
typedef long long int MeinInt;

Fr einen long long int wird ein Synonym namens MeinInt definiert. Der neue Datentyp MeinInt kann nun zur Definition von Objekten des Typs verwendet werden:
MeinInt v;

Auch hier liegt der Vorteil klar auf der Hand. Sollte Bedarf an einem anderen ganzzahligen Datentypen entstehen, dann braucht nur der typedef gendert zu werden, und schon verwendet das gesamte Programm den neuen Typen.

167

Klassen

Nehmen wir als weiteres Beispiel die Methode berechneAbsolutwert von Becher. Der Rckgabetyp der Methode ist float, es knnte aber durchaus sein, dass es vielleicht doch noch int werden soll. Um diesen Wechsel auch noch in einer spteren Phase der Programmentwicklung ohne greren Aufwand zu ermglichen, wird ein typedef verwendet. Der bersichtlichkeit halber werden zugleich die private- und public-Bereiche der Klasse vertauscht:
class Becher { public: typedef float AbsWertTyp; Becher(std::string i, int fa, float fu); void ausgabe() const; bool reichtKapazitaet(int ml) const; bool reichtKapazitaet(const Becher* b) const; static AbsWertTyp berechneAbsolutwert(float gw, float ps); private: std::string inhalt; int fassungsvermoegen; float fuellhoehe; static int maxmenge; };
Listing 4.24 Die Klassendefinition von Becher mit typedef

Der typedef steht im public-Teil der Klasse, damit er von auen zugnglich ist. Wir betrachten gleich noch ein Beispiel dazu, mssen aber zuerst einen Blick auf die Definition von berechneAbsolutwert werfen:
Becher::AbsWertTyp Becher::berechneAbsolutwert(float gw, float ps) { return(static_cast<AbsWertTyp>(gw*ps/100.0F)); }
Listing 4.25 Die Methode berechneAbsolutwert mit typedef

Es ist vielleicht berraschend, dass bei der Methodendefinition der Rckgabetyp nun mit expliziter Klassenangabe (Becher::AbsWertTyp) deklariert wird, obwohl bei der Methodendeklaration ein bloes AbsWertTyp ausreicht. Die Erklrung ist eigentlich ganz einfach: Wenn der Compiler beginnt, den Funktionskopf zu lesen, beginnt er links mit dem Rckgabetyp. Zu diesem Zeitpunkt wei er noch nicht, dass es der Rckgabetyp einer Becher-Methode

168

Verschachtelte Klassen

4.10

ist und wsste auch nicht, wo er AbsWertTyp suchen sollte, denn der Name knnte von mehreren Klassen verwendet worden sein. Erst, nachdem der Compiler mit der Information Becher::berechneAbsolutwert wei, dass die Methode zur Klasse Becher gehrt, kann einfach AbsWertTyp geschrieben werden, wie im Anweisungsblock zu sehen ist. Auch bei den Funktionsparametern htte bereits der Typname ohne Klassenangabe ausgereicht. Der static_cast in der return-Anweisung ist unverzichtbar, weil der Ausdruck einen float-Wert ergibt und umgewandelt werden muss fr den Fall, dass AbsWertTyp nicht float ist. Aus diesem Grund ist auch bei den reichtKapazitaet-Methoden eine Anpassung oder Ergnzung der Casts notwendig, aber das sollten Sie einmal selbst versuchen.

4.10

Verschachtelte Klassen

Klassen knnen innerhalb anderer Klassen definiert werden. Im folgenden Beispiel wird die Klasse LokaleKlasse innerhalb von Hauptklasse definiert:
class Hauptklasse { public: class LokaleKlasse { }; };
Listing 4.26 Eine verschachtelte Klassendefinition

Hinweis Die lokale Klasse ist ein Klassenelement der bergeordneten Klasse. Es gelten die Zugriffsrechte wie bei anderen Klassenelementen auch.

Sie erzeugen ein Objekt der lokalen Klasse von auerhalb so:
Hauptklasse::LokaleKlasse o;

Der Zugriff ist mglich, weil LokaleKlasse im public-Teil von Hauptklasse definiert wurde. Aber wie sehen die Zugriffsrechte zwischen den beiden Klassen aus? Jede Klasse bekommt ein privates Attribut sowie eine Methode test, in der ein Objekt der jeweils anderen Klasse erzeugt und auf das private Attribut zugegriffen wird:

169

Klassen

class Hauptklasse { // Privates Attribut von Hauptklasse int hauptPrivat; public: class LokaleKlasse { // Privates Attribut von LokaleKlasse int lokalPrivat; public: void test() { Hauptklasse ho; ho.hauptPrivat=10; // OK } }; void test() { LokaleKlasse lo; lo.lokalPrivat=10; } };
Listing 4.27 Zugriffsrechte verschachtelter Klassen

// Fehler

Die lokale Klasse gilt als Element von Hauptklasse und hat daher, wie alle Klassenelemente auch, Zugriff auf die privaten Elemente. Anders sieht es aus mit dem Zugriff auf die privaten Elemente der lokalen Klasse von der bergeordneten Klasse aus. Dieser verhlt sich wie ein Zugriff von auen, deshalb sind nur die ffentlichen Elemente ansprechbar. Sollen alle Elemente der lokalen Klasse von der umgebenden Klasse aus ansprechbar sein, dann muss die lokale Klasse die umgebende Klasse als Freund deklarieren:
class Hauptklasse { int hauptPrivat; public: class LokaleKlasse { friend class Hauptklasse; int lokalPrivat; public: void test() { Hauptklasse ho; ho.hauptPrivat=10; // OK

170

Vererbung

4.11

} }; void test() { LokaleKlasse lo; lo.lokalPrivat=10; } };


Listing 4.28

// Fehler

Eine friend-Deklaration

Nun hat auch die uere Klasse Zugriff auf alle Elemente der lokalen Klasse.
Tipp Eine lokale Klasse kann im privaten Bereich der ueren Klasse stehen und wre dann von auen nicht mehr ansprechbar. Dieser Umstand ist ausgesprochen praktisch, wenn Objekte einer Klasse nur innerhalb einer anderen Klasse erstellt werden sollen/ drfen. Wie zum Beispiel eine Listen- oder Baumklasse und ihre Knoten.

4.11

Vererbung

Die Technik der Vererbung ist ein wesentliches Abstraktionsprinzip der objektorientierten Programmierung und dient hauptschlich einem einzigen Zweck: der Erweiterung bestehender Funktionalitten, ohne die bereits vorhandene Implementierung verndern zu mssen. Angenommen, die Klasse Becher soll um die Fhigkeit erweitert werden, einen Aufdruck zu besitzen (wie z. B. Weltbester C++-Programmierer). Sie knnten diese Eigenschaft problemlos in die Klasse Becher einbauen. Dann tritt der Nchste mit einem Wunsch an Sie heran: Die Klasse Becher soll verschiedene Stoffe in unterschiedlicher Menge aufnehmen knnen (z. B. Kaffee, Milch und Zucker) Auch das bauen Sie in die Klasse ein. Nun hat der zuknftige Mischer aber auch noch den vorhin implementierten Aufdruck dabei, obwohl er ihn vielleicht nicht bentigt. Sie knnen dieses Beispiel weiterspinnen, bis Sie eine riesige, monolithische Klasse erhalten, die zwar fast alles kann, von dem der Anwender aber immer nur maximal 10 % bentigt. Dieses Problem wird mit Vererbung umgangen. Ein anderes Problem ist der Wunsch, eine Klasse zu erweitern, deren Quellcode Ihnen nicht zur Verfgung steht. Auch das ist mit Vererbung lsbar.

171

Klassen

4.11.1

Das Wesen der Vererbung

Die in fast allen Fllen verwendete Vererbung ist die ffentliche Vererbung, die ein Verhltnis ist ein(e) zwischen Klassen zum Ausdruck bringt. C++ kennt noch zwei andere Arten der Vererbung.1 Da vom .NET-Framework aber nur die ffentliche Vererbung untersttzt wird, wollen wir uns hier auf sie beschrnken. Als einfaches Studienbeispiel soll die Becher-Klasse, wie im vorigen Abschnitt angesprochen, um die Mglichkeit erweitert werden, einen Aufdruck zu ermglichen. Wir knnen sagen: Ein Becher mit Aufdruck ist ein Becher, der zustzlich noch einen Aufdruck besitzt. Der Kern dieser Aussage lautet: Ein Becher mit Aufdruck ist ein Becher. Das ist auch logisch, denn ein Becher mit Aufdruck kann all das, was auch ein normaler Becher kann, er hat eben nur zustzlich einen Aufdruck. Und genau das macht die Vererbung. Die Klasse, an die vererbt wird (auch Subklasse oder abgeleitete Klasse genannt), erbt alle Eigenschaften der vererbenden Klasse (auch Basisklasse oder Superklasse genannt) und kann diese nach Bedarf erweitern. Bei der Vererbung stehen die ffentlichen Elemente der Basisklasse als ffentliche Elemente der Subklasse zur Verfgung. Die geschtzten Elemente der Basisklasse sind als geschtzte Elemente der Subklasse vorhanden.
Klasse B Unzugnglicher Bereich Private Elemente Klasse A Klasse A Privater Bereich Private Elemente Klasse A Geschtzte Elemente Klasse A ffentliche Elemente Klasse A Klasse A vererbt an Klasse B Geschtzter Bereich Geschtzte Elemente Klasse A ffentlicher Bereich ffentliche Elemente Klasse A

Abbildung 4.4 Das Prinzip der ffentlichen Vererbung


1 Weitere Informationen finden Sie in Willms 2005.

172

Konstruktoren und Vererbung

4.12

Nur: Die privaten Elemente der Basisklasse werden zwar auch an die Subklasse vererbt, sie sind aber von der Subklasse aus nicht ansprechbar. Abbildung 4.4 stellt den Zusammenhang grafisch dar. Es ist vielleicht etwas gewhnungsbedrftig, dass im oberen Fall Klasse B Elemente von Klasse A geerbt hat, auf die sie nicht zugreifen kann. Aber andererseits ist dies eine logische Folge aus der Forderung, dass auf private Elemente einer Klasse nur die Klasse selbst zugreifen darf.

4.11.2

Die Syntax der Vererbung

Die Syntax der ffentlichen Vererbung sieht so aus:


class Subklassenname : public Basisklassenname { // Erweiterungen der Subklasse };

Syntaktisch ist die Vererbung schnell umgesetzt. Die neue Klasse wird zunchst wie in Abschnitt 4.1, Definition einer Klasse, beschrieben mit leerem Anweisungsblock angelegt:
class BecherMitAufdruck { }

Hinter dem Klassennamen wird, durch einen Doppelpunkt getrennt, der Name der Basisklasse (in unserem Fall Becher) angegeben:
class BecherMitAufdruck : public Becher { }
Listing 4.29 Die Syntax der Vererbung

Das Schlsselwort public vor dem Basisklassennamen leitet die von uns gewnschte ffentliche Vererbung ein.

4.12

Konstruktoren und Vererbung

Allerdings werden Sie feststellen, dass der Compiler einen Fehler meldet. Und zwar beschwert er sich ber einen fehlenden Standardkonstruktor in der Klasse Becher. Ein guter Zeitpunkt also, ein wenig die Konstruktion von

173

Klassen

Objekten abgeleiteter Klassen zu beleuchten. Wir knnen der Lsung bereits mit einigen einfachen Fragen erstaunlich nah kommen: Wie wird in einer Klasse ein Objekt erzeugt? ber den Konstruktor. Wie konstruiert ein Konstruktor ein Objekt? Indem er entweder im Anweisungsblock oder in der Elementinitialisierungsliste die Attribute des Objekts initialisiert meist mithilfe bergebener Argumente. Hier tritt schon der erste Fauxpas zutage: Unser Konstruktor besitzt noch keine sinnvollen Parameter. Diese kleine formale Unschrfe einmal kurz beiseite geschoben, tritt ein neues Problem auf: Wie soll der Konstruktor von BecherMitAufdruck die Objektattribute initialisieren? Einfach durch Zuweisung oder ber die Elementinitialisierungsliste eher nicht, denn wie Abbildung 4.4 schn darstellt, knnen die Elemente der Subklasse und dazu gehrt der Konstruktor von BecherMitAufdruck nicht auf die privaten Elemente der Basisklasse zugreifen. Wir bruchten also etwas, dass auf der einen Seite Zugriff auf die privaten Elemente der Basisklasse hat es kommt also nur ein Element der Basisklasse in Frage und auf der anderen Seite in der Lage ist, die Basisklassenattribute zu initialisieren. Einmal kurz ber den Quellcode von Becher meditiert, findet sich der geeignete Kandidat: Der Konstruktor von Becher. Er ist ffentlich und daher von der Basisklasse aus ansprechbar, und er wei, wie die Attribute zu initialisieren sind. Wir brauchen also nur vom Subklassenkonstruktor aus den Basisklassenkonstruktor aufzurufen. Dummerweise bentigt der Basisklassenkonstruktor zur Initialisierung der Attribute drei Argumente. Damit ihm diese Argumente bergeben werden knnen, muss der Subklassenkonstruktor diese vom Anwender einfordern. Die grobe Richtung steht also fest: Der Basisklassenkonstruktor bentigt drei Argumente, die der Subklassenkonstruktor ber eigene Parameter beschaffen muss. Betrachten wir dazu kurz die Datei BecherMitAufdruck.cpp, die den aktualisierten Konstruktor enthlt:
#include "BecherMitAufdruck.h" using namespace std; BecherMitAufdruck::BecherMitAufdruck(string i, int fa, float fu) { }
Listing 4.30 Die Datei BecherMitAufdruck.cpp

174

Konstruktoren und Vererbung

4.12

Die Konstruktordeklaration in der Klassendefinition muss ebenfalls mit den Parametern versehen werden. Kompilieren lsst sich das Programm aber immer noch nicht, denn wir haben den Basisklassenkonstruktor noch nicht aufgerufen.
Hinweis Der Basisklassenkonstruktor wird in der Elementinitialisierungsliste des Subklassenkonstruktors aufgerufen.

Der endgltige Konstruktor sieht so aus:


BecherMitAufdruck::BecherMitAufdruck(string i, int fa, float fu) : Becher(i, fa, fu) { }
Listing 4.31 Aufruf des Basisklassenkonstruktors

Und wenn Sie sich fragen, wie der ursprngliche Fehler entstanden ist und was er bedeutet: Es muss immer ein Basisklassenkonstruktor aufgerufen werden.
Achtung In C++ muss der Subklassenkonstruktor einen Basisklassenkonstruktor aufrufen.

Welcher Konstruktor aufgerufen wird es knnen durch berladung mehrere existieren , spielt keine Rolle. Diese Regel ist so wichtig, dass selbst dann ein Basisklassenkonstruktor aufgerufen wird, wenn der Programmierer keinen expliziten Aufruf angegeben hat.
Hinweis Besitzt der Subklassenkonstruktor keinen expliziten Aufruf eines Basisklassenkonstruktors, dann wird der Standardkonstruktor der Basisklasse aufgerufen.

Als Standardkonstruktor wird der Konstruktor ohne Parameter bezeichnet. Zu Beginn wurde also automatisch dieser Standardkonstruktor von Becher aufgerufen. Da wir einen solchen aber nicht programmiert haben, kam die Fehlermeldung kein geeigneter Standardkonstruktor verfgbar.

175

Klassen

Nun endlich kann ein Objekt der neuen Klasse erstellt werden:
BecherMitAufdruck b("Milch", 300, 90);

Die Klasse BecherMitAufdruck hat durch die Vererbung alle Fhigkeiten der Basisklasse Becher geerbt. Ohne weiter entwicklerisch aktiv werden zu mssen, kann ein BecherMitAufdruck-Objekt daher ber die geerbte Methode ausgabe ausgegeben werden:
b.ausgabe();

Alle anderen in Becher implementierten Methoden sind ebenfalls verfgbar.

4.13

Erweitern durch Vererbung

Der bisher betriebene Aufwand fr die Klasse BecherMitAufdruck hat uns zu dem Punkt gebracht, dass sie exakt die gleichen Fhigkeiten besitzt wie ihre Basisklasse Becher. Das klingt nicht nach einem groartigen Gewinn, aber: Wir haben ber die Vererbung die Funktionalitten von Becher bernommen, ohne die Klasse Becher verndern zu mssen. Diese Technik funktioniert daher auch bei Klassen, auf deren Quellcode wir keinen Zugriff haben (in diese Rubrik fallen die spter besprochenen Klassen der .NET-Bibliothek). Die Klasse BecherMitAufdruck kann jetzt allerdings nach Belieben erweitert werden und soll im weiteren Verlauf ihrem Namen alle Ehre machen. Der Becheraufdruck soll in der Klasse als Text gespeichert werden. Wir bentigen dazu in BecherMitAufdruck ein entsprechendes Attribut. Damit dieses Attribut initialisiert werden kann, muss der Konstruktor um einen Parameter erweitert werden:
class BecherMitAufdruck : public Becher { std::string aufdruck; public: BecherMitAufdruck(std::string i, int fa, float fu, std::string auf); };
Listing 4.32 Die Klasse BecherMitAufdruck mit neuem Attribut

Das neue Attribut steht direkt am Klassenanfang und besitzt deshalb implizit privates Zugriffsrecht. Im oberen Beispiel wurde der zustzliche Konstruktorparameter an die Parameterliste angehngt. Die Parameterreihenfolge ist allerdings beliebig und kann nach Bedarf verndert werden. Die Parameter-

176

Methoden berschreiben

4.14

listen von Deklaration und Definition mssen aber bereinstimmen. Die Definition des Konstruktors sieht so aus:
BecherMitAufdruck::BecherMitAufdruck(string i, int fa, float fu, string auf) : Becher(i, fa, fu), aufdruck(auf) { }
Listing 4.33 Der neue Konstruktor von BecherMitAufdruck

Weitere Funktionalitt kann auf diese Weise nach Belieben hinzugefgt werden. Eine Zugriffsmethode fr den Aufdruck wre beispielsweise nicht schlecht:
std::string getAufdruck() const { return(aufdruck); }
Listing 4.34 Die Zugriffsmethode getAufdruck

Oben ist nur die Definition der Methode zu sehen. Handelt es sich bei dieser Definition um eine externe oder um eine Inline-Definition? Wre es eine externe Definition, msste angegeben werden, zu welcher Klasse die Methode gehrt (BecherMitAufdruck::getAufdruck), insofern muss es sich um eine Methodendefinition innerhalb der Klassendefinition handeln. Falls Sie nicht mehr genau wissen, welche Auswirkungen das const hinter dem Methodenkopf hat, dann schlagen Sie schnell in Abschnitt 4.6, Konstanzwahrende Methoden, nach.

4.14

Methoden berschreiben

Wir wissen aus den vorigen Abschnitten, dass in der Klasse BecherMitAufdruck durch die Vererbungsbeziehung zu Becher deren Methode ausgabe geerbt wurde und aufgerufen werden kann. Nun wre es fr die Klasse BecherMitAufdruck aber schn, wenn bei der Ausgabe auch der Becheraufdruck ausgegeben wrde. Aber wie stellen wir das an? Die bestehende Methode ausgabe entsprechend abzundern, ist aus einem einfachen Grund nicht mglich: Die Methode gehrt zu Becher, und dort

177

Klassen

existiert das auszugebende Attribut aufdruck noch nicht. Die Konsequenz daraus fhrt zu einer neuen Methode fr BecherMitAufdruck. Eine Methode mit demselben Namen ist auf den ersten Blick nicht mglich, denn die Parameterliste wrde sich nicht von der geerbten Methode unterscheiden und ein berladen unmglich machen. Eine Methode mit anderem Namen zu implementieren ist auch keine saubere Lsung, denn es stnde weiterhin die geerbte ausgabe-Methode zur Verfgung, die den Aufdruck nicht mit ausgibt. Die Lsung ist erschreckend simpel: Wenn wir in BecherMitAufdruck eine Methode ausgabe implementieren, dann ist das kein berladen. Wir erinnern uns (siehe Abschnitt 4.7, berladen von Methoden): berladen ist ein Name nur dann, wenn er mehrfach im selben Bezugsrahmen definiert ist. Die geerbte Methode gehrt zum Bezugsrahmen Becher, die neue Methode wird zum Bezugsrahmen BecherMitAufdruck gehren. Unterschiedliche Bezugsrahmen, daher kein berladen, vielmehr: ein berschreiben.
Hinweis Wird in einer abgeleiteten Klasse eine Methode mit gleichem Namen und Parameterliste einer geerbten Methode definiert, dann wird die geerbte Methode mit der neuen Methode berschrieben.

Praktisch heit das: Wir knnen in BecherMitAufdruck eine neue Methode


ausgabe definieren und haben damit einfach die alte berschrieben: void BecherMitAufdruck::ausgabe() const { cout << "Becher mit " << inhalt << " und Aufdruck \"" << aufdruck << "\"" << endl; }
Listing 4.35 Erster Versuch einer Methode ausgabe

Fertig? Mitnichten! Die Methode wird bei der Kompilation einen Fehler melden. Der Grund msste Ihnen bereits bekannt sein, wird aber im folgenden Abschnitt noch genauer besprochen.

4.15

Geschtzte Attribute

Das Problem bei der Methode aus Listing 4.35 liegt im Zugriff auf das Attribut inhalt. Es handelt sich hierbei um ein privates Attribut von Becher, weshalb eine Methode von BecherMitAufdruck keinen Zugriff darauf hat. Wir

178

Geschtzte Attribute

4.15

haben nun zwei Mglichkeiten. Die erste besteht darin, das Attribut mit geschtztem Zugriffsrecht zu versehen. Wie in Abschnitt 4.3, Zugriffsrechte, bereits beschrieben wurde, erlaubt das geschtzte Zugriffsrecht auch abgeleiteten Klassen den Zugriff. Eigentlich genau das, was wir brauchen:
class Becher { public: typedef float AbsWertTyp; Becher(std::string i, int fa, float fu); void ausgabe() const; bool reichtKapazitaet(int ml) const; bool reichtKapazitaet(const Becher* b) const; static AbsWertTyp berechneAbsolutwert(float gw, float ps); protected: std::string inhalt; private: int fassungsvermoegen; float fuellhoehe; static int maxmenge; };
Listing 4.36 Die Klasse Becher mit geschtztem Attribut inhalt

Nun lsst sich das Programm problemlos kompilieren. Aber: Eine vollstndige Datenkapselung ist nicht mehr gewhrleistet, weil auf inhalt nun jede abgeleitete Klasse zugreifen kann. Dieses Zugriffsrecht bezieht sich zwar nur auf das geerbte Attribut, und eine Methode der Klasse BecherMitAufdruck ist nicht in der Lage, auf das inhalt-Attribut eines Becher-Objekts zuzugreifen. Trotzdem kann eine abgeleitete Klasse eventuell von der Basisklasse auferlegte Beschrnkungen umgehen. Aus diesem Grund sieht ein sauberer Ansatz weiterhin private Attribute vor und ermglicht den Zugriff ber Methoden, die nach Bedarf dann mit geschtztem Zugriffsrecht versehen werden knnen. Dazu machen wir das Attribut inhalt wieder privat und erweitern die Klasse um eine ffentliche Methode getInhalt:
class Becher { public: typedef float AbsWertTyp; Becher(std::string i, int fa, float fu); void ausgabe() const; bool reichtKapazitaet(int ml) const;

179

Klassen

bool reichtKapazitaet(const Becher* b) const; static AbsWertTyp berechneAbsolutwert(float gw, float ps); std::string getInhalt() const { return(inhalt); } private: std::string inhalt; int fassungsvermoegen; float fuellhoehe; static int maxmenge; };
Listing 4.37 Die Klasse Becher mit getInhalt

Die Methode ausgabe von BecherMitAufdruck muss nun nur noch entsprechend angepasst werden:
void BecherMitAufdruck::ausgabe() const { cout << "Becher mit " << getInhalt() << " und Aufdruck \"" << aufdruck << "\"" << endl; }
Listing 4.38 Die endgltige Fassung von BecherMitAufdruck::ausgabe

4.16

Polymorphie

Wir wissen mittlerweile, dass die ffentliche Vererbung eine Beziehung ist ein(e) darstellt. In den letzten Abschnitten haben wir deshalb ber die Vererbung zum Ausdruck gebracht, dass ein Becher mit Aufdruck ein Becher ist. Im Umkehrschluss muss es deshalb mglich sein, einen Becher mit Aufdruck als gewhnlichen Becher zu behandeln, denn er ist ja ein Becher. Dieser Sachverhalt wird in der objektorientierten Programmierung Polymorphie genannt.
Polymorphie berall dort, wo ein Objekt der Basisklasse erwartet wird, kann auch ein Objekt einer abgeleiteten Klasse verwendet werden.

Nehmen wir im einfachsten Fall einen Zeiger vom Typ Becher:


Becher *bptr;

180

Polymorphie

4.16

Weil ein Becher mit Aufdruck ein Becher ist, kann die Adresse eines BecherMitAufdruck-Objekts einem Becher-Zeiger zugewiesen werden:
BecherMitAufdruck b("Milch", 300, 90, "Meine Privattasse"); bptr = &b;

ber diesen Zeiger knnen dann die Becher-Methoden des Objekts aufgerufen werden:
cout << bptr->getFuellmenge() << endl;

Achtung Polymorphie funktioniert nur mit Zeigern und Referenzen.

ber den Becher-Zeiger knnen allerdings keine speziellen Methoden der Klasse BecherMitAufdruck aufgerufen werden, denn der Zeiger wei schlielich nicht, dass das Becher-Objekt, auf das er zeigt, in Wirklichkeit ein BecherMitAufdruck-Objekt ist. Praktischer Einsatz der Polymorphie knnte eine maximum-Funktion sein, die zwei Becher-Objekte bergeben bekommt und den Becher mit mehr Inhalt zurckliefert:
Becher* maximum(Becher* b1, Becher* b2) { if(b1->getFuellmenge()>=b2->getFuellmenge()) return(b1); else return(b2); }
Listing 4.39 Eine maximum-Funktion fr Becher

Die Funktion ist so geschrieben, dass bei gleicher Fllmenge das erste Objekt zurckgegeben wird, genauso verhalten sich auch die Funktionen der C++Standardbibliothek. Wegen der Polymorphie knnen maximum beliebige Becher bergeben werden, solange deren Klassen von Becher abgeleitet sind. Diese Art der Programmierung bietet ein enormes Ma an Wiederverwendbarkeit, denn es kann Programmcode geschrieben werden, der mit Objekten arbeitet, an die der Programmierer bis dato nicht einmal gedacht hat.

181

Klassen

4.17

Virtuelle Methoden

Im Rahmen der Polymorphie knnen Effekte auftreten, die vielleicht nicht auf Anhieb klar sind. Nehmen wir folgendes Beispiel:
BecherMitAufdruck b("Milch", 300, 90, "Privattasse"); Becher *bptr = &b; bptr->ausgabe();

Was wird ausgegeben? Oder, programmtechnisch gefragt, welche ausgabeMethode wird aufgerufen, die von Becher oder die von BecherMitAufdruck? Es gibt zwei Argumente: 1. Die ausgabe-Methode von BecherMitAufdruck wird aufgerufen, weil das Objekt ein BecherMitAufdruck-Objekt ist. 2. Die ausgabe-Methode von Becher wird aufgerufen, weil der Zeiger vom Typ Becher* ist. Im Normalfall werden Datentypen zur Kompilationszeit geprft. Dies wird statische Typberprfung genannt. Und zur Kompilationszeit zeigt bptr auf ein Becher-Objekt, weil bptr vom Typ Becher* ist. Es spielt dabei keine Rolle, dass dem Zeiger whrend des Programmlaufs ein BecherMitAufdruck-Objekt zugewiesen wird. Es wird also die ausgabe-Methode von Becher aufgerufen. Obwohl programmtechnisch nachvollziehbar, ist das Verhalten nicht unbedingt erwnscht. Denn nur, weil der Zugriff auf das Objekt ber einen Zeiger vom Typ Becher* stattfindet, sollte trotzdem die richtige ausgabe-Methode aufgerufen werden. Um die richtige ausgabe-Methode aufzurufen, muss das Programm den tatschlichen Typ des Objekts, auf das bptr zeigt, zur Laufzeit prfen. Dies wird dynamische Typberprfung genannt. Aktiviert wird die dynamische Typberprfung fr eine Methode, indem vor der Deklaration in der Basisklasse das Schlsselwort virtual geschrieben wird. Daher wird eine solche Methode in C++ auch virtuelle Methode genannt. Fr die dynamische Typberprfung spielt es keine Rolle, wie weit der tatschliche Typ in der Klassenhierarchie von der Basisklasse entfernt ist. Sollte beispielsweise von der Klasse BecherMitAufdruck eine Klasse BecherMitAufdruckUndBild abgeleitet werden, dann wrde ber den Becher-Zeiger, wenn er auf ein solches Objekt zeigt, die ausgabe-Methode von BecherMitAufdruckUndBild aufgerufen falls die Methode in der Klasse existiert. Es muss sich aber um eine tatschliche berschreibung handeln; die Methode

182

UML

4.18

in der Subklasse muss genauso heien wie die berschriebene Methode und in Parameterliste und Rckgabetyp komplett bereinstimmen.
Hinweis Virtuelle Methoden gewhrleisten, dass sich das Verhalten eines Objekts nicht ndert, wenn ber einen Basisklassenzeiger darauf zugegriffen wird. Das sogenannte LSP (Liskovsche Substitutionsprinzip) wird damit eingehalten.

Dynamische Typberprfung funktioniert nur mit nicht-statischen Methoden. Ein Grund mehr, auf den direkten Attributzugriff zu verzichten.

4.18

UML

Um im weiteren Verlauf des Buchs eine mglichst klare und einfache Darstellungsform fr Klassen und deren Beziehungen einsetzen zu knnen, die darber hinaus auch noch weit verbreitet ist, wollen wir diesen Abschnitt dem Klassendiagramm der UML (Unified Modeling Language) widmen. Die UML definiert einen Satz an Diagrammen, mit denen dynamische und statische Eigenschaften von Methoden und Klassen dargestellt werden knnen. Die bisher verwendeten Diagramme zur Darstellung des Programmflusses in diesem Buch sind Aktivittsdiagramme der UML.
Becher -inhalt : string -fassungsvermoegen : int -fuellhoehe : float -maxmenge : int = 1000 +Becher(in i : string, in fa : int, in fu : float) +ausgabe() +reichtKapazitaet(in ml : int) : bool +reichtKapazitaet(in b : const Becher*) : bool +berechneAbsolutwert(in gw : float, in ps : float) : AbsWertTyp +getInhalt() : string +getFuellmenge() : int

BecherMitAufdruck -aufdruck : string +BecherMitAufdruck(in i : string, in fa : int, in fu : float, in auf : string) +getAufdruck() : string +ausgabe()
Abbildung 4.5 Die Becher-Hierarchie im UML-Klassendiagramm

183

Klassen

Um den statischen Aufbau von Klassen und deren Beziehungen untereinander darzustellen, wird das UML-Klassendiagramm eingesetzt. In Abbildung 4.5 ist die aktuelle Becher-Hierarchie als UML-Klassendiagramm dargestellt. Im Klassendiagramm wird eine Klasse durch ein in drei Bereiche aufgeteiltes Rechteck reprsentiert. Der obere Bereich beinhaltet den Klassennamen, darunter stehen die Attribute und im untersten Abschnitt die Methoden. Vor den Attributen und Methoden wird mit einem Zeichen das Zugriffsrecht des Elements angezeigt. Dabei steht:
- fr privates Zugriffsrecht # fr geschtztes Zugriffsrecht + fr ffentliches Zugriffsrecht

Hinter Attributen steht, durch einen Doppelpunkt getrennt, deren Typ und dahinter optional mit Gleichheitszeichen der Initialisierungswert. Der Initialisierungswert wird nur angegeben, wenn er allgemeingltig ist, was nur bei statischen Elementen der Fall ist. Statische Elemente sind unterstrichen. Besitzt eine Methode einen Rckgabetyp, dann steht er, durch Doppelpunkt getrennt, hinter der Parameterliste. Ein Parameter besteht aus seinem Namen und seinem dahinter mit Doppelpunkt folgenden Datentyp. Optional kann vor dem Parameternamen noch die bergaberichtung spezifiziert werden. Hier gibt es drei Mglichkeiten:
in: Der Parameter dient nur zur Wertbergabe an die Methode. Fr ihn

muss beim Aufruf ein gltiges Argument angegeben werden. Entspricht der Wert- oder Adressbergabe in C++.
inout: Fr diesen Parameter muss beim Aufruf ein gltiges Argument

angegeben werden. nderungen am Parameter innerhalb der Methode wirken sich jedoch auf das beim Aufruf bergebene Argument aus. Entspricht in C++ der bergabe als Referenz.
out: Das an die Methode bergebene Argument dient nur dazu, innerhalb der Methode beschrieben zu werden und muss beim Aufruf keinen gltigen Wert besitzen. Es wird innerhalb der Methode beschrieben und vom Aufrufer ausgelesen. Diese Variante wird von C++ nicht direkt untersttzt, kann aber mit Referenzen simuliert werden.

Die Vererbung wird durch den Generalisierungspfeil zum Ausdruck gebracht, der immer von der Subklasse zur Basisklasse weist. Im weiteren Verlauf werden jeweils bei Bedarf noch einige Darstellungselemente hinzukommen.

184

Schnittstellen

4.19

4.19

Schnittstellen

Wir knnen nun fr die Klasse Becher eine Bibliothek an Funktionen programmieren und wissen, dass jede Subklasse von Becher, egal wie tief sie in der Hierarchie liegen mag, mit dieser Bibliothek arbeiten kann. Und mit den virtuellen Methoden ist gewhrleistet, dass sich das Verhalten der Subklassenobjekte nicht ndert, wenn ber einen Basisklassenzeiger auf sie zugegriffen wird.
Hinweis Unter .NET gibt es keine Funktionen. Dort wird eine solche Funktionsbibliothek durch statische Methoden einer Klasse realisiert.

Da wir fr die Erweiterung der Klassenhierarchie keine nderungen an der Bibliothek vornehmen mssen, ist die auf Becher basierende Klassenhierarchie beliebig erweiterbar. Auch die Funktionsbibliothek kann um Funktionen erweitert werden, ohne dass die tatschlichen Subklassen von Becher bekannt sein mssen. Wir sehen, die Vererbung erlaubt ein hohes Ma an Wiederverwendbarkeit, wie es ohne sie nicht mglich wre. Der bisherige Ansatz hat nur noch einen Schnheitsfehler: Jede Klasse, die mit der bestehenden Funktionsbibliothek zusammenarbeiten will, muss von der Klasse Becher oder einer ihrer Subklassen abgeleitet werden. Was aber, wenn die Objekte der neuen Klasse gar keine Becher sind? Das Problem entstand dadurch, dass die Funktionsbibliothek fr eine konkrete Klasse programmiert wurde. Geschickter wre es, wenn die Bibliothek nicht von einer konkreten Klasse, sondern lediglich von einer bestimmten Fhigkeit oder Funktionalitt abhngig wre. Nehmen wir als einfaches Beispiel die Funktionalitt der Ausgabe. Wir wollen Funktionen schreiben, die von den zu bearbeitenden Objekten lediglich die Fhigkeit erwarten, ausgegeben werden zu knnen. Dies soll ber eine Methode ausgabe geschehen. Wir knnten denselben Fehler wie vorhin begehen und die Funktionen mit Zeigern des Typs Becher* versehen, denn Becher besitzt eine ausgabe-Methode. Wollten wir dann aber vielleicht eine geometrische Form ausgeben, dann msste diese von Becher erben, womit es zu der merkwrdigen Aussage Eine geometrische Form ist ein Becher kme, nur um die Funktionsbibliothek nutzen zu knnen.

185

Klassen

Um solch unrealistische Beziehungen zu vermeiden, mssen wir die Basisklasse weiter abstrahieren. Wenn die Funktionsbibliothek nur eine ausgabeMethode bentigt, dann sollte die oberste Basisklasse auch nur diese ausgabe-Methode besitzen. Wir werden diese Klasse IAusgabe nennen (das vorangestellte I steht fr Interface, auf Deutsch Schnittstelle) und als InlineKlasse also ohne Quellcodedatei erstellen. Die Header-Datei sieht so aus:
#pragma once #include <iostream> class IAusgabe { public: virtual void ausgabe() const { std::cout << "Basis-Ausgabe" << std::endl; } };
Listing 4.40 Die Basisklasse IAusgabe

Die Methode ausgabe mssen wir als virtuell deklarieren, damit eine berschreibung in der Subklasse korrekt aufgerufen wird. Die Klasse steht nicht im Namensbereich Getraenke, weil sie auch als Basisklasse anderer Hierarchien dienen kann. Als primitives Beispiel einer auf dieser Basisklasse fuenden Bibliothek soll die folgende Funktion dienen:
void simpleAusgabe(IAusgabe* obj) { obj->ausgabe(); }
Listing 4.41 Die Funktion simpleAusgabe

Zugegeben, diese Funktion ist nicht wrdig, in eine praxisrelevante Bibliothek aufgenommen zu werden, sie demonstriert aber auf einfache Weise, worauf es ankommt. Auch eine komplexe Funktion wird nicht anders auf das Objekt zugreifen. Noch kann die Klasse Becher aber nicht von der neuen Bibliothek bearbeitet werden, denn sie hat IAusgabe noch nicht als Basisklasse. Das ist schnell nachgeholt:
#pragma once #include "IAusgabe.h" #include <string>

186

Schnittstellen

4.19

class Becher : public IAusgabe { // Klasseninhalt };


Listing 4.42 Becher als Subklasse von IAusgabe

Alles bestens. Aber noch immer hat der Ansatz drei Schnheitsfehler, die eine gemeinsame Ursache teilen. Betrachten wir IAusgabe etwas genauer. Sie besitzt eine Methode ausgabe, auf die die Klassenbibliothek zugreift und die von den Subklassen mit ihren spezifischen Methoden berschrieben wird. Die Methode in IAusgabe wird aber nie etwas Sinnvolles ausgeben knnen, weil sie nicht wissen kann, welche Klassen alle von ihr ableiten. Trotzdem mussten wir die Methode mit einem Anweisungsblock ausstatten, der einen sinnlosen Text ausgibt. Gut, die Ausgabe des Textes hat theatralische Grnde, denn der Anweisungsblock htte auch leer bleiben knnen. Eine leere Methode ist aber auch kein sehr viel sauberer Ansatz. Aber was wirklich verrckt ist: Obwohl die Klasse nichts macht, kann von ihr ein Objekt erzeugt und an eine der Bibliotheksfunktionen bergeben werden:
IAusgabe o; simpleAusgabe(&o);

Und noch schlimmer: Da eine von ihr abgeleitete Klasse die unsinnige ausgabe-Methode erbt, ist die Subklasse nicht gezwungen, eine eigene ausgabe-Methode zu implementieren:
class Teddybaer : public IAusgabe { std::string fellfarbe; public: Teddybaer(std::string ff) : fellfarbe(ff) {} };
Listing 4.43 Die Klasse Teddybaer

Wenn jetzt jemand einen Teddybren mit der gewnschten Fellfarbe konstruiert, dann wird er bei der Ausgabe vermutlich mit einer Erwhnung eben jener Farbe rechnen, aber Pustekuchen:
Teddybaer baer("schwarz"); baer.ausgabe(); // Aufruf von IAusgabe::ausgabe

187

Klassen

All das sind Aufflligkeiten im Verhalten der Klassenhierarchie, die Sie im Optimalfall vermeiden sollten.

4.19.1 Rein virtuelle Methoden


Es gibt in der objektorientierten Programmierung glcklicherweise die Mglichkeit, die Existenz einer Methode einzufordern, ohne sie selbst implementieren zu mssen. Solche Methoden werden abstrakte Methoden genannt. C++ bezeichnet sie auch als rein virtuelle Methoden. Deklariert werden sie durch ein =0 hinter dem Methodenkopf. Schauen wir uns das einmal praktisch an der Klasse IAusgabe an:
class IAusgabe { public: virtual void ausgabe() const =0; };
Listing 4.44 Die Klasse IAusgabe mit rein virtueller Methode

Es fllt Ihnen sicher auf, dass es sich bei der rein virtuellen Methode nur noch um eine Deklaration handelt. Eine Definition also ausfhrbarer Programmcode ist nicht mehr notwendig. Doch die abstrakte Methode hat weitreichende Konsequenzen, denn dadurch wird die Klasse selbst ebenfalls abstrakt.
Hinweis Eine Klasse mit abstrakten Methoden ist eine abstrakte Klasse. Von abstrakten Klassen kann kein Objekt erzeugt werden.

Demnach kann IAusgabe nicht mehr instanziiert werden:


IAusgabe o; // Fehler

Eine abstrakte Methode wird erst dann konkret, wenn sie in einer Subklasse mit einer konkreten Methode also einer Methode mit Definition berschrieben wurde. Dies ist bisher in der Klasse Teddybaer noch nicht geschehen. Die Klasse hat die abstrakte Methode von IAusgabe geerbt und ist damit ebenfalls eine abstrakte Klasse. Um einen Teddybren erzeugen zu knnen, muss ausgabe berschrieben werden:
class Teddybaer : public IAusgabe { std::string fellfarbe; public: Teddybaer(std::string ff)

188

Schnittstellen

4.19

: fellfarbe(ff) {} void ausgabe() const { std::cout << "Baer mit Fellfarbe " << fellfarbe << std::endl; } };
Listing 4.45 Die Klasse Teddybaer mit eigener Methode ausgabe

Nun kann auch wieder ein Teddybaer-Objekt erzeugt werden. Auf diese Weise wird jeder Programmierer, der von IAusgabe ableitet, gezwungen, eine ausgabe-Methode zu programmieren. Leider kann er nicht gezwungen werden, eine sinnvolle Methode zu implementieren, aber dass er eine implementieren muss, ist viel wert. Denn die auf ausgabe zurckgreifende Bibliothek hat die Gewissheit, dass immer eine ausgabe-Methode vorhanden ist. Denn eine Klasse ohne ausgabe-Methode ist abstrakt; von ihr kann kein Objekt erzeugt werden. Eine abstrakte Klasse muss dabei nicht zwangslufig von der direkt folgenden Subklasse implementiert werden. Im Gegenteil, eine Subklasse kann noch weitere abstrakte Methoden hinzufgen, wie IGeometrischeFigur zeigt:
class IGeometrischeFigur : public IAusgabe { public: virtual double umfang() const =0; virtual double flaeche() const =0; };
Listing 4.46 Die Klasse IGeometrischeFigur

Die Klasse IGeometrischeFigur knnte die Basisklasse einer anderen Bibliothek sein, die mit Flchen und Umfngen arbeitet. Weil sie von IAusgabe abgeleitet ist, knnen ihre Subklassen zustzlich noch die Ausgabebibliothek verwenden, die bisher nur aus der Methode simpleAusgabe besteht. Eine Subklasse von IGeometrischeFigur muss die abstrakten Methoden von IGeometrischeFigur und IAusgabe implementieren, damit Objekte von ihr erzeugt werden knnen. Als Beispiel sei hier die Klasse Rechteck vorgestellt:
class Rechteck : public IGeometrischeFigur { double x,y,b,h; public: Rechteck(double x, double y, double b, double h)

189

Klassen

: x(x), y(y), b(b), h(h) {} void ausgabe() const { std::cout << "Rechteck mit Breite " << b << " und Hoehe " << h << std::endl; } double umfang() const { return(2*b+2*h); } double flaeche() const { return(b*h); } };
Listing 4.47 Die Klasse Rechteck

Etwas merkwrdig mag der Konstruktor erscheinen, weil die Parameter denselben Namen wie die Attribute haben. Obwohl es problemlos funktioniert, sollten Sie es in der Praxis vermeiden. Da Sie in Ihrer C++-Karriere aber aller Wahrscheinlichkeit nach auch Code anderer Personen in die Finger bekommen werden, kann es nicht schaden, in loser Folge einige Unarten zu demonstrieren, damit Sie gewappnet sind. Deswegen gleich eine Frage: Welches h wird in der Ausgabe des Konstruktors unten ausgegeben?
Rechteck(double x, double y, double b, double h) : x(x), y(y), b(b), h(h) { std::cout << h << std::endl; }

In diesem Fall spielt es natrlich keine Rolle, welches h ausgegeben wird, weil beide den gleichen Wert haben. Es gibt aber Situationen, in denen die Frage entscheidend ist. Die Frage kann mit einer anderen Frage beantwortet werden: Welches h ist lokaler? Eindeutig der Parameter. Deshalb wird in der Ausgabe auch der Inhalt des Parameters ausgegeben. Aber wie kann das Attribut trotzdem angesprochen werden, obwohl es vom Parameter verdeckt ist? Wir verwenden einfach den Objektzeiger this (siehe Abschnitt 4.4.2, this):
Rechteck(double x, double y, double b, double h) : x(x), y(y), b(b), h(h) { std::cout << h << std::endl; // Parameter std::cout << this->h << std::endl; // Attribut }

190

Schnittstellen

4.19

Schauen wir uns abschlieend noch in Abbildung 4.6 die aktuelle Klassenhierarchie an:
IAusgabe +ausgabe()

IGeometrischeFigur +umfang() : double +flaeche() : double

Becher

BecherMitAufdruck

Rechteck -x : double -y : double -b : double -h : double +Rechteck(ein x : double, ein y : double, ein b : double, ein h : double) +ausgabe() +umfang() : double +flaeche() : double

Abbildung 4.6

Die aktuelle Klassenhierarchie

Die Klassen Becher und BecherMitAufdruck sind ohne ihre Attribute und Methoden dargestellt, weil sich an ihnen nichts gendert hat. Wo vorhanden, sind die Namensbereiche der Klassen mit angegeben. Im Diagramm ist zu erkennen, dass abstrakte Klassen und Methoden kursiv dargestellt werden.

4.19.2 Rein abstrakte Klassen


Klassen, die als Elemente nur abstrakte Methoden besitzen, werden als rein abstrakte Klassen bezeichnet. Sie entsprechen in Standard-C++ den Schnittstellen, die nicht direkt von C++ untersttzt werden, unter .NET (siehe Abschnitt 9.14, Schnittstellen) aber eine wichtige Rolle spielen. Grundstzlich gilt als Regel von der es natrlich auch Ausnahmen gibt , dass ganz oben in der Klassenhierarchie immer eine Schnittstelle bzw. rein abstrakte Klasse stehen sollte.

191

Klassen

4.20 Downcasts
Betrachten wir folgenden Codeschnipsel:
BecherMitAufdruck b("Tee", 200, 95, "Top-Becher"); Becher * bptr=&b;

Wir wissen, dass wegen der Polymorphie die Adresse des BecherMitAufdruckObjekts in einem Zeiger des Typs Becher* gespeichert werden kann. ber diesen Zeiger knnen alle Methoden von Becher aufgerufen werden. Sollte es sich um virtuelle Methoden handeln, dann wird die Methode des tatschlichen Typs aufgerufen, wie in Abschnitt 4.17, Virtuelle Methoden, gezeigt wurde. ber den Becher-Zeiger knnen aber keine Methoden aufgerufen werden, die in der Subklasse neu angelegt wurden, wie in diesem Fall getAufdruck. Manchmal ist aber genau das notwendig. Dazu muss der ursprngliche Typ des Objekts ber eine Typumwandlung wiederhergestellt werden. Der dafr notwendige Cast nennt sich dynamic_cast:
BecherMitAufdruck *p = dynamic_cast<BecherMitAufdruck*>(bptr);

Die in bptr gespeicherte Adresse wir in den Typ BecherMitAufdruck* umgewandelt und dem Zeiger p zugewiesen. Wir wissen an dieser Stelle, dass bptr tatschlich auf ein BecherMitAufdruck-Objekt zeigt. Hufig wird der dynamic_ cast aber in Situationen angewendet, bei denen keine hundertprozentige Klarheit darber herrscht, von welchem Typ das Objekt wirklich ist. Sollte das Objekt nicht von dem Typ sein, in den umgewandelt wird, schlgt die Umwandlung fehl. Es ist in solchen Fllen dann ntig, den Erfolg der Umwandlung zu berprfen. Sollte die Umwandlung misslingen, liefert der Cast einen Nullzeiger zurck:
if(p) cout << p->getAufdruck() << endl;

Der Aufruf der BecherMitAufdruck-Methode wird nur dann ausgefhrt, wenn die Umwandlung erfolgreich war.
Tipp Ein dynamic_cast sollte nur dann ausgefhrt werden, wenn logisch gewhrleistet ist, dass der Downcast erfolgreich sein wird. Ist es notwendig, den Erfolg des Downcasts zu berprfen, oder wird er verwendet, um Informationen ber den tatschlichen Typ zu erhalten, dann deutet das meist auf einen Designfehler hin.

192

Dieses Kapitel beschftigt sich mit Themen von C++, die bei der Entwicklung komplexerer Software unverzichtbar oder zumindest sehr hilfreich sind.

Fortgeschrittene Sprachelemente

Viele Programme lassen sich schreiben, ohne je dieses Kapitel gelesen zu haben. Trotzdem werden Sie feststellen, dass diese Themen das Leben erheblich vereinfachen knnen. Speziell geht es um: Namensbereiche, mit denen Programmelemente logisch gruppiert werden knnen Ausnahmen, die eine fortschrittliche Variante der Fehlermitteilung innerhalb des Programms bieten dynamische Speicherverwaltung, mit deren Hilfe Speicher je nach Bedarf zur Laufzeit beschafft werden kann Templates, ein Konstrukt, mit dem Typen von Klassen und Funktionen variabel gehalten werden knnen die Funktionalitt von Operatoren fr eigene Datentypen durch berladen zu erweitern

5.1

Namensbereiche

In Abschnitt 1.5, using, wurde bereits erklrt, was genau Namensbereiche sind. Hier soll nun besprochen werden, wie eigene Namensbereiche erstellt werden. In den letzten Abschnitten haben wir die Klasse Becher erstellt, die Getrnke aufnehmen soll. Theoretisch wre vorstellbar, dass ein anderer ebenfalls eine Klasse Becher programmiert hat, die zur Mischung von Lacken verwendet wird. Es knnte geschmackliche Einbuen mit sich bringen, wenn die beiden Klassen verwechselt oder gemischt eingesetzt wrden. Glcklicherweise kann die geschilderte Problematik nicht eintreten, denn wrden diese beiden

193

Fortgeschrittene Sprachelemente

gleichnamigen Klassen im selben Namensraum definiert, dann htte dies einen Compilerfehler zur Folge. Stattdessen tritt ein anderes Problem auf: Was, wenn in einem Projekt sowohl der Getrnkebecher als auch der Lackbecher bentigt werden? Momentan htten wir nur die Mglichkeit, die beiden Klassen umzubenennen, z. B. in GetraenkeBecher und LackBecher. Dieser Ansatz setzt allerdings voraus, dass die Klassen noch gendert werden knnen, was bei fremdentwickelten Klassen nicht immer mglich ist. Die bliche Lsung fr dieses Problem sind Namensbereiche. Und zwar erstellen wir unseren eigenen Namensbereich, in den wir unsere Klasse Becher hineinsetzen. Ein Namensbereich wird mit dem Schlsselwort namespace und einem darauffolgenden Namen erstellt. In den dahinterstehenden geschweiften Klammern steht der Inhalt des Namensbereichs. Die Syntax von namespace:
namespace Name { }

Im Gegensatz zu den Klassen steht hinter der schlieenden geschweiften Klammer kein Semikolon. Exemplarisch wollen wir die Klasse Becher in den Namensbereich Getraenke verfrachten. Nehmen wir uns zunchst die Klassendefinition vor:
#pragma once #include <string> namespace Getraenke { class Becher { // Hier der bekannte Klasseninhalt }; }
Listing 5.1 Der Namensbereich Getraenke in Becher.h

Die Methodendefinitionen und Initialisierungen statischer Elemente in der cpp-Datei mssen wir ebenfalls in den Namensbereich Getraenke schieben:

194

Namensbereiche

5.1

#include "Becher.h" #include <iostream> using namespace std; namespace Getraenke { int Becher::maxmenge=1000; // Hier stehen die Methodendefinitionen }
Listing 5.2 Der Namensbereich Getraenke in Becher.cpp

Eine weitere nderung erfhrt die Verwendung der Klasse Becher. Da sie nicht mehr im globalen Namensraum steht, mssen wir den sie enthaltenden Namensbereich explizit angeben:
Getraenke::Becher c("Kaffee", 200, 100); c.ausgabe(); cout << Getraenke::Becher::berechneAbsolutwert(1.85F, 3581.44F) << endl;

5.1.1

using namespace

Oder wir verwenden die bekannte Vereinfachung durch ein using namespace:
#include <iostream> #include "Becher.h" using namespace std; using namespace Getraenke; int main() { Becher c("Kaffee", 200, 100); c.ausgabe(); cout << Becher::berechneAbsolutwert(1.85F, 3581.44F) << endl; }
Listing 5.3 Der Einsatz von using namespace

195

Fortgeschrittene Sprachelemente

5.1.2

Darstellung in der UML

In der UML steht der Namensbereich mit :: getrennt vor dem Klassennamen, wie Abbildung 5.1 zeigt.
Getraenke::Becher -inhalt : string -fassungsvermoegen : int -fuellhoehe : float -maxmenge : int = 1000 +Becher(ein i : string, ein fa : int, ein fu : float) +ausgabe() +reichtKapazitaet(ein ml : int) : bool +reichtKapazitaet(ein b : const Becher*) : bool +berechneAbsolutwert(ein gw : float, ein ps : float) : AbsWertTyp +getInhalt() : string +getFuellmenge() : int

Abbildung 5.1

Der Namensbereich in der UML

5.1.3

Verschachtelte Namensbereiche

Namensbereiche drfen auch verschachtelt werden, um eine logische Hierarchie zum Ausdruck zu bringen:
namespace Getraenke { namespace Heissgetraenke { } namespace Kaltgetraenke { class Limo {}; } class Wasser {}; }
Listing 5.4 Verschachtelte Namensbereiche

Hier werden im Namensbereich Getraenke die beiden Namensbereiche Heissgetraenke und Kaltgetraenke definiert. Um ein Objekt der Klasse Limo zu erzeugen, mssen wir den gesamten Namensbereichspfad angeben:
Getraenke::Kaltgetraenke::Limo l;

Die using namespace-Anweisung, die den Namensbereich Kaltgetraenke zugnglich macht, she so aus:

196

Dynamische Speicherverwaltung

5.2

using namespace Getraenke::Kaltgetraenke;

Auch wenn es logisch unsinnig ist, dass der Konstruktor der Klasse Wasser ein Objekt der Klasse Limo erzeugt, werden wir dies hier einmal umsetzen, um den Zugriff zu demonstrieren:
class Wasser { public: Wasser() { Kaltgetraenke::Limo l; } };
Listing 5.5 Die Klasse Wasser

Weil sich die Klasse Wasser im Namensbereich Getraenke befindet, muss sie lediglich den Namensbereich Kaltgetraenke ansprechen, um ein Objekt von Limo zu erzeugen.

5.1.4

Synonyme definieren

Stellen Sie sich vor, der Herr Mller aus Ihrer Abteilung hat eine Klasse X geschrieben und diese ordentlich in einen eigenen Namensbereich gepackt:
namespace MeinErsterEigenerNamensbereich { class X {}; }

Je nachdem, wie oft Sie den Namen dieses Namensbereichs schreiben mssen, knnte das Gesprchsstoff fr die nchste Weihnachtsfeier bieten. Glcklicherweise erlaubt es C++, fr Namensbereiche Synonyme zu erstellen:
namespace Mueller = MeinErsterEigenerNamensbereich;

Nun ist die Klasse X gleich viel humaner zu erreichen:


Mueller::X x;

5.2

Dynamische Speicherverwaltung

Wenn wir bisher Speicher bentigten, als Objekt oder Array, wurde er immer zur Kompilationszeit also statisch angefordert:

197

Fortgeschrittene Sprachelemente

Becher b("Limo", 500, 70); int f[20];

Die oberen Anweisungen definieren ein Becher-Objekt und ein 20-elementiges int-Array. Deren Inhalt und Gre wird bereits bei der Programmierung festgelegt. Hufig kann die notwendige Gre eines Arrays aber erst zur Laufzeit bestimmt werden, weil vielleicht der Anwender zuerst nach der gewnschten Gre gefragt wird. Es muss also ein Mechanismus her, mit dem Objekte oder Arrays zur Laufzeit erstellt werden knnen. Und genau dazu dient die dynamische Speicherverwaltung.

5.2.1

Erzeugen von Objekten

Objekte werden dynamisch mit dem Befehl new erzeugt. Hinter new wird der zu erstellende Datentyp mit eventuell notwendigen Konstruktorparametern angegeben. new liefert dann die Adresse des erstellten Objekts zurck, die im Normalfall in einem Zeiger gespeichert wird. ber den Zeiger kann dann in der bekannten Syntax auf das Objekt zugegriffen werden:
Becher *p = new Becher("Suppe", 200, 98); p->ausgabe();

In ANSI C++ geht die Verantwortung des ber new reservierten Speicherbereichs an den Programmierer ber. Das heit, entweder das Programm gibt dynamisch reservierten Speicher wieder frei oder niemand! Speziell bei Programmen mit langer ununterbrochener Laufzeit (wie auf Servern) knnen die durch Nichtfreigabe des Speichers entstehenden Speicherlecks (memory leak) das System in die Knie zwingen oder das Programm zum Absturz bringen.
Achtung Dynamisch reservierter Speicher muss vom Programm wieder freigegeben werden, sonst entstehen Speicherlecks.

Der Befehl zur Freigabe von Speicher heit delete. Ihm bergeben Sie die Adresse des freizugebenden Speichers:
delete(p);

Sie sollten nur Speicher freigeben, der auch vorher reserviert wurde. Nachdem der Speicher freigegeben wurde, darf ber den Zeiger nicht mehr auf

198

Dynamische Speicherverwaltung

5.2

ihn zugegriffen werden. Speicher kann nur in den Portionsgren freigegeben werden, in denen er auch reserviert wurde.

5.2.2

Erzeugen von Arrays

Das dynamische Erzeugen von Arrays funktioniert hnlich. Das folgende Beispiel fragt den Anwender nach der gewnschten Anzahl an Werten, reserviert das zur Speicherung ntige Array und liest die Werte von der Tastatur ein:
cout << "Wie viele Werte:"; int x; cin >> x; int *f = new int[x]; for(int i=0; i<x; ++i) { cout << "Wert " << i+1 << ":"; cin >> f[i]; } delete[](f);

Bei der Freigabe des Speichers mit delete wurden ebenfalls leere eckige Klammern verwendet. Diese sind notwendig, damit fr jedes Element im Array ein eventuell vorhandener Destruktor aufgerufen wird. Was genau ein Destruktor ist, behandelt der nchste Abschnitt.

5.2.3

Destruktoren

Um die Arrays und deren dynamische Reservierung ein wenig zu kapseln, knnten Sie auf die Idee kommen, eine Klasse zu schreiben, deren Konstruktor bergeben bekommt, wie gro das Feld sein soll. Eine sehr gute Idee! Die folgende Klasse IntFeld speichert int-Werte in einem Feld, dessen Gre dem Konstruktor bergeben wurde:
class IntFeld { int *feld; int groesse; public: IntFeld(int g) : groesse(g) { feld = new int[groesse]; }

199

Fortgeschrittene Sprachelemente

void setWert(int pos, int wert) { feld[pos]=wert; } int getWert(int pos) const { return(feld[pos]); } };
Listing 5.6 Die Klasse IntFeld

Beschrieben und gelesen wird das interne Feld ber die Methoden setWert und getWert. Eine elegantere Mglichkeit werden wir mit dem berladen von Operatoren (Abschnitt 5.5, Operatoren berladen) und den Indexern (Abschnitt 9.4, Indexer) noch kennenlernen. In der Praxis wre es unverzichtbar, innerhalb von getWert und setWert die Position auf Gltigkeit zu prfen. Hier soll die Klasse aber so schlank wie mglich gehalten werden. Kommen wir auf den Punkt zu sprechen, auf den dieses Beispiel hinauslaufen soll. Der Speicher fr das Feld wurde im Konstruktor dynamisch angefordert, und wir wissen, dass dieser Speicher irgendwo wieder freigegeben werden muss. Nur wo? Momentan knnten wir alleinig eine Methode implementieren, bei deren Aufruf der Speicher freigegeben wird. Dieser Ansatz hat zwei Nachteile: Der Anwender der Klasse darf nicht vergessen, die Methode aufzurufen. Nachdem der Speicher ber die Methode freigegeben wurde, knnte weiterhin mit getWert und setWert darauf zugegriffen werden, mit fatalen Konsequenzen. Wir bruchten ein Gegenstck zum Konstruktor, etwas, dass automatisch beim Abbau des Objekts aufgerufen wird. Und genau dazu ist der Destruktor da. Da ein Destruktor nicht explizit aufgerufen wird, kann er nicht ber unterschiedliche Parameterlisten berladen werden; jede Klasse besitzt nur einen Destruktor, der eine leere Parameterliste besitzt. Der Name des Destruktors lautet wie der des Konstruktors, nur mit vorangestellter Tilde (~). Der Destruktor fr die Klasse IntFeld sieht damit wie folgt aus:
~IntFeld() { delete[](feld); }
Listing 5.7 Der Destruktor von IntFeld

200

Dynamische Speicherverwaltung

5.2

Destruktoren werden in C++ fr Aufrumarbeiten verwendet. Oft bewirken sie das Gegenteil des Konstruktors.
Hinweis Jede Klasse besitzt einen Destruktor. Sollten Sie keinen eigenen schreiben, fgt der Compiler einen Standard-Destruktor hinzu, der nichts macht.

Virtuelle Destruktoren

Eine virtuelle Methode dient der dynamischen Typberprfung. Sollte sie ber einen Basisklassenzeiger aufgerufen werden, wird der wirkliche Typ des Objekts bestimmt und dessen Methode aufgerufen. Dieses Verhalten ist oft auch bei einem Destruktor erwnscht. Nehmen wir nachstehendes Fragment:
Becher *p = new BecherMitAufdruck("Suppe", 200, 98,"Toptasse"); delete(p);

Die letzte Anweisung lscht das Objekt, dessen Adresse in p gespeichert ist. Weil p vom Typ Becher* ist, wird der Destruktor der Klasse Becher aufgerufen. Es handelt sich hier aber um ein Objekt der Klasse BecherMitAufdruck. Sollte deren Destruktor Aufrumarbeiten zu erledigen haben, dann werden diese nicht ausgefhrt. Es msste in diesem Fall sichergestellt werden, dass der richtige Datentyp ermittelt und dann dessen Destruktor aufgerufen wird. Genau wie bei jeder anderen Methode auch, mssen wir dazu den Destruktor als virtuell deklarieren.
Hinweis Als Basisklassen fungierende Klassen sollten immer virtuelle Destruktoren besitzen.

5.2.4

Wenn new fehlschlgt

Bei den Speichermengen, mit denen heutige Computer gesegnet sind, ist es schon auerordentlich schwierig, mit einer eigenen ernsthaften Anwendung an das Speicherlimit zu gelangen. Trotzdem knnen andere Programme den Speicher bereits stark geplndert haben, so dass die eigene Anwendung leer ausgeht.

201

Fortgeschrittene Sprachelemente

Wird mit new eine Speichergre angefordert, die nicht mehr an einem Stck verfgbar ist, dann wirft new eine Ausnahme namens bad_alloc. Wie genau Ausnahmen behandelt werden, zeigt der nchste Abschnitt.

5.3

Ausnahmen

Kommen wir zur objektorientierten Fehlerbehandlung. Der aufgetretene Fehler wird dabei durch ein Objekt reprsentiert, welches ber den Ausnahmenmechanismus an die fehlerbehandelnde Stelle transportiert wird.

5.3.1

Ausnahmen werfen

Die Fehlerbehandlung soll an der Klasse Becher demonstriert werden. In Kapitel 4, Klassen, haben wir erfahren, dass der Vorteil des Zugriffs auf Attribute ber Methoden und deren Initialisierung mit Konstruktoren darin besteht, Code fr eine mgliche Fehlerbehandlung unterbringen zu knnen. Das nehmen wir nun in Angriff. Hier zur Erinnerung noch einmal der Konstruktor von Becher:
Becher::Becher(string i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { }
Listing 5.8 Der Konstruktor von Becher

Im Wesentlichen sollen Becher mit negativem Fassungsvermgen oder Fllmenge verhindert werden. Es wre vielleicht auch noch interessant, nur bestimmte Inhalte zuzulassen und undefinierbare Flssigkeiten, wie Blubberbrhe, zu unterbinden. Das soll an dieser Stelle aber vernachlssigt werden. Das Prfen auf einen negativen Wert ist ein Kinderspiel:
Becher::Becher(string i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { if(fa<0) // Hier Fehler fr negatives Fassungsvermgen if(fu<0) // Hier Fehler fr negative Fllmenge }

202

Ausnahmen

5.3

Ob nun der Wert des Attributs oder des Parameters berprft wird, ist hier nebenschlich, weil beide den gleichen Wert haben. Die Parameter besitzen nur einen krzeren Namen. Es wurde oben kurz angerissen, dass bei Eintreten eines Fehlers ein Objekt den Fehler reprsentiert. Der Einfachheit halber entscheiden wir uns fr einen Fehler in Textform und verwenden ein string-Objekt. Um mit dem Fehlerobjekt eine Ausnahme zu erzeugen, muss es wie man sagt geworfen werden. Der dafr notwendige Befehl heit throw:
Becher::Becher(string i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { if(fa<0) throw string("Negatives Fassungsvermoegen"); if(fu<0) throw string("Negative Fuellmenge"); }
Listing 5.9 Der Becher-Konstruktor mit Fehlerbehandlung

Die Aufrufhierarchie soll zu Demonstrationszwecken vertieft werden. Dazu schreiben wir eine Funktion erzeugeKaffee, die immer 0,3l-Becher Kaffee erzeugt, nur die Menge ist variabel:
Becher* erzeugeKaffee(float menge) { return(new Becher("Kaffee",300,menge)); }
Listing 5.10 Die Funktion erzeugeKaffee

Innerhalb von main knnen wir jetzt bequem eine halbvolle Tasse Kaffee bestellen:
Becher *k=erzeugeKaffee(50); delete(k);

Die Tasse wird zwar direkt im Anschluss ihrer Erzeugung wieder ausgegossen, aber das Prinzip drfte klar sein. Mit dieser Vorarbeit werden wir nun eine Tasse Kaffee mit negativer Fllmenge erzeugen:
Becher *k=erzeugeKaffee(-50);

Wenn das Programm nach der Kompilation ausgefhrt wird, erscheint ein in Abbildung 5.2 dargestellter Dialog.

203

Fortgeschrittene Sprachelemente

Abbildung 5.2 Ergehnis einer unbehandelten Ausnahme

An dieses Fenster knnen Sie sich schon einmal gewhnen. Es erscheint nmlich immer, wenn eine Ausnahme geworfen wurde, auf die Ihr Programm nicht reagiert hat. Da .NET geradezu um sich wirft mit Ausnahmen, wird dies hufiger geschehen. Im aktuellen Fall ist es aber nicht verwunderlich, dass eine Ausnahme geworfen wurde, denn genau das war die Aufgabe des in den Becher-Konstruktor eingefgten Codes. Um zustzliche Informationen ber den Fehler zu erhalten, sollten Sie das Programm ber den Menpunkt Debuggen Debuggen starten beziehungsweise ber Drcken von (F5) starten. Mit dieser Option wird der Debugger auf den Plan gerufen. Es erscheint der in Abbildung 5.3 dargestellte Dialog, der weitere Details enthllt.

Abbildung 5.3 Eine unbehandelte Ausnahme

Der erste Satz bringt das Problem auf den Punkt: Unbehandelte Ausnahme. Dahinter steht noch der Typ der Ausnahme. Auch wenn es schwierig zu erkennen ist, handelt es sich bei dem Typ um den von uns verwendeten String. Den Dialog schlieen Sie mit Unterbrechen, und er gibt den Blick frei auf das Debuggerfenster, das in Abbildung 5.4 gezeigt ist.

204

Ausnahmen

5.3

Abbildung 5.4 Das Debugger-Fenster

Das Codefenster zeigt die Methode, in der die Ausnahme geworfen wurde. Das mit Aufrufliste betitelte Fenster unten rechts zeigt die Aufrufhierarchie des Programms. Fr uns sind nur die Eintrge mit C++ als Sprache interessant. Es beginnt in der Funktion main, von dort aus wird die Funktion erzeugeKaffee aufgerufen, die wegen der Erzeugung des Becher-Objekts wiederum den Becher-Konstruktor aufruft. Bei einer Ausnahme verlsst das Programm in umgekehrter Aufrufreihenfolge die Methoden/Funktionen, bis die Ausnahme irgendwo auf diesem Weg aufgefangen wird. Wird die Ausnahme wie im aktuellen Programm berhaupt nicht aufgefangen, hat dies die Beendigung des Programms in an unusual way zur Folge. Wir, die bestrebt sind, das eigene Programm nicht so entwrdigend enden zu lassen, knnen in jeder der Methoden/Funktionen, durch die die Ausnahme wandert, eine Fehlerbehandlung einbauen. Doch erst einmal sollten Sie den Debugger ber das Men mit Debuggen Debuggen beenden beenden. Weitere Informationen ber die Verwendung des Debuggers finden Sie in Abschnitt A.1.

205

Fortgeschrittene Sprachelemente

5.3.2

Ausnahmen fangen

Obwohl es technisch mglich wre, macht es im oberen Beispiel keinen Sinn, die Ausnahme im Konstruktor aufzufangen, denn dort wurde der Fehler erkannt und die Ausnahme geworfen. Msste dort eine Fehlerbehandlung stattfinden, dann knnte sie mit in den Anweisungsblock gepackt werden, der die Ausnahme wirft. Erst einmal lassen wir die Ausnahme bis in die main-Funktion vordringen und fangen sie dort auf. Der erste Schritt zum Fangen einer Ausnahme liegt in der Kennzeichnung des Codes, fr den im Fehlerfall eine Ausnahme gefangen werden soll. Der Code wird dazu in einen sogenannten try-block gesteckt:
try { Becher *k=erzeugeKaffee(-50); delete(k); }

Der relevante Code in der main-Funktion besteht aus dem Aufruf der erzeugeKaffee-Funktion, der nun im try-Block steht. Der try-Block besagt jedoch nur, dass eine in ihm auftretende Ausnahme aufgefangen wird, nicht aber, welche. Fr die zu fangenden Datentypen werden hinter dem tryBlock in Form von catch-Blcken entsprechende Ausnahme-Handler angegeben:
try { Becher *k=erzeugeKaffee(-50); delete(k); } catch(string s) { cout << "Fehler: " << s << endl; }

Die Ausnahme-Handler sind an den vorausgehenden try-Block gebunden. Tritt im try-Block eine Ausnahme auf, dann springt der Programmfluss entweder zu einem Ausnahme-Handler, der den passenden Datentyp als Parameter besitzt, oder bricht die aktuelle Funktion/Methode wegen unbehandelter Ausnahme ab. Die Ausnahme muss dann auf hherer Ebene gefangen werden, um einen kompletten Programmabbruch zu verhindern. Sollen Ausnahmeobjekte unterschiedlicher Typen gefangen werden, dann ist fr jeden Typ ein eigener Ausnahme-Handler mit eigenstndigem catch-Block anzulegen, die hintereinander aufgefhrt werden (anders das Abfangen ver-

206

Ausnahmen

5.3

schiedener Subklassentypen ber den Basisklassentyp, siehe Abschnitt 16.7, TreeView die Baumdarstellung, im .NET-Teil des Buchs). Innerhalb eines Ausnahme-Handlers kann ber den Parameter auf das geworfene Objekt zugegriffen werden. Im oberen Beispiel wird auf diese Weise der Text des stringObjekts ausgegeben. Ist ein Ausnahme-Handler abgearbeitet worden, springt der Programmfluss hinter den letzten Handler und fhrt dort fort.

5.3.3

Unterschiedliche Ausnahmen auffangen

Die oben angesprochene Mglichkeit, fr einen try-Block mehrere Handler zu definieren, wollen wir genauer besprechen. Dazu wird im Konstruktor von Becher zustzlich berprft, ob die prozentuale Fllmenge den Wert 100 nicht berschreitet. Andernfalls wird der fehlerhafte Wert als Fehlerobjekt geworfen:
Becher::Becher(string i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { if(fa<0) throw string("Negatives Fassungsvermoegen"); if(fu<0) throw string("Negative Fuellmenge"); if(fu>100) throw fu; }

Das neu geworfene Objekt ist vom Typ float, eine Ergnzung des try-Blocks in main um einen Ausnahme-Handler fr float ist daher ratsam:
try { Becher *k=erzeugeKaffee(5000); delete(k); } catch(string s) { cout << "Fehler: " << s << endl; } catch(float f) { cout << "Zu viele Prozent: " << f << endl; }

Der Aufruf von erzeugeKaffee wurde bereits abgendert, um die neu implementierte Ausnahme zu provozieren. Schauen wir uns noch einmal in Abbildung 5.5 den genauen Ablauf an.

207

Fortgeschrittene Sprachelemente

Ausnahme-Handler
[Ausnahme]

[Objektyp = string]

string-Handler

try-Block
k=erzeugeKaffee

[sonst] [Objektyp = int] [sonst]

int-Handler

delete(k)

Ausnahme weiterwerfen

Abbildung 5.5 Die Ausnahmebehandlung als Aktivittsdiagramm

Der try-Block definiert den Bereich, in dem eine Ausnahme potenziell aufgefangen wird. Wurde eine Ausnahme geworfen und es spielt keine Rolle, bei welcher Anweisung dies geschah , dann wird die Abarbeitung des tryBlocks unterbrochen, und die vorhandenen Ausnahme-Handler in der durch ihre Anordnung im Quellcode vorgegebenen Reihenfolge werden nach einem passenden Datentypen durchsucht. Der erste Handler, dessen Datentyp das in der Ausnahme geworfene Objekt aufnehmen kann, wird abgearbeitet. Nach Beendigung des Handlers fhrt der Programmfluss hinter dem try-Block fort. Ist kein passender Handler vorhanden, wird das Ausnahmeobjekt weitergeworfen, um auf einer hheren Ebene des Programms aufgefangen zu werden oder das Programm als unbehandelte Ausnahme zu beenden.

5.3.4

Ausnahmen weiterwerfen

Die Titel der Abschnitte in diesem Kapitel klingen fast wie ein neuartiges Ballspiel, aber es besteht die Mglichkeit, innerhalb eines Handlers durch bloe Angabe des Befehls throw die aktuell bearbeitete Ausnahme weiterzuwerfen, damit an anderer Stelle nochmals auf sie reagiert werden kann:
try { Becher *k=erzeugeKaffee(-50); delete(k);

208

Ausnahmen

5.3

} catch(string s) { cout << "Fehler: " << s << endl; throw; }

Dieses Vorgehen ist z. B. sinnvoll, wenn innerhalb einer Bibliothek auf eine Ausnahme reagiert werden muss, der Fehler aber auch dem Anwender der Bibliothek mitgeteilt werden soll.

5.3.5

Eigene Ausnahme-Klassen

Oft ist es sinnvoll, nicht nur mitzuteilen, dass ein Fehler aufgetreten ist, sondern auch noch die genaueren Umstnde zu beschreiben. Im Falle unseres Bechers mchte die Fehler behandelnde Instanz vielleicht wissen, welche ungltige Fllmenge den Fehler verursacht hat. In solchen Fllen bietet es sich an, eine eigene Ausnahme-Klasse zu schreiben, deren Objekte dann die gewnschten Informationen aufnehmen und an die Fehlerbehandlung weitergeben knnen. Die Klasse Becher soll dazu eine lokale Klasse UngueltigeFuellhoehe bekommen. Diese Klasse speichert nur die ungltige Fllhhe. Sie besitzt einen Konstruktor, dem die Fllhhe bergeben wird, und eine Zugriffsmethode GetFuellhoehe, mit der die ungltige Fllhhe aus dem Objekt herausgeholt werden kann:
class Becher : public IAusgabe { public: class UngueltigeFuellhoehe { float fuellhoehe; public: UngueltigeFuellhoehe(float fh) : fuellhoehe(fh) { } float GetFuellhoehe() const { return(fuellhoehe); } }; // Hier der Rest der Becher-Klasse };
Listing 5.11 Die Definition von UngueltigeFuehoehe in Becher.h

209

Fortgeschrittene Sprachelemente

Den Konstruktor von Becher mssen wir jetzt noch anpassen, damit er im Falle einer ungltigen Fllhhe ein Objekt der neuen Ausnahme-Klasse wirft:
Becher::Becher(string i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { if(fa<0) throw string("Negatives Fassungsvermoegen"); if(fu<0 || fu>100) throw UngueltigeFuellhoehe(fu); }
Listing 5.12 Der neue Konstruktor von Becher

Aufgefangen und ausgewertet werden, knnte die Ausnahme z. B. so:


try { Becher* b=erzeugeKaffee(-20); } catch(Becher::UngueltigeFuellhoehe e) { cout << "Fehlerhafte Fuellhoehe:" << e.GetFuellhoehe() << endl; }
Listing 5.13 Exemplarisches Auffangen von UngueltigeFuellhoehe

Da es sich bei UngueltigeFuellhoehe um eine lokale Klasse handelt, muss der Name der umgebenden Klasse auerhalb mit angegeben werden (Becher::UnguelgieFuellhoehe). Der Name e ist willkrlich und dient nur dazu, das aufgefangene Objekt namentlich ansprechen zu knnen, damit GetFuellhoehe aufgerufen werden kann.

5.4

Templates

Templates auch Vorlagen oder Schablonen genannt sind eine speziell fr C++ entwickelte Form der Abstraktion. Ein Hauptteil der C++-Standardbibliothek, die STL, basiert auf dieser Technik. Obwohl Templates von .NET und seiner Sprache C# nicht in dieser Art untersttzt werden, knnen sie auch bei der .NET-Programmierung mit C++ eingesetzt werden.

210

Templates

5.4

5.4.1

Funktionstemplates

Als Paradebeispiel fr ein potenzielles Funktionstemplate holen wir die maximum-Funktion aus Abschnitt 2.3, Funktionen, wieder hervor:
int maximum(int x, int y) { if(x>=y) return(x); else return(y); }
Listing 5.14 Die Funktion maximum fr int-Werte

Sie bekommt zwei int-Werte bergeben und liefert den greren der beiden Werte zurck. Stellen Sie sich vor, die aktuellen Umstnde erfordern eine weitere maximum-Funktion, die den greren zweier float-Werte ermitteln soll. Mit der vorigen maximum-Funktion im Rcken ist diese schnell geschrieben. Dank der Technik des berladens aus Abschnitt 4.7, berladen von Methoden, knnen wir ihr sogar den gleichen Namen geben:
float maximum(float x, float y) { if(x>=y) return(x); else return(y); }
Listing 5.15 Die Funktion maximum fr float-Werte

Und jetzt wird noch jeweils eine maximum-Funktion bentigt fr double, long, char Sptestens jetzt sucht ein Programmierer nach Alternativen. Zumal sich die beiden Funktion so frappierend hneln. Nur die Typen im Funktionskopf sind unterschiedlich. Und genau hier kommen die Templates ins Spiel.
Hinweis Mit einem Template knnen innerhalb einer Funktion oder Klasse Datentypen variabel gehalten werden.

Besprechen wir die Bedeutung an einem konkreten Beispiel:


template<typename Typ> Typ maximum(Typ x, Typ y) {

211

Fortgeschrittene Sprachelemente

if(x>=y) return(x); else return(y); }


Listing 5.16 Das Funktionstemplate maximum

Die Syntax eines Funktionstemplates:


template<typename Typname [, typename Typname]...>

Die Definition eines Templates beginnt mit dem Wort template. Dahinter stehen in spitzen Klammern jeweils mit dem Schlsselwort typename die Namen der variablen Typen. Im oberen Fall besitzt das Template einen variablen Typ namens Typ (der Name des variablen Typs ist nach den Namensregeln fr Bezeichner frei whlbar, siehe Abschnitt 1.7.1, Bezeichner). Hinter dem Templatekopf steht eine normale Funktion, die den variablen Typ des Templates verwenden kann, als wre es ein gltiger Datentyp. Die Definition des Templates erzeugt noch keinen Code. Sollte jetzt allerdings eine Funktion maximum aufgerufen werden
cout << maximum(3,8) << endl;

dann sucht der Compiler zuerst nach einer konkreten Funktion maximum, die zwei int-Werte erwartet. Die gibt es nicht mehr, also versucht er sein Glck mit den Templates, die maximum heien. Sollte es ein Template geben, dessen feste und variable Typen zu dem Funktionsaufruf passen, dann wird aus dem Template eine konkrete Funktion fr den konkreten Typ erstellt. Im oberen Fall kann aus dem maximum-Template eine zum Aufruf passende Funktion erstellt werden, wenn der variable Typ Typ durch int ersetzt wird. Fr einen weiteren Aufruf mit anderen Datentypen geschieht das Gleiche:
cout << maximum(3.14, 22.67) << endl;

Nun wird aus dem Template eine maximum-Funktion erstellt, die zwei doubleWerte erwartet und einen double-Wert zurckliefert. Und das ist eine wichtige Konsequenz bei der Verwendung von Templates.
Achtung Lediglich der Quellcode wird durch Templates krzer, nicht der endgltige Programmcode.

212

Templates

5.4

Im oberen Fall wurde maximum mit zwei verschiedenen Typen aufgerufen, weshalb der Compiler aus dem Template auch zwei unterschiedliche Funktionen erzeugt. Im kompilierten Programm sind ebenso zwei maximum-Funktionen vorhanden, als wren die beiden Funktionen konkret implementiert worden. Templates vereinfachen nur den zu schreibenden Programmcode. Und sie erhhen die Wiederverwendbarkeit, weil sie auch mit Typen zusammenarbeiten knnen, die bei der Erstellung des Templates noch nicht bekannt waren. Das maximum-Template beispielsweise kann das grere von zwei Objekten eines beliebigen Typs ermitteln, auf den der >-Operator anwendbar ist. Funktionstemplates knnen auch fr Methoden definiert werden. Aber nicht nur Funktionen oder Methoden knnen als Templates formuliert werden, sondern auch Klassen! Mehr darber erfahren Sie im nchsten Abschnitt.

5.4.2

Klassentemplates

In Listing 5.6 in Abschnitt 5.2.3, Destruktoren, wurde eine Klasse IntFeld vorgestellt, die in der Lage ist, ein int-Array beliebiger Gre zu verwalten. Wollten wir jetzt aber lieber ein Feld mit double-Werten haben, dann msste entweder die aktuelle Klasse angepasst oder eine neue Klasse programmiert werden. Sie sehen, worauf es hinausluft: Knnte der zu verwaltende Typ variabel gehalten werden, dann wren zwei Fliegen mit einer Klappe geschlagen. Natrlich ist die Lsung unseres Problems ein Template. Um der Tatsache Rechnung zu tragen, dass mit dem Template beliebige Datentypen verwaltet werden knnen, heit es nur noch Feld:
template<typename Typ> class Feld { Typ *feld; int groesse; public: Feld(int g) : groesse(g) { feld = new Typ[groesse]; } ~Feld() { delete[](feld); }

213

Fortgeschrittene Sprachelemente

void setWert(int pos, Typ wert) { feld[pos]=wert; } Typ getWert(int pos) const { return(feld[pos]); } };
Listing 5.17 Das Template Feld

Der Aufbau des Klassentemplates luft analog zu den Funktionstemplates. Wie bei den Funktionstemplates wird im Templatekopf der Name des variablen Datentypen bestimmt, der in der darauffolgenden Klasse als konkreter Datentyp verwendet wird. Nur das Erzeugen eines Klassenobjekts sieht etwas anders aus. Bei der Objektdefinition kann der Compiler nicht ermitteln, welchen Typ der variable Datentyp annehmen soll, deswegen muss ihm dies in spitzen Klammern hinter dem Klassennamen angegeben werden:
Feld<int> f(20); f.setWert(0,30); cout << f.getWert(0) << endl;

5.5

Operatoren berladen

Dieser Abschnitt behandelt die Fhigkeit von C++, Operatoren fr eigene Datentypen berladen zu knnen. Um eine greifbarere Vorstellung von den dahinterliegenden Mechanismen zu bekommen, implementieren wir zwei Klassen, fr die wir einige Operatoren berladen werden. Als Erstes wre da die Klasse Aufnahmemedium, deren Objekte ein beliebiges Aufnahmemedium reprsentieren, welches eine bestimmte Kapazitt und die Zeit, die bereits bespielt wurde, speichert:
class Aufnahmemedium { int kapazitaet; int bespieltezeit; public: Aufnahmemedium(int k=240, int z=0) : kapazitaet(k), bespieltezeit(z) { }

214

Operatoren berladen

5.5

int GetKapazitaet() const { return (kapazitaet); } int GetBespielteZeit() const { return (bespieltezeit); } };
Listing 5.18 Die Klasse Aufnahmemedium

Der Konstruktor definiert Standardwerte fr seine Parameter. Wird keine bespielte Zeit angegeben, dann ist das Medium zu Beginn unbespielt. Wird auch keine Kapazitt angegeben, dann wird zu Ehren der frheren VHS-Kassetten eine maximale Spielzeit von 240 Minuten definiert. Um auch einen etwas komplexeren Datentypen zu besitzen, der mit dynamischer Speicherverwaltung arbeitet, entwerfen wir die Klasse Mediensammlung, die mehrere Objekte von Aufnahmemedium verwalten kann:
class Mediensammlung { Aufnahmemedium* feld; int groesse; int anzahl; public: /* ** Fehlerklassen fr Ausnahmen */ class FalscherIndex {}; class KapazitaetUeberschritten {}; /* ** Konstruktor legt dynamisch ein Feld der Gre g an */ Mediensammlung(int g) : groesse(g), anzahl(0), feld(0) { feld=new Aufnahmemedium[groesse]; } /* ** Kopierkonstruktor, fertigt tiefe Kopie an */ Mediensammlung(const Mediensammlung& ms)

215

Fortgeschrittene Sprachelemente

: groesse(ms.groesse), anzahl(ms.anzahl), feld(0) { feld=new Aufnahmemedium[groesse]; for(int i=0; i<anzahl; ++i) feld[i]=ms.feld[i]; } /* ** Destruktor gibt das Feld wieder frei */ ~Mediensammlung(void) { delete[](feld); } /* ** Anzahl liefert die Anzahl der gespeicherten Medien */ int Anzahl() const { return(anzahl); } /* ** Fgt ein Medium hinzu oder wirft bei ** Platzmangel eine Ausnahme */ void MediumHinzufuegen(const Aufnahmemedium& medium) { if(anzahl<groesse) feld[anzahl++]=medium; else throw KapazitaetUeberschritten(); } /* ** Liefert ein gespeichertes Medium oder ** wirft bei falschem Index eine Ausnahme */ Aufnahmemedium GetMedium(int p) const { if(p<0 || p>(anzahl-1)) throw FalscherIndex(); return (feld[p]); } };
Listing 5.19 Die Klasse Mediensammlung

216

Operatoren berladen

5.5

5.5.1

Zuweisungsoperatoren

Schauen wir uns zunchst die Zuweisungsoperatoren an.


Kopier-Zuweisungsoperator

Der Kopier-Zuweisungsoperator weist ein Objekt einem anderen Objekt derselben Klasse zu. Er wird als Methode der Klasse implementiert. Die Syntax des Kopier-Zuweisungsoperators:
Klassenname& operator=(const Klassenname& objektname) { }

Der Kopier-Zuweisungsoperator erwartet eine Referenz auf ein konstantes Objekt, so knnen auch Konstanten einem Objekt zugewiesen werden. Die Operator-Methode liefert das Objekt selbst zurck, um Verkettungen der Art a=b=c umsetzen zu knnen. Das Objekt wird als Referenz zurckgegeben, damit die syntaktisch erlaubte (aber logisch meist unsinnige) Reihenfolge (a=b)=c umgesetzt werden kann.
Hinweis Wird kein eigener Kopier-Zuweisungsoperator implementiert, fgt der Compiler implizit einen Kopier-Zuweisungsoperator hinzu, der aber nur eine flache Kopie anfertigt, das Objekt also nur attributweise kopiert.

Dieser implizite Kopier-Zuweisungsoperator reicht fr die Klasse Aufnahmemedium voll aus, denn dort mssen nur Attribute kopiert werden. Anders sieht es bei Mediensammlung aus. Dort mssen bei einer Zuweisung das alte Array freigegeben, ein neues der entsprechenden Gre reserviert und alle Aufnahmemedium-Objekte kopiert werden. Die Methode knnte so aussehen:
Mediensammlung& operator=(const Mediensammlung& ms) { // Selbstzuweisung verhindern if(feld!=ms.feld) { // Neuen Speicherblock reservieren Aufnahmemedium* tmp=new Aufnahmemedium(ms.groesse); // Elemente kopieren for(int i=0; i<ms.anzahl; ++i) tmp[i]=ms.feld[i];

217

Fortgeschrittene Sprachelemente

// Altes Feld lschen und Anzahl und Groesse bernehmen delete[](feld); feld=tmp; anzahl=ms.anzahl; groesse=ms.groesse; } // Verweis auf sich selbst zurckgeben return(*this); }
Listing 5.20 Der Kopier-Zuweisungsoperator von Mediensammlung

Der Operator prft, ob ein Objekt sich selbst zugewiesen wird (a=a), denn dann muss kein neues Feld angelegt, kopiert und das alte wieder gelscht werden. Andererseits ist die Selbstzuweisung so unwahrscheinlich, dass fr diesen seltenen Fall ein Mehraufwand in Kauf genommen werden kann, dafr dann aber in allen anderen Fllen die unntige Prfung nicht durchgefhrt werden muss. Die Zuweisung a=b wird jetzt vom Compiler transformiert zu:
a.operator=(b);

Mit einem Trick lsst sich die Methode noch weiter vereinfachen. Die Klasse Mediensammlung besitzt bereits einen selbst definierten Kopierkonstruktor, der eine tiefe Kopie anfertigt. Warum nicht diesen verwenden?
Mediensammlung& operator=(Mediensammlung ms) { Aufnahmemedium* tmp=feld; feld=ms.feld; ms.feld=tmp; anzahl=ms.anzahl; groesse=ms.groesse; return(*this); }
Listing 5.21 Der verbesserte Kopier-Zuweisungsoperator

Die Methode erwartet jetzt nicht mehr eine Referenz, sondern ein Objekt. Dieses muss beim Aufruf kopiert werden, und zwar implizit mit dem Kopierkonstruktor. Das Objekt ms ist damit eine Kopie des bergebenen Objekts. Wir brauchen jetzt nur noch die Arrays auszutauschen (damit wir das frisch kopierte Array bernehmen und bei der Freigabe des lokalen Objekts unser altes Array freigegeben wird) und die Werte von anzahl und groesse zu bernehmen.

218

Operatoren berladen

5.5

Kombinierte Zuweisungsoperatoren

Die kombinierten Zuweisungsoperatoren kombinieren die Zuweisung mit einer arithmetischen oder bitweisen Operation. Wenn diese Operatoren nicht gerade fr einen numerischen Datentypen berladen werden, stellt sich oft die Frage, welches Verhalten mit dem entsprechenden Operator denn umgesetzt werden soll. Nehmen wir stellvertretend den +=-Operator. Womit rechnet man, wenn dieser Operator auf Objekte des Typs Aufnahmemedium angewendet wird? Oft ist es eine Definitionssache, die dann entsprechend dokumentiert werden muss. Ein prominentes Beispiel ist der <<-Operator in Kombination mit cout. Dort hat er keinerlei bitweise Eigenschaften mehr, sondern dient der Ausgabe von Objekten. Definieren wir also, dass der +=-Operator bei Aufnahmemedium einfach die bespielte Zeit hinzuaddiert. Sollte die Kapazitt nicht ausreichen, wird eine Ausnahme des selbst definierten Typs KapazitaetUnzureichend geworfen:
Aufnahmemedium& operator+=(const Aufnahmemedium& am) { if(this!=&am) { if((bespieltezeit+am.bespieltezeit)>kapazitaet) throw KapazitaetUnzureichend(); bespieltezeit+=am.bespieltezeit; } return(*this); }
Listing 5.22 operator+= von Aufnahmemedium

In diesem Fall ist eine Prfung auf Selbstzuweisung unumgnglich, weil ein Aufnahmemedium im Normalfall nicht sich selbst kopieren kann. Die Prfung wird vorgenommen, indem die Adressen der beiden Objekte verglichen werden. Bei der Klasse Mediensammlung ist der +=-Operator etwas aufwndiger, weil dort mit dynamischem Speicher gearbeitet wird:
Mediensammlung& operator+=(const Mediensammlung& ms) { if(this!=&ms) { // Neue Gre bestimmen und Array reservieren int ngroesse=groesse+ms.groesse; Aufnahmemedium* nfeld=new Aufnahmemedium[ngroesse];

219

Fortgeschrittene Sprachelemente

// Daten des aufrufenden Objekts kopieren for(int i=0; i<anzahl; ++i) nfeld[i]=feld[i]; // Daten des bergebenen Objekts kopieren for(int i=0; i<ms.anzahl; ++i) nfeld[anzahl+i]=ms.feld[i]; // Altes Array freigeben und neue Daten bernehmen delete[](feld); feld=nfeld; groesse=ngroesse; anzahl+=ms.anzahl; } return(*this); }
Listing 5.23 operator+= fr Mediensammlung

5.5.2

Arithmetische Operatoren

Stellvertretend fr die arithmetischen Operatoren werden wir hier den +-Operator fr die Klasse Aufnahmemedium berladen. Da bei dem +-Operator ein neues Objekt erzeugt wird, wollen wir im neuen Objekt die Summe der bespielten Zeit und die Summe der Kapazitten bilden. Die berladenen Operatoren haben bislang immer Auswirkungen auf das aufrufende Objekt gehabt. Jetzt wird ein Ergebnis gebildet, ohne dass ein an der Operation beteiligtes Objekt verndert wird. Deshalb haben wir zwei Mglichkeiten, diesen Operator zu definieren.
Operator als Methode

Die erste Variante ist die bereits angewendete, die Definition als Methode:
const Aufnahmemedium operator+(const Aufnahmemedium& am) const { Aufnahmemedium tmp(kapazitaet+am.kapazitaet, bespieltezeit+am.bespieltezeit); return(tmp); }
Listing 5.24 operator+ als Methode

Die Methode erzeugt ein neues Objekt und liefert dieses zurck. Das zurckgegebene Objekt ist konstant, damit Schreibweisen wie (a+b)=c nicht kompiliert werden.

220

Operatoren berladen

5.5

Operator als Funktion

Bisher haben wir alle Methoden inline in die Klasse geschrieben. Alle binren Operatoren, bei denen nicht zwangslufig ein Objekt der eigenen Klasse auf der linken Seite steht, knnen wahlweise auch als Funktion anstelle einer Methode implementiert werden. In diesem Fall kann der Operator nicht mehr in der Klasse stehen, sondern liegt als eigenstndige Funktion auerhalb der Klasse vor. Deswegen mssen wir die Funktion in Deklaration (abgelegt in der .h-Datei) und Definition (abgelegt in der .cpp-Datei) aufteilen. Die Syntax einer operator-Funktion:
const Klassenname operator+(const Klassenname& objektname1, const Klassenname& objektname1) { }

Nun nicht mehr Element der Klasse, hat die Funktion auch keinen Zugriff mehr auf die privaten Elemente der Klasse. Das Problem ist ziemlich einfach dadurch gelst, dass die Funktion als Freund der Klasse deklariert wird:
class Aufnahmemedium { friend const Aufnahmemedium operator+(const Aufnahmemedium& am1, const Aufnahmemedium& am2); // ... };
Listing 5.25 Friend-Deklaration der Operator-Funktion

Die operator+-Funktion selbst ist damit ein Klacks:


const Aufnahmemedium operator+(const Aufnahmemedium& am1, const Aufnahmemedium& am2) { Aufnahmemedium tmp(am1.kapazitaet+am2.kapazitaet, am1.bespieltezeit+am2.bespieltezeit); return(tmp); }
Listing 5.26 Die operator+-Funktion von Aufnahmemedium

In diesem konkreten Fall wre eine friend-Deklaration aber nicht einmal ntig gewesen, weil alle notwendigen Informationen ber die ffentliche Schnittstelle von Aufnahmemedium ermittelt werden knnen:

221

Fortgeschrittene Sprachelemente

const Aufnahmemedium operator+(const Aufnahmemedium& am1, const Aufnahmemedium& am2) { Aufnahmemedium tmp(am1.GetKapazitaet()+am2.GetKapazitaet(), am1.GetBespielteZeit()+am2.GetBespielteZeit()); return(tmp); }
Listing 5.27 operator+ mithilfe ffentlicher Zugriffsmethoden

Diese Variante sollten Sie, wann immer mglich, whlen, da der Operator hier keinerlei Informationen ber die Interna der Klasse bentigt. Und genau das sollte fr nicht zur Klasse gehrende Elemente auch immer gelten. Es gibt aber Flle, in denen die Operatoren auf interne Objektinformationen zugreifen mssen, so dass eine ffentliche Zugriffsmethode nicht in Frage kommt. Dann bleibt keine andere Wahl als eine friend-Deklaration.
Methode oder Funktion?

Eine noch zu klrende Frage ist, ob es besser ist, einen Operator als Methode oder als Funktion zu implementieren. Der Ausdruck a+b wird bei einer Methode zu
a.operator+(b);

umgewandelt. Bei einer Funktion kommt


operator+(a,b);

heraus. Eines fllt gleich ins Auge.


Hinweis Wird ein Operator als Methode implementiert, dann muss der linke Operand vom Typ der Klasse sein, in welcher der Operator definiert wurde.

Diese Einschrnkung hat bei der Klasse Aufnahmemedium keine Auswirkungen, da dort immer nur Objekte untereinander verknpft werden. Werden eigene numerische Datentypen implementiert, dann kann es aber reizvoll sein, diese auch mit den elementaren numerischen Typen verknpfen zu knnen. Des Weiteren kann der Compiler bei Funktionsparametern implizite Typumwandlungen vornehmen (siehe Abschnitt 4.5.4, Explizite Konstruktoren). Bei einer Methode ist der Typ des linken Operanden hingegen fest. Funktionen sollten daher den Methoden vorgezogen werden, wenn dadurch die Datenkapselung nicht leidet.

222

Operatoren berladen

5.5

5.5.3

Vergleichsoperatoren

Auch die Vergleichsoperatoren knnen berladen werden. Sie liefern als Ergebnis einen Booleschen Wert zurck, der bestimmt, ob der Vergleich stimmt oder nicht. Exemplarisch soll der Operator == fr die Klasse Aufnahmemedium berladen werden:
bool operator==(const Aufnahmemedium& am1, const Aufnahmemedium& am2) { return(am1.GetKapazitaet()==am2.GetKapazitaet() && am1.GetBespielteZeit()==am2.GetBespielteZeit()); }
Listing 5.28 operator== fr Aufnahmemedium

Der Operator == wurde als Funktion implementiert. Er kommt mit der ffentlichen Schnittstelle von Aufnahmemedium aus und bentigt daher keine friend-Deklaration.

5.5.4

Indexoperator

Den Indexoperator zu berladen macht immer dann Sinn, wenn Objekte einer Klasse als Container anderer Objekte dienen. Ein Beispiel ist die hier verwendete Klasse Mediensammlung. Bisher mssen wir die gespeicherten Medien ber GetMedium und Mediumhinzufuegen ansprechen. Viel schner wre es, wenn wir Folgendes schreiben knnten:
Mediensammlung ms(10); cout << ms[2].GetBespielteZeit() << endl;

Und genau das ermglicht uns der Indexoperator:


Aufnahmemedium& operator[](int idx) { return(feld[idx]); }
Listing 5.29 Der Indexoperator fr Mediensammlung

Das Aufnahmemedium muss als Referenz zurckgegeben werden, damit auch Zuweisungen mglich sind:
Aufnahmemedium am(120,30); ms[0]=am;

223

Fortgeschrittene Sprachelemente

Man knnte auch auf die Idee kommen, eine konstante Mediensammlung anzulegen:
const Mediensammlung ms(10); cout << ms[2].GetBespielteZeit() << endl; // FEHLER

Erstaunlicherweise lsst sich der Indexoperator nicht mehr verwenden, obwohl wir nur lesend auf das Objekt zugreifen. Das liegt daran, dass die OperatorMethode nicht konstanzwahrend ist (Abschnitt 4.6, Konstanzwahrende Methoden). Aber selbst wenn sie es wre, knnte ber die Referenz als Rckgabeparameter das Objekt gendert werden. Deshalb bentigen wir einen zweiten Indexoperator, der konstanzwahrend ist und nur eine Kopie zurckliefert:
Aufnahmemedium operator[](int idx) const { return(feld[idx]); }
Listing 5.30 Ein konstanzwahrender Indexoperator

Hinweis Die Eigenschaft const (konstanzwahrend) reicht als Unterscheidungskriterium beim berladen aus.

5.5.5

Dereferenzierungsoperator

Stellen Sie sich vor, wir wollten eine Klasse programmieren, deren Objekte Zeiger auf Zeichen eines Strings sein sollen. Dazu speichern die Objekte den String, der die Zeichen enthlt, und den Index des Zeichens, auf das gerade gezeigt wird. Der Index ist zu Beginn auf 0 gesetzt:
class StringZeiger { std::string& str; int pos; public: StringZeiger(std::string& s) : str(s), pos(0) { } };
Listing 5.31 Die Klasse StringZeiger mit Konstruktor

Wir knnen nun einen String erzeugen mitsamt einem StringZeiger darauf:

224

Operatoren berladen

5.5

string s="Andre"; StringZeiger sz(s);

Nur, wie sprechen wir jetzt ber sz das Zeichen an? Bei einem echten Zeiger bruchten wir nur zu dereferenzieren. Um die Fhigkeit, dereferenziert werden zu knnen, auch unseren Objekten zu verleihen, berladen wir den Dereferenzierungsoperator:
char& operator*() { return(str[pos]); }
Listing 5.32 Der Dereferenzierungsoperator von Stringzeiger

Der Dereferenzierungsoperator liefert eine Referenz auf das Zeichen des Strings zurck, damit auch Zuweisungen an ihn funktionieren. Nun kann unser eigenes Objekt auch dereferenziert werden:
cout << *sz << endl;

5.5.6

Inkrement- und Dekrementoperator

Momentan ist der StringZeiger in seiner Funktionalitt allerdings noch etwas eingeschrnkt, denn er zeigt immer nur auf das erste Zeichen. Bei einem Zeiger wrde jetzt der Inkrementoperator dafr sorgen, dass der Zeiger auf das nchste Zeichen zeigt. Um diese Funktionalitt zu erreichen, mssen wir den Inkrementoperator (und dabei gleich auch den Dekrementoperator) berladen:
StringZeiger& operator++() { ++pos; if(pos>=str.size()) pos=0; return(*this); } StringZeiger& operator--() { --pos; if(pos<0) pos=str.size()-1; return(*this); }
Listing 5.33 Der Prinkrement- und -dekrementoperator

225

Fortgeschrittene Sprachelemente

Die Pr-Operatoren liefern das Objekt selbst zurck, denn der um 1 erhhte oder verminderte StringZeiger ist immer noch ein StringZeiger. Nun knnen solche Sachen mit unseren eigenen Objekten geschrieben werden:
for(int i=0; i<10; ++i) cout << *(++sz) << endl;

Um die Post-Operatoren zu implementieren, mssen wir einen Trick anwenden. Denn sowohl der Prinkrement- also der Postinkrementoperator heien ++. Der Trick besteht darin, dass die operator++-Methode fr den Post-Operator einen Funktionsparameter bekommt, der aber keinerlei Bedeutung hat:
StringZeiger operator++(int) { StringZeiger tmp=*this; ++pos; if(pos>=str.size()) pos=0; return(tmp); } StringZeiger operator--(int) { StringZeiger tmp=*this; --pos; if(pos<0) pos=str.size()-1; return(tmp); }
Listing 5.34 Der Postinkrement- und -dekrementoperator

Die Post-Operatoren mssen eine Kopie des eigenen Objekts anfertigen, weil ja der Zustand vor dem Inkrement/Dekrement zurckgegeben werden muss. Deshalb liefern die Post-Operatoren auch keine Referenz zurck.
Tipp Eine Funktion/Methode sollte niemals einen Zeiger oder eine Referenz auf eine ihrer lokalen Variablen/Objekte zurckliefern, weil diese nach Beendigung der Funktion/ Methode nicht mehr existieren.

226

Die Philosophie der STL

6.1

Dieses Kapitel behandelt den wohl mchtigsten Teil der C++-Standardbibliothek. Die drei wesentlichen Komponenten, Container, Iteratoren und Algorithmen, werden in ihrer Struktur und Anwendung besprochen.

Die STL

Die Standard Template Library, kurz STL, ist ein Teil der C++-Standardbibliothek und basiert mageblich auf den in Abschnitt 5.4, Templates, besprochenen Templates. Sie befasst sich im Wesentlichen mit der Bereitstellung von Datenstrukturen, die als Container fr andere Objekte fungieren, und den Algorithmen, die auf diesen Containern operieren. Diese Container sind als Templates implementiert, um eine hohe Flexibilitt und Typsicherheit zu erzielen.

6.1

Die Philosophie der STL

Das besondere Konzept der STL besteht darin, dass die Algorithmen nicht direkt auf die Container zugreifen, sondern die Schnittstelle zwischen Algorithmus und Container durch sogenannte Iteratoren entkoppelt ist. Abbildung 6.1 zeigt diesen Zusammenhang.

Algorithmus

Iterator

Container

Abbildung 6.1

Das Zusammenspiel von Algorithmen, Iteratoren und Containern

Der Iterator wird im Allgemeinen vom entsprechenden Container zur Verfgung gestellt und besitzt die notwendigen Informationen, um sich innerhalb der Datenstruktur bewegen zu knnen. Er versetzt die Algorithmen durch seine einheitliche Schnittstelle in die Lage, auf die Daten des Containers zugreifen zu knnen, ohne etwas ber die interne Struktur des Containers wissen zu mssen.

227

Die STL

Durch diese Entkopplung kann ein Algorithmus mit jedem beliebigen Container zusammenarbeiten, solange der entsprechende Iterator existiert. Auf diese Weise kann die STL durch eigene Container ergnzt werden, ohne dass die bereits vorhandenen Algorithmen neu programmiert werden mssen. Auf der anderen Seite wird ein selbst geschriebener Algorithmus, der ber Iteratoren auf die Daten zugreift, mit jedem STL-konformen Container zusammenarbeiten knnen. Dieses Konzept ist dem Prinzip der Collections unter .NET berlegen. Glcklicherweise lsst sich die STL unter .NET auch nutzen, wodurch C++-Programmierer zum einen mit ihren gewohnten Strukturen arbeiten und ihre eigenen Ergnzungen unter .NET weiterverwenden knnen und zum anderen einen Vorteil gegenber den Nutzern der anderen .NET-Sprachen haben, fr die es keine STL gibt.

6.1.1

Container

Die STL untersttzt mehrere Container, die alle in Tabelle 6.1 aufgelistet sind.1 Zustzlich ist noch mit angegeben, auf welcher Datenstruktur die einzelnen Container basieren.
Container
vector deque list set multiset map multimap string

Strukturtyp Array Array doppelt verkettete Liste Baum Baum Baum Baum Feld

Tabelle 6.1 Die von der STL zur Verfgung gestellten Container

Dabei zhlen die Strings nicht hundertprozentig zu den STL-Containern, weil sie bestimmte Zugriffsmglichkeiten fr Iteratoren nicht untersttzen. Da
1 Im nchsten C++-Standard werden noch weitere Container wie array, forward_list, unordered_map oder unordered_set enthalten sein, die hier jedoch nicht aufgefhrt werden.

228

Die Philosophie der STL

6.1

Strings jedoch Verwendung finden, werden wir uns auch mit ihnen beschftigen. Einer der einfachsten Container ist der in der Header-Datei vector definierte Vektor. Er wird intern als Array implementiert und kann damit auch alle fr Felder typischen Operationen umsetzen. Die typische Definition eines Vektors sieht so aus:
vector<int> vektor(20);

Die Variable vektor entspricht nun einem 20-elementigen int-Feld. Dieses Beispiel konfrontiert uns auch gleich mit einer Besonderheit von Containern.
Hinweis Werden bei der Erzeugung eines Containers auch Elemente angelegt, dann wird fr diese der Standardkonstruktor (siehe Abschnitt 4.5.5, Standardkonstruktor) aufgerufen.

Im oberen Beispiel wurde daher fr alle 20 int-Elemente der Standardkonstruktor aufgerufen.

6.1.2

Iteratoren

Wie zu Beginn des Kapitels bereits erwhnt, bilden die Iteratoren die Schnittstelle zwischen den Containern und den Algorithmen. Doch was genau ist ein Iterator? Im Grunde genommen sind Iteratoren nichts weiter als abstrakte Zeiger.
Hinweis Als abstrakten Zeiger bezeichnet man ein Objekt, welches sich wie ein Zeiger verhlt, jedoch nicht notwendigerweise ein Zeiger sein muss. In der STL sind es blicherweise Klassenobjekte.

Nehmen wir als Beispiel ein Array:


int feld[20];

Wenn wir dieses Array nun mithilfe eines Zeigers ausgeben mchten, dann knnte dies wie folgt aussehen:
int* zeiger=feld; for(int x=0;x<20;x++) { cout << *zeiger << endl;

229

Die STL

++zeiger; }

Es wurde bewusst die Prfix-Notation der operator++-Methode eingesetzt, da diese, wie in Abschnitt 1.9.3, Inkrement und Dekrement, erklrt, Laufzeitvorteile mit sich bringt. Wenn wir die Idee des Zugreifens auf ein Array mithilfe eines Zeigers weiterentwickeln, erhalten wir eine Klasse, welche die Zeigerfunktionalitt kapselt:
template <typename Typ>class FeldIterator { private: Typ* feld; public: explicit FeldIterator(Typ* f) { feld=f; } Typ& operator*() { return(*feld); } FeldIterator& operator++() { feld++; return(*this); } FeldIterator& operator--() { feld++; return(*this); } bool operator!=(const FeldIterator<Typ>& i) { return(feld!=i.feld); } };
Listing 6.1 Die Template-Klasse FeldIterator

Dieses Template definiert nur die absolut notwendigen Methoden, um unsere Klasse fr das aktuelle Beispiel als Zeiger fungieren zu lassen. Der Operator != wurde berladen, um unseren Iterator spter fr Algorithmen tauglich zu machen. Ein richtiger Iterator weist noch weitere Eigenschaften auf, mit denen wir uns in Abschnitt 6.10 beschftigen werden.

230

Die Philosophie der STL

6.1

Wollen wir mit diesem abstrakten Zeiger das Feld ausgeben, dann sieht das so aus:
FeldIterator<int> iterator(feld); for(int y=0;y<20;y++) { cout << *iterator << endl; ++iterator; }

Vom reinen Zugriff her unterscheidet sich dieser abstrakte Zeiger nicht von einem gewhnlichen Zeiger. Ein gewaltiger Unterschied macht sich aber dann bemerkbar, wenn wir nicht mehr ein Feld, sondern eine Liste ausgeben wollen. Bei einem normalen Zeiger knnen wir dann nicht mehr den Inkrementoperator verwenden, sondern mssen das nachfolgende Listenelement ber den Verkettungsverweis innerhalb des aktuellen Elements ermitteln. Der abstrakte Zeiger hingegen kapselt die Ermittlung des nachfolgenden Elements in seiner Klasse, weswegen sich bei der Verwendung nichts ndern wird. Lediglich die interne Implementation der Methoden operator++ und operator-- ndert sich. Davon bekommt der Anwender jedoch nichts mit. Das bedeutet, mithilfe von abstrakten Zeigern kann ein einheitlicher Zugriff auf alle nur denkbaren Datenstrukturen implementiert werden. Und genau darin liegt die Strke der Iteratoren, die ja abstrakte Zeiger sind. Je nach Container werden an den Iterator verschiedene Anforderungen gestellt, bzw. nicht jeder Container kann alle mglichen Zugriffstypen untersttzen. Ein auf einem Array basierender Container kann beispielsweise ohne Bedenken einen direkten Zugriff (etwa durch ein berladen des Indexoperators) zur Verfgung stellen, whrend ein solcher Zugriff bei einer verketteten Liste obwohl programmtechnisch durchaus mglich nicht ohne erhebliche Einbuen in der Laufzeit einhergeht. Deswegen werden die Iteratoren in verschiedene Kategorien eingeteilt, die jeweils eine bestimmte Menge von Operationen zur Verfgung stellen. Tabelle 6.2 liefert einen kurzen berblick.
output
++ lesen schreiben

input X X

forward X X X

bidirectional X X X

random-access X X X

Tabelle 6.2

Die Iteratorkategorien im berblick

231

Die STL

output
-> ==, != -+, -, +=, -= <, >, <=, >= [ ]

input X X

forward X X

bidirectional X X X

random-access X X X X X X

Tabelle 6.2

Die Iteratorkategorien im berblick (Forts.)

Dieser Tabelle ist beispielsweise zu entnehmen, dass der Random-Access-Iterator der bei Weitem mchtigste Iterator ist. Wegen der uerst grozgigen Zugriffsoperatoren (wie dem Indexoperator) wird dieser Iterator jedoch nur von den wenigsten Containern untersttzt. Wenn im Folgenden Iteratoren als Parameter auftreten, dann werden an diese Iteratoren im Allgemeinen Mindestanforderungen gestellt. Um diese Mindestanforderungen zu definieren, wird die Iteratorkategorie angegeben, die diese Anforderungen noch erfllt. Wenn eine Funktion also einen bidirektionalen Iterator bentigt, dann kann dort kein Output-Iterator, jedoch ohne Probleme ein Random-Access-Iterator verwendet werden. Da wir noch viel mit Iteratoren zu tun haben werden, einigen wir uns auf Abkrzungen, die in Tabelle 6.3 aufgefhrt sind.
Iteratorkategorie output input forward bidirectional random-access Abkrzung Output Input Forward Bi Random

Tabelle 6.3 Abkrzungen fr die Iteratorkategorien

Sollte es sich um einen konstanten Iterator handeln, also einen Iterator, der nur auf einem Container lesen, aber keine Vernderungen vornehmen kann, dann wird der Kennzeichnung ein C vorangestellt. CRandom wre damit ein konstanter Random-Access-Iterator. Zustzlich kann jeder Iterator in Form eines Reverse-Iterators auftreten. Wir werden diese besondere Form noch in

232

Die Philosophie der STL

6.1

Abschnitt 6.11, besprechen. Hier sei nur soviel gesagt, dass mit ReverseIteratoren ein Container rckwrts durchlaufen werden kann.

6.1.3

Algorithmen

Die Algorithmen sind nun diejenigen Funktionen, die ber Iteratoren auf die Container zugreifen. Um das an einem Beispiel zu demonstrieren, wollen wir den simplen Algorithmus copy verwenden. Er wird folgendermaen aufgerufen:
copy(Iterator beginn, Iterator ende, Iterator ziel)

Dabei sind beginn, ende und ziel durch Iteratoren definierte Positionen, sogenannte Iteratorpositionen. Zuerst legen wir zwei Felder an:
int quellfeld[20]; int zielfeld[20];

Nun definieren wir drei Iteratoren unseres eigenen Typs, die den drei fr copy bentigten Positionen entsprechen:
FeldIterator<int> beginn(quellfeld); FeldIterator<int> ende(&quellfeld[20]); FeldIterator<int> ziel(zielfeld);

Achtung Die das Ende definierende Iteratorposition zeigt immer auf die Position hinter dem Ende.

Abbildung 6.2 verdeutlicht dies.


begin end

Abbildung 6.2 Die Start- und Endposition mit Iteratoren

Der Aufruf der copy-Funktion ist jetzt einfach. Um die Algorithmen der STL nutzen zu knnen, mssen wir die Datei algorithm mittels #include einbinden:
copy(beginn,ende,ziel);

Da Iteratoren nur lesend und berschreibend auf Container einwirken, knnen mit Algorithmen keine neuen Elemente zu einem Container hinzugefgt

233

Die STL

werden. Lediglich das berschreiben von Elementen ist mglich. Auf diese Thematik wird spter noch intensiver eingegangen.

6.1.4

Allokatoren

Die Container der STL verwenden zur Verwaltung ihres Speichers sogenannte Allokatoren.
Hinweis Ein Allokator ist eine Klasse, die Methoden zur Reservierung und Freigabe von Speicher zur Verfgung stellt.

Auf diese Weise kann das einem Container zugrunde liegende Speichermodell durch bloes Auswechseln des Allokators gendert werden.

6.2

Grundlagen

Bevor wir mit den Containern starten, schauen wir uns zuerst ein paar Punkte an, die zum besseren Verstndnis notwendig sind.

6.2.1

Zeitkomplexitt

Um Algorithmen oder allgemein Operationen auf Daten qualitativ bewerten zu knnen, wird ein objektives Ma bentigt. Es bietet sich an, als Ma die vom Algorithmus bentigte Zeit zu verwenden. Dabei verdienen die Laufzeiten, die der Algorithmus schlimmstenfalls (worst case), bestenfalls (best case) und durchschnittlich (average case) bentigt, besondere Aufmerksamkeit. Um eine Laufzeit angeben zu knnen, die nur den Algorithmus selbst bewertet, drfen keine implementationsabhngigen Faktoren mit einflieen. Nehmen wir als Beispiel die sequenzielle Suche. Sie findet ein vorhandenes Element, indem sie am Anfang der Datenstruktur beginnt und dann hintereinander jedes Element mit dem gesuchten Element vergleicht. Ist ein Vergleich positiv, dann wurde das Element gefunden. Erreicht die Suche erfolglos das Ende der Datenstruktur, dann ist das gesuchte Element nicht in der Datenstruktur enthalten. Da bei einer Anzahl von n Elementen maximal n Vergleiche durchgefhrt werden mssen, besitzt die sequenzielle Suche eine obere Grenze der Lauf-

234

Grundlagen

6.2

zeit, die k * n entspricht. Dabei ist k eine umgebungs- und implementierungsabhngige Konstante, die fr die eigentliche Betrachtung des Algorithmus keine Rolle spielt. Auf einem mit 3 GHz getakteten Rechner ist k beispielsweise kleiner als bei einem mit 2 GHz getakteten Rechner mit gleichem Prozessor. Und ein Multimedia-Freund, der den Verlauf der sequenziellen Suche grafisch auf dem Bildschirm anzeigen lsst, wird ein greres k haben als jemand, der den reinen Algorithmus direkt in Maschinensprache implementiert. Deswegen kann die Konstante k bei der Betrachtung der Laufzeit vernachlssigt werden. Sie ist nicht algorithmusspezifisch. Die sequenzielle Suche liegt damit in O(n)-Zeit. Die O-Notation beschreibt das worst-case-Verhalten.
Einteilung in Laufzeitklassen

Man kann die Laufzeiten von Algorithmen in Klassen aufteilen. Eine solche Aufteilung wurde in Tabelle 6.4 vorgenommen.
Laufzeit O(1) Bezeichnung konstant Beispiel Die Operationen push und pop bei Stacks bentigen im gnstigsten Fall konstante Zeit. Sie sind damit unabhngig von der Elementanzahl. Die binre Suche oder das Suchen in hhenbalancierten Bumen fllt in diese Kategorie. Bei linearer Erhhung der Elementanzahl steigt die bentigte Suchzeit immer langsamer. Die bereits besprochene sequenzielle Suche besitzt ein lineares Laufzeitverhalten. Die bentigte Zeit steigt proportional zur Elementanzahl. Ein optimales, auf Schlsselvergleichen basierendes Sortierverfahren kann bestenfalls O(n * log n)-Zeit besitzen. Einfache Sortierverfahren wie Bubblesort besitzen ein quadratisches Laufzeitverhalten. Beispielsweise bentigen alle Probleme, die in die Kategorie der NP-Vollstndigkeit fallen, zu ihrer Lsung exponentielle Laufzeit.

O(log n)

logarithmisch

O(n)

linear

O(n * log n)

n * log n

O(n2) O(2n)

quadratisch exponentiell

Tabelle 6.4 Die verschiedenen Laufzeitklassen

Abbildung 6.3 zeigt die stilisierten Graphen der einzelnen Laufzeitklassen. Die Grafik dient nur der Darstellung, in welchem Mae die Laufzeit bei

235

Die STL

zunehmendem n fr jede Laufzeitklasse steigt. Die Graphen sind nicht mastabsgetreu.


exponentiell quadratisch N log N linear

logarithmisch

konstant

Abbildung 6.3 Die bentigte Zeit der Laufzeitkassen in Abhngigkeit von der Elementanzahl

Eine Verdopplung der Elementanzahl wirkt sich bei den Laufzeitklassen, wie in Tabelle 6.5 aufgefhrt, auf die Laufzeit aus.
Laufzeitklasse konstant logarithmisch linear n * log n Verhalten Die bentigte Zeit bleibt konstant. Die bentigte Zeit nimmt um einen konstanten Betrag zu. Die bentigte Zeit verdoppelt sich. Die bentigte Zeit verdopppelt sich und erhht sich zustzlich noch um einen konstanten Wert (Verknpfung von linearer und logarithmischer Laufzeit). Die bentigte Zeit vervierfacht sich. Die bentigte Zeit quadriert sich.

quadratisch exponentiell

Tabelle 6.5 Das Verhalten der Laufzeit bei einer Verdopplung der Elementanzahl

Best Case und Average Case

Hufig ist es interessant, nicht nur den worst case zu betrachten. Der gnstigste Fall wird mit der Omega-Notation ausgedrckt. Fr die sequenzielle Suche tritt die gnstigste Situation dann ein, wenn das zu suchende Element als Erstes in der Datenstruktur steht. In diesem Fall bentigt die Suche eine Laufzeit von 1. Um den gnstigsten Fall auszudrcken, verwenden wir die Omega-Notation: (1). Fr den Fall, dass das Laufzeitverhalten fr den besten und den schlimmsten Fall identisch ist, benutzt man die Theta-Notation. Beispielsweise bentigen

236

Grundlagen

6.2

die Stack-Operationen push und pop im gnstigsten und schlimmsten Fall jeweils konstante Laufzeit, weswegen ein Laufzeitverhalten von (1) gilt. Fr die Praxis ist auch der durchschnittliche Fall interessant. Der hufig eingesetzte Sortier-Algorithmus Quicksort arbeitet beispielsweise schlimmstenfalls in O(n2)-Zeit, was auf den ersten Blick eher abschreckend wirkt. Weil er aber im durchschnittlichen Fall nur ungefhr eine Zeit von n *log n bentigt, wird er gerne eingesetzt.

6.2.2

Funktionsobjekte

Des fteren kommen in der STL Methoden oder Algorithmen vor, denen eine Funktion bergeben werden muss. Diese Funktion wird dann zur Ermittlung eines Entscheidungskriteriums herangezogen. Ein Beispiel ist der remove_if-Algorithmus, der aufgrund eines bestimmten Kriteriums Elemente aus einem Bereich herauslscht:
remove_if(anfang, ende, funktion)

Angenommen, wir wollten alle Elemente eines Bereichs, die einen geraden Wert haben, herauslschen, dann knnten wir folgende Funktion schreiben:
bool iseven(int a) { return((a%2)==0); }

Unter der Voraussetzung, dass a und b zwei Iteratorpositionen in einem intElemente enthaltenden Container sind, wrde folgende Anweisung alle geraden Werte lschen:
remove_if(a,b,iseven); remove_if ruft fr jedes Element n die Funktion iseven(n) auf und entschei-

det anhand des Rckgabewertes (wahr oder falsch), ob das Element gelscht wird oder nicht. Was aber, wenn iseven beispielsweise mitzhlen soll, wie hufig es aufgerufen wurde, oder wenn festgehalten werden soll, wie viele Elemente einen geraden Wert hatten? Die Lsung sind Funktionsobjekte.
Funktionsobjekt Unter dem Begriff Funktionsobjekt versteht man ein Objekt einer Klasse, die einen Funktionsaufrufoperator besitzt und damit wie eine Funktion aufgerufen werden kann.

237

Die STL

Passen wir iseven schnell an:


class iseven { public: bool operator()(int a) { return((a%2)==0); } };
Listing 6.2 Die Klasse iseven

Der Aufruf von remove_if ndert sich damit in:


remove_if(a,b,iseven() );

Der Aufruf iseven() steht natrlich nicht fr den Aufruf der operator()Methode, sondern fr den Aufruf des impliziten Standardkonstruktors, der ein Objekt von iseven erzeugt. Dieses Objekt wird dann an remove_if bergeben. Damit alle verwendeten Funktionsobjekte eine gemeinsame Basis haben, stellt die STL zwei Basisklassen zur Verfgung, fr einparametrige Funktionsobjekte und fr zweiparametrige Funktionsobjekte. Diese Basisklassen knnen wie folgt aussehen:
template<typename TypA, typename Ergebnis> struct unary_function { typedef TypA argument_type; typedef Ergebnis result_type; }; template<typename TypA, typename TypB, typename Ergebnis> struct binary_function { typedef TypA first_argument_type; typedef TypB second_argument_type; typedef Ergebnis result_type; };
Listing 6.3 unary_function und binary_function

Durch das Ableiten von diesen Klassen besitzen alle Funktionsobjekttypen ber ihre Basisklasse einheitliche Namen und Typen. Die STL-Definitionen von unary_function und binary_function finden Sie in der Header-Datei functional. Um unser Funktionsobjekt nun STL-konform zu implementieren, mssen wir von der Klasse unary_function ableiten:

238

Grundlagen

6.2

template<typename Typ> class iseven : public unary_function<Typ, bool> { public: bool operator()(int a) { return((a%2)==0); } };
Listing 6.4 iseven mit unary_function als Basisklasse

Der entsprechende Aufruf von remove_if sieht dann so aus:


remove_if(a,b,iseven<int>());

Prdikate

Im Zusammenhang mit Algorithmen und Containern, dort speziell mit den Container-Methoden, werden des fteren Prdikate verwendet. Nachdem wir nun wissen, was Funktionsobjekte sind, ist der Schritt zu einem Prdikat nur noch ein kleiner:
Prdikat Unter dem Begriff Prdikat versteht man ein Funktionsobjekt, welches einen Wert vom Typ bool zurckliefert.

Mit unserem Funktionsobjekt iseven haben wir bereits ein Prdikat implementiert, ohne es zu wissen. Wenn Prdikate eingesetzt werden, dann meistens in Form von elementaren logischen Operationen. Damit nicht jeder Benutzer von Prdikaten das Rad wieder neu erfinden muss, stellt die STL einige grundlegende Prdikate in der Header-Datei functional zur Verfgung, die in Tabelle 6.6 aufgelistet sind.
Name
equal_to Greater greater_equal Less less_equal logical_and logical_not

Funktionalitt
a == b a > b a >= b a < b a <= b a && b !a

Tabelle 6.6

Vordefinierte Prdikate

239

Die STL

Name
logical_or not_equal_to

Funktionalitt
a || b a != b

Tabelle 6.6

Vordefinierte Prdikate (Forts.)

Das Prdikat logical_not ist das einzige Prdikat, welches von unary_ function erbt, und somit nur einen Parameter besitzt. Alle anderen Prdikate sind von binary_function abgeleitet und damit zweiparametrig. Als Beispiel schauen wir uns einmal an, wie das Prdikat equal_to aussehen knnte:
template<typename Typ> struct equal_to : public binary_function<Typ, Typ, bool> { bool operator()(const Typ& a, const Typ& b) const { return (a==b); } };
Listing 6.5 Das Prdikat equal_to

Immer, wenn in diesem Buch ein Prdikat gefordert wird, werden folgende Abkrzungen als Datentyp angegeben:
Abkrzung Pred BinPred Bedeutung Prdikat mit einem Parameter Prdikat mit zwei Parametern

Tabelle 6.7 Die verwendeten Abkrzungen fr Prdikate

Arithmetische Objekte

Abgesehen von den Prdikaten wurden noch einige arithmetische Operationen als Funktionsobjekte implementiert:
Name
divides minus modulus multiplies negate Plus

Funktionalitt
a /b a b a % b a * b - a a + b

Tabelle 6.8 Vordefinierte arithmetische Funktionsobjekte

240

Grundlagen

6.2

Wenn Funktionen ein Funktionsobjekt fordern, dann wird dies durch Verwendung der folgenden Abkrzungen kenntlich gemacht:
Abkrzung Op BinOp Bedeutung Funktionsobjekt mit einem Parameter Funktionsobjekt mit zwei Parametern

Tabelle 6.9 Die verwendeten Abkrzungen fr arithmetische Funktionsobjekte

Dabei ist zu bercksichtigen, dass Prdikate auch Funktionsobjekte sind. BinOp kann daher ein zweiparametriges arithmetisches Funktionsobjekt, aber auch ein zweiparametriges Prdikat sein.
Binder

Hufig wird ein zweiparametriges Prdikat mit einem festen Wert bentigt. Wenn z. B. alle Elemente gelscht werden sollen, die den Wert 10 haben, dann kme das Prdikat equal_to nur bedingt in Frage. Aber nur deswegen ein eigenes Prdikat mit nur einem Parameter zu schreiben, wre zu aufwndig. Weil solche Flle hufiger auftreten, wurde die STL um sogenannte Binder erweitert.
Binder Ein Binder benutzt ein zweiparametriges Funktionsobjekt und belegt einen der beiden Parameter mit einem festen Wert. Das so entstandene einparametrige Funktionsobjekt kann dann wie gewohnt eingesetzt werden.

Von der STL werden dazu zwei Binder zur Verfgung gestellt. Der eine belegt den ersten Parameter mit einem festen Wert und heit bind1st. Der zweite belegt den zweiten Parameter mit einem festen Wert und wird bind2nd genannt. Schauen wir uns einmal ein konkretes Beispiel an. Im Folgenden wird mittels remove_if ein bestimmter Bereich abgearbeitet und jedes Element, welches den Wert 10 hat, gelscht:
remove_if(a,b,bind1st(equal_to<int>(),10)); bind1st hat als ersten Parameter das zu bindende Funktionsobjekt. Der zweite Parameter spezifiziert den Wert, mit dem der gebundene Parameter belegt werden soll. Analog dazu existiert bind2nd mit festem erstem Wert.

241

Die STL

6.2.3

Paare

Bei Containern und Algorithmen werden ab und zu Paare bentigt. Zum Beispiel besteht bei dem Container map jedes Element sowohl aus einem Schlssel als auch aus den Nutzdaten. Dabei besitzen Schlssel und Nutzdaten fr gewhnlich unterschiedliche Datentypen. Um diese beiden Objekte trotzdem als ein Objekt betrachten zu knnen, wurden die Paare ins Leben gerufen. Dabei knnte die Definition eines Paares wie folgt aussehen (die Definition von pair und ihren Funktionen finden Sie in der Header-Datei utility):
template <typename TypA, typename TypB> struct pair { TypA first; TypB second; pair() : first(), second() {} pair(const TypA& a, const TypB& b) : first(a), second(b) {} };
Listing 6.6 Die Klasse pair

Die expliziten Aufrufe der Standardkonstruktoren von first und second im Standardkonstruktor von pair sind nicht notwendig, erhhen hier aber die bersichtlichkeit. Um ein Paar zu erzeugen, verwenden wir fr Templates bliche Definitionen:
pair<int, char*> a,b(2,"Andre");

Um das bloe Erzeugen eines Paares etwas angenehmer zu gestalten, existiert die Template-Funktion make_pair:
template < typename TypA, typename TypB> inline pair<TypA, TypB> make_pair(const TypA& a, const TypB& b) { return(pair<TypA, TypB>(a,b)); }
Listing 6.7 Die Funktion make_pair

Der Funktion werden einfach die beiden Objekte bergeben, und sie liefert das entsprechende Paar zurck. Paare mssen natrlich auch verglichen werden knnen. Grundstzlich sind zwei Paare dann gleich, wenn jeweils die beiden ersten Objekte und die bei-

242

Grundlagen

6.2

den zweiten Objekte gleich sind. Die entsprechende Operator-Funktion, die bei Paaren global definiert wird, sieht wie folgt aus:
template < typename TypA, typename TypB> inline bool operator==(const TypA& a, const TypB& b) { return((a.first==b.first) && (a.second==b.second)); }
Listing 6.8 Die operator==-Funktion von pair

Zustzlich bentigen wir noch einen Vergleich auf kleinerer Ebene. Ein Paar a ist genau dann kleiner als ein Paar b, wenn entweder das erste Objekt von a kleiner ist als das erste Objekt von b oder die beiden ersten Objekte gleich und das zweite Objekt von a kleiner als das zweite Objekt von b ist:
template < typename TypA, typename TypB> inline bool operator<(const TypA& a, const TypB& b) { return((a.first<b.first) || ((!(b.first<a.first))&&(a.second<b.second))); }
Listing 6.9 Die operator<-Funktion von pair

Innerhalb von operator< wurde bewusst auf einen Einsatz von operator== verzichtet. Schauen wir uns den Ausdruck !(b<a) einmal an. Umgeformt bedeutet er a<=b. Da der Fall a<b jedoch im vorigen Booleschen Ausdruck behandelt wird, kann an dieser Stelle nur a==b gelten. Warum nur die Vergleiche auf Gleichheit und kleiner implementiert werden, erfahren Sie im nchsten Abschnitt.

6.2.4

Vergleichsoperatoren

Grundstzlich muss ein STL-Objekt, um alle Vergleichsoperationen zu untersttzen, lediglich die Funktionen operator< und operator== zur Verfgung stellen. Alle weiteren Vergleichsoperatoren liegen berladen als globale Template-Funktionen vor (die Template-Funktionen finden Sie in der HeaderDatei utility, im Namensbereich std::rel_ops). Ein Beispiel fr den >-Operator knnte folgendermaen aussehen:
template < typename Typ> inline bool operator>(const Typ& a, const Typ& b) {

243

Die STL

return(b<a); }
Listing 6.10 Die globale Template-Funktion operator>

Die anderen Operatoren sind analog dazu implementiert.

6.2.5

Bereichsangaben

Da im Zusammenhang mit Iteratoren, Algorithmen und Containern hufig Bereiche definiert werden, zeigt Tabelle 6.10, welcher Schreibweise in der Mathematik und in diesem Buch welche Definition zugrunde liegt.
Schreibweise [10, 20] [10, 20[ ]10, 20] ]10, 20[ [10, 20) (10, 20] (10,20) Alternativ Bedeutung Der Bereich von 10 bis 20, einschlielich der 10 und der 20. Der Bereich von 10 bis 20, einschlielich der 10, aber ohne die 20. Der Bereich von 10 bis 20, ohne die 10, aber einschlielich der 20. Der Bereich von 10 bis 20, ausschlielich 10 und 20.

Tabelle 6.10 Bereichsangaben

6.3

Vektoren

Vektoren sind vom internen Aufbau her gesehen die einfachsten aller Container (mal abgesehen von array, welcher eine feste Gre besitzt, die nicht dynamisch verndert werden kann), und zusammen mit den Deques wohl auch die meistgenutzten. Ein Vektor gleicht einem in C++ typischen Feld. In Abbildung 6.4 sehen Sie einen Vektor der Gre 5 und der Kapazitt 12. Der Vektor beinhaltet damit augenblicklich fnf Elemente und kann insgesamt zwlf Elemente aufnehmen, ohne neuen Speicher anfordern zu mssen. Wie bereits erwhnt, sind in diesem Fall die Positionen 511 keineswegs leer, sondern es befinden sich dort Elemente des entsprechenden Typs, fr die der Standardkonstruktor aufgerufen wurde mit der einzigen Ausnahme, dass der Speicherbereich eines Vektors nicht initialisiert wird, wenn die Kapazitt mit reserve vergrert wurde. Die Abbildung zeigt die Situation im Speicher.

244

Vektoren

6.3

10

11

Abbildung 6.4 Ein Vektor der Gre 5 und Kapazitt 12

Diese lineare Anordnung von Elementen nennt man auch Sequenz. Damit wir das vector-Template verwenden knnen, mssen wir die Datei vector einbinden. Die Definitionen gehren dem Namensbereich std an. Da die vector-Klasse als Template implementiert ist, knnen wir fr jeden beliebigen Datentyp einen Vektor erzeugen. Ein konkreter Vektor jedoch kann nur Elemente eines bestimmten Datentyps aufnehmen. Die Parameter des Templates bestimmen den zu verwaltenden Datentyp und den Typ des Allokators:
template<typename Typ, typename Allok=allocator<Typ> > class std::vector { ... };
Listing 6.11 Deinitionsskelett von vector

Im Folgenden steht Allok damit fr den im Template ausgewhlten Allokator-Typ. Innerhalb der Klasse werden einige Datentypen deklariert. Um eine Vorstellung zu bekommen, wie das praktisch vonstatten geht, sind diese Definitionen fr den Vektor einmal aufgefhrt:
typedef typedef typedef typedef typedef typedef typedef typedef Typ value_type; Allok allocator_type; typename Allok::size_type size_type; typename Allok::difference_type difference_type; typename Allok::pointer pointer; typename Allok::const_pointer const_pointer; typename Allok::reference reference; typename Allok::const_reference const_reference;

Es ist zu erkennen, dass einfach nur die Definitionen des Allokators bernommen werden. Tabelle 6.11 beschreibt die Bedeutung der einzelnen Datentypen.
Datentyp
value_type allocator_type

Beschreibung Typ der zu verwaltenden Elemente Allokator-Typ

Tabelle 6.11 Die Datentypen von vector

245

Die STL

Datentyp
size_type

Beschreibung Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder die Kapazitt des Vektors Typ fr die Angabe von Abstnden zweier Elemente Zeiger-Typ Typ fr konstante Zeiger Referenz-Typ Typ fr konstante Referenzen

difference_type pointer const_pointer reference const_reference

Tabelle 6.11 Die Datentypen von vector (Forts.)

size_type definiert den Datentyp, mit dem Gren ausgedrckt werden. Im

Normalfall ist dieser Typ vom Allokator als unsigned int definiert. Um Abstnde (z. B. von Iteratorpositionen) auszudrcken, die auch negativ sein knnen, wird der Typ difference_type verwendet, der im Allgemeinen vom Typ int ist.

6.3.1

Konstruktoren

Im Folgenden sind die Konstruktoren von vector aufgefhrt. Auf die Angabe der Template-Parameter im Konstruktornamen wird verzichtet:
explicit vector(const Allok& a=Allok() )

Erzeugt einen leeren Vektor. Bei Bedarf kann ein Allokator-Objekt angegeben werden.
vector(const vector<Typ, Allok>& v)

Erzeugt einen Vektor als Kopie von Vektor v (Kopierkonstruktor). Dabei mssen Daten- und Allokator-Typ bei beiden Vektoren bereinstimmen.
explicit vector(size_type anz, const Typ& obj=Typ(), const Allok& a=Allok())

Erzeugt einen Vektor mit anz Elementen des Typs Typ. Alle erzeugten Elemente sind eine Kopie des Objekts obj. Sollte kein Objekt angegeben werden, so wird eines mithilfe des zugehrigen Standardkonstruktors erzeugt. Bei Bedarf kann ein Allokator-Objekt angegeben werden.
vector (CInput anf, CInput end, const Allok& a=Allok())

Erzeugt einen Vektor, der Kopien der Elemente im Bereich [anf, end[ enthlt. anf und end sind dabei Iteratorpositionen. Die Gre des erzeugten

246

Vektoren

6.3

Vektors entspricht damit exakt der Anzahl der im Bereich [anf, end[ enthaltenen Elemente. Bei Bedarf kann ein Allokator-Objekt angegeben werden.

6.3.2

Operatoren

Fr den Vektor sind die folgenden Operatoren berladen.


operator=

Der Zuweisungsoperator wird dazu eingesetzt, um einem Vektor einen anderen Vektor zuzuweisen. Auf diese Weise wird eine komplette Kopie angelegt.
vector<Typ,Allok>& operator=(const vector<Typ,Allok>& v);

Vergleichsoperatoren

Der Vektor definiert die Vergleichsoperatoren < und ==, die in Kombination mit den Vergleichsoperator-Templates alle Vergleiche bereitstellen.
Hinweis Wie bereits im Grundlagen-Kapitel erwhnt, reichen diese beiden Vergleichsoperatoren aus, um auch alle anderen Vergleiche formulieren zu knnen. Diese anderen Vergleichsoperatoren sind in der STL bereits als Template-Funktionen definiert.

Der Effizienz wegen greifen manche Implementationen nicht auf die von der STL vorgegebenen Templates zurck, sondern implementieren alle Vergleichsoperatoren selbst.
template<typename Typ, typename Allok> bool std::operator<(const vector<Typ, Allok>& v1, const vector<Typ, Allok>& v2); template<typename Typ, typename Allok> bool std::operator==(const vector<Typ, Allok>& v1, const vector<Typ, Allok>& v2);

Dabei werden die Vektoren lexikografisch verglichen.


Gleichheit von Vektoren Zwei Vektoren v1 und v2 sind genau dann gleich, wenn fr alle Indizes n={0,...,size1} gilt: v1[n]==v2[n].

Daher knnen Vektoren ungleicher Elementanzahl nicht gleich sein.

247

Die STL

Vektoren in Relation Ein Vektor v1 ist genau dann kleiner als ein Vektor v2, wenn fr die ersten Elemente v1[n] und v2[n], die ungleich sind, gilt: v1[n]<v2[n] oder wenn fr alle Elemente gilt: v1[n]==v2[n], aber v1 weniger Elemente als v2 beinhaltet.

Der Vektor [2, 8, 20, 17] ist damit kleiner als der Vektor [2, 8, 18, 17] und der Vektor [2, 3, 8] ist kleiner als der Vektor [2, 3, 8, 1].
operator[ ]

Der Indexoperator wird genau wie bei gewhnlichen Feldern fr den direkten Elementzugriff verwendet. Der Operator existiert dabei in zwei Varianten, fr variable und konstante Vektoren:
reference operator[] (size_type index); const_reference operator[] (size_type index) const;

Achtung Bei den STL-Containern prft operator[ ] nicht, ob der angegebene Index im gltigen Bereich liegt.

Es ist daher uerst wichtig, den Index selbst auf seine Gltigkeit hin zu prfen, um mgliche Fehler zu vermeiden. Folgende Anweisungen wrden keinen Laufzeitfehler verursachen:
vector<int> v(10); v[20]=0;

Weil die Elemente eines Vektors in einem einzigen Speicherbereich angeordnet sind, kann ber den Indexoperator jedes Element in O(1)-Zeit angesprochen werden.

6.3.3

Methoden

Im Folgenden werden die fr vector implementierten Methoden besprochen. Doch zuerst eine bersicht:
Methode
assign at back

Kurzbeschreibung Weist einem Vektor Elemente zu. Spricht Vektor-Element ber Index an. Liefert letztes Element des Vektors.

Tabelle 6.12 Die Methoden von vector

248

Vektoren

6.3

Methode
begin capacity clear empty end erase front insert max_size pop_back push_back rbegin rend reserve resize size swap

Kurzbeschreibung Liefert Iteratorposition auf erstem Element des Vektors. Liefert Kapazitt des Vektors. Lscht alle Elemente des Vektors. berprft, ob Vektor leer ist. Liefert Iteratorposition hinter dem letzten Element des Vektors. Lscht Elemente. Liefert das erste Element des Vektors. Fgt Elemente ein. maximal mglicheGre eines Vektors Entfernt letztes Element. Hngt Element am Ende des Vektors an. Reverse-Iteratorposition auf dem letzten Element des Vektors Reverse-Iteratorposition vor dem ersten Element des Vektors Vergrert die Kapazitt des Vektos. Verndert die Kapazitt des Vektors. Ermittelt die Elementanzahl. Vertauscht zwei Vektoren.

Tabelle 6.12 Die Methoden von vector (Forts.)

assign Elemente zuweisen


void assign(size_type anz, const Typ obj=Typ() );

Weist einem Vektor anz Elemente des Typs Typ zu. Alle angelegten Elemente sind Kopien des Objekts obj. obj besitzt als Standardwert ein mit dem Standardkonstruktor von Typ erzeugtes Objekt.
Achtung Durch assign gehen die vorher im Vektor gespeicherten Elemente verloren.
void assign(Input anf, Input end);

Weist einem Vektor Kopien der im Bereich [anf, end[ liegenden Elemente zu. Die vorher im Vektor gespeicherten Elemente gehen dabei verloren.
at Index mit Prfung
reference at(size_type index); const_reference at(size_type index) const;

249

Die STL

Funktionsgleich mit dem Indexoperator, liefert at eine Referenz auf das Element mit Index index zurck. Allerdings prft at zustzlich, ob der Index im gltigen Bereich liegt. Wenn nicht, dann wird eine out_of_range-Exception ausgelst. at wurde jeweils fr variable und konstante Vektoren implementiert. Genau wie der Indexoperator kann auch at jedes Element in O(1)-Zeit ansprechen.
back letztes Element im Vektor
reference back(); const_reference back() const;

Liefert eine Referenz auf das letzte Element des Vektors (das Element mit Index size()-1). back wurde jeweils fr variable und konstante Vektoren implementiert. Da back mithilfe der Methode at implementiert werden kann, besitzt auch back eine Laufzeit von O(1).
begin Iteratorposition des ersten Elements
Random begin(); CRandom begin() const;

Liefert die Iteratorposition des ersten Elements im Vektor. begin besitzt eine Laufzeit von O(1).
capacity Kapazitt des Vektors
size_type capacity() const;

Liefert die Kapazitt eines Vektors.


Hinweis Als Kapazitt bezeichnet man die Anzahl von Elementen, die ein Vektor speichern kann, ohne neuen Speicherplatz reservieren zu mssen.

Deswegen liefert capacity immer einen Wert, der mindestens gleich gro ist wie der von size zurckgegebene Wert.
clear Vektorinhalt lschen
void clear(); clear lscht alle Elemente eines Vektors. Die Kapazitt bleibt jedoch erhal-

ten. Das hat zur Folge, dass der Vektor selbst nach einem Aufruf von clear

250

Vektoren

6.3

nicht weniger Speicher verbraucht als vorher. clear bentigt O(m)-Zeit, wobei m fr die Anzahl der zu lschenden Elemente steht.
empty Vektor leer?
bool empty() const;

Gibt Auskunft darber, ob ein Vektor augenblicklich Elemente enthlt oder nicht.
end Iteratorposition hinter dem letzten Element
Random end(); CRandom end() const;

Liefert die Iteratorposition hinter dem letzten Element im Vektor. end besitzt eine Laufzeit von O(1).
erase Elemente lschen
Random erase(Random pos);

Lscht in einem Vektor das Element an der Iteratorposition pos, und liefert die Iteratorposition hinter pos zurck.
Random erase(Random anf, Random end);

Lscht in einem Vektor die im Bereich [anf, end[ liegenden Elemente, und liefert die Iteratorposition end zurck. Der grundlegende Ablauf der erase-Methode ist in Abbildung 6.5 dargestellt. Die Elemente mit den Inhalten e, f, g und h (in der Abbildung grau hinterlegt) sollen gelscht werden. erase lscht nun nicht zuerst die entsprechenden Elemente, sondern kopiert die Elemente, die durch das Lschen aufrcken (die Elemente i bis o) an die Positionen der zu lschenden Elemente. Durch dieses Kopieren sind die letzten vier Elemente berflssig und knnen nun gelscht werden.
a b c d e f g h i j k l m n o

Abbildung 6.5 Die Funktionsweise von erase

251

Die STL

erase bentigt O(m+l)-Zeit, wobei m fr die Anzahl der zu kopierenden und

l fr die Anzahl der zu lschenden Elemente stehen.


front erstes Element
reference front(); const_reference front() const;

Liefert eine Referenz auf das erste Element des Vektors (das Element mit Index 0). Die Methode ist jeweils fr variable und konstante Vektoren implementiert. Da front mithilfe der Methode at implementiert werden kann, besitzt auch front eine Laufzeit von O(1).
insert Elemente einfgen
Random insert(Random pos, const Typ& obj=Typ() );

In einem Vektor wird an der Iteratorposition pos ein Element als Kopie von obj eingefgt. obj besitzt als Standartwert ein mit dem Standardkonstruktor von Typ erzeugtes Objekt. insert liefert die Iteratorposition des neu eingefgten Objektes zurck.
void insert(Random pos, size_type anz, const Typ& obj);

In einem Vektor wird an der Iteratorposition pos eine Anzahl von anz Elementen als Kopien von obj eingefgt.
void insert(Random pos, CInput anf, CInput end);

In einem Vektor werden an der Iteratorposition pos Kopien der im Bereich [anf, end[ liegenden Elemente eingefgt. Fr den Fall, dass die Kapazitt des Vektors zum Einfgen ausreicht, bentigt insert O(m+l)Zeit, wobei m fr die Anzahl der zu kopierenden und l fr die Anzahl der einzufgenden Elemente stehen.
Hinweis
insert stellt von der Funktionalitt her die Umkehrung von erase dar.

Sollte die Kapazitt nicht ausreichen und deswegen ein neuer Speicherbereich angelegt werden, dann bentigt insert O(n+1)-Zeit, wobei n fr die aktuelle Elementanzahl des Vektors und l fr die Anzahl der einzufgenden Elemente stehen.
Tipp Wenn ungefhr abgeschtzt werden kann, wie viele Elemente der Vektor aufnehmen muss, dann kann dem letzten Fall durch Einsatz von reserve vorgebeugt werden.

252

Vektoren

6.3

max_size maximal mgliche Gre


size_type max_size() const;

Liefert die maximale Gre zurck, die ein Vektor auf dem aktuellen System haben kann.
pop_back letztes Element entfernen
void pop_back();

Lscht das letzte Element eines Vektors. Obwohl pop_back gerne Stack-Operation genannt wird, ist diese Bezeichnung trgerisch. Denn im Gegensatz zur blichen Stack-Operation pop, die das entfernte Element auch zurckliefert, besitzt pop_back keinen Rckgabewert. pop_back besitzt eine Laufzeit von O(1).
push_back Element hinten anhngen
void push_back(const Typ& obj);

Hngt an einen Vektor eine Kopie des Elements obj an. Wenn die Kapazitt des Vektors ausreicht, besitzt push_back eine Laufzeit von O(1). Ansonsten wird O(n)-Zeit bentigt, wobei n fr die Anzahl der Elemente im Vektor steht.
rbegin Reverse-Iterator auf das letzte Element
reverse_iterator rbegin(); const_reverse_iterator rbegin() const;

Liefert die Iteratorposition des letzten Elements im Vektor in Form eines Reverse-Iterators. rbegin besitzt eine Laufzeit von O(1).
rend Reserse-Iterator auf das erste Element
reverse_iterator rend(); const_reverse_iterator rend() const;

Liefert die Iteratorposition hinter dem letzten Element im Vektor in Form eines Reverse-Iterators. rend besitzt eine Laufzeit von O(1).
reserve Vektorkapazitt vergrern
void reserve(size_type anz);

253

Die STL

Reserviert Speicherplatz fr die Aufnahme von anz Elementen. Sollte die aktuelle Kapazitt kleiner als anz sein, dann wird der Vektor um die ntige Menge von Elementen vergrert. Andernfalls ist reserve ohne Wirkung.
Hinweis Der reservierte Speicher wird nicht mit Elementen initialisiert.
reserve bentigt schlimmstenfalls eine Laufzeit von O(n), wobei n die aktuelle Anzahl der im Vektor befindlichen Elemente darstellt. Der ungnstigste Fall tritt dann ein, wenn ein neuer Speicherbereich reserviert wird und deswegen alle Elemente des Vektors umkopiert werden mssen.

resize Vektorkapazitt anpassen


void resize(size_type anz, const Typ& obj=Typ() );

ndert die Kapazitt eines Vektors auf die Gre anz. Sollte anz kleiner sein als die aktuelle Gre des Vektors, dann werden die berflssigen Elemente gelscht. Fr den Fall, dass anz grer als die aktuelle Vektorgre ist, werden entsprechend viele Elemente als Kopie von obj an den Vektor angehngt. obj besitzt als Standardwert ein mit dem Standardkonstruktor von Typ erzeugtes Objekt. resize bentigt schlimmstenfalls eine Laufzeit von O(n), wobei n die aktuelle Anzahl der im Vektor befindlichen Elemente darstellt. Der ungnstigste Fall tritt dann ein, wenn ein neuer Speicherbereich reserviert wird und deswegen alle Elemente des Vektors umkopiert werden mssen.
size Anzahl der Elemente im Vektor
size_type size() const

Liefert die Anzahl der augenblicklich im Vektor gespeicherten Elemente. size liefert daher immer einen Wert, der kleiner oder gleich dem Wert von capacity ist.
swap Inhalt zweier Vektoren vertauschen
void swap(vector<Typ, Allok>& v);

Vertauscht zwei Vektoren. swap besitzt eine Laufzeit von O(1).

254

Vektoren

6.3

6.3.4

Vektoren im Einsatz

Zuerst legen wir einen leeren Vektor v an, der Elemente des Typs int aufnehmen kann:
vector<int> v;

Wenn wir die Gre und die Kapazitt des Vektors betrachten, dann stellen wir fest, dass beide den Wert 0 besitzen. Folgende Zeile bringt die gewnschten Informationen auf den Bildschirm:
cout << v.size() << " " << v.capacity() << endl;

Wir knnen nun mittels push_back Elemente an den Vektor anhngen:


v.push_back(12);

Nun werden die Gre und die Kapazitt des Vektors den Wert 1 haben.
Hinweis Wenn Sie die Beispiele selbst am Rechner durchspielen, dann mssen die Werte der im Buch angegebenen Kapazitten nicht unbedingt mit Ihren Beobachtungen bereinstimmen, da dieser Wert von Implementation zu Implementation variieren kann.

Wenn wir nun mit einer Schleife sukzessive Elemente anhngen und die Werte von Gre und Kapazitt analysieren, dann erkennen wir folgende Regelmigkeit: Sollte die Kapazitt des Vektors nicht ausreichen, dann wird sie verdoppelt. Der Vektor kann mit clear gelscht werden:
v.clear();

Die Gre ist zwar auf 0 zurckgesetzt, die Kapazitt besitzt jedoch ihre vorherige Gre. Das hat folgenden Grund: Einmal reservierter Speicherplatz wird nicht automatisch verkleinert. Das liegt daran, dass ein Speicherbereich programmtechnisch nicht einfach verkleinert oder vergrert werden kann. Es muss stattdessen zuerst ein neuer Speicherbereich angefordert werden, der die gewnschte Gre hat. Dann werden die weiterhin bentigten Elemente vom alten in den neuen Speicherbereich transferiert und danach der alte Speicherbereich gelscht.

255

Die STL

Als Konsequenz sind alle Verweise auf Elemente des Vektors nicht mehr gltig (Methoden, wie etwa pop_back, liefern nur einen Verweis auf ein Element), denn die Elemente befinden sich nach der Umsiedlung in einem ganz anderen Speicherbereich.

6.4

Deque

Deques sind den Vektoren sehr hnlich. Nach auen hin merkt man bis auf eine etwas erweiterte Funktionalitt keinen Unterschied.

6.4.1

Aufbau einer Deque

Allerdings ist die interne Organisation einer Deque etwas aufwndiger. Schauen wir uns die einfachste Variante einer Deque einmal in Abbildung 6.6 an.
Speicherbereich 1 0 7 6 5 4 3 2 1 1 2 0 3 0 4 1 Speicherbereich 2 5 2 6 3 7 4 5 6 7

Abbildung 6.6

Aufbau einer Deque

Eine Deque ist nicht mehr wie ein Vektor in einem einzigen Speicherbereich untergebracht, sondern besitzt mindestens zwei getrennte Bereiche. Daher auch der Name Deque (von Double Ended Queue, auf Deutsch etwa Schlange mit zwei Enden) Diese Speicherbereiche sind fr sich jedoch wieder linear angeordnet und ermglichen einen wahlfreien Zugriff. Daher wird die Deque ebenfalls zu den Sequenzen gezhlt. In der Abbildung sehen Sie innerhalb der Elemente den logischen Index, also den Index, mit dem der Benutzer arbeitet. Unter den Elementen sehen Sie den physikalischen Index der Speicherbereiche. Durch diese Organisation ist es mit geringem Zeitaufwand mglich, an beiden Enden einer Deque Elemente anzuhngen. Bei einem Vektor hingegen kostet das Einfgen am Anfang O(n)-Zeit, wobei n fr die Elementanzahl im Vektor steht. Abbildung 6.7 verdeutlicht die Vorgehensweise beim Einfgen.

256

Deque

6.4

a
7 6 5 4 3

0 2

1 1

2 0

3 0

4 1

5 2

6 3

7 4 5 6 7

b
7 6 5 4 3

0 2

1 1

2 0

3 0

4 1

5 2

6 3

7 4

8 5 6 7

c
7 6 5 4

0 3

1 2

2 1

3 0

4 0

5 1

6 2

7 3

8 4

9 5 6 7

Abbildung 6.7 Anhngen an beide Enden der Deque

In Abbildung 6.7a sehen wir die Ausgangssituation. Abbildung 6.7b zeigt die Deque, nachdem ein Element am Ende angehngt wurde. Diese Vorgehensweise entspricht der eines Vektors. Sollte die Kapazitt des betroffenen Speicherbereichs nicht erschpft sein, so kann diese Operation in O(1)-Zeit ausgefhrt werden. Der logische Index der bereits in der Deque gespeicherten Elemente wird durch das Anhngen eines Elements am Ende nicht verndert. Das Anhngen eines Elements am Anfang der Deque ist in Abbildung 6.7c dargestellt. Programmtechnisch ist diese Situation identisch mit der vorherigen: Solange die Kapazitt ausreicht, bentigt diese Operation O(1)-Zeit. Das Anhngen am Anfang hat jedoch Folgen fr den logischen Index der bereits vorhandenen Elemente. Da sich diese Auswirkungen aber ausschlielich auf den logischen Index beziehen, erscheint es dem Benutzer, als wren die Elemente verschoben worden, um dem neuen Element am Anfang der Deque Platz zu machen. Tatschlich bleibt der physikalische Index aber unverndert; es findet programmtechnisch kein Verschieben statt. Das ist der gewaltige Unterschied zum Vektor. Da bei ihm keine Unterscheidung zwischen logischem und physikalischem Index gefllt wird, muss ein Anhngen am Anfang des Vektors durch ein Verschieben aller bereits vorhandenen Elemente erkauft werden. Und das bedeutet O(n)-Zeit. Die Speicher-Organisation der Deque kann noch etwas verndert werden, um weitere Nachteile, die beim Vektor unumgnglich waren, zu beheben. Abbildung 6.8 zeigt einen Ansatz.
0 5 6 7 8 1 9 2 10 3 11 4 12 13 21 29 14 22 30 15 23 16 24 17 25 18 26 19 27 20 28

Abbildung 6.8 Ein erweiterter Ansatz der Deque-Organisation

257

Die STL

Da die Deque sowieso schon zwei Speicherbereiche verwendet, knnen wir fr den Fall, dass die Kapazitt einer der beiden Speicherbereiche berschritten wird, ergnzend einen weiteren Speicherbereich anlegen. Dadurch wird die Arbeitsfolge neuen Speicher anfordern, Elemente kopieren, alten Speicher freigeben nicht mehr bentigt. Auf diese Weise ist ein Anhngen an beiden Enden uneingeschrnkt in O(1)-Zeit mglich. Bei all diesen Vorteilen der Deque stellt sich die Frage, warum es berhaupt einen Vektor gibt. Das Anhngen zhlt zwar zur Laufzeitklasse O(1), allerdings ist durch den erhhten Verwaltungsaufwand, der durch die verschiedenen Speicherbereiche begrndet ist, die Konstante k bei Deques grer als bei Vektoren (ausfhrliche Informationen zur Konstante k und den Laufzeitklassen finden Sie in Abschnitt 6.2.1, Zeitkomplexitt). Das wiederum bedeutet, dass ein Vektor absolut gemessen schneller ist als eine Deque. Eine weitere absolute Laufzeiteinbue ergibt sich bei den Iteratoren. Whrend bei einem Vektor ein Iterator durch einen elementaren Zeiger reprsentiert werden kann (fr einen linearen Speicherbereich besitzt ein elementarer Zeiger alle Eigenschaften, die von einem Iterator erwartet werden), muss bei einer Deque ein abstrakter Zeiger implementiert werden. Denn die verschiedenen Speicherbereiche der Deque mssen fr den Benutzer gekapselt werden, ansonsten knnte kein dem Vektor identisches Verhalten simuliert werden. Der Benutzer sieht nur den logischen Speicherbereich, der ihm linear vorkommt. Grerer Verwaltungsaufwand entsteht auch, wenn ein Element irgendwo in der Deque eingefgt werden muss. Denn dann kommt auch die Deque nicht umhin, Elemente physikalisch zu verschieben. Und das ist aufgrund der Speicherorganisation der Deque aufwndiger, und damit zeitintensiver als beim Vektor. Aus diesem Grund sollten Sie entsprechend den Anforderungen, die Sie an den Container stellen, entscheiden, ob Sie einen Vektor oder eine Deque verwenden wollen: Wenn absehbar ist, wie viele Elemente aufgenommen werden mssen, oder wenn die Wahrscheinlichkeit gering ist, dass eine vorgegebene Containergre berschritten wird, dann geht die Tendenz zum Vektor. Werden Elemente hauptschlich am Ende angehngt und entfernt (wie bei einem Stack), dann ist das ebenfalls ein Argument fr einen Vektor.

258

Deque

6.4

Werden Elemente auch oder nur am Anfang der Sequenz hinzugefgt oder entfernt (wie bei einer Queue), dann fllt die Wahl auf die Deque. Ein weiterer Vorteil der Deque liegt in der besseren Ausnutzung des Arbeitsspeichers. Auf Systemen, die nicht allzu ppig mit RAM ausgestattet sind, lassen sich mehrere kleinere Speicherbereiche eher auffinden als ein groer.

6.4.2

Definition einer Deque

Damit wir die Deque der STL nutzen knnen, mssen wir die Datei deque einbinden. Die Definitionen gehren dem Namensbereich std an. Genau wie der Vektor, ist auch die Deque als Template definiert. Die Parameter des Templates bestimmen den zu verwaltenden Datentyp und den Typ des Allokators:
template<typename Typ, typename Allok=allocator<Typ> > class std::deque { ... };
Listing 6.12 Die Definition von deque

6.4.3

Methoden

Die Deque besitzt die gleichen Konstruktoren, Operatoren und Methoden wie der Vektor plus einige Ergnzungen. Tabelle 6.13 listet die Methoden auf. Im Anschluss werden die neu hinzugekommenen Methoden im Detail betrachtet.
Methode
assign at back begin clear empty end erase front

Kurzbeschreibung Weist einer Deque Elemente zu. Spricht ein Deque-Element ber den Index an. letztes Element der Deque Iteratorposition auf erstem Element der Deque Lscht alle Elemente der Deque. berprft, ob eine Deque leer ist. Iteratorposition hinter dem letzten Element der Deque Lscht Elemente. erstes Element der Deque

Tabelle 6.13 Die Methoden von deque

259

Die STL

Methode
insert max_size pop_back pop_front push_back push_front rbegin rend resize size swap

Kurzbeschreibung Fgt Elemente ein. maximal mgliche Gre einer Deque Entfernt das letzte Element einer Deque. Entfernt das erste Element einer Deque. Hngt ein Element am Ende der Deque an. Fgt ein Element am Anfang der Deque ein. Reverse-Iteratorposition auf letztem Element der Deque Reverse-Iteratorposition vor dem ersten Element der Deque Verndert die Gre der Deque. Ermittelt die Elementanzahl. Vertauscht zwei Deques.

Tabelle 6.13 Die Methoden von deque (Forts.)

pop_front Element am Anfang entfernen


void pop_front();

Lscht das erste Element einer Deque, ohne es zurckzuliefern. pop_front besitzt eine Laufzeit von O(1).
push_front Element am Anfang einfgen
void push_front(const Typ& obj);

Fgt an den Anfang einer Deque eine Kopie des Elements obj an. push_front besitzt eine Laufzeit von O(1).

6.5

Listen

Auf der Leiter der Komplexitt liegt die Datenstruktur Liste eine Stufe hher als die Deque. Durch eine aufwndigere Organisation knnen Mngel der Laufzeit, wie beispielsweise die O(n)-Zeit von insert, behoben werden. Allerdings haben diese Laufzeitverbesserungen ihren Preis. In Abbildung 6.9a sehen wir fnf Elemente, die als Sequenz (wie z. B. bei einem Vektor) gespeichert sind. Alle fnf Elemente liegen in einem gemeinsamen Speicherblock.

260

Listen

6.5

a b c d

Liste

Abbildung 6.9 Der Aufbau einer doppelt verketteten Liste

Und gerade weil sich die Elemente einen Speicherbereich teilen, bentigt das Einfgen eines Elements in der Mitte der Datenstruktur so viel Zeit: Es mssen Elemente verschoben werden. Und schlimmstenfalls muss sogar ein neuer Speicherblock reserviert werden, falls die Kapazitt berschritten wird. Der erste Schritt, die Beziehungen der gespeicherten Elemente untereinander zu verringern, ist der, dass jedes Element speichertechnisch autonom wird. Wenn jedes Element seinen eigenen Speicher hat, dann muss beim Einfgen oder Entfernen eines Elements nur dessen Speicher bercksichtigt werden, nicht aber der der anderen Elemente. Damit sich die einzelnen Elemente aber nicht im Speicher verlieren, mssen sie verbunden werden. Dazu wird jedes Element in einer Datenstruktur gekapselt, die zwei Zeiger enthlt. Einen auf den Vorgnger und einen auf den Nachfolger. Abbildung 6.9c zeigt die Kapselung der Elemente und Abbildung 6.9d die anschlieende Verkettung. Um in O(1)-Zeit auf den Anfang und das Ende der Liste zugreifen zu knnen, wird noch ein Listenkopf wie in Abbildung 6.9e hinzugefgt. Weil zum Einfgen oder Entfernen von Elementen keine Elemente mehr verschoben, sondern lediglich Zeiger verbogen werden mssen, kann ein Element an einer beliebigen Stelle in der Liste in O(1)-Zeit eingefgt oder entfernt werden. Da aber jedes Listenelement nur einen Verweis auf seinen direkten Vorgnger und Nachfolger besitzt, kann ein wahlfreier Elementzugriff nur noch in O(n)-Zeit gewhrleistet werden (im Gegensatz zu vector und deque, die diesen Zugriff in O(1)-Zeit ermglichen).

261

Die STL

Aus diesem Grund stellt die Liste auch keine Random-Access-Iteratoren zur Verfgung, sondern lediglich bidirektionale Iteratoren. Einen Container, auf den mittels bidirektionaler Iteratoren zugegriffen werden kann, denn man auch reversiblen Container. Eine weitere Eigenschaft reversibler Container ist die Tatsache, dass sie Reverse-Iteratoren zur Verfgung stellen.
Hinweis Als reversiblen Container bezeichnet man einen Container, dessen Elemente eine feste Reihenfolge besitzen und auf den mittels bidirektionaler Iteratoren zugegriffen werden kann.

6.5.1

Datentypen

Damit wir die Liste der STL nutzen knnen, mssen wir die Datei list einbinden. Die Definitionen gehren dem Namensbereich std an. Wie alle Container der STL ist auch die Liste als Template definiert. Die Parameter des Templates bestimmen den zu verwaltenden Datentyp und den Typ des Allokators:
template<typename Typ, typename Allok=allocator<Typ> > class std::list { ... };
Listing 6.13 Die Definition von list

Innerhalb der Klasse werden wieder einige STL-typische Datentypen deklariert:


Datentyp
value_type allocator_type size_type

Beschreibung Typ der zu verwaltenden Elemente Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder die Kapazitt der Liste Typ fr die Angabe von Abstnden zweier Elemente Zeiger-Typ Typ fr konstante Zeiger Referenz-Typ Typ fr konstante Referenzen

difference_type pointer const_pointer reference const_reference

Tabelle 6.14 Die Datentypen von list

262

Listen

6.5

6.5.2

Konstruktoren

Dieser Teil des Kapitels widmet sich den Konstruktoren der Liste. Auf die Angabe der Template-Parameter im Konstruktornamen wird verzichtet:
explicit list(const Allok& a=Allok() )

Erzeugt eine leere Liste. Bei Bedarf kann ein Allokator-Objekt angegeben werden.
list(const list<Typ, Allok>& l)

Erzeugt eine Liste als Kopie von Liste l (Kopierkonstruktor). Dabei mssen Daten- und Allokator-Typ bei beiden Listen bereinstimmen.
explicit list(size_type anz, const Typ& obj=Typ(), const Allok& a=Allok())

Erzeugt eine Liste mit anz Elementen des Typs Typ. Alle erzeugten Elemente sind eine Kopie des Objektes obj. Sollte kein Objekt angegeben werden, so wird eines mithilfe des zugehrigen Standardkonstruktors erzeugt. Bei Bedarf kann ein Allokator-Objekt angegeben werden.
list(CInput anf, CInput end, const Allok& a=Allok())

Erzeugt eine Liste, die Kopien der Elemente im Bereich [anf, end[ enthlt. anf und end sind dabei Iteratorpositionen. Die Gre der erzeugten Liste entspricht damit exakt der Anzahl der im Bereich [anf, end[ enthaltenen Elemente. Bei Bedarf kann ein Allokator-Objekt angegeben werden.

6.5.3

Operatoren

Es folgen die fr list berladenen Operatoren. Im Gegensatz zu den Vektoren und Deques besitzt die Liste keinen Indexoperator mehr. Obwohl dieser programmtechnisch durchaus implementiert werden knnte, wrde er nicht mehr der Erwartung entsprechen, seine Aufgabe in O(1)-Zeit erledigen zu knnen. Da eine schlechtere Laufzeit als O(1) fr den Indexoperator nicht akzeptabel ist, wird er nicht zur Verfgung gestellt.
operator=

Der Zuweisungsoperator wird dazu eingesetzt, um eine Liste einer anderen Liste zuzuweisen. Auf diese Weise wird eine komplette Kopie angelegt. Eventuell in der alten Liste gespeicherte Elemente werden dabei gelscht.
list<Typ,Allok>& operator=(const list<Typ,Allok>& l);

263

Die STL

Vergleichsoperatoren

Zum Vergleich zweier Listen dienen folgende Template-Funktionen, die durch die restlichen, als Template definierten Vergleichsoperatoren der STL ergnzt werden knnen. Hufig sind der Effizienz wegen alle Vergleichsoperatoren im Listen-Container vorhanden.
template<typename Typ, typename Allok> bool std::operator<(const list<Typ, Allok>& l1, const list<Typ, Allok>& l2); template<typename Typ, typename Allok> bool std::operator==(const list<Typ, Allok>& l1, const list<Typ, Allok>& l2);

Dabei werden die Listen lexikografisch verglichen. Die Regeln entsprechen denen der Vektoren.

6.5.4

Methoden

Im Folgenden werden die fr deque implementierten Methoden besprochen. Tabelle 6.15 gibt eine bersicht.
Methode
assign back begin clear empty end erase front insert max_size merge pop_back pop_front push_back

Kurzbeschreibung Weist einer Liste Elemente zu. letztes Element der Liste Iteratorposition auf erstem Element der Liste Lscht alle Elemente der Liste. berprft, ob Liste leer ist. Iteratorposition hinter dem letzten Element der Liste. Lscht Elemente. erstes Element der Liste Fgt Elemente ein. maximal mglicheGre einer Liste Verschmilzt Listen. Entfernt das letzte Element einer Liste. Entfernt das erste Element einer Liste. Hngt ein Element am Ende der Liste an.

Tabelle 6.15 Die Methoden von list

264

Listen

6.5

Methode
push_front rbegin remove remove_if rend resize reverse size splice swap unique

Kurzbeschreibung Fgt ein Element am Anfang der Liste ein. Reverse-Iteratorposition auf letztem Element der Liste Entfernt Elemente einer Liste. Entfernt Elemente einer Liste. Reverse-Iteratorposition vor dem ersten Element der Liste Verndert die Gre der Liste. Kehrt die Reihenfolge der Elemente um. Ermittelt die Elementanzahl. Hngt Elemente um. Vertauscht zwei Listen. Entfernt benachbarte Duplikate.

Tabelle 6.15 Die Methoden von list (Forts.)

assign Elemente zuweisen


void assign(size_type anz, const Typ obj=Typ() );

Weist einer Liste anz Elemente des Typs Typ zu. Alle angelegten Elemente sind Kopien des Objekts obj. Der Standardwert von obj ist ein mit dem Standardkonstruktor von Typ erzeugtes Objekt.
void assign(Input anf, Input end);

Weist einer Liste Kopien der im Bereich [anf, end[ liegenden Elemente zu. Die vorher in der Liste gespeicherten Elemente werden dabei gelscht.
back Verweis auf das letzte Element
reference back(); const_reference back() const;

Liefert eine Referenz auf das letzte Element der Liste. back ist jeweils fr variable und konstante Listen implementiert und besitzt eine Laufzeit von O(1).
begin Iterator auf das erste Element
Bi begin(); CBi begin() const;

Liefert die Iteratorposition des ersten Elements in der Liste. begin besitzt eine Laufzeit von O(1).

265

Die STL

clear Listeninhalt lschen


void clear(); clear lscht alle Elemente einer Liste und bentigt dazu O(m)-Zeit, wobei m

fr die Anzahl der zu lschenden Elemente steht.


empty Liste leer?
bool empty() const;

Gibt Auskunft darber, ob eine Liste augenblicklich Elemente enthlt oder nicht.
end Iterator hinter dem letzten Element
Bi end(); CBi end() const;

Liefert die Iteratorposition hinter dem letzten Element der Liste. end besitzt eine Laufzeit von O(1).
erase Elemente lschen
Bi erase(Bi pos);

Lscht in einer Liste das Element an der Iteratorposition pos, und liefert die Iteratorposition hinter pos zurck.
Bi erase(Bi anf, Bi end);

Lscht in einer Liste die im Bereich [anf, end[ liegenden Elemente, und liefert die Iteratorposition end zurck. erase bentigt O(m)-Zeit, wobei m fr die Anzahl der zu lschenden Elemente steht.
front erstes Element der Liste
reference front(); const_reference front() const;

Liefert eine Referenz auf das erste Element der Liste. front ist jeweils fr variable und konstante Listen implementiert und besitzt eine Laufzeit von O(1).
insert Elemente einfgen
Bi insert(Bi pos, const Typ& obj=Typ() );

In eine Liste wird an der Iteratorposition pos ein Element als Kopie von obj eingefgt. Als Standardwert besitzt obj ein mit dem Standardkon-

266

Listen

6.5

struktor von Typ erzeugtes Objekt. insert liefert die Iteratorposition des neu eingefgten Objekts zurck. Die Funktionsweise von insert ist in Abbildung 6.10 dargestellt.
x

a
a

pos b c d

pos b c d

b
a

c
a

RckgabePos. x

pos b c d

Abbildung 6.10 Die Funktionsweise von insert

Abbildung 6.10a zeigt die Liste mit angegebener Einfgeposition sowie das einzufgende Element. Zuerst wird das Element in einem Listenobjekt gekapselt, damit die Verweise auf Vorgnger und Nachfolger verfgbar sind (Abbildung 6.10b). Zu guter Letzt wird das einzufgende Element, wie in Abbildung 6.10c dargestellt, in die Listenstruktur eingebunden und dessen Position zurckgeliefert.
void insert(Bi pos, size_type anz, const Typ& obj);

In eine Liste wird an der Iteratorposition pos eine Anzahl von anz Elementen als Kopien von obj eingefgt. Zum Einfgen der einzelnen Elemente wird insert(pos, obj) verwendet.
void insert(Bi pos, CInput anf, CInput end);

In eine Liste werden an der Iteratorposition pos Kopien der im Bereich [anf, end[ liegenden Elemente eingefgt. Zum Einfgen der einzelnen Elemente wird insert(pos, obj) verwendet. insert bentigt O(m)-Zeit, wobei m fr die Anzahl der einzufgenden Elemente steht.
max_size maximal mgliche Listengre
size_type max_size() const;

Liefert die maximale Gre zurck, die eine Liste auf dem aktuellen System haben kann.

267

Die STL

merge Listen verschmelzen


void merge(list<Typ, Allok>& l)

Unter der Voraussetzung, dass die aufrufende Liste und die Liste l jeweils fr sich sortiert sind, werden die Elemente der Liste l mittels Splicing in die aufrufende Liste verschoben. Die Elemente der Liste l werden dabei so eingefgt, dass bei Gleichheit die Elemente der aufrufenden Liste vor denen der Liste l stehen. Die so entstandene Liste ist stabil sortiert. Die fr die Verschmelzung notwendigen Vergleiche werden ber die operator<-Funktion der Elemente vorgenommen.
Hinweis Eine Sortierung ist dann stabil, wenn die Reihenfolge gleicher Elemente zueinander erhalten bleibt.

Einen genaueren Einblick in die Funktionsweise von merge liefert Abbildung 6.11. Zugunsten der bersichtlichkeit wurde auf die Darstellung der Verweise verzichtet.
l a b c d e

a
Aufr. Liste a a b d

b
Aufr. Liste a a a b b c d d e

Abbildung 6.11

Die Funktionsweise von merge

Nach dem Aufruf von merge ist die Liste l leer.


void merge(list<Typ, Allok>& l, BinPred fkt)

Anstelle des <-Operators wird bei dieser merge-Variante das zweiparametrige Prdikat fkt zum Vergleichen verwendet. fkt muss so implementiert sein, dass der Rckgabewert von fkt(p1, p2) genau dann wahr ist, wenn p1<p2 gilt. merge besitzt lineare Laufzeit.
pop_back letztes Element entfernen
void pop_back();

268

Listen

6.5

Lscht das letzte Element einer Liste, ohne es zurckzuliefern. pop_back besitzt eine Laufzeit von O(1).
pop_front erstes Element entfernen
void pop_front();

Lscht das erste Element einer Liste, ohne es zurckzuliefern. pop_front besitzt eine Laufzeit von O(1).
push_back Element hinten anhngen
void push_back(const Typ& obj);

Hngt eine Kopie des Elements obj an das Ende einer Liste. push_back besitzt eine Laufzeit von O(1).
push_front Element am Anfang einfgen
void push_front(const Typ& obj);

Fgt eine Kopie des Elements obj an den Anfang einer Liste an. push_front besitzt eine Laufzeit von O(1).
rbegin Reverse-Iterator auf das letzte Element
reverse_iterator rbegin(); const_reverse_iterator rbegin() const;

Liefert die Iteratorposition des letzten Elements der Liste in Form eines Reverse-Iterators. rbegin besitzt eine Laufzeit von O(1).
remove Elemente entfernen
void remove(const Typ& obj)

Alle Elemente, die dem Objekt o gleich sind, werden aus der Liste gelscht. Um auf Gleichheit zu prfen, wird die operator==-Funktion der Elemente verwendet. remove bentigt O(n)-Zeit, wobei n fr die Anzahl aller Elemente in der Liste steht.
remove_if Elemente ber Bedingung entfernen
void remove_if(Pred fkt)

269

Die STL

Alle Elemente der Liste, bei denen das einparametrige Prdikat fkt(element) wahr ist, werden gelscht. remove_if bentigt O(n)-Zeit, wobei n fr die Anzahl aller Elemente in der Liste steht.
rend Reverse-Iterator auf die Position vor dem ersten Element
reverse_iterator rend(); const_reverse_iterator rend() const;

Liefert die Iteratorposition hinter dem letzten Element der Liste in Form eines Reverse-Iterators. rend besitzt eine Laufzeit von O(1).
resize Listengre anpassen
void resize(size_type anz, const Typ& obj=Typ() );

ndert die Gre einer Liste auf die Gre anz. Sollte anz kleiner sein als die aktuelle Gre der Liste, dann werden die berflssigen Elemente gelscht. Fr den Fall, dass anz grer als die aktuelle Gre der Liste ist, werden entsprechend viele Elemente als Kopie von obj an die Liste angehngt. Als Standardwert besitzt obj ein mit dem Standardkonstruktor von Typ erzeugtes Objekt. resize bentigt eine Laufzeit von O(m), wobei m die Anzahl der zu lschenden oder hinzuzufgenden Elemente darstellt.
reverse Elementreihenfolge umdrehen
void reverse()

Dreht die Reihenfolge der Listenelemente um. Das Umsetzen der Elemente geschieht durch Splicing. reverse bentigt lineare Laufzeit.
size Anzahl der Listenelemente
size_type size() const

Liefert die Anzahl der augenblicklich in der Liste gespeicherten Elemente.


sort Listenelemente sortieren
void sort()

Die Liste wird stabil sortiert. Die fr die Verschmelzung notwendigen Vergleiche werden ber die operator<-Funktion der Elemente vorgenommen.

270

Listen

6.5

void sort(BinPred fkt)

Anstelle des <-Operators wird bei dieser merge-Variante das zweiparametrige Prdikat fkt zum Vergleichen verwendet. fkt muss bei fkt(p1, p2) genau dann einen wahren Wert zurckliefern, wenn p1<p2 gilt. Die Laufzeit von sort betrgt O(n log n), wobei n fr die Anzahl der zu sortierenden Elemente steht.
splice
void splice(Bi pos, list<Typ, Allok>& l)

Alle Elemente der Liste l werden an die Position pos der aufrufenden Liste verschoben. Dabei wird das Verschieben nicht durch tatschliches Verschieben der Elemente, sondern durch das Umhngen der Listenelemente implementiert.
void splice(Bi pos, list<Typ, Allok>& l, Bi epos)

Das an der Position epos befindliche Element der Liste l wird an die Position pos der aufrufenden Liste durch Umhngen der Listenelemente verschoben.
void splice(Bi pos, list<Typ, Allok>& l, Bi anf, Bi end)

Die im Bereich [anf, end[ befindlichen Elemente der Liste l werden an die Position pos der aufrufenden Liste durch Umhngen der Listenelemente verschoben. splice besitzt ein konstantes Laufzeitverhalten ( O(1) ). Abbildung 6.12 zeigt die Funktionsweise des Splicings anhand des Aufrufs aufrufliste.splice(pos, l, anf, end).
anf l a e j pos Aufr. Liste z h w q s end l

l pos w q

b
Aufr. Liste z h

l pos

c
Aufr. Liste z h e j s

Abbildung 6.12

Die Funktionsweise von splice

271

Die STL

swap Listen vertauschen


void swap(list<Typ, Allok>& l);

Vertauscht zwei Listen. swap besitzt eine Laufzeit von O(1).


unique benachbarte Duplikate lschen
void unique()

Lscht alle benachbarten Duplikate einer Liste.


Hinweis Unter benachbarten Duplikaten versteht man gleiche Elemente, die in ihrer Reihenfolge direkt hintereinanderliegen.

Dabei bleibt das erste Element aus einer Reihe gleicher Elemente erhalten. Die notwendigen Vergleiche werden ber die operator==-Funktion der Elemente vorgenommen. Ein Beispiel fr die Funktionsweise von unique zeigt Abbildung 6.13. Man erkennt sehr schn, dass wirklich nur benachbarte Duplikate gelscht werden, denn nach dem Aufruf von unique befinden sich immer noch zwei Elemente d in der Liste, weil diese eben nicht benachbart sind.
a
Liste a a a b d d b c e

Liste

Abbildung 6.13 Die Funktionsweise von unique

void unique(BinPred fkt)

Anstelle des ==-Operators wird bei dieser unique-Variante das zweiparametrige Prdikat fkt zum Vergleichen verwendet. fkt muss so implementiert sein, dass bei fkt(p1, p2) der Rckgabewert genau dann wahr ist, wenn p1==p2 gilt. unique besitzt eine lineare Laufzeit.

6.6

Sets

Mit der Liste haben wir einen Container, der einige der Unzulnglichkeiten der Sequenz-Container Vektor und Deque eliminiert. Zum Beispiel konnte die Laufzeit beim Einfgen von Elementen irgendwo im Container erheblich

272

Sets

6.6

verbessert werden. Was aber, wenn wir ein Element in einer sortierten Liste suchen wollen? Oder wenn ein Element so in die Liste eingefgt werden muss, dass die Liste nach dem Einfgen immer noch sortiert ist? Diese Aufgaben erfordern, dass zuerst eine bestimmte Stelle innerhalb der Liste gefunden werden muss. Da auch die Liste ihre Elemente linear anordnet, kann das Suchen einer Stelle oder eines Elements nur in O(n)-Zeit bewltigt werden. Um dieses Laufzeitverhalten zu verbessern, gehen die Sets einen anderen Weg. Die Elemente liegen in ihrer Reihenfolge nicht mehr linear hintereinander, sondern besitzen jeweils zwei Nachfolger. Abbildung 6.14 zeigt ein entsprechendes Set-Element.

Abbildung 6.14 Ein Element eines Sets/Multisets

Hinweis Eine Datenstruktur, bei der jedes Element bis zu zwei Nachfolger haben kann und die keine Zyklen besitzt, nennt man Binrbaum.

Ausgehend von einem Knoten (so nennt man ein Element in einem Baum) bezeichnet man diesen Knoten als Vater und die direkt nachfolgenden Elemente als Shne. Das Entscheidungskriterium, ob ein Sohn am linken oder rechten Ast des Vaters angehngt wird, liegt in der Sortierung. Ist der Sohn kleiner als der Vater, dann wird er an den linken Ast gehngt. Andernfalls wird er an den rechten Ast gehngt. Abbildung 6.15 zeigt einen ausgewogenen Baum mit mehreren Elementen.
42 17 9 38 55 69 88

Abbildung 6.15 Ein Baum mit mehreren Elementen

Angenommen, wir suchen das Element 55, dann beginnen wir bei der Wurzel, dem obersten Knoten des Baumes, und vergleichen das Element mit dem gesuchten. Das gesuchte Element ist grer als 42 und muss deshalb im rechten Ast liegen. Das nchste Element ist 69. Unser gesuchtes Element ist kleiner und wird sich deshalb im linken Ast befinden. Der dritte Vergleich ist schlielich von Erfolg gekrnt.

273

Die STL

Wenn sich die Hhen (das ist der lngste Weg, der von der Wurzel aus den Baum hinaufgelaufen werden kann) der jeweils nebeneinanderliegenden Teilbume nicht groartig unterscheiden, lsst sich ein Element in O(log n)Zeit (logarithmische Laufzeit) finden, bzw. es lsst sich in dieser Zeit zeigen, dass das Element nicht im Baum gespeichert ist.
Teilbaum Jeder Knoten kann wiederum als Wurzel eines Baumes betrachtet werden. In Abbildung 6.15 sind die Knoten 17 und 69 jeweils Wurzeln eines Teilbaumes. Diese beiden Teilbume liegen nebeneinander.

Das Einfgen eines Elements funktioniert hnlich wie das Suchen. Es wird einfach nach dem einzufgenden Element gesucht, und wenn das Ende des Baums erreicht wurde, dann wissen wir, das Element muss genau an dieser Stelle angehngt werden. Abbildung 6.16 zeigt dies am Beispiel des Elements 30, welches in den Baum eingefgt wird.
42 17 9 38 55 69 88

30

Abbildung 6.16

Einfgen eines Elements in einen Baum

Whrend des Einfgens kann ein allerdings ein unangenehmer Effekt auftreten. Dieses Problem tritt ausgeprgt zutage, wenn eine Folge von bereits sortierten Elementen in den Baum eingefgt werden soll. Abbildung 6.17 zeigt das sukzessive Einfgen der Elemente 30, 41, 58, 69 und 74 in einen leeren Baum.
30 41 58 69 74

Abbildung 6.17

Ein degenerierter Baum

274

Sets

6.6

Es entsteht ein Baum, bei dem jeder Knoten nur einen rechten Nachfolger hat. Das wiederum bedeutet, die Elemente sind linear wie bei einer Liste angeordnet. Man spricht daher von einem degenerierten Baum.
Hinweis In einem degenerierten Baum besitzt die Suchfunktion lineare Laufzeit.

Sie sollten daher bestrebt sein, dieser Degeneration mit entsprechenden Mitteln entgegenzutreten. Ein Kriterium fr beginnende Degeneration sind unterschiedliche Hhen von Teilbumen. In diesem Zusammenhang ist noch der Begriff Balance zu erwhnen.
Hinweis Als Balance eines Knotens bezeichnet man die Differenz der Hhe des rechten Teilbaums und der Hhe des linken Teilbaums.

Schauen wir uns dazu Abbildung 6.18a an.

42 17 55 70 69 88 17 42 55

69 88 70

Abbildung 6.18 Beginnende Degeneration eines Baums

Der Teilbaum mit 17 als Wurzel hat eine Hhe von 0, wohingegen der Teilbaum mit Wurzelelement 69 eine Hhe von 2 aufweist. Damit hat der Knoten 42 eine Balance von 2. Eine Methode, die unterschiedlichen Hhen auszugleichen, ist die sogenannte Rotation. Abbildung 6.18b zeigt den Baum, nachdem bei Element 69 eine Linksrotation vorgenommen wurde.
Hinweis Einen Baum, der Manahmen zum Ausgleich der Hhen trifft, nennt man hhenbalanciert oder ausgeglichen.

Um eine logarithmische Laufzeit fr Suchen und Einfgen zu garantieren, sind die Sets und Multisets als hhenbalancierte Bume implementiert. Der

275

Die STL

Unterschied zwischen beiden liegt nur in der Eindeutigkeit der Elemente. In einem Set drfen keine gleichen Elemente vorkommen, in einem Multiset dagegen schon.

6.6.1

Definition

Damit wir die Sets der STL nutzen knnen, mssen wir die Datei set einbinden. Die Definitionen gehren dem Namensbereich std an. Auch set ist als Template definiert. Die Parameter des Templates bestimmen den zu verwaltenden Datentyp (STyp), die zu verwendende Vergleichsfunktion (Comp), und den Typ des Allokators (Allok).
template<typename STyp, typename Comp=less<STyp>, typename Allok=allocator<STyp> > class std::set { ... };
Listing 6.14 Die Definition von set

Die voreingestellte Vergleichsfunktion ist das Prdikat less. Die Definition von multiset ist identisch. Innerhalb der Klasse werden wieder einige Datentypen deklariert:
Datentyp
value_type value_compare key_type key_compare

Beschreibung Typ der zu verwaltenden Elemente Funktionstyp, mit dem Elemente verglichen werden Typ der Schlssel. Bei Sets und Multisets identisch mit value_type Funktionstyp, mit dem Schlssel verglichen werden. Bei Sets und Multisets identisch mit value_compare Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder die Kapazitt des Sets/Multisets Typ fr die Angabe von Abstnden zweier Elemente Zeiger-Typ Typ fr konstante Zeiger Referenz-Typ Typ fr konstante Referenzen

allocator_type size_type

difference_type pointer const_pointer reference const_reference

Tabelle 6.16 Die Datentypen von set und multiset

276

Sets

6.6

Da Sets, Multisets, Maps und Multimaps alle auf hhenbalancierten Bumen basieren, wird fr diese vier Container bei manchen Implementationen eine Basisklasse definiert, die die Eigenschaften des hhenbalancierten Baums kapselt. Sollte dies der Fall sein, dann bernehmen die Container size_type, difference_type, pointer, const_pointer, reference und const_reference nicht direkt vom Allokator, sondern von der Baum-Basisklasse. Fr den Benutzer von Set oder Multiset spielt dies jedoch keine Rolle, da die Typnamen durch die entsprechenden typedef-Anweisungen in der set- und multiset-Klasse vereinheitlicht werden.

6.6.2

Konstruktoren

Schauen wir uns zuerst die Konstruktoren an. Auf die Angabe aller Templateparameter im Konstruktornamen wird wieder verzichtet. Die Konstruktoren sind fr Sets und Multisets implementiert, werden aber nur fr Sets angegeben:
explicit set(const Comp& fkt=Comp(), const Allok& a=Allok());

Erzeugt ein leeres Set. Als Parameter kann ein eigenes Prdikat zum Vergleichen und ein Allokator angegeben werden. Wird kein Prdikat angegeben, so wird ein mit dem Standardkonstruktor erzeugtes Prdikat der Klasse Comp verwendet. Wird kein Allokator angegeben, so wird einer mit dem Standardtkonstruktor von Allok erzeugt.
set(CInput anf, CInput end, const Comp& fkt=Comp(), const Allok& a=Allok())

Fr die Parameter fkt und a gilt dasselbe wie fr den vorigen Konstruktor. Zustzlich werden jedoch Kopien der Elemente im Bereich [anf, end[ in das Set eingefgt.

6.6.3

Operatoren

Als Nchstes sind die berladenen Operatoren an der Reihe. Sowohl Sets als auch Multisets besitzen die hier angegebenen Operatoren. Exemplarisch werden jedoch nur die Operatoren fr set aufgelistet.

277

Die STL

operator=

Der Zuweisungsoperator wird dazu eingesetzt, um ein Set einem anderen Set zuzuweisen. Auf diese Weise wird eine komplette Kopie angelegt. Eventuell im alten Set gespeicherte Elemente werden dabei gelscht.
set<STyp, Comp, Allok> &operator=(const set<STyp, Comp, Allok>& s);

Vergleichsoperatoren

Zum Vergleich zweier Sets dienen folgende Template-Funktionen, die durch die restlichen, als Template definierten Vergleichsoperatoren der STL ergnzt werden knnen. Im Allgemeinen sind der Effizienz wegen alle Vergleichsoperatoren im Set-Container vorhanden.
template<STyp, Comp, Allok> bool std::operator<(const set<STyp, Comp, Allok>& s1, const set<STyp, Comp, Allok>& s2); template<STyp, Comp, Allok> bool std::operator==(const set<STyp, Comp, Allok>& s1, const set<STyp, Comp, Allok>& s2);

Sets werden genau wie die anderen Container lexikografisch verglichen.

6.6.4

Methoden

Tabelle 6.17 listet die Methoden von set und multiset auf, die anschlieend detailliert betrachtet werden.
Methode
begin clear count empty end equal_range erase find

Kurzbeschreibung Iteratorposition auf erstem Element des Sets/Multisets Lscht alle Elemente des Sets/Multisets. Zhlt das Vorkommen eines bestimmten Elements. berprft, ob Set/Multiset leer ist. Iteratorposition hinter dem letzten Element des Sets/Multisets Liefert einen Bereich gleicher Elemente zurck. Lscht Elemente. Sucht die Position eines Elements.

Tabelle 6.17 Die Methoden von set und multiset

278

Sets

6.6

Methode
insert max_size lower_bound rbegin rend

Kurzbeschreibung Fgt Elemente ein. maximal mglicheGre eines Sets/Multisets Liefert die Anfangsposition eines Bereichs gleicher Elemente. Reverse-Iteratorposition auf letztem Element des Sets/Multisets Reverse-Iteratorposition vor dem ersten Element des Sets/Multisets Ermittelt die Elementanzahl. Vertauscht zwei Sets/Multisets. Liefert die Endposition eines Bereichs gleicher Elemente.

size swap upper_bound

Tabelle 6.17 Die Methoden von set und multiset (Forts.)

begin Iteratorposition des ersten Elements


Bi begin(); CBi begin() const;

Liefert die Iteratorposition des ersten Elements im Set/Multiset. begin besitzt eine Laufzeit von O(1).
clear alle Elemente lschen
void clear();

Lscht alle Elemente eines Sets/Multisets. clear bentigt O(m)-Zeit, wobei m fr die Anzahl der zu lschenden Elemente steht.
count Elemente zhlen
size_type count(const STyp& obj) const;

Ermittelt, wie viele Elemente sich im Set/Multiset befinden, die gleich mit obj sind. Da nur bei Multisets gleiche Elemente zugelassen sind, kann count bei set nur einen Wert von maximal 1 zurckliefern.
empty Set leer?
bool empty() const;

Gibt einen wahren Wert zurck, wenn das Set/Multiset keine Elemente enthlt.

279

Die STL

end Iteratorposition hinter dem letzten Element


Bi end(); CBi end() const;

Liefert die Iteratorposition hinter dem letzten Element des Sets/Multisets. end besitzt eine Laufzeit von O(1).
equal_range
pair<Bi,Bi> equal_range(const STyp& obj); pair<CBi,CBi> equal_range(const STyp& obj) const;

Als Zusammenfassung von lower_bound und upper_bound liefert equal_range ein Paar zurck, welches in Form von Iteratorpositionen den Bereich der mit obj gleichen Elemente enthlt. Sollte kein mit obj gleiches Element im Set/ Multiset enthalten sein, dann werden mit end() erzeugte Iteratoren zurckgeliefert. Abbildung 6.19 gibt einen Einblick in die Funktionsweise von equal_range. Da Iteratoren auch einen Baum zu einer Sequenz abstrahieren, zeigt die Abbildung den Baum als sortierte Sequenz.
lower_bound upper_bound

obj obj obj obj obj obj

Abbildung 6.19 Die Funktionsweise von equal_range

Sie sollten beachten, dass wie bei durch Iteratoren definierten Bereichen blich die Ende-Position hinter dem letzten Element steht. equal_range bentigt O(log n)-Zeit, wobei n fr die Anzahl der im Set/Multiset gespeicherten Elemente steht.
erase Elemente lschen
Bi erase(Bi pos);

Lscht in einem Set/Multiset das Element an der Position pos, und liefert die hinter pos liegende Iteratorposition, zurck.
size_type erase(const STyp& obj);

Lscht alle mit obj gleichen Objekte des Sets/Multisets. Der Rckgabewert gibt die Anzahl der gelschten Elemente an. Da in einem Set ein Element nicht mehrfach vorkommen darf, wird der von erase zurckgelieferte Wert maximal 1 sein.

280

Sets

6.6

Bi erase(Bi anf, Bi end);

Lscht die im Bereich [anf, end[ liegenden Elemente eines Sets/Multisets, und liefert die Iteratorposition end zurck. Das Lschen der Elemente bentigt O(m)-Zeit, wobei m fr die Anzahl der zu lschenden Elemente steht. Das nach jedem Lschen eines Elements notwendige Ausgleichen des Baumes kostet O(log n)-Zeit, wobei n fr die Anzahl der Set/MultisetElemente nach dem jeweiligen Lschen steht. Wir nehmen daher den schlimmsten Fall, nmlich die Elementanzahl des Baums vor dem Lschen. erase hat damit eine Laufzeit von O(m log n).
find Elemente suchen
Bi find(const STyp& obj); CBi find(const STyp& obj) const;

Liefert die Iteratorposition des ersten mit obj gleichen Elements zurck. Sollte kein mit obj gleiches Element im Set/Multiset enthalten sein, dann wird ein mit end() erzeugter Iterator zurckgeliefert. find bentigt O(log n)Zeit, wobei n fr die Anzahl der im Set/Multiset gespeicherten Elemente steht.
insert Elemente einfgen
Bi insert(const STyp& obj); pair<Bi, bool> insert(const STyp& obj); //Multiset-Version // Set-Version

Fgt eine Kopie von obj in ein Set/Multiset ein. Bei einem Multiset liefert insert die Iteratorposition des neu eingefgten Objekts zurck. Bei einem Set liefert insert ein pair-Objekt zurck, welches einen Booleschen Wert enthlt, der Auskunft darber gibt, ob das Element eingefgt werden konnte. Wenn ja, dann ist der im Paar angegebene Iterator gltig. Sollte das Element bereits existieren, darf es nicht ein zweites Mal in ein Set eingefgt werden.
Bi insert(Bi pos, STyp& obj);

Fgt eine Kopie von obj in ein Set/Multiset ein. Die Einfgeposition pos ist leider nicht frei whlbar, da die Sortierung des Sets/Multisets nicht zerstrt werden darf. Die Methode liefert die Position des eingefgten Elements zurck.
void insert(CInput anf, CInput end)

In einem Set/Multiset werden Kopien der im Bereich [anf, end[ liegenden Elemente eingefgt. Wie erase besitzt insert eine Laufzeit von O(m log n).

281

Die STL

lower_bound Anfang der Sequenz gleicher Objekte


Bi lower_bound(const STyp& obj); CBi lower_bound(const STyp& obj) const;

Liefert die Anfangsposition eines Bereichs mit obj gleichen Elementen als Iteratorposition zurck. Sehen Sie dazu auch Abbildung 6.19. Sollte kein mit obj gleiches Element im Set/Multiset enthalten sein, dann wird ein mit end() erzeugter Iterator zurckgeliefert. lower_bound bentigt O(log n)-Zeit, wobei n fr die Anzahl der im Set/Multiset gespeicherten Elemente steht.
max_size maximal mgliche Gre
size_type max_size() const;

Liefert die maximale Gre zurck, die ein Set/Multiset auf dem aktuellen System haben kann.
rbegin Reverse-Iterator auf das letzte Element
reverse_iterator rbegin(); const_reverse_iterator rbegin() const;

Liefert die Iteratorposition des letzten Elements im Set/Multiset in Form eines Reverse-Iterators. rbegin besitzt eine Laufzeit von O(1).
rend Reverse-Iterator auf Position vor dem ersten Element
reverse_iterator rend(); const_reverse_iterator rend() const;

Liefert die Iteratorposition hinter dem letzten Element im Set/Multiset in Form eines Reverse-Iterators. rend besitzt eine Laufzeit von O(1).
size Anzahl der Elemente
size_type size() const

Liefert die Anzahl der augenblicklich im Set/Multiset gespeicherten Elemente.


upper_bound Ende der Sequenz gleicher Objekte
Bi upper_bound(const STyp& obj); CBi upper_bound(const STyp& obj) const;

282

Maps

6.7

Liefert die Endposition eines Bereichs mit obj gleichen Elementen als Iteratorposition zurck. Sehen Sie dazu auch Abbildung 6.19. Sollte kein mit obj gleiches Element im Set/Multiset enthalten sein, dann wird ein mit end() erzeugter Iterator zurckgeliefert. upper_bound bentigt O(log n)-Zeit, wobei n fr die Anzahl der im Set/Multiset gespeicherten Elemente steht.

6.7

Maps

Maps sind eine Weiterentwicklung der Sets. Whrend bei den Sets die Elementdaten automatisch als fr das Sortieren relevante Daten angesehen werden, trennt eine Map die fr das Sortieren wichtigen Daten (Schlssel) von den Elementdaten. Abbildung 6.20 zeigt ein Beispiel fr eine Map.
70 Otto

63

Willi

88

Lisa

65

Karl

Abbildung 6.20 Aufbau einer Map

Im (grafisch) linken Teil des Knotens steht der Schlssel, der zur Sortierung der Map verwendet wird. Die Elementdaten (rechte Seite), allein betrachtet, besitzen fr sich keine Sortierung. Der Unterschied zwischen Maps und Multimaps liegt darin, dass eine Multimap mehrere gleiche Schlssel verwalten kann, eine Map jedoch nicht. Sollen beispielsweise Personendaten verwaltet werden, dann wre der Schlssel Personalausweisnummer eindeutig, es knnte daher eine Map verwendet werden. Wrde der Schlssel Geburtsdatum eingesetzt, msste eine Multimap eingesetzt werden, da das Geburtsdatum nicht eindeutig ist. Damit wir die Maps der STL nutzen knnen, mssen wir die Datei map einbinden. Die Definitionen gehren dem Namensbereich std an. Auch map ist als Template definiert, allerdings im Vergleich zu den Sets um den Schlsseltyp ergnzt. Die Parameter des Templates bestimmen den Schlsseltyp (STyp), den Datentyp (DTyp), die zu verwendende Vergleichsfunktion (Comp) und den Typ des Allokators (Allok).

283

Die STL

template<typename typename typename typename class std::map { ... };

STyp, DTyp, Comp=less<STyp>, Allok=allocator<STyp> >

Die voreingestellte Vergleichsfunktion ist das Prdikat less. Die Definition von multimap ist identisch. Innerhalb der Klasse werden wieder einige Datentypen deklariert, die in Tabelle 6.18 aufgelistet sind:
Datentyp
key_type key_compare

Beschreibung Datentyp der Schlssel Funktionstyp, mit dem Schlssel verglichen werden (im Allgemeinen das Prdikat less) Typ der zu verwaltenden Elemente. Es ist zu beachten, dass value_type aus Datenelement und Schlssel besteht. Funktionstyp, mit dem Elemente verglichen werden (siehe folgenden Absatz) Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder die Kapazitt der Map/Multimap Typ fr die Angabe von Abstnden zweier Elemente Zeiger-Typ Typ fr konstante Zeiger Referenz-Typ Typ fr konstante Referenzen

value_type

value_compare

allocator_type size_type

difference_type pointer const_pointer reference const_reference

Tabelle 6.18 Die Datentypen von map und multimap

Eine Besonderheit der Maps/Multimaps ist der Datentyp value_compare. Obwohl dieser Datentyp von seiner Bedeutung her eigentlich die Nutzelemente vergleicht, muss aber sichergestellt werden, dass bei Elementvergleichen immer die Schlssel der Elemente verglichen werden. Aus diesem Grund ist value_compare eine eigene Klasse, die den ()-Operator berldt. Die Klasse kapselt damit den vergleichenden Zugriff auf die Nutzdaten. Obwohl nach auen hin die Nutzdaten verglichen werden, vergleicht value_ compare in Wirklichkeit die Schlssel:

284

Maps

6.7

class value_compare : public binary_function<value_type, value_type, bool> { friend class map<STyp, DTyp, Comp, Allok>; public: Comp comp; value_compare(Comp fkt) : comp(fkt) {} bool operator()(const value_type& o1, const value_type& o2) const { return (comp(o1.first, o2.first)); } };
Listing 6.15 Die Klasse value_compare von map/multimap

6.7.1

Konstruktoren

Die Konstruktoren sind fr Maps und Multimaps implementiert, werden hier aber nur fr Maps angegeben:
explicit map(const Comp& fkt=Comp(), const Allok& a=Allok());

Erzeugt eine leere Map/Multimap. Als Parameter kann ein eigenes Prdikat zum Vergleichen und ein Allokator angegeben werden. Wird kein Prdikat angegeben, so wird ein mit dem Standardkonstruktor erzeugtes Prdikat der Klasse Comp verwendet. Wird kein Allokator angegeben, so wird einer mit dem Standardkonstruktor von Allok erzeugt.
map(CInput anf, CInput end, const Comp& fkt=Comp(), const Allok& a=Allok())

Fr die Parameter fkt und a gilt dasselbe wie fr den vorigen Konstruktor. Zustzlich werden jedoch Kopien der Elemente im Bereich [anf, end[ in die Map/Multimap eingefgt.

6.7.2

Operatoren

Als Nchstes sind die berladenen Operatoren an der Reihe. Sowohl Maps als auch Multimaps besitzen die hier angegebenen Operatoren. Exemplarisch werden jedoch nur die Operatoren fr map aufgelistet.

285

Die STL

operator=

Der Zuweisungsoperator wird dazu eingesetzt, um eine Map/Multimap einer anderen Map/Multimap zuzuweisen. Auf diese Weise wird eine komplette Kopie angelegt. Eventuell in der alten Map/Multimap gespeicherte Elemente werden dabei gelscht.
map<STyp, DTyp, Comp, Allok> &operator=(const map<STyp, DTyp, Comp, Allok>& m);

Vergleichsoperatoren

Zum Vergleich zweier Maps dienen folgende Template-Funktionen, die durch die restlichen, als Template definierten Vergleichsoperatoren der STL ergnzt werden knnen. Normalerweise sind aber aus Effizienzgrnden alle Vergleichs-Operatoren im Map-/Multimap-Container vorhanden.
template<typename STyp, typename DTyp, typename Comp, typename Allok> bool std::operator<(const map<STyp, DTyp, Comp, Allok>& m1, const map<STyp, DTyp, Comp, Allok>& m2); template<typename STyp, typename DTyp, typename Comp, typename Allok> bool std::operator==(const map<STyp,DTyp,Comp,Allok>& m1, const map<STyp,DTyp,Comp,Allok>& m2);

Maps/Multimaps werden genau wie Sets lexikografisch verglichen.

6.7.3

Methoden

Tabelle 6.19 listet die Methoden von von map und multimap auf. Die Methoden, die sich aufgrund der unterschiedlichen Struktur von set und map von denen der Sets unterscheiden, werden anschlieend im Detail aufgelistet.
Methode
begin clear count

Kurzbeschreibung Iteratorposition auf erstem Element der Map/Multimap Lscht alle Elemente der Map/Multimap. Zhlt das Vorkommen eines bestimmten Elements.

Tabelle 6.19 Die Methoden von map und multimap

286

Maps

6.7

Methode
empty end equal_range erase find insert max_size lower_bound rbegin rend

Kurzbeschreibung berprft, ob Map/Multimap leer ist. Iteratorposition hinter dem letzten Element der Map/Multimap Liefert einen Bereich gleicher Elemente zurck. Lscht Elemente. Sucht die Position eines Elements. Fgt Elemente ein. maximal mgliche Gre einer Map/Multimap Liefert die Anfangsposition eines Bereichs gleicher Elemente. Reverse-Iteratorposition auf letztem Element der Map/Multimap Reverse-Iteratorposition vor dem ersten Element der Map/Multimap Ermittelt die Elementanzahl. Vertauscht zwei Maps/Multimaps. Liefert die Endposition eines Bereichs gleicher Elemente.

size swap upper_bound

Tabelle 6.19 Die Methoden von map und multimap (Forts.)

count
size_type count(const STyp& obj) const;

Ermittelt, wie viele Elemente sich in der Map/Multimap befinden, die einen mit obj gleichen Schlssel besitzen. Da nur bei Multimaps gleiche Elemente zugelassen sind, kann count bei set nur einen Wert von maximal 1 zurckliefern.
insert
// Nur bei Multimap: Bi insert(const pair<const STyp, DTyp>& obj); // Nur bei Map: pair<Bi, bool> insert(const pair<const STyp, DTyp>& obj);

Fgt eine Kopie von obj in eine Map/Multimap ein. Bei einer Multimap liefert insert die Iteratorposition des neu eingefgten Objekts zurck. Bei einer Map liefert insert ein pair-Objekt zurck, welches einen Booleschen Wert enthlt, der Auskunft darber gibt, ob das Element eingefgt werden konnte. Wenn ja, dann ist der im Paar angegebene Iterator gltig. Sollte das Element bereits existieren, darf es nicht ein zweites Mal in eine Map eingefgt werden.

287

Die STL

Bi insert(Bi pos, pair<const STyp, DTyp>& obj);

Fgt eine Kopie von obj in eine Map/Multimap ein. Die Einfgeposition pos ist nicht frei whlbar, da die Sortierung der Map/Multimap nicht zerstrt werden darf. Die Methode liefert die Position des eingefgten Elements zurck.
void insert(CInput anf, CInput end)

In eine Map/Multimap werden Kopien der im Bereich [anf, end[ liegenden Elemente eingefgt. Das Einfgen der Elemente bentigt O(m)-Zeit, wobei m fr die Anzahl der einzufgenden Elemente steht. Das nach jedem Einfgen eines Elements notwendige Ausgleichen des Baumes kostet O(log n)-Zeit, wobei n fr die Anzahl der Map/Multimap-Elemente nach dem jeweiligen Einfgen steht. Wir nehmen daher den schlimmsten Fall, nmlich die Elementanzahl des Baumes nach dem Einfgen aller Elemente. erase hat damit eine Laufzeit von O(m log n).

6.8

Strings

Auf den ersten Blick sind Strings nichts weiter als eine Zeichenfolge, die Ctypisch nullterminiert ist, also als letzten Wert eine Null hat. Daher knnte ein String grundstzlich mittels beispielsweise vector<char> implementiert werden. Weil an einen String aber von der Funktionalitt her andere Anforderungen gestellt werden und es auch mglich sein sollte, Strings mit unterschiedlichen Zeichenstzen verwenden zu knnen, wurde eine eigene Klasse basic_string entworfen:
template<typename ZTyp, typename Traits=char_traits<ZTyp>, typename Allok= allocator<ZTyp> > class std::basic_string {...};
Listing 6.16 Die Definition von basic_string

Dabei steht ZTyp fr den verwendeten Zeichentyp. Fr diesen Zeichentyp mssen wir sogenannte char_traits definieren, in denen die Charakteristika des Zeichens spezifiziert sind. Zustzlich knnen wir noch einen entsprechenden Allokator mit angegeben.

288

Strings

6.8

6.8.1

char_traits

Da die Zeicheneigenschaften ein wichtiger Punkt beim Arbeiten mit Strings sind, wollen wir uns zuerst genauer mit diesen Eigenschaften befassen, bevor wir uns in die Tiefen der Klasse basic_string strzen. Als ersten Punkt schauen wir uns die Definition des Templates an:
template<typename ZTyp> struct std::char_traits {...};
Listing 6.17 Die Definition von char_traits

Fr spezielle Zeichentypen gibt es entsprechende Spezialisierungen, z. B. fr char:


template<> struct char_traits<char> {...};

Eine solche Spezialisierung muss auch implementiert werden, wenn basic_ string mit einem selbst definierten Zeichentyp zusammenarbeiten soll. Wir werden uns bei der weiteren Betrachtung mit dem Standard-Template beschftigen. char_traits definiert einige Datentypen:
Datentyp
char_type int_type pos_type off_type state_type

Beschreibung der Zeichentyp, fr den die char_traits definiert werden der Typ fr die Zahlendarstellung des Zeichens Mit diesem Typ werden Positionen in Streams definiert. der Typ zur Definition von Offsets in Streams der Typ, mit dem Stream-Zustnde definiert werden

Tabelle 6.20 Die Datentypen von char_traits

Da jedes Zeichen eines Zeichensatzes einen eindeutigen numerischen Wert besitzen muss, wird innerhalb von char_traits der Typ int_type definiert. Fr die Typen pos_type, off_type und state_type werden die bei den Streams definierten Datentypen bernommen. Kommen wir nun zu den Methoden von char_traits.
assign Zeichen zuweisen
static void assign(ZTyp& c1, const ZTyp& c2);

289

Die STL

Weist einem Zeichen c1 ein Zeichen c2 zu.


static ZTyp* assign(ZTyp* s, size_t anz, const ZTyp& c);

Fllt die anz Zeichen groe Sequenz s mit dem Zeichen c.


compare Zeichen vergleichen
static int compare(const ZTyp* s1, const ZTyp* s2, size_t anz);

Die beiden anz Zeichen langen Zeichenketten s1 und s2 werden lexikografisch verglichen. Die Rckgabewerte sind die folgenden:
s1 < s2 ergibt einen negativen Wert. s1 == s2 ergibt 0. s1 > s2 ergibt einen positiven Wert.

copy Zeichen kopieren


static ZTyp* copy(ZTyp* s1, const ZTyp* s2, size_t anz);

Kopiert die ab der Position s2 liegenden anz Zeichen an die Position s1. Die Position s1 wird anschlieend von der Methode zurckgegeben.
eof
static int_type eof();

Liefert den Wert fr end of file zurck.


eq Prfen auf Gleichheit
static bool eq(const ZTyp& c1, const ZTyp& c2);

Liefert genau dann einen wahren Wert zurck, wenn die beiden Zeichen c1 und c2 gleich sind.
eq_int_type numerischen Wert auf Gleichheit prfen
static bool eq_int_type(const int_type& n1, const int_type& n2);

Vergleicht die beiden numerischen Werte n1 und n2, die beide jeweils ein Zeichen darstellen. eq_int_type liefert genau dann einen wahren Wert zurck, wenn die beiden Werte n1 und n2 gleich sind.

290

Strings

6.8

find Zeichen suchen


static const ZTyp* find(const ZTyp* s, size_t anz, const ZTyp& c)

Sucht in der anz Zeichen langen Sequenz s nach dem Zeichen c. Wenn das Zeichen gefunden wird, liefert die Methode einen Zeiger darauf zurck. Andernfalls ist der Rckgabewert ein Nullzeiger.
length Lnge der Zeichensequenz
static size_t length(const ZTyp* s);

Liefert die Lnge der Zeichensequenz s zurck. Die Zeichenendekennung fliet dabei nicht in die Lnge mit ein.
lt Prfen auf kleiner
static bool lt(const ZTyp& c1, const ZTyp& c2);

Liefert genau dann einen wahren Wert zurck, wenn die Bedingung c1 < c2 gilt.
move sicheres Kopieren von Zeichen
static ZTyp* move(ZTyp* s1, const ZTyp* s2, size_t anz);

Kopiert die ab der Position s2 liegenden anz Zeichen an die Position s1. Die Position s1 wird anschlieend von der Methode zurckgegeben. Im Gegensatz zu copy wird die Sequenz auch dann noch ordnungsgem kopiert, wenn sich die Speicherbereiche von s1 und s2 berlappen.
not_eof
static int_type not_eof(const int_type& n);

Prft den numerischen Wert n, der ein Zeichen reprsentiert, daraufhin, ob er ungleich mit eof() ist. Ist n ungleich eof(), dann liefert die Methode n zurck, andernfalls !eof().
to_char_type numerischen Wert in Zeichen umwandeln
static ZTyp to_char_type(const int_type& n);

Wandelt den numerischen Wert n in das korrespondierende Zeichen um, und liefert es zurck.

291

Die STL

to_int_type Zeichen in numerischen Wert umwandeln


static int_type to_int_type(const ZTyp& c);

Wandelt das Zeichen c in den entsprechenden numerischen Wert um, und liefert ihn zurck.

6.8.2

basic_string

Nachdem nun die char_traits bekannt sind, kommen wir wieder zu basic_
string selbst zurck. Damit die Klasse fr gewhnliche Strings unproblema-

tisch verwendet werden kann, gibt es folgende Typdefinitionen:


typedef basic_string<char> string; typedef basic_string<wchar_t> wstring;

Damit wir basic_string, string oder wstring verwenden knnen, mssen wir die Datei string einbinden. Die Definitionen befinden sich wieder im Namensbereich std. basic_string definiert einige STL-typische Datentypen, die in Tabelle 6.21 aufgelistet sind.
Datentyp
value_type allocator_type size_type

Beschreibung Typ der zu verwaltenden Elemente Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder die Kapazitt des Strings Typ fr die Angabe von Abstnden zweier Zeichen Zeiger-Typ Typ fr konstante Zeiger Referenz-Typ Typ fr konstante Referenzen

difference_type Pointer const_pointer Reference const_reference

Tabelle 6.21 Die Datentypen von basic_string

Um die weiteren Betrachtungen bersichtlicher zu gestalten, unterstellen wir folgende, eigene Typdefinition:
typedef basic_string<ZTyp, Traits, Allok> MeinTyp;

Da diese Definition virtueller Natur ist, bedeutet das fr den praktischen Einsatz, dass Sie jedes Mal, wenn MeinTyp Verwendung findet, tatschlich basic_string<ZTyp, Traits, Allok> schreiben mssen.

292

Strings

6.8

Ein Attribut von basic_string wird hufiger Verwendung finden und soll daher kurz besprochen werden:
static const size_type npos;

Die Konstante npos definiert den grten Wert, den der Datentyp size_ type annehmen kann, und wird blicherweise dann eingesetzt, wenn es keine gltige Position gibt oder wenn das Ende als Position verwendet werden soll.

6.8.3

Konstruktoren

Schauen wir uns zunchst die Konstruktoren von basic_string an. Auf die Angabe der Template-Parameter im Konstruktornamen wird verzichtet:
explicit basic_string(const Allok& a=Allok() )

Erzeugt einen leeren String. Die Angabe eines Allokator-Objekts ist mglich.
basic_string(const MeinTyp& s);

Erzeugt einen String als Kopie von String s (Kopierkonstruktor). Dabei mssen die Template-Parameter bei beiden Strings bereinstimmen.
basic_string(const MeinTyp s, size_type pos, size_type anz=npos, const Allok& a=Allok());

Erzeugt einen String, und initialisiert ihn mit anz Zeichen, die ab Position pos in s liegen ([pos, pos+anz[). Wird fr anz kein Wert angegeben, dann werden alle Zeichen ab Position pos bis zum Ende von s verwendet.
basic_string(const ZTyp* s, size_type anz, const Allok& a=Allok());

Erzeugt einen String, und initialisiert ihn mit den ersten anz Zeichen der Zeichensequenz s.
basic_string(const ZTyp* s, const Allok& a=Allok());

Erzeugt einen String, und initialisiert ihn mit der Zeichensequenz s. s muss nullterminiert sein (C-String).
basic_string(size_type anz, ZTyp c, const Allok& a=Allok());

Erzeugt einen String, und initialisiert ihn mit anz Kopien des Zeichens c.
basic_string(CInput anf, CInput end, const Allok& a=Allok());

Erzeugt einen String, und initialisiert ihn mit den Zeichen des Bereichs [anf, end[.

293

Die STL

6.8.4

Operatoren

Dieses Teilkapitel listet die Operatoren von basic_string auf.


operator=

Der Zuweisungsoperator wird verwendet, um einem bereits existierenden String einen anderen String, eine Zeichensequenz oder ein einzelnes Zeichen zuzuweisen.
MeinTyp& operator=(const MeinTyp& s);

Weist einem String einen anderen String s zu. Der aufrufende String ist damit eine Kopie von s.
MeinTyp& operator=(const ZTyp* s);

Weist einem String eine nullterminierte Zeichensequenz s zu (C-String). Der aufrufende String ist damit eine Kopie von s.
MeinTyp& operator=(ZTyp c);

Weist einem String das Zeichen c zu. Der aufrufende String ist damit eine Kopie von c.
Vergleichsoperatoren

Fr Strings sind die Vergleichsoperatoren berladen, wovon der Gleichheitsoperator im Folgenden exemplarisch aufgefhrt wird:
template<typename ZTyp, typename Traits, typename Allok> bool std::operator==(const basic_string<ZTyp, Traits, Allok>& s1, const basic_string<ZTyp, Traits, Allok>& s2);

operator[ ]

Der Indexoperator wird genau wie bei C-Strings oder gewhnlichen Arrays fr den direkten Elementzugriff verwendet. Der Operator existiert dabei in zwei Varianten, fr variable und konstante Strings:
reference operator[] (size_type index); const_reference operator[] (size_type index) const;

Hinweis
operator[] prft den Index nicht auf seine Gltigkeit.

294

Strings

6.8

Es ist daher uerst wichtig, den Index selbst auf seine Gltigkeit zu prfen, oder sicher zu sein, dass der Index gltig ist. Der Indexoperator bentigt O(1)-Zeit.
operator+

Strings setzen den +-Operator dazu ein, um Strings, Zeichensequenzen und einzelne Zeichen miteinander zu verknpfen.
template<typename ZTyp, typename Traits, typename Allok> basic_string<typename ZTyp, typename Traits typenameclass Allok> operator+(const basic_string<ZTyp,Traits,Allok>& s1, const basic_string<ZTyp,Traits,Allok>& s2);

Die operator+-Funktion gibt es auch noch in Verbindung mit den Datentypen const ZTyp* und const ZTyp.
operator+=

Der Operator += wird dazu verwendet, um Strings, Zeichensequenzen oder auch nur einzelne Zeichen an einen String anzuhngen. Der Operator verhlt sich hnlich wie die Methode append.
MeinTyp& operator+=(const MeinTyp& s);

Hngt an einen String eine Kopie des Strings s an.


MeinTyp& operator+=(const ZTyp* s);

Hngt an einen String eine Kopie der nullterminierten Zeichensequenz s an.


MeinTyp& operator+=(ZTyp c);

Hngt an einen String eine Kopie des Zeichens c an.

6.8.5

Methoden

Dieser Abschnitt behandelt die Methoden von basic_string. Einen berblick verschafft Ihnen Tabelle 6.22.
Methode
append assign at

Kurzbeschreibung Hngt an einen String Zeichen oder einen anderen String an. Weist einem String Zeichen oder einen anderen String zu. Spricht ein Zeichen des Strings ber seinen Index an.

Tabelle 6.22 Die Methoden von basic_string

295

Die STL

Methode
begin c_str capacity compare empty end erase find find_first_not_of find_first_of find_last_not_of

Kurzbeschreibung Iteratorposition des ersten Zeichens Liefert einen C-String-Zeiger auf den String. Gibt die Kapazitt des Strings zurck. Vergleicht Strings. berprft, ob ein String Zeichen enthlt. Iteratorposition hinter dem letzten Zeichen Lscht Zeichen aus einem String. Sucht Zeichenfolgen im String. Sucht nach nicht im Suchstring vorhandenen Zeichen. Sucht nach im Suchstring vorhandenen Zeichen. Sucht rckwrts nach nicht im Suchstring vorhandenen Zeichen. Sucht rckwrts nach im Suchstring vorhandenen Zeichen. Fgt in einen String Zeichen oder Strings ein. Ermittelt die Lnge des Strings (wie size). Liefert die maximal mgliche Gre des Strings. Hngt ein Zeichen an den String an. Liefert einen Reverse-Iterator auf das letzte Zeichen. Liefert einen Reverse-Iterator vor das erste Zeichen. Ersetzt Zeichen im String. Reserviert Speicher fr den String. Verndert die Kapazitt des Strings. Sucht rckwrts nach Zeichenfolgen. Ermittelt die Lnge des Strings (wie length). Erzeugt einen Teilstring. Tauscht den Inhalt zweier Strings aus.

find_last_of insert length maxsize push_back rbegin rend replace reserve resize rfind size substr swap

Tabelle 6.22 Die Methoden von basic_string (Forts.)

append Strings und Zeichen anhngen


MeinTyp& append(const MeinTyp& s);

Hngt an einen String eine Kopie des Strings s an.

296

Strings

6.8

MeinTyp& append(const MeinTyp& s, size_type pos, size_type anz);

Hngt an einen String Kopien der anz Zeichen an, die ab Position pos in s liegen ([pos, pos+anz[). Sollte pos auerhalb des fr s gltigen Bereichs liegen, wird eine out_of_range-Exception erzeugt.
MeinTyp& append(const ZTyp* s);

Hngt an einen String eine Kopie der nullterminierten Zeichensequenz s an.


MeinTyp& append(const ZTyp* s, size_type anz);

Hngt an einen String eine Kopie der ersten anz Zeichen der Zeichensequenz s an.
MeinTyp& append(size_type anz, ZTyp c);

Hngt an einen String anz Kopien des Zeichens c an.


MeinTyp& append (CInput anf, CInput end);

Hngt an einen String die Zeichen des Bereichs [anf, end[ an. Sollte durch den Aufruf von append der endgltige String grer als der grtmgliche String werden, dann wird eine length_error-Exception erzeugt. append besitzt eine Laufzeit von O(m), wobei m fr die Anzahl der anzuhngenden Zeichen steht.
assign Strings und Zeichen zuweisen
MeinTyp& assign(const MeinTyp& s);

Weist einem String einen anderen String s zu. Der aufrufende String ist damit eine Kopie von s.
MeinTyp& assign(const MeinTyp& s, size_type pos, size_type anz);

Weist einem String anz Zeichen zu, die ab Position pos in s liegen ([pos, pos+anz[). Sollte pos auerhalb des fr s gltigen Bereichs liegen, wird eine out_of_range-Exception erzeugt.
MeinTyp& assign(const ZTyp* s);

Weist einem String die Zeichensequenz s zu.


MeinTyp& assign(const ZTyp* s, size_type anz);

Weist einem String die ersten anz Zeichen der Zeichensequenz s zu.
MeinTyp& assign(size_type anz, ZTyp c);

Weist einem String anz Kopien des Zeichens c zu.

297

Die STL

MeinTyp& assign (CInput anf, CInput end);

Weist einem String die Zeichen des Bereichs [anf, end[ zu. Sollte durch den Aufruf von assign der endgltige String grer als der grtmgliche String werden, dann wird eine length_error-Exception erzeugt. assign besitzt eine Laufzeit von O(m), wobei m fr die Anzahl der zuzuweisenden Zeichen steht.
at prfender Indexoperator
reference at(size_type index); const_reference at(size_type index) const;

Funktionsgleich mit dem Indexoperator, liefert at eine Referenz auf das Element mit Index index zurck. Allerdings prft at zustzlich, ob der Index im gltigen Bereich liegt. Wenn nicht, dann wird eine out_of_range-Exception ausgelst. Genau wie der Indexoperator kann auch at jedes Element in O(1)Zeit ansprechen.
begin Iteratorposition auf erstem Zeichen
Random begin(); CRandom begin() const;

Liefert die Iteratorposition des ersten Zeichens im String. begin besitzt eine Laufzeit von O(1).
c_str String als C-String
const ZTyp* c_str() const;

Liefert einen Zeiger auf den Anfang des Strings zurck. Der String ist nullterminiert, so dass er als konstanter C-String verwendet werden kann.
capacity Kapazitt des Strings
size_type capacity() const;

Liefert die Kapazitt eines Strings.


compare Strings vergleichen

Fr alle Varianten der compare-Methode gilt, dass die beteiligten Zeichensequenzen lexikografisch miteinander verglichen werden. Die Methode liefert folgende Werte zurck:

298

Strings

6.8

<0, wenn der aufrufende String kleiner als das Funktionsargument ist =0, wenn der aufrufende String gleich dem Funktionsargument ist >0, wenn der aufrufende String grer als das Funktionsargument ist Die Varianten der compare-Methode sind:
int compare(const MeinTyp& s) const;

Vergleicht den aufrufenden String mit dem String s.


int compare(size_type pos, size_type anz, const MeinTyp& s) const; anz Zeichen des aufrufenden Strings, die ab Position pos liegen, werden

mit s verglichen ([pos, pos+anz[). Dabei wird der ber pos und anz definierte Teilstring als kompletter String der Lnge anz betrachtet. Sollte pos auerhalb des fr den aufrufenden String gltigen Bereichs liegen, wird eine out_of_range-Exception erzeugt.
int compare(size_type pos, size_type anz, const MeinTyp& s, size_type spos, size_type sanz) const; anz Zeichen des aufrufenden Strings, die ab Position pos liegen, werden

mit sanz Zeichen des Strings s verglichen, die ab Position spos liegen (ein Vergleich der Bereiche [pos, pos+anz[ und [spos, spos+sanz[). Sollte spos auerhalb des fr s gltigen Bereichs oder pos auerhalb des fr den aufrufenden String gltigen Bereichs liegen, wird eine out_of_range-Exception erzeugt.
int compare(const ZTyp* s) const;

Der aufrufende String wird mit der Zeichensequenz s verglichen.


int compare(size_type pos, size_type anz, const ZTyp* s, size_type sanz=npos) const; anz Zeichen des aufrufenden Strings, die ab Position pos liegen, werden

mit den ersten sanz Zeichen von s verglichen. Dabei wird der ber pos und anz definierte Teilstring als kompletter String der Lnge anz betrachtet. Wird fr sanz kein Wert angegeben, dann werden alle Zeichen von s zum Vergleich herangezogen. Sollte pos auerhalb des fr den aufrufenden String gltigen Bereichs liegen, wird eine out_of_range-Exception erzeugt. compare bentigt O(m)-Zeit, wobei m die Anzahl der zu vergleichenden Zeichen ist.

299

Die STL

empty String leer?


bool empty() const;

Liefert true, wenn der String keine Zeichen enthlt. Andernfalls wird false zurckgegeben.
end Iteratorposition hinter dem letzten Zeichen
Random end(); CRandom end() const;

Liefert die Iteratorposition hinter dem letzten Zeichen des Strings. end besitzt eine Laufzeit von O(1).
erase Zeichen entfernen
MeinTyp& erase(size_type pos=0, size_type anz=npos);

Eine Anzahl von anz Zeichen, die im aufrufenden String ab Position pos liegen, werden gelscht. Die Methode liefert eine Referenz auf den String zurck. Sollte fr pos kein Wert angegeben werden, so wird als Position das erste Zeichen des Strings genommen. Wird fr anz kein Wert spezifiziert, dann werden von Position pos an alle Zeichen bis zum Ende des Strings gelscht.
Random erase(Random pos);

Lscht im aufrufenden String das Zeichen an der Iteratorposition pos, und liefert die Iteratorposition hinter pos zurck.
Random erase(Random anf, Random end);

Lscht in einem String die im Bereich [anf, end[ liegenden Zeichen, und liefert die Iteratorposition end zurck. erase bentigt O(m+l)-Zeit, wobei m fr die Anzahl der zu kopierenden und l fr die Anzahl der zu lschenden Elemente stehen.
find Strings und Zeichen suchen
size_type find(const MeinTyp& s, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem String s. Die Methode liefert die Position des ersten Vorkommens von s zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find(const ZTyp* s, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach der nullterminierten Zeichensequenz s. Die Methode liefert die Position des ersten Vorkom-

300

Strings

6.8

mens von s zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find(const ZTyp* s, size_type pos, size_type anz) const;

Sucht im aufrufenden String ab der Position pos nach den ersten anz Zeichen der nullterminierten Zeichensequenz s. Die Methode liefert die Position des ersten Vorkommens der anz Zeichen von s zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find(ZTyp c, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem Zeichen c. Die Methode liefert die Position des ersten Vorkommens von c zurck. Sollte c nicht gefunden werden, dann ist der Rckgabewert npos. Eine vernnftige Implementation von find braucht O(n+m)-Zeit, wobei n fr die Anzahl der Zeichen des aufrufenden Strings und m fr die Anzahl der Zeichen im Suchstring stehen.
find_first_not_of erstes nicht enthaltenes Zeichen
size_type find_first_not_of(const MeinTyp& s, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Vorkommen eines Zeichens, welches nicht im String s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos. Abbildung 6.21 zeigt ein Beispiel, welches einen String daraufhin prft, ob er eine gltige Ganzzahl beinhaltet. Weil eine Ganzzahl nur aus den Zahlen 09 bestehen kann, verwenden wir als Suchstring 0123456789. Dann wird im Teststring 162772.20 die Position des ersten Zeichens gesucht, welches nicht im Suchstring enthalten ist. Und das ist der Punkt an Position 6.
Suchstring: pos 0 1 2 3 4 5 6 7 8 9 Ergebnis

1 6 2 7 7 2 . 2 0 a 6 0 5 10

Abbildung 6.21 Ein Beispiel zu find_first_not_of

size_type find_first_not_of(const ZTyp* s, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Vorkom-

301

Die STL

men eines Zeichens, welches nicht in der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_first_not_of(const ZTyp* s, size_type pos, size_type anz) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Vorkommen eines Zeichens, welches nicht in den ersten anz Zeichen der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_first_not_of(ZTyp c, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Zeichen, welches nicht gleich mit c ist. Die Methode liefert bei erfolgreicher Suche die Position zurck. Sollte c nicht gefunden werden, dann ist der Rckgabewert npos. find_first_not_of braucht O(n*m)-Zeit, wobei n fr die Anzahl der Zeichen im aufrufenden String und m fr die Anzahl der Zeichen im Suchstring stehen.
find_first_of erstes im String enthaltenes Zeichen
size_type find_first_of(const MeinTyp& s, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Vorkommen eines Zeichens, welches ebenfalls im String s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos. Das Beispiel in Abbildung 6.22 sucht nach runden oder eckigen Klammern. Das erste Zeichen, welches ab Position 0 gefunden wird, ist die offene runde Klammer an Position 9. Und 9 ist auch der Wert, der als Suchergebnis zurckgeliefert wird.
Suchstring: pos [ ( ) ]

Ergebnis ) 15 { }

o p e r a t o r = ( c h a r 0 5 10

Abbildung 6.22

Ein Beispiel zu find_first_of

302

Strings

6.8

size_type find_first_of(const ZTyp* s, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Vorkommen eines Zeichens, welches ebenfalls in der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_first_of(const ZTyp* s, size_type pos, size_type anz) const;

Sucht im aufrufenden String ab der Position pos nach dem ersten Vorkommen eines Zeichens, welches ebenfalls in den ersten anz Zeichen der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_first_of(ZTyp c, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos nach dem Zeichen c. Die Methode liefert die Position des ersten Vorkommens von c zurck. Sollte c nicht gefunden werden, dann ist der Rckgabewert npos. find_first_of braucht O(n*m)-Zeit, wobei n fr die Anzahl der Zeichen im aufrufenden String und m fr die Anzahl der Zeichen im Suchstring stehen.
find_last_not_of letztes nicht enthaltenes Zeichen
size_type find_last_not_of(const MeinTyp& s, size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Vorkommen eines Zeichens, welches nicht im String s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_last_not_of(const ZTyp* s, size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Vorkommen eines Zeichens, welches nicht in der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.

303

Die STL

size_type find_last_not_of(const ZTyp* s, size_type pos, size_type anz) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Vorkommen eines Zeichens, welches nicht in den ersten anz Zeichen der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_last_not_of(ZTyp c,size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Zeichen, welches nicht gleich mit c ist. Die Methode liefert bei erfolgreicher Suche die Position zurck. Sollte c nicht gefunden werden, dann ist der Rckgabewert npos. find_last_not_of braucht O(n*m)-Zeit, wobei n fr die Anzahl der Zeichen im aufrufenden String und m fr die Anzahl der Zeichen im Suchstring stehen.
find_last_of letztes enthaltenes Zeichen
size_type find_last_of(const MeinTyp& s, size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Vorkommen eines Zeichens, welches ebenfalls im String s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_last_of(const ZTyp* s, size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Vorkommen eines Zeichens, welches ebenfalls in der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type find_last_of(const ZTyp* s, size_type pos, size_type anz) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem ersten Vorkommen eines Zeichens, welches ebenfalls in den ersten anz Zeichen der nullterminierten Zeichenkette s enthalten ist. Die Methode liefert bei erfolgreicher Suche die Position des Fundes zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.

304

Strings

6.8

size_type find_last_of(ZTyp c, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem Zeichen c. Die Methode liefert die Position des ersten Vorkommens von c zurck. Sollte c nicht gefunden werden, dann ist der Rckgabewert npos. find_last_of braucht O(n*m)-Zeit, wobei n fr die Anzahl der Zeichen im aufrufenden String und m fr die Anzahl der Zeichen im Suchstring stehen.
insert Zeichen einfgen
MeinTyp& insert(size_type pos, const MeinTyp& s);

Fgt im aufrufenden String an der Position pos eine Kopie des Strings s ein. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_ range-Exception geworfen.
MeinTyp& insert(size_type pos, const MeinTyp& s, size_type spos, size_type sanz);

Fgt im aufrufenden String an der Position pos eine Kopie der sanz Zeichen ein, die ab Position spos in s liegen. Sollte pos oder spos auerhalb des gltigen Bereichs liegen, wird eine out_of_range-Exception geworfen.
MeinTyp& insert(size_type pos, const ZTyp* s);

Fgt im aufrufenden String an der Position pos eine Kopie der Zeichensequenz s ein. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_range-Exception erzeugt.
MeinTyp& insert(size_type pos, const ZTyp* s, size_type anz);

Fgt im aufrufenden String an der Position pos eine Kopie der ersten anz Zeichen der Zeichensequenz s ein. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_range-Exception geworfen.
MeinTyp& insert(size_type pos, size_type anz, ZTyp c);

Fgt im aufrufenden String an der Position pos anz Kopien des Zeichens c ein. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_ range-Exception geworfen.
Random insert(Random pos, ZTyp c);

Fgt im aufrufenden String an der Iteratorposition pos eine Kopie des Zeichens c ein. Zurckgegeben wird die Iteratorposition des eingefgten Zeichens.

305

Die STL

Random insert(Random pos, size_type anz, ZTyp c);

Fgt im aufrufenden String an der Iteratorposition pos anz Kopien des Zeichens c ein. Zurckgegeben wird die Iteratorposition des ersten eingefgten Zeichens.
MeinTyp& insert (Random pos, CInput anf, CInput end);

Fgt im aufrufenden String an der Iteratorposition pos Kopien der Zeichen des Bereichs [anf, end[ ein. Sollte durch den Aufruf von insert der endgltige String grer als der grtmgliche String werden, dann wird eine length_error-Exception geworfen. Fr den Fall, dass die Kapazitt des Strings zum Einfgen ausreicht, bentigt insert O(m+l)-Zeit, wobei m fr die Anzahl der zu kopierenden und l fr die Anzahl der einzufgenden Zeichen stehen. Sollte die Kapazitt nicht ausreichen und deswegen ein neuer Speicherbereich angelegt werden, dann bentigt insert O(n+1)Zeit, wobei n fr die aktuelle Zeichenanzahl des Strings und l fr die Anzahl der einzufgenden Zeichen steht.
length Anzahl der Zeichen
size_type length() const

Liefert die Anzahl der augenblicklich im String gespeicherten Zeichen.


max_size maximal mgliche Gre
size_type max_size() const;

Liefert die maximale Gre zurck, die ein String auf dem aktuellen System haben kann.
push_back Zeichen am Ende anhngen
void push_back(ZTyp& c);

Hngt an einen String eine Kopie des Zeichens c an. Wenn die Kapazitt des Strings ausreicht, besitzt push_back eine Laufzeit von O(1). Ansonsten wird O(n)-Zeit bentigt, wobei n fr die Anzahl der Zeichen im String steht.
rbegin Reverse-Iterator auf das letzte Zeichen
reverse_iterator rbegin(); const_reverse_iterator rbegin() const;

Liefert die Iteratorposition des letzten Zeichens im String in Form eines Reverse-Iterators. rbegin besitzt eine Laufzeit von O(1).

306

Strings

6.8

rend Reverse-Iteratorposition vor dem ersten Zeichen


reverse_iterator rend(); const_reverse_iterator rend() const;

Liefert die Iteratorposition vor dem ersten Zeichen im String in Form eines Reverse-Iterators. rend besitzt eine Laufzeit von O(1).
replace Zeichen ersetzen
MeinTyp& replace(size_type pos, size_type anz, const MeinTyp& s);

Im aufrufenden String werden die Zeichen im Bereich [pos, pos+anz[ durch eine Kopie des Strings s ersetzt. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_range-Exception geworfen.
MeinTyp& replace(size_type pos, size_type anz, const MeinTyp& s, size_type spos, size_type sanz);

Im aufrufenden String werden die Zeichen im Bereich [pos, pos+anz[ durch eine Kopie des Bereichs [spos, spos+sanz[ aus String s ersetzt. Sollte pos oder spos auerhalb des gltigen Bereichs liegen, wird eine out_of_rangeException geworfen.
MeinTyp& replace(size_type pos, size_type anz, const ZTyp* s);

Im aufrufenden String werden die Zeichen im Bereich [pos, pos+anz[ durch eine Kopie der nullterminierten Zeichensequenz s ersetzt. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_range-Exception geworfen.
MeinTyp& replace(size_type pos, size_type anz, const ZTyp* s, size_type sanz);

Im aufrufenden String werden die Zeichen im Bereich [pos, pos+anz[ durch eine Kopie des Bereichs [0, sanz[ aus der nullterminierten Zeichensequenz s ersetzt. Sollte pos auerhalb des gltigen Bereichs liegen, wird eine out_of_range-Exception geworfen.
MeinTyp& replace(size_type pos, size_type anz, size_type sanz, ZTyp c);

Im aufrufenden String werden die Zeichen im Bereich [pos, pos+anz[ durch anz Kopien des Zeichens c ersetzt.

307

Die STL

MeinTyp& replace(Random anf, Random end, const MeinTyp& s);

Im aufrufenden String werden die Zeichen im Bereich [anf, end[ durch eine Kopie des Strings s ersetzt.
MeinTyp& replace(Random anf, Random end, const ZTyp* s);

Im aufrufenden String werden die Zeichen im Bereich [anf, end[ durch eine Kopie der nullterminierten Zeichensequenz s ersetzt.
MeinTyp& replace(Random anf, Random end, const ZTyp* s, size_type anz);

Im aufrufenden String werden die Zeichen im Bereich [anf, end[ durch eine Kopie des Bereichs [0, anz[ aus der nullterminierten Zeichensequenz s ersetzt.
MeinTyp& replace(Random anf, Random end, size_type anz, ZTyp c);

Im aufrufenden String werden die Zeichen im Bereich [anf, end[ durch anz Kopien des Zeichens c ersetzt.
MeinTyp& replace(Random anf, Random end, CInput anf2, CInput end2);

Im aufrufenden String werden die Zeichen im Bereich [anf, end[ durch eine Kopie des Bereichs [anf2, end2[ ersetzt.
reserve Platz reservieren
void reserve(size_type anz);

Reserviert Speicherplatz fr die Aufnahme von anz Zeichen. Sollte die aktuelle Kapazitt kleiner als anz sein, dann wird der String um die ntige Menge von Zeichen vergrert. Andernfalls ist reserve ohne Wirkung. reserve bentigt schlimmstenfalls eine Laufzeit von O(n), wobei n die aktuelle Anzahl der im String enthaltenen Zeichen darstellt.
resize Stringgre verndern
void resize(size_type anz, ZTyp c=ZTyp(0) );

ndert die Kapazitt eines Strings auf die Gre anz. Sollte anz kleiner sein als die aktuelle Gre des Strings, dann werden die berflssigen Elemente gelscht. Fr den Fall, dass anz grer als die aktuelle Stringgre ist, werden entsprechend viele Elemente als Kopie von c an den String angehngt. Per Voreinstellung ist c ein mit dem Konstruktor von ZTyp(0) erzeugtes Objekt.

308

Strings

6.8

resize bentigt schlimmstenfalls eine Laufzeit von O(n), wobei n die aktuelle

Anzahl der im String enthaltenen Zeichen darstellt.


rfind Strings und Zeichen von hinten suchen
size_type rfind(const MeinTyp& s, size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem String s. Die Methode liefert die Position des ersten Vorkommens von s zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type rfind(const ZTyp* s, size_type pos=npos) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach der nullterminierten Zeichensequenz s. Die Methode liefert die Position des ersten Vorkommens von s zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type rfind(const ZTyp* s, size_type pos, size_type anz) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach den ersten anz Zeichen der nullterminierten Zeichensequenz s. Die Methode liefert die Position des ersten Vorkommens der anz Zeichen von s zurck. Sollte s nicht gefunden werden, dann ist der Rckgabewert npos.
size_type rfind(ZTyp c, size_type pos=0) const;

Sucht im aufrufenden String ab der Position pos rckwrts nach dem Zeichen c. Die Methode liefert die Position des ersten Vorkommens von c zurck. Sollte c nicht gefunden werden, dann ist der Rckgabewert npos. Eine vernnftige Implementation von rfind braucht O(n+m)-Zeit, wobei n fr die Anzahl der Zeichen des aufrufenden Strings und m fr die Anzahl der Zeichen im Suchstring stehen.
size Anzahl der Zeichen im String
size_type size() const

Liefert die Anzahl der augenblicklich im String gespeicherten Zeichen.


substr Teilstring ermitteln
MeinTyp substr(size_type pos=0, size_type anz=npos) const;

Liefert einen String zurck, der die Zeichen des Bereichs [pos, pos+anz[ aus dem aufrufenden String enthlt. Sollte pos auerhalb des gltigen Bereichs

309

Die STL

liegen, wird eine out_of_range-Exception geworfen. substr bentigt eine Laufzeit von O(n), wobei n fr die Gre des erzeugten Strings steht.
swap zwei Strings vertauschen
void swap(MeinTyp& s);

Vertauscht zwei Strings. swap besitzt eine Laufzeit von O(1).

6.9

Adapter

Unter einem Adapter, auch Sequenz-Adapter oder Container-Adapter genannt, versteht man die Implementierung eines abstrakten Datentypen, der als implizite Datenstruktur einen STL-Container verwendet. Der Adapter kapselt dabei die Funktionalitt des verwendeten Containers und lsst nur den Zugriff auf die vom ADT bentigten Funktionalitten zu. Diese Funktionen heien dann im Allgemeinen auch anders als die entsprechenden Container-Versionen.

6.9.1

Stacks

Ein Stack ist eine sogenannte LIFO-Struktur. LIFO ist die Abkrzung fr Last In First Out, was soviel wie als Letzer rein, als Erster raus bedeutet. Die Funktionsweise eines Stacks ist die Arbeitsweise, die dem Finanzamt hufig unterstellt wird: Alle Akten stapeln sich und dann werden sie von oben nach unten abgearbeitet. Daher auch die These, dass derjenige, der seine Steuererklrung als Letzter abgibt, sie als Erster zurckbekommt. Wie gesagt, es ist nur eine These. Abbildung 6.23 zeigt die vom Adapter Stack zur Verfgung gestellten Funktionen und ihre Auswirkungen:
Wachstumsrichtung

27

19

-4

55

73

59

27

19

-4

55

73

59

33

push(33)

27

19

-4

55

73

59

33

top()

27

19

-4

55

73

59

pop()

Abbildung 6.23 Die Methoden der Stacks

Die Methode push hngt ein Element an den Stack an.

310

Adapter

6.9

ber top kann das letzte Element (auch als oberstes Element bezeichnet) ausgelesen werden, ohne es jedoch aus dem Stack zu entfernen. Die Methode pop schlielich entfernt das oberste Element des Stacks (also dasjenige Element, welches zuletzt eingefgt wurde), allerdings ohne es zurckzuliefern. Wie bereits erwhnt, kapseln Adapter die Funktionalitt von STL-Containern. Abbildung 6.24 zeigt grafisch dargestellt die Funktionen, die der Stack von einem Container bentigt.
push()

Container

push_back()

pop_back()

pop()

back()

top()

Abbildung 6.24 Die Kapselung des Containers durch stack

Daher kann der Stack-Adapter jeden Container, der die Funktionen push_ back, pop_back und back besitzt, fr seine Zwecke verwenden. Die Definition von stack sieht wie folgt aus:
template<typename Typ, class Container=deque<Typ> > class std::stack {...};

Die Stack-Definition finden Sie in der Header-Datei stack. Wird explizit kein zu verwendender Container angegeben, dann wird eine Deque verwendet. Innerhalb von stack werden einige wenige Datentypen definiert, die wiederum die Datentypen des verwendeten Containers als Grundlage haben. Tabelle 6.23 listet sie auf.
Datentyp
value_type allocator_type size_type

Beschreibung Typ der zu verwaltenden Elemente Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder Kapazitt

Tabelle 6.23 Die Datentypen von stack

311

Die STL

Darber hinaus besitzt der Stack ein geschtztes Attribut container, welches den verwendeten Container darstellt.

6.9.2

Konstruktoren

Der Stack besitzt einen Konstruktor:


explicit stack(const Container &c=Container()) : container(c) {}

Dieser Konstruktor deckt gleichzeitig den Standardkonstruktor und den Kopierkonstruktor ab.

6.9.3

Operatoren

Der Stack besitzt folgende Operatoren:


operator=
stack<Typ, Container> &operator=(const stack<Typ, Container> &s) { container=s.container; }

Vergleichsoperatoren

Fr die Vergleichsoperatoren werden hier wieder exemplarisch operator< und operator== aufgefhrt:
template<typename Typ, typename Container> bool operator<(const stack<Typ, Container> &s1, const stack<Typ, Container> &s2) { return (s1.c < s2.c); } template<typename Typ, typenameclass Container> bool operator==(const stack<Typ, Container> &s1, const stack<Typ, Container> &s2) { return (s1.c == s2.c); }

312

Adapter

6.9

6.9.4

Methoden

Im Folgenden sind die Methoden des Stacks aufgefhrt.


Methode
empty push pop size top

Kurzbeschreibung berprft, ob der Stack Elemente enthlt. Legt ein Element auf den Stack. Lscht das oberste Element des Stacks. Liefert die Anzahl der gespeicherten Elemente. Gibt eine Referenz auf das oberste Element zurck.

Tabelle 6.24 Die Methoden des Stacks

empty Stack leer?


bool empty() const;

Gibt Auskunft darber, ob ein Stack augenblicklich Elemente enthlt oder nicht.
push Element anhngen
void push(const value_type &obj);

Hngt eine Kopie des Elements obj an einen Stack an.


pop letztes Element entfernen
void pop(void);

Lscht das letzte Element des Stacks.


size Anzahl der Elemente im Stack
size_type size() const;

Liefert die Anzahl der augenblicklich im Stack gespeicherten Elemente.


top
value_type &top(); const value_type &top() const;

Liefert eine Referenz auf das zuletzt im Stack gespeicherte Element.

313

Die STL

6.9.5

Queues

Im Gegensatz zu Stacks handelt es sich bei Queues um eine FIFO-Struktur. FIFO ist die Abkrzung fr First In First Out, was soviel wie als Erster rein, als Erster raus bedeutet. Auch die FIFO-Struktur ist Behrden sehr vertraut. Da wre z. B. das Prozedere beim Beantragen eines neuen Personalausweises zu nennen. Sie betreten das Einwohnermeldeamt und mssen eine Nummer ziehen. Der rger ist gro, wenn die eigene Nummer 582 betrgt, die augenblickliche aber gerade mal 311. Die Queue verkrpert das Motto: Wer zuerst kommt, mahlt zuerst. Abbildung 6.25 zeigt die vom Adapter Queue zur Verfgung gestellten Funktionen und ihre Auswirkungen:
Wachstumsrichtung

27

19

-4

55

73

59

27

19

-4

55

73

59

33

push(33)

27

19

-4

55

73

59

33

pop()

27

19

-4

55

73

59

33

back()

27

19

-4

55

73

59

33

front()

Abbildung 6.25 Die Methoden einer Queue

Die Methode push hngt ein Element an die Queue an. Die Methode pop entfernt das erste Element der Queue (also dasjenige Element, welches von den vorhandenen Elementen als Erstes eingefgt wurde), allerdings ohne es zurckzuliefern. ber back kann das Element ausgelesen werden, welches als Letztes eingefgt wurde, ohne es jedoch aus der Queue zu entfernen. Und front liest das Element aus, welches als Erstes eingefgt wurde. Auch front entfernt das Element nicht aus der Queue. Die Kapselung der Container-Funktionen sieht bei einer Queue aus, wie in Abbildung 6.26 dargestellt. Daher kann der Queue-Adapter jeden Container, der die Funktionen push_ back, pop_front, front und back besitzt, fr seine Zwecke verwenden.

314

Adapter

6.9

pop()

push()

pop_front()

push_back()

Container

front()

back()

front()

back()

Abbildung 6.26

Die Kapselung des Containers durch queue

Die Definition von queue sieht wie folgt aus:


template<typename Typ, typename Container=deque<Typ> > class std::queue {...};
Listing 6.18 Die Definition von queue

Die Queue-Definition finden Sie in der Header-Datei queue. Wird kein zu verwendender Container explizit angegeben, dann wird eine Deque benutzt. Innerhalb von queue werden dieselben Datentypen wie auch bei stack verwendet. Tabelle 6.25 zeigt sie.
Datentyp
value_type allocator_type size_type

Beschreibung Typ der zu verwaltenden Elemente Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder Kapazitt

Tabelle 6.25 Die Datentypen von queue

Auch die Queue besitzt ein geschtztes Attribut container, welches den verwendeten Container darstellt.

6.9.6

Konstruktoren

Die Queue besitzt einen Konstruktor, der auch als Standard- oder Kopierkonstruktor verwendet werden kann:

315

Die STL

explicit queue(const Container &c=Container()) : container(c) {}

6.9.7

Operatoren

Die Operatoren hneln denen des Stacks, weil sie lediglich auf die Operatoren des Containers zurckgreifen.
operator=
queue<Typ, Container> &operator=(const queue<Typ, Container> &s) { container=s.container; }

Vergleichsoperatoren

Exemplarisch werden hier die Operatoren < und == aufgefhrt:


template<typename Typ, typename Container> bool operator<(const queue<Typ, Container> &s1, const queue<Typ, Container> &s2) { return (s1.c < s2.c); } template<typename Typ, typename Container> bool operator==(const stack<Typ, Container> &s1, const stack<Typ, Container> &s2) { return (s1.c == s2.c); }

6.9.8

Methoden

Tabelle 6.26 gibt einen berblick.


Methode
back empty front push pop size

Kurzbeschreibung Gibt eine Referenz auf das letzte Element der Queue zurck. berprft, ob die Priority-Queue Elemente enthlt. Gibt eine Referenz auf das erste Element zurck. Hngt ein Element an die Queue an. Lscht das erste Element der Queue. Liefert die Anzahl der gespeicherten Elemente.

Tabelle 6.26 Die Methoden von queue

316

Adapter

6.9

back letztes Element


value_type &back(); const value_type &back() const;

Liefert eine Referenz auf das letzte Element in der Queue.


empty Queue leer?
bool empty() const;

Gibt Auskunft darber, ob eine Queue augenblicklich Elemente enthlt oder nicht.
front erstes Element
value_type &front(); const value_type &front() const;

Liefert eine Referenz auf das erste Element in der Queue.


push Element anhngen
void push(const value_type &obj);

Hngt eine Kopie des Elements obj an eine Queue an.


pop Element vorne entfernen
void pop(void);

Lscht das erste Element der Queue.


size Anzahl der Elemente in der Queue
size_type size() const;

Liefert die Anzahl der augenblicklich in der Queue gespeicherten Elemente.

6.9.9

Priority-Queues

Eine Priority-Queue, oder auch Prioritts-Warteschlange genannt, besitzt eine Element-Reihenfolge, die nicht mehr davon abhngt, wann ein Element in den ADT eingefgt wurde. Jedes Element besitzt eine gewisse Prioritt, anhand derer bestimmt wird, welches Element als Nchstes entfernt wird. Ein Beispiel fr eine Priority-Queue ist ein Arzt, der seinen Patienten Ter-

317

Die STL

mine gibt. Egal, in welcher Reihenfolge die Patienten das Wartezimmer betreten, die Behandlungsreihenfolge ist durch die Uhrzeit der Termine bestimmt. Abbildung 6.27 zeigt die vom Adapter priority_queue zur Verfgung gestellten Funktionen.
push() pop() top()

Container
Abbildung 6.27 Die Methoden der Priority-Queue

Bei einer Priority-Queue kann keine eindeutige Zuordnung ihrer Funktionen zu den Funktionen oder Elementen des Containers erfolgen, da diese stark von der Implementation abhngen: Die Methode push fgt ein Element in die Priority-Queue ein. ber top kann das Element mit der hchsten Prioritt angesprochen werden, ohne es jedoch aus der Priority-Queue zu entfernen. Die Methode pop schlielich entfernt das Element mit der hchsten Prioritt aus der Priority-Queue, allerdings ohne es zurckzuliefern. Die Definition von priority_queue sieht wie folgt aus:
template<typename Typ, typename Container=vector<Typ>, typename Vergleich=less<Container::value_type> > class std::priority_queue {...};

Die Definitionen der Priority-Queue finden Sie in der Header-Datei queue. Wird explizit kein zu verwendender Container angegeben, dann wird ein Vektor verwendet. Als Datentypen existieren wieder die blichen drei Verdchtigen, zu sehen in Tabelle 6.27.
Datentyp
value_type allocator_type size_type

Beschreibung Typ der zu verwaltenden Elemente Allokator-Typ Grentyp, fr Angaben ber aktuelle Gre, maximale Gre oder Kapazitt

Tabelle 6.27 Die Datentypen von priority_queue

318

Adapter

6.9

6.9.10 Konstruktoren
Die Konstruktoren der Priority-Queue sehen folgendermaen aus:
explicit priority_queue(const Vergleich &v=Vergleich(), const allocator_type &a=allocator_type());

Dieser Konstruktor erzeugt eine leere Priority-Queue, der optional eine Vergleichsfunktion und ein Allokator-Objekt bergeben werden kann.
priority_queue(CInput anf, CInput end, const const Vergleich &v=Vergleich(), const allocator_type &a=allocator_type());

Dieser Konstruktor erzeugt eine Priority-Queue, die mit Kopien der Elemente [anf, end[ gefllt wird. Optional knnen eine Vergleichsfunktion und ein Allokator-Objekt bergeben werden.

6.9.11 Methoden
Im Folgenden sind die Methoden der Priority-Queue aufgefhrt. Tabelle 6.28 gibt einen berblick.
Methode
empty push pop size top

Kurzbeschreibung berprft, ob die Priority-Queue Elemente enthlt. Fgt ein Element in die Priority-Queue ein. Lscht das Element mit der hchsten Prioritt aus der Priority-Queue. Liefert die Anzahl der gespeicherten Elemente. Gibt eine Referenz auf das Element mit der hchsten Prioritt zurck.

Tabelle 6.28 Die Methoden von priority_queue

empty Priority-Queue leer?


bool empty() const;

Gibt Auskunft darber, ob eine Priority-Queue augenblicklich Elemente enthlt oder nicht.
push Element einfgen
void push(const value_type &obj);

Fgt eine Kopie des Elemente obj in eine Priority-Queue ein.

319

Die STL

pop Element entfernen


void pop(void);

Entfernt das Element mit der hchsten Prioritt aus der Priority-Queue.
size Anzahl der Elemente in der Priority-Queue
size_type size() const;

Liefert die Anzahl der augenblicklich in der Priority-Queue gespeicherten Elemente zurck.
top Element ansprechen
value_type &top(); const value_type &top() const;

Liefert eine Referenz auf das Element mit der hchsten Prioritt.

6.10

Iteratoren

In diesem Abschnitt soll genauer beleuchtet werden, wie ein Iterator funktioniert, welche Anforderungen an ihn gestellt werden und welche Arten von Iteratoren es gibt. Wie schon zu Beginn des Themas STL angedeutet, sind Itratoren das Bindeglied zwischen den STL-Containern und STL-Algorithmen.
Hinweis Iteratoren abstrahieren STL-Container zu einer Sequenz.

Als Konsequenz spielt es fr den Benutzer eines Iterators keine Rolle, ob der Container seine Elemente in einem Array, einer verketteten Liste oder einem Baum anordnet. Fr ihn gibt es nur den Anfang und das Ende der Elemente. Und auf dem Weg vom Anfang zum Ende luft der Iterator an jedem Element des Containers vorbei. Dabei sieht die abstrahierte Sequenz aus, wie in Abbildung 6.28 dargestellt.
begin end

Abbildung 6.28 Eine durch Iteratoren abstrahierte Sequenz

320

Iteratoren

6.10

Dass der das Ende der Sequenz markierende Iterator nicht auf das letzte Element, sondern hinter das letzte Element zeigt, ist der Grund dafr, warum bei der Angabe von Bereichen die rechte Grenze immer ausgeschlossen ist. Der Bereich in Abbildung 6.28 ist daher [begin, end[. Sehen wir uns einmal ein Beispiel an, welches alle Elemente eines Sets mithilfe eines Iterators ausgibt:
set<char> s; /* Hier muss etwas ins Set geschrieben werden */ set<char>::iterator i; for(i=s.begin(); i!=s.end(); ++i) cout << *i;
Listing 6.19 Ausgabe eines Sets mit Iteratoren

Zuerst wird eine Instanz des im Set definierten Iterators erzeugt. In der Schleife wird diesem Iterator dann der Wert, den die Funktion begin zurckliefert also ein Iterator auf das erste Element des Sets zugewiesen. ber den Inkrementoperator wird unser Iterator so lange erhht, bis er die Position hinter dem letzten Element des Sets erreicht hat. Eine Besonderheit ist hier die Tatsache, dass der Prinkrementoperator und nicht der Postinkrementoperator verwendet wurde. Warum dies der Fall ist, besprechen wir noch eingehender, wenn wir uns spter mit der Implementation eines Iterators beschftigen.

6.10.1 Iteratorkategorien
Betrachten wir die Fhigkeiten der Iteratoren etwas genauer. Tabelle 6.29 zeigt die von den einzelnen Iteratorkategorien untersttzten Operatoren.
output
++ lesen schreiben -> ==, !=

input X X

forward X X X

bidirectional X X X X X

random-access X X X X X

X X X

X X

Tabelle 6.29 Die Iteratorkategorien

321

Die STL

output
-+, -, +=, -= <, >, <=, >= [ ]

input

forward

bidirectional X

random-access X X X X

Tabelle 6.29 Die Iteratorkategorien (Forts.)

Wenn man die Tabelle eingehender betrachtet, dann fllt auf, dass beispielsweise ein Forward-Iterator ein um bestimmte Operatoren erweiterter InputIterator ist. Genau wie ein Bidirectional-Iterator ein erweiterter Forward-Iterator ist. Ein Algorithmus, der Forward-Iteratoren bentigt, kann daher auch mit Bidirectional-Iteratoren arbeiten. Um diesen Umstand auszudrcken, bentigen wir Polymorphismus, der durch Vererbung erzeugt wird. Abbildung 6.29 zeigt die vererbungstechnischen Zusammenhnge.
Output-Iterator Input-Iterator

Forward-Iterator

Bidirectional-Iterator

Random-Access-Iterator

Abbildung 6.29 Die Beziehungen der Iteratoren untereinander

Obwohl der Forward-Iterator rein von der Funktionalitt her sowohl vom Input-Iterator als auch vom Output-Iterator erben msste, ist dem nicht so. Er erbt lediglich vom Input-Iterator.

6.10.2 Iterator-Tags
Tatschlich stehen die Iteratoren selbst in keinem hierarchischen Verhltnis. Um aber trotzdem von den Vorteilen einer Hierarchie programmtechnisch Gebrauch machen zu knnen, wurden sogenannte Iterator-Tags (auf Deutsch etwa Iteratorkennzeichen) definiert, die untereinander die oben beschriebene Beziehung besitzen:
struct input_iterator_tag {};

322

Iteratoren

6.10

struct output_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag {}; struct bidirectional_iterator_tag : public forward_iterator_tag {}; struct random_access_iterator_tag : public bidirectional_iterator_tag {};
Listing 6.20 Die Iterator-Tags

6.10.3 Iterator-Traits
Um mit einem Iterator arbeiten zu knnen, muss er bestimmte Datentypen definieren. Zum Beispiel muss er mitteilen knnen, zu welcher Iteratorkategorie er gehrt oder von welchem Datentyp die Elemente sind, auf die er zeigt usw. Damit dies mglichst einheitlich geschieht und nicht jeder Programmierer eines Iterators in Wild-West-Manier Datentypen definiert, gibt es die sogenannten Iterator-Traits, was auf Deutsch soviel wie Iteratoreigenschaften heit.
template <typename Iterator> struct iterator_traits { typedef typename Iterator::iterator_category iterator_category; typedef typename Iterator::value_type value_type; typedef typename Iterator::difference_type difference_type; typedef typename Iterator::pointer pointer; typedef typename Iterator::reference reference; };
Listing 6.21 Die Iterator-Traits

Die Erklrungen dieser Datentypen finden Sie in Tabelle 6.30.

323

Die STL

Datentyp
iterator_category

Beschreibung Iteratorkategorie. Dieser Datentyp entspricht dem zum Iterator gehrenden iterator_tag. Datentyp der Elemente, auf die der Iterator zeigt Datentyp, mit dem Abstnde zwischen Iteratoren ausgedrckt werden Datentyp des Rckgabewerts der operator->-Funktion Datentyp des Rckgabewerts der operator*-Funktion

value_type difference_type

pointer reference

Tabelle 6.30 Die Datentypen von iterator_traits

Mithilfe dieses Templates knnen fr jeden implementierten Iterator die dazugehrigen iterator_traits erzeugt werden. Da die in den iterator_ traits definierten Datentypen die Datentypen des Iterators selbst als Grundlage verwenden, funktioniert dieses Template auch mit selbst programmierten Iteratoren. Allerdings nur unter folgender Voraussetzung:
Hinweis Damit ein selbst programmierter Iterator STL-konform ist, muss er alle in Tabelle 6.30 aufgefhrten Datentypen definieren.

Ein Sonderfall muss jedoch bercksichtigt werden. Wie schon des fteren erwhnt wurde, sind Iteratoren in gewisser Weise abstrakte Zeiger. Andererseits knnen auch die elementaren Zeiger als Iteratoren verwendet werden (beispielsweise verwendet der vector-Container elementare Zeiger als Iteratoren). Da ein elementarer Zeiger aber keine Klasse besitzt, kann er auch nicht die notwendigen Datentypen angeben, aus denen dann die iterator_ traits erzeugt werden knnten. Damit die elementaren Zeiger dennoch als Iteratoren fungieren knnen, mssen wir fr sie spezielle definieren:
template <typename Typ> struct iterator_traits<Typ*> { typedef random_access_iterator_tag iterator_category; typedef Typ value_type; typedef ptrdiff_t difference_type; typedef Typ* pointer; typedef Typ& reference; };
Listing 6.22 iterator_traits fr Zeigertypen

324

Iteratoren

6.10

Ein auf elementaren Zeigern basierender Iterator zhlt wie aus den iterator_traits ersichtlich zu den Random-Access-Iteratoren. Der Datentyp ptrdiff_t ist ein bereits in C-Tagen fr Abstnde verwendeter Datentyp, der in der Header-Datei cstddef definiert ist.
Output-Iterator

Der Output-Iterator ist wohl die primitivste Iteratorkategorie. Er kann nur Daten schreiben und mit dem ++-Operator das nchste Element auswhlen. Er wird hauptschlich dazu verwendet, Daten in einen Ausgabe-Strom zu schreiben, und findet eine Anwendung im sogenannten Ostream-Iterator.
Achtung Ein Output-Iterator kann ber einen Bereich nur einmal iterieren.

Input-Iterator

Der Input-Iterator ist gewissermaen das Gegenstck zum Output-Iterator, kann aber dabei noch etwas mehr. Bezogen auf den Elementzugriff kann der Input-Iterator die Elemente nur lesen. Er besitzt aber die Vergleichsoperatoren == und != und kann damit das Ende eines Bereichs feststellen. Input-Iteratoren sind beispielsweise die Istream-Iteratoren, die spter noch besprochen werden.
Achtung Ein Input-Iterator kann ber einen Bereich nur einmal iterieren.

Forward-Iterator

Der Forward-Iterator vereint die Funktionalitt von Input- und Output-Iterator (obwohl das Forward-Iterator-Tag nur vom Input-Iterator-Tag abgeleitet ist). Er kann also sowohl Elemente lesen als auch beschreiben.
Tipp Von einem Forward-Iterator kann eine Kopie erzeugt werden.

Aus diesem Grund kann ein Forward-Iterator einen Bereich auch mehrmals durchlaufen; es wird einfach eine Kopie der Anfangsposition angelegt.
Tipp Der Forward-Iterator besitzt eine weitere Besonderheit: Von einem Forward-Iterator kann eine nicht initialisierte Kopie erzeugt werden.

325

Die STL

Bidirectional-Iterator

Unter einem Bidrectional-Iterator kann man sich einen Forward-Iterator vorstellen, der auch rckwrts also mit operator-- ber einen Bereich iterieren kann. Beispiele fr Bidirectional-Iteratoren sind die Iteratoren der Listen und Bume.
Random-Access-Iterator

Die leistungsfhigste Kategorie ist der Random-Access-Iterator, der verglichen mit dem Bidirectional-Iterator um den Indexoperator und die Vergleichsoperatoren <, <=, >= und > erweitert wurde. Ein Beispiel fr RandomAccess-Iteratoren sind die Iteratoren der Vektoren und der Deques. Hufig knnen Random-Access-Iteratoren mit elementaren Zeigern umgesetzt werden. Manchmal ist die Struktur jedoch wie bei der Deque etwas komplizierter, so dass eine eigene Iteratorklasse implementiert werden muss.

6.11

Reverse-Iteratoren

Ein Reverse-Iterator iteriert einen Container in umgekehrter Richtung. Sie knnten natrlich einwenden, dass ein Container auch mit normalen Iteratoren rckwrts iteriert werden kann, in der Schleife muss anstelle des ++-Operators lediglich der Operator verwendet werden. Das stimmt zwar, allerdings hat man bei der Verwendung von Fremdimplementationen (wie z. B. den Algorithmen der STL) keine Mglichkeit, die Schleifen zu ndern, und muss daher auf Reverse-Iteratoren zurckgreifen. Da ein Reverse-Iterator in die andere Richtung luft, muss es auch zwei Richtungen geben.
Achtung Um Reverse-Iteratoren verwenden zu knnen, muss der Container mindestens mit Bidirectional-Iteratoren ausgestattet sein.

Bei den Containern haben wir bereits die beiden wesentlichen Methoden zur Erzeugung von Reverse-Iteratoren kennengelernt, rbegin und rend. Abbildung 6.30 zeigt die von diesen Funktionen zurckgelieferten Positionen.

326

Reverse-Iteratoren

6.11

rend()

rbegin()

10

Abbildung 6.30

Die Positionen von rbegin und rend

Nun sind Reverse-Iteratoren ein von der STL zur Verfgung gestelltes Konzept. Aus diesem Grund muss ein STL-konformer Container keine eigenen Reverse-Iteratoren implementieren. Der Reverse-Iterator greift von auen auf die entsprechenden Elemente des Containers zu und muss daher genau wie die Algorithmen die Iteratoren des Containers verwenden.
Hinweis Ein Reverse-Iterator verwendet die Iteratoren des Containers fr den Zugriff.

Aber es gibt ein kleines Problem: Ohne die Iterationsreihenfolge zu betrachten, liegt der Reverse-Iteratorbereich [rbegin, rend[ mit normalen Iteratoren abgebildet im Bereich ]begin-1, end-1]. Der von rend zurckgelieferte Reverse-Iterator besitzt damit eine Position, die mit herkmmlichen Iteratoren nicht darstellbar ist, nmlich die Position vor dem ersten Element. Und doch kann der Reverse-Iterator nur ber eben diese herkmmlichen Iteratoren auf den Container zugreifen. Die Lsung dieses Dilemmas ist in Abbildung 6.31 dargestellt.
Position des ReverseIterators Position des internen Iterators

Abbildung 6.31

Die interne und ffentliche Iteratorposition

Die Position, die der Reverse-Iterator nach auen hin reprsentiert, ist in Wirklichkeit die um ein Element vorverlegte Position eines normalen Iterators. Anders herum ausgedrckt: Die interne Iteratorposition liegt immer ein Element hinter dem vom Reverse-Iterator angesprochenen Element. Abbildung 6.32 zeigt die internen Iteratorpositionen fr die von rbegin und rend zurckgelieferten Reverse-Iteratoren.

327

Die STL

rend()

rbegin()

10

Abbildung 6.32

Die internen Positionen von rbegin und rend

Man erkennt, dass die internen Positionen von rbegin und rend genau mit den Positionen von end und begin bereinstimmen.

6.11.1

Insert-Iteratoren

Wenn wir mit Iteratoren arbeiten, dann knnen wir die Elemente, auf die Iteratoren zeigen, lesen und beschreiben. Wir knnen allerdings keine Elemente einfgen. Nehmen wir als Beispiel einen Kopiervorgang, bei dem der Bereich [anf, end[an die Position pos kopiert werden soll. Der entsprechende copy-Aufruf sieht wie folgt aus:
copy(anf, end, pos);

Abbildung 6.33 stellt das Geschehen grafisch dar.


anf a b c d pos 1 2 3 4 5 6 7 8 9 e end f g h i j k

anf a b c d e

end f g h pos i j k

b
1 2 3 b c d e

Abbildung 6.33

Der Kopiervorgang mit normalen Iteratoren

Der Bereich [anf, end[ umfasst vier Elemente. Daher werden die vier ab Position pos liegenden Elemente von den Elementen des Bereichs [anf, end[ berschrieben. copy liefert die Position hinter dem zuletzt berschriebenen Element zurck. Hufig ist es jedoch wnschenswert, dass die Elemente nicht den Zielbereich berschreiben, sondern an die Stelle pos eingefgt werden. Aus diesem

328

Reverse-Iteratoren

6.11

Grund gibt es die sogenannten Insert-Iteratoren. Genau genommen existieren drei Kategorien von Insertern (so werden Insert-Iteratoren auch genannt), Front-Inserter, Back-Inserter und Inserter. Wir werden uns diese drei Iteratoren im Folgenden genauer ansehen.
Front-Insert-Iterator

Ein Front-Inserter fgt Daten am Anfang des Containers ein. Seine Definition sieht wie folgt aus:
front_insert_iterator<Container<Typ> >(Container<Typ> &c);

Wenn wir einen Front-Inserter fr eine Deque definieren mchten, die char-Elemente beinhaltet, dann she das so aus:
deque<char> d; front_insert_iterator<deque<char> >(d);

Das Einfgen von Elementen einer deque<char> d1 an den Anfang einer anderen deque<char> d2 mithilfe von copy wird folgendermaen angegeben:
copy(d1.begin(), d1.end(), front_insert_iterator<deque<char> >(d2));

Der Vorgang des Einfgens eines Bereichs an den Anfang eines Containers durch einen Front-Insert-Iterator ist in Abbildung 6.34 dargestellt.
anf a b 2 c 3 d 4 e 5 end f 6 g 7 h 8 i 9 j k

a
1

anf a b d c c d b e 1

end f 2 g 3 h 4 i 5 j 6 k 7 8 9

b
e

Abbildung 6.34

Der Kopiervorgang mit einem Front-Inserter

Die Fragen, warum die Elemente in umgekehrter Reihenfolge in den Container eingefgt wurden und wie der Front-Inserter berhaupt Elemente am Anfang eines Containers einfgt, lassen sich zusammen beantworten.

329

Die STL

Hinweis Der Front-Insert-Iterator verwendet zum Einfgen die push_front-Methode des Containers.

Als Konsequenz knnen Front-Inserter nur mit Containern arbeiten, die die Methode push_front zur Verfgung stellen.

6.11.2

Back-Insert-Iterator

Der Back-Inserter funktioniert analog zum Front-Inserter, nur dass er Elemente am Ende eines Containers anhngt. Abbildung 6.35 zeigt einen solchen Vorgang.
anf a b 2 c 3 d 4 e 5 end f 6 g 7 h 8 i 9 j k

a
1

anf a b 2 c 3 d 4 e 5

end f 6 g 7 h 8 i 9 j b k c d e

b
1

Abbildung 6.35

Der Kopiervorgang mit einem Back-Inserter

Aufgrund der Natur des Anhngens haben die Elemente die gleiche Reihenfolge wie im ursprnglichen Bereich.
Hinweis Der Back-Insert-Iterator verwendet zum Einfgen die push_back-Methode des Containers.

6.11.3 Insert-Iterator
Der Insert-Iterator ist die allgemeinste Form der Inserter, denn man kann die Einfgeposition selbst bestimmen. Abbildung 6.36 zeigt einen solchen Einfgevorgang an der beliebigen Position pos:

330

Reverse-Iteratoren

6.11

anf a b c d pos 1 2 3 4 5 e

end f g h i j k

anf a b c d e

end f g h pos i j k

b
1 2 3 b c d e

Abbildung 6.36

Der Kopiervorgang mit einem Inserter

Um die Elemente einfgen zu knnen, bentigt der Insert-Iterator die insert-Funktion des Containers in der Form insert(iterator,objekt).
Hinweis Der Insert-Iterator verwendet zum Einfgen die insert-Methode des Containers.

Wenn Sie sich die Funktionsweise der insert-Funktion genauer anschauen, so fllt Ihnen sicher auf, dass sie die Position des eingefgten Elements zurckliefert. Fgten Sie an dieser Stelle das zweite Element ein, dann stnde es im Container vor dem ersten eingefgten Element. Abbildung 6.36 zeigt jedoch, dass die eingefgten Elemente im Container die gleiche Reihenfolge besitzen wie im Ursprungsbereich. Der Insert-Iterator sorgt also fr den Erhalt der ursprnglichen Reihenfolge.

6.11.4 Stream-Iteratoren
Stream-Iteratoren wurden ins Leben gerufen, um eine Verbindung zwischen dem Stream-Konzept und dem Container-Konzept der STL herzustellen. Dabei bieten diese Iteratoren die Mglichkeit, Daten in einen Strom zu schreiben oder aus einem Strom zu lesen.
Ostream-Iterator

Der erste Stream-Iterator, mit dem wir uns beschftigen wollen, ist der Ostream-Iterator, der Daten in einen Ausgabestrom schreiben kann. Die Definition eines solchen Iterators sieht wie folgt aus:

331

Die STL

ostream_iterator<char> oi1(cout);

Der Konstruktor des Iterators bekommt dabei den Ausgabestrom, in den der Iterator die Elemente schreiben soll, bergeben. Im obigen Beispiel wurde dazu der Standard-Ausgabestrom cout gewhlt. Mithilfe des copyAlgorithmus knnen so Elemente eines Bereichs bequem ausgegeben werden:
copy(v2.begin(), v2.end(),oi2);

Dabei werden die Elemente direkt hintereinander ausgegeben. Es ist mglich, zustzlich eine Zeichenkette anzugeben, die als Trennung zwischen den Elementen ausgegeben wird und diese dadurch abgrenzt:
ostream_iterator<char> oi2(std::cout,"\n");

Hier wurde als Trenner \n gewhlt, damit jedes Element in einer neuen Zeile ausgegeben wird.
Istream-Iterator

Der Istream-Iterator operiert genau wie der Ostream-Iterator auf Streams. Dabei liest der Istream-Iterator Daten aus einem Stream ein. Das Template definiert den einzulesenden Datentypen, ber den Konstruktor des IstreamIterators wird der zu verwendende Eingabestrom als Parameter bergeben:
istream_iterator<int> lesen(cin);

Wird der Parameter im Konstruktor weggelassen, dann ist der Iterator ungltig:
istream_iterator<int> ende;

Ein ungltiger Iterator wird dazu benutzt, um die Eingabe zu terminieren. Ein Beispiel:
list<int> li; back_insert_iterator<list<int> > bi(li); for(;lesen!=ende; ++lesen) bi=*lesen; copy(li.begin(), li.end(), ostream_iterator<int>(cout,"\n"));

Eine Liste wird mit den vom Istream-Iterator eingelesenen Elementen beschrieben. Die einlesende Schleife wird dann abgebrochen, wenn lesen ungltig wird, denn dann ist lesen==ende. Die anschlieende copy-Anwei-

332

Reverse-Iteratoren

6.11

sung gibt die in der Liste gespeicherten Elemente aus. Bei der Eingabe ist es wichtig, dass Sie zum Schluss etwas Ungltiges eingeben, damit Sie die Schleife terminieren, z. B. 23 42 88 g. Weil g ein fr den Datentypen int ungltiger Wert ist, wird auch der Istream-Iterator ungltig. Deswegen bricht die Schleife ab.

6.11.5 Methoden fr Iteratoren


Um mit Iteratoren besser arbeiten zu knnen, wurden zwei globale Funktionen definiert, mit denen Iteratorpositionen verndert (advance) oder Abstnde zwischen Iteratorpositionen ermittelt werden knnen (distance). Dabei werden wir uns auch anschauen, wie eine Funktion fr verschiedene Iteratorkategorien berladen werden kann, um entsprechend den Fhigkeiten der jeweiligen Kategorie eine optimale Laufzeit zu erhalten.
advance

Die Funktion advance verndert die Iteratorposition. Bei einem positiven Offset wird der Iterator nach vorne bewegt und bei einem negativen Offset nach hinten (nur ab Bidirectional-Iteratoren). Die Grundfunktion, die fr jede Iteratorkategorie aufgerufen wird, besitzt zwei Parameter, wobei der erste Parameter der Iterator und der zweite Parameter der Offset ist.
template<typename Iterator, typename Dist> inline void advance(Iterator &i, Dist d) { advance(i,d, iterator_traits<Iterator>::iterator_category()); }

Diese zweiparametrige advance-Funktion ruft jetzt eine dreiparametrige advance-Funktion auf, die als dritten Parameter die Iteratorkategorie besitzt. Dieser dritte Parameter ermglicht es, fr jede Iteratorkategorie eine spezielle advance-Funktion zu berladen. Nehmen wir als erstes Beispiel die Funktion fr Input- und Forward-Iteratoren:
template<typename Iterator, typename Dist> inline void advance(Iterator &i, Dist d, input_iterator_tag) { while(d-- > 0) ++i; }

Da Input-Iteratoren nur die Inkrementierung untersttzen, darf bei einem negativen Offset nichts geschehen. Um die Unterschiede zu demonstrieren, hier noch die Version fr Random-Access-Iteratoren:

333

Die STL

template<typename Iterator, typename Dist> inline void advance(Iterator &i, Dist d, random_access_iterator_tag) { i=i+d; }

Die Funktion braucht den Iterator nicht mit einer Schleife zu verschieben, sondern macht sich den wahlfreien Zugriff der Random-Access-Iteratoren zunutze.
distance

Die distance-Funktion ermittelt den Abstand zweier Iteratoren. Dabei muss der Iterator, der als erster Parameter angegeben wird, vor demjenigen Iterator liegen, der als zweiter Parameter aufgefhrt wird. Die distance-Funktion verfolgt den gleichen Ansatz wie advance, zuerst eine zweiparametrige Funktion aufrufen, die dann anhand der Iteratorkategorie die tatschliche dreiparametrige Variante aufruft:
template<typename Iterator> inline typename iterator_traits<Iterator>::difference_type distance(Iterator i1, Iterator i2) { return(distance(i1,i2, iterator_traits<Iterator>::iterator_category())); }

6.12

Algorithmen

In diesem Abschnitt werden wir uns mit den von der STL bereitgestellten Algorithmen beschftigen, mit deren Hilfe Manipulationen an Containern oder Suchen nach Elementen durchgefhrt werden knnen.

6.12.1

Sequenzlngen

Einige Algorithmen geben fr den ersten Bereich eine Anfangs- und eine Endposition an, aber fr den zweiten Bereich fordern sie nur eine Anfangsposition. Ein solcher Algorithmus ist z. B. equal, der zwei Sequenzen vergleicht und der mit equal(anf1, end1, anf2) aufgerufen wird.

334

Algorithmen

6.12

Alle Algorithmen, die die beiden Bereiche mit nur drei Positionen beschreiben, gehen davon aus, dass die beiden Sequenzen gleich lang sind. Die erste Sequenz ist der Bereich [anf1, end1[ und hat damit eine Lnge von end1-anf1. Diese Subtraktion wrde konkret nur bei Random-Access-Iteratoren funktionieren. Glcklicherweise brauchen wir sie hier nur zum Verstndnis. In der Praxis ist sie nicht notwendig. Die zweite Sequenz beginnt bei anf2. Da diese Sequenz genauso lang ist wie die erste, muss auch sie eine Lnge von end1anf1 besitzen. Die Endposition der zweiten Sequenz ist somit anf2+(end1anf1). Damit ist der zweite Bereich [anf2, anf2+(end1-anf1)[. Abbildung 6.37 verdeutlicht die Zusammenhnge noch einmal.
anf1
Container1: end1-anf1

end1

anf2
Container2: end1-anf1

anf2+(end1-anf1)

Abbildung 6.37

Die Lngen von Sequenzen

Das Ende des zweiten Bereichs als konkrete Position ist programmtechnisch allerdings uninteressant, weil beide Sequenzen gleich lang sind:
for(;anf1!=end1; ++anf1, ++anf2)

Dies ist eine fr viele Algorithmen typische Schleife. Sie iteriert ber beide Sequenzen, berprft das Ende aber nur anhand der ersten Sequenz. Da diese Algorithmen davon ausgehen, dass beide Sequenzen gleich lang sind, ist das legitim.

6.12.2 Iteratorzugriffe
Es ist nichts Neues, dass Algorithmen ber Iteratoren auf die Container zugreifen. Wir wollen uns an dieser Stelle aber einige Gedanken ber die Konsequenzen machen. Fr die Betrachtung kann man die Algorithmen grob in zwei Kategorien einteilen. Die eine Kategorie beinhaltet die Algorithmen, die einzelne Elemente oder mehrere Elemente eines Bereichs bearbeiten. Die zweite Kategorie kopiert einen Bereich von Elementen irgendwohin (ob die Elemente dabei modifiziert werden, spielt keine Rolle). Diese kopierenden Algorithmen definieren meist eine Quellsequenz [anf1, end1[ und eine Zielposition anf2. Je nachdem, um was fr einen Iterator es sich bei anf2 han-

335

Die STL

delt, knnen die unterschiedlichsten Ergebnisse erzielt und damit auch Probleme geschaffen werden.
Normaler Iterator auf Container

Unter einem normalen Iterator wollen wir einen Iterator verstehen, der weder Stream- noch Insert-Iterator ist. Sollte auf den Zielbereich mit einem normalen Iterator zugegriffen werden, dann berschreibt dieser im Zielcontainer insgesamt end1-anf1 Elemente ab Position anf2 mit den Elementen aus Bereich [anf1, end1[.
Hinweis Normale Iteratoren knnen keine Elemente einfgen.

Dabei ist es ganz wichtig, zu beachten, dass im Zielcontainer ab Position anf2 auch wirklich noch eine Anzahl von end1-anf1 Elementen vorhanden ist. Sollten Sie mit einem normalen Iterator die Containergrenze berschreiten, dann haben Sie ein Problem.
Insert-Iterator auf Container

Wenn Sie fr anf2 einen Insert-Iterator verwenden, dann brauchen Sie sich ber gengend Elemente keine Sorgen zu machen, denn es liegt ja in der Natur der Inserter, dass sie Elemente einfgen.
Stream-Iteratoren

Ein Stream-Iterator macht in der Regel auch keine Probleme. Sie mssen nur bercksichtigen, dass der Rckgabewert, der bei den Algorithmen, ber die wir hier sprechen, im Allgemeinen die Position hinter dem letzten berschriebenen oder eingefgten Element ist, bei Stream-Iteratoren eine andere Bedeutung hat.
Achtung Stream-Iteratoren haben in dem Sinne keine Positionen, deswegen wird einfach der Stream-Iterator selbst zurckgeliefert.

6.12.3 Ein alphabetischer berblick


Im Folgenden sind alle Algorithmen alphabetisch aufgefhrt:

336

Algorithmen

6.12

Algorithmus
adjacent_find binary_search copy copy_backward count count_if equal equal_range fill fill_n find find_end find_first_of

Beschreibung Sucht benachbarte Duplikate. Prft, ob ein Element in einem Bereich vorkommt. Kopiert eine Sequenz. Kopiert eine Sequenz, beginnend am Ende. Zhlt Elemente. Zhlt Elemente. Prft, ob die Elemente zweier Sequenzen gleich sind. Kombination von lower_bound und upper_bound Ersetzt Elemente eines Bereichs. Ersetzt eine Anzahl von Elementen. Sucht das erste Vorkommen eines Elements. Sucht das letzte Vorkommen einer Sequenz. Sucht nach Elementen, die in zwei Sequenzen vorhanden sind. Sucht das erste Vorkommen eines Elements. Ersetzt Elemente eines Bereichs durch das Funktionsergebnis. Ersetzt eine Anzahl von Elementen durch das Funktionsergebnis. Prft, ob eine Menge komplett in einer anderen enthalten ist. Verschmilzt zwei sortierte Sequenzen an Ort und Stelle. Vertauscht zwei an Iteratorpositionen befindliche Elemente. Vergleicht zwei Sequenzen lexikografisch. Ermittelt den Anfang eines Bereichs gleicher Elemente. Wandelt eine Sequenz in einen Heap um. Ermittelt das grere zweier Elemente. Ermittelt das grte Element einer Sequenz. Verschmilzt zwei sortierte Sequenzen. Ermittelt das kleinere zweier Elemente. Ermittelt das kleinste Element einer Sequenz.

find_if generate

generate_n

includes

inplace_merge iter_swap

lexicographical_compare lower_bound make_heap max max_element merge min min_element

Tabelle 6.31

Alle Algorithmen in alphabetischer Reihenfolge

337

Die STL

Algorithmus
mismatch

Beschreibung Bestimmt aus zwei Sequenzen die ersten Elemente, die sich unterscheiden. Bestimmt die nchste Permutation. partielle Sortierung einer Sequenz Kopiert den partiell sortierten Bereich einer Sequenz. Partitioniert eine Sequenz. Entfernt ein Element aus einem Heap. Bestimmt die vorherige Permutation. Fgt ein Element in einen Heap ein. Ordnet die Elemente einer Sequenz in zuflliger Reihenfolge an. Lscht bestimmte Elemente. Kopiert und lscht dabei bestimmte Elemente. Kopiert und lscht dabei bestimmte Elemente. Lscht bestimmte Elemente. Ersetzt bestimmte Elemente. Kopiert und ersetzt dabei bestimmte Elemente. Kopiert und ersetzt dabei bestimmte Elemente. Ersetzt bestimmte Elemente. Dreht die Elementreihenfolge einer Sequenz um. Kopiert Elemente in umgekehrter Reihenfolge. Rotiert Elemente einer Sequenz. Kopiert und rotiert dabei die Elemente einer Sequenz. Sucht das erste Vorkommen einer Sequenz. Sucht das erste Vorkommen einer Sequenz gleicher Elemente. Bildet die Differenz zweier Mengen. Bildet die Schnittmenge zweier Mengen.

next_permutation partial_sort partial_sort_copy partition pop_heap prev_permutation push_heap random_shuffle

remove remove_copy remove_copy_if remove_if replace replace_copy replace_copy_if replace_if reverse reverse_copy rotate rotate_copy search search_n

set_difference set_intersection

set_symmetric_difference Bildet die symmetrische Differenz zweier Mengen. set_union sort sort_heap

Bildet die Vereinigungsmenge zweier Mengen. Sortiert eine Sequenz. Wandelt einen Heap in eine sortierte Sequenz um.
Alle Algorithmen in alphabetischer Reihenfolge (Forts.)

Tabelle 6.31

338

Algorithmen

6.12

Algorithmus
stable_partition stable_sort swap swap_ranges unique unique_copy

Beschreibung Partitioniert eine Sequenz stabil. Sortiert eine Sequenz stabil. Vertauscht zwei Elemente. Vertauscht Elemente zweier Sequenzen. Lscht benachbarte Duplikate. Kopiert Elemente und lscht dabei benachbarte Duplikate. Ermittelt das Ende eines Bereichs gleicher Elemente.

upper_bound

Tabelle 6.31

Alle Algorithmen in alphabetischer Reihenfolge (Forts.)

Im weiteren Verlauf werden die Algorithmen entsprechend ihrer Wirkungsweise in logische Gruppen eingeteilt und dabei detailliert besprochen.

6.12.4 Nicht-modifizierende Algorithmen


Kommen wir zuerst zu den Algorithmen, die weder eine nderung an der Reihenfolge noch am Inhalt der Elemente vornehmen. Zu diesen Algorithmen zhlen die in Tabelle 6.32 aufgefhrten.
Algorithmus
adjacent_find count count_if equal find find_end find_first_of

Beschreibung Sucht benachbarte Duplikate. Zhlt Elemente. Zhlt Elemente. Prft, ob die Elemente zweier Sequenzen gleich sind. Sucht das erste Vorkommen eines Elements. Sucht das letzte Vorkommen einer Sequenz. Sucht nach Elementen, die in zwei Sequenzen vorhanden sind. Sucht das erste Vorkommen eines Elements. Vergleicht zwei Sequenzen lexikografisch. Ermittelt das grere zweier Elemente. Ermittelt das grte Element einer Sequenz. Ermittelt das kleinere zweier Elemente.

find_if lexicographical_compare max max_element min

Tabelle 6.32

Alle nicht-modifizierenden Algorithmen in alphabetischer Reihenfolge

339

Die STL

Algorithmus
min_element mismatch

Beschreibung Ermittelt das kleinste Element einer Sequenz. Bestimmt aus zwei Sequenzen die ersten Elemente, die sich unterscheiden. Sortiert ein Element an seine richtige Position. Sucht das erste Vorkommen einer Sequenz. Sucht das erste Vorkommen einer Sequenz gleicher Elemente.

nth_element search search_n

Tabelle 6.32

Alle nicht-modifizierenden Algorithmen in alphabetischer Reihenfolge (Forts.)

6.12.5 Elemente suchen


In diesem Kapitel sind die Algorithmen zusammengefasst, die nach einzelnen Elementen suchen.
adjacent_find benachbarte Duplikate finden
template<typename Forward> Forward adjacent_find(Forward anf, Forward end);

Die Funktion sucht im Bereich [anf, end[ nach benachbarten Duplikaten und liefert die Iteratorposition auf das erste Element der ersten gefundenen Duplikate. Sollten keine benachbarten Duplikate existieren, wird end zurckgeliefert. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen.
template<typename Forward, typename BinPred> Forward adjacent_find(Forward anf, forward end, BinPred fkt);

Die Funktion sucht im Bereich [anf, end[ nach benachbarten Duplikaten und liefert die Iteratorposition auf das erste Element der ersten gefundenen Duplikate. Dabei gelten zwei benachbarte Elemente e1 und e2 als Duplikat, wenn fkt(e1, e2) einen wahren Wert liefert. Sollten keine benachbarten Duplikate existieren, wird end zurckgeliefert. Die Funktion bentigt O(n)-Zeit, wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch unique, unique_copy.
find Elemente suchen
template<typename Forward, Typename Typ> Forward find(Forward anf, Forward end, const Typ &obj);

340

Algorithmen

6.12

Sucht im Bereich [anf, end[ nach dem ersten Vorkommen des Elements obj. Sollte die Suche erfolgreich sein, liefert die Funktion die entsprechende Iteratorposition zurck, andernfalls end. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion bentigt O(n)-Zeit, wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch find_if, binary_search.
find_first_of erstes Vorkommen eines Mengenelements
template<typename Forward1, typename Forward2> Forward1 find_first_of(Forward1 anf1, Forward1 end1, Forward2 anf2, Forward2 end2);

Sucht im Bereich [anf1, end1[ nach dem ersten Vorkommen eines Elements, welches sich ebenfalls im Bereich [anf2, end2[ befindet. Sollte die Suche erfolgreich sein, liefert die Funktion die Iteratorposition des Elements im Bereich [anf1, end1[ zurck, andernfalls end1. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen.
template<typename Forward1, typename Forward2, typename BinPred> Forward1 find_first_of(Forward1 anf1, Forward1 end1, Forward2 anf2, Forward2 end2, BinPred fkt);

Funktionsweise wie die erste Variante, nur dass die Elemente mit dem binren Prdikat fkt verglichen werden. Zwei Elemente e1 und e2 gelten genau dann als gleich, wenn fkt(e1,e2) wahr ist. find_first_of bentigt eine Laufzeit von O(n*m), wobei n fr die Anzahl der Elemente im ersten Bereich und m fr die Elementanzahl im zweiten Bereich stehen.
find_if Suchen mit eigenem Kriterium
template<typename Forward, typename Pred> Forward find_if(Forward anf, Forward end, Pred fkt);

Sucht im Bereich [anf, end[ nach dem ersten Vorkommen eines Elements e, fr das fkt(e) einen wahren Wert liefert. Sollte die Suche erfolgreich sein, liefert die Funktion die entsprechende Iteratorposition zurck, andernfalls end. Die Funktion bentigt O(n)-Zeit, wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch find, binary_search.

341

Die STL

6.12.6 Elemente zhlen


In diesem Abschnitt werden die Algorithmen besprochen, die Elemente zhlen.
count gleiche Elemente zhlen
template<typename Input, typename Typ> iterator_traits<Input>::difference_type count(Input anf, Input end, const Typ &obj);

Zhlt im Bereich [anf, end[ die Elemente, die gleich mit obj sind. Die Funktion liefert die Anzahl der gefundenen Elemente zurck. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion bentigt O(n)-Zeit, wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch count_if.
count_if Zhlen mit eigenem Kriterium
template<typename Input, typename Pred> iterator_traits<Input>::difference_type count_if(Input anf, Input end, Pred fkt);

Zhlt im Bereich [anf, end[ die Elemente, fr die der Aufruf fkt(element) einen wahren Wert liefert. Die Funktion liefert die Anzahl der gefundenen Elemente zurck. Die Funktion bentigt O(n)-Zeit, wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch count.

6.12.7 Minimum und Maximum


Es folgen die Algorithmen, um das Minimum und Maximum zweier Elemente oder einer Sequenz zu bestimmen.
max Maximum zweier Elemente finden
template<typename Typ> const Typ &max(const Typ &o1, const Typ &o2);

Liefert eine Referenz auf das grere der beiden Elemente o1 und o2 zurck. Bei Gleichheit wird eine Referenz auf o1 zurckgegeben. Diese Information ist wichtig, da gleiche Elemente nicht notwendigerweise dasselbe Element sein mssen. Der Vergleich wird mit der operator<-Funktion der Elemente vorgenommen.

342

Algorithmen

6.12

template<typename Typ, typename BinPred> const Typ &max(const Typ &o1, const Typ &o2, BinPred fkt);

Von der Funktionsweise identisch mit der vorigen Variante, verwendet diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(1). Siehe auch max_element, min, min_ element.
max_element Maximum einer Sequenz finden
template<typename Forward> Forward max_element(Forward anf, Forward end);

Liefert eine Referenz auf das grte Element im Bereich [anf, end[ zurck. Existieren mehrere Elemente mit dem grten Wert, so wird eine Referenz auf dasjenige Element zurckgegeben, das zuerst gefunden wurde. Der Vergleich wird mit der operator<-Funktion der Elemente vorgenommen.
template<typename Forward, typename BinPred> Forward max_element(Forward anf, Forward end, BinPred fkt);

Von der Funktionsweise identisch mit der vorigen Variante, verwendet diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch min_element, max, min.
min Minimum zweier Elemente finden
template<typename Typ> const Typ &min(const Typ &o1, const Typ &o2);

Liefert eine Referenz auf das kleinere der beiden Elemente o1 und o2 zurck. Bei Gleichheit wird eine Referenz auf o1 zurckgegeben. Diese Information ist wichtig, da gleiche Elemente nicht notwendigerweise dasselbe Element sein mssen. Der Vergleich wird mit der operator<-Funktion der Elemente vorgenommen.
template<typename Typ, typename BinPred> const Typ &min(const Typ &o1, const Typ &o2, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, verwendet diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt

343

Die STL

die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(1). Siehe auch min_element, max, max_ element.
min_element Minimum einer Sequenz finden
template<typename Forward> Forward min_element(Forward anf, Forward end);

Liefert eine Referenz auf das kleinste Element im Bereich [anf, end[ zurck. Existieren mehrere Elemente mit dem kleinsten Wert, so wird eine Referenz auf dasjenige Element zurckgegeben, das zuerst gefunden wurde. Der Vergleich wird mit der operator<-Funktion der Elemente vorgenommen.
template<typename Forward, typename BinPred> Forward min_element(Forward anf, Forward end, BinPred fkt);

Von der Funktionsweise identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Siehe auch max_element, min, max.

6.12.8 Sequenzen suchen


Die folgenden Algorithmen suchen nach Sequenzen innerhalb von Sequenzen.
find_end letztes Vorkommen einer Sequenz
template<typename Forward1, typename Forward2> Forward1 find_end(Forward1 anf1, Forward1 end1, Forward2 anf2, Forward2 end2);

Sucht im Bereich [anf1, end1[ nach dem letzten Vorkommen der Sequenz [anf2, end2[. Bei erfolgreicher Suche wird die Position des ersten Elements der Sequenz im durchsuchten Bereich zurckgeliefert. Andernfalls liefert die Funktion end1 zurck. Die Vergleiche werden mit der operator==Funktion der Elemente vorgenommen.
template<typename Forward1, typename Forward2, typename BinPred>

344

Algorithmen

6.12

Forward1 find_end(Forward1 anf1, Forward1 end1, Forward2 anf2, Forward2 end2, BinPred fkt);

Funktionsweise wie die erste Variante, nur dass die Elemente mit dem binren Prdikat fkt verglichen werden. Zwei Elemente e1 und e2 gelten als gleich genau dann, wenn fkt(e1,e2) wahr ist. Bei erfolgreicher Suche wird die Position des ersten Elements der Sequenz im durchsuchten Bereich zurckgeliefert. Andernfalls liefert die Funktion end1 zurck. Die Funktion hat eine Laufzeit von O((n-m)m), wobei n fr die Anzahl der Elemente im zu durchsuchenden Bereich und m fr die Anzahl der Elemente in der zu suchenden Sequenz stehen.
search erstes Vorkommen einer Sequenz
template<typename Forward1, typename Forward2> Forward1 search(Forward1 anf1, Forward1 end1, Forward2 anf2, Forward2 end2);

Sucht im Bereich [anf1, end1[ nach dem ersten Vorkommen der Sequenz [anf2, end2[. Bei erfolgreicher Suche wird die Position des ersten Elements der Sequenz im durchsuchten Bereich zurckgeliefert. Andernfalls liefert die Funktion end1 zurck.
template<typename Forward1, typename Forward2, typename BinPred> Forward1 find_end(Forward1 anf1, Forward1 end1, Forward2 anf2, Forward2 end2, BinPred fkt);

Funktionsweise wie die erste Variante, nur dass die Elemente mit dem binren Prdikat fkt verglichen werden. Zwei Elemente e1 und e2 gelten als gleich genau dann, wenn fkt(e1,e2) wahr ist. Bei erfolgreicher Suche wird die Position des ersten Elements der Sequenz im durchsuchten Bereich zurckgeliefert. Andernfalls liefert die Funktion end1 zurck. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion hat eine Laufzeit von O((n-m)m), wobei n fr die Anzahl der Elemente im zu durchsuchenden Bereich und m fr die Anzahl der Elemente in der zu suchenden Sequenz stehen.
search_n hintereinanderliegende gleiche Elemente finden
template<typename Forward1, typename Anzahl, typename Typ> Forward1 search_n(Forward1 anf1, Forward1 end1, Anzahl anz, const Typ &obj);

345

Die STL

Sucht im Bereich [anf1, end1[ nach dem ersten Vorkommen von anz hintereinanderliegenden Elementen, die alle gleich mit obj sind. Bei erfolgreicher Suche wird die Position des ersten mit obj gleichen Elements zurckgeliefert. Andernfalls liefert die Funktion end1 zurck. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen.
template<typename Forward1, typename Anzahl, typename Typ, typename BinPred> Forward1 search_n(Forward1 anf1, Forward1 end1, Anzahl anz, const Typ &obj, BinPred fkt);

Funktionsweise wie die erste Variante, nur dass die Elemente mit dem binren Prdikat fkt verglichen werden. Zwei Elemente e1 und e2 gelten als gleich genau dann, wenn fkt(e1,e2) wahr ist. Die Funktion hat eine Laufzeit von O((n-m)m), wobei n fr die Anzahl der Elemente im zu durchsuchenden Bereich und m fr die Anzahl der hintereinanderliegenden Elemente stehen.

6.12.9 Sequenzen vergleichen


Dieser Abschnitt listet alle Funktionen auf, die Sequenzen vergleichen.
equal zwei Sequenzen gleich?
template<typename Input1, typename Input2> bool equal(Input1 anf1, Input1 end1, Input2 anf2);

Liefert einen wahren Wert zurck, wenn der Bereich [anf1, end1[ mit dem Bereich [anf2, anf2+(end1-anf1)[ bereinstimmt. Andernfalls ist der Rckgabewert false. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion equal wird beispielsweise dazu verwendet, die ==-Operatoren der Container zu implementieren.
template<typename Input1, typename Input2, typename BinPred> bool equal(Input1 anf1, Input1 end1, Input2 anf2, BinPred fkt);

Von der Funktion her identisch mit der ersten Variante, nur dass die Elemente mit dem binren Prdikat fkt verglichen werden. Zwei Elemente e1 und e2 gelten als gleich genau dann, wenn fkt(e1,e2) wahr ist. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht. Siehe auch mismatch, lexicographical_compare.

346

Algorithmen

6.12

lexicographical_compare lexikografischer Vergleich


template<typename Input1, typename Input2> bool lexicographical_compare(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2);

Vergleicht die Bereiche [anf1, end1[ und [anf2, end2[ lexikografisch. Dabei wird true zurckgeliefert, wenn der erste Bereich kleiner als der zweite ist. In allen anderen Fllen ist der Rckgabewert false. Es kann mit dieser Funktion also nicht unterschieden werden, ob der erste Bereich grer als der zweite oder gleich dem zweiten Bereich ist.
Hinweis Der erste Bereich ist lexikografisch genau dann kleiner als der zweite, wenn der erste Bereich weniger Elemente besitzt als der zweite oder wenn fr die ersten sich unterscheidenden Elemente gilt, dass das Element des ersten Bereichs kleiner als das des zweiten ist.
lexicographical_compare kann z. B. eingesetzt werden, um die operator<-

Funktion von Containern zu implementieren.


template<typename Input1, typename Input2, typename BinPred> bool lexicographical_compare(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, verwendet diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im kleineren Bereich steht.
mismatch Verweis auf die beiden ersten ungleichen Elemente
template<typename Input1, typename Input2> pair<Input1, Input2> mismatch(Input1 anf1, Input1 end1, Input2 anf2);

Vergleicht den Bereich [anf1, end1[ mit dem Bereich [anf2, anf2+(end1anf1)[ und liefert ein Paar zurck, welches die Iteratorpositionen der ers-

ten beiden Elemente beinhaltet, die nicht gleich sind. Wenn keine unterschiedlichen Elemente gefunden werden, dann wird das Iteratorpaar (end1, anf2+(end1-anf1) ) zurckgeliefert. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen.

347

Die STL

template<typename Input1, typename Input2, typename BinPred> pair<Input1, Input2> mismatch(Input1 anf1, Input1 end1, Input2 anf2, BinPred fkt);

Von der Funktion her identisch mit der ersten Variante, nur dass die Elemente mit dem binren Prdikat fkt verglichen werden. Zwei Elemente e1 und e2 gelten als gleich genau dann, wenn fkt(e1,e2) wahr ist. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht. Siehe auch equal, lexicographical_ compare.

6.12.10 for_each
template<typename Input, typename Op> Op for_each(Input anf, Input end, Op fkt);

Ruft fr jedes Element im Bereich [anf, end[ das Funktionsobjekt fkt(element) auf. Anschlieend gibt for_each das Funktionsobjekt fkt zurck.
Hinweis Obwohl ein entsprechendes Funktionsobjekt auch nderungen an Elementen vornehmen knnte, sollten Sie davon Abstand nehmen, weil for_each zu den nichtmodifizierenden Algorithmen gehrt.

Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.

6.12.11 Modifizierende Algorithmen


Dieser Abschnitt umfasst alle Algorithmen, die nderungen an Elementen vornehmen. Dazu zhlt hauptschlich das Kopieren, Verschieben und Ersetzen von Elementen. Tabelle 6.33 bietet eine bersicht.
Algorithmus
copy copy_backward fill fill_n

Beschreibung Kopiert eine Sequenz. Kopiert eine Sequenz, beginnend am Ende. Ersetzt Elemente eines Bereichs. Ersetzt eine Anzahl von Elementen.

Tabelle 6.33 Die modifizierenden Algorithmen im berblick

348

Algorithmen

6.12

Algorithmus
generate generate_n iter_swap next_permutation prev_permutation random_shuffle remove remove_copy remove_copy_if remove_if replace replace_copy replace_copy_if replace_if reverse reverse_copy rotate rotate_copy swap swap_ranges unique unique_copy

Beschreibung Ersetzt Elemente eines Bereichs durch das Funktionsergebnis. Ersetzt eine Anzahl von Elementen durch Funktionsergebnis. Vertauscht zwei an Iteratorpositionen befindliche Elemente. Bestimmt die nchste Permutation. Bestimmt die vorherige Permutation. Ordnet die Elemente einer Sequenz in zuflliger Reihenfolge an. Lscht bestimmte Elemente. Kopiert und lscht dabei bestimmte Elemente. Kopiert und lscht dabei bestimmte Elemente. Lscht bestimmte Elemente. Ersetzt bestimmte Elemente. Kopiert und ersetzt dabei bestimmte Elemente. Kopiert und ersetzt dabei bestimmte Elemente. Ersetzt bestimmte Elemente. Dreht die Elementreihenfolge einer Sequenz um. Kopiert Elemente in umgekehrter Reihenfolge. Rotiert Elemente einer Sequenz. Kopiert und rotiert dabei die Elemente einer Sequenz. Vertauscht zwei Elemente. Vertauscht Elemente zweier Sequenzen. Lscht benachbarte Duplikate. Kopiert Elemente und lscht dabei benachbarte Duplikate.

Tabelle 6.33 Die modifizierenden Algorithmen im berblick (Forts.)

6.12.12 Sequenzen kopieren


Es folgen die Algorithmen zum Kopieren von Sequenzen.
copy Sequenz kopieren
template<typename Input, typename Output> Output copy(Input anf1, Input end1, Output anf2);

Kopiert den Bereich [anf1, end1[ nach anf2. Rckgabewert ist die Iteratorposition hinter dem zuletzt kopierten Element (bei normalen Iteratoren anf2+(end1-anf1) ).

349

Die STL

Achtung
copy funktioniert nur dann einwandfrei, wenn sich der Anfang des Zielbereichs und das Ende des Quellbereichs nicht berlappen.

Sollten sich die beiden Bereiche berlappen, dann existiert die in Abbildung 6.38 dargestellte Situation.
anf1 anf2 3 4 5 6 7 8 end1 9 10 11 12 13 14 15 16

anf1

end1 2 3 4 5

anf2 6 13 14 15 16

b
1 2 3 4 5 6 7

Abbildung 6.38

Die Problemsituation bei copy

Abbildung 6.38a zeigt die Sequenz vor Beginn des Kopierens. Der rechteckige Bereich markiert die zu kopierenden Elemente. Abbildung 6.38b zeigt den Zustand der Sequenz, nachdem fnf Elemente kopiert wurden. Die ursprnglich noch zu kopierenden Elemente 8, 9 und 10 wurden bereits mit den Elementen 2, 3 und 4 berschrieben und knnen nicht mehr korrekt kopiert werden. Abbildung 6.38c zeigt das ganze Ausma des Schadens. Um solche berlappenden Bereiche korrekt zu kopieren, muss auf die Funktion copy_backward ausgewichen werden. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.
copy_backward Sequenz von hinten beginnend kopieren
template<typename Bi1, typename Bi2> Bi2 copy_backward(Bi1 anf1, Bi1 end1, Bi2 end2);

Kopiert den Bereich [anf1, end1[ rckwrts, also am Ende beginnend, nach end2. Rckgabewert ist die Iteratorposition des zuletzt kopierten Elements (bei normalen Iteratoren end2-(end1-anf1) ).
Achtung
copy_backward funktioniert nur dann einwandfrei, wenn sich das Ende des Zielbereichs und der Anfang des Quellbereichs nicht berlappen.

350

Algorithmen

6.12

Sollte oben beschriebene berlappung vorliegen, muss zum Kopieren copy verwendet werden. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.

6.12.13 Elemente ersetzen


In diesem Abschnitt werden die Algorithmen besprochen, mit denen Elemente ersetzt oder kopiert und ersetzt werden knnen.
fill Sequenz mit Element fllen
template<typename Forward, typename Typ> void fill(Forward anf, Forward end, const Typ &nwert);

Ersetzt alle Elemente im Bereich [anf, end[ mit dem Objekt nwert. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
fill_n Sequenz mit Elementen fllen
template<typename Output, typename Anzahl, typename Typ> void fill_n(Output anf, Anzahl anz, const Typ &nwert);

Ersetzt ab Position anf anz Elemente mit dem Objekt nwert. Die Funktion besitzt ein lineares Laufzeitverhalten (O(anz) ).
generate Sequenz mit generierten Elementen fllen
template<typename Forward, typename Generator> void generate(Forward anf, Forward end, Generator gen);

Ersetzt alle Elemente im Bereich [anf, end[ mit dem Ergebnis des Aufrufs gen(). Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Die Laufzeitkomplexitt von gen ist nicht bercksichtigt.
generate_n Sequenz mit generierten Elementen fllen
template<typename Output, typename Anzahl, typename Generator> void generate_n(Output anf, Anzahl anz, Generator gen);

Ersetzt ab Position anf anz Elemente mit dem Ergebnis des Aufrufs gen(). Die Funktion besitzt ein lineares Laufzeitverhalten (O(anz) ). Die Laufzeitkomplexitt von gen ist nicht bercksichtigt.

351

Die STL

replace bestimmte Elemente ersetzen


template<typename Forward, typename Typ> void replace(Forward anf, Forward end, const Typ &awert, const Typ &nwert);

Ersetzt alle Elemente im Bereich [anf, end[, die gleich mit awert sind, mit dem Objekt nwert. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
replace_copy Elemente kopieren und ersetzen
template<typename Input, typename Output, typename Typ> Output replace_copy(Input anf1, Input end1, Output anf2, const Typ &awert, const Typ &nwert);

Kopiert den Bereich [anf1, end1[ nach anf2, und ersetzt dabei alle Elemente, die gleich mit awert sind, mit dem Objekt nwert. Rckgabewert ist die Iteratorposition hinter dem zuletzt kopierten Element (bei normalen Iteratoren anf2+(end1-anf1) ). Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.
replace_copy_if Elemente kopieren und bedingt ersetzen
template<typename Input, typename Output, typename Pred, typename Typ> Output replace_copy_if(Input anf1, Input end1, Output anf2, Pred fkt, const Typ &nwert);

Kopiert den Bereich [anf1, end1[ nach anf2, und ersetzt dabei alle Elemente, fr die der Aufruf fkt(element) einen wahren Wert liefert, mit dem Objekt nwert. Rckgabewert ist die Iteratorposition hinter dem zuletzt kopierten Element (bei normalen Iteratoren anf2+(end1-anf1)). Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.
replace_if Elemente bedingt ersetzen
template<typename Forward, typename Pred, typename Typ> void replace_if(Forward anf, Forward end, Pred fkt, const Typ &nwert);

352

Algorithmen

6.12

Ersetzt alle Elemente im Bereich [anf, end[, fr die der Aufruf fkt(element) einen wahren Wert liefert, mit dem Objekt nwert. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.

6.12.14 Elemente lschen


In diesem Abschnitt werden die Algorithmen zum Lschen von Elementen aufgefhrt.
remove Elemente lschen
template<typename Forward, typename Typ> Forward remove(Forward anf, Forward end, const Typ &obj);

Im Bereich [anf, end[ werden alle Elemente gelscht, die mit obj gleich sind. Die Funktion liefert die Position hinter dem letzten nicht gelschten Element zurck. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Da Iteratoren Elemente nicht wirklich lschen knnen, wird das Problem, wie in Abbildung 6.39 dargestellt, gelst.
anf end

anf

pos

end

b
Abbildung 6.39 Die Funktionsweise von remove

Die zu lschenden Elemente (in der Abbildung grau unterlegt) werden an das Ende des Containers kopiert. Die Funktion liefert dann eine Iteratorposition auf das erste zu lschende beziehungsweise die Iteratorposition hinter dem letzten nicht zu lschenden Element zurck. Der so entstandene Bereich [pos, end[ kann dann mit der erase-Funktion des Containers gelscht werden. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
remove_copy mit speziellem Objekt ungleiche Objekte kopieren
template<typename Input, typename Output, typename Typ> Output remove_copy(Input anf1, Input end1, Output anf2, const Typ &obj);

353

Die STL

Der Bereich [anf1, end1[ wird nach anf2 kopiert. Dabei werden alle Elemente nicht kopiert, die mit obj gleich sind. Die Funktion liefert die Position hinter dem zuletzt kopierten Element zurck. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.
remove_copy_if Elemente bedingt kopieren
template<typename Input, typename Output, typename Pred> Output remove_copy_if(Input anf1, Input end1, Output anf2, Pred fkt);

Der Bereich [anf1, end1[ wird nach anf2 kopiert. Dabei werden alle Elemente nicht kopiert, fr die der Aufruf fkt(element) einen wahren Wert liefert. Die Funktion liefert die Position hinter dem zuletzt kopierten Element zurck. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.
remove_if Elemente bedingt lschen
template<typename Forward, typename Pred> Forward remove_if(Forward anf, Forward end, Pred fkt);

Im Bereich [anf, end[ werden alle Elemente gelscht, fr die der Aufruf fkt(element) einen wahren Wert liefert. Die Funktion liefert die Position hinter dem letzten nicht gelschten Element zurck. Da Iteratoren Elemente nicht wirklich lschen knnen, wird das Problem, wie in Abbildung 6.39 gezeigt, gelst. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
unique lscht benachbarte Duplikate
template<typename Forward> Forward unique(Forward anf, Forward end);

Lscht im Bereich [anf, end[ benachbarte Duplikate. Die Funktion liefert die Position hinter dem letzten nicht gelschten Element zurck. Da Iteratoren Elemente nicht wirklich lschen knnen, wird das Problem, wie in Abbildung 6.39 gezeigt, gelst.
Hinweis Unter benachbarten Duplikaten versteht man gleiche Elemente, die in ihrer Reihenfolge direkt hintereinander liegen.

354

Algorithmen

6.12

Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen.


template<typename Forward, typename BinPred> Forward unique(Forward anf, Forward end, BinPred fkt);

Von der Funktionsweise identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei muss fkt so implementiert sein, dass fkt(element1, element2) genau dann einen wahren Wert liefert, wenn element1==element2 gilt.
Tipp Um eine Sequenz komplett von Duplikaten zu befreien, muss sie vorher sortiert werden.

Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
unique_copy Sequenz ohne benachbarte Duplikate kopieren
template<typename Input, typename Output> Output unique_copy(Input anf1, Input end1, Output anf2);

Kopiert den Bereich [anf1, end1[ nach anf2, und lscht dabei benachbarte Duplikate. Die Funktion liefert die Position hinter dem zuletzt kopierten Element zurck. Die Vergleiche werden mit der operator==-Funktion der Elemente vorgenommen. Weitere Informationen ber benachbarte Duplikate finden sie bei unique.
template<typename Input, typename Output, typename BinPred> Output unique_copy(Input anf1, Input end1, Output anf2, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei muss fkt so implementiert sein, dass fkt(element1, element2) genau dann einen wahren Wert liefert, wenn element1==element2 gilt. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.

355

Die STL

6.12.15 Elementreihenfolge verndern


In diesem Abschnitt finden Sie alle Algorithmen, die die Reihenfolge einer Sequenz verndern.
iter_swap Elemente vertauschen
template<typename Forward1, typename Forward2> inline void iter_swap(Forward1 i1, Forward2 i2);

Vertauscht die beiden an den Iteratorpositionen i1 und i2 liegenden Elemente. Die Funktion bentigt konstante Laufzeit (O(1)-Zeit).
next_permutation lexikografisch nchste Permutation bilden
template<typename Bi> bool next_permutation(Bi anf, Bi end);

Wandelt die Permutation im Bereich [anf, end[ in die lexikografisch folgende Permutation um. Sollte dabei die lexikografisch kleinstmgliche Permutation erzeugt werden, dann wird false zurckgeliefert, andernfalls true. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
Hinweis Permutationen von n Elementen sind die Anordnungen aller n Elemente in jeder mglichen Reihenfolge.
template<typename Bi, typename BinPred> bool next_permutation(Bi anf, Bi end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.
prev_permutation lexikografisch vorherige Permutation bilden
template<typename Bi> bool prev_permutation(Bi anf, Bi end);

Wandelt die Permutation im Bereich [anf, end[ in die lexikografisch vorhergehende Permutation um. Sollte dabei die lexikografisch grtmgliche Permutation erzeugt werden, dann wird false zurckgeliefert,

356

Algorithmen

6.12

andernfalls true. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Bi, typename BinPred> bool prev_permutation(Bi anf, Bi end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.
random_shuffle Elemente zufllig anordnen
template<typename Random> void random_shuffle(Random anf, Random end);

Ordnet die Elemente im Bereich [anf, end[ gleichverteilt zufllig an. Die Funktion hat eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
reverse Elementreihenfolge umdrehen
template<typename Bi> void reverse(Bi anf, Bi end);

Dreht die Reihenfolge der Elemente im Bereich [anf, end[ um. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
reverse_copy Elemente kopieren und Reihenfolge umdrehen
template<typename Bi, typename Output> Output reverse_copy(Bi anf1, Bi end1, Output anf2);

Kopiert die Elemente im Bereich [anf1, end1[ in umgekehrter Reihenfolge nach anf2. Zurckgegeben wird die Iteratorposition hinter dem zuletzt kopierten Element. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.
rotate Elemente rotieren
template<typename Forward> void rotate(Forward anf, Forward mit, Forward end);

357

Die STL

Verschiebt die Elemente im Bereich [anf, end[ so, dass das Element an der frheren Position mit nun an der Position anf steht. Links aus dem Bereich geschobene Elemente werden rechts wieder hineingeschoben. Abbildung 6.40 verdeutlicht dies.
anf mit 2 3 4 5 6 7 8 9 10 11 12 end

a
1

9 10 11 12 1

Abbildung 6.40

Die Funktionsweise von rotate

Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
rotate_copy Elemente kopieren und rotieren
template<typename Forward, typename Output> Output rotate_copy(Forward anf, Forward mit, Forward end, Output anf2);

Kopiert den Bereich [anf, end[ nach anf, und verschiebt die Elemente dabei so, dass das Element an Position mit im kopierten Bereich an Position anf2 liegt. Links aus dem Bereich geschobene Elemente werden rechts wieder hineingeschoben. Fr weitere Informationen schauen Sie bitte unter rotate nach. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
swap Elemente vertauschen
template<typename Typ> inline void swap(Typ &o1, Typ &o2);

Vertauscht die Elemente o1 und o2. Im Gegensatz zu iter_swap werden die Objekte hier ber Referenzen und nicht mittels Iteratorpositionen angesprochen. Die Funktion bentigt konstante Laufzeit (O(1)-Zeit).
swap_ranges Sequenzen vertauschen
template<typename Forward1, typename Forward2> Forward2 swap_ranges(Forward1 anf1, Forward1 end1, Forward2 anf2);

358

Algorithmen

6.12

Vertauscht die Elemente des Bereichs [anf1, end1[ mit den Elementen des Bereichs [anf2, anf2+(end1-anf1)[. Zurckgegeben wird die Iteratorposition hinter dem zweiten Bereich (bei normalen Iteratoren anf2+(end1-anf1)). Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf1, end1[ steht.

6.12.16 transform Elemente umwandeln


template<typename Input, typename Output, typename Op> Output transform(Input anf1, Input end1, Output anf2, Op fkt);

Ruft fr jedes Element im Bereich [anf1, end1[ die Funktion fkt(element1) auf, und weist das Ergebnis den Elementen ab Position anf2 zu. Rckgabewert ist die Iteratorposition hinter dem zuletzt zugewiesenen Element (bei normalen Iteratoren anf2+(end1-anf1)).
template<typename Input1, typename Input2, typename Output, typename BinOp> Output transform(Input1 anf1, Input1 end1, Input2 anf2, Output anf3, BinOp fkt);

Ruft fr jedes Element im Bereich [anf1, end1[ und [anf2, anf2+(end1anf1)[ die Funktion fkt(element1,element2) auf, und weist das Ergebnis

den Elementen ab Position anf3 zu. Rckgabewert ist die Iteratorposition hinter dem zuletzt zugewiesenen Element (bei normalen Iteratoren anf3+(end1-anf1)). Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht. Die Laufzeitkomplexitt von op ist nicht bercksichtigt.

6.12.17 Sortierende Algorithmen


In diesem Abschnitt finden Sie alle Algorithmen, die Sequenzen sortieren oder sortierte Sequenzen voraussetzen. Tabelle 6.34 gibt einen berblick.
Algorithmus
binary_search equal_range includes

Beschreibung Prft, ob ein Element in einem Bereich vorkommt. Kombination von lower_bound und upper_bound Prft, ob eine Menge komplett in einer anderen enthalten ist.

Tabelle 6.34 Die Algorithmen zum Thema Sortieren

359

Die STL

Algorithmus
inplace_merge lower_bound make_heap merge nth_element partial_sort partial_sort_copy partition pop_heap push_heap set_difference set_intersection set_symmetric_ difference set_union sort sort_heap stable_partition stable_sort upper_bound

Beschreibung Verschmilzt zwei sortierte Sequenzen an Ort und Stelle. Ermittelt den Anfang eines Bereichs gleicher Elemente. Wandelt eine Sequenz in einen Heap um. Verschmilzt zwei sortierte Sequenzen. Sortiert ein Element an seine richtige Position. Partielle Sortierung einer Sequenz. Kopiert den partiell sortierten Bereich einer Sequenz. Partitioniert eine Sequenz. Entfernt ein Element aus einem Heap. Fgt ein Element in einen Heap ein. Bildet die Differenz zweier Mengen. Bildet die Schnittmenge zweier Mengen. Bildet die symmetrische Differenz zweier Mengen. Bildet die Vereinigungsmenge zweier Mengen. Sortiert eine Sequenz. Wandelt einen Heap in eine sortierte Sequenz um. Partitioniert eine Sequenz stabil. Sortiert eine Sequenz stabil. Ermittelt das Ende eines Bereichs gleicher Elemente.

Tabelle 6.34 Die Algorithmen zum Thema Sortieren (Forts.)

nth_element partielles Sortieren


template<typename Random> void nth_element(Random anf, Random pos, Random end);

Der Bereich [anf, end[ wird so weit sortiert, bis an der Position pos dasjenige Element steht, das auch dort stnde, wenn der Bereich komplett sortiert wre. Des Weiteren gilt nach dem Aufruf von nth_element, dass kein Element im Bereich [pos, end[ kleiner ist als ein Element im Bereich [anf, pos[. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen. Abbildung 6.41 zeigt die Wirkungsweise von nth_ element.

360

Algorithmen

6.12

anf

pos

end

a
8 37 59 12 23 9 45 17 4 88 1 93

anf

pos 1 8 12 4 17 59 37 93 88 45 23

end

b
9

Abbildung 6.41

Die Funktionsweise von nth_element

template<typename Random, typename BinPred> void nth_element(Random anf, Random pos, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt im Durchschnitt eine lineare Laufzeit. Sie ist damit wesentlich schneller als partial_sort.
partial_sort partielles Sortieren
template<typename Random> void partial_sort(Random anf, Random pos, Random end);

Die Sequenz [anf, end[ wird partiell sortiert. Das bedeutet, dass nur der Bereich [anf, pos[ korrekt sortiert ist. Der Bereich [pos, end[ bleibt unsortiert. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen. Abbildung 6.42 stellt die Funktionsweise grafisch dar. Nachdem partial_sort aufgerufen wurde, ist der Bereich [anf, pos[ sortiert und der Bereich [pos, end[ unsortiert. Die Reihenfolge der unsortierten Elemente zueinander kann sich durch die partielle Sortierung gendert haben.
anf pos end

a
8 37 59 12 23 9 45 17 4 88 1 93

anf

pos 4 8 9 12 45 59 37 93 88 17 23

end

b
1

Abbildung 6.42

Die Funktionsweise von partial_sort

361

Die STL

template<typename Random, typename BinPred> void partial_sort(Random anf, Random pos, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion bentigt eine Laufzeit von O(n*log m), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ und m fr die Anzahl der Elemente im Bereich [anf, pos[ stehen.
partial_sort_copy Elemente kopieren und partiell sortieren
template<typename Input, typename Random> Random partial_sort_copy(Input anf, Input end, Random anf2, Random end2);

Der Bereich [anf2, end2[ wird mit den ersten end2-anf2 partiell sortierten Elementen des Bereichs [anf, end[ beschrieben. Der Bereich [anf, end[ wird dabei nicht verndert. Die Funktion liefert die Position hinter dem zuletzt geschriebenen Element (im Normalfall end2) zurck. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen. Weitere Informationen zum partiellen Sortieren finden Sie bei partial_sort.
template<typename Input typename Random, typename BinPred> Random partial_sort_copy(Input anf, Input end, Random anf2, Random end2, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion bentigt eine Laufzeit von O(n*log m), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ und m fr die Elementanzahl des kleineren der beiden Bereiche [anf, end[ und [anf2, end2[ stehen.
partition Sequenz in zwei Bereiche aufteilen
template<typename Bi, typename Pred> Bi partition(Bi anf, Bi end, Pred fkt);

Teilt den Bereich [anf, end[ in zwei Bereiche, wobei der erste Bereich alle Elemente enthlt, fr die fkt(element) den Wert true liefert. Der zweite Bereich enthlt alle Elemente, fr die fkt(element) den Wert false liefert.

362

Algorithmen

6.12

Rckgabewert ist die Iteratorposition pos des ersten Elements, fr den fkt(element) den Wert false liefert. Der erste Bereich ist damit [anf, pos[ und der zweite [pos, end[. Abbildung 6.43 zeigt die Funktionsweise. Die Elemente, fr die der Funktionsaufruf true ist, sind hellgrau, die anderen dunkelgrau unterlegt. Nach dem Aufruf liefert partition die Position des ersten Elements, fr den der Funktionsaufruf false ergeben hat.
anf end

anf

pos

end

b
Abbildung 6.43 Die Funktionsweise von partition

Achtung Die Partitionierung ist nicht stabil. Das heit, dass die Reihenfolge gleicher Elemente zueinander nicht notwendigerweise erhalten bleibt.

Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
sort Sequenz sortieren
template<typename Random> void sort(Random anf, Random end);

Sortiert die Elemente im Bereich [anf, end[. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
Achtung Die Sortierung ist nicht stabil. Deswegen bleibt die Reihenfolge gleicher Elemente zueinander nicht unbedingt erhalten.
template<typename Random, typename BinPred> void sort(Random anf, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Der fr sort verwendete Sortier-Algorithmus ist Quicksort. Im ungnstigsten

363

Die STL

Fall hat die Funktion eine Laufzeit von O(n2). Im Mittel ergibt sich jedoch eine Laufzeit von ca. O(n*log n), dabei steht n fr die Anzahl der Elemente im Bereich [anf, end[.
stable_partition stabile Aufteilung in zwei Bereiche
template<typename Bi, typename Pred> Bi stable_partition(Bi anf, Bi end, Pred fkt);

Teilt den Bereich [anf, end[ in zwei Bereiche, wobei der erste Bereich alle Elemente enthlt, fr die fkt(element) den Wert true liefert. Der zweite Bereich enthlt alle Elemente, fr die fkt(element) den Wert false liefert. Rckgabewert ist die Iteratorposition pos des ersten Elements, fr den fkt(element) den Wert false liefert. Der erste Bereich ist damit [anf, pos[ und der zweite [pos, end[. Im Gegensatz zu partition sortiert stable_ partition stabil; die Reihenfolge gleicher Elemente zueinander bleibt erhalten. Steht gengend Speicher zur Verfgung, dann bentigt die Funktion O(n)-Zeit, ansonsten O(n*log n). Dabei steht n fr die Anzahl der Elemente im Bereich [anf, end[.
stable_sort stabiles Sortieren
template<typename Random> void stable_sort(Random anf, Random end);

Sortiert die Elemente im Bereich [anf, end[. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen. Im Gegensatz zu sort sortiert stable_sort stabil; die Reihenfolge gleicher Elemente zueinander bleibt erhalten. Als Basis dient der Sortieralgorithmus Mergesort.
template<typename Random, typename BinPred> void stable_sort(Random anf, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion hat eine Laufzeit von O(n*log n), dabei steht n fr die Anzahl der Elemente im Bereich [anf, end[.

6.12.18 Algorithmen fr sortierte Sequenzen


Die folgenden Algorithmen sind nur anwendbar, wenn die betroffene Sequenz entsprechend sortiert ist.

364

Algorithmen

6.12

binary_search binre Suche


template<typename Forward, typename Typ> bool binary_search(Forward anf, Forward end, const Typ &obj);

Liefert den Wert true, wenn das Objekt obj im Bereich [anf, end[ enthalten ist. Andernfalls ist der Rckgabewert false. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Forward, typename Typ, typename BinPred> bool binary_search(Forward anf, Forward end, const Typ &obj, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Mit Random-Access-Iteratoren besitzt die Funktion eine Laufzeit von O(log n), andernfalls O(n). Dabei steht n fr die Elemente im Bereich [anf, end[.
equal_range Position einer Sequenz gleicher Elemente suchen
template<typename Forward, typename Typ> pair<Forward, Forward> equal_range(Forward anf, Forward end, const Typ &obj);

Als Kombination von lower_bound und upper_bound sucht equal_range in der Sequenz [anf, end[ einen Bereich mit obj gleichen Elementen und liefert die Iteratorposition des ersten Vorkommens und die Iteratorposition hinter dem letzten Vorkommen als Paar zurck. Sollte obj nicht gefunden werden, so wird, falls vorhanden, die Position des nchstgreren Elements zurckgeliefert. Sollte auch dies nicht mglich sein, ist der Rckgabewert end.
anf
lower_bound upper_bound

end

obj obj obj obj

Abbildung 6.44

Die Funktionsweise von equal_range

Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.

365

Die STL

template<typename Forward, typename Typ, typename BinPred> pair<Forward, Forward> equal_range(Forward anf, Forward end, const Typ &obj, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Mit Random-Access-Iteratoren besitzt die Funktion eine Laufzeit von O(log n), andernfalls O(n). Dabei steht n fr die Elemente im Bereich [anf, end[.
inplace_merge Sequenzen verschmelzen
template<typename Bi> void merge(Bi anf, Bi mit, Bi end);

Verschmilzt die sortierten, hintereinanderliegenden Sequenzen [anf, mit[ und [mit, end[ zu einer neuen sortierten Sequenz, die im Bereich [anf, end[ gespeichert wird. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen. Fr weitere Informationen ber den Vorgang des Verschmelzens lesen Sie bitte bei merge nach.
template<typename Bi, typename BinPred> void merge(Bi anf, Bi mit, Bi end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Ist gengend Speicher vorhanden, bentigt die Funktion lineare Laufzeit. Im schlimmsten Fall besitzt sie jedoch O(n*log n)-Zeit, wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.
lower_bound Beginn einer Sequenz gleicher Elemente suchen
template<typename Forward, typename Typ> Forward lower_bound(Forward anf, Forward end, const Typ& obj);

Sucht in der Sequenz [anf, end[ einen Bereich mit obj gleichen Elementen, und liefert die Iteratorposition des ersten Vorkommens zurck. Sollte obj nicht gefunden werden, so wird, falls vorhanden, die Position des nchstgreren Elements zurckgeliefert. Sollte auch dies nicht mglich sein, ist der Rckgabewert end. Die Vergleiche werden mit der operator<-Funk-

366

Algorithmen

6.12

tion der Elemente vorgenommen. Fr weitere Informationen sehen Sie bitte unter equal_range nach.
Tipp
lower_bound eignet sich dazu, die Einfgeposition eines Elements zu finden, bei der die Sortierung nicht zerstrt wird.

template<typename Forward, typename Typ, typename BinPred> Forward lower_bound(Forward anf, Forward end, const Typ &obj, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Mit Random-Access-Iteratoren besitzt die Funktion eine Laufzeit von O(log n), andernfalls O(n). Dabei steht n fr die Elemente im Bereich [anf, end[.
merge Sequenzen verschmelzen
template<typename Input1, typename Input2, typename Output> Output merge(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3);

Verschmilzt die sortierten Sequenzen [anf1, end1[ und [anf2, end2[ zu einer neuen sortierten Sequenz, die ab Position anf3 gespeichert wird. Zurckgegeben wird die Position hinter dem zuletzt gespeicherten Element. Abbildung 6.45 verdeutlicht diesen Vorgang.
anf1 1 4 8 9 12 end1 anf2 2 4 6 8 11 17 23 end2

anf3

anf3

b
1 2 4 4 6 8 8 9 11 12 17 23

Abbildung 6.45

Die Funktionsweise von merge

Bei gleichen Elementen haben die Elemente aus dem ersten Bereich Vorrang. Die Reihenfolge gleicher Elemente eines Bereichs zueinander bleibt

367

Die STL

erhalten. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Input1, typename Input2, typename Output, typename BinPred> Output merge(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(n), wobei n fr die gemeinsame Elementanzahl beider Bereiche steht.
upper_bound Ende einer Sequenz gleicher Elemente suchen
template<typename Forward, typename Typ> Forward upper_bound(Forward anf, Forward end, const Typ& obj);

Sucht in der Sequenz [anf, end[ einen Bereich mit obj gleichen Elementen, und liefert die Iteratorposition hinter dem letzen Vorkommen zurck. Sollte obj nicht gefunden werden, so wird, falls vorhanden, die Position des nchstgreren Elements zurckgeliefert. Sollte auch dies nicht mglich sein, ist der Rckgabewert end. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen. Fr weitere Informationen sehen Sie bitte unter equal_range nach.
Tipp
upper_bound eignet sich dazu, die Einfgeposition eines Elements zu finden, bei der die Sortierung nicht zerstrt wird.

template<typename Forward, typename Typ, typename BinPred> Forward upper_bound(Forward anf, Forward end, const Typ &obj, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Mit Random-Access-Iteratoren besitzt die Funktion eine Laufzeit von O(log n), andernfalls O(n). Dabei steht n fr die Elemente im Bereich [anf, end[.

368

Algorithmen

6.12

6.12.19 Algorithmen fr Mengen


Auch diese Algorithmen setzen voraus, dass die die Mengen reprsentierenden Sequenzen sortiert sind.
includes Ist die Sequenz Teilmenge einer anderen?
template<typename Input1, typename Input2> bool includes(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2);

Liefert true zurck, wenn alle Elemente der Menge [anf2, end2[ auch in der Menge [anf1, end1[ enthalten sind. Andernfalls ist der Rckgabewert false. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Input1, typename Input2, typename BinPred> bool includes(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.
set_difference Differenz zweier Mengen
template<typename Input1, typename Input2, typename Output> Output set_difference(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3);

Bildet die Differenz der beiden Mengen [anf1, end1[ und [anf2, end2[ und speichert diese ab anf3. Rckgabewert ist die Position hinter dem letzten Element der erzeugten Menge. Die Vergleiche werden mit der operator<Funktion der Elemente vorgenommen.
Hinweis Unter der Differenz zweier Mengen versteht man alle Elemente, die in der ersten, nicht aber in der zweiten Menge enthalten sind.

369

Die STL

template<typename Input1, typename Input2, typename Output, typename BinPred> Output set_difference(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.
set_intersection Schnittmenge zweier Sequenzen
template<typename Input1, typename Input2, typename Output> Output set_intersection(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3);

Bildet die Schnittmenge (auch Durchschnittsmenge genannt) der beiden Mengen [anf1, end1[ und [anf2, end2[, und speichert diese ab anf3. Rckgabewert ist die Position hinter dem letzten Element der erzeugten Menge. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
Hinweis Die Schnittmenge beinhaltet alle Elemente, die sowohl in der ersten als auch in der zweiten Menge enthalten sind.
template<typename Input1, typename Input2, typename Output, typename BinPred> Output set_intersection(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.
set_symmetric_difference symmetrische Differenz zweier Sequenzen
template<typename Input1, typename Input2, typename Output> Output set_symmetric_difference(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3);

370

Algorithmen

6.12

Bildet die symmetrische Differenz der beiden Mengen [anf1, end1[ und [anf2, end2[, und speichert diese ab anf3. Rckgabewert ist die Position hinter dem letzten Element der erzeugten Menge. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
Hinweis Unter der symmetrischen Differenz zweier Mengen versteht man alle Elemente, die in der ersten oder in der zweiten, nicht aber in beiden Mengen enthalten sind.
template<typename Input1, typename Input2, typename Output, typename BinPred> Output set_symmetric_difference(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.
set_union Vereinigungsmenge zweier Sequenzen
template<typename Input1, typename Input2, typename Output> Output set_union(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3);

Bildet die Vereinigungsmenge der beiden Mengen [anf1, end1[ und [anf2, end2[, und speichert diese ab anf3. Rckgabewert ist die Position hinter dem letzten Element der erzeugten Menge. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
Hinweis Die Vereinigungsmenge beinhaltet alle Elemente der ersten und der zweiten Menge.

Dabei finden sich Elemente, die sowohl in der ersten als auch in der zweiten Menge enthalten sind, in der Vereinigungsmenge nur einmal wieder.
template<typename Input1, typename Input2, typename Output, typename BinPred> Output set_union(Input1 anf1, Input1 end1, Input2 anf2, Input2 end2, Output anf3, BinPred fkt);

371

Die STL

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.

6.12.20 Heap-Algorithmen
Heaps sind eine gewisse Form von Binrbaum. Folgende Regel muss gelten:
Definition Ist jeder Knoten eines Binrbaums grer als seine Shne, dann handelt es sich um einen Heap Fr den Fall, dass gleiche Werte mehrmals erlaubt sind, muss anstelle der Bedingung grer eine Bedingung grer oder gleich verwendet werden.

Deshalb steht bei einem Heap das grte Element immer an der Wurzel. Ein Heap kann sehr leicht in einem Feld dargestellt werden. Die Heap-Bedingung sieht auf eine Sequenz bertragen so aus:
Definition In einem n Elemente groen Heap gilt fr jedes Element e(m): Wenn 2m<=n, dann ist e(m)>=e(2m) und wenn 2m+1<=n, dann ist e(m)>=e(2m+1).

Bei dieser Betrachtung ist das Element an Position 1 gleichbedeutend mit der Wurzel des Heap-Baums.
make_heap Heap erzeugen
template<typename Random> void make_heap(Random anf, Random end);

Macht aus der Sequenz [anf, end[ einen Heap. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Random, typename BinPred> void make_heap(Random anf, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt lineare Laufzeit.

372

Algorithmen

6.12

pop_heap Element aus Heap entfernen


template<typename Random> void pop_heap(Random anf, Random end);

Entfernt aus einem im Bereich [anf, end[ liegenden Heap das erste (und nach der Sortierung grte) Element. Der um ein Element verkleinerte Heap liegt im Bereich [anf, end-1[. Das entfernte Element befindet sich an Position end-1. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Random, typename BinPred> void pop_heap(Random anf, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt logarithmische Laufzeit.
push_heap Element zu Heap hinzufgen
template<typename Random> void push_heap(Random anf, Random end);

Unter der Voraussetzung, dass sich im Bereich [anf, end-1[ ein gltiger Heap befindet, wird das an Position end-1 liegende Element in diesen Heap eingefgt. Der um ein Element vergrerte Heap liegt im Bereich [anf, end[. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.
template<typename Random, typename BinPred> void push_heap(Random anf, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt logarithmische Laufzeit.
sort_heap Heap in sortierte Sequenz umwandeln
template<typename Random> void sort_heap(Random anf, Random end);

Wandelt den Heap im Bereich [anf, end[ in eine sortierte Sequenz um. Die Vergleiche werden mit der operator<-Funktion der Elemente vorgenommen.

373

Die STL

template<typename Random, typename BinPred> void sort_heap(Random anf, Random end, BinPred fkt);

Von der Funktionsweise her identisch mit der vorigen Variante, benutzt diese Funktion das Prdikat fkt zum Vergleich. Dabei bestimmt fkt die Reihenfolge zweier Elemente. Fr eine aufsteigende Reihenfolge muss fkt(o1,o2) genau dann einen wahren Wert liefern, wenn o1<o2 gilt. Die Funktion besitzt eine Laufzeit von O(n*log n), wobei n fr die Anzahl der Elemente im Bereich [anf, end[ steht.

374

In diesem Kapitel wird ein etwas aufwndigeres Projekt besprochen. Ein einfaches Adressbuch auf Textbasis wollen wir mit einer eigenen STL-Komponente verwalten.

Praxis Adressbuch

Fr das eigene Adressbuch wird zunchst eine eigene, STL-kompatible Listenklasse implementiert. Nach diesem Schema knnen Sie spter auch eigene Container fr die STL fit machen. Anschlieend entwickeln wir die fr das Adressbuch notwendigen Klassen. Wenn Sie an dieser Stelle nicht an einem tieferen Einblick in die STL interessiert sind, knnen Sie auch gerne im Adressbuch die von der STL bereitgestellte Listenklasse verwenden und Abschnitt 7.1 berspringen.

7.1

Die eigene Liste

Das Programmieren einer eigenen Liste zhlt zu den klassischen AnfngerAufgaben eines Programmierers. Im ersten Abschnitt des Praxis-Kapitels wollen wir ebenfalls eine solche Liste implementieren, aber STL-konform. Von der Struktur her wird es eine doppelt verkettete Liste als Template. Das Grundgerst des Template sieht wie folgt aus:
#ifndef LIST_H #define LIST_H #include <memory> template<typename Typ, typename Allok=std::allocator<Typ> > class list { public: // // Definition der internen Typen // typedef Allok allocator_type;

375

Praxis Adressbuch

typedef typedef typedef typedef typedef typedef typedef typedef

typename Allok::size_type size_type; typename Allok::difference_type difference_type; typename Allok::pointer pointer; typename Allok::const_pointer const_pointer; typename Allok::reference reference; typename Allok::const_reference const_reference; typename Allok::value_type value_type; list<Typ, Allok> MeinTyp;

/* ** Ausnahme-Klasse */ class out_of_range {}; private: // // Knoten-Klasse // struct Knoten { Knoten *vor,*nach; Typ *daten; }; /* ** Listen-Attribute */ Allok allok; Knoten *anfang, *ende; size_type lsize; };
Listing 7.1 Das Grundgerst der Liste

Die Datentypen der Liste werden vom Allokator der STL bernommen.
Hinweis Die Angabe von typename hinter typedef ist notwendig, wenn ein Typ (z. B. Allok::size_type) abhngig ist von einem Template-Parameter (in diesem Beispiel Allok).

Die Klasse out_of_range wird als Ausnahme geworfen, wenn Indizes oder Positionen auerhalb des gltigen Bereichs liegen. Der Knoten besitzt, wie fr eine doppelt verkettete Liste blich, einen Verweis auf seinen Vorgnger und auf seinen Nachfolger. Zustzlich verweist er noch auf das gespeicherte Ele-

376

Die eigene Liste

7.1

ment. Die Liste selbst merkt sich den verwendeten Allokator, Verweise auf den ersten und letzten Knoten sowie auf die Anzahl der Elemente in der Liste. Unsere Liste wird den fr die Elementdaten notwendigen Speicher ber den Allokator reservieren und freigeben. In der Praxis wre es sinnvoll, auch die Knoten ber Allokatoren zu verwalten. Da diese Klasse aber hauptschlich der Demonstration gilt, wollen wir die Knotenverwaltung der bersichtlichkeit halber auf dem herkmmlichen Wege abwickeln. Um die Implementierung der Liste mglichst gering zu halten, muss die Verwaltung der Datenstruktur so wenige Sonderflle wie mglich besitzen. Ein simpler Trick sorgt fr eine drastische Vereinfachung. Durch die Verwendung eines Anfangs- und eines Endknotens, die auch bei leerer Liste vorhanden sind, werden Knoten immer zwischen zwei andere Knoten eingefgt, egal, wo in der Liste die Einfgeposition liegt. Abbildung 7.1 zeigt eine leere Liste.
anfang ende

Abbildung 7.1

Eine leere Liste

Kein Element wird jemals vor dem Knoten anfang oder hinter dem Knoten ende eingefgt. Aus diesem Grund findet jedes Einfgen immer zwischen zwei Knoten statt. Ob diese Knoten Nutzdaten enthalten oder nur die Listengrenze bilden, spielt dabei keine Rolle.

7.1.1

Konstruktoren und Destruktoren

Die Anweisungen zur Erzeugung einer leeren Liste werden in der Hilfsfunktion Skelett gekapselt, auf die die Konstruktoren dann zugreifen:
void Skelett() { anfang=new Knoten; ende=new Knoten; anfang->nach=ende; anfang->vor=anfang; ende->vor=anfang; ende->nach=ende; lsize=0; }
Listing 7.2 Die Methode Skelett

377

Praxis Adressbuch

Der Knoten anfang zeigt als Nachfolger auf ende und als Vorgnger auf sich selbst. Analog dazu zeigt der Knoten ende als Vorgnger auf anfang und als Nachfolger auf sich selbst. Als Konstruktoren werden der Standardkonstruktor, der Kopierkonstruktor und ein Konstruktor, der eine Kopie einer bereits bestehenden Liste erzeugt, implementiert:
explicit list(const Allok &a=Allok()) :allok(a) { Skelett(); }
Listing 7.3 Der Standardkonstruktor

Dieser Konstruktor ist kein wirklicher Standardkonstruktor. Sein Parameter besitzt jedoch einen Standard-Wert, daher kann er auch als Default-Konstruktor eingesetzt werden. Er ist als explicit deklariert, damit der Compiler mit seiner Hilfe kein implizite Umwandlung von Allok nach liste vornimmt.
list(const MeinTyp &l) :allok(l.allok) { Skelett(); assign(l.begin(), l.end()); }
Listing 7.4 Der Kopierkonstruktor

Der Kopierkonstruktor ist die erste Stelle, an der eine Entscheidung zwischen Eleganz und Effizienz getroffen werden muss. Die Liste l ber die assignMethode zu kopieren, die vom Container sowieso zur Verfgung gestellt werden muss, ist ein eleganter Weg. Es wre aber effizienter gewesen, eine Schleife zu programmieren, welche die zu kopierende Liste auf Knotenebene durchluft. Dadurch wre der zeitliche Overhead der Iteratoren eingespart worden.
template<typename Input> list(Input anf, Input end,const Allok &a=Allok()) : allok(a){ Skelett(); for(;anf!=end; ++anf) push_back(*anf); }
Listing 7.5 Der Konstruktor zum Kopieren eines Bereichs

378

Die eigene Liste

7.1

Bezogen auf die Effizienz-/Eleganz-Betrachtung gilt hier das Gleiche wie fr den Kopierkonstruktor: Eine Schleife auf Knotenebene ist schneller als der Aufruf von push_back auf Iteratorebene. Der Destruktor lscht alle Knoten mit Nutzdaten (unter Zuhilfenahme der Hilfsmethode Destroy). Anschlieend werden die beiden begrenzenden Knoten gelscht:
~list() { Knoten *akt=anfang->nach; while(akt!=ende) { akt=akt->nach; Destroy(akt->vor); } delete(anfang); delete(ende); }
Listing 7.6 Der Destruktor

Beim Lschen muss eine Unterscheidung zwischen Nutzknoten und begrenzenden Knoten getroffen werden, weil die begrenzenden Knoten nur Knotenhllen sind, sie verweisen auf keine Nutzdaten. Die Methode Destroy gibt aber auch die Nutzdaten frei. Es wre eleganter gewesen, auch die begrenzenden Knoten mit Nutzdaten zu versehen (deren Inhalt unerheblich wre, da er sowieso nicht benutzt wrde). Aber abhngig davon, wie gro die von der Liste zu verwaltenden Elemente sind, knnen zwei zustzlich angelegte aber nicht notwendige Elemente die Effizienz der Liste drcken. Der Destruktor htte ebenfalls eleganter formuliert werden knnen, wenn anstelle der Schleife die clearMethode verwendet worden wre.

7.1.2

Hilfsmethoden

Bestimmte Operationen werden innerhalb der Listen-Methoden hufiger bentigt, deswegen bietet es sich an, diese Operationen in privaten Hilfsmethoden zu kapseln. Als Erstes wre die Methode Create zu erwhnen, der ein Element bergeben wird. Von diesem Element fertigt sie eine Kopie an und packt einen Knoten drumherum. Dieser fertige Knoten wird dann zurckgegeben:

379

Praxis Adressbuch

Knoten *Create(const Typ &d) { Typ *tmp=allok.allocate(1,0); allok.construct(tmp,d); Knoten *ktmp=new Knoten; ktmp->daten=tmp; return(ktmp); }
Listing 7.7 Die Methode Create

Zuerst wird ber die allocate-Methode des Allokators uninitialisierter Speicher fr ein Element angefordert. Die construct-Methode des Allokators initialisiert dann den angeforderten Speicher mit dem bergebenen Element (welches hier konkret dasjenige Element ist, das in einen Knoten gesetzt werden soll). Der Knoten selbst wird konventionell ber new erzeugt. Anschlieend wird das mit dem Allokator erzeugte Element in den Knoten eingebaut. Als nchste Methode ist Destroy an der Reihe, die einen Knoten lscht. Destroy fand bereits im Destruktor Verwendung:
void Destroy(Knoten *k) { allok.destroy(k->daten); allok.deallocate(k->daten,1); delete(k); }
Listing 7.8 Die Methode Destroy

Destroy zerstrt als Erstes das im reservierten Speicher befindliche Element

mittels der Allokator-Funktion destroy. Danach kann der reservierte Speicher ohne die Gefahr eines entstehenden Ressourcenlecks ber die AllokatorFunktion deallocate freigegeben werden. Zu guter Letzt wird noch der Knoten selbst mittels des konventionellen delete gelscht. Eine weitere Hilfsmethode ist Distance, die die Anzahl der Knoten im Bereich [a, e] ermittelt. Bitte beachten Sie, dass in diesem Fall der Knoten an Position e mit eingeschlossen ist. [ ]-Bereiche sind fr die interne Verwaltung einfacher zu implementieren als [ [-Bereiche:
size_type Distance(Knoten *a, Knoten *e){ size_type d=1; while(a!=e){ a=a->nach; d++;

380

Die eigene Liste

7.1

} return(d); }
Listing 7.9 Die Methode Distance

Obwohl fr Distanzen normalerweise der Datentyp difference_type verwendet werden sollte, wird hier size_type eingesetzt, damit der Rckgabewert typgleich mit lsize ist. Auerdem wre die Situation a>e schon in den Distance aufrufenden Funktionen ein Fehler. Daher knnen innerhalb von Distance keine negativen Distanzen auftreten. Die nchste zu besprechende Methode ist Link, die mehrere, untereinander bereits korrekt verkettete Knoten den Bereich [a,e] an der Position pos in die Liste einfgt. Zurckgegeben wird die Position des ersten eingefgten Elements. Abbildung 7.2 verdeutlicht die Funktionsweise.
a e

pos

b
Abbildung 7.2 Die Funktionsweise von Link

Die Implementierung sieht so aus:


Knoten *Link(Knoten *pos, Knoten *a, Knoten *e) { if(pos==anfang) throw(out_of_range()); a->vor=pos->vor; pos->vor->nach=a; e->nach=pos; pos->vor=e; lsize+=Distance(a,e); return(a); }
Listing 7.10 Die Methode Link

381

Praxis Adressbuch

Die Methode Link benutzt Distance, um die Anzahl der Knoten im Bereich [a,e] zu bestimmen, denn genau um diese Anzahl erhht sich durch das Einbinden die Elementanzahl der Liste. Eine Sonderform von Link, die nur einen einzelnen Knoten in die Liste einfgt, macht sich die obige allgemeine Version zunutze:
Knoten *Link(Knoten *pos, Knoten *k) { return(Link(pos,k,k)); }
Listing 7.11 Link fr einen einzelnen Knoten

Die Umkehrfunktion von Link ist Unlink, die den Bereich [a,e] aus einer Liste entfernt. Zurckgegeben wird die Position hinter dem letzten entfernten Element:
Knoten *Unlink(Knoten *a, Knoten *e) { if((a==anfang)||(e==ende)) throw(out_of_range()); e->nach->vor=a->vor; a->vor->nach=e->nach; lsize-=Distance(a,e); return(e->nach); }
Listing 7.12 Die Methode Unlink

Auch fr Unlink wird eine Sonderform fr einen einzelnen Knoten implementiert, die auf der allgemeinen Unlink-Methode basiert:
Knoten *Unlink(Knoten *k){ return(Unlink(k,k)); }
Listing 7.13 Unlink fr einen einzelnen Knoten

Die nchste Methode ist Erase. Sie ist eine Verbindung von Unlink und Destroy, denn sie lscht Knoten des Bereichs [a,e[. Die Erase-Methode liegt im Abstraktionsgrad eine Stufe hher als Unlink und nhert sich dem der Iteratoren an. Aus diesem Grund werden wir in Erase auch mit [ [-Bereichen arbeiten. Die Verweise bleiben jedoch auf Knotenebene:

382

Die eigene Liste

7.1

Knoten *Erase(Knoten *a, Knoten *e) { Knoten *tmp=e; if((a!=e)&&lsize) { e=e->vor; size_type d=Distance(a, e); Unlink(a,e); Knoten *akt; while(a!=e){ akt=a; a=a->nach; Destroy(akt); } Destroy(e); return(tmp); } else return(a); }
Listing 7.14 Die Methode Erase

Die Methode htte eleganter implementiert werden knnen, indem einfach eine Schleife ber alle Knoten des zu lschenden Bereichs luft und fr jeden Knoten Unlink(k) und Destroy(k) aufruft. Dadurch msste aber jeder Knoten einzeln von der Liste abgekoppelt werden, was fr jede Abkopplung das Umsetzen von zwei Zeigern erfordert. Durch das Abkoppeln des gesamten Bereichs mssen unabhngig von der Gre des Bereichs immer nur zwei Zeiger umgesetzt werden. Die Erase-Funktion fr einen einzelnen Knoten wurde der Effizienz wegen implementiert, ohne die allgemeingltige Form zu verwenden:
Knoten *Erase(Knoten *k) { if(!k) return(k); Knoten *tmp=k->nach; Unlink(k); Destroy(k); return(tmp); }
Listing 7.15 Erase fr einen einzelnen Knoten

383

Praxis Adressbuch

7.1.3

ffentliche Methoden

Dieser Abschnitt widmet sich den fr den Benutzer zugnglichen Methoden.


push, pull, back und front

Diese Gruppe von Methoden lsst sich mit den uns zur Verfgung stehenden Hilfsmethoden sehr elegant formulieren:
void push_back(const Typ &d){ Link(ende, Create(d)); } void pull_back(void){ Unlink(ende->vor); } void push_front(const Typ &d){ Link(anfang->nach, d); } void pull_front(void){ Unlink(anfang->nach); } Typ &back(void) const{ return(ende->vor->daten); } Typ &front(void) const{ return(anfang->nach->daten); }
Listing 7.16 Die einfachen Zugriffs-Methoden

assign

Die assign-Methode wurde bereits vom Kopierkonstruktor benutzt und soll hier genauer betrachtet werden:
template<typename Input> void assign(Input a, Input e) { clear(); insert(end(),a,e); }
Listing 7.17 Die Methode assign

384

Die eigene Liste

7.1

Die Methode lscht zuerst den Speicher mit clear und fgt dann ber insert die Elemente des Bereichs [a,e[ in die Liste ein.
clear

Mit clear werden alle Elemente der Liste gelscht:


void clear() { Erase(anfang->nach, ende); }
Listing 7.18 Die Methode clear

insert

Die insert-Methode fgt Elemente in die Liste ein. Fr das Einfgen gibt es verschiedene Ausfhrungen:
iterator insert(iterator pos, const Typ &d=Typ()){ return(iterator(Link(pos.knoten,Create(d)),this)); }
Listing 7.19 insert fr ein Element

Die Funktion fgt ein Element d an der Position pos in die Liste ein. Zurckgegeben wird die Iteratorposition des eingefgten Elements (der Konstruktor des Iterators wird in Abschnitt 7.1.4, Iteratoren, noch eingehender behandelt):
void insert(iterator pos, size_type anz, const Typ &d){ while(anz--) insert(pos,d); }
Listing 7.20 Ein Element wiederholt einfgen

Diese Variante fgt anz mal das Element d an der Stelle pos in die Liste ein.
template<typename Input> void insert(iterator pos, Input a, Input e) { while(a!=e) insert(pos,*a++); }
Listing 7.21 Einen Bereich einfgen

385

Praxis Adressbuch

Dies ist die Version, die in assign zur Anwendung kam. Sie fgt den Bereich [a,e[ an der Stelle pos in die Liste ein. Dabei greift sie auf die insert-Methode fr ein Element zurck.
erase

Die Methode erase lscht Elemente aus der Liste. Sie wird in zwei Versionen implementiert:
iterator erase(iterator pos) { return(iterator(Erase(pos.knoten),this)); }
Listing 7.22 erase fr einen einzelnen Knoten

Diese Version lscht den Knoten an der Position pos unter Zuhilfenahme der Hilfsmethode Erase. Zurckgegeben wird die Iteratorposition hinter dem gelschten Element.
iterator erase(iterator a, iterator e) { return(iterator(Erase(a.knoten, e.knoten),this)); }
Listing 7.23 erase fr einen Bereich

Der Bereich [a,e[ wird gelscht. Zurckgegeben wird die Iteratorposition hinter dem zuletzt gelschten Element.
remove

Diese Methode lscht jedes Vorkommen von obj in der Liste:


void remove(const Typ &obj) { Knoten *akt=anfang->nach; while(akt!=ende) if(*akt->daten==obj) akt=Erase(akt); else akt=akt->nach; }
Listing 7.24 Die Methode remove

386

Die eigene Liste

7.1

remove_if

Lscht alle Elemente, fr die der Aufruf fkt(element) den Wert true liefert:
template<typename Pred> void remove_if(Pred fkt) { Knoten *akt=anfang->nach; while(akt!=ende) if(fkt(*akt->daten)) akt=Erase(akt); else akt=akt->nach; }
Listing 7.25 Die Methode remove_if

reverse

Die Funktion reverse dreht die Reihenfolge der Listenelemente um:


void reverse() { if(1<lsize){ size_type anz=lsize; Knoten *ins=ende,*pos=anfang->nach, *del; while(anz--) { del=pos; pos=pos->nach; Unlink(del); ins=Link(ins,del); } } }
Listing 7.26 Die Methode reverse

Die hinter dieser Funktion steckende Idee ist, dass die Elemente am Anfang der Liste entfernt und am Ende der Liste wieder eingefgt werden. Wenn die Elemente dabei so eingefgt werden, dass ein Element immer vor das davor eingefgte Element gesetzt wird, dann wird dabei die Reihenfolge umgekehrt.
splice

Als Splicing bezeichnet man das Verschieben von Elementen durch Umhngen der Knoten. Dadurch mssen keine neuen Elemente erzeugt und alte zer-

387

Praxis Adressbuch

strt werden, wie es beim herkmmlichen Verschieben der Fall wre. Die allgemeine Form von splice ist diejenige Version, die spter auch von den anderen beiden Implementationen aufgerufen wird. Sie verschiebt den Bereich [a,e[ an die Position pos der aufrufenden Liste:
void splice(iterator pos, MeinTyp &l, iterator a, iterator e){ if(a!=e) { Knoten *ka=a.knoten, *ke=e.knoten->vor; l.Unlink(ka, ke); Link(pos.knoten, ka, ke); } }
Listing 7.27 Einen Bereich verschieben

Die nchste Variante verschiebt alle Elemente einer Liste an die Position pos innerhalb der aufrufenden Liste:
void splice(iterator pos, MeinTyp &l){ splice(pos,l,l.begin(),l.end()); }
Listing 7.28 Eine Liste in eine andere verschieben

Die folgende Version verschiebt lediglich ein Element, das Element an Position a wird an Position pos gesetzt:
void splice(iterator pos, MeinTyp &l, iterator a){ iterator e=a++; splice(pos,l, a,e); }
Listing 7.29 Ein Element verschieben

merge

Die Funktion merge verschmilzt zwei sortierte Listen zu einer (die genaue Funktionsweise ist in Abschnitt 6.12.18, Algorithmen fr sortierte Sequenzen, erklrt):
void merge(MeinTyp &l) { if(!l.empty()) { Knoten *anf1=anfang->nach, *anf2=l.anfang->nach; while((anf1!=ende)&&(!l.empty())) { if(*anf2->daten<*anf1->daten){

388

Die eigene Liste

7.1

Knoten *tmp=anf2; anf2=anf2->nach; l.Unlink(tmp); Link(anf1,tmp); } else anf1=anf1->nach; } if((anf1==ende)&&(!l.empty())) splice(end(),l); } }


Listing 7.30 Die Methode merge

unique

Die Methode unique entfernt benachbarte Duplikate:


void unique() { if(1<lsize){ Knoten *l=anfang->nach, *r=l->nach; while(r!=ende) { if(*l->daten==*r->daten){ Erase(r); r=l->nach; } else{ l=r; r=r->nach; } } } }
Listing 7.31 Die Methode unique

Vergleichsoperatoren

Betrachten wir zunchst den Gleichheitsoperator:


template<typename Typ, typename Allok> bool operator==(const list<Typ,Allok> &l1, const list<Typ,Allok> &l2){ return (l1.size() == l2.size() && equal(l1.begin(), l1.end(), l2.begin())); }
Listing 7.32 Der Gleichheitsoperator

389

Praxis Adressbuch

Da der STL-Algorithmus equal davon ausgeht, dass die zu vergleichenden Listen gleich lang sind, mssen wir vorher explizit sicherstellen, dass sie auch wirklich gleich lang sind. Sollten sich ihre Lngen unterscheiden, knnen sie nicht mehr gleich sein:
template<typename Typ, typename Allok> bool operator<(const list<Typ,Allok> &l1, const list<Typ,Allok> &l2){ return (lexicographical_compare(l1.begin(), l1.end(), l2.begin(), l2.end())); }
Listing 7.33 Der Kleiner-Operator

Der Implementierung des <-Operators macht sich den STL-Algorithmus lexicographical_compare zunutze.
empty, size und max_size

Die Implementierung dieser Methoden ist trivial und bedarf keiner weiteren Ausfhrung:
bool empty(void) const { return(lsize==0); } size_type size() const { return(lsize); } size_type max_size(){ return(allok.max_size()); }
Listing 7.34 Die Methoden zu Grenangaben

Iterator-Funktionen

Auch die Iterator-Funktionen sind von der Implementierung her nicht sonderlich aufregend:
iterator begin(void) { return(iterator(anfang->nach,this)); } iterator end(void) {

390

Die eigene Liste

7.1

return(iterator(ende,this)); }
Listing 7.35 Die Iterator-Funktionen

7.1.4

Iteratoren

Fr die Klasse wurden variable und konstante Iteratoren implementiert. Da sich die beiden Iteratorvarianten nur unwesentlich unterscheiden, werden wir hier nur die Klasse iterator genauer betrachten:
class iterator : public iterator<bidirectional_iterator_tag, Typ> { friend class list<Typ,Allok>; typedef list<Typ, Allok> ListenTyp; typedef Knoten KnotenTyp; private: ListenTyp *liste; KnotenTyp *knoten;

Weil der Iterator auf die Liste zugreifen muss, bekommt er einen Verweis auf sie. Die Iteratoren operieren auf den Knoten der Liste, weil nur sie die bentigten Verkettungsinformationen besitzen:
iterator(KnotenTyp *k, ListenTyp *l){ knoten=k; liste=l; }

Dieser private Konstruktor erzeugt aus einer Knotenposition einen Iterator. Da nur die Liste die Knotenposition kennen kann, darf auch nur sie auf diesen Konstruktor zugreifen:
public: iterator() {}; // // Kopierkonstruktor // iterator(const iterator &i){ knoten=i.knoten; liste=i.liste; }

391

Praxis Adressbuch

// // Zuweisungsoperator // iterator &operator=(const iterator &i){ knoten=i.knoten; liste=i.liste; return(*this); } // // Gleichheitsoperator // bool operator==(const iterator &i) const{ return(knoten==i.knoten); } // // Ungleichheitsoperator // bool operator!=(const iterator &i) const{ return(knoten!=i.knoten); } // // Dereferenzierungsoperator // reference operator*() const{ return(*knoten->daten); } // // Zeigeroperator // pointer operator->() const{ return(knoten->daten); } // // Prinkrementoperator // iterator &operator++(){ if(knoten==liste->ende) return(*this); knoten=knoten->nach; return(*this); }

392

Die eigene Liste

7.1

Die Iteratoren mssen dafr Sorge tragen, dass sie sich nicht auf eine ungltige Position manvrieren. Deswegen bleibt ein Iterator, der die Position end besitzt, nach einem operator++-Aufruf weiterhin auf end. Er verndert seine Position nicht.
// // Prdekrementoperator // iterator &operator--(){ if(knoten==liste->anfang->nach) return(*this); knoten=knoten->vor; return(*this); }

Ein Iterator, der die Position begin inne hat, wird nach einem Aufruf von operator-- immer noch auf begin stehen.
// // Prinkrementoperator // iterator operator++(int){ if(knoten==liste->ende) return(*this); iterator t=*this; knoten=knoten->nach; return(t); } // // Prdekrementoperator // iterator operator--(int){ if(knoten==liste->anfang->nach) return(*this); iterator t=*this; knoten=knoten->vor; return(t); } };
Listing 7.36 Die Klassendefinition von iterator

393

Praxis Adressbuch

7.2

Die Klasse Kontakt

Nachdem der bisherige Teil dieses Praxis-Kapitels sehr viel mit Algorithmik und Konventionen zu tun hatte, kommen wir jetzt wieder zu etwas Greifbarerem, der Klasse Kontakt, deren Objekte einen Eintrag in unserem Telefonbuch darstellen sollen. Formulieren wir zunchst die Anforderungen an die Klasse: Der Einfachheit halber sollen die Objekte aus Vornamen, Nachnamen und Telefonnummer bestehen. Die Daten mssen eingelesen werden knnen. Die Daten mssen ausgegeben werden knnen. Die Daten mssen verndert werden knnen. Zwei Kontakte mssen verglichen werden knnen. Es soll in einem Kontakt gesucht werden knnen.

7.2.1

Die Klassendefinition

class Kontakt { // // Attribute // std::string vorname; std::string nachname; std::string telnummer; public: // // Konstruktoren // Kontakt() { } Kontakt(const std::string& vn, const std::string& nn, const std::string& tn); // // Zugriffsmethoden

394

Die Klasse Kontakt

7.2

// std::string getVorname() const { return(vorname); } std::string getNachname() const { return(nachname); } std::string getTelefonnummer() const { return(telnummer); } void setVorname(const std::string& s) { vorname=s; } void setNachname(const std::string& s) { nachname=s; } void setTelefonnummer(const std::string& s) { telnummer=s; } // // Weitere Methodendeklarationen // void einlesen(); void ausgeben() const; bool enthalten(const std::string& kw) const; bool gleich(const Kontakt& k) const; static int vergleichen(const Kontakt& k1, const Kontakt& k2); };
Listing 7.37 Die Klassendefinition von Kontakt

Die Methoden, die keine nderungen am Objekt vornehmen, sind als konstanzwahrend deklariert, damit sie auch fr konstante Objekte aufgerufen werden knnen. Die kleineren, meist nur aus einer Anweisung bestehenden Methoden wurden direkt in der Klassendefinition definiert und sind damit inline.

395

Praxis Adressbuch

7.2.2

Die Methoden

Dieser Abschnitt bespricht die Methoden, die auerhalb der Klassendefinition definiert wurden.
Konstruktor
Kontakt::Kontakt(const string& vn, const string& nn, const string& tn) : vorname(vn), nachname(nn), telnummer(tn) { }
Listing 7.38 Der Konstruktor von Kontakt

Der Konstruktor verwendet die Elementinitialisierungsliste zur Initialisierung der Attribute. Deshalb bleibt der Anweisungsblock leer. Theoretisch knnten hier noch die Werte der Attribute auf ihre Gltigkeit hin geprft werden, aber das sparen wir uns.
einlesen
void Kontakt::einlesen() { cout << "Vorname :"; getline(cin, vorname); cout << "Nachname :"; getline(cin, nachname); cout << "Telefonnummer:"; getline(cin, telnummer); }
Listing 7.39 Die Methode einlesen von Kontakt

Das Einlesen geschieht denkbar einfach. Der Anwender wird nach dem Vornamen, dem Nachnamen und der Telefonnummer gefragt. Auch hier knnten wieder Prfungen auf Gltigkeit der Werte vorgenommen werden.
ausgeben
void Kontakt::ausgeben() const { cout << nachname << ", " << vorname << ", " << telnummer; }
Listing 7.40 Die Methode ausgeben von Kontakt

396

Die Klasse Kontakt

7.2

Die Methode gibt einfach die drei Attribute ber cout aus. Eine Alternative wre das berladen des <<-Operators. Spter unter .NET wrde die ToStringMethode berschrieben, welche die Zeichendarstellung eines Objekts zurckliefert.
gleich
bool Kontakt::gleich(const Kontakt& k) const { return(vorname==k.vorname && nachname==k.nachname && telnummer==k.telnummer); }
Listing 7.41 Die Methode gleich von Kontakt

Zwei Kontakte sind genau dann gleich, wenn Vorname, Nachname und Telefonnummer bereinstimmen.
vergleichen
int Kontakt::vergleichen(const Kontakt& k1, const Kontakt& k2) { int v=k1.nachname.compare(k2.nachname); if(v!=0) return v; v=k1.vorname.compare(k2.vorname); if(v!=0) return v; return(k1.telnummer.compare(k2.telnummer)); }
Listing 7.42 Die Methode vergleichen von Kontakt

Die statische Methode vergleichen verwendet die compare-Methode von string, die einen negativen Wert zurckliefert, wenn der aufrufende String lexikografisch kleiner ist als der bergebene, einen positiven Wert, wenn der aufrufende String grer ist als der bergebene, und 0, wenn beide Strings gleich sind. Zuerst werden die Nachnamen verglichen. Sollten diese sich bereits unterscheiden, dann wird der von compare gelieferte Wert von der Methode zurckgegeben. Andernfalls werden die Vornamen verglichen.

397

Praxis Adressbuch

Sollte sich dort ein Unterschied zeigen, so wird dieser zurckgegeben. Ansonsten wird im letzten Schritt noch die Telefonnummer verglichen. Durch diese Staffelung wird zuerst nach Nachnamen, bei gleichen Nachnamen noch nach Vornamen und schlielich nach Telefonnummern gruppiert.
enthalten
bool Kontakt::enthalten(const std::string& kw) const { return(vorname.find(kw)!=string::npos || nachname.find(kw)!=string::npos || telnummer.find(kw)!=string::npos); }
Listing 7.43 Die Methode enthalten von Kontakt

Der Methode wird ein String bergeben, und es wird geprft, ob diese Zeichenkette in einem der drei Attribute (Vorname, Nachname und Alter) enthalten ist. Wenn ja, liefert sie true zurck, andernfalls false.

7.3

Die Klasse Kontaktliste

Die Klasse Kontaktliste bernimmt die Verwaltung der Kontakte im Telefonbuch. Sie verwendet die zu Beginn des Kapitels implementierte Listenklasse als Datencontainer. Die Typen sind jedoch ber typedef festgelegt, ein Wechsel zu einem anderen STL-konformen Datencontainer wre damit kein Problem. Die Klasse soll folgende Funktionen untersttzen: Kontakte sollen hinzugefgt werden knnen. Kontakte sollen entfernt werden knnen. Die Kontakte sollen aufgelistet werden knnen. Die Kontakte sollen sortiert werden knnen. Es soll nach einem Kontakt gesucht werden knnen.

7.3.1

Die Klassendefinition

Betrachten wir zunchst die Klassendefinition:


class Kontaktliste { public:

398

Die Klasse Kontaktliste

7.3

// // Typdefinitionen // typedef list<Kontakt> containertyp; typedef containertyp::iterator iterator; typedef containertyp::const_iterator const_iterator; // // Attribute // private: containertyp liste; // // Methodendeklarationen // public: void hinzufuegen(const Kontakt& k); void vertauschen(iterator& i1, iterator& i2); void sortieren(); Kontakt* suchen(const std::string& kw, int pos=0); int indexVon(const Kontakt& k) const; bool entfernen(int idx); void auflisten() const; };
Listing 7.44 Die Klassendefinition von Kontaktliste

Die Klassendefinition ist eigentlich selbsterklrend. Die Bedeutung der Methodenkpfe wird bei der detaillierten Besprechung der Methoden noch klarer.

7.3.2

Die Methoden

Gehen wir die Methoden der Klasse durch.


hinzufuegen
void Kontaktliste::hinzufuegen(const Kontakt& k) { liste.push_back(k); }
Listing 7.45 Die Methode hinzufuegen von Kontaktliste

399

Praxis Adressbuch

Diese Methode ist ein Klacks, weil die ganze Arbeit des Hinzufgens vom Container bernommen wird.
auflisten
void Kontaktliste::auflisten() const { const_iterator pos=liste.begin(); int akt=0; while(pos!=liste.end()) { cout << setw(3) << akt << " "; pos->ausgeben(); cout << endl; ++pos; ++akt; } }
Listing 7.46 Die Methode auflisten von Kontaktliste

Diese Methode verwendet einen Iterator (pos), um die im Datencontainer gespeicherten Kontakte zu durchlaufen. Um den Index ausgeben zu knnen, zhlt zustzlich noch die int-Variable akt mit. Initialisiert wird der Iterator mit liste.begin(), dem Iterator auf das erste Element des Containers. Die Schleife luft so lange, bis pos die Position liste.end() erreicht hat, die Position hinter dem letzten Element des Containers. Innerhalb der Schleife wird der Manipulator setw aus der Header-Datei iomanip verwendet, der fr die nchste Ausgabe die angegebene Breite reserviert. Das hat in diesem Fall zur Folge, dass die ausgegebenen Indizes immer korrekt untereinanderstehen, egal, ob er ein, zwei oder drei Zeichen breit ist. Zur Ausgabe des Kontakts wird dessen ausgeben-Methode aufgerufen.
entfernen
bool Kontaktliste::entfernen(int idx) { // // Negativer Index ist ungltig // if(idx<0) return(false); // // Mit Index und Iterator den Container durchlaufen,

400

Die Klasse Kontaktliste

7.3

// bis entweder der Index des zu lschenden Elements // oder das Ende des Containers erreicht ist // iterator it=liste.begin(); int akt=0; while(it!=liste.end() && akt!=idx) { ++it; ++akt; } // // Wenn Index gefunden, dann lschen // if(it!=liste.end()) { liste.erase(it); return(true); } else { return(false); } }
Listing 7.47 Die Methode entfernen von Kontaktliste

Die Position des zu lschenden Kontakts wird ber den Index mitgeteilt. Das macht es etwas schwieriger, weil eine Liste keinen wahlfreien Zugriff ber einen Indexoperator gestattet. Stattdessen mssen wir sequenziell durch die Liste laufen und den Index mitzhlen, bis wir den zu lschenden Kontakt erreicht haben. Im Prinzip ist es dieselbe Idee wie bei der Ausgabe der Kontakte, nur dass hier nicht bis zum Containerende, sondern bis zum zu lschenden Kontakt traversiert wird.
suchen
Kontakt* Kontaktliste::suchen(const string& kw, int pos) { iterator it=liste.begin(); int akt=0; while(it!=liste.end()) { if(akt>=pos && it->enthalten(kw)) return(&*it); ++it; ++akt; }

401

Praxis Adressbuch

return(nullptr); }
Listing 7.48 Die Methode suchen von Kontaktliste

Auch suchen verwendet eine mit entfernen und auflisten vergleichbare Traversionstechnik. Nur ist jetzt neben dem Ende des Datencontainers das zweite Abbruchkriterium das Finden eines Kontakts mit dem entsprechenden Suchstring. Ein weiterer Unterschied ist noch in der Art zu finden, wie das zweite Abbruchkriterium implementiert wurde. Es liegt nicht als Schleifenbedingung vor, sondern wird innerhalb der Schleife mit if formuliert. Der Abbruch wird durch das return ausgefhrt. Der zweite Funktionsparameter pos gibt an, ab welchem Index erst mit der Suche begonnen werden soll. Da eine Liste nur sequenziell traversiert werden kann, mssen wir immer vorne anfangen, auch wenn erst ab dem zehnten Element gesucht werden soll. Die Methode liefert einen Zeiger zurck, um eine Mglichkeit zu haben, ein nicht gefunden an den Aufrufer zurckzugeben. Sollte ein Kontakt gefunden werden, wird dessen Adresse zurckgegeben, andernfalls nullptr.
vertauschen
void Kontaktliste::vertauschen(iterator& i1, iterator& i2) { Kontakt tmp=*i1; *i1=*i2; *i2=tmp; }
Listing 7.49 Die Methode vertauschen von Kontaktliste

Diese Methode ist eigentlich nur eine Hilfsmethode fr das gleich vorgestellte sortieren. Sie knnte auch gut privates Zugriffsrecht bekommen. Ihre Aufgabe ist einfach: Sie vertauscht die beiden Kontakte, deren Position mit den beiden bergebenen Iteratoren bestimmt werden.
sortieren
void Kontaktliste::sortieren() { // // Nur Listen mit mehr als einem Element sortieren //

402

Die Klasse Kontaktliste

7.3

if(liste.size()>1) { bool vertauscht; // // Wiederhole, solange vertauscht wurde // do { vertauscht=false; iterator cur=liste.begin(); iterator suc=cur; ++suc; // // Container durchlaufen, paarweise vergleichen // und ggfs. vertauschen // while(suc!=liste.end()) { if(Kontakt::vergleichen(*cur,*suc)>0) { vertauschen(cur, suc); vertauscht=true; } ++cur; ++suc; } } while(vertauscht); } }
Listing 7.50 Die Methode sortieren von Kontaktliste

Die sortieren-Methode implementiert das Sortierverfahren Bubblesort, eines der einfachsten, die existieren. Das Prinzip ist einfach. Eine Schleife durchluft den Container und vergleicht dabei zwei Kontakte paarweise. Sollten diese beiden Kontakte nicht in der richtigen Reihenfolge zueinander stehen, dann werden die beiden vertauscht. So luft die Schleife durch den Container und vertauscht alle Paare, die nicht in der richtigen Reihenfolge stehen. Sollte die Schleife eine Vertauschung vorgenommen haben, dann war der Container noch nicht sortiert und die Schleife nimmt erneut die Arbeit auf, so lange, bis keine Vertauschung mehr stattgefunden hat. Dann ist der Container sortiert.

403

Praxis Adressbuch

Dieses Sortierverfahren lsst sich mit bidirektionalem Zugriff, wie ihn nur Listen erlauben, problemlos implementieren. Fr performantere Verfahren wie Quicksort wre wahlfreier Zugriff notwendig.
indexVon
int Kontaktliste::indexVon(const Kontakt& k) const { const_iterator it=liste.begin(); int akt=0; while(it!=liste.end()) { if(k.gleich(*it)) return(akt); ++it; ++akt; } return(-1); }
Listing 7.51 Die Methode indexVon von Kontaktliste

Die Methode indexVon bietet dem Anwender die Mglichkeit, den Index eines Kontakts zu ermitteln. Er kann ber diesen Umweg einen mit suchen gefundenen Kontakt lschen (indem er mit indexVon dessen Index bestimmt und diesen dann entfernen bergibt).

7.4

Die Hauptfunktion

Die Hauptfunktion stellt ein kleines Men auf Textbasis bereit, ber das die einzelnen Methoden der Kontaktliste aufgerufen werden knnen:
int main() { // // Objekte anlegen // Kontaktliste kl; int eingabe; // // TextMen // do { cout << "Telefonbuch V0.01\n---------------------" << endl; cout << "1 Kontakt hinzufuegen" << endl;

404

Die Hauptfunktion

7.4

cout cout cout cout cout

<< << << << <<

"2 "3 "4 "5 "0

Kontakte auflisten" << endl; Kontakt entfernen" << endl; Kontakt suchen" << endl; Kontakte sortieren" << endl; Programm beenden" << endl;

cout << "Ihre Wahl:"; cin >> eingabe; cin.ignore(); // // Hinzufgen // if(eingabe==1) { Kontakt k; k.einlesen(); kl.hinzufuegen(k); } // // Auflisten // if(eingabe==2) { kl.auflisten(); cout << endl; } // // Entfernen // if(eingabe==3) { cout << "Zu entfernende Position:"; int pos; cin >> pos; cin.ignore(); if(kl.entfernen(pos)) cout << "Kontakt entfernt!" << endl; else cout << "Entfernen nicht moeglich!" << endl; cout << endl; } // // Suchen

405

Praxis Adressbuch

// if(eingabe==4) { cout << "Wonach soll gesucht werden:"; string suche; getline(cin,suche); Kontakt* kptr=kl.suchen(suche,2); if(kptr!=nullptr) { kptr->ausgeben(); cout << endl; } else cout << "Keinen passenden Kontakt gefunden" << endl; cout << endl; } // // Sortieren // if(eingabe==5) { kl.sortieren(); } } while(eingabe!=0); }
Listing 7.52 Die Hauptfunktion des Telefonbuchs

Die einzelnen if-Blcke htten es der Modularitt wegen grundstzlich verdient, als eigene Funktionen implementiert zu werden. Dieses Praxisbeispiel muss aber nicht noch weiter aufgebohrt werden.

406

TEIL II C++/CLI

Dieses Kapitel stellt den Aufbau des .NET-Frameworks vor und zeigt die in C++/CLI wiederzufindenden notwendigen Ergnzungen von ANSI C++.

Grundlagen von C++/CLI

Wir betreten nun das Land der Microsoft-Erweiterungen, die ANSI C++ zu einer .NET-kompatiblen Sprache werden lassen, C++/CLI. Aber warum .NET? Warum nicht weiter ANSI C++? Und wenn schon eine Sprache mit Laufzeitumgebung, warum dann nicht Java? Java ist eine Kombination aus Sprache und Framework, aus diesem Grund mssten zwei Vergleiche angestellt werden: Java versus C++ und Java versus .NET. Solche Vergleiche sind nach meinen Recherchen bisher noch nicht objektiv gefhrt worden und werden wahrscheinlich auch nicht gefhrt werden knnen. Auerdem wird ein Buch das behandelte Thema tendenziell in einem besseren Licht erscheinen lassen. Hier sollen daher die positiven Eigenschaften von C++ und .NET hervorgehoben werden, whrend die Wahrung des Rufs der hier Benachteiligten ein anderes Buch bernehmen kann.

8.1

C++/CLI

Eine reine C++-Anwendung ist blicherweise performanter als Java. Darber hinaus kann in C++ hardwarenher programmiert werden, weswegen Treiber oder an die Grenze der Rechnerleistung gehende Spiele so gut wie nie in Java geschrieben werden. Zum Thema Performanz vielleicht noch ein Zitat von der Homepage des JCreator, einer Java-Entwicklungsumgebung: JCreator is written entirely in C++, which makes it fast and efficient compared to the Java based editors/IDE's. Ein weiterer, groer Vorteil von C++ gegenber den anderen Sprachen des Visual Studios ist die Mglichkeit, mit ihr als einziger Sprache auch von .NET unabhngige Programme schreiben zu knnen (was wir bisher ja auch getan haben).

409

Grundlagen von C++/CLI

8.2

.NET

Wenn .NET mit Java verglichen werden soll, dann fallen zunchst einmal viele hnlichkeiten auf. Nun wre es gerade bei Microsoft naiv, anzunehmen, dass diese hnlichkeiten zuflliger Natur sind. Java hat seine Verbreitung zum einen seiner Plattformunabhngigkeit zu verdanken. Egal, auf welchem Rechner eine Java-Anwendung entwickelt wurde, sie luft berall dort, wo die Java-Laufzeitumgebung vorhanden ist. Ein weiterer Vorteil liegt in der im Vergleich zum heimlichen Vorbild C++ strkeren objektorientierten Ausprgung. Drittens ist Java bezogen auf C++ mit einer geradezu allmchtigen Klassenbibliothek gesegnet. Der Entscheidung, ein strker auf Windows fokussiertes Konkurrenzprodukt zu entwickeln, verdankt .NET seine Existenz. Dabei muss Microsoft zugute gehalten werden, dass die Entwicklungsarbeit bei Weitem nicht mit dem Kopieren der Konkurrenztechnologie aufhrte, sondern verstrkt in die Verbesserung bestehender und die Entwicklung neuer Anstze gesteckt wurde. Dabei entstand ein Framework, welches u. a. folgende Eigenschaften besitzt: Objektorientierung pur: Alles in .NET ist auf eine Klasse zurckzufhren, jedes Objekt, jede Funktion (die es unter .NET nur noch als statische Methoden gibt), jede Variable. Es besteht keine Wahlmglichkeit mehr zwischen unterschiedlichen Programmierparadigmen entweder objektorientiert oder gar nicht. Sprachunabhngigkeit: Unter .NET ist es unerheblich, welche Programmiersprache verwendet wird, solange sie .NET-konform ist. Mehr noch: Projekte knnen in verschiedenen Programmiersprachen geschrieben sein. Weil jedes Programm in eine Zwischensprache bersetzt wird, kann beispielsweise ein C++-Entwickler eine in C# geschriebene Klasse ohne Einschrnkungen verwenden. Die C#-Klasse greift vielleicht auf eine VB.NET-Klasse zu. Es gibt sogar Bestrebungen nach einem Java.NET. Performanz: Im Gegensatz zu Java, dass auf allen Plattformen luft und damit gewissermaen auf den kleinsten gemeinsamen Nenner dieser Plattformen aufbauen muss, kann .NET direkt die bereits vorhandene Win32-API und alle ihre Features verwenden. Begrenzte Plattformunabhngigkeit: .NET-Anwendungen sind bezogen auf die einzelnen Windows-Versionen uneingeschrnkt plattformunabhngig. Fr Windows-Entwickler ist damit schon viel gewonnen. Es gibt

410

.NET

8.2

zwar Projekte, eine .NET-Laufzeitumgebung auch auf anderen Systemen wie Linux zu entwickeln. Eine vollstndige Kompatibilitt ist bisher aber noch nicht erreicht und wird auch durch die Tatsache erschwert, dass .NET viele windowstypische Elemente besitzt. Verwalteter Code: Eine .NET-Anwendung luft vergleichbar mit Java in einer Laufzeitumgebung. Diese Laufzeitumgebung (CLR) berwacht Sicherheitsrichtlinien, Speicherverwaltung etc. Das Programm luft sozusagen abgeschottet vom System in einer Box, die den Zugriff auf das restliche System reglementiert. Abbildung 8.1 zeigt die wichtigsten .NET-Komponenten, die nachstehend detaillierter vorgestellt werden.
Das .NET-Framework Common Language Runtime (CLR) Common Language Specification (CLS) Common Type System (CTS) WPF
Abbildung 8.1 Das .NET- Framework

Klassenbibliothek System-Klassen Windows-Forms ASP.NET GDI+ ADO.NET

Es gibt leider nicht die eine Aufteilung des .NET-Frameworks in seine Komponenten. Was fr den einen schon zu detailliert, ist fr den anderen zu stark zusammengefasst. Auch die Zuordnung einzelner Klassen zu einzelnen Gruppen mag je nach Betrachtungsschwerpunkt variieren.

8.2.1

Common Language Runtime (CLR)

Die CLR ist das Herzstck von .NET, denn sie ist der Teil, der die .NETAnwendungen ablaufen lsst. Schauen wir uns zum genaueren Verstndnis in Abbildung 8.2 die Kompilation einer .NET-Anwendung an. Wie bereits erwhnt wurde, spielt es bis auf kleinere Aspekte keine Rolle, in welcher Sprache die .NET-Anwendung geschrieben wurde, solange sie kompatibel mit der CLS ist.

411

Grundlagen von C++/CLI

CLS-kompatible Sprache C++/CLI C# VB.NET Andere

Kompilation zur Entwicklungszeit

MSIL

Assembly in Form einer .exe- oder .dll-Datei

Kompilation durch einen JIT-Compiler

Nativer Code Common Language Runtime (CLR)


Abbildung 8.2 Kompilation einer .NET-Anwendung

Der Quellcode der verwendeten Programmiersprache wird durch Kompilation zur Kompilationszeit in einen Zwischencode, der MSIL (Microsoft Intermedia Language) oder kurz IL genannt wird, bersetzt. Das Ergebnis dieser bersetzung wird Assembly genannt. Die Assembly mit ihrem Zwischencode ist von auen nicht von einer herkmmlichen exe- oder dll-Datei zu unterscheiden. Wenn eine Assembly gestartet wird, dann wird sie der Obhut der CLR bergeben, die zunchst den Zwischencode mit einem JIT-Compiler in nativen, auf der aktuellen Plattform ausfhrbaren Code kompiliert.
JIT-Compiler JIT ist die Abkrzung fr just in time und bezeichnet Compiler, die einen Code bei Bedarf kompilieren.

Dabei wird nicht zwangslufig das gesamte Programm kompiliert, sondern je nach Bedarf auch nur einzelne Teile. Das Kompilat wird fr schnellere zuknftige Zugriffe zwischengespeichert. Nach Wunsch kann die Kompilation auch direkt bei der Installation der Anwendung durchgefhrt werden, so dass auch beim Erststart keine Verzgerungen auftreten. Whrend der Ausfhrung des kompilierten Codes stehen dem Programm diverse Dienste zur Verfgung, wobei es von anderen Diensten der CLR berwacht wird. Dazu zhlen u. a.:

412

.NET

8.2

Garbage Collection: Ein Dienst, der permanent nach nicht mehr bentigten Objekten Ausschau hlt und diese freigibt. Ihm ist es zu verdanken, dass die Freigabe von dynamisch reservierten Objekten nicht mehr zu den Aufgaben des Programmierers zhlt. Eine Annehmlichkeit, die Java-Programmierer zu schtzen wissen. Typenkontrolle: Es wird sichergestellt, dass ein Verweis eines bestimmten Typs auch tatschlich nur auf Objekte dieses Typs verweist. So wird eine auerordentlich hohe Typsicherheit erreicht. Sicherheit: Es ist mglich, zu konfigurieren, wer eine Assembly aufrufen oder von wo aus sie aufgerufen werden darf und welche Rechte sie dadurch erhlt. Beispielsweise kann der Zugriff auf die Registry untersagt werden etc. Ausnahmebehandlung Interoperabilitt zwischen verwaltetem und nicht verwaltetem Code

8.2.2

Common Language Specification (CLS)

Die CLS definiert die an eine .NET-konforme Programmiersprache gestellten Anforderungen. Dabei geht es nicht um die Syntax, sondern um die Eigenschaften der Sprache. Diese Anforderungen gelten nur fr die Teile der Assembly, die offen gelegt sind, auf die also in anderen Sprachen programmierte Anwendungen zugreifen knnten. Die innerhalb der Assembly gekapselten Komponenten sind davon nicht betroffen. So ist es beispielsweise in C++ weiterhin mglich, Funktionen zu schreiben. Nur knnen diese nicht von anderen .NET-Sprachen aufgerufen werden. Damit Sie eine Vorstellung erhalten, um welche Art von Anforderungen es sich handelt, werden einige von Ihnen hier aufgefhrt: Alle Bezeichner in einem Bezugsrahmen mssen eindeutig sein. Einzige Ausnahme ist das berladen des Bezeichners (siehe Abschnitt 4.7, berladen von Methoden). Auch unterschiedliche Elementarten (z. B. Attribut und Methode) drfen nicht denselben Bezeichner besitzen. Es reicht nicht aus, wenn sich zwei Bezeichner nur in der Gro- und Kleinschreibung unterscheiden (fr VB-Programmierer eine Freude, fr manchen C++-Programmierer ein herber Schlag). Ein Array darf nur CLS-kompatible Datentypen enthalten, und seine Indizes mssen bei 0 beginnen. berladene Methoden mssen sich in ihrer Parameterliste unterscheiden.

413

Grundlagen von C++/CLI

Einige Sprachen erfllen in ihrer Grundform nicht alle Eigenschaften und mssen fr .NET angepasst oder erweitert werden. Die Indizes von Visual Basic (VB) beispielsweise beginnen bei 1. Die .NET-Version von VB muss die CLS erfllen und die Indizes daher bei 0 beginnen lassen. Auch C++ ist in der ANSI-Form nicht CLS-konform. Einige Eigenschaften werden nicht untersttzt und mussten daher in C++/CLI hinzugefgt werden.

8.2.3

Common Type System (CTS)

Das CTS beschreibt, wie die Datentypen in .NET aufgeteilt sind. Abbildung 8.3 zeigt die Aufteilung hierarchisch.

Typ
Werttypen Verweistypen

Zeigertypen Integrierte Werttypen Benutzerdefinierte Werttypen Enumerationen (Aufzhlungen)

Selbstbeschreibende Typen

Schnittstellen -Typen (Interfaces)

Klassentypen

Felder (Arrays)

Benutzerdefinierte Klassen

Gekapselte Werttypen

Delegaten

Abbildung 8.3

Aufteilung der CTS-Typen

Jeder Typ in .NET ist entweder ein Werttyp oder ein Verweistyp. Werttypen sind Datentypen, deren Objekte statisch auf dem Stack angelegt werden. Auf diese Objekte kann direkt zugegriffen werden. Zu diesen Typen gehren: Integierte Werttypen: Dazu zhlen die ganzzahligen und FliekommaDatentypen sowie char und bool. Benutzerdefinierte Werttypen: Sie werden in Abschnitt 9.6, Wertklassen, besprochen. Aufzhlungen: Sie sind Thema in Abschnitt 9.9, Aufzhlungen.

414

.NET

8.2

Unter Verweistypen versteht man Datentypen, deren Objekte nicht direkt, sondern ber einen Verweis angesprochen werden. Eine Mglichkeit, in ANSI C++ solche Verweise zu realisieren, sind Zeiger (siehe Abschnitt 3.4, Zeiger). Verweistypen werden dynamisch (in ANSI C++ mit new) auf dem Heap angelegt. Hierzu zhlen: Zeigertypen: Unter .NET werden in C++ besondere Zeiger, Trackinghandles genannt, verwendet, mit denen sich Abschnitt 8.5, Trackinghandle, befasst. Arrays: Werden fr ANSI C++ in Abschnitt 3.1, Arrays, abgehandelt. Die C++/CLI-Variante wird in Abschnitt 8.8, Arrays, erklrt. Benutzerdefinierte Klassen: Die ANSI-Form wurde bereits in Kapitel 4, Klassen, erlutert, die Besonderheiten unter .NET erfahren Sie in Kapitel 9, .NET-Klassen. Schnittstellentypen: Wie in ANSI C++ Schnittstellen simuliert werden, ist Thema in Abschnitt 4.19.2, Rein abstrakte Klassen. Echten Schnittstellen, wie sie in .NET gebruchlich sind, widmet sich Abschnitt 9.14, Schnittstellen. Gekapselte Werttypen: Weil in .NET alles auf Objekte zurckzufhren ist, mssen die Werttypen in Objekte gekapselt werden knnen. Dieser Vorgang wird Boxing genannt. Delegaten: Die Eigenschaft, auf Methoden einer Klasse zu verweisen, stellt Abschnitt 9.15, Delegaten, vor. Folgende konkrete Typen stehen uns unter C++/CLI zur Verfgung:
C++/CLI
bool char unsigned char Short unsigned short int oder long unsigned int oder unsigned long long long

.NET
System::Boolean System::SByte System::Byte System::Int16 System::UInt16 System::Int32 System::UInt32

CLS ja nein ja ja nein ja nein ja

Wertebereich
true, false

[-128, 127] [0, 255] [-32.768, 32.767] [0, 65.535] [-231, 231-1] [0, 232-1] [-263, 263-1]

System::Int64

Tabelle 8.1

Die C++-Typen unter .NET

415

Grundlagen von C++/CLI

C++/CLI
unsigned long long float double System::Decimal

.NET
System::UInt64 System::Single System::Double System::Decimal

CLS nein ja ja ja ja ja ja

Wertebereich [0, 264-1] [+/1,5*10-45, +/3,4*1038] [+/5,0*10-324, +/1,7*10308] [+/1,0*10-27, +/7,9*1027] bei 28 Ziffern Genauigkeit Unicode-Zeichen 231 Unicode-Zeichen universell

wchar_t System::String^ System::Object^

System::Char System::String System::Object

Tabelle 8.1

Die C++-Typen unter .NET (Forts.)

Die Spalte C++/CLI listet den Datentyp auf, wie er unter C++/CLI verwendet wird. Das Zeichen ^ wird als Trackinghandle bezeichnet und in Abschnitt 8.5 erklrt. Die Spalte .NET bezeichnet die dazugehrige .NET-Klasse. Um eine Kompatibilitt zu CLS zu gewhrleisten, mssen verwendete Datentypen, die nicht innerhalb einer Klasse gekapselt und damit von auen zugnglich sind, CLS-kompatibel sein, in der Spalte CLS muss also ja stehen. Eine CLS-kompatible Sprache muss alle CLS-kompatiblen Datentypen untersttzen.

8.2.4

Klassenbibliothek

Wenn Sie bereits hufiger in C++ programmiert haben und sich nun mit C++/ CLI beschftigen wollen, dann wird Ihnen die .NET-Klassenbibliothek den Atem rauben. Sie ist so mchtig, dass so heit es ein einzelner Mensch sie nie wird vollstndig beherrschen knnen, und lsst so gut wie keine Wnsche offen. Gerade im Vergleich zur C++-Standardbibliothek ist sie ein regelrechtes Wunderwerk an Mglichkeiten. In Kombination mit der auch unter .NET verfgbaren STL bieten sich enorme Mglichkeiten. Die .NET-Klassenbibliothek ist thematisch in Namensbereiche aufgeteilt, die wiederum Namensbereiche enthalten usw. Fr einen groben berblick werden die Hauptbestandteile der Bibliothek kurz vorgestellt: System-Klassen: Dieser Bereich deckt die Klassen fr die alltgliche Arbeit ab und ist u. a. unterteilt in String-Klassen, mathematische Klassen, Konsolenoperationen, Dateiverwaltung etc.

416

.NET

8.2

Windows Forms: Diese Klassen stellen die grafischen Elemente fr die Oberflchenprogrammierung zur Verfgung, wie Fenster, Schaltflchen, Bildlaufleisten, Mens, Statusleisten, Werkzeugleisten, Ansichten etc. GDI+: Hier finden Sie die Klassen zum direkten Zeichnen, z. B. in ein Fenster oder zum spteren Drucken. ADO.NET: Beinhaltet die fr die Datenbankanbindung und -verwaltung notwendigen Klassen. ASP.NET: Die hier enthaltenen Klassen untersttzen den Entwurf dynamischer Webseiten. Die Dynamik der Webseiten kann mit einer beliebigen .NET-Sprache programmiert werden. Im weiteren Verlauf des Buchs werden wir in einige oben vorgestellte Bereiche hineinschnuppern. Das gesamte .NET ist viel zu mchtig, um in einem Buch erschpfend behandelt zu werden. Daher kann hier auch nur eine Auswahl an Klassen vorgestellt werden. Fr weitergehende Informationen sei auf die Dokumentation zu .NET hingewiesen, die ber den Menpunkt Hilfe Index geffnet wird.

8.2.5

WPF

Die WPF (Windows Presentation Foundation) stellt ein neues Konzept der Oberflchengestaltung dar. Die Windows Forms erzeugen eine Oberflche, indem von den entsprechenden Klassen Objekte erzeugt und diese dann wie ein Baukasten zusammengesetzt werden, wie in einer objektorientierten Umgebung auch nicht anders zu erwarten. Der Nachteil dieser Vorgehensweise liegt zum einen darin, dass eine nderung der Oberflche auch eine nderung des Programmcodes zur Folge hat und daher Programmteile neu kompiliert werden mssen. Zum anderen bedeutet eine Beschreibung der Oberflche mit Programmcode auch immer eine Sprachabhngigkeit. Diese Nachteile vermeidet die WPF, indem sie eine eigene Beschreibungssprache namens XAML (Extensible Application Markup Language) bereitstellt, die in ihrer Struktur hnlich XML ist. Die WPF ist im Begriff, die Oberflchengestaltung mittels Windows Forms abzulsen. Dieser Prozess ist in C# schon einen guten Schritt vorangegangen, unter C++/CLI spielt sie noch keine wesentliche Rolle.

417

Grundlagen von C++/CLI

8.3

CLR-Konsolenanwendung

Die ersten Schritte in die C++/CLI-Welt unternehmen wir mit einer CLR-Konsolenanwendung. Wie in Abbildung 8.4 zu sehen, wird dazu die Vorlagengruppe CLR und dort die CLR-Konsolenanwendung ausgewhlt.

Abbildung 8.4

Erstellen einer CLR-Konsolenanwendung

Im obigen Fall wurde ein Projekt mitsamt Projektmappe erstellt, es ist aber auch ohne Weiteres mglich, ein CLR-Projekt in einer Projektmappe zu erstellen, die bisher nur Win32-Konsolenprojekte beinhaltet. Das durch Klicken auf OK erzeugte Projekt ist in Abbildung 8.5 zu sehen.

Abbildung 8.5

Die erzeugte CLR-Konsolenanwendung

418

Das Beispielprogramm

8.4

8.3.1

Die Projekt-Dateien

Die erstellte Anwendung kommt gleich mit einigen Dateien daher, deren Relevanz und Bedeutung kurz angesprochen werden soll: Die Datei app.ico enthlt das fr die Applikation verwendete Icon. Die Ressourcedatei app.rc kann mit Ressourcen wie Zeichenketten bestckt werden, die sich mit der Express-Version von Visual C++ nicht bearbeiten lassen. Die beiden stdafx-Dateien sind notwendig fr das von Visual C++ untersttzte Prinzip der vorkompilierten Header-Dateien. Wird beispielsweise eine Header-Datei in viele andere Dateien eingebunden, selbst aber kaum gendert, dann bietet es sich an, diese Datei in stdafx.h einzubinden. Sie wird dadurch beim ersten Mal kompiliert und liegt ab dann in kompilierter Form vor. Die Buildzeiten knnen sich durch intelligenten Einsatz der vorkompilierten Header gerade bei groen Projekten auffallend verbessern. Beachten Sie, dass die Datei stdafx.h in jeder cpp-Datei zu Beginn eingebunden werden muss. In AssemblyInfo.cpp finden sich Informationen ber die Assembly (Autor, Version, Copyright etc.), die nach Wunsch gendert werden knnen. Die Datei HelloNet.cpp schlielich enthlt den bisherigen Code der Anwendung, der einer genaueren Betrachtung bedarf.

8.4

Das Beispielprogramm

Das explizite return am Ende von main ist nicht notwendig und kann entfernt werden. Unter .NET wird mit Unicode-Zeichen gearbeitet. Unicode-Zeichen knnen nicht nur 256, sondern 65.536 verschiedene Zeichen darstellen und sind damit in der Lage, die Schriftzeichen nahezu jeder Sprache zu reprsentieren. Daher knnen Bezeichner jetzt auch Umlaute enthalten. Das L vor der konstanten Zeichenkette ist in C++/CLI nur wichtig, wenn eine Zeichenkette einem nativen String zugewiesen wird, und kann bei .NET-Strings entfernt werden. Das Programm sieht nun so aus:
#include "stdafx.h" using namespace System; int main(array<System::String ^> ^args)

419

Grundlagen von C++/CLI

{ Console::WriteLine("Hello World"); }
Listing 8.1 Das HelloWorld-Programm der CLR-Anwendung

8.4.1

stdafx

Das Einbinden von stdafx.h muss in jeder Quellcodedatei als erste Amtshandlung vollzogen werden und dient der Untersttzung vorkompilierter Header-Dateien.

8.4.2

Namensbereich System

Der Namensbereich System ist der oberste Namensbereich aller .NET-Komponenten und vergleichbar mit std unter ANSI C++. Ihn mittels using namespace global verfgbar zu machen, ist in Quellcodedateien auerordentlich sinnvoll.

8.4.3

main

Auch eine CLR-Konsolenanwendung besitzt eine main-Funktion, nur die Parameterliste unterscheidet sich ein wenig von der ANSI C++-Variante. Die Funktion bekommt einen Verweis auf ein Array mit String-Verweisen bergeben. Die verwiesenen Strings beinhalten mgliche Parameter, die beim Aufruf des Programms angegeben wurden. Wie genau Verweise und Arrays unter .NET funktionieren, wird in spteren Abschnitten erklrt.

8.4.4

WriteLine

Die main-Funktion beinhaltet nur eine einzige Anweisung:


Console::WriteLine("Hello World");

Mit unserem bisherigen Wissen kann schon einiges von dieser Anweisung erklrt werden: Bei WriteLine muss es sich wegen der Parameterliste um eine Funktion innerhalb eines Namensbereichs oder um eine Methode innerhalb einer Klasse handeln. Nun ist .NET vollstndig objektorientiert und untersttzt keine Funktionen, also muss es eine Methode sein.
Console kann kein Objekt sein, denn sonst mssten wir auf WriteLine mit

dem Elementzugriffsoperator . zugreifen. Ein Zeiger (siehe Abschnitt 3.4.5,

420

Trackinghandle

8.5

Zeiger auf Struktur- und Klassenobjekte) fllt auch flach, denn mit ihm mssten wir den Zeigeroperator -> verwenden. Der Bezugsrahmenoperator :: zeigt klar den Zugriff auf ein statisches Klassenelement (siehe Abschnitt 4.8, Statische Klassenelemente) an. Es handelt sich also um die statische Methode WriteLine der Klasse Console. Sie hat unter .NET die gleiche Bedeutung wie cout in ANSI C++, die Ausgabe auf die Konsole. Der Name WriteLine suggeriert, dass es sich um die Ausgabe einer Zeile handelt, also wie cout mit abschlieendem endl. Es existiert auch noch die statische Methode Write, die eine Zeile nicht beendet.

8.5

Trackinghandle

.NET unterscheidet zwei Arten von Typen: Wert- und Verweistypen. Werttypen sind wie blich zu definieren (Tabelle 8.1 listet u. a. die zur Verfgung stehenden elementaren Werttypen auf):
short a; int b; long long c; // 2 Bytes gro // 4 Bytes gro // 8 Bytes gro

Verweistypen unter .NET sind verwaltete Typen. Die CLR sorgt dafr, dass Objekte nach ihrem Gebrauch gelscht werden. Sie kann aber auch ein Objekt im Speicher verschieben, wenn es Vorteile bringt. Fr den Programmierer heit das, die Adresse des Objekts kann sich ndern. Ist die ursprngliche Adresse in einem Zeiger gespeichert (und das wird sie bei einem Verweistyp), dann zeigt der Zeiger auf einen Speicherbereich, an dem sich das Objekt nicht mehr befindet. Ein Zugriff htte fatale Folgen. Um diese Problematik zu umschiffen, wird ein anderer Mechanismus bentigt, der sich Trackinghandle nennt. Ein Trackinghandle auf String-Objekte sieht so aus:
String^ s;

Trackinghandle Ein Trackinghandle ist das C++/CLR-quivalent eines Zeigers und wird mit ^ definiert.

Anstelle des bei herkmmlichen Zeigern blichen * wird zur Definition eines Trackinghandles das Dach ^ verwendet. Nun knnen wir dem Trackinghandle eine Zeichenkette zuweisen:
s="Andr Willms";

421

Grundlagen von C++/CLI

Aus der Zeichenkette wird automatisch ein String-Objekt erzeugt. Der Zugriff auf die Elemente des Objekts erfolgt, wie gewohnt, ber den Zeigeroperator. Die folgende Anweisung gibt die Anzahl der Zeichen im String aus:
Console::WriteLine(s->Length);

Nach unserem Kenntnisstand msste es sich bei Length um ein Attribut handeln. Eine Methode kann es aufgrund der fehlenden Parameterliste nicht sein. Bevor Sie sich aber wegen eines vermeintlichen Verlusts der Datenkapselung an die Stirn fassen, kann Entwarnung gegeben werden. Es liegt hier ein neues Klassenelement namens Eigenschaft vor. Eigenschaften werden in Abschnitt 9.3, Eigenschaften, genauer unter die Lupe genommen. Im Gegensatz zu herkmmlichen Zeigern ist das Trackinghandle in der Lage, das Verschieben des Objekts, auf das es verweist, mitzuverfolgen und verweist damit immer auf das gltige Objekt. Zeiger, die auf nichts zeigen, werden mit dem Wert 0 initialisiert. Bei Trackinghandles ist dies nullptr:
s = nullptr;

Das Trackinghandle s verweist nun offiziell auf nichts mehr.

8.6

Trackingreferenz

Manchmal ist es notwendig, einen Verweis auf ein Trackinghandle zu besitzen. Nehmen wir folgende Funktion:
void test(String^ x) { x="C++/CLI"; }

Diese Funktion wird mit folgendem Codefragment aufgerufen:


String^ s="C++"; test(s); Console::WriteLine(s);

Was wird ausgegeben? Natrlich C++, weil die Zuweisung an x in test nur das x betrifft, aber keine Auswirkung auf s hat. Was aber, wenn innerhalb von test das Trackinghandle s verndert werden soll? Dazu bentigen wir eine Referenz auf ein Trackinghandle, also eine Trackingreferenz. Eine Trackingreferenz wird mit % definiert:

422

Ausgabe

8.7

void test(String^% x) { x="C++/CLI"; }


Listing 8.2 Der Einsatz einer Trackingreferenz

Trackingreferenz Eine Trackingreferenz ist eine Referenz auf ein Trackinghandle. Vergleichbar mit einem Zeiger oder einer Referenz auf einen Zeiger in ANSI C++.

Nun ist das an test bergebene s ebenfalls gendert, weil x als Trackingreferenz nur ein Alias fr s ist. Zum besseren Verstndnis knnte ein Vergleich mit den normalen Referenzen in Abschnitt 3.5, Referenzen, helfen.

8.7

Ausgabe

An dieser Stelle soll noch ein wenig die Ausgabe mit WriteLine und Write erlutert werden. Im ursprnglichen Beispiel wurde die Zeichenkette Hello World ausgegeben. Werttypen werden ebenso behandelt:
int b=4; Console::WriteLine(b);

Was aber, wenn Text und Variablen gemischt ausgegeben werden sollen? Dazu bieten Write und WriteLine die Mglichkeit, eine Zeichenkette mit Platzhaltern anzugeben, wobei die Platzhalter bei der Ausgabe mit dem Wert eines Objekts ersetzt werden:
Console::WriteLine("Es ist {0} gespeichert!", b);

Der erste Parameter bildet den auszugebenden String mit Platzhaltern. Die Platzhalter bestehen aus einem geschweiften Klammerpaar, welches den mit 0 beginnenden Index des zu verwendenden Objekts umschliet. Man nennt diesen Platzhalter auch Parameterbezeichner. Es knnen mehrere Platzhalter in beliebiger Reihenfolge angegeben werden:
String^ a="Andr"; String^ b="Willms"; Console::WriteLine("Name: {0} {1}", a, b); Console::WriteLine("Name: {1}, {0}", a, b);

423

Grundlagen von C++/CLI

Auf dem Bildschirm werden die Namen Andr Willms und Willms, Andr ausgegeben. Es berrascht auch positiv, dass Sonderzeichen nun korrekt ausgegeben und damit auch endlich Umlaute verwendet werden knnen. Wenn zur Definition eines Parameterbezeichners geschweifte Klammern verwendet werden, dann muss es auch einen Trick geben, um geschweifte Klammern auf dem Bildschirm auszugeben. Genau: einfach die gewnschte Klammer zweimal angeben:
int x=10; Console::WriteLine("{{0}} gibt {0} aus", x);

8.7.1

Formatierte Ausgabe

Die Parameterbezeichner haben aber noch andere Tricks auf Lager. Wir haben bisher immer das Platzhalterformat {N} verwendet, wobei N der Index des auszugebenden Objekts ist. Das komplette Format lautet so:
{N [,B] [:F[S] ]}

Die Buchstaben sind Platzhalter und haben die in Tabelle 8.2 aufgefhrten Bedeutungen. Elemente in eckigen Klammern sind optional. Tabelle 10.4 listet die mglichen Formate auf.
1

Platzhalter
N B

Bedeutung mit 0 beginnender Index des auszugebenden Objekts Breite der Ausgabe in Zeichen (bei positiven Werten rechtsbndig, bei negativen linksbndig) Ausgabeformat1 Anzahl der signifikanten Stellen

F S

Tabelle 8.2 Bedeutung der Platzhalter im Parameterbezeichner

Der nachstehende WriteLine-Aufruf gibt das Objekt mit einer reservierten Breite von fnf Zeichen in der Fliekommadarstellung mit zwei Nachkommastellen aus:
double x=3.1415926; Console::WriteLine("!{0,5:F2}!", x);
1 Die mglichen Formate hngen vom auszugebenden Datentyp ab. In Abschnitt CultureInfo, CultureInfo, finden Sie die Formate fr Datums-, Zeit- und Zahlenausgaben.

424

Arrays

8.8

Die Ausrufezeichen dienen nur der optischen Begrenzung des Ausdrucks, um die reservierte Breite von fnf Zeichen zu erkennen. Es ist auch angenehm, zu sehen, dass .NET bei der Ausgabe das in Deutschland bliche Komma und nicht den amerikanischen Punkt verwendet.

8.8

Arrays

Fr einen Vergleich der einzelnen Array-Arten sollen zunchst kurz die bekannten Mglichkeiten rekapituliert werden. Ein statisches Array mit einer zur Kompilationszeit bestimmten Gre, abgelegt auf dem Stack, definieren wir so:
int sf[20];

Ein Array dynamisch auf dem Heap reservieren wir mit dem folgenden Code:
int *df1=new int[20]; delete(df1);

Nur nicht das delete vergessen, ansonsten lauern Speicherlecks. Unter .NET knnen wir erwarten, von der CLR verwaltete Arrays vorzufinden. Dazu muss es sich um einen Verweistyp handeln, fr dessen Adresse ein Trackinghandle bentigt wird:
array<int> ^f;

Das definierte Trackinghandle kann auf verwaltete int-Arrays zeigen. Im Gegensatz zu unverwalteten Elementen, die dynamisch mit new reserviert werden, heit der Befehl zur dynamischen Erzeugung von Objekten verwalteter Typen gcnew. Bei der Erzeugung eines verwalteten Arrays steht die Gre in runden Klammern:
f = gcnew array<int>(10);

Die beiden letzten Anweisungen knnen wir zusammenfassen:


array<int> ^f = gcnew array<int>(10);

Auf Elemente des verwalteten Arrays wird wie gewohnt mit eckigen Klammern zugegriffen:
f[8]=3;

Nur eine Freigabe mit delete bleibt uns erspart, weil das zu den Aufgaben des Garbage-Collectors der CLR zhlt.

425

Grundlagen von C++/CLI

8.8.1

Arrays initialisieren

Die Elemente eines Arrays knnen wir bei der Erzeugung desselben mit Werten initialisieren:
array<int>^ x={2,8,22,63,35};

Durch diese Schreibweise wird automatisch ein Array erzeugt, dessen Elemente mit den angegebenen Werten initialisiert sind und dessen Gre der Anzahl der Werte entspricht.

8.8.2

Mehrdimensionale Arrays

Um Arrays mit mehreren Dimensionen zu erzeugen, mssen wir die Anzahl der Dimensionen mit Komma getrennt hinter dem Datentyp angeben. Folgende Anweisung definiert ein Trackinghandle fr ein zweidimensionales int-Array:
array<int,2> ^f;

Die Gren der einzelnen Dimensionen werden bei der Erzeugung ebenfalls mit Komma getrennt:
f = gcnew array<int,2>(8,3);

Die Separation der Dimensionen beim Elementzugriff erfolgt wiederum mit Komma:
f[3,2]=1;

8.8.3

for each

Um Arrays zu durchlaufen, bietet C++/CLI eine neue Schleife namens for each an. Mit ihr knnen alle Elemente eines Arrays durchlaufen werden:
array<int>^ f=gcnew array<int>(5); for each(int i in f) { Console::WriteLine(i); }
Listing 8.3 Die Schleife for each

Innerhalb der runden Klammern von for each definieren wir eine Variable beliebigen Namens (hier i), die vom selben Typ wie die Elemente des Arrays sein sollte. Dahinter steht mit dem Schlsselwort in getrennt der Name des zu durchlaufenden Arrays.

426

Typumwandlung

8.10

Die Schleife wiederholt ihren Anweisungsblock so oft, wie Elemente im angegebenen Array sind. In jedem Schleifendurchlauf beinhaltet i ein Element des Arrays. Die for each-Schleife ist nicht nur auf Arrays anwendbar, sondern auf jeden Typen, der die Schnittstelle (siehe Abschnitt 9.14) IEnumerable (siehe Abschnitt 10.9) implementiert.

8.9

Eingabe

Die Klasse Console bietet mit der Methode ReadLine auch die Mglichkeit, Eingaben ber die Tastatur einzulesen. Die Eingabe wird mit Drcken der Eingabetaste beendet und als String-Objekt zurckgeliefert. Es ist also ein Trackinghandle vom Typ String erforderlich, um das Ergebnis aufzunehmen:
Console::Write("Ihre Eingabe:"); String ^s=Console::ReadLine(); Console::WriteLine("Ergebnis:{0}", s);

Andere Datentypen wie int oder double einzulesen ist auf direktem Wege nicht mglich. Stattdessen mssen wir das eingelesene String-Objekt in den gewnschten Datentyp konvertieren.

8.10

Typumwandlung

Die Klasse Convert stellt statische Methoden zur Typumwandlung bereit. Tabelle 8.3 listet sie auf. Jede Methode ist fr alle aufgefhrten Datentypen berladen. Wir knnen also beispielsweise jeden der aufgefhrten Datentypen in double konvertieren.
Methode
ToBoolean ToByte() ToChar() ToDateTime ToDecimal() ToDouble

Beschreibung Konvertiert Argument zu bool. Konvertiert Argument zu unsigned char. Konvertiert Argument zu wchar_t. Konvertiert Argument zu System::DateTime^. Konvertiert Argument zu System::Decimal^. Konvertiert Argument zu double.
Die statischen Methoden von Convert

Tabelle 8.3

427

Grundlagen von C++/CLI

Methode
ToInt16() ToInt32() ToInt64() ToSByte() ToSingle() ToString() ToUInt16() ToUInt32() ToUInt64()

Beschreibung Konvertiert Argument zu short. Konvertiert Argument zu int. Konvertiert Argument zu long long. Konvertiert Argument zu char. Konvertiert Argument zu float. Konvertiert Argument zu System::String^. Konvertiert Argument zu unsigned short. Konvertiert Argument zu unsigned int. Konvertiert Argument zu unsigned long long.

Tabelle 8.3

Die statischen Methoden von Convert (Forts.)

Die Methoden fhren nur gltige Konvertierungen durch, das heit, der zu konvertierende Wert darf den Wertebereich des Zieltyps nicht ber- oder unterschreiten. Bei der folgenden Konvertierung werden die Nachkommastellen einfach abgeschnitten:
Convert::ToInt16(3.14);

Liegt der zu konvertierende Wert aber auerhalb des Wertebereichs, wird eine Ausnahme des Typs OverflowException geworfen:
Convert::ToInt16(80000); //OverflowException

Soll ein String-Objekt in einen Typ konvertiert werden, der ein anderes numerisches Format als das im String enthaltene erwartet, dann wird eine Ausnahme vom Typ FormatException geworfen.
Convert::ToInt16("3.14"); //FormatException

Fr den Fall, dass riskante Typkonvertierungen vorgenommen werden, ist eine Ausnahmebehandlung unumgnglich.

8.11

Ausnahmen

Die Ausnahmebehandlung unter .NET funktioniert, wie in Abschnitt 5.3, Ausnahmen, besprochen, nur dass hier die Ausnahmetypen eine Klassenhierarchie bilden, die System::Exception als Basisklasse hat. Abbildung 8.6 zeigt einen Ausschnitt der Hierarchie.

428

Ausnahmen

8.11

Exception

ApplicationException

SystemException

ArgumentException

ArithmeticException

ArgumentOutOfRange Exception

ArgumentNullException

DivideByZeroException

OverflowException

NotFiniteNumber Exception

IndexOutOfRangeException

InvalidOperationException

NotSupportedException

FormatException

NullReferenceException

MemberAccessException

IO.IOException

OutOfMemoryException

TypeLoadException

Abbildung 8.6

Ein Ausschnitt der Exception-Hirarchie

Von Exception sind zwei Klassen abgeleitet:


SystemException: Basisklasse aller von der CLR geworfenen Ausnahmen ApplicationException: Basisklasse aller von der Anwendung geworfenen

Ausnahmen Ausnahmen, die von der eigenen Anwendung geworfen werden, sollten sich in diese Hierarchie eingliedern und von ApplicationException abgeleitet sein. Die Klasse Exception stellt einige ntzliche Eigenschaften bereit, die in Tabelle 8.4 aufgefhrt sind.

429

Grundlagen von C++/CLI

Eigenschaft
TargetSite

Bedeutung Liefert den Namen der Methode, die die Ausnahme geworfen hat. Liefert die Aufrufreihenfolge zur Methode, in der die Ausnahme geworfen wurde. Liefert eine Beschreibung der Ausnahme als String.

StackTrace

Message

Tabelle 8.4

Ntzliche Eigenschaften der Klasse Exception

Das folgende Beispiel fngt eine der im vorigen Abschnitt erzeugten Ausnahmen wegen ungltiger Konvertierung auf und gibt die aus Exception ausgelesenen Informationen aus. Ausnahmen sind verwaltete Typen, weswegen die Ausnahme-Handler mit einem Trackinghandle arbeiten mssen:
try { Convert::ToInt16(80000); } catch(OverflowException^ e) { Console::WriteLine("{0}\n",e->TargetSite); Console::WriteLine("{0}\n",e->StackTrace); Console::WriteLine("{0}\n",e->Message); }

In Abschnitt 4.11, Vererbung, wurde erklrt, dass durch ffentliche Vererbung eine Beziehung ist ein(e) zum Ausdruck gebracht wird. Eine OverflowException ist damit auch eine Exception und knnen wir berall dort verwenden, wo eine Exception erwartet wird (Polymorphie). Im Umkehrschluss kann ein Ausnahme-Handler eine Ausnahme des Typs Exception fangen und fngt damit automatisch auch alle von Exception abgeleiteten Typen:
try { Convert::ToInt16(80000); } catch(OverflowException^ e) { // OverFlowException und Subtypen fangen } catch(Exception^ e) { // Exception und Subtypen fangen }

Bei der Anordnung der Ausnahme-Handler mssen wir darauf achten, dass wir die Subtypen immer zuerst auffhren. Stnde im oberen Code der

430

STL/CLR

8.12

Exception-Handler vor dem OverflowException-Handler, dann htte der Exception-Handler auch den Subtyp OverflowException aufgefangen. Der OverflowException-Handler wrde niemals in Aktion treten.

8.11.1

finally

Unter .NET gibt es die Mglichkeit, hinter den Ausnahme-Handlern einen finally-Block zu definieren. Dieser Block wird immer ausgefhrt, unabhngig davon, ob berhaupt eine Ausnahme aufgetreten ist, ob die Ausnahme gefangen wurde oder ob im Ausnahme-Handler eine neue Ausnahme aufgetreten ist. Sie haben damit die Garantie, dass der finally-Block auf jeden Fall abgearbeitet wird. Er eignet sich daher ideal fr Aufrumarbeiten wie Dateien schlieen oder Datenbankverbindungen trennen:
try { Convert::ToInt16(80000); } catch(OverflowException^ e) { // OverFlowException und Subtypen fangen } catch(Exception^ e) { // Exception und Subtypen fangen } finally { Console::WriteLine("Immer ausgefhrter Code"); }

8.12

STL/CLR

Die Bedeutung der STL-Programmierer wurde von Microsoft erkannt und auch unter .NET bereitgestellt, dort heien sie dann STL/CLR. Um die .NETVersionen der Elemente von der originalen STL unterscheiden zu knnen, liegen sie im Namensbereich cliext. Diesen Namensbereich mssen wir auch bei include mit angeben. Folgende Anweisung bindet die .NET-Version des Vektors ein:
#include <cliext/vector>

Es bietet sich an, diesen Namensbereich mittels using namespace global verfgbar zu machen:
using namespace cliext;

431

Grundlagen von C++/CLI

Erzeugt werden die Container dann, wie bereits in Kapitel 6, Die STL, beschrieben. Der folgende Codeschnipsel legt einen Vektor fr int-Werte an und fgt einen Wert in den Container ein:
vector<int> v; v.push_back(55);

Auch Iteratoren funktionieren gleich:


vector<int>::iterator i; for(i=v.begin(); i!=v.end(); ++i) { Console::WriteLine(*i); }

Abgesehen davon, dass Allokatoren wegen des verwalteten Codes von .NET keine Rolle mehr spielen, stehen alle Container, Adapter und Algorithmen zur Verfgung, die in Kapitel 6 vorgestellt wurden.

432

Dieses Kapitel bespricht den objektorientierten Ansatz unter .NET, der im Vergleich zu ANSI C++ einige Ergnzungen erfahren hat.

Klassendefinition unter .NET

Wie schon des fteren erwhnt, ist der objektorientierte Ansatz unter .NET strker ausgeprgt als in ANSI C++. Diese Erweiterungen sollten wir noch besprechen, um ein besseres Verstndnis von der .NET-Klassenbibliothek zu erhalten.

9.1

Eine verwaltete Klasse erstellen

Eine verwaltete Klasse wird in C++ zunchst wie eine herkmmliche Klasse definiert, nur dass vor class das Schlsselwort ref gesetzt wird. Exemplarisch soll die aus Kapitel 4, Klassen, wohlbekannte Klasse Becher als verwaltete Klasse implementiert werden:
ref class Becher { };
Listing 9.1 Definition einer verwalteten Klasse

Die Syntax einer Klassendefinition:


ref class Klassenname { // Elemente der Klasse };

Bei der Erstellung verwalteter Klassen bietet Visual C++ ebenfalls Untersttzung. Dabei wird die Klasse so erstellt, wie in Abschnitt 4.1.1, Erstellen einer Klasse mit Visual C++, erklrt, nur der Klassen-Assistent unterscheidet sich in einem Punkt, wie Abbildung 9.1 zeigt.

433

Klassendefinition unter .NET

Abbildung 9.1

Der Klassen-Assistent unter .NET

Die Option Verwaltet ist nicht lnger abgeblendet und kann verndert werden. Der Punkt bleibt natrlich abgehakt, weil eine verwaltete Klasse erstellt werden soll. Wenn Sie sich den erstellten Quellcode anschauen, wird Ihnen der fehlende Destruktor auffallen. Die Aufrumarbeiten fr verwaltete Klassen werden im Normalfall von der Garbage Collection der CLR erledigt, daher wird ein Destruktor nur noch in seltenen Fllen bentigt. Die Becher-Klasse wurde in den folgenden Listings bereits in einen eigenen Namensbereich gesteckt und mit Attributen und Konstruktor versehen:
#pragma once namespace Getraenke { ref class Becher { System::String^ inhalt; int fassungsvermoegen; float fuellhoehe; public: Becher(System::String^ i, int fa, float fu); }; }
Listing 9.2 Die Header-Datei fr Becher

434

Eine verwaltete Klasse erstellen

9.1

Wie schon unter ANSI C++ wird in der Header-Datei kein using namespace verwendet. Interessant ist auch, dass wir fr die Verwendung der .NET-Bibliothek keine Header-Dateien einbinden mssen. Die von Visual C++ erstellte Quellcodedatei bindet stdafx bereits ein. Bei einer manuell erstellten Quellcodedatei muss dieses include unbedingt hinzugefgt und als Erstes ausgefhrt werden:
#include "StdAfx.h" #include "Becher.h" using namespace System; namespace Getraenke { Becher::Becher(String^ i, int fa, float fu) : inhalt(i), fassungsvermoegen(fa), fuellhoehe(fu) { } }
Listing 9.3 Die Quellcodedatei fr Becher

Ein Objekt der neuen Klasse ist schnell angelegt:


Becher b1("Wasser",300, 90); // Stack Becher^ b2 = gcnew Becher("Wasser",300, 90); // Heap

Das Objekt b1 wird dabei, wie aus ANSI C++ bekannt, auf dem Stack und das Objekt b2 auf dem Heap angelegt. Dazu verwenden wir den Befehl gcnew, der ein verwaltetes Objekt erzeugt. Dieses wird bei Bedarf vom Garbage Collector automatisch freigegeben im Gegensatz zu ANSI C++, wo mit new dynamisch angelegte Objekte explizit wieder mit delete freigegeben werden mussten.

9.1.1

Zugriffsrecht auf die Klasse

Unter .NET kann jede Klasse mit einem Zugriffsrecht versehen werden. Mit public ist die Klasse berall zugnglich, mit private nur innerhalb der eigenen Assembly. Soll auf die Klasse Becher uneingeschrnkt zugegriffen werden knnen, dann mssen Sie sie mit public versehen:
public ref class Becher { // ... };
Listing 9.4 Die Klasse Becher mit ffentlichem Zugriffsrecht

435

Klassendefinition unter .NET

9.2

Die Ausgabe

Die ursprngliche Becher-Klasse besa eine Methode ausgabe. Erstaunlicherweise besitzt die verwaltete Klasse Becher bereits ebenfalls die Fhigkeit, ausgegeben zu werden:
Becher^ b = gcnew Becher("Wasser",300, 90); Console::WriteLine(b);

Wenn diese Ausgabe vielleicht auch nicht erwartet wurde:


Getraenke.Becher

Aber wo kommt diese Ausgabe her? Unter .NET besitzen alle Klassen die Klasse Object als Basisklasse, auch wenn nicht explizit von ihr abgeleitet wurde. Object stellt u. a. eine ToString-Methode bereit, die von Write und WriteLine zur Ausgabe des Objekts verwendet wird. Mit Object als Basisklasse hat Becher die Methode ToString geerbt. Um also eine an die Klasse angepasste Ausgabe zu erhalten, muss die Methode ToString berschrieben werden (siehe Abschnitt 4.14, Methoden berschreiben). Der bersichtlichkeit wegen definieren wir sie inline:
ref class Becher { // Hier stehen die Attribute public: Becher(System::String^ i, int fa, float fu); System::String^ ToString() { return("Becher mit "+inhalt); } };
Listing 9.5 Eine erste ToString-Methode

Im direkten Vergleich zur ausgabe-Methode der ANSI C++-Klasse fllt auf, dass die Methode nicht als konstanzwahrend deklariert wurde (siehe Abschnitt 4.6, Konstanzwahrende Methoden). Noch eher wird wahrscheinlich auffallen, dass der obige Code nicht kompiliert wird. Der Compiler meckert, dass die ToString-Methode der BecherKlasse mir der ToString-Methode von Object bereinstimmt, die Methode in Becher aber nicht als virtuell deklariert wurde. Unter .NET reicht es nicht aus, die Basisklassenmethode als virtuell zu deklarieren. Das Schlsselwort virtual muss auch bei den Subklassenmethoden verwendet werden, weil .NET die Mglichkeit bietet, das virtuelle Verhalten einer Methode in einer

436

Eigenschaften

9.3

Subklasse zu beenden (eben, wenn kein virtual mehr angegeben wird). Haben wir die Methode dann explizit als virtuell deklariert, beschwert sich der Compiler immer noch, weil bestimmt werden muss, ob die virtuelle Methode der Subklasse die Basisklassenmethode nur berschreibt oder mit ihr eine neue Hierarchie beginnt. Hier soll die Basisklassenmethode nur berschrieben werden, deshalb verwenden wir das Schlsselwort override. Genauere Informationen ber das Verhalten von Subklassenmethoden erhalten Sie in Abschnitt 9.10, Vererbung:
virtual System::String^ ToString() override { return("Becher mit "+inhalt); }
Listing 9.6 Die korrekte ToString-Methode

Und jetzt werden die Becher auch ber Write und WriteLine korrekt ausgegeben. Die Methode zeigt auch, dass .NET-Strings wie die Strings der C++Standardbibliothek mit + verknpft werden knnen (siehe Abschnitt 8.3, CLR-Konsolenanwendung).

9.3

Eigenschaften

Die Becher-Klasse hat ihre Attribute wie es sich gehrt als privat deklariert. Um diese Attribute zugnglich zu machen, mssen Methoden her, einmal zum Auslesen:
System::String^ getInhalt() { return(inhalt); }
Listing 9.7 Die getInhalt-Methode

Und weil die Mglichkeit der Umetikettierung in der Lebensmittelindustrie immer wichtiger wird, soll auch eine set-Methode hinzugefgt werden:
void setInhalt(System::String^ i) { inhalt=i; }
Listing 9.8 Die setInhalt-Methode

Wenn nun ein Becher Milch in Kakao umgewandelt und der Inhalt anschlieend ausgegeben werden soll, dann sieht das so aus:

437

Klassendefinition unter .NET

Becher^ b = gcnew Becher("Milch",200, 100); b->setInhalt("Kakao"); Console::WriteLine(b->getInhalt());

Funktioniert alles bestens. Die Methodenaufrufe sind zwar syntaktisch aufwndiger als ein Zugriff auf Attribute, haben aber den Vorteil, Code zur berprfung der bergebenen Werte aufnehmen zu knnen. Trotzdem ist der Zugriff auf Attribute einfach angenehmer. Diese Meinung ist weit verbreitet, und ihr wird unter .NET mit Eigenschaften Rechnung getragen. Eine Eigenschaft ist technisch gesehen eine Funktion, spter in der Anwendung wird auf sie aber wie auf ein Attribut zugegriffen. Definiert wird eine Eigenschaft mithilfe des Schlsselworts property, gefolgt vom Datentyp und Namen der Eigenschaft. Wenn Sie ber die Eigenschaft ein Attribut der Klasse ansprechen wollen, dann sollte der Datentyp der Eigenschaft mit dem Datentyp des Attributs bereinstimmen:
property System::String^ Inhalt { }
Listing 9.9 Definition einer Eigenschaft

Innerhalb des Anweisungsblocks der Eigenschaft werden zwei Funktionen get und set definiert, mit denen die lesende und beschreibende Fhigkeit der Eigenschaft implementiert wird. Die get-Funktion liefert einen Wert zurck und hat als Rckgabetyp den Datentyp der Eigenschaft:
System::String^ get() { return(inhalt); }
Listing 9.10 Die get-Funktion der Eigenschaft

Die Syntax einer Eigenschaft:


property Datentyp Eigenschaftenname { Datentyp get() { // Anweisungsblock } void set(Datentyp i) { // Anweisungsblock } }

438

Eigenschaften

9.3

Die set-Funktion muss dem Attribut einen Wert zuweisen und bentigt dazu einen Funktionsparameter mit dem Datentyp der Eigenschaft:
void set(System::String^ i) { inhalt=i; }
Listing 9.11 Die set-Funktion der Eigenschaft

In die get- und set-Funktionen knnen wir wie zuvor in die Zugriffsmethoden berprfende Codeteile integrieren, die im Fehlerfall eine Ausnahme werfen sollten. Die gesamte Eigenschaft sieht so aus:
property System::String^ Inhalt { System::String^ get() { return(inhalt); } void set(System::String^ i) { inhalt=i; } }
Listing 9.12 Die komplette Eigenschaft

Eine Eigenschaft kann auch nur eine der beiden Funktionen beinhalten. In Abhngigkeit davon, welche Funktion implementiert wurde, ist die Eigenschaft nur lesend oder nur beschreibend. Abbildung 9.2 fasst die Syntax einer Eigenschaft nochmal zusammen. Es knnte bei dieser Eigenschaft der Eindruck entstehen, dass eine Forderung der CLS verletzt wird, nmlich, dass sich zwei Bezeichner im selben Bezugsrahmen nicht nur in ihrer Gro-/Kleinschreibung unterscheiden drfen. Wir haben in der Klasse einmal das Attribut inhalt und die Eigenschaft Inhalt. Allerdings bezieht sich diese Forderung nur auf von auen zugngliche Elemente. Das private Attribut ist fr Anwender aber verdeckt, und daher ist nur die Eigenschaft sichtbar und somit berhaupt kein Konflikt vorhanden.

439

Klassendefinition unter .NET

Schlsselwort zur Definition einer Eigenschaft

Rckgabetyp

Name der Eigenschaft (Frei whlbar)

property System::String^ Inhalt {


Lesende Eigenschaft Rckgabetyp. Muss mit Eigenschaft bereinstimmen

System::String^ get() { return(zaehler); }


An die Eigenschaft bergebener Wert. Name frei whlbar, Typ muss mit Eigenschaft bereinstimmen.

Schreibende Eigenschaft Schreibende Eigenschaft hat immer als Rckgabetyp

void set(System::String^ i) { inhalt=i; } }

Abbildung 9.2

Die Syntax einer Eigenschaft

9.3.1

Externe Definition

Die get- und set-Funktionen einer Eigenschaft sind technisch nichts anderes als Methoden und knnen daher auch extern also in der Quellcodedatei definiert werden. Der Anweisungsblock der Eigenschaft enthlt dann nur noch die Deklarationen:
property System::String^ Inhalt { System::String^ get(); void set(System::String^ i); }
Listing 9.13 Deklarationen der Eigenschaftsfunktionen

Bei der Definition mssen wir fr jede Funktion angeben, zu welcher Klasse und Eigenschaft sie gehrt:
String^ Becher::Inhalt::get() { return(inhalt);

440

Eigenschaften

9.3

} void Becher::Inhalt::set(String^ i) { inhalt=i; }


Listing 9.14 Die Definitionen der Eigenschaftsfunktionen

9.3.2

Unterschiedliche Zugriffsrechte

Manchmal ist es wnschenswert, auf die get- und die set-Funktion unterschiedliche Zugriffsrechte zu vergeben. Die lesende Eigenschaft soll beispielsweise ffentlich sein, die schreibende aber nur abgeleiteten Klassen zugnglich gemacht werden. Das ist insofern kein Problem, da Zugriffsspezifizierer innerhalb des Anweisungsblocks der Eigenschaft angegeben werden knnen. Sie mssen nur darauf achten, dass innerhalb der Klasse kein weniger restriktives Zugriffsrecht als das auf die Eigenschaft vergeben wird:
public: property System::String^ Inhalt { System::String^ get(); protected: void set(System::String^ i); }
Listing 9.15 get und set mit unterschiedlichen Zugriffsrechten

Der obere Code ist korrekt, weil das Zugriffsrecht der Eigenschaft public ist und innerhalb der Eigenschaft auf protected eingeschrnkt wird. Folgender Code wird nicht kompiliert, obwohl von der Logik her das Gleiche formuliert wird:
protected: property System::String^ Inhalt { void set(System::String^ i); public: System::String^ get(); }
Listing 9.16 Fehlerhafte Aufweichung des Zugriffsrechts

Hier wird innerhalb der Eigenschaft das vor der Eigenschaft deklarierte Zugriffsrecht aufgeweicht. Und das ist nicht erlaubt.

441

Klassendefinition unter .NET

9.3.3

Eigenschaften ohne Attribute

Eigenschaften knnen nicht nur dazu verwendet werden, Attribute auszulesen und zu beschreiben. Mit ihnen knnen auch Attribute simuliert werden, die real nicht im Objekt existieren. Zum Beispiel knnten wir fr die Klasse Becher eine Eigenschaft definieren, die die im Becher enthaltene Menge in Millilitern liefert:
property float AbsoluteMenge { float get() { return(fassungsvermoegen*fuellhoehe/100.0F); } }
Listing 9.17 Ein simuliertes Attribut

Die obere Eigenschaft besitzt nur eine get-Funktion und kann deshalb nicht schreibend verwendet werden. Fr den Anwender der Klasse steht nun ein Attribut AbsoluteMenge bereit, welches in der Speicherstruktur des Objekts keine Entsprechung besitzt. Es ist nur durch Rechenleistung entstanden. Diese Nur Lese-Eigenschaft ist bei simulierten Attributen hufig anzutreffen, weil die enthaltene Rechnung in ihrer inversen Form nicht immer eindeutig ist oder die Umkehrfunktion einen viel greren Rechenaufwand erfordert. Fr die obere Eigenschaft wre eine set-Funktion aber nicht schwer umzusetzen:
void set(float w) { fuellhoehe=w*100.0F/fassungsvermoegen; }
Listing 9.18 Die set-Funktion fr AbsoluteMenge

9.3.4

Virtuelle und statische Eigenschaften

Eigenschaften drfen statisch sein, wobei dann alle vorhandenen Funktionen der Eigenschaft statisch sein mssen. Virtuell drfen die Eigenschaften auch sein, hier darf allerdings auch nur eine Funktion als virtuell deklariert werden.

9.4

Indexer

In Listing 5.6 in Abschnitt 5.2.3, Destruktoren, wurde die Klasse IntFeld vorgestellt, die zu weiteren Anschauungszwecken nun in eine verwaltete Klasse umgewandelt wird:

442

Indexer

9.4

ref class IntFeld { array<int>^ feld; public: IntFeld(int g) { feld = gcnew array<int>(g); } void setWert(int pos, int wert) { feld[pos]=wert; } int getWert(int pos) { return(feld[pos]); } };
Listing 9.19 IntFeld als verwaltete Klasse

Auf einen Destruktor kann verzichtet werden, weil die Klasse nur aus verwalteten Elementen besteht, die von der CLR freigegeben werden. Die Klasse ist aber nur bedingt praxistauglich, weil die implementierte Funktionalitt bereits im .NET-Array enthalten ist. Trotzdem ist sie fr das folgende Thema ein ideales Anschauungsmaterial. Der Zugriff auf die einzelnen Elemente des Feldes luft wie bei der Vorgngerklasse ber die Methoden getWert und setWert. Es wre aber sehr schn, wenn die Feldelemente, wie gewohnt, ber einen Indexoperator angesprochen werden knnten.

9.4.1

Eigenschaften-Indexer

Und genau dazu dienen die Indexer. Mit ihnen wird der Zugriff ber einen Indexoperator implementiert. Syntaktisch ist ein Indexer nichts anderes als eine mit einem Indexoperator erweiterte Eigenschaft:
property int Feld[int] { }
Listing 9.20 Definition eines Indexers

Die get- und set-Methoden bentigen jetzt jeweils einen Parameter mehr, damit ihnen der Index bergeben werden kann:

443

Klassendefinition unter .NET

property int Feld[int] { int get(int idx) { return(feld[idx]); } void set(int idx, int wert) { feld[idx]=wert; } }
Listing 9.21 Der vollstndige Indexer

Der Bezeichner der Indizes ist im Rahmen der Namensregeln frei whlbar. Den Zugriff auf das Feld knnen wir jetzt bequem ber den Indexoperator vornehmen:
IntFeld^ f = gcnew IntFeld(20); f->Feld[10]=88; Console::WriteLine(f->Feld[10]);

Wir knnen beliebig viele Eigenschaften mit Indexern versehen.

9.4.2

Klassen-Indexer

Aber irgendwie ist der Zugriff auf die Arrayelemente noch nicht das Gelbe vom Ei, sind wir es doch von Arrays gewohnt, in der Form f[] und nicht ber ein Klassenelement (f->Feld[]) auf die Elemente zuzugreifen. Fr diesen Spezialfall gibt es die Klassen-Indexer, auch Default-Indexer genannt. Sie sind syntaktisch gleich aufgebaut wie die bisherigen Indexer, nur dass sie default heien mssen:
property int default[int] { int get(int idx) { return(feld[idx]); } void set(int idx, int wert) { feld[idx]=wert; } }
Listing 9.22 Ein Klassen-Indexer

Mit einem Klassen-Indexer kann der Indexoperator direkt auf das Klassenobjekt angewendet werden.

444

Ressourcenfreigabe

9.5

9.4.3

Mehrdimensionale Indexer

Es knnen auch mehrdimensionale Indexer geschrieben werden. Als kleines Beispiel soll die Klasse Matrix herhalten, die einen zweidimensionalen Klassen-Indexer besitzt:
ref class Matrix { array<float,2> ^a; public: Matrix(int d1, int d2) { a=gcnew array<float,2>(d1,d2); } property float default[int,int] { float get(int i1, int i2) { return(a[i1,i2]); } void set(int i1, int i2, float v) { a[i1, i2]=v; } } };
Listing 9.23 Eine Matrix-Klasse mit zweidimensionalem Indexer

Die mehrdimensionalen Indexer knnen auch fr Eigenschaften-Indexer verwendet werden.

9.5

Ressourcenfreigabe

In den meisten Fllen besitzen Attribute verwalteter Klassen ebenfalls verwaltete Typen. Sie werden von der CLR freigegeben, sobald das Objekt nicht lnger bentigt wird. Wann genau diese Freigabe erfolgt, ist nicht festgelegt und kann durchaus erst am Ende des Programms geschehen. Andererseits gibt es Ressourcen, die nur so lange wie notwendig blockiert werden sollten. Dazu knnen Verbindungen zu Servern gehren oder Dateien mit exklusivem Zugriff, die mglichst schnell wieder anderen Programmen oder Prozessen zugnglich gemacht werden mssen. Es stellt sich die Frage, wie vom Objekt belegte Ressourcen deterministisch (soll heien, zu einem fest definierten Zeitpunkt) freigegeben werden knnen, um Engpsse oder Deadlocks zu vermeiden.

445

Klassendefinition unter .NET

9.5.1

Deterministische Freigabe verwalteter Ressourcen

Nehmen wir als Beispiel die Klasse Datei, die im Konstruktor eine Datei ffnet, deren Namen ihm bergeben wurde. Zwar wird die Dateiverwaltung erst in Kapitel 20, Datenbankanbindung, erklrt, doch soll sie hier verwendet werden, um ein mglichst realistisches Beispiel prsentieren zu knnen:
ref class Datei { System::IO::FileStream ^file; public: Datei(System::String ^n) { file=gcnew System::IO::FileStream(n, System::IO::FileMode::Open); } };
Listing 9.24 Die Klasse Datei

Sie erzeugen ein Objekt dieser Klasse auf die bekannte Weise:
Datei^ d = gcnew Datei("test.txt");

Um das Objekt der Obhut des Garbage Collectors zu bergeben, mssen Sie jegliche Verweise darauf lschen:
d=nullptr;

Wenn das Objekt abgebaut wird, geschieht das Gleiche mit seinen Attributen, und im Zuge des Abbaus des FileStream-Objekts wird auch die Datei geschlossen. Und das kann irgendwann zwischen dem Lschen des Trackinghandles und dem Programmende geschehen. Was aber, wenn die Datei mglichst schnell geschlossen werden soll? Am besten zu einem festgelegten Zeitpunkt? Die Lsung besteht im Anlegen eines Destruktors (mehr zu Destruktoren unter ANSI C++ finden Sie in Abschnitt 5.2.3, Destruktoren):
ref class Datei { System::IO::FileStream ^file; public: Datei(System::String ^n) { file=gcnew System::IO::FileStream(n, System::IO::FileMode::Open); }

446

Ressourcenfreigabe

9.5

~Datei() { file->Close(); } };
Listing 9.25 Die Klasse Datei mit Destruktor

Die Definition eines Destruktors hat zur Folge, dass er in die Methode Dispose umgewandelt wird und die Klasse automatisch die Schnittstelle IDisposable implementiert. Wie Schnittstellen definiert und implementiert werden, erfahren Sie im Detail in Abschnitt 9.14, Schnittstellen. Die Dispose-Methode wird unter .NET verwendet, wenn Aufrumarbeiten nicht erst dann ausgefhrt werden sollen, wenn der Garbage Collector das Objekt abbaut, sondern zu einem gewnschten Zeitpunkt. In anderen .NETSprachen muss die Dispose-Methode dazu explizit aufgerufen werden. In C++ handelt es sich aber um einen Destruktor, und der wird nicht explizit aufgerufen, sondern, wie in C++ blich, ber den Aufruf von delete:
Datei^ d = gcnew Datei("test.txt"); delete(d); // Schlieen der Datei

Es soll hier noch einmal betont werden: Der Aufruf von delete hat nicht wie in ANSI C++ einen Abbau des Objekts zur Folge, sondern den Aufruf der Dispose-Methode. Es ist daher durchaus mglich wenn vielleicht auch nicht immer sinnvoll , dass der Destruktor mehrfach aufgerufen wird. Wenn die Aufrumarbeiten im Destruktor so gestaltet sind, dass sie nur einmal ausgefhrt werden drfen (z. B. weil nicht verwalteter Speicher freigegeben wird), dann muss die Klasse durch Implementierung eines geeigneten Mechanismus dafr sorgen, dass der Code im Destruktor nur einmal ausgefhrt wird:
ref class Datei { System::IO::FileStream ^file; bool disposed; public: Datei(System::String ^n) : disposed(false) { file=gcnew System::IO::FileStream(n, System::IO::FileMode::Open); } ~Datei() { if(!disposed) { file->Close();

447

Klassendefinition unter .NET

disposed=true; } } };
Listing 9.26 Schutz des Destruktors vor mehrmaligem Aufrufen

Das Boolesche Attribut disposed wird bei der Erzeugung des Objekts auf false gesetzt. Der Destruktorcode wird nur ausgefhrt, wenn disposed den Wert false hat. Das ist nur einmal der Fall, weil disposed im Destruktor auf true gesetzt wird.

9.5.2

Freigabe nicht verwalteter Ressourcen

Auch unter .NET ist es in C++ problemlos mglich, unverwaltete Objekte, z. B. aus der C++-Standardbibliothek, in verwalteten Klassen zu verwenden. Leider wei der Garbage-Collector nicht, wie unverwaltete Klassenelemente freizugeben sind. Zur Demonstration implementieren wir eine Klasse StrCon, die dynamisch einen unverwalteten String aus std reserviert und ihn mit dem Konstruktorargument initialisiert:
ref class StrCon { std::string *str; public: StrCon(const std::string &s) { str=new std::string(s); } };
Listing 9.27 Die Klasse StrCon

In der Header-Datei der Klasse darf nicht vergessen werden, die Datei string einzubinden. Nun knnen wir ein Objekt von StrCon anlegen und es mit Lschen des Trackinghandles dem Garbage Collector bergeben:
StrCon^ s = gcnew StrCon("Test"); s=nullptr;

Das Objekt wird irgendwann vom Garbage Collector freigegeben. Aber was passiert mit dem string-Objekt? Nichts, es vergammelt im Speicher, bis das Programm beendet wird. Fr Programme mit langen Laufzeiten, wie Server, ein Unding, denn jedes StrCon-Objekt erzeugt eine neue string-Leiche. Dass wir den Code zur Freigabe des string-Objekts selbst programmieren mssen, ist mittlerweile vermutlich klar. Aber wie bekommen wir den Garbage

448

Ressourcenfreigabe

9.5

Collector dazu, unseren Code auszufhren? Das Zauberwort lautet Finalizer. Ein Finalizer ist eine Methode, die vom Garbage Collector vor dem Abbau eines Objekts aufgerufen wird wenn das Objekt einen Finalizer besitzt. Die Definition eines Finalizers hat in C++ eine besondere, leicht an Destruktoren angelehnte Syntax:
ref class StrCon { std::string *str; public: StrCon(const std::string &s) { str=new std::string(s); } !StrCon() { // Finalizer delete(str); } };
Listing 9.28 StrCon mit Finalizer

Vor dem Abbau eines StrCon-Objekts wird der Garbage Collector nun den Finalizer aufrufen, der das string-Objekt freigibt.

9.5.3

Deterministische Freigabe nicht verwalteter Ressourcen

Sollen nicht verwaltete Komponenten deterministisch freigegeben werden, dann mssen wir Destruktor und Finalizer kombinieren. Im Finalizer ist bereits der Freigabecode enthalten, den Destruktor rufen wir ber delete auf, also muss lediglich im Destruktor der Finalizer aufgerufen werden:
ref class StrCon { std::string *str; public: StrCon(const std::string &s) { str=new std::string(s); } ~StrCon() { this->!StrCon(); //this ist zwingend System::GC::SuppressFinalize(this); } !StrCon() { // Finalizer delete(str); } };
Listing 9.29 StrCon mit Finalizer und Destruktor

449

Klassendefinition unter .NET

Im Destruktor wird die statische Methode SuppressFinalize fr das Objekt aufgerufen, um dem Garbage Collector mitzuteilen, dass fr dieses Objekt der Finalizer nicht mehr aufgerufen werden soll. Das ist im aktuellen Fall wichtig, weil der String nicht zweimal freigegeben werden kann.

9.5.4

Zusammenfassung

Die letzten Abschnitte knnen, wie folgt, zusammengefasst werden: Besteht die Klasse nur aus verwalteten Komponenten, dann sind Destruktor und Finalizer nicht notwendig. Ein Finalizer wird bentigt, wenn die Klasse nicht verwaltete Komponenten freigeben muss. Ein Destruktor muss eingesetzt werden, wenn die Klasse Komponenten zeitnah freigeben muss. Fr Objekte mit Finalizer muss bei der Freigabe durch den Garbage Collector ein erhhter Aufwand betrieben werden, deshalb sollten Sie eine Klasse nicht leichtfertig oder pauschal mit einem Finalizer ausstatten.

9.6

Wertklassen

Im Gegensatz zu Verweisklassen werden Wertklassen nicht dynamisch mit gcnew erzeugt und ber ein Trackinghandle angesprochen. Als Beispiel soll eine simple Klasse Lng dienen, die einen long-Wert speichern und ber ToString ausgeben kann. Bei einer Wertklasse wird anstelle von ref das Schlsselwort value verwendet:
value class Lng { long l; public: Lng(long lo) : l(lo) {} property long Wert { long get() { return(l); } void set(long w) { l=w; }

450

Wertklassen

9.6

} virtual System::String^ ToString() override { return(l.ToString()); } };


Listing 9.30 Die Wertklasse Lng

Objekte von Wertklassen legen Sie an, und deren Elemente sprechen Sie an, wie Sie es von ANSI C++ her kennen:
Lng g(20); Console::WriteLine(g); g.Wert=30; Console::WriteLine(g);

Wird ein Wertklassenobjekt einem anderen zugewiesen, dann wird das Zielobjekt mit den Werten des Quellobjekts berschrieben. Es existieren weiterhin zwei Objekte, die nach der Zuweisung nur den gleichen Inhalt besitzen. Bei Verweisklassen wird bei einer Zuweisung lediglich der Inhalt des Trackinghandles zugewiesen vergleichbar mit dem Zuweisen eines Zeigers an einen anderen. Nach der Zuweisung zeigen die beiden Trackinghandles auf dasselbe Objekt. In C++/CLI knnen von Verweisklassen Wertobjekte und von Wertklassen Verweisobjekte erzeugt werden (Erzeugung auf dem Stack oder dem Heap). .NET trennt die beiden Varianten aber strikt:
Achtung Unter .NET darf ein Objekt einer Wertklasse nie an ein Trackinghandle und ein Objekt einer Verweisklasse nie als Kopie bergeben werden. Das muss bei den ffentlichen Methoden bercksichtigt werden, wenn sie von anderen .NET-Sprachen angesprochen werden mssen.

Darber hinaus gibt es bei Wertklassen noch andere Unterschiede: Sie drfen keinen Destruktor oder Finalizer besitzen. Wertklassen drfen nur von Schnittstellen erben, nicht aber von anderen Wert- oder Verweisklassen. Wertklassen knnen nicht als Basisklasse fungieren. In C++/CLI gilt zustzlich die Besonderheit, dass Wertklassen keinen Standardkonstruktor besitzen drfen. Als Standardkonstruktor wird der parameterlose Konstruktor bezeichnet.

451

Klassendefinition unter .NET

9.7

Operatoren berladen

Einen Operator zu berladen, heit, die Funktionalitt des Operators auf eigene Typen zu erweitern. Nehmen wir als Beispiel den Werttypen Lng aus dem vorangegangenen Abschnitt. Letztlich reprsentiert ein Objekt dieser Klasse lediglich einen long-Wert, weshalb eine Addition solcher Objekte durchaus sinnig ist:
Lng a(20), b(30); Lng c = a + b;

Sinnig ist es allemal, aber leider nicht mglich; der Compiler wird die Addition von a und b nicht kompilieren. ber die Technik des berladens von Operatoren knnen wir dem Compiler mitteilen, wie z. B. der +-Operator auf Objekte des Typs Lng anzuwenden ist. Ein Operator wird berladen, indem die Klasse mit bestimmten statischen Methoden ausgerstet wird. Tabelle 9.1 zeigt die wichtigsten berladbaren Operatoren.
C++-Operator
+ (binr) += = & (binr) &= | |= -/ /= == ^ ^= > >= ++ !=

Alternative Methode
Add Add Asign BitwiseAnd BitwiseAnd BitwiseOr BitwiseOr Decrement Divide Divide Equals Xor Xor Compare Compare Increment Compare

Tabelle 9.1

Die wichtigsten berladbaren Operatoren

452

Operatoren berladen

9.7

C++-Operator
<< <<= < <= && || % %= *= * (binr) ~ >> - (binr) -= - (unr) + (unr)

Alternative Methode
LeftShift LeftShift Compare Compare And Or Mod Mod Multiply Multiply OnesComplement RightShift Subtract Subtract Negate Plus

Tabelle 9.1

Die wichtigsten berladbaren Operatoren (Forts.)

Die fr den jeweiligen Operator zu implementierende Methode heit operator, gefolgt vom tatschlichen Operator. Fr den +-Operator heit die Methode beispielsweise operator+. Zustzlich zum Namen des Operators ist immer auch eine alternative Methode angegeben. Um CLS-konform zu sein, muss diese Methode fr jeden berladenen Operator implementiert sein, weil nicht jede Sprache (z. B. Visual Basic) das berladen von Operatoren erlaubt. Vielleicht ist Ihnen aufgefallen, dass Compare mehrfach als alternative Methode vorkommt. Das hngt damit zusammen, dass das Ergebnis von Compare alle Vergleichsmglichkeiten abdeckt. Tabelle 9.2 zeigt die Rckgabewerte fr die einzelnen Mglichkeiten
Compare(a,b) mit
a<b a==b a>b

Ergebnis
<0 0 >0

Tabelle 9.2 Die Rckgabewerte von Compare

453

Klassendefinition unter .NET

9.7.1

Operatoren fr Werttypen

Im Folgenden wird der Additionsoperator fr die Klasse Lng berladen:


value class Lng { long l; public: Lng(long lo) : l(lo) {} property long Wert { long get() {return(l);} void set(long w) {l=w;} } virtual System::String^ ToString() override { return(l.ToString()); } static Lng Add(Lng a, Lng b) { return(Lng(a.l+ b.l)); } static Lng operator+(Lng a, Lng b) { return(Add(a,b)); } };
Listing 9.31 Die Klasse Lng mit berladenem +-Operator

Um Codeverdopplungen zu vermeiden, sollte die Funktionalitt in der alternativen Methode implementiert sein und von der operator-Methode nur noch aufgerufen werden.

9.7.2

Operatoren fr Verweistypen

Bei Verweistypen sehen die Methoden etwas anders aus, weil anstelle von Werten mit Verweisen gearbeitet und das neue Objekt dynamisch erzeugt werden muss. Als Beispiel versehen wir die Klasse Becher mit einem Additionsoperator. Durch die Addition sollen die Becher bei gleichem Inhalt auf magische Weise zu einem neuen Becher verschmelzen, dessen Fassungsvermgen und Inhalt der Summe der beiden Becher entspricht:
ref class Becher { // Hier steht der Rest der Klasse static Becher^ Add(Becher^ x, Becher^ y) { if(x->inhalt!=y->inhalt) throw gcnew System::Exception("Ungleicher Inhalt");

454

Operatoren berladen

9.7

Becher^ b = gcnew Becher(x->inhalt, 1,1); b->fassungsvermoegen=x->fassungsvermoegen+ y->fassungsvermoegen; b->fuellhoehe=(x->AbsoluteMenge+y->AbsoluteMenge)*100/ b->fassungsvermoegen; return(b); } static Becher^ operator+(Becher^ x, Becher^ y) { return(Add(x,y)); } };
Listing 9.32 Operator + fr die Klasse Becher

Zuerst wird in der Add-Methode geprft, ob die beiden Becher denselben Inhalt haben. Bedenken Sie, dass der Vergleich der beiden Strings mit != nur deshalb mglich ist, weil die Klasse String die Operatoren == und != berladen hat. Eigentlich wrden durch diesen Vergleich die Inhalte der Trackinghandles verglichen. Ausnahmen sind Verweistypen, daher muss das zu werfende Objekt mit gcnew erzeugt werden. Im weiteren Verlauf wird dann die Summe der Fassungsvermgen gebildet und der prozentuale Inhalt des neuen Bechers berechnet. Eigentlich wren die berechneten Werte direkt im Konstruktor des neuen Objekts angegeben worden, um gleich ein ordentlich konstruiertes Objekt zu haben. Der Zwischenschritt, zuerst ein Objekt mit unsinnigen Werten zu erstellen, ist hier nur der bersicht wegen eingefhrt worden.

9.7.3

Vergleichsoperatoren

Vergleichsoperatoren werden nach denselben Regeln berladen, die wir bei dem Additionsoperator kennengelernt haben. Der einzige Unterschied besteht im Rckgabetyp, der hier bool ist. Unter .NET gilt fr Vergleichsoperatoren die Regel, dass sie immer symmetrisch berladen werden sollten. Das heit, bei einem berladenen Gleichheitsoperator auch den Ungleichoperator mit berladen, bei <= auch >= und bei < auch >. Schauen wir uns die Operatoren == und != fr Lng an ohne die umgebende Klasse:
static int Compare(Lng a, Lng b) { if(a.l<b.l) return(-1);

455

Klassendefinition unter .NET

if(a.l>b.l) return(1); return(0); } static bool Equals(Lng a, Lng b) { return(a.l==b.l); } static bool operator==(Lng a, Lng b) { return(Equals(a,b)); } static bool operator!=(Lng a, Lng b) { return(!Equals(a,b)); }
Listing 9.33 Vergleichsoperatoren fr Lng

Die Methode Equals htte auch ber einen Aufruf von Compare implementiert werden knnen, ich habe mich aber fr die jetzige Lsung entschieden, weil sie performanter ist.

9.7.4

Umwandlungsoperatoren

Eine besondere Form von Operatoren definiert, in welchen anderen Typ der eigene Typ umgewandelt werden kann. Fr die Klasse Lng bietet sich beispielsweise eine Umwandlung in long an, weil Lng-Objekte nichts anderes als long-Werte speichern. Operatoren, die dies ermglichen, werden Umwandlungsoperatoren genannt und sehen so aus:
static operator long(Lng ln) { return(ln.l); }
Listing 9.34 Ein Umwandlungsoperator von Lng nach long

Ein Umwandlungsoperator ist immer eine statische Methode der Klasse, dessen Typ umgewandelt werden soll. Hinter dem Schlsselwort operator steht der Typ, in den umgewandelt werden soll. Der Umwandlungsoperator besitzt keinen expliziten Rckgabetyp, weil durch die Aussage, er wandelt in long um, automatisch als Rckgabetyp long feststeht. Mit diesem Operator kann jetzt ein Lng-Objekt einer long-Variablen zugewiesen werden:

456

Literale

9.8

Lng lng(50); long l=lng;

Manche Umwandlungen sind zwar in gewisser Hinsicht sinnvoll, aber nicht so intuitiv, dass sie implizit vollzogen werden sollten. Ein fr solche Umwandlungen zustndiger Umwandlungsoperator kann dazu als explizit deklariert werden:
static explicit operator long(Lng ln) { return(ln.l); }
Listing 9.35 Ein expliziter Umwandlungsoperator

Nun kann die Umwandlung nur noch explizit mit static_cast durchgefhrt werden:
Lng lng(50); long l=static_cast<long>(lng);

9.8

Literale

Literale bieten die Mglichkeit, Konstanten auf Klassenebene zu definieren, vergleichbar mit konstanten statischen Attributen (siehe Abschnitt 4.8.2, Statische Attribute). Genau wie konstante statische Attribute werden sie bei der Definition initialisiert, erlauben aber alle elementaren Datentypen:
ref class public: literal literal literal literal }; Test { int i=20; double d=3.14; wchar_t c='C'; bool b=false;

Listing 9.36 Ein Beispiel fr Literale

Angesprochen werden sie wie statische Attribute:


Console::WriteLine(Test::d);

Unter .NET sollten Sie sie den konstanten statischen Attributen vorziehen, weil sie ber die Metadaten auch anderen Compilern zur Verfgung stehen.

457

Klassendefinition unter .NET

9.9

Aufzhlungen

Aufzhlungen sind unter .NET ein weiterer Klassentyp. Sie dienen dazu, eine Gruppe von Konstanten zu kapseln. Nehmen wir als Beispiel eine Klasse Waschmaschine, die sich gem ihres Waschprogramms in verschiedenen Zustnden (Vorwsche, Hauptwsche etc.) befinden kann. Der aktuelle Zustand wird dazu hchstwahrscheinlich als Attribut gespeichert. Ein erster, einfacher Ansatz knnte so aussehen:
ref class Waschmaschine { int zustand; public: Waschmaschine() : zustand(0) {} };

Die Klasse besitzt ein Attribut zustand und einen Konstruktor, der den Zustand mit 0 initialisiert. Und jetzt mssten sich eigentlich zwei Fragen aufdrngen: Warum wird der Zustand gerade mit 0 initialisiert, und was bedeutet die 0? Wir knnten eine Interpretation wagen und mutmaen, dass sich ein neu erzeugtes Objekt des Typs Waschmaschine zu Beginn wahrscheinlich im ausgeschalteten Zustand befindet und 0 daher wahrscheinlich der Wert fr den Zustand ausgeschaltet sein wird. Diese Form des Erahnens zeichnet keinen guten Programmierstil aus. Eine Mglichkeit der Verbesserung kennen wir bereits, Konstanten:
ref class public: literal literal literal Waschmaschine { int Ausgeschaltet=0; int Vorwaesche=1; int Hauptwaesche=2;

Waschmaschine() : zustand(Ausgeschaltet) {} void setZustand(int z) { zustand=z; } private:

458

Aufzhlungen

9.9

int zustand; };
Listing 9.37 Die Klasse Waschmaschine mit Konstanten

Die Konstanten werden weil sie konstant sind fr alle Objekte denselben Wert haben, daher bietet es sich an, sie als statische Elemente zu deklarieren. ber die Bezeichnung der Konstanten ist direkt klar, in welchem Zustand sich das Objekt befindet, und ber setZustand kann der Zustand verndert werden:
Waschmaschine^ w=gcnew Waschmaschine; w->setZustand(Waschmaschine::Vorwaesche);

Soweit, so gut. Dumm nur, dass auch Folgendes mglich ist:


w->setZustand(10);

In welchem Zustand befindet sich die Waschmaschine jetzt? Um diese Problematik etwas einzudmmen und die Konstanten strker zu kapseln, gibt es spezielle Klassen, die nur Konstanten definieren. Dadurch entsteht ein neuer Typ, der zur Definition des Attributs verwendet werden kann. Diese besonderen Klassen heien Aufzhlungen und werden mit dem Schlsselwort enum definiert:
ref class Waschmaschine { public: enum class Zustand {Ausgeschaltet, Vorwaesche, Hauptwaesche}; Waschmaschine() : zustand(Zustand::Ausgeschaltet) {} void setZustand(Zustand z) { zustand=z; } private: Zustand zustand; };
Listing 9.38 Die Klasse Waschmaschine mit Aufzhlung

459

Klassendefinition unter .NET

Die Konstanten der Aufzhlung sind technisch gesehen statische Konstanten und mssen ber den Klassennamen angesprochen werden. Das Attribut ist nun vom Typ der Aufzhlung und kann nur noch in der Aufzhlung definierte Werte aufnehmen. Wenn der Zustand ber die Methode setZustand gendert werden soll, mssen ihr Konstanten der Aufzhlung bergeben werden:
w->setZustand(Waschmaschine::Zustand::Vorwaesche);

Damit die Aufzhlung auerhalb der Klasse ansprechbar ist, muss sie ffentliches Zugriffsrecht besitzen.
Hinweis Aufzhlungsklassen knnen auch eigenstndig in einem Namensbereich stehen und mssen nicht zwangslufig als Unterklasse implementiert werden.

Eine implizite Konvertierung in den Typ Zustand ist nicht mglich, daher lsst sich die folgende Anweisung nicht kompilieren:
w->setZustand(10); // FEHLER!

Die Holzhammermethode funktioniert natrlich immer:


w->setZustand(static_cast<Waschmaschine::Zustand>(10));

Aber das ist Vorsatz, und der wird von keiner Versicherung abgedeckt. Die Werte der Aufzhlungskonstanten beginnen bei 0 und werden jeweils um eins erhht. Es knnen aber auch explizit Werte zugewiesen werden:
enum class Buchstaben {A=8, B=3, C, d=-5};

Im obigen Beispiel hat C den Wert 4, weil die vorhergehende Konstante den Wert 3 besitzt.

9.9.1

Explizite Typangabe

Ohne besondere Vorkehrungen sind die Datentypen der Aufzhlungskonstanten int. Manchmal sollen aber Attribute eines Aufzhlungstyps abgespeichert werden und knnen dadurch an einen bestimmten Datentyp gebunden sein. Auch aus konomischer Sicht ist es beispielsweise nicht sinnvoll, bei der Waschmaschine ein 4 Byte groes Attribut zu besitzen, dessen Wertebe-

460

Aufzhlungen

9.9

reich mit dem 1 Byte groen Typ Byte ebenso gut htte abgedeckt werden knnen. Deshalb knnen Sie hinter dem Namen der Aufzhlung den gewnschten Typ mit Doppelpunkt getrennt angeben:
enum class Zustand : System::Byte {Ausgeschaltet, Vorwaesche, Hauptwaesche};

Natrlich sollten explizit angegebene Werte im Bereich des verwendeten Typs liegen.

9.9.2

Flags

Stellen Sie sich folgende Problematik vor: Sie mchten einen Datentyp besitzen, der den aktuellen Zustand eines einfachen Joysticks reprsentiert. Im Gegensatz zu einer Waschmaschine, die sich immer exakt in einem Zustand befindet, knnen bei einem Joystick mehrere Zustnde gleichzeitig eintreten, denn jemand kann den Hebel nach oben rechts bewegen und dabei den Feuerknopf gedrckt halten. Wie aber knnen diese in Kombination auftretenden Zustnde mit einem Attribut abgedeckt werden? Der Trick ist simpel: Jeder Wert ist nichts anderes als eine Folge von Bits. Jedes dieser Bits kann den Wert 0 oder 1 besitzen. Mit diesen beiden Werten kann ein einzelnes Bit eine ja-/nein-, an-/aus-, gedrckt-/nicht gedrckt- etc. Aussage speichern, je nach Interpretation. Der Datentyp Byte besteht aus 8 Bit, die jedes fr sich 0 oder 1 sein knnen. Wenn wir die Bits einzeln ansprechen, dann haben wir mit diesem Byte acht einzelne, voneinander unabhngige Mglichkeiten, einen ja-/nein-Zustand zu speichern. Genau wie im Dezimalsystem die Ziffer einer Zahl fr eine Zehnerpotenz steht (365 ist nichts anderes als 3*102+6*101+5*100, also 3*100+6*10+5*1), so steht jedes Bit fr eine Zweierpotenz (der binre Wert 1011 entspricht 1*23+0*22+1*21+1*20, also 1*8+0*4+1*2+1*1). Die 8 Bit eines Bytes haben damit die Wertigkeit von 20 bis 27, also 1, 2, 4, 8, 16, 32, 64 und 128. Bei einem Wert von 49 wissen wir, dass die Bits 0, 4 und 5 gesetzt sind, weil 49 aus der Summe der Zweierpotenzen 20+24+25, also 1+16+32 besteht. Die Bits eines Wertes, die eine eigenstndige Bedeutung haben, nennt man Flag, auf Deutsch soviel wie Flagge (Eine Flagge ist gehisst oder nicht). Der erste Schritt, das ursprngliche Problem mit dem Joystick zu lsen, liegt in Konstanten, die jede fr sich dem Wert eines Bits entsprechen:

461

Klassendefinition unter .NET

enum class Controls : System::Byte {Fire=1, Up=2, Down=4, Left=8, Right=16};

Doch wie knnen die einzelnen Flags verknpft werden? Als erster Gedanke bietet sich die Addition an, denn in den oberen Rechenbeispielen wurden die Bitwerte auch aufaddiert. An dieser Stelle soll aber Gebrauch von den bitweisen Operatoren gemacht werden, die bereits Thema in Abschnitt 1.9.4, Bitweise Operatoren, waren. Bei der bitweisen inklusiven Oder-Verknpfung reicht es aus, wenn das Bit mindestens in einem der beiden Operanden gesetzt ist, damit es im Ergebnis ebenfalls gesetzt ist. Diese Verknpfung ist damit wie geschaffen, um zwei Flags zu verknpfen:
Controls a=Controls::Fire; Controls b=Controls::Down; Controls c=a|b; Console::WriteLine(c);

Fr c wird nach der Zuweisung der Wert 5 ausgegeben, weil die Bits der Operanden beide im Ergebnis gesetzt sind. Um dem Compiler mitzuteilen, dass es sich bei der Aufzhlung um Flags handelt, kann davor ein .NET-Attribut gesetzt werden:
[System::Flags] enum class Controls : System::Byte {Fire=1, Up=2, Down=4, Left=8, Right=16};

.NET-Attribute Die in eckigen Klammern stehenden .NET-Attribute drfen nicht mit den Attributen innerhalb der Klassen verwechselt werden.

Das Attribut hat zur Folge, dass nun bei der Ausgabe eines Objekts vom Typ Controls die einzelnen Flagnamen ausgegeben werden. Wenn Sie im Nachhinein in Erfahrung bringen wollen, welche Flags in c gesetzt sind, dann mssen Sie jedes einzelne Flag mittels einer bitweisen Und-Verknpfung prfen. Ein Objekt vom Typ Controls kann nicht ohne explizite Umwandlung mit einem int verglichen werden, daher wird die Aufzhlung um den Wert fr gar nichts gedrckt erweitert:
[System::Flags] enum class Controls : System::Byte {None=0, Fire=1, Up=2, Down=4, Left=8, Right=16};

462

Vererbung

9.10

Jetzt kann der Vergleich formuliert werden:


if((c&Controls::Fire)!=Controls::None) Console::WriteLine("Feuer gedrckt");

Da Fire nur ein Bit gesetzt hat, kann die Und-Verknpfung nur dann einen Wert ungleich Null ergeben, wenn dieses Bit auch in c gesetzt ist (bei der Und-Verknpfung ist ein Bit im Ergebnis nur dann gesetzt, wenn das Bit in beiden Operanden gesetzt ist).

9.10

Vererbung

Vererbung war bereits Thema von Abschnitt 4.11. An dieser Stelle wollen wir lediglich besprechen, wie sich unter .NET die Vererbung syntaktisch verndert und in ihren Mglichkeiten erweitert hat. Zur Demonstration werden einfache, aber sinnlose Klassen herhalten, bei denen der Fokus auf das programmtechnische Verhalten gelegt werden kann. Beginnen wir mit der Klasse Basis, die spter die Rolle der Basisklasse bernehmen soll:
ref class Basis { int basis; public: Basis(int i) : basis(i) {} property int Wert { int get() { return(basis); } void set(int i) { basis=i; } } void ausgabe() { System::Console::WriteLine("Basis:"+basis); } };
Listing 9.39 Die Klasse Basis

Sie besitzt als Attribut einen int-Wert, der ber den Konstruktor initialisiert wird. Fr den Zugriff steht eine Eigenschaft Wert und die Methode ausgabe zur Verfgung. Wir wissen bereits, wie eigene Klassen mit der Console-Klasse

463

Klassendefinition unter .NET

ausgegeben werden knnen (siehe Abschnitt 9.2, Die Ausgabe). Um aber nicht bereits eine geerbte Methode berschreiben zu mssen (denn jede verwaltete Klasse hat Object als Basisklasse und erbt damit die ToStringMethode), wird zu Anschauungszwecken eine eigene Ausgabemethode implementiert. Ein Objekt von Basis ist schnell angelegt und die Methoden sind ebenso schnell ausgetestet:
Basis^ b=gcnew Basis(20); b->ausgabe(); Console::WriteLine(b->Wert);

Nun folgt eine von Basis abgeleitete Klasse mit dem Namen Abgeleitet. Sie definiert eine eigene ausgabe-Methode und eine neue Eigenschaft Wert:
ref class Abgeleitet : Basis { int ab; public: Abgeleitet(int i, int a) : Basis(i), ab(a) {} property int Wert { int get() { return(ab); } void set(int i) { ab=i; } } void ausgabe() { System::Console::WriteLine("Abgeleitet:"+ab); } };
Listing 9.40 Die Klasse Abgeleitet

Ein Unterschied zu ANSI C++ tritt bereits zutage: Weil es unter .NET nur ffentliche Vererbung gibt, knnen wir auf das Schlsselwort public vor der Basisklasse verzichten. Auch von der abgeleiteten Klasse ist ein Objekt problemlos erstellt:
Abgeleitet^ a=gcnew Abgeleitet(20,30); a->ausgabe(); Console::WriteLine(a->Wert);

Als Nchstes wollen wir ber einen Basisklassenzeiger zugreifen:

464

Vererbung

9.10

b=a; b->ausgabe(); Console::WriteLine(b->Wert);

ber den Basisklassenzeiger verhlt sich das Abgeleitet-Objekt wie ein Basis-Objekt. Mit den grundlegenden Vererbungsmechanismen bereits vertraut, erkennen Sie die Ursache. Die Methoden der Basisklasse sind nicht virtuell. Basisklassen sollten die potenziell fr berschreiben in Frage kommenden Methoden immer als virtuell deklarieren. Listing 9.41 zeigt die nderungen in komprimierter Darstellung:
ref class Basis { int basis; public: Basis(int i) : basis(i) {} property int Wert { virtual int get() {return(basis);} virtual void set(int i) {basis=i;} } virtual void ausgabe() { System::Console::WriteLine("Basis:"+basis); } };
Listing 9.41 Die Klasse Basis mit virtuellen Funktionen

Der Compiler wird sich nun beschweren, dass die Methoden der abgeleiteten Klasse keine Kennzeichnung haben, ob es sich um eine berschreibende oder eine neue Methode handelt. Um das gewnschte Verhalten zu erreichen, mssen Sie die Methoden der Basisklasse berschreiben und die Methoden der abgeleiteten Klasse daher mit override versehen. Zustzlich muss die Subklasse ihre Methoden explizit als virtuell deklarieren:
ref class Abgeleitet : Basis { int ab; public: Abgeleitet(int i, int a) : Basis(i), ab(a) {} property int Wert { virtual int get() override {return(ab);} virtual void set(int i) override {ab=i;} } virtual void ausgabe() override { System::Console::WriteLine("Abgeleitet:"+ab); } };
Listing 9.42 Die Klasse Abgeleitet mit virtuellen Methoden

465

Klassendefinition unter .NET

9.10.1 override versus new


Um den Unterschied zwischen override und new zu erklren, soll folgende Klassenhierarchie aus vier Klassen herhalten, die allesamt eine Methode Print besitzen, mit der ausgegeben wird, um welche Klasse es sich handelt. Die Methoden sind virtuell und berschreiben jeweils die Methode der Basisklasse, damit bei Aufrufen ber Basisklassenzeiger die korrekte Methode aufgerufen wird:
ref class public: virtual }; ref class public: virtual }; ref class public: virtual }; ref class public: virtual }; A { void Print() {Console::WriteLine("A");} B : public A { void Print() override {Console::WriteLine("B");} C : public B { void Print() override {Console::WriteLine("C");} D : public C { void Print() override {Console::WriteLine("D");}

Listing 9.43 Eine Klassenhierarchie

Nun erzeugen wir zwei D-Objekte und weisen sie unterschiedlichen Basisklassenzeigern zu. Die Ergebnisse der Methodenaufrufe sind wie erwartet:
A ^a=gcnew D; C ^c=gcnew D; a->Print(); // Ausgabe D c->Print(); // Ausgabe D

Jetzt wird die Methode in der Klasse C mit new deklariert:


ref class C : public B { public: virtual void Print() new {Console::WriteLine("C");} };

Durch das new beginnt fr die Methode Print in Klasse C eine neue Klassenhierarchie. Die dynamische Typprfung endet fr Print daher vor C, also bei Klasse B, beziehungsweise beginnt bei C neu. Oder anders ausgedrckt: Die

466

Abstrakte Methoden und Klassen

9.11

Methode Print der Klasse B wird in Klasse C nicht berschrieben, sondern die Methode Print in C ist eine komplett neue Methode. Wenn ber ein Trackinghandle des Typs A oder B jetzt die Print-Methode eines Typs D aufgerufen wird, dann wird die Print-Methode von B ausgefhrt, weil ab C eine neue Hierarchie beginnt. Fr alle Zeiger ab Klasse C bleibt das Verhalten gleich:
A ^a=gcnew D; C ^c=gcnew D; a->Print(); // Ausgabe B c->Print(); // Ausgabe D

Diese Eigenschaft wird zugegebenermaen weitaus seltener eingesetzt als override.

9.11

Abstrakte Methoden und Klassen

Beim Klassendesign kommt es hufiger vor, dass eine Basisklasse bereits eine gewisse Funktionalitt zur Verfgung stellt, zur korrekten Ausfhrung dieser Funktionalitt aber Informationen notwendig sind, die erst in den Subklassen zur Verfgung gestellt werden. Nehmen wir die Klasse Mitarbeiter. Sie kann eine Methode getMonatsgehalt bereitstellen, die aus dem Jahresgehalt das durchschnittliche Monats-

gehalt ermittelt. Wie das Jahresgehalt ausfllt, entscheidet sich aber erst in der Subklasse, weil ein einfacher Angestellter hchstwahrscheinlich eine andere Gehaltstruktur haben wird als ein Abteilungsleiter. Werfen wir einen Blick auf einen ersten Entwurf der Klasse Mitarbeiter:
ref class Mitarbeiter { public: float getMonatsgehalt() { return(getJahresGehalt()/12); } };

Die Klasse wird sich so noch nicht kompilieren lassen, weil der Compiler nicht wei, wo die Methode getJahresgehalt zu finden ist. Sie muss in der Klasse verfgbar gemacht werden, aber in den Subklassen durch eine spezialisierte Variante ersetzt werden knnen. Die Wahl fllt daher auf eine virtuelle Methode.

467

Klassendefinition unter .NET

Die Methode kann in Mitarbeiter aber keinen sinnvollen Rckgabewert liefern, also muss es eine bereits besprochene rein-virtuelle Methode (siehe Abschnitt 4.19.1, Rein virtuelle Methoden) werden, die auch abstrakte Methode genannt wird. Unter .NET kann zwar auch die aus ANSI C++ bekannte Schreibweise =0 verwendet werden, passender ist aber das Schlsselwort abstract. Eine Klasse mit abstrakten Methoden wird automatisch ebenfalls abstrakt, das heit, von ihr knnen keine Objekte erzeugt werden. Das macht im Fall der Klasse Mitarbeiter auch Sinn, denn ihr fehlt schlielich eine funktionsfhige Methode getJahresgehalt. Obwohl die Klasse durch die abstrakte Methode schon abstrakt ist, muss sie unter .NET explizit als abstrakt deklariert werden:
ref class Mitarbeiter abstract { public: float getMonatsgehalt() { return(getJahresgehalt()/12); } virtual float getJahresgehalt() abstract; };
Listing 9.44 Die Klasse Mitarbeiter

Eine von einer abstrakten Basisklasse abgeleitete Subklasse erbt die abstrakte Methode und ist zunchst auch abstrakt. Erst, wenn alle abstrakten Methoden berschrieben wurden, wird die Klasse konkret und kann instanziiert werden. Unter .NET ist es auch mglich, eine Klasse ohne abstrakte Methoden als abstrakt zu deklarieren. Eine davon abgeleitete Klasse ist automatisch konkret (weil sie keine abstrakten Methoden geerbt hat). Doch zurck zu unserer Firmenhierarchie. Um eine konkrete Klasse zu erhalten, soll die Klasse Abteilungsleiter abgeleitet werden, die ein Jahresgehalt und Weihnachtsgeld erhlt:
ref class Abteilungsleiter : Mitarbeiter { float jahresgehalt; float weihnachtsgeld; public: Abteilungsleiter(float jg, float wg) : jahresgehalt(jg), weihnachtsgeld(wg) {}

468

Versiegelte Klassen

9.13

virtual float getJahresgehalt() override { return(jahresgehalt+weihnachtsgeld); } };


Listing 9.45 Die Klasse Abteilungsleiter

Von dieser Klasse kann ein Objekt erzeugt und die geerbte Methode getMonatsgehalt aufgerufen werden:
Abteilungsleiter^ p=gcnew Abteilungsleiter(4000,2000); Console::WriteLine(p->getMonatsgehalt());

9.12

Versiegelte Methoden

Eine Methode ist versiegelt, wenn sie nicht mehr berschrieben werden kann. Wenn Sie zu dem Schluss kommen sollten, die Methode getJahresgehalt in Abteilungsleiter sei der Weisheit letzter Schluss und einfach undenkbar, dass jemand eine noch bessere oder sinnvollere Methode implementieren knnte, dann deklarieren Sie die Methode als sealed:
ref class Abteilungsleiter : Mitarbeiter { float jahresgehalt; float weihnachtsgeld; public: Abteilungsleiter(float jg, float wg) : jahresgehalt(jg), weihnachtsgeld(wg) {} virtual float getJahresgehalt() override sealed { return(jahresgehalt+weihnachtsgeld); } };
Listing 9.46 Eine versiegelte Methode in Abteilungsleiter

Nur virtuelle Methoden knnen versiegelt werden.

9.13

Versiegelte Klassen

Es ist aber auch mglich, eine komplette Klasse zu versiegeln. Von dieser Klasse kann dann nicht mehr abgeleitet werden. In der darzustellenden Firmenhierarchie gibt es vielleicht keine Erweiterung mehr von Abteilungsleiter,

469

Klassendefinition unter .NET

und Sie wollen eventuellen Versuchen, doch noch eine Subklasse zu erstellen, im Vorfeld bereits eine Abfuhr erteilen, dann deklarieren Sie einfach die Klasse als sealed:
ref class Abteilungsleiter sealed : Mitarbeiter { float jahresgehalt; float weihnachtsgeld; public: Abteilungsleiter(float jg, float wg) : jahresgehalt(jg), weihnachtsgeld(wg) {} virtual float getJahresgehalt() override sealed { return(jahresgehalt+weihnachtsgeld); } };
Listing 9.47 Die versiegelte Klasse Abteilungsleiter

Das sealed bei der Methode getJahresgehalt knnte theoretisch eingespart werden, denn wenn von einer Klasse nicht mehr abgeleitet werden kann, dann ist auch das berschreiben einer ihrer Methoden nicht mehr mglich. Die beiden Klassen funktionieren einwandfrei, sind aber nicht gerade .NETmig programmiert. Unter .NET sollten die Zugriffsmethoden durch Eigenschaften ersetzt werden:
ref class Mitarbeiter abstract { public: property float Monatsgehalt { float get() { return(Jahresgehalt/12); } } property float Jahresgehalt { virtual float get() abstract; } }; ref class Abteilungsleiter sealed : Mitarbeiter { float jahresgehalt; float weihnachtsgeld; public: Abteilungsleiter(float jg, float wg) : jahresgehalt(jg), weihnachtsgeld(wg) {}

470

Schnittstellen

9.14

property float Jahresgehalt { virtual float get() override sealed { return(jahresgehalt+weihnachtsgeld); } } };


Listing 9.48 Mitarbeiter und Abteilungsleiter mit Eigenschaften

Die get- und set-Funktionen der Eigenschaften knnen ebenso virtuell, abstrakt oder versiegelt sein wie normale Methoden. Wichtig ist nur, dass sich die Deklarationen auf die einzelnen Funktionen der Eigenschaft beziehen, nicht auf die gesamte Eigenschaft. Eine Eigenschaft knnte theoretisch eine abstrakt-virtuelle get-Funktion und eine versiegelte set-Funktion besitzen.

9.14

Schnittstellen

In Abschnitt 4.19, Schnittstellen, wurde das Grundprinzip von Schnittstellen bereits besprochen. Hier soll die Definition einer Schnittstelle unter .NET behandelt werden. Schnittstellen kommen immer dann zum Einsatz, wenn an eine Subklasse gestellte Anforderungen von der Basisklasse nicht geleistet werden knnen oder sollen. Gewissermaen war das auch Thema des vorangegangenen Abschnitts, denn die Klasse Mitarbeiter hat von ihren Subklassen eine Eigenschaft Jahresgehalt gefordert. In ANSI C++ ist eine Schnittstelle nichts anderes als eine Klasse, die nur aus rein-virtuellen Methoden besteht. Unter .NET existiert ein eigenes Konstrukt, dass mit dem Schlsselwort interface eingeleitet wird. Im Folgenden wird eine Schnittstelle IAusgebbar definiert, die eine Methode ausgeben fordert:
interface class IAusgebbar { void ausgeben(); };
Listing 9.49 Definition einer Schnittstelle

Eine Klasse, die von der Schnittstelle ableitet im Zusammenhang mit Schnittstellen wird von implementieren gesprochen , ist abstrakt, bis sie die von der Schnittstelle geforderten Methoden implementiert. Die in der Schnittstelle deklarierten Methoden entsprechen in ihrem Verhalten einer

471

Klassendefinition unter .NET

abstrakten Methode. Syntaktisch gibt es zu Klassen und deren Methoden aber einige Besonderheiten: Die Methoden einer Schnittstelle sind automatisch ffentlich, eine Angabe von public ist nicht notwendig. Das ist logisch, denn nur eine ffentliche Methode ist berschreibbar und von auen zugnglich. Um eine Methode sinnvoll berschreiben bzw. implementieren zu knnen, muss sie virtuell sein. Eine Methode ohne Definition ist darber hinaus auch noch abstrakt. Aus diesem Grund ist eine Schnittstellenmethode immer virtuell und abstrakt, ohne dass die entsprechenden Schlsselwrter angegeben werden mssen. Schnittstellen drfen Deklarationen von Methoden, Eigenschaften (siehe Abschnitt 9.3) und deren Fuktionen sowie von Ereignissen enthalten. Eine Schnittstelle darf keine Definitionen also Anweisungsblcke fr Methoden, Eigenschaftsfunktionen oder Ereignisse besitzen. Die Definition von statischen Elementen (Attributen, Eigenschaften, Methoden, Ereignissen) ist allerdings erlaubt. Von einer Schnittstelle knnen Wert- und Verweisklassen sowie andere Schnittstellen abgeleitet werden. Die implementierende Methode der Subklasse darf kein Schlsselwort override besitzen, da die Schnittstellenmethode nicht berschrieben, sondern implementiert wird. Die Klasse Ganzzahl implementiert die Schnittstelle IAusgebbar:
ref class Ganzzahl : IAusgebbar { int zahl; public: Ganzzahl(int i) :zahl(i) {} virtual void ausgeben() { System::Console::WriteLine(zahl); } };
Listing 9.50 Die Klasse Ganzzahl

Wie oben erwhnt, ist die implementierte Methode virtuell, besitzt aber kein override.

472

Schnittstellen

9.14

9.14.1 Schnittstellen und Mehrfachvererbung


.NET untersttzt zwar keine Mehrfachvererbung, aber eine Klasse kann mehrere Schnittstellen implementieren. In den meisten Fllen lsst sich ein auf Mehrfachvererbung basierendes Klassendesign auf die Mehrfachimplementierung von Schnittstellen reduzieren, von daher treten die Vorteile einer echten Mehrfachvererbung gegenber der damit einhergehenden Nachteile und Schwierigkeiten in den Hintergrund. Zur Demonstration wollen wir das obige Beispiel um eine Schnittstelle IGanzzahlig ergnzen, die Ganzzahl dann ebenfalls implementiert. Fr Abwechslung sorgt der Einsatz einer Eigenschaft anstelle einer Methode:
interface class IGanzzahlig { property int Zahl { int get(); } };
Listing 9.51 Die Schnittstelle IGanzzahlig

Es folgt die Implementierung in der Klasse:


ref class Ganzzahl : IAusgebbar, IGanzzahlig { int zahl; public: Ganzzahl(int i) :zahl(i) {} virtual void ausgeben() { System::Console::WriteLine(zahl); } property int Zahl { virtual int get() { return(zahl); } } };
Listing 9.52 Mehrfachimplementierung bei Ganzzahl

Die zu implementierenden Schnittstellen werden einfach mit Komma getrennt im Klassenkopf aufgefhrt.

473

Klassendefinition unter .NET

9.14.2 Identische Methoden


Wenn es mglich ist, in einer Klasse mehrere Schnittstellen zu implementieren, dann kann es theoretisch auch passieren, dass zwei Schnittstellen zwei Methoden mit demselben Namen und derselben Signatur vorgeben:
interface class A { void ausgabe(); }; interface class B { void ausgabe(); };
Listing 9.53 Zwei Schnittstellen mit derselben Methode

Wenn diese beiden Methoden dieselbe Bestimmung haben es also ausreicht, fr beide Methoden eine Implementierung zu besitzen , dann unterscheidet sich der Mechanismus nicht von dem bisher angewendeten:
ref class C : A, B { public: virtual void ausgabe() { Console::WriteLine("Ausgabe"); } };
Listing 9.54 Gemeinsame Implementierung

Unerheblich, ber welche Schnittstelle die Methode ausgerufen wird, das Ergebnis ist dasselbe:
C^ c=gcnew C; A^ a=c; B^ b=c; a->ausgabe(); b->ausgabe();

Es ist aber durchaus mglich, dass die Methoden der beiden Schnittstellen, obwohl gleichen Namens, eine unterschiedliche Implementierung bentigen. Dazu ist eine spezielle Syntax notwendig, weil es keine zwei Methoden im selben Gltigkeitsbereich mit gleichem Namen und gleicher Signatur geben kann:
ref class C : A, B { public:

474

Delegaten

9.15

virtual void ausgabea() = A::ausgabe { Console::WriteLine("A"); } virtual void ausgabeb() = B::ausgabe { Console::WriteLine("B"); } };
Listing 9.55 Individuelle Implementierung

Die Namen der Methoden in der implementierenden Klasse (hier ausgabea und ausgabeb) sind beliebig whlbar. Der Aufruf ber die Schnittstellentypen bleibt gleich:
C^ c=gcnew C; A^ a=c; B^ b=c; a->ausgabe(); b->ausgabe();

Mssen die Methoden ber C aufgerufen werden, dann mssen wir den neuen Namen verwenden:
c->ausgabea(); c->ausgabeb();

9.15

Delegaten

Wie wir an allen bisherigen Beispielen gesehen haben, luft die Kommunikation zwischen Objekten primr ber Methodenaufrufe. Der Aufruf einer Methode kann Daten an das Objekt bergeben oder Daten aus dem Objekt liefern. Bezeichnend fr die bisherige Kommunikation ist die Passivitt der Objekte zwischen den Methodenaufrufen. Ein Becher-Objekt fhrt keinen Programmcode aus und ndert auch seinen Zustand nicht, wenn keine Methoden von ihm aufgerufen werden. Und weil allein unser Programm Methoden aufruft, kann sich der Zustand des Objekts auch nur durch unsere Aufrufe ndern. Etwas schwieriger wird es, wenn nicht nur unser Programm den Zustand eines Objekts ndert (in Multithreading-Umgebungen z. B. knnen mehrere Threads Methoden eines Objekts aufrufen, oder der Benutzer ndert den Objektzustand ber eine grafische Benutzeroberflche), wenn wir aber ber eine fremde

475

Klassendefinition unter .NET

Zustandsnderung informiert werden wollen. Der erste Schritt zur Lsung dieser Problematik besteht in einem Mechanismus, mit dem wir zur Laufzeit bestimmen knnen, welche Methoden aufgerufen werden sollen. Grundlage unserer Betrachtungen sind die in Listing 9.56 vorgestellten Testklassen:
ref class KlasseA { public: void fktA(String^ s) { Console::WriteLine("Klasse A:"+s); } }; ref class KlasseB { public: static void fktB(String^ s) { Console::WriteLine("Klasse B:"+s); } };
Listing 9.56 Die Testklassen KlasseA und KlasseB

Die Klasse KlasseA besitzt eine herkmmliche Objektmethode, KlasseB ist mit einer statischen Methode (siehe Abschnitt 4.8.1, Statische Methoden) ausgestattet. Wenn jetzt whrend der Laufzeit entschieden werden soll, ob die Methode von KlasseA oder KlasseB aufgerufen wird, dann knnen wir dies bisher nur mit einer Verzweigung programmieren:
KlasseA^ objA=gcnew KlasseA; Console::Write("1 fr KlasseA, 2 fr KlasseB:"); int x=Convert::ToInt32(Console::ReadLine()); if(x==1) objA->fktA("test"); else if(x==2) KlasseB::fktB("test");
Listing 9.57 Methodenverweise zur Laufzeit ohne Delegaten

Wir kennen bisher aber noch keine Mglichkeit, die ermittelte Methode einer anderen Methode zu bergeben, gewissermaen ein Verweis auf eine Methode. Und genau hier kommen die Delegaten ins Spiel. Ein Delegat ist nmlich von seiner Bedeutung her nichts anderes als ein Verweis auf eine Methode. Wird der Delegat aufgerufen, ruft dieser die Methode auf, auf die er verweist. Abbildung 9.3 stellt diesen Sachverhalt grafisch dar.

476

Delegaten

9.15

Methode Aufrufendes Objekt Delegat Methode


Abbildung 9.3 Die Funktionsweise eines Delegaten

Um einen Delegaten zu definieren, mssen Sie zuerst den gewnschten Delegattyp spezifizieren. Der Delegattyp sieht aus wie eine Funktions- oder Methodendeklaration, wird aber mit dem Schlsselwort delegate eingeleitet:
delegate void DelegatTyp(String^);

Die Definition eines Delegattyps kann auerhalb einer Klasse stehen oder Bestandteil einer Klasse sein. Im letzteren Fall knnen Sie den Zugriff auf den Delegattyp ber ein entsprechendes Zugriffsrecht einschrnken (vergleichbar mit typedef, siehe Abschnitt 4.9, typedef). Die Syntax einer Delegatdeklaration:
delegate Rckgabetyp Delegattypname(Parameterliste);

Wie Abbildung 9.3 zeigt, verweist ein Delegat auf Methoden. Auf welche Methoden er verweisen kann, wird ber seine Signatur festgelegt. Der oben definierte Typ DelegatTyp kann nur auf Methoden zeigen, die keinen Rckgabewert und einen Funktionsparameter des Typs String^ besitzen. Wird ein Delegat erzeugt, dann wird dem Konstruktor entweder ein Objekt und eine Methode der Klasse bergeben, wenn es sich um eine Objektmethode handelt, oder nur eine Methode, wenn auf eine statische Methode verwiesen werden soll. Delegaten selbst sind Verweistypen. Im Folgenden macht das Beispiel aus Listing 9.57 Gebrauch von einem Delegaten:
KlasseA^ objA=gcnew KlasseA; Console::Write("1 fr KlasseA, 2 fr KlasseB:"); int x=Convert::ToInt32(Console::ReadLine()); DelegatTyp^ dlg; if(x==1) dlg=gcnew DelegatTyp(objA, &KlasseA::fktA); else if(x==2)

477

Klassendefinition unter .NET

dlg=gcnew DelegatTyp(&KlasseB::fktB); dlg->Invoke("test");


Listing 9.58 Methodenverweise mit Delegaten

Um die Methode selbst von einem Aufruf derselbigen unterscheiden zu knnen, wird ein & davor gesetzt. Bei dieser Methodenangabe muss auch bei Objektmethoden die Klasse mit angegeben werden. ber die Delegat-Methode Invoke sie besitzt dieselbe Signatur wie der Delegattyp werden die Methoden, auf die der Delegat verweist, aufgerufen. Der Delegat selbst ist ein Objekt und knnte daher einer Methode bergeben werden.

9.15.1 Multicast-Delegaten
Ein Delegat kann auch auf mehrere Methoden verweisen, man spricht in diesem Fall von einem Multicast-Delegaten. Dazu werden bestehende Delegaten mit dem Operator + verknpft. Daraus entsteht ein neuer Delegat:
KlasseA^ objA=gcnew KlasseA; DelegatTyp^ dlg1=gcnew DelegatTyp(objA, &KlasseA::fktA); DelegatTyp^ dlg2=gcnew DelegatTyp(&KlasseB::fktB); DelegatTyp^ mdlg=dlg1+dlg2; mdlg->Invoke("test");
Listing 9.59 Beispiel eines Multicast-Delegaten

Ein Delegat kann auch mit += zu einem anderen hinzugefgt werden:


dlg1+=dlg2;

Methodenverweis entfernen

Um einen Methodenverweis aus einem Multicast-Delegaten zu entfernen, mssen Sie die zu entfernende Methode zuerst in einen Delegaten packen und diesen dann mit -= entfernen. Die folgende Anweisung entfernt den Verweis auf KlasseB::fktB aus dem Delegaten dlg1:
dlg1-=gcnew DelegatTyp(&KlasseB::fktB);

478

Ereignisse

9.16

9.16

Ereignisse

Kommen wir wieder auf das ursprngliche Problem dieses Kapitels zu sprechen; wir wollen ber eine von fremdem Code ausgefhrte nderung eines Objektzustands informiert werden. Der Schlssel dazu sind die Ereignisse (events), deren Funktionsweise in Abbildung 9.4 zusammengefasst ist.
Abonniert

Lst aus

Ereignis (Event)

Ruft auf

EreignisHandler (Methode)

EreignisEmpfnger

Ereignis- Verffentlicht Quelle


Abbildung 9.4 Die Funktionsweise von Ereignissen

Ein Objekt kann die nderung seines Objektzustands ber ein Ereignis zugnglich machen. Dieses Objekt ist die Ereignisquelle. Sie verffentlicht das Ereignis, so dass andere Objekte es abonnieren knnen. Sie werden damit zu Ereignisempfngern. Bei einer Auslsung des Ereignisses werden alle Ereignisempfnger benachrichtigt. Gehen wir diesen Mechanismus Schritt fr Schritt durch. Die Art, wie ein Ereignis sein Auslsen mitteilt, wird ber einen Delegattyp bestimmt. In unserem Fall soll die Ereignisquelle lediglich einen String bermitteln. Die einem Ereignis zugrunde liegenden Delegaten werden Ereignis-Handler bzw. EventHandler genannt, der besseren Wiedererkennung wegen enden die Namen dieser Delegattypen unter .NET mit EventHandler. Wir folgen dieser Konvention und nennen unseren Delegattypen TextEventHandler:
delegate void TextEventHandler(String^);

Die Ereignisquelle stellt nun ein auf diesem Delegaten basierendes Ereignis bereit:
ref class Ereignisquelle { public: event TextEventHandler^ Text; void EreignisAuslsen() {

479

Klassendefinition unter .NET

Text("Ereignistext"); // Auslsen des Ereignisses } };


Listing 9.60 Die Ereignisquelle

Die Syntax der Ereignisdefinition:


event Delegattyp Ereignisname;

Ein Ereignis kann nur innerhalb der Klasse ausgelst werden. Daher besitzt Ereignisquelle eine Hilfsmethode EreignisAuslsen, mit der spter das Auftreten des Ereignisses simuliert wird.
Achtung Ein Ereignis kann nur innerhalb der Klasse ausgelst werden.

Um das Beispiel zu vervollstndigen, kommt noch eine an diesem Ereignis interessierte Klasse Ereignisempfnger hinzu:
ref class Ereignisempfnger { public: void eventbehandlung(String^ s) { Console::WriteLine("Ereignis ausgelst:"+s); } };
Listing 9.61 Der Ereignisempfnger

Die Klasse Ereignisempfnger besteht nur aus einer Methode, die bei der Auslsung des Ereignisses aufgerufen werden soll. Damit ein Ereignis auftreten und auf dieses reagiert werden kann, sind entsprechende Objekte ntig:
Ereignisquelle^ q=gcnew Ereignisquelle; Ereignisempfnger^ e=gcnew Ereignisempfnger;

Abonniert wird ein Ereignis, indem Sie die gewnschte Methode in einen Delegaten packen und diesen dem Ereignis mit += hinzufgen:
q->Text+=gcnew TextEventHandler(e, &Ereignisempfnger::eventbehandlung);

Schlussendlich kann ber EreignisAuslsen das Ereignis ausgelst werden:


q->EreignisAuslsen();

480

Ereignisse

9.16

Bei einer sauberen Programmierung sorgt der Ereignisempfnger selbst fr ein Abonnement des gewnschten Ereignisses, wie Sie im kommenden Beispiel sehen werden.

9.16.1 Die Klasse Becher mit Ereignissen


Dieser Abschnitt stattet die Klasse Becher zu Demonstrationszwecken mit einem Ereignis aus, das bei einem leeren Becher ausgelst wird. Dazu bentigen wir zuerst die Methoden Leeren und Fuellen, mit denen die Fllmenge des Bechers verndert werden kann:
int Leeren(int menge) { if(menge>=AbsoluteMenge) { int m=static_cast<int>(AbsoluteMenge); fuellhoehe=0; return(m); } else { AbsoluteMenge-=menge; return(menge); } } int Fuellen(int menge) { if(menge>(fassungsvermoegen-AbsoluteMenge)) { int m=static_cast<int>(fassungsvermoegen-AbsoluteMenge); fuellhoehe=100; return(m); } else { AbsoluteMenge+=menge; return(menge); } }
Listing 9.62 Die Methoden Leeren und Fuellen von Becher

Die Methoden geben die hinzuzufgende oder abzuziehende Menge in Millilitern an und liefern die tatschlich hinzugefgte/abgezogene Menge in Millilitern zurck. Wie im vorigen Abschnitt besprochen, bentigen wir zu allererst einen Delegaten, der die Signatur des Ereignisses festlegt. In diesem Fall reicht der bloe Aufruf einer Methode, Parameter sind nicht notwendig. Der Delegat soll BecherLeerEventHandler heien und wird vor der Klassendefini-

481

Klassendefinition unter .NET

tion von Becher platziert (falls Sie sich ber den langen Namen des Delegaten wundern, dann warten Sie einmal ab, bis Sie mit den EventHandlern von .NET konfrontiert werden!):
delegate void BecherLeerEventHandler();

Darauf fuend bekommt die Klasse Becher ein Ereignis geschneidert, dass BecherLeer genannt wird:
ref class Becher { // ... public: event BecherLeerEventHandler^ BecherLeer; // ... };

Das hinzugefgte Ereignis muss jetzt noch bei leerem Becher ausgelst werden. Das geschieht passenderweise in der Methode Leeren:
int Leeren(int menge) { if(menge>=AbsoluteMenge) { int m=static_cast<int>(AbsoluteMenge); fuellhoehe=0; BecherLeer(); return(m); } else { AbsoluteMenge-=menge; return(menge); } }
Listing 9.63 Die Methode Leeren mit Ereignisauslser

Wer wre prdestinierter, auf einen leeren Becher zu reagieren, als ein Kellner? Schnell schreiben wir eine solche Klasse, stellen sein Licht aber insofern unter den Scheffel, als der Kellner hier nur einen Becher beobachten kann:
ref class Kellner { Becher^ becher; void nachfuellen() { Console::WriteLine("Nachfllen: "+becher); } public: Kellner(Becher^ b) : becher(b) {

482

Ereignisse

9.16

becher->BecherLeer+=gcnew BecherLeerEventHandler(this, &Kellner::nachfuellen); } };


Listing 9.64 Die Klasse Kellner

Der Konstruktor des Kellners bekommt den zu beobachtenden Becher bergeben und abonniert sein Ereignis BecherLeer. Folgendes Fragment zeigt die Funktionsweise:
Becher^ b=gcnew Becher("Milch", 300, 100); Kellner^ k=gcnew Kellner(b); Console::WriteLine(b->Leeren(200)); Console::WriteLine(b->Leeren(200));

Beim zweiten Aufruf von Leeren werden die noch im Becher verbliebenen 100 ml geleert und der Kellner benachrichtigt.

483

Dieses Kapitel fasst einige ntzliche Klassen des .NET-Frameworks zusammen, die ansonsten ber das Buch verteilt htten werden mssen. Dazu zhlen u. a. String-Klassen und die Collections.

10
10.1

Ntzliche .NET-Klassen
CultureInfo

Heutzutage wird Software international vermarktet und auf allen Erdteilen eingesetzt. Einmal abgesehen von der offensichtlichsten Problematik der unterschiedlichen Sprachen und Nationalitten ihrer Anwender, gilt es auch noch andere Unterschiede zu bercksichtigen. Sie reichen von der Sortierung von Text, die abhngig vom verwendeten Zeichensatz sein kann, ber den verwendeten Kalender und der damit einhergehenden Darstellung des Datums bis hin zur Darstellung von Whrungen, numerischen und prozentualen Werten. Diese Informationen werden unter .NET in Objekten des Typs CultureInfo gespeichert. Jede laufende .NET-Anwendung verwendet ein solches Objekt, um die oben angesprochenen Punkte der ausgewhlten Kultur gem darzustellen. Um innerhalb des Programms an das eingesetzte CultureInfo Objekt zu gelangen, muss zunchst ein Trackinghandle auf den laufenden Thread ermittelt werden. Die Klasse Thread steht im Namensbereich System::Threading und besitzt eine statische Eigenschaft CurrentThread, die einen Verweis auf den aktuellen Thread liefert:
Threading::Thread^ th=Threading::Thread::CurrentThread;

Thread Laufende Anwendungen werden im Betriebssystem als Threads gefhrt. Jeder Thread kann wiederum mehrere Prozesse besitzen, die parallel abgearbeitet werden (Multithreading).

485

10

Ntzliche .NET-Klassen

ber die Eigenschaft CultureInfo des Threads kann das aktuell verwendete CultureInfo-Objekt ermittelt oder neu gesetzt werden. Die Klasse Culture Info gehrt zum Namensbereich System::Globalization:
Globalization::CultureInfo^ ci; ci=Threading::Thread::CurrentThread->CurrentCulture;

Einige Eigenschaften von CultureInfo sind in Tabelle 10.1 aufgefhrt.


Eigenschaft
Calendar CompareInfo

Beschreibung Liefert das von der Kultur verwendete Calendar-Objekt. Liefert ein CompareInfo-Objekt, welches beschreibt, wie Zeichenketten von der Kultur verglichen werden (wichtig fr korrekte Vergleiche von kulturabhngigen Sonderzeichen). Liefert oder setzt das verwendete DateTimeFormatInfoObjekt, welches die Darstellung von Datum und Uhrzeit festlegt. Liefert den Kulturnamen in der Sprache der Kultur. Liefert den Kulturnamen auf Englisch. Liefert Sprach- und Regionscode der Kultur. Liefert oder setzt das verwendete NumberFormatInfo-Objekt, welches die Darstellung von Zahlen, Whrungen und Prozenten definiert. Liefert ein Array von Calendar-Objekten, die von der Kultur verwendet werden knnen.

DateTimeFormat

DisplayName EnglishName Name NumberFormat

OptionalCalendars

Tabelle 10.1 Einige Eigenschaften von CultureInfo

Auf den Aufbau der Format-Klassen wird im weiteren Verlauf noch eingegangen. Es knnen auch eigene CultureInfo-Objekte erstellt werden. Dem Konstruktor wird dabei die Bezeichnung der Kultur bergeben. Im Folgenden wird ein CultureInfo-Objekt fr Deutschland erzeugt:
Globalization::CultureInfo^ ci; ci=gcnew Globalization::CultureInfo("de-DE");

Die Lndercodes entnehmen Sie bitte der .NET-Dokumentation.

10.1.1

DateTimeFormatInfo

Die Klasse DateTimeFormatInfo aus dem Namensbereich Globalization beschreibt, welche Formate bei der Stringformatierung untersttzt werden

486

CultureInfo

10.1

und stellt Muster zum individuellen Zusammenstellen der Datums- und Uhrzeitinformationen bereit. Tabelle 10.2 zeigt die Formate angewendet auf den 23. Mai 1949, 12 Uhr. 123
Format
d D f F g G m oder M r oder R s t T u U y oder Y

Ergebnis 23.05.1949 Montag, 23. Mai 1949 Montag, 23. Mai 1949 12:00 Montag, 23. Mai 1949 12:00:00 23.05.1949 12:00 23.05.1949 12:00:00 23 Mai Mon, 23 May 1949 12:00:00 GMT1 1949-05-23T12:00:002 12:00 12:00:00 1949-05-23 12:00:00Z Montag, 23. Mai 1949 10:00:003 Mai 1949

Tabelle 10.2 Die Datumsformate

Das folgende Beispiel gibt das oben verwendete Datum in einer ausfhrlichen Version aus:
DateTime^ dt= gcnew DateTime(1949, 5,23,12,0,0); Console::WriteLine("{0:D}", dt);

Tabelle 10.3 listet die bei der individuellen Formatierung von Datums- und Zeitangaben verfgbaren Platzhalter auf. Wenn ein Platzhalter zustzlich in der Schreibweise mit % vorkommt (z. B. %d), dann muss dieser immer dann statt des einzelnen Buchstabens verwendet werden, wenn der Platzhalter einzeln verwendet wird.

1 RFC1123 2 Sortierbar nach ISO 8601 mit Ortszeit 3 Verwendung der koordinierten Weltzeit

487

10

Ntzliche .NET-Klassen

Format
d oder %d dd ddd

Beschreibung der Tag des Monats ohne fhrende Null der Tag des Monats mit fhrender Null dreistellige Abkrzung des Wochentags, definiert unter
AbbreviatedDayNames

dddd M oder %M MM MMM

vollstndiger Name des Wochentags, definiert unter DayNames der Monat ohne fhrende Null der Monat mit fhrender Null dreistellige Abkrzung des Monats, definiert unter
AbbreviatedMonthNames

MMMM y oder %y yy yyyy gg h oder %h hh H oder %H HH

vollstndiger Name des Monats, definiert unter MonthNames das Jahr ohne Jahrhundert und ohne fhrende Null das Jahr ohne Jahrhundert mit fhrender Null vierstellige Jahreszahl der Zeitraum (wenn vorhanden) die Stunde in 12h-Schreibweise ohne fhrende Null die Stunde in 12h-Schreibweise mit fhrender Null die Stunde in 24h-Schreibweise ohne fhrende Null Die Stunde in 24h-Schreibweise mit fhrender Null. Einstellige Stundenangaben weisen eine fhrende Null auf. die Minute ohne fhrende Null die Minute mit fhrender Null die Sekunde ohne fhrende Null die Sekunde mit fhrender Null der Sekundenbruchteil auf eine Stelle genau der Sekundenbruchteil auf zwei, drei, vier, fnf, sechs oder sieben Stellen genau, ohne fhrende Nullen das erste Zeichen des AM-/PM-Kennzeichners, wie es unter AMDesignator und PMDesignator angegeben ist der AM-/PM-Kennzeichner, wie er unter AMDesignator und PMDesignator definiert ist Zeitzonenoffset in Stunden (inkl. + oder -) ohne fhrende Null Zeitzonenoffset in Stunden (inkl. + oder -) mit fhrender Null

m oder %m mm s oder %s ss f oder %f ff, fff, ffff, fffff, ffffff, fffffff t oder %t tt

z oder %z zz

Tabelle 10.3 Die Platzhalter bei DateTimeFormatInfo

488

String

10.2

Format
zzz

Beschreibung vollstndiger Zeitzonenoffset in Stunden und Minuten (inkl. + oder -) mit fhrenden Nullen Trennzeichen fr Zeitangaben, definiert in TimeSeparator Trennzeichen fr Datumsangaben, definiert in DateSeparator

: /

Tabelle 10.3 Die Platzhalter bei DateTimeFormatInfo (Forts.)

10.1.2

NumberFormatInfo

Die ebenfalls im Namensbereich Globalization definierte Klasse NumberFormatInfo definiert die bei der Ausgabe numerischer Werte mglichen Formate. Tabelle 10.4 listet sie auf.
Format
C D E F G N P X

Bedeutung lokales Whrungsformat (Currency) dezimale Ganzzahl Exponentialschreibweise (3,5E2) Fliekomma-Format automatische Wahl der kompakteren Form von E oder F numerische Zahl einschlielich Separatoren Prozentzahl hexadezimale Zahl

Tabelle 10.4 Die mglichen Formate von NumberFormatInfo

10.2

String

Die Klasse String gehrt zum Namensbereich System. Ein Objekt kann durch dynamisches Erzeugen mit gcnew oder dem Zuweisen einer Zeichenkette erstellt werden:
String^ s1=gcnew String("Andr"); String^ s2="Willms";

Fr Strings ist der +-Operator berladen, der die einzelnen Zeichenketten zu einer Zeichenkette verknpft:
String^ s3=s1+" "+s2; Console::WriteLine(s3);

489

10

Ntzliche .NET-Klassen

Die Eigenschaft Length dient der Ermittlung der Zeichenanzahl im String. Die Klasse String stellt einige Methoden zur Verfgung, die den String-Inhalt scheinbar ndern. Aber Achtung: Diese Methoden ndern nicht den bestehenden String, sondern erzeugen einen neuen String, der die vorgenommenen nderungen enthlt. Dementsprechend schlecht ist auch die Performance. Wenn viele nderungen an Strings vorgenommen werden mssen, sollten Sie Objekte der Klasse StringBuilder verwenden.

10.2.1

String-Inhalte ansprechen

Die rudimentrste Methode, Inhalte des Strings zu erhalten, ist der zeichenweise Zugriff ber den Indexer der Klasse. Der folgende Code gibt einen String zeichenweise aus:
s1=".NET ist nett"; for(int i=0; i<s1->Length; i++) Console::Write(s1[i]); Console::WriteLine();

Substring
String^ Substring(int position); String^ Substring(int position, int laenge);

Mit der Methode Substring kann ein Teilstring herauskopiert werden. Die Methode gibt es in zwei Ausfhrungen. Wird nur die Position angegeben, dann besteht der Teilstring aus allen Zeichen ab der angegebenen Position bis zum Ende des Originalstrings. Bei zustzlicher Lngenangabe ist der Teilstring so lang, wie angegeben. Sind ab der angegebenen Position weniger Zeichen im Originalstring, als bei Lnge angegeben, dann wird eine ArgumentOutOfRangeException geworfen.
s1=".NET ist nett"; Console::WriteLine(s1->Substring(1,3)); // Liefert "NET"

Split
array<String^>^ Split(array<wchar_t>^ trennzeichen);

Die Methode Split zerlegt einen String in Teilstrings, wobei die als Trennzeichen bergebenen Zeichen als Trennkriterium verwendet werden. Das folgende Beispiel zerlegt einen String und gibt die erzeugten Teilstrings mit einer Schleife aus:

490

String

10.2

s1=".NET ist nett"; array<String^>^ sa = s1->Split(' '); for(int i=0; i<sa->Length; i++) Console::WriteLine(sa[i]);

Abgesehen von der oben aufgefhrten Version, ist Split noch mit einigen Varianten berladen, bei denen z. B. die maximale Anzahl an erzeugten Teilstrings angegeben werden kann.

10.2.2 String-Inhalte verndern


Replace
String^ Replace(wchar_t wchar_t String^ Replace(String^ String^ altesZeichen, neuesZeichen); alterTeilstring, neuerTeilstring);

Die Methode ersetzt jedes Vorkommen des alten Zeichens/Strings durch das neue Zeichen bzw. den neuen String.
ToLower
String^ ToLower(); String^ ToLower(CultureInfo^ kultur);

Wandelt den String gem der aktuellen oder der bergebenen Kultur in Kleinbuchstaben um. Das Ergebnis wird zurckgeliefert.
ToUpper
String^ ToUpper(); String^ ToUpper(CultureInfo^ kultur);

Wandelt den String gem der aktuellen oder der bergebenen Kultur in Grobuchstaben um. Das Ergebnis wird zurckgeliefert.

10.2.3 String-Inhalte hinzufgen


Insert
String^ Insert(int position, String^ string);

Insert fgt in den aufrufenden String an der angegebenen Position einen anderen String ein und liefert den neuen String zurck. Ist die Position nicht im gltigen Bereich, wird eine ArgumentOutOfRangeException geworfen. Sollte als

491

10

Ntzliche .NET-Klassen

einzufgender String nullptr bergeben werden, wird eine ArgumentNullException geworfen.


String ^s1="A Willms"; s1=s1->Insert(1,"ndre"); Console::WriteLine(s1);

Damit s1 auf den neuen String verweist, muss dem Handle der Rckgabewert von Insert zugewiesen werden.
PadLeft
String^ PadLeft(int gesamtbreite); String^ PadLeft(int gesamtbreite, wchar_t fuellzeichen);

Die Methode fgt so lange Leerzeichen (oder das Zeichen fuellzeichen) am Anfang des Strings ein, bis seine Breite gesamtbreite entspricht und richtet den String damit rechtsbndig aus. Sollte gesamtbreite zu Beginn kleiner sein als die Lnge des Strings, dann wird der String unverndert zurckgeliefert.
PadRight
String^ PadRight(int gesamtbreite); String^ PadRight(int gesamtbreite, wchar_t fuellzeichen);

Die Methode fgt so lange Leerzeichen (oder das Zeichen fuellzeichen) am Ende des Strings ein, bis seine Breite gesamtbreite entspricht und richtet den String damit linksbndig aus. Sollte gesamtbreite zu Beginn kleiner sein als die Lnge des Strings, dann wird der String unverndert zurckgeliefert.

10.2.4 String-Inhalte lschen


Remove
String^ Remove(int position); String^ Remove(int position, int lnge);

Mittels Remove kann ein Teilstring aus einem String entfernt werden. Das Ergebnis wird von der Methode zurckgeliefert. Wird nur die Position angegeben, fllt der Teilstring ab der Position bis zum Ende des Strings dem Lschen zum Opfer. Bei einer zustzlichen Lngenangabe bezieht sich das Entfernen nur auf die angegebene Lnge. Sollten position oder lnge einzeln oder in Kombination einen ungltigen Bereich des Strings ansprechen, dann wird eine ArgumentOutOfRangeException geworfen.

492

String

10.2

String ^s1="Andr Willms"; s1=s1->Remove(1,4)->Insert(1,"."); Console::WriteLine(s1); // Liefert "A. Willms"

Weil die Methoden von String das Ergebnis zurckliefern, kann im oberen Beispiel fr das von Remove zurckgelieferte Objekt gleich die Methode Insert aufgerufen werden, deren Ergebnis dann s1 zugewiesen wird.
Trim
String^ String^ String^ String^ Trim() Trim(array<wchar_t>^ zuLoeschendeZeichen); TrimEnd(array<wchar_t>^ zuLoeschendeZeichen); TrimStart(array<wchar_t>^ zuLoeschendeZeichen);

Die Methode Trim lscht alle Leerzeichen und Whitespaces (bzw. die im Array zuLoeschendeZeichen angegebenen Zeichen) am Anfang und am Ende des Strings und liefert das Ergebnis zurck. TrimEnd lscht die angegebenen Zeichen nur am Ende des Strings, TrimStart nur am Anfang.

10.2.5 Strings vergleichen


Zum einen sind fr die Klasse String die Vergleichsoperatoren == und != berladen, zum anderen stehen die folgenden Methoden zur Verfgung.
Compare
static int Compare(String^ s1, String^ s2); static int Compare(String^ s1, String^ s2, bool ignoriereGrossKlein)

Die Methode vergleicht zwei Strings und liefert ein Ergebnis gem Tabelle 10.5. Die zweite Variante von Compare erlaubt es, beim Vergleich die Ground Kleinschreibung zu ignorieren.
Compare(s1,s2) mit
a<b a==b a>b

Ergebnis
<0 0 >0

Tabelle 10.5 Die Rckgabewerte von String::Compare

493

10

Ntzliche .NET-Klassen

CompareTo
int CompareTo(String^ s2);

Die Methode vergleicht den aufrufenden String mit dem bergebenen String und liefert einen der in Tabelle 10.5 aufgefhrten Rckgabewerte.
EndsWith
bool EndsWith(String^ s); bool EndsWith(String^ s, bool ignoriereGrossKlein, CultureInfo^ kultur);

Die Methode liefert true, wenn der aufrufende String mit den Zeichen in s beginnt. Andernfalls liefert sie false. In der zweiten Fassung kann zustzlich noch bestimmt werden, ob Gro- und Kleinschreibung bercksichtigt und fr welche Kultur der Vergleich durchgefhrt werden soll.
StartsWith
bool StartsWith(String^ s); bool StartsWith(String^ s, bool ignoriereGrossKlein, CultureInfo^ kultur);

Die Methode liefert true, wenn der aufrufende String mit den Zeichen in s endet. Andernfalls liefert sie false. In der zweiten Fassung kann zustzlich noch bestimmt werden, ob Gro- und Kleinschreibung bercksichtigt und fr welche Kultur der Vergleich durchgefhrt werden soll.

10.2.6 Suchen in Strings


IndexOf
int IndexOf(String^ s) int IndexOf(String^ s, int startposition) int IndexOf(String^ s, int startposition, int anzahl)

Die oben angegebenen Methoden sind auch noch fr einzelne Zeichen berladen. Die Methode sucht ab dem Beginn des Strings oder ab startposition bis zum Ende oder fr maximal anzahl Positionen nach dem String s. Wird er gefunden, dann liefert die Methode den Index der Fundstelle zurck. Bei erfolgloser Suche ist der Rckgabewert 1.

494

String

10.2

IndexOfAny
int IndexOfAny(array<wchar_t>^ zeichen); int IndexOfAny(array<wchar_t>^ zeichen, int startPosition); int IndexOfAny(array<wchar_t>^ zeichen, int startPosition, int anzahl);

Die Methode sucht ab dem Beginn des Strings oder ab startposition bis zum Ende oder fr maximal anzahl Positionen nach einem der im Array zeichen befindlichen Zeichen. Wird eines gefunden, dann liefert die Methode den Index der Fundstelle zurck. Bei erfolgloser Suche ist der Rckgabewert 1.
LastIndexOf
int LastIndexOf(String^ s) int LastIndexOf(String^ s, int startposition) int LastIndexOf(String^ s, int startposition, int anzahl)

Die oben angegebenen Methoden sind auch noch fr einzelne Zeichen berladen. Die Methode sucht ab dem Ende des Strings oder ab startposition bis zum Anfang oder fr maximal anzahl Positionen nach dem String s. Wird er gefunden, dann liefert die Methode den Index der Fundstelle zurck. Bei erfolgloser Suche ist der Rckgabewert 1.
LastIndexOfAny
int LastIndexOfAny(array<wchar_t>^ zeichen); int LastIndexOfAny(array<wchar_t>^ zeichen, int startPosition); int LastIndexOfAny(array<wchar_t>^ zeichen, int startPosition, int anzahl);

Die Methode sucht ab dem Ende des Strings oder ab startposition bis zum Anfang oder fr maximal anzahl Positionen nach einem der im Array zeichen befindlichen Zeichen. Wird eines gefunden, dann liefert die Methode den Index der Fundstelle zurck. Bei erfolgloser Suche ist der Rckgabewert 1.

10.2.7 Strings formatieren


Format
static String^ Format (String^ format, Object^ argument); static String^ Format (String^ format, array<Object^>^ argumente;)

495

10

Ntzliche .NET-Klassen

Um Strings vergleichbar mit der Ausgabe von Write und WriteLine zu formatieren, existiert die statische Methode Format.
int x=20; String^ s2=String::Format("Der Wert lautet {0}",x); Console::WriteLine(s2);

Die in Abschnitt 10.1, CultureInfo, vorgestellten Formate knnen hier ebenfalls verwendet werden.

10.3

StringBuilder

Die Klasse StringBuilder gehrt zum Namensbereich System::Text. Ihre Objekte reprsentieren genau wie String-Objekte eine Zeichenkette. Sie sind jedoch auf die Zeichenverarbeitung spezialisiert. nderungen am String werden immer direkt am Objekt vorgenommen und nicht wie bei String in einem neuen Objekt abgelegt. Die Methoden von StringBuilder liefern zwar auch Verweise auf Objekte zurck, es ist aber ein Verweis auf das aufrufende Objekt, um kaskadierte Aufrufe wie im Beispiel zu Remove im Abschnitt Remove in Abschnitt 10.2.4 zu ermglichen. Genau wie String stellt StringBuilder die Eigenschaft Length zur Verfgung, um die Anzahl der im Objekt gespeicherten Zeichen ermitteln zu knnen. Aus einem StringBuilder-Objekt kann ber die Methode ToString ein String-Objekt erzeugt werden.

10.3.1 String-Inhalte ansprechen


Um den Inhalt eines Objekts anzusprechen, stellt die Klasse StringBuilder nur einen Indexer zur Verfgung, mit dem Zeichen gelesen und beschrieben werden knnen. Werden Methoden wie Substring bentigt, dann muss aus dem StringBuilder-Objekt ein String erzeugt werden.

10.3.2 String-Inhalte hinzufgen


Append
StringBuilder^ Append(CLSTyp obj);

Fr CLSTyp kann einer der in Tabelle 8.1 in Abschnitt 8.2.3, Common Type System (CTS), vorgestellten Datentypen eingesetzt werden. Die Methode wandelt den entsprechenden Typ in eine Zeichenkette um und hngt sie an

496

StringBuilder

10.3

die Zeichenkette im StringBuilder-Objekt an. Zurckgeliefert wird ein Verweis auf das aufrufende StringBuilder-Objekt.
AppendFormat
StringBuilder^ AppendFormat(String^ format, Object^ argument); StringBuilder^ AppendFormat(String^ format, array<Object^>^ argumente);

Erzeugt eine formatierte Zeichenkette, vergleichbar mit der Ausgabe von Write und WriteLine, und hngt diese an die Zeichenkette im StringBuilderObjekt an. Zurckgeliefert wird ein Verweis auf das aufrufende StringBuilder-Objekt.
Insert
StringBuilder^ Insert(int position, CLSTyp obj);

Fr CLSTyp kann einer der in Tabelle 8.1 in Abschnitt 8.2.3, Common Type System (CTS), vorgestellten Datentypen eingesetzt werden. Die Methode wandelt den entsprechenden Typ in eine Zeichenkette um und fgt in an Position position in die Zeichenkette im StringBuilder-Objekt ein. Zurckgeliefert wird ein Verweis auf das aufrufende StringBuilder-Objekt.

10.3.3 String-Inhalte verndern


Zum zeichenweisen Verndern des Inhalts steht der Indexer der Klasse bereit sowie die Methode Replace.
Replace
StringBuilder^ Replace(wchar_t wchar_t StringBuilder^ Replace(String^ String^ altesZeichen, neuesZeichen); alterTeilstring, neuerTeilstring);

Die Methode ersetzt jedes Vorkommen des alten Zeichens/Strings durch das neue Zeichen bzw. den neuen String. Zurckgeliefert wird ein Verweis auf das aufrufende StringBuilder-Objekt.

497

10

Ntzliche .NET-Klassen

10.3.4 String-Inhalte lschen


Remove
StringBuilder^ Remove(int position, int laenge);

Mittels Remove wird ein laenge Zeichen langer Teilstring an Position position aus einem StringBuilder-Objekt entfernt. Zurckgeliefert wird ein Verweis auf das aufrufende StringBuilder-Objekt. Sollten position oder laenge einzeln oder in Kombination einen ungltigen Bereich des Strings ansprechen, dann wird eine ArgumentOutOfRangeException geworfen.

10.3.5 StringBuilder-Objekte vergleichen


Equals
bool Equals(StringBuilder^ sb);

Liefert true zurck, wenn das aufrufende und das bergebene StringBuilder-Objekt den gleichen Inhalt besitzen. Andernfalls ist der Rckgabe-

wert false.

10.4

Char

Die Klasse Char reprsentiert ein Unicode-Zeichen des Typs wchar_t. Sie bietet Methoden zur Bestimmung, welcher Art das Zeichen ist. Tabelle 10.6 listet die wichtigsten Methoden auf. Sie kommen in zwei Arten vor:
static bool isXX(wchar_t zeichen); static bool isXX(String^ s, int zeichenposition);

Die erste Variante bekommt das zu prfende Zeichen direkt bergeben. Die zweite Fassung prft das Zeichen im String s an Position zeichenposition.
Methode
IsControl IsDigit IsLetter IsLetterOrDigit IsLower

Liefert true, wenn Steuerzeichen Dezimalzahl Buchstabe Dezimalzahl oder Buchstabe Kleinbuchstabe

Tabelle 10.6 Die wichtigsten Prfmethoden von Char

498

Collections

10.5

Methode
IsNumber IsPunctuation IsSeparator IsSymbol IsUpper IsWhiteSpace

Liefert true, wenn Zahl Satzzeichen Trennzeichen Symbolzeichen Grobuchstabe Whitespace (Leerzeichen, FF, NL, CR, HT oder VT)

Tabelle 10.6 Die wichtigsten Prfmethoden von Char (Forts.)

Genau wie String besitzt Char die Umwandlungsmethoden ToLower und ToUpper, bei denen nur das zu konvertierende Zeichen oder auch ein CultureInfo-Objekt bergeben werden kann:
static wchar_t ToLower(wchar_t zeichen); static wchar_t ToLower(wchar_t zeichen, CultureInfo^ kultur); static wchar_t ToUpper(wchar_t zeichen); static wchar_t ToUpper(wchar_t zeichen, CultureInfo^ kultur);

10.5

Collections

Unter Collections (Auflistungen) versteht man Klassen, deren Objekte Gruppen anderer Objekte verwalten. Eine Collection haben wir unter .NET bereits mit dem Array kennengelernt. Sie sind unter .NET das quivalent der STL unter ANSI C++, allerdings nicht so mchtig. Trotzdem ist ihr Verstndnis wesentlich, weil .NET selbst natrlich nur die .NET-Collections verwendet. Die verschiedenen Collections sowie die mit ihnen zusammenhngenden Klassen und Schnittstellen befinden sich im Namensbereich System:: Collections. Bevor wir tiefer in diese Thematik einsteigen, steht noch die Betrachtung einiger Schnittstellen an, die von Methoden einer Auflistung verwendet werden.

499

10

Ntzliche .NET-Klassen

10.6

IComparer

Die Schnittstelle IComparer fordert die Implementierung einer CompareMethode:


int Compare(Object^ obj1, Object^ obj2);

Die Rckgabewerte von Compare mssen den in Tabelle 9.2 in Abschnitt 9.7, Operatoren berladen, aufgefhrten Werten entsprechen. Objekte von IComparer abgeleiteter Klassen kommen immer dann zum Einsatz, wenn ein auf ein bestimmtes Problem zugeschnittener Vergleich bentigt wird. Betrachten wir als Beispiel die Klasse BecherVergleich, die eine Compare-Methode fr Becher-Objekte bereitstellt:
ref class BecherVergleich : IComparer { public: virtual int Compare(Object^ obj1, Object^ obj2) { float am1=(dynamic_cast<Becher^>(obj1))->AbsoluteMenge; float am2=(dynamic_cast<Becher^>(obj2))->AbsoluteMenge; if(am1<am2) return(-1); if(am1>am2) return(1); return(0); } };
Listing 10.1 Die Klasse BecherVergleich

Um die Becher-Eigenschaft AbsoluteMenge aufrufen zu knnen, muss auf die Object-Verweise zunchst ein Downcast (siehe Abschnitt 4.20, Downcasts) ausgefhrt werden. Dabei ist es essenziell, dass die an die Compare-Methode bergebenen Objekte tatschlich der Klasse Becher oder von ihr abgeleiteter Klassen angehren. In allen anderen Fllen wird eine Ausnahme geworfen.

10.7

IComparable

Die IComparable-Schnittstelle besteht nur aus einer Methode namens CompareTo, die das aufrufende Objekt mit dem bergebenen Objekt vergleicht und einen Rckgabewert gem Tabelle 9.2 in Abschnitt 9.7, Operatoren berladen, besitzt:

500

Collection-Schnittstellen

10.8

int CompareTo(Object^ objekt);

Die Schnittstelle sollte von allen Klassen implementiert werden, deren Objekte sich vergleichen lassen knnen oder mssen. Die Klasse Becher knnte diese Schnittstelle beispielsweise implementieren, um die absolute Fllmenge zweier Becher vergleichen zu knnen:
ref class Becher : System::IComparable { // ... public: virtual int CompareTo(Object^ b) { float am=(dynamic_cast<Becher^>(b))->AbsoluteMenge; if(AbsoluteMenge<am) return(-1); if(AbsoluteMenge>am) return(1); return(0); } // ... };
Listing 10.2 Die Klasse Becher implementiert IComparable

10.8

Collection-Schnittstellen

Collections unterscheiden sich in der internen Datenstruktur und den bereitgestellten Zugriffsmglichkeiten. Grundlage aller Collections bilden die in Abbildung 10.1 dargestellten Schnittstellen, die selbst wiederum eine Hierarchie bilden.
interface IEnumerable

interface ICollection

interface IList

interface IDictionary

Abbildung 10.1 Die Collection-Schnittstellen

Diese Schnittstellen sollen nun im Einzelnen betrachtet werden.

501

10

Ntzliche .NET-Klassen

10.9

IEnumerable

Die Schnittstelle IEnumerable fordert von einer Collection, aufzhlbar zu sein. Dazu bietet sie die Methode GetEnumerator, die einen die Schnittstelle IEnumerator implementierenden Enumerator liefert. Abbildung 10.2 zeigt die beiden Klassen mit den fr uns wichtigen Elementen. Eigenschaften zhlen technisch zu den Methoden, deshalb sind sie in der UML unter den Methoden aufgefhrt. Um sie im Diagramm dennoch von den Methoden unterscheiden zu knnen, habe ich mich entschlossen, vor den Namen der Eigenschaft das Stereotyp <<property>> zu setzen.
interface IEnumerable +GetEnumerator() : IEnumerator^ interface IEnumerator +<<property>> Current() : Object^ +MoveNext() : bool +Reset()

Abbildung 10.2 Die Schnittstellen IEnumerable und IEnumerator

Ein Enumerator bietet die Mglichkeit, eine Collection vom Anfang bis zum Ende sequenziell zu durchlaufen (in der STL von ANSI C++ entspricht das einem Forward-Iterator). Mit der Methode Reset wird die Position des Enumerators vor das erste Element gesetzt. Durch den ersten Aufruf von MoveNext springt der Enumerator auf das nchste in diesem Fall erste Element. Ob es ein solches Element gibt, teilt die Methode mit ihrem Booleschen Rckgabewert mit. Liefert MoveNext den Wert true, dann ist der aktuelle Wert ber Current verfgbar. Das folgende Beispiel durchluft ein herkmmliches Array mit einem Enumerator (dies setzt voraus, dass ein Array die Schnittstelle IEnumerable implementiert, was natrlich der Fall ist):
array<int>^ x={2,8,22,63,35}; IEnumerator^ e=x->GetEnumerator(); e->Reset(); while(e->MoveNext()) Console::WriteLine(e->Current);
Listing 10.3 Ein Array mit Enumerator durchlaufen

Im obigen Beispiel dient der Aufruf von Reset nur der Demonstration, da der Enumerator nach seiner Erzeugung immer vor dem ersten Element steht. Die Methode Reset muss nur aufgerufen werden, wenn derselbe Enumerator ein weiteres Mal ber die Elemente traversieren soll.

502

ICollection

10.10

10.10 ICollection
Wie in Abbildung 10.3 zu sehen, erweitert die Schnittstelle ICollection die Schnittstelle IEnumerable um die Eigenschaft Count, mit der die Anzahl der Elemente in der Collection bestimmt werden kann, und um die Methode CopyTo, die ein Kopieren der Collection in ein Array erlaubt.
interface IEnumerable

interface ICollection +<<property>> Count() : int +CopyTo(inout array : Array^, in position : int)

Abbildung 10.3 Die Schnittstelle ICollection

Listing 10.4 zeigt den Einsatz der von ICollection deklarierten Elemente. Die Inhalte zweier Arrays werden ber die CopyTo-Methode in ein Array kopiert, dessen Gre spter ausgegeben wird. Die Gre des Arrays ist ber die Eigenschaft Length verfgbar. Um auch Count nutzen zu knnen, muss ber einen ICollection-Verweis zugegriffen werden.
array<int>^ a1={1,2,3}; array<int>^ a2={4,6,8}; array<int>^ x=gcnew array<int>(6); a1->CopyTo(x,0); a2->CopyTo(x,3); ICollection^ c=x; Console::WriteLine(c->Count); for each(int i in c) Console::WriteLine(i);
Listing 10.4 ICollection im Einsatz

Zwei Collections, welche die Schnittstelle ICollection implementieren, sind Queue und Stack.

10.10.1 Queue
Der Begriff Queue bezeichnet eine Schlange im Sinne von Menschen- oder Warteschlange. Genau wie bei einer Warteschlange ist das zuerst in die

503

10

Ntzliche .NET-Klassen

Schlange getretene Element auch das erste, welches die Schlange wiederverlsst. Man spricht von einer FIFO-Struktur (first in first out, auf Deutsch etwa zuerst rein, zuerst raus). Sie entspricht in ihrer Struktur dem STL-Adapter queue aus Kapitel 6.9.5, Queues. Die Klasse besitzt u. a. einen parameterlosen Konstruktor und einen Konstruktor, der den Inhalt der Queue aus einer bergebenen ICollection konstruiert:
Queue(); Queue(ICollection^ collection);

Abgesehen von implementierten Schnittstellenmethoden besitzt die Klasse noch andere interessante Methoden, von denen die wichtigsten in Tabelle 10.7 aufgefhrt sind.
Methode
Clear Contains

Beschreibung Lscht den Queueinhalt. Liefert true, wenn das bergebene Objekt in der Queue enthalten ist. Entfernt ein Element. Fgt ein Element hinzu. Ermittelt das erste Element. Liefert eine Queue als Array zurck.

Dequeue Enqueue Peek ToArray

Tabelle 10.7 Die Methoden von Queue

Dequeue Element entfernen


Object^ Dequeue();

Entfernt das Objekt am Anfang der Queue, und liefert es zurck.


Enqueue Element hinzufgen
void Enqueue(Object^ objekt);

Hngt das bergebene Objekt an das Ende der Queue an.


Peek Element ansehen
Object^ Peek();

Liefert wie Dequeue das Objekt am Anfang der Queue, entfernt es aber nicht.

504

ICollection

10.10

ToArray als Array zurckliefern


array<Object^>^ ToArray();

Liefert ein Array mit den Elementen der Queue zurck.


Weitere Methoden

ber die von ICollection geforderte Funktionalitt hinaus bietet Queue noch die Methoden Clear und Contains, die bei IList erklrt werden.
Anwendungsbeispiel

Im Folgenden werden drei int-Werte in eine Queue hineingeschrieben und anschlieend Werte aus der Queue gelesen und entfernt, bis keine Elemente mehr enthalten sind:
Queue^ q=gcnew Queue; q->Enqueue(23); q->Enqueue(34); q->Enqueue(45); while(q->Count!=0) Console::WriteLine(q->Dequeue());
Listing 10.5 Ein Beispiel fr Queue

10.10.2 Stack
Bei einem Stack (zu Deutsch Stapel) handelt es sich um eine LIFO-Struktur (last in, first out, auf Deutsch etwa zuletzt rein, zuerst raus); das zuletzt auf den Stapel gelegte Element ist das erste, das wieder entfernt wird. Die Struktur ist identisch mit dem STL-Adapter stack aus Abschnitt 6.9.1, Stacks. Typisches Anwendungsbeispiel fr einen Stack ist die Verwaltung der Rcksprungadressen bei Methodenaufrufen. Der Punkt, von dem zuletzt zu einer Methode gesprungen wurde, zu dem muss nach Beendigung der Methode als Erstes wieder zurckgesprungen werden. Die Klasse besitzt einige Konstruktoren, von denen hier zwei vorgestellt werden:
Stack(); Stack(ICollection^ collection);

Wie bei einer Queue knnen ein leerer oder ein aus einer bergebenen ICollection konstruierter Stack erzeugt werden. Die Klasse ergnzt die Schnittstellenmethoden noch um die in Tabelle 10.8 aufgefhrten Methoden.

505

10

Ntzliche .NET-Klassen

Methode
Clear Contains

Beschreibung Lscht den Stackinhalt. Liefert true, wenn das bergebene Objekt im Stack enthalten ist. Ermittelt das oberste Element. Entfernt das oberste Element. Legt ein Element oben auf. Liefert Stack als Array zurck.
Die Methoden von Stack

Peek Pop Push ToArray

Tabelle 10.8

Peek Element ansehen


Object^ Peek();

Liefert wie Pop das oberste Objekt des Stacks, entfernt es aber nicht.
Pop Element entfernen
Object^ Pop();

Entfernt das oberste Element des Stacks, und liefert es zurck.


Push Element hinzufgen
void Push(Object^ objekt);

Legt das bergebene Objekt oben auf dem Stack ab.


ToArray als Array zurckliefern
array<Object^>^ ToArray();

Liefert ein Array mit den Elementen des Stacks zurck.


Weitere Methoden

Wie Queue besitzt Stack noch die bei IList erklrten Methoden Clear und Contains.
Anwendungsbeispiel

Um den Unterschied zu verdeutlichen, wird im Folgenden das Beispiel der Queue verwendet und auf Stacks umgendert:

506

IList

10.11

Stack^ s=gcnew Stack; s->Push(23); s->Push(34); s->Push(45); while(s->Count!=0) Console::WriteLine(s->Pop());


Listing 10.6 Ein Beispiel fr Stack

Die Zahlen werden in umgekehrter Reihenfolge ihres Einfgens ausgegeben.

10.11 IList
Die Schnittstelle IList stellt eine Erweiterung von ICollection dar und bildet die Grundlage aller Collections mit wahlfreiem Zugriff. Abbildung 10.4 zeigt die Schnittstelle im berblick und Tabelle 10.9 stellt die Elemente detaillierter dar.
Wahlfreier Zugriff Unter wahlfreiem Zugriff wird die Mglichkeit verstanden, jedes Element einer Collection direkt ansprechen zu knnen. Mit den Worten der STL wre jede von IList abgeleitete Collection in der Lage, Random-Access-Iteratoren bereitzustellen. Im Gegensatz dazu muss bei einem sequenziellen Zugriff immer am Anfang der Collection begonnen und die Collection bis zum gewnschten Element durchlaufen werden.
interface IEnumerable interface ICollection

interface IList +<<property>> IsFixedSize() : bool +<<property>> IsReadOnly() : bool +<<property>> Item(in position : int) : Object^ +Add(in objekt : Object^) : int +Clear() +Contains(in objekt : Object^) : bool +IndexOf(in objekt : Object^) : int +Insert(in position : int, in objekt : Object^) +Remove(in objekt : Object^) +RemoveAt(in position : int)

Abbildung 10.4 Die Schnittstelle IList

507

10

Ntzliche .NET-Klassen

Element
IsFixedSize

Beschreibung Liefert true, wenn die Gre der Liste nicht verndert werden kann (was bei Arrays der Fall ist). Liefert true, wenn die Listeninhalte nicht verndert werden knnen. Der Klassen-Indexer (siehe Abschnitt 9.4.2) der Liste. ber ihn kann auf die Elemente wahlfrei zugegriffen werden. Fgt ein Element hinzu, und liefert den Index des neuen Elements zurck. Lscht den Listeninhalt. Liefert true, wenn das bergebene Objekt in der Liste enthalten ist. Liefert den Index des bergebenen Objekts in der Liste oder 1, falls das Objekt nicht in der Liste enthalten ist. Fgt ein Objekt an eine spezielle Position in der Liste ein. Entfernt das bergebene Objekt aus der Liste. Entfernt das am bergebenen Index befindliche Objekt aus der Liste.
Die von IList geforderten Methoden und Eigenschaften

IsReadOnly Item

Add

Clear Contains IndexOf

Insert Remove RemoveAt

Tabelle 10.9

Nachstehend werden die Klassen vorgestellt, die IList implementieren.

10.11.1 ArrayList
Die Klasse ArrayList basiert intern auf einem Array. Die Klasse sorgt jedoch dafr, dass sich das Array bei Bedarf dynamisch vergrert. Die wichtigsten Konstruktoren dieser Klasse erzeugen eine leere oder aus einer Collection erzeugte ArrayList:
ArrayList(); ArrayList(ICollection^ collection);

Als die Schnittstelle IList implementierende Klasse besitzt ArrayList alle von IList, ICollection und IEnumerator geforderten Methoden und darber hinaus auch die in Tabelle 10.10 aufgelisteten.
Methode
AddRange BinarySearch GetRange

Beschreibung Bereich hinzufgen binre Suche Liefert einen Bereich von Elementen.

Tabelle 10.10 Die Methoden von ArrayList

508

IList

10.11

Methode
InsertRange LastIndexOf RemoveRange Reverse Sort

Beschreibung Fgt mehrere Elemente ein. Sucht ein Element von hinten beginnend. Entfernt einen Bereich von Elementen. Dreht die Elementreihenfolge um. Sortiert Elemente.

Tabelle 10.10 Die Methoden von ArrayList (Forts.)

AddRange Bereich hinzufgen


void AddRange(ICollection^ collection);

Die Elemente der bergebenen Collection werden an die ArrayList angehngt.


BinarySearch binre Suche

Die BinarySearch-Methoden implementieren den Suchalgorithmus der binren Suche. Voraussetzung fr diesen Algorithmus ist eine sortierte ArrayList.
int BinarySearch(Object^ objekt);

Sucht in der ArrayList nach dem Objekt objekt. Der Typ von objekt muss die Schnittstelle IComparable (Abschnitt 10.7, IComparable) implementieren.
int BinarySearch(Object^ objekt, IComparer^ vergleicher);

Sucht in derArrayList nach objekt, und verwendet fr die Vergleiche das Objekt vergleicher (die Schnittstelle IComparer ist in Abschnitt 10.6, IComparer erklrt). Die Compare-Methode von vergleicher sollte daher in der Lage sein, den entsprechenden Typ zu vergleichen.
int BinarySearch(int position, int anzahl, Object^ objekt, IComparer^ vergleicher);

Sucht in den anzahl Objekten ab der Indexposition position das Element objekt, und verwendet fr die Vergleiche vergleicher. Wird fr vergleicher ein nullptr bergeben, dann kommt der Standardvergleicher zum Einsatz.

509

10

Ntzliche .NET-Klassen

GetRange Bereich liefern


ArrayList^ GetRange(int position, int anzahl);

Liefert die anzahl Elemente ab Position position als ArrayList zurck.


InsertRange Bereich einfgen
void InsertRange(int position, ICollection^ collection);

Fgt in die ArrayList ab position die Elemente von collection ein.


LastIndexOf Element von hinten suchen
int LastIndexOf(Object^ objekt); int LastIndexOf(Object^ objekt, int position); int LastIndexOf(Object^ objekt, int position, int anzahl);

Liefert durch sequenzielle Suche die Position des letzten Vorkommens von objekt. Wird objekt nicht gefunden, dann ist das Ergebnis 1. Optional knnen Sie noch die Startposition der Suche (position) und die Anzahl der Zeichen, in denen gesucht werden soll (anzahl), angeben.
RemoveRange Bereich entfernen
void RemoveRange(int position, int anzahl);

Entfernt aus der ArrayList ab Position position anzahl Elemente.


Reverse Reihenfolge umdrehen
void Reverse(); void Reverse(int position, int anzahl);

Dreht die Reihenfolge der ArrayList-Elemente bzw. der anzahl Elemente ab position um.
Sort Sortieren

Sortiert die ArrayList mithilfe des Quicksort-Algorithmus.


void Sort();

Sortiert die gesamte ArrayList. Der Elementtyp der ArrayList muss die IComparable-Schnittstelle implementieren.

510

IDictionary

10.12

void Sort(IComparer^ vergleicher);

Sortiert die gesamte ArrayList, wobei die Vergleiche ber das Objekt vergleicher vorgenommen werden.
void Sort(int position, int anzahl, IComparer^ vergleicher);

Sortiert anzahl Elemente ab Position position, und verwendet fr die Vergleiche vergleicher. Bei bergabe von nullptr fr vergleicher wird der Standardvergleich verwendet.

10.12 IDictionary
Die Schnittstelle IDictionary ist von ICollection abgeleitet und ist die Basis aller Schlssel-Wert-Collections. Ein Eintrag in einem Dictionary besteht immer aus zwei Komponenten, dem Schlssel, der fr die Sortierung und das Wiederauffinden des Eintrags erforderlich ist, und dem damit verknpften Wert. Der Schlssel muss dabei innerhalb des Dictionarys eindeutig sein. Der Name wurde gewhlt in Anlehnung an ein Wrterbuch (englisch Dictionary), welches als Schlssel das Wort und als Wert die Erklrung des Wortes beinhaltet. Abbildung 10.5 zeigt die Elemente von IDictionary sowie den dazugehrigen IDictionaryEnumerator. Tabelle 10.11 erklrt die Funktion der Methoden und Eigenschaften.
interface IEnumerable interface ICollection interface IDictionary +<<property>> IsFixedSize() : bool +<<property>> IsReadOnly() : bool +<<property>> Item(in schluessel : int) : Object^ +<<property>> Keys() : ICollection^ +<<property>> Values() : ICollection^ +Add(in schluessel : Object^, in wert : Object^) : int +Clear() +Contains(in schluessel : Object^) : bool +GetEnumerator() : IDictionaryEnumerator^ +Remove(in schluessel : Object^) interface IEnumerator +<<property>> Current() : Object^ +MoveNext() : bool +Reset()

interface IDictionaryEnumerator +<<property>> Entry() : DictionaryEntry +<<property>> Key() : Object^ +<<property>> Value() : Object^

Abbildung 10.5 Die Schnittstelle IDictionary mit Enumerator

511

10

Ntzliche .NET-Klassen

Element
IsFixedSize

Beschreibung Liefert true, wenn die Gre des Dictionarys nicht gendert werden kann. Liefert true, wenn die Elemente des Dictionarys nicht gendert werden knnen. Der Klassen-Indexer (siehe Abschnitt xxx) des Dictionarys. ber den Schlssel kann der dazugehrige Wert ermittelt werden. Liefert alle Schlssel des Dictionarys als ICollection. Liefert alle Werte des Dictionarys als ICollection. Fgt ein Schlssel-Wert-Paar zum Dictionary hinzu. Lscht alle Elemente des Dictionarys. Liefert true, wenn ein Paar mit dem angegebenen Schlssel existiert. Liefert einen IDictionaryEnumerator fr das Dictionary. Entfernt das Schlssel-Wert-Paar mit dem angegebenen Schlssel.

IsReadOnly

Item

Keys Values Add Clear Contains

GetEnumerator Remove

Tabelle 10.11 Die von IDictionary geforderten Methoden und Eigenschaften

Der IDictionaryEnumerator liefert ber die Eigenschaft Entry ein Objekt des Werttyps DictionaryEntry, welcher die Eigenschaften Key und Value besitzt. Die Schnittstelle IDictionary wird von den Klassen SortedList und Hashtable implementiert.

10.12.1 Hashtable
Die Klasse Hashtable speichert ihre Elemente mithilfe einer Hash-Tabelle. Um in einer Hashtable gespeichert werden zu knnen, muss der Schlsseltyp die von Object geerbte Methode GetHashCode berschreiben:
int GetHashCode();

Die Methode muss einen fr jedes Objekt eindeutigen Wert liefern. Zwei gleiche Objekte mssen auch denselben Hashcode besitzen. Dazu muss meist auch die Equals-Methode berschrieben werden, die true liefert, wenn das aufrufende und das bergebene Objekt gleich sind:
bool Equals (Object^ objekt); Hashtable besitzt ber ein Dutzend Konstruktoren, hier sollen aber nur die beiden einfachsten vorgestellt werden: Hashtable(); Hashtable(IDictionary^ dictionary);

512

IDictionary

10.12

Der erste Konstruktor erzeugt eine leere Hashtable, der zweite konstruiert die Hashtable aus den Elementen des bergebenen IDictionary. Die Hashtable implementiert die Methoden von IEnumerable, ICollection und IDictionary und besitzt zustzlich:
ContainsKey Ist Schlssel enthalten?
bool ContainsKey(Object^schluessel);

Liefert true, wenn die Hashtable einen Eintrag mit dem angegebenen Schlssel enthlt.
ContainsValue Ist Wert enthalten?
bool ContainsValue(Object^ wert);

Liefert true, wenn die Hashtable einen Eintrag mit dem angegebenen Wert enthlt.

10.12.2 SortedList
Die SortedList ist in gewisser Weise eine Hybrid-Collection, denn sie erlaubt einerseits den wahlfreien Zugriff ber einen Index, wie wir es von IList gewohnt sind. Andererseits implementiert sie aber die IDictionary-Schnittstelle, die es ermglicht, Elemente ber einen Schlssel anzusprechen. Die Schlssel mssen dabei in der SortedList eindeutig sein. Die hier betrachteten Konstruktoren knnen eine leere SortedList oder eine mit den Elementen des bergebenen IDictionary gefllte konstruieren.
SortedList(); SortedList(IDictionary^ dictionary);

Die Klasse implementiert die Methoden von IEnumerable, ICollection und IDictionary und ergnzt diese um die in Tabelle 10.12 gezeigten.
Methode
ContainsKey ContainsValue GetByIndex GetKey

Beschreibung Ist ein Schlssel enthalten? Ist ein Wert enthalten? Liefert ein Objekt ber den Index. Liefert einen Schlssel ber den Index.

Tabelle 10.12 Die Methoden von SortedList

513

10

Ntzliche .NET-Klassen

Methode
GetKeyList GetValueList IndexOfKey IndexOfValue RemoveAt SetByIndex

Beschreibung Liefert eine Liste aller Schlssel. Liefert eine Liste aller Werte. Liefert einen Index eines Schlssels. Liefert einen Index eines Werts. Entfernt ein Element an der Position. Setzt einen Wert an den Index.

Tabelle 10.12 Die Methoden von SortedList (Forts.)

ContainsKey Ist Schlssel enthalten?


bool ContainsKey(Object^schluessel);

Liefert true, wenn die SortedList einen Eintrag mit dem angegebenen Schlssel enthlt.
ContainsValue Ist Wert enthalten?
bool ContainsValue(Object^ wert);

Liefert true, wenn die SortedList einen Eintrag mit dem angegebenen Wert enthlt.
GetByIndex hole Objekt ber Index
Object^ GetByIndex(int position);

Liefert den Wert an der angegebenen Position.


GetKey hole Schlssel ber Index
Object^ GetKey(int position);

Liefert den Schlssel an der angegebenen Position.


GetKeyList hole Schlsselliste
IList^ GetKeyList();

Liefert eine IList mit allen in der SortedList gespeicherten Schlsseln.


GetValueList hole Wertliste
IList^ GetValueList();

Liefert eine IList mit allen in der SortedList gespeicherten Werten.

514

Generische Collections

10.13

IndexOfKey Index eines Schlssels


int IndexOfKey(Object^ schluessel);

Liefert die Position des angegebenen Schlssels oder 1, wenn der Schlssel nicht in der SortedList enthalten ist.
IndexOfValue Index eines Werts
int IndexOfValue(Object^ wert);

Liefert die Position des ersten Vorkommens von wert oder 1, wenn der Wert nicht in der SortedList enthalten ist.
RemoveAt an Position entfernen
void RemoveAt(int position);

Lscht das Schlssel-Wert-Paar an der angegebenen Position.


SetByIndex Wert ber Index setzen
void SetByIndex(int position, Object^ wert);

Setzt den Wert an Position position auf wert.

10.13 Generische Collections


Die bisher in diesem Kapitel vorgestellten Collections sind bereits ganz nett wenn auch fr einen STL-gewohnten C++-Programmierer etwas sprlich , aber sie haben einen groen Nachteil: den Verlust von Typinformation. Ein Beispiel:
ArrayList^ al=gcnew ArrayList; al->Add(gcnew Becher("Milch", 200,90)); al->Add(gcnew Becher("Kaffee", 300,80)); al->Add(gcnew Becher("Tee", 300,90)); Becher^ b=dynamic_cast<Becher^>(al[0]); if(b!=nullptr) Console::WriteLine(b); else Console::WriteLine("Kein Becher");
Listing 10.7 Ein Beispiel fr verloren gegangene Typinformation

515

10

Ntzliche .NET-Klassen

In die ArrayList werden drei Becher-Objekte gespeichert. Wenn nun einer dieser Becher angesprochen und einem Becher-Verweis zugewiesen werden soll, dann ist ein dynamic_cast unabdingbar, weil die ArrayList alle ihre Elemente als vom Typ Object verwaltet und der tatschliche Typ dadurch verloren gegangen ist. Schlimmer noch, es ist ohne Weiteres mglich, ein Objekt anderen Typs zwischen die Becher zu mogeln:
al->Add(1);

Bevor in Listing 10.7 ber den Verweis b auf den Becher zugegriffen werden soll, ist daher zu berprfen, ob es sich wirklich um ein Becher-Objekt handelt. Wir wissen, dass der dynamic_cast einen nullptr liefert, wenn die Umwandlung nicht durchgefhrt werden konnte. Ist b ungleich nullptr, dann handelt es sich tatschlich um ein Becher-Objekt. Es wre doch viel schner, wenn sich die Collection merken wrde, dass sie Becher-Objekte speichert. Und genau dazu sind die in .NET 2.0 hinzugekommenen generischen Collections da. Sie stehen im Namensbereich System.Collections.Generic. Alle bisher besprochenen Collections gibt es auch in einer gleichnamigen generischen Fassung. Lediglich die ArrayList heit nun schlicht List. Den generischen Collections mssen Sie, hnlich den C++Templates (siehe Kapitel 14, Ntzliche Windows-Forms-Klassen), in spitzen Klammern angeben, welcher Typ zu verwalten ist. Die Definition einer Liste fr Becher sieht demnach so aus:
List<Becher^>^ l;

Schreiben wir das obige Beispiel so um, dass es generische Collections verwendet. Um den Unterschied klarer hervorzuheben, wurde der GenericNamensbereich explizit angegeben:
Generic::List<Becher^>^ al=gcnew Generic::List<Becher^>; al->Add(gcnew Becher("Milch", 200,90)); al->Add(gcnew Becher("Kaffee", 300,80)); al->Add(gcnew Becher("Tee", 300,90)); Becher^ b=al[0]; Console::WriteLine(b);
Listing 10.8 Der Einsatz generischer Collections

516

Generische Collections

10.13

Nun bleibt die Typinformation erhalten, die Zuweisung eines Listenelements an einen Becher-Verweis erfordert keinen Downcast mehr. Wenn Sie sich nun fragen, warum berhaupt noch die alten Collections besprochen wurden: Die meisten der Klassen, denen wir spter bei der Oberflchenprogrammierung begegnen werden, setzen sie ein.

10.13.1 Die generischen Schnittstellen


Die Methoden der Schnittstellen IEnumerable, ICollection, IList und IDictionary sind bei den generischen Versionen unterschiedlich zugeordnet worden. Abbildung 10.6 zeigt die generischen Schnittstellen im Klassendiagramm.
interface IEnumerable +GetEnumerator() : IEnumerator^

interface IEnumerable<T> +GetEnumerator() : IEnumerator<T>^

interface ICollection<T> +<<property>> Count() : int +<<property>> IsReadOnly() : bool +Add(in objekt : T) +Clear() +Contains(in objekt : T) : bool +CopyTo(inout array : Array^, in position : int) +Remove(in objekt : T) : bool

interface IList<T> +<<property>> Item(in position : int) : T +IndexOf(in objekt : T) : int +Insert(in position : int, in objekt : T) +RemoveAt(in position : int)

interface IDictionary<TKey, TValue> +<<property>> Item(in schluessel : TKey) : TValue +<<property>> Keys() : ICollection<TKey> +<<property>> Values() : ICollection<TValue> +Add(in schluessel : TKey, in wert : TValue) +ContainsKey(in schluessel : TKey) : bool +Remove(in schluessel : TKey) : bool +TryGetValue(in schluessel : TKey, in wert : TValue%) : bool

Abbildung 10.6 Die Schnittstellen der generischen Collections

517

10

Ntzliche .NET-Klassen

Bleibt noch zu klren, wie IDictionary, die zwei variable Typen (TKey und TValue) besitzt, von einer Schnittstelle mit nur einem variablen Datentyp (ICollection) abgeleitet werden kann. Die Lsung besteht in einem Werttyp KeyValuePair, der die beiden variablen Datentypen von IDicionary als Attribute besitzt. Dieser eine Typ (KeyValuePair<TKey, TValue>) ist dann der Typ von ICollection. In C++ gesprochen, sieht dieses Vererbungsverhltnis so aus:
generic<typename TKey, typename TValue> public interface class IDictionary : ICollection<KeyValuePair<TKey, TValue>>

Hier ist auch schn zu sehen, wie eigene generische Klassen programmiert werden knnen; mit dem Schlsselwort generic, und dahinter in spitzen Klammern und mit Komma getrennt die variablen Typen, jeweils eingeleitet mit dem Schlsselwort typename. Zustzlich zu den bekannten Collections gibt es bei den generischen Collections noch zwei Neuzugnge, LinkedList und SortedDictionary: Diese sollen hier aber nicht weiter besprochen werden.

10.14 Anwendungsbeispiele
Die Arbeit mit den Collections wird Sie unter .NET verfolgen, deshalb zeigt dieser Abschnitt die Anwendung einiger Klassen, um das Verstndnis zu vertiefen.

10.14.1 Personen verwalten mit List


Es soll ein Programm geschrieben werden, dass Personen mit Vornamen, Nachnamen und Sternzeichen verwalten kann. Die Personen sollen primr nach Nachnamen und sekundr nach Vornamen sortiert sein. Die Klasse heit PersonHoroskop:
01: 02: 03: 04: 05: 06: ref class PersonHoroskop : System::IComparable<PersonHoroskop^> { System::String^ vorname; System::String^ nachname; System::String^ sternzeichen; public: PersonHoroskop(System::String^ vn, System::String^ nn,

518

Anwendungsbeispiele

10.14

07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:

System::String^ sz) : vorname(vn), nachname(nn), sternzeichen(sz) { } PersonHoroskop() { Eingabe(); } virtual System::String^ ToString() override { return(nachname+", "+vorname+": "+sternzeichen); } void Eingabe() { System::Console::Write("Nachname:"); nachname=System::Console::ReadLine(); System::Console::Write("Vorname:"); vorname=System::Console::ReadLine(); System::Console::Write("Sternzeichen:"); sternzeichen=System::Console::ReadLine(); } virtual int CompareTo(PersonHoroskop^ p) { int x=nachname->CompareTo(p->nachname); if(x!=0) return(x); return(vorname->CompareTo(p->vorname)); } };
Die Klasse PersonHoroskop

Listing 10.9

01: Objekte der Klasse sollen von einer generischen Collection gespeichert werden. Daher leitet PersonHoroskop von der generischen IComparableVersion ab. Als Typ wird PersonHoroskop^ angegeben. 0204: die Attribute der Klasse 0608: der Konstruktor zur Initialisierung der Attribute 0911: Der parameterlose Konstruktor, der ber den Aufruf von Eingabe den Anwender zur manuellen Initialisierung der Attribute zwingt. 1214: die berschriebene ToString-Methode, um Objekte der Klasse mit Write und WriteLine ausgeben zu knnen 1522: die Eingabe-Methode

519

10

Ntzliche .NET-Klassen

2327: Die CompareTo-Methode, die von der Schnittstelle IComparable gefordert wird. Die Methode vergleicht zuerst die Nachnamen der beiden Personen. Sollten diese gleich sein, werden noch die Vornamen zum Vergleich herangezogen. Die Verwaltung beschrnkt sich auf das Hinzufgen und Auflisten der Personen. Zu bungszwecken knnen Sie noch weitere Funktionalitt hinzufgen. Die notwendigen Namensbereiche sind mittels using namespace global verfgbar gemacht:
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: List<PersonHoroskop^>^ li=gcnew List<PersonHoroskop^>; wchar_t input; PersonHoroskop^ p; do { Console::WriteLine("h Neue Person hinzufgen"); Console::WriteLine("l Personen auflisten"); Console::WriteLine("0 Ende"); Console::Write("\nEingabe:"); input=Convert::ToChar(Console::ReadLine()); switch(input) { case 'h': p=gcnew PersonHoroskop; li->Add(p); li->Sort(); break; case 'l': for(int i=0; i<li->Count; ++i) Console::WriteLine(i+": "+li[i]); Console::WriteLine(""); break; } }while(input!='0');
Die Verwaltung der Personen

Listing 10.10

01: Anlegen der zu verwendenden Auflistung 02: Char-Variable, die spter die Menauswahl des Anwenders speichert 0509: Ausgabe des Auswahlmens und Abfrage der Anwenderwahl 1115: Person hinzufgen. Ein neues Objekt vom Typ PersonHoroskop wird mit dem parameterlosen Konstruktor erzeugt, der ber die Methode

520

Anwendungsbeispiele

10.14

Eingabe die Daten der Person vom Anwender erfragt. Die neue Person wird mit Add an die Auflistung angehngt und die Auflistung anschlieend mit Sort sortiert. Die Sort-Methode greift dazu auf die Schnittstelle IComparable zu, die von PersonHoroskop implementiert wurde. 1620: Personen auflisten. Theoretisch htten die Elemente der Auflistung auch mit einer for each-Schleife aufgelistet werden knnen (zur Erinnerung: die for each-Schleife kann den Inhalt jeden Typs durchlaufen, der die IEnumerable-Schnittstelle implementiert). Da aber der Index der Person mit ausgegeben werden soll, ist eine normale for-Schleife angebrachter.

10.14.2 Eine Collection mit int-Werten absteigend sortieren


Die Art und Weise, wie Elemente einer Auflistung sortiert werden, lsst sich an zwei Stellen manipulieren: bei eigenen Klassen ber die Implementierung der IComparable-Schnittstelle bei fremden Klassen ber die Bereitstellung einer zustzlichen, die IComparer-Schnittstelle implementierenden Klasse Die erste Variante kam bereits im vorigen Abschnitt bei der Personenverwaltung zum Einsatz, daher soll im Folgenden die Sortierung von int-Elementen verndert werden. Die Int32-Klasse lsst sich von uns nicht ndern, daher ist ein IComparer unsere einzige Mglichkeit, Einfluss auf die Sortierung zu nehmen:
ref class IntAbwrtsVergleicher : IComparer<int> { public: virtual int Compare(int i1,int i2) { return(i2.CompareTo(i1)); } };
Listing 10.11 Eine eigene Implementierung von IComparer

Die implementierte Compare-Methode ruft die CompareTo-Methode von Int32 mit vertauschten Operanden auf, so dass aus der aufsteigenden eine absteigende Sortierung wird:
List<int>^ li=gcnew List<int>; Random^ rnd=gcnew Random; for(int i=0; i<10; ++i) {

521

10

Ntzliche .NET-Klassen

int zahl=rnd->Next(); Console::WriteLine(zahl); li->Add(zahl); } Console::WriteLine("---"); li->Sort(gcnew IntAbwrtsVergleicher); for each(int z in li) Console::WriteLine(z);
Listing 10.12 Eine int-Liste, absteigend sortiert

Das Beispiel nutzt die im folgenden Abschnitt erklrte Klasse Random zur Erzeugung von Zufallszahlen. Die Zufallszahlen werden an die Liste angehngt und danach mit Sort sortiert. Damit Sort unseren eigenen Vergleicher verwendet, wird ihr ein Objekt des Typs IntAbwrtsVergleicher bergeben. Und schon ist die Liste absteigend sortiert. Der Komplexitt eines eigenen Vergleichers oder der gewnschten Vergleichsregeln sind dabei keine Grenzen gesetzt.

10.15 Random Zufallszahlen


Kommen wir nun von den Collections weg und werfen einen Blick auf andere Kleinode der .NET-Bibliothek, die fter mal ntzlich werden knnen. Bei der Programmierung kommt es ab und an vor, dass Zufallszahlen bentigt werden. Wirklich zufllig sind die Zahlen dabei nicht, weil ein herkmmlicher Computer die Zahlen nur mithilfe eines Algorithmus erzeugen kann. Von einem solchen Algorithmus wird allerdings erwartet, dass die von ihm produzierten Zahlen wie bei echten Zufallszahlen gleichverteilt sind. Die Klasse, die diesen Zufallsgenerator implementiert, heit Random und befindet sich im Namensbereich System. Ein Algorithmus ist nichts anderes als eine Rechenvorschrift. Wenn der Algorithmus daher mit den gleichen Werten beginnt, dann erzeugt er auch die gleiche Folge von Zufallszahlen. In den meisten Fllen ist man daher bestrebt, den Algorithmus immer mit einem anderen Startwert beginnen zu lassen. Dazu bietet Random einen parameterlosen Konstruktor, der den Startwert aus der aktuellen Uhrzeit berechnet. ber den einparametrigen Konstruktor kann der Startwert manuell festgelegt werden.
Random(); Random(int startwert);

522

Math mathematische Funktionen

10.16

Nachstehend sind die zur Erzeugung von Zufallszahlen implementierten Methoden aufgefhrt.
Next nchste Zufallszahl

Liefert eine int-Zufallszahl grer gleich 0 zurck. Die verschiedenen berladungen erlauben eine Einschrnkung des Wertebereichs.
int Next();

Liefert einen zuflligen int-Wert zwischen einschlielich 0 und ausschlielich Int32::MaxValue (entspricht dem Wert 2.147.483.647).
int Next(int maxwert);

Liefert einen zuflligen int-Wert zwischen einschlielich 0 und ausschlielich maxwert. Dabei muss maxwert grer oder gleich 0 sein.
int Next(int minwert, int maxwert);

Liefert einen zuflligen int-Wert zwischen einschlielich minwert und ausschlielich maxwert. Dabei mssen folgende Bedingungen gelten:
minwert ist grer oder gleich Int32::MinValue (entspricht dem Wert

-2.147.483.648).
maxwert ist kleiner oder gleich Int32::MaxValue; maxwert ist grer oder gleich minwert.

NextBytes zufllige Byte-Werte


void NextBytes(array<unsigned char>^ puffer);

Fllt das bergebene Array mit zuflligen Byte-Werten (Bereich 0255).


NextDouble nchster zuflliger double
double NextDouble();

Liefert einen zuflligen double-Wert zwischen einschlielich 0 und ausschlielich 1.

10.16 Math mathematische Funktionen


Mathematische Funktionen stehen in Form statischer Methoden der Klasse Math bereit, die sich im Namensbereich System befindet.

523

10

Ntzliche .NET-Klassen

10.16.1 Methoden
Tabelle 10.13 zeigt eine bersicht der Methoden, die anschlieend detaillierter betrachtet werden.
Methode
Abs Acos Asin Atan BigMul Ceiling Cos Cosh Exp Floor Log Log10 Max Min Pow Round Sign Sin Sinh Sqrt Tan Tanh

Beschreibung Absolutwert Arcus Cosinus Arcus Sinus Arcus Tangens Multiplizieren groer Zahlen Rundet auf die nchstgrere ganze Zahl auf. Kosinus Kosinus hyperbolicus Potenz von e Rundet auf die nchstkleinere ganze Zahl ab. natrlicher Logarithmus Logarithmus zur Basis 10 Maximum zweiter Werte Minimum zweier Werte Potenz Runden Vorzeichen bestimmen Sinus Sinus hyperbolicus Quadratwurzel Tangens Tangens hyperbolicus

Tabelle 10.13 Die Methoden von Math

Die trigonometrischen Funktionen geben Double::NaN zurck, wenn der Parameter nicht im gltigen Bereich liegt.
Abs Absolutwert

Diese Methode ist fr alle vorzeichenbehafteten CLR-Typen berladen und liefert den Betrag des bergebenen Werts zurck.

524

Math mathematische Funktionen

10.16

Acos Arcus Cosinus


static double Acos(double kosinus);

Liefert den Winkel im Bogenma fr einen bergebenen Kosinus.


Asin Arcus Sinus
static double Asin(double sinus);

Liefert den Winkel im Bogenma fr einen bergebenen Sinus.


Atan Arcus Tangens
static double Atan(double sinus);

Liefert den Winkel im Bogenma fr einen bergebenen Tangens.


BigMul Multiplizieren groer Zahlen
static long long BigMul(int a, int b);

Multipliziert zwei int-Werte, und liefert das Ergebnis als long long zurck.
Ceiling Aufrunden
static Decimal Ceiling(Decimal wert); static double Ceiling(double wert);

Der bergebene Wert wird auf die nchste Ganzzahl aufgerundet zurckgegeben.
Cos Kosinus
static double Cos(double winkel);

Liefert den Kosinus fr einen im Bogenma bergebenen Winkel.


Cosh Kosinus hyperbolicus
static double Cosh(double winkel);

Liefert den hyperbolischen Kosinus fr einen im Bogenma bergebenen Winkel.


Exp
static double Exp(double d);

Liefert ed zurck.

525

10

Ntzliche .NET-Klassen

Floor Abrunden
static Decimal Floor(Decimal wert); static double Floor(double wert);

Der bergebene Wert wird auf die nchste Ganzzahl abgerundet zurckgegeben.
Log natrlicher Logarithmus
static double Log(double wert);

Liefert den Logarithmus von wert zur Basis e.


static double Log(double wert, double basis);

Liefert den Logarithmus von wert zur Basis basis.


Log10
static double Log10(double wert);

Liefert den Logarithmus von wert zur Basis 10.


Max Maximum

berladen fr alle numerischen Typen der CLR. Erwartet zwei Werte und liefert den greren der beiden Werte zurck.
Min Minimum

berladen fr alle numerischen Typen der CLR. Erwartet zwei Werte und liefert den kleineren der beiden Werte zurck.
Pow Potenz
static double Pow(double x, double y);

Liefert die Potenz xy.


Round Runden

Rundet den bergebenen Wert. Liegt er genau zwischen zwei Zahlen, dann wird zur geraden Zahl hin gerundet.
static Decimal Round(Decimal wert); static double Round(double wert);

Rundet wert auf eine Ganzzahl.

526

Math mathematische Funktionen

10.16

static Decimal Round(Decimal wert, int dezimalstellen); static double Round(double wert, int dezimalstellen);

Liefert den auf dezimalstellen Stellen gerundeten Wert wert zurck. Es gibt noch weitere berladungen von Round, bei denen u. a. die Art des Rundens verndert werden kann, dazu sei aber auf die .NET-Dokumentation verwiesen.
Sign Vorzeichen

Fr alle vorzeichenbehaftete CLR-Typen berladen. Die mglichen Rckgabewerte sind in Tabelle 10.14 aufgefhrt.
bergebener Wert <0 =0 >0 Rckgabewert 1 0 +1

Tabelle 10.14 Die mglichen Rckgabewerte von Sign

Sin Sinus
static double Sin(double winkel);

Liefert den Sinus fr einen im Bogenma bergebenen Winkel.


Sinh Sinus hyperbolicus
static double Sinh(double winkel);

Liefert den hyperbolischen Sinus fr einen im Bogenma bergebenen Winkel.


Sqrt Quadratwurzel
static double Sqrt(double wert);

Liefert die Quadratwurzel von wert.


Tan Tangens
static double Tan(double winkel);

Liefert den Tangens fr einen im Bogenma bergebenen Winkel.

527

10

Ntzliche .NET-Klassen

Tanh Tangens hyperbolicus


static double Tanh(double winkel);

Liefert den hyperbolischen Tangens fr einen im Bogenma bergebenen Winkel.

10.16.2 Konstanten
Die Klasse Math definiert noch zwei Konstanten, die in Tabelle 10.15 zu sehen sind.
Konstante
E PI

Wert die Eulersche Zahl die Kreiszahl

Tabelle 10.15 Die Konstanten von Math

10.17 Console die Konsole


Mithilfe der Klasse Console haben wir bereits Text auf der Konsole ausgegeben und Eingaben ber die Tastatur eingelesen. Die Klasse kann aber noch viel mehr, insbesondere, was die Darstellung und Positionierung von Text im Konsolenfenster betrifft. Darber hinaus knnen mit ihr auch die Standardstrme (Eingabe-, Ausgabe- und Fehlerstrom) und deren Kodierung verndert werden. Dies wird in diesem Buch aber nicht weiter vertieft.

10.17.1 Eigenschaften
Die Konsole unterscheidet den Pufferbereich, in dem Eingaben stattfinden knnen, und den Konsolenfensterbereich, also den Bereich des Pufferbereichs, der im Konsolenfenster sichtbar ist. Der Pufferbereich muss mindestens so gro sein wie der Konsolenfensterbereich. Ist er grer, dann kann der Konsolenfensterbereich mithilfe der Scrollbalken im Pufferbereich bewegt werden. Tabelle 10.16 zeigt die fr uns interessanten Eigenschaften von Console. Die Eigenschaften BackgroundColor und ForegroundColor liefern oder erwarten einen der in Tabelle 10.17 aufgefhrten Werte der ConsoleColor-Aufzhlung.

528

Console die Konsole

10.17

Eigenschaft
BackgroundColor

Beschreibung Liefert oder setzt die Hintergrundfarbe der Konsole als ConsoleColor-Wert. Liefert oder setzt die Hhe des Pufferbereichs. Liefert oder setzt die Breite des Pufferbereichs. Liefert true, wenn die Feststelltaste aktiviert ist. Liefert oder setzt die Spaltenposition des Cursors im Pufferbereich. Liefert oder setzt die prozentuale Hhe des Cursors bezogen auf eine Zeichenzelle. Liefert oder setzt die Zeilenposition des Cursors im Pufferbereich. Liefert oder setzt die Sichtbarkeit des Cursors als Booleschen Wert (true = Cursor sichtbar). Liefert oder setzt die Vordergrundfarbe der Konsole als ConsoleColor-Wert. Liefert die durch Schriftart und Bildschirmauflsung bestimmte maximale Anzahl an Fensterzeilen. Liefert die durch Schriftart und Bildschirmauflsung bestimmte maximale Anzahl an Fensterspalten. Liefert true, wenn die (Num)-Taste aktiviert ist. Liefert oder setzt den Titel des Konsolenfensters. Liefert oder setzt die Hhe des Konsolenfensterbereichs in Zeilen. Liefert oder setzt die horizontale Position des Konsolenfensters bezogen auf den Bildschirmpuffer. Liefert oder setzt die vertikale Position des Konsolenfensters bezogen auf den Bildschirmpuffer. Liefert oder setzt die Breite des Konsolenfensterbereichs in Spalten.

BufferHeight BufferWidth CapsLock CursorLeft

CursorSize

CursorTop

CursorVisible

ForegroundColor

LargestWindowHeight

LargestWindowWidth

NumberLock Title WindowHeight

WindowLeft

WindowTop

WindowWidth

Tabelle 10.16 Die Eigenschaften von Console

Konstante
Black Blue Cyan

Farbe Schwarz Blau Zyan

Tabelle 10.17 Die Farben der Aufzhlung ConsoleColor

529

10

Ntzliche .NET-Klassen

Konstante
DarkBlue DarkCyan DarkGray DarkGreen DarkMagenta DarkRed DarkYellow Gray Green Magenta Red White Yellow

Farbe Dunkelblau Dunkelzyan Dunkelgrau Dunkelgrn Dunkelmagenta Dunkelrot Ocker Grau Grn Magenta Rot Wei Gelb

Tabelle 10.17 Die Farben der Aufzhlung ConsoleColor (Forts.)

10.17.2 Methoden
Die Methoden der Klasse Console sind in Tabelle 10.18 aufgelistet.
Methode
Beep Clear OpenStandardError OpenStandardInput OpenStandardOutput Read ReadKey ReadLine ResetColor SetBufferSize SetCursorPosition SetError

Beschreibung Erzeugt einen Lautsprecherton. Lscht den Bildschirminhalt. Ermittelt den Standard-Fehlerstrom. Ermittelt den Standard-Eingabestrom. Ermittelt den Standard-Ausgabestrom. Liest Zeichen. Ermittelt den Tastaturdruck. Liest Textzeile ein. Setzt die Farben zurck. Setzt die Gre des Pufferbereichs. Setzt die Cursorposition. Setzt den Standard-Fehlerstrom.

Tabelle 10.18 Die Methoden von Console

530

Console die Konsole

10.17

Methode
SetIn SetOut SetWindowPosition

Beschreibung Setzt den Standard-Eingabestrom. Setzt den Standard-Ausgabestrom. Setzt die Position des sichtbaren Bereichs des Puffers. Setzt die Fenstergre. Schreibt Textzeile. Schreibt Textzeile mit Zeilenende.

SetWindowSize Write WriteLine

Tabelle 10.18 Die Methoden von Console (Forts.)

Beep Lautsprecherton
static void Beep(); static void Beep(int frequenz, int dauer);

Erzeugt einen 200 ms dauernden und 800 Hz hohen Ton ber den Konsolenlautsprecher. In der zweiten Fassung von Beep kann die Frequenz und Dauer frei gewhlt werden. Die Frequenz muss im Bereich von 37 Hz bis 32.767 Hz liegen. Folgendes Codefragment erzeugt einen Toneffekt, der fr ernsthafte Anwendungen vermutlich nur bedingt geeignet ist:
for(int x=100; x<2000; x+=50) for(int i=50; i<4000; i+=x) Console::Beep(i,2);

Clear Bildschirm lschen


static void Clear();

Lscht den Inhalt des Pufferbereichs und damit auch den des Konsolenfensters. Der Hintergrund wird vollstndig in der mit BackgroundColor definierten Farbe ausgefllt.
OpenStandardError Standard-Fehlerstrom ermitteln
static Stream^ OpenStandardError();

Liefert den aktuell als Fehlerausgabe eingestellten Textstrom als StreamObjekt zurck.

531

10

Ntzliche .NET-Klassen

OpenStandardInput Standard-Eingabestrom ermitteln


static Stream^ OpenStandardInput();

Liefert den aktuell fr die Konsolenausgabe eingestellten Textstrom als Stream-Objekt zurck.
OpenStandardOutput Standard-Ausgabestrom ermitteln
static Stream^ OpenStandardOutput();

Liefert den aktuell als Fehlerausgabe eingestellten Textstrom als StreamObjekt zurck.
Read Zeichen lesen
static int Read();

Liefert das nchste Zeichen im Eingabepuffer oder 1, falls keines vorhanden ist.
ReadKey Tastaturdruck ermitteln
static ConsoleKeyInfo ReadKey(); static ConsoleKeyInfo ReadKey(bool unterdruecken);

Ermittelt den nchsten Tastendruck als ConsoleKeyInfo-Objekt, und gibt das Zeichen auf der Konsole aus. Mit unterdruecken auf false gesetzt, kann die Ausgabe des Zeichens unterbunden werden. Die Klasse ConsoleKeyInfo besitzt die in aufgefhrten Eigenschaften4
Eigenschaft
Key KeyChar Modifiers

Beschreibung Liefert die Taste als ConsoleKey-Objekt.4 Liefert die Taste als Char. Liefert die eventuell gedrckten Modifizierer ((Strg)-, (Alt)oder ()-Taste) als ConsoleModifiers-Objekt.

Tabelle 10.19 Die Eigenschaften von ConsoleKeyInfo

ReadLine Textzeile einlesen


static String^ ReadLine()

Liest eine Textzeile vom Standard-Eingabestrom der Konsole.


4 Die Auflistung aller mglichen Tasten wrde hier zuviel Platz einnehmen und sollte bei Bedarf der .NET-Dokumentation von Microsoft entnommen werden.

532

Console die Konsole

10.17

ResetColor Farben zurcksetzen


static void ResetColor();

Setzt die Vorder- und Hintergrundfarbe der Konsole auf die Standardfarben zurck.
SetBufferSize
static void SetBufferSize(int breite, int hoehe);

Setzt die Gre des Pufferbereichs auf die angegebene breite in Spalten und hoehe in Zeilen.
SetCursorPosition Cursorposition setzen
static void SetCursorPosition(int x, int y);

Setzt die Position des Cursors im Pufferbereich.


SetError Standard-Fehlerstrom setzen
void SetError(TextWriter^ error);

Setzt den Standard-Fehlerstrom auf das TextWriter-Objekt error.


SetIn Standard-Eingabestrom setzen
void SetIn(TextReader^ in);

Setzt den Standard-Eingabestrom auf das TextReader-Objekt in.


SetOut Standard-Ausgabestrom setzen
void SetOut(TextWriter^ out);

Setzt den Standard-Ausgabestrom auf das TextWriter-Objekt out.


SetWindowPosition
static void SetWindowPosition(int x, int y);

Positioniert den sichtbaren Bereich des Konsolenfensters innerhalb des Pufferbereichs.

533

10

Ntzliche .NET-Klassen

SetWindowSize Fenstergre setzen


static void SetWindowSize(int breite, int hoehe);

Setzt die Gre des Konsolenfensters auf die angegebene breite in Spalten und hoehe in Zeilen.
Write Textzeile schreiben
static void Write(CLR-Typ)

Schreibt eine Textzeile in den Standard-Ausgabestrom der Konsole. Die Methode ist fr alle CLR-kompatiblen Typen berladen.
WriteLine Textzeile mit Zeilenende schreiben
static void WriteLine(CLR-Typ)

Schreibt eine Textzeile mit Zeilenende in den Standard-Ausgabestrom der Konsole. Die Methode ist fr alle CLR-kompatiblen Typen berladen.

10.18 Environment die Umgebung


Die Umgebung, in der das Programm luft, kann von entscheidendem Interesse sein, aus diesem Grund existiert im System-Namensbereich des .NETFrameworks die Klasse Environment, ber die sich entsprechende Informationen ermitteln lassen.

10.18.1 Eigenschaften
Tabelle 10.20 zeigt eine Auswahl der verfgbaren Eigenschaften.
Eigenschaft
CommandLine CurrentDirectory MachineName OSVersion

Beschreibung Liefert die Befehlszeile des Prozesses. Liefert oder setzt den Pfad des aktuellen Prozess-Verzeichnisses. Liefert den NetBIOS-Namen des lokalen Computers. Liefert die Bezeichnung und Versionsnummer der Plattform als OperatingSystem-Objekt. Liefert die Anzahl der Prozessoren im Computer. Liefert den Pfad des Systemverzeichnisses.

ProcessorCount SystemDirectory

Tabelle 10.20 Die Eigenschaften von Environment

534

Environment die Umgebung

10.18

Eigenschaft
TickCount UserDomainName UserInteractive

Beschreibung Liefert die seit dem Systemstart vergangenen Millisekunden. Liefert den Netzwerkdomnennamen des aktuellen Benutzers. Liefert true, wenn der Prozess im interaktiven Modus ausgefhrt wird, eine Kommunikation mit dem Anwender ber GUI-Elemente also mglich ist. Liefert den Namen der Person, die den aktuellen Thread gestartet hat. Liefert ein Version-Objekt mit Informationen ber die Version der CLR.

UserName

Version

Tabelle 10.20 Die Eigenschaften von Environment (Forts.)

10.18.2 Methoden
In Tabelle 10.21 finden Sie eine bersicht der Methoden von Environment.
Methode
Exit GetCommandLineArgs GetEnvironmentVariable GetEnvironmentVariables GetFolderPath SetEnvironmentVariable

Beschreibung Verlsst das Programm. Ermittelt die Kommandozeilenparameter. Ermittelt die Umgebungsvariable. Ermittelt die Umgebungsvariablen. Ermittelt die Ordnerpfade. Setzt die Umgebungsvariable.

Tabelle 10.21 Die Methoden von Environment

Exit Programm verlassen


static void Exit(int exitcode);

Beendet das Programm mit dem bergebenen exitcode. Der bergebene exitcode entspricht dem Wert, der bei einem Programmende durch Beendigung der main-Funktion mit return am Ende derselben zurckgegeben wird.
GetCommandLineArgs
static array<String^>^ GetCommandLineArgs();

Liefert die an das Programm bergebenen Kommandozeilenparameter als String-Array.

535

10

Ntzliche .NET-Klassen

GetEnvironmentVariable
static String^ GetEnvironmentVariable(String^ variable);

Die einfache Form von GetEnvironmentVariable liefert den Wert der Umgebungsvariablen mit dem Namen variable. Sie liefert nullptr, wenn unter dem angegebenen Namen keine Umgebungsvariable existiert.
static String^ GetEnvironmentVariable(String^ variable, EnvironmentVariableTarget suchgebiet);

Bei der zweiten Version lsst sich ber einen Wert der Aufzhlung EnvironmentVariableTarget bestimmen, wo nach der Umgebungsvariablen gesucht werden soll. Tabelle 10.22 listet die Elemente der Aufzhlung auf.
Konstante Beschreibung
Machine

Die Umgebungsvariable wird im Pfad HKEY_LOCAL_MACHINE\System\ CurrentControlSet\Control\Session Manager\Environment der Registrierung gesucht. Die Umgebungsvariable wird in dem dem aktuellen Prozess zugeordneten Umgebungsblock gesucht (gleichbedeutend mit der einfachen Version von GetEnvironmentVariable). Die Umgebungsvariable wird im Pfad HKEY_CURRENT_USER\Environment der Registrierung gesucht.

Process

User

Tabelle 10.22 Die Werte der Aufzhlung EnvironmentVariableTarget

GetEnvironmentVariables
static IDictionary^ GetEnvironmentVariables(); static IDictionary^ GetEnvironmentVariables( EnvironmentVariableTarget suchgebiet);)

Liefert alle Umgebungsvariablen der angegebenen Umgebung (eine Auflistung der mglichen Umgebungen finden Sie in Tabelle 10.22). Wird keine Umgebung angegeben, dann werden alle Umgebungsvariablen in dem dem Prozess zugeordneten Umgebungsblock geliefert. Innerhalb des IDictionary bilden der Name einer Umgebungsvariable und deren Wert jeweils ein Schlssel-Wert-Paar. Das nachstehende Codefragment gibt alle Umgebungsvariablen des Umgebungsblocks auf dem Bildschirm aus:
for each(DictionaryEntry d in Environment::GetEnvironmentVariables()) Console::WriteLine(d.Key+":"+d.Value);

536

GC der Garbage-Collector

10.19

GetFolderPath
static String^ GetFolderPath(SpecialFolder ordner);

Mit dieser Methode lassen sich die Pfade spezieller Ordner (wie z. B. des Internet-Caches, des Verzeichnisses fr Cookies, des Startmens etc.) ermitteln. Die Elemente der Aufzhlung SpecialFolder lesen Sie bitte in der .NETDokumentation nach.
SetEnvironmentVariable
static void SetEnvironmentVariable(String^ variable, String^ wert); static void SetEnvironmentVariable(String^ variable, String^ wert, EnvironmentVariableTarget ziel);

Setzt die Umgebungsvariable variable auf den Wert wert. Optional kann die Zielumgebung spezifiziert werden (eine Auflistung der mglichen Umgebungen finden Sie in Tabelle 10.22). Ohne Angabe einer Zielumgebung wird der aktuelle Umgebungsblock verwendet.

10.19 GC der Garbage-Collector


Obwohl die Garbage-Collection im Hintergrund verlsslich ihren Dienst verrichtet, kann es manchmal notwendig sein, ihr auf die Sprnge zu helfen. Dieser Abschnitt soll nicht in die Tiefen des GC-Algorithmus einsteigen, sondern kurz die wichtigsten Methoden vorstellen, die ber die Klasse GC im Namensbereich System bereit gestellt werden, zu sehen in Tabelle 10.23.
Methode
Collect GetTotalMemory KeepAlive ReRegisterForFinalize

Beschreibung Gibt Speicher frei. Ermittelt den belegten Speicher. Schtzt ein Objekt vor Lschung.
Finalize soll bei Objektzerstrung aufgerufen werden. Finalize soll bei Objektzerstrung nicht aufgerufen werden.

SuppressFinalize

Tabelle 10.23 Die Methoden von GC

537

10

Ntzliche .NET-Klassen

Collect
static void Collect();

Erzwingt den Versuch, den gesamten verwalteten Speicher freizugeben, auf den kein Zugriff mehr besteht. Es besteht jedoch keine Garantie, dass der komplette Speicher freigegeben wird, auf den kein Zugriff mehr besteht. Diese Methode greift in den Algorithmus der Garbage Collection ein und sollte nur aufgerufen werden, wenn wirklich viel Speicher freizugeben ist, z. B. weil groe Objekte nicht mehr bentigt werden.
GetTotalMemory
static long long GetTotalMemory(bool erzwingeFreigabe);

Die Methode liefert eine mglichst genaue Schtzung des belegten verwalteten Speichers. Bei bergabe von true fr erzwingeFreigabe wird vorher noch eine Garbage Collection durchgefhrt.
KeepAlive
static void KeepAlive(Object^ objekt);

Manchmal kommt es vor, dass ein Objekt nicht freigegeben werden darf, obwohl kein verwalteter Verweis mehr darauf existiert. Diese Situation tritt beispielsweise immer dann ein, wenn das Objekt noch von nicht verwaltetem Code bentigt wird. Um knstlich einen Verweis zu verwenden und der Garbage Collection damit mitzuteilen, dass ein Objekt nicht freigegeben werden darf, muss fr dieses Objekt KeepAlive aufgerufen werden. Dabei wird mit dem Aufruf von KeepAlive der Punkt angegeben, bis zu dem das Objekt nicht freigegeben werden darf, denn der Aufruf ist die letzte Verwendung des Verweises. Danach kann das Objekt freigegeben werden.
ReRegisterForFinalize
static void ReRegisterForFinalize(Object^ objekt);

Fordert den Garbage Collector auf, fr objekt bei der Freigabe den Finalizer (Anwendungszweck und Implementierung eines Finalizers werden in Abschnitt 9.5, Ressourcenfreigabe, besprochen) aufzurufen. Diese Methode ist nur dann sinnvoll, wenn im Vorfeld der potenzielle Aufruf des Finalizers mit SuppressFinalize unterbunden wurde.

538

Timer der Taktgeber

10.20

SuppressFinalize
static void SuppressFinalize(Object^ objekt);

Hindert den Garbage Collector daran, bei der Freigabe von objekt dessen Finalizer aufzurufen.

10.20 Timer der Taktgeber


Die Klasse Timer befindet sich im System::Threading-Namensbereich. Objekte der Klasse Timer dienen dazu, in regelmigen Abstnden eine Methode aufzurufen. Die Klasse wird immer dann eingesetzt, wenn in immer wiederkehrender Folge kleine Aufgaben ausgefhrt werden sollen, die unabhngig vom Hauptprogramm laufen. Diese Fhigkeit kann spter dazu verwendet werden, in regelmigen Abstnden das Neuzeichnen eines Fensters in Auftrag zu geben oder andere Aktualisierungen durchzufhren.
Konstruktoren

Die wichtigsten Konstruktoren sind im Folgenden aufgelistet:


Timer (TimerCallback^ delegat, Object^ status, int startverzoegerung, int intervall); Timer (TimerCallback^ delegat, Object^ status, long long startverzoegerung, long long intervall);

Dem Konstruktor wird die aufzurufende Methode als TimerCallback-Delegat bergeben. Das Objekt status wird vom Timer-Objekt an die aufzurufende Methode bergeben. Auf diese Weise kann eine Methode von verschiedenen Timer-Objekten aufgerufen werden und die einzelnen Objekte ber status unterscheiden. Die startverzoegerung ist die Zeit in Millisekunden, die verstreichen muss, bis der Timer zum ersten Mal die Methode aufruft. Wird die Konstante Timeout::Infinite angegeben, dann startet der Timer nie. Ab dem ersten Start wird die Methode alle intervall Millisekunden aufgerufen. Wird fr intervall der Wert Timeout::Infinite angegeben, dann findet keine Wiederholung des Methodenaufrufs statt. Die Zeiten knnen als int- oder als long long-Wert bergeben werden. Der TimerCallback-Delegat sieht so aus:
delegate void TimerCallback(Object^ state);

539

10

Ntzliche .NET-Klassen

Es wird also eine Methode ohne Rckgabewert und mit einem Object als Parameter erwartet.
Change
bool Change (int startverzoegerung, int intervall); bool Change (long long startverzoegerung, long long intervall);

Mit der Methode Change knnen die Startverzgerung und das Zeitintervall zwischen den Methodenaufrufen verndert werden.

10.20.1 Ein Beispiel


Im folgenden Beispiel soll jede Sekunde das aktuelle Datum mit Uhrzeit ausgegeben werden. Dazu wird zunchst eine Klasse entworfen, die eine statische Methode besitzt, die Datum und Uhrzeit auf dem Bildschirm ausgibt (der Namensbereich System::Threading wurde vorher mit using namespace verfgbar gemacht):
class Ticker { public: static void ticker(Object^ o) { Console::WriteLine(DateTime::Now.ToString()); } };
Listing 10.13 Die Klasse Ticker

In der main-Funktion wird das entsprechende Timer-Objekt erstellt. Es soll direkt beginnen und die Methode dann jede Sekunde (=1.000 Millisekunden) aufrufen. Damit das Programm nicht gleich beendet ist, wurde eine Eingabe hinzugefgt:
int main(array<System::String ^> ^args) { Timer ^t=gcnew Timer(gcnew TimerCallback(&Ticker::ticker), nullptr, 0, 1000); Console::Write("Eingabe:"); String^ s=Console::ReadLine(); }
Listing 10.14 Die main-Funktion

540

In diesem Kapitel dreht sich alles um die Dateiverwaltung. Es wird der Zugriff auf Laufwerke und Verzeichnisse sowie das Lesen und Beschreiben von Dateien wird besprochen.

11

Dateiverwaltung

Auf Konsolenebene knnen Sie bereits komplexere Programme schreiben. Die mit den Programmen erstellten Ergebnisse oder verwalteten Daten sind aber nicht von langer Dauer, weil Sie noch keine Mglichkeit kennen, Daten persistent zu speichern. Das werden wir in diesem Kapitel nachholen. Wir werden uns von der obersten Ebene, den Laufwerken, ber die Verzeichnisse zu den Dateien durcharbeiten, um diese schlussendlich zu lesen und zu beschreiben. Eins haben alle hier besprochenen Klassen gemeinsam: Sie gehren zum Namensbereich System::IO. Die folgenden Kapitel gehen davon aus, dass der Namensbereich IO ber using namespace verfgbar gemacht wurde:
using namespace System; using namespace IO;

In der zweiten Anweisung reicht IO als Angabe aus, weil vorher der Namensbereich System global verfgbar gemacht wurde, der IO enthlt. Ohne using namespace System msste der Namensbereich IO voll qualifiziert werden:
using namespace System::IO;

Es bietet sich an, immer die ausfhrliche Schreibweise zu verwenden, um unabhngig von anderen using-Anweisungen zu bleiben, die vielleicht einmal entfernt werden knnten.

11.1

DateTime

Im Zusammenhang mit Dateien und Verzeichnissen liefern oder erwarten einige Methoden den Typ DateTime. Er dient dazu, ein Datum mitsamt Uhrzeit im Bereich 1.1.0001 bis zum Jahr 9999 bis auf 100 Nanosekunden genau zu anzugeben.

541

11

Dateiverwaltung

Nanosekunden 100 Nanosekunden entsprechen 0,0001 Millisekunden oder 0,0000001 Sekunden. Ein Lichtstrahl, der ca. 300.000 Kilometer in der Sekunde zurcklegt, kommt in dieser Zeit nur ca. 30 Meter weit. Unter .NET wird die Einheit von 100 Nanosekunden Tick genannt.

Von den zahlreichen Konstruktoren des Werttyps sollen hier nur drei vorgestellt werden:
DateTime(long long ticks); DateTime(int jahr, int monat, int tag); DateTime(int jahr, int monat, int tag, int stunde, int minute, int sekunde);

Der heilige Abend 2010 knnte so definiert werden:


DateTime ha(2010,12,24,18,0,0);

11.1.1

ffentliche Eigenschaften

Tabelle 11.1 listet die wichtigsten Eigenschaften von DateTime auf.1


Eigenschaft
Date

Beschreibung Liefert das Datum als DateTime. Die Uhrzeit ist auf 00:00:00 gesetzt. Liefert den Tag des Monats (131). Liefert den Wochentag als DayOfWeek. Liefert den Tag des Jahres (1366). Liefert die Stunden (023). Liefert die Millisekunden (0999). Liefert die Minuten (059). Liefert den Monat (112). Statisch. Liefert das lokale Datum und die lokale Uhrzeit als DateTime.1 Liefert die Sekunden (059). Liefert die Ticks als long long.

Day DayOfWeek DayOfYear Hour Millisecond Minute Month Now

Second Ticks

Tabelle 11.1

Die wichtigsten Eigenschaften von DateTime

1 Die Klasse TimeSpan reprsentiert eine Zeitspanne. Nhere Informationen entnehmen Sie bitte der .NET-Dokumentation.

542

DateTime

11.1

Eigenschaft
TimeOfDay Today

Beschreibung Liefert die seit Mitternacht vergangene Zeit als TimeSpan. Statisch. Liefert das aktuelle Datum als DateTime. Die Uhrzeit ist auf 00:00:00 gesetzt. Liefert das Jahr (19999).

Year

Tabelle 11.1

Die wichtigsten Eigenschaften von DateTime (Forts.)

Die DayOfWeek-Aufzhlung enthlt die Konstanten Monday, Tuesday, Wednesday, Thursday, Friday, Saturday und Sunday.

11.1.2

ffentliche Methoden

Die Klasse DateTime bietet einige Methoden, um Zeitabschnitte hinzuzuaddieren oder abzuziehen. Sie alle beginnen mit Add (AddDays, AddHours etc.) und sollen hier nicht weiter ausgefhrt werden. Zum Vergleich von DateTime-Objekten sind alle Vergleichsoperatoren fr diese Klasse berladen.
DaysInMonth Tage im Monat
public static int DaysInMonth(int jahr, int monat);

Diese Methode liefert die Anzahl der Tage im angegebenen Monat.


IsDaylightSavingTime Sommerzeit?
bool IsDaylightSavingTime()

Liefert true, wenn sich der durch das DateTime-Objekt definierte Zeitpunkt in der Sommerzeit der aktuellen Zeitzone befindet. Andernfalls liefert die Methode false.
IsLeapYear Schaltjahr?
static bool IsLeapYear(int jahr);

Liefert true, wenn es sich bei dem bergebenen Jahr um ein Schaltjahr handelt. Andernfalls liefert die Methode false.
ParseExact String zu DateTime
public static DateTime ParseExact(string s, string format, IFormatProvider kultur);

543

11

Dateiverwaltung

Liefert ein DateTime-Objekt zurck, welches die in s enthaltenen Datumsund Zeitangaben enthlt. Das Format der Angaben wird ber den String format definiert. ber kultur kann ein entsprechendes CultureInfo-Objekt (siehe Abschnitt 10.1, CultureInfo) bergeben werden, um Besonderheiten einer Kultur zu bercksichtigen. Bei Angabe von nullptr wird die aktuelle Kultur verwendet. Tabelle 10.3 in Abschnitt 10.1.1, DateTimeFormatInfo, listet die verwendbaren Formatmuster auf. Schauen wir uns ein Beispiel an:
String^ s="01.03.2006"; DateTime d=DateTime::ParseExact(s,"d/M/yyyy",nullptr);

Obwohl der Tag eine fhrende Null besitzt, wird bei der Erkennung das Formatzeichen von Tagen ohne fhrende Null verwendet. Dies gilt auch fr andere Elemente wie Monate oder Minuten. Ein Element ohne fhrende Null mit einem Formatmuster fr Elemente mit fhrender Null einzulesen, erzeugt eine Ausnahme. Im Formatstring wurde zur Trennung das Zeichen / verwendet, weil es, wie aus der Tabelle zu entnehmen ist, fr das ber die Kultur definierte Datumstrennzeichen steht. Wird ein aus einem Zeichen bestehendes Formatmuster isoliert verwendet (z. B. d), dann muss vor diesem Zeichen ein % gesetzt werden (%d).
Subtract Zeitspanne ermitteln
TimeSpan Subtract(DateTime objekt); DateTime Subtract(TimeSpan objekt);

Zieht die entsprechende Zeit oder Zeitspanne vom aufrufenden DateTimeObjekt ab.
ToString String aus DateTime
String^ ToString(String^ format);

Liefert das aufrufende DateTime-Objekt als String zurck, dessen Formatierung dem in format bergebenen Format entspricht. Die zur Verfgung stehenden Formatmuster sind in Tabelle 10.2 in Abschnitt 10.1.1, DateTimeFormatInfo, aufgefhrt. Soll ein Zeichen ausgegeben werden, welches einem Formatmuster entspricht, dann muss vor dieses Zeichen ein Backslash gesetzt werden. Der Backslash selbst wird mit \\ ausgegeben.

544

Laufwerke

11.2

11.2

Laufwerke

Kommen wir jetzt zur Dateiverwaltung und beginnen mit den Laufwerken. Alle Informationen ber verfgbare Laufwerke werden von der Klasse DriveInfo zur Verfgung gestellt. Deren wichtigste Methode GetDrives ist statisch und liefert ein Array von Verweisen auf DriveInfo-Objekte, die die verfgbaren Laufwerke reprsentieren:
array<DriveInfo^>^ laufwerke=DriveInfo::GetDrives();

Ist ein Laufwerksbuchstabe bekannt, dann kann auch gezielt ein Objekt erzeugt werden:
DriveInfo^ d=gcnew DriveInfo("C");

Bei der Angabe der Laufwerksbuchstaben sind Gro- und Kleinbuchstaben erlaubt. Die Klasse DriveInfo stellt einige interessante Eigenschaften bereit, die in Tabelle 11.2 aufgefhrt sind.
Eigenschaft
AvailableFreeSpace

Beschreibung Liefert den fr den aktuellen Anwender freien Speicherplatz des Laufwerks in Byte als long long. Liefert den Namen des Dateiformats (z. B. NTFS). Liefert den Laufwerkstyp als DriveType (Tabelle 11.3). Liefert einen Booleschen Wert, der Auskunft darber gibt, ob das Laufwerk bereit ist. Liefert den Namen des Laufwerks. Liefert das Stammverzeichnis des Laufwerks als DirectoryInfo. Liefert den gesamten freien Speicherplatz in Byte als long long. Liefert die Gesamtgre des Speicherplatzes des Laufwerks in Byte als long long. Liefert die Datentrgerbezeichnung.

DriveFormat DriveType IsReady

Name RootDirectory

TotalFreeSpace

TotalSize

VolumeLabel

Tabelle 11.2

Die Eigenschaften von DriveInfo

545

11

Dateiverwaltung

DriveType
CDRom Fixed Network NoRootDirectory Ram Removable Unknown

Beschreibung optischer Datentrger, wie CD, DVD etc. Festplatte Netzlaufwerk ein Laufwerk ohne Stammverzeichnis RAM-Datentrger Wechseldatentrger, wie Disk, USB-Stick etc. unbekannter Laufwerkstyp
Die Elemente der Aufzhlung DriveType

Tabelle 11.3

Das folgende Beispiel listet die vorhandenen Laufwerke mitsamt ihrer Datentrgerbezeichnung auf:
array<DriveInfo^>^ laufwerke=DriveInfo::GetDrives(); Console::WriteLine("{0,-10}{1,-12}", "Laufwerk", "Bezeichnung"); for each(DriveInfo^ d in laufwerke) { Console::Write("{0,-10}",d->Name); if(!d->IsReady) Console::WriteLine("Nicht bereit"); else Console::WriteLine("{0,-12}",d->VolumeLabel); }
Listing 11.1 Die Auflistung aller Laufwerke

Wenn auf Informationen des Laufwerks zugegriffen werden soll, dann mssen Sie vorher prfen, ob das Laufwerk bereit ist, denn von einer nicht eingelegten CD kann schwerlich die Datentrgerbezeichnung ermittelt werden. Sollten Sie es trotzdem versuchen, wird eine IOException geworfen.

11.3

Verzeichnisse

Die wesentlichen Klassen zur Verwaltung von Verzeichnissen sind Directory und DirectoryInfo. Die Objekte der Klasse DirectoryInfo reprsentieren ein Verzeichnis, und alle Klassenelemente beziehen sich auf dieses Verzeichnis. Die Klasse Directory dagegen stellt ausschlielich statische Methoden zur

546

Verzeichnisse

11.3

Verfgung, bei denen der Name des betroffenen Verzeichnisses angegeben werden muss. Bei hufigem Zugriff auf dasselbe Verzeichnis sollten Sie aus Grnden der Performanz die Klasse DirectoryInfo vorziehen.

11.3.1

Directory

Dieser Abschnitt behandelt die statischen Methoden der Klasse Directory. Tabelle 11.4 listet sie auf.
Methode
CreateDirectory Delete Exists GetCreationTime GetCurrentDirectory GetDirectories GetDirectoryRoot GetFiles GetFileSystemEntries GetLastAccessTime GetLastWriteTime GetLogicalDrives GetParent Move SetCurrentDirectory

Beschreibung Erstellt ein Verzeichnis. Lscht ein Verzeichnis. Prft, ob ein Verzeichnis existiert. Ermittelt das Erstellungsdatum. Ermittelt das aktuelle Verzeichnis. Liefert alle Unterverzeichnisse. Liefert das Wurzelverzeichnis. Liefert alle Dateien eines Verzeichnisses. Liefert alle Verzeichnisse und Dateien. Ermittelt die Zeit des letzten Zugriffs. Ermittelt die Zeit des letzten Schreibens. Ermitelt alle logischen Laufwerke. Ermittelt das bergeordnete Verzeichnis. Verschiebt ein Verzeichnis. Setzt das aktuelle Verzeichnis.

Tabelle 11.4

Die Methoden von Directory

Sollte fr die Ausfhrung der Methoden (z. B. Verzeichnis lschen) nicht die notwendige Berechtigung vorhanden sein, dann wird eine UnauthorizedAccessException geworfen.
CreateDirectory Verzeichnis erstellen
static DirectoryInfo^ CreateDirectory(String^ pfad);

Erzeugt ein Verzeichnis und liefert ein DirectoryInfo-Objekt auf das erstellte Verzeichnis zurck. Dabei werden alle nicht vorhandenen Verzeichnisse

547

11

Dateiverwaltung

erstellt. Wird beispielsweise als Pfad C:\AB\CDE\F angegeben, dann wird das Verzeichnis AB, darin das Verzeichnis CDE und wiederum darin das Verzeichnis F angelegt, wenn diese nicht existieren sollten. Zumindest das letzte Verzeichnis F darf noch nicht existieren, sonst wird eine Ausnahme geworfen.
Delete Verzeichnis lschen
static void Delete(String^ pfad); static void Delete(String^ pfad, bool komplett);

Lscht das durch pfad spezifizierte, leere Verzeichnis oder, fr den Fall, dass fr komplett der Wert true bergeben wird, das Verzeichnis mitsamt enthaltener Dateien und Unterverzeichnissen.
Exists Existiert Verzeichnis?
static bool Exists(String^ pfad);

Liefert true, wenn das mit pfad spezifizierte Verzeichnis existiert. Andernfalls wird false zurckgeliefert.
GetCreationTime Erstellungsdatum
static DateTime GetCreationTime(String^ pfad);

Liefert Erstellungsdatum und -uhrzeit des ber pfad spezifizierten Verzeichnisses. Mit der Methode SetCreationTime kann das Erstellungsdatum gesetzt werden.
GetCurrentDirectory aktuelles Verzeichnis
static String^ GetCurrentDirectory();

Liefert das aktuelle Verzeichnis der Anwendung.


GetDirectories alle Unterverzeichnisse
static array<String^>^ GetDirectories(String^ pfad); static array<String^>^ GetDirectories(String^ pfad, String^ filter); static array<String^>^ GetDirectories(String^ pfad, String^ filter, SearchOption option);

548

Verzeichnisse

11.3

Liefert alle in pfad befindlichen Verzeichnisse als Array von Strings. ber filter kann der unter Windows bekannte Filter verwendet werden, bei dem ? fr ein beliebiges Zeichen und * fr kein, ein oder mehrere beliebige Zeichen steht. Zum Beispiel liefert der Ausdruck *.cpp alle Quellcodedateien. Mittels option kann angegeben werden, ob auch in Unterverzeichnissen gesucht werden soll (SearchOption::AllDirectories) oder nur im angegebenen Verzeichnis (SearchOption::TopDirectoryOnly).
GetDirectoryRoot Wurzelverzeichnis
static String^ GetDirectoryRoot(String^ pfad);

Liefert das Stammverzeichnis des mit pfad spezifizierten Verzeichnisses.


GetFiles alle Dateien
static array<String^>^ GetFiles(String^ pfad); static array<String^>^ GetFiles(String^ pfad, String^ filter;) static array<String^>^ GetFiles(String^ pfad, String^ filter, SearchOption option);

Liefert alle in pfad befindlichen Dateien als Array von Strings. Die Parameter filter und option verhalten sich, wie bei GetDirectories erklrt.
GetFileSystemEntries alle Verzeichnisse und Dateien
static array<String^>^ GetFileSystemEntries(String^ pfad); static array<String^>^ GetFileSystemEntries(String^ pfad, String^ filter);

Liefert die Namen aller Unterverzeichnisse und Dateien des ber pfad spezifizierten Verzeichnisses. Der Parameter filter verhlt sich wie bei GetDirectories erklrt.
GetLastAccessTime Zeit des letzten Zugriffs
static DateTime GetLastAccessTime(String^ pfad);

Liefert Datum und Uhrzeit des letzten Zugriffs auf das ber pfad angegebene Verzeichnis. Mit der Methode SetLastAccessTime knnen Datum und Uhrzeit des letzten Zugriffs gesetzt werden.

549

11

Dateiverwaltung

GetLastWriteTime Zeit des letzten Schreibens


static DateTime GetLastWriteTime(String^ pfad);

Liefert Datum und Uhrzeit des letzten Schreibzugriffs auf das ber pfad angegebene Verzeichnis. Mit der Methode SetLastWriteTime knnen Datum und Uhrzeit des letzten Schreibzugriffs gesetzt werden.
GetLogicalDrives logische Laufwerke
static array<String^>^ GetLogicalDrives();

Liefert die Namen aller logischen Laufwerke.


GetParent bergeordnetes Verzeichnis
static DirectoryInfo^ GetParent(String^ pfad);

Liefer das bergeordnete Verzeichnis von pfad als DirectoryInfo.


Move Verzeichnis verschieben
static void Move(String^ quellpfad, String^ zielpfad);

Verschiebt das ber quellpfad spezifizierte Verzeichnis nach zielpfad.


SetCurrentDirectory aktuelles Verzeichnis setzen
static void SetCurrentDirectory(String^ pfad);

Setzt das aktuelle Verzeichnis der Anwendung.

11.3.2

FileSystemInfo

Die abstrakte Klasse FileSystemInfo stellt u. a. die in Tabelle 11.5 aufgefhrten Eigenschaften bereit, die von den Klassen DirectoryInfo und FileInfo geerbt werden.
Eigenschaft
Attributes

Beschreibung Liefert und setzt die Dateiattribute ber die FileAttributesAufzhlung (Tabelle 11.6 zeigt die wichtigsten Flags der Aufzhlung). Liefert oder setzt den Erstellungszeitpunkt. Liefert true, wenn Datei oder Verzeichnis existieren.

CreationTime Exists

Tabelle 11.5

Die wichtigsten Eigenschaften von FileSytemInfo

550

Dateien

11.4

Eigenschaft
Extension FullName LastAccessTime LastWriteTime

Beschreibung Liefert die Dateiendung (z. b. .exe.) Liefert den vollstndigen Datei-/Verzeichnispfad. Liefert oder setzt den Zeitpunkt des letzten Zugriffs. Liefert oder setzt den Zeitpunkt des letzten Schreibzugriffs.

Tabelle 11.5

Die wichtigsten Eigenschaften von FileSytemInfo (Forts.)

Attribut
Archive Compressed Directory Encrypted Hidden Normal

Beschreibung Archivstatus der Datei komprimierte Datei Datei ist ein Verzeichnis. verschlsselte Datei oder Verzeichnis versteckte Datei Normale Datei. Es existieren keine weiteren Attribute. Die Datei ist offline und damit nicht sofort verfgbar. schreibgeschtzte Datei Systemdatei Temporre Datei. Wird bevorzugt im Arbeitsspeicher abgelegt und sollte nicht lnger als ntig existieren.
Die wichtigsten Flags der FileAttributes-Aufzhlung

NotContentIndexed keine Inhaltsindizierung durch das Betriebssystem Offline ReadOnly System Temporary

Tabelle 11.6

11.3.3

DirectoryInfo

Objekte der Klasse DirectoryInfo reprsentieren ein Verzeichnis. Der Konstruktor erwartet den Pfad des Verzeichnisses als Parameter. Die bereitgestellten Methoden entsprechen denen von Directory, nur dass es sich hier nicht um statische, sondern um Objektmethoden handelt. Zustzlich zu den von FileSystemInfo geerbten Eigenschaften besitzt die Klasse noch die Eigenschaft Parent, die das bergeordnete Verzeichnis enthlt, sowie Root, ber die das Stammverzeichnis verfgbar ist.

11.4

Dateien

Die Aufteilung der Dateiklassen luft analog zu den Verzeichnissen. Die Klasse File stellt statische Methoden mit dem entsprechenden Dateinamen

551

11

Dateiverwaltung

als Argument bereit, wohingegen die Objekte von FileInfo eine Datei reprsentieren, fr die dann die Methoden aufgerufen werden knnen.

11.4.1

File

Eine Datei kann mittels Delete gelscht, ber Move verschoben und mit Copy kopiert werden. Zur Abfrage und Vernderung der Erstellungs- oder nderungszeit existieren die bei Directory erklrten Methoden GetCreationTime, GetLastAccessTime, GetLastWriteTime sowie die dazugehrigen Set-Methoden. Zustzlich bietet File noch einige andere interessante Methoden. Tabelle 11.7 zeigt sie.
Methode
AppendAllText AppendText ReadAllBytes ReadAllLines ReadAllText WriteAllBytes WriteAllLines WriteAllText

Beschreibung Hngt Text an. Erzeugt TextWriter fr eine Datei. Liest Bytes ein. Liest Zeilen ein. Liest eine komplette Textdatei ein. Schreibt Bytes in eine Datei. Schreibt Text zeilenweise in eine Datei. Schreibt Text in eine Datei.

Tabelle 11.7

Die Methoden von File

AppendAllText Text anhngen


static void AppendAllText(String^ pfad, String^ inhalt); static void AppendAllText(String^ pfad, String^ inhalt, Encoding^ kodierung);

Hngt die in einem String gespeicherten Zeichen an eine Textdatei. Sollte die Datei noch nicht existieren, wird sie angelegt. Ohne explizite Kodierung wird UTF-8 ohne Bytereihenfolgemarkierung verwendet.
AppendText
static StreamWriter^ AppendText(String^ pfad);

Erstellt einen StreamWriter (Abschnitt 11.7.1, StreamWriter), ber den Text an die in pfad angegebene Datei angehngt werden kann.

552

Dateien

11.4

ReadAllBytes Bytes lesen


static array<unsigned char>^ ReadAllBytes(String^ pfad);

Die Methode liest eine Binrdatei in ein unsigned char-Array und liefert dieses zurck.
ReadAllLines Zeilen lesen
static array<String^>^ ReadAllLines(String^ pfad); static array<String^>^ ReadAllLines(String^ pfad, Encoding^ kodierung);

Liest eine Textdatei zeilenweise ein, speichert sie in ein String-Array, und liefert dieses zurck. Der Parameter kodierung bestimmt die verwendete Zeichenkodierung. Ohne Angabe einer Kodierung werden UTF-8 und UTF-32 erkannt.
ReadAllText komplette Textdatei lesen
static String^ ReadAllText(String^ pfad); static String^ ReadAllText(String^ pfad, Encoding^ kodierung);

Liest eine Textdatei in einen String, und liefert diesen zurck. Der Parameter kodierung bestimmt die verwendete Zeichenkodierung. Ohne Angabe einer Kodierung werden UTF-8 und UTF-32 erkannt.
WriteAllBytes Bytes schreiben
static void WriteAllBytes(String^ pfad, array<unsigned char>^ bytes);

Speichert den Inhalt eines unsigned char-Arrays in eine Binrdatei.


WriteAllLines Zeilen schreiben
static void WriteAllLines(String^ pfad, array<String^>^ zeilen); static void WriteAllLines(String^ pfad, array<String^>^ zeilen, Encoding^ kodierung);

Speichert die in einem String-Array gespeicherten Zeilen in eine Textdatei. Ohne explizite Kodierung wird UTF-8 ohne Bytereihenfolgemarkierung verwendet.

553

11

Dateiverwaltung

WriteAllText Text schreiben


static void WriteAllText(String^ pfad, String^ inhalt); static void WriteAllText(String^ pfad, String^ inhalt, Encoding^ kodierung);

Speichert die in einem String gespeicherten Zeichen in eine Textdatei. Ohne explizite Kodierung wird UTF-8 ohne Bytereihenfolgemarkierung verwendet.

11.4.2

FileInfo

Die Klasse FileInfo besitzt auch die von FileSystemInfo geerbten Eigenschaften, fgt aber noch einige hinzu, von denen die interessantesten in Tabelle 11.8 aufgelistet sind.
Eigenschaft
Directory

Beschreibung Liefert das bergeordnete Verzeichnis als DirectoryInfoObjekt. Liefert den vollstndigen Pfad des Verzeichnisses. Liefert true, wenn die Datei schreibgeschtzt ist. Liefert die Dateigre als long long.

DirectoryName IsReadOnly Length

Tabelle 11.8

Die wichtigsten Eigenschaften von FileInfo

11.5

Dateistrme

Die vorigen Abschnitte haben einen Einblick in das Dateisystem gegeben und allerlei Mglichkeiten geboten, Informationen ber Laufwerke, Verzeichnisse und Dateien zu erhalten. Jetzt steht der wichtigste Punkt der Dateiverwaltung an das Lesen und Beschreiben von Dateien. Das Grundprinzip dieses Vorgangs ist in Abbildung 11.1 dargestellt. Die Verbindung zu einer Datei stellt ein Dateistrom (filestream) her, der die betroffene Datei ffnet und auf den anschlieend spezielle Leser- oder Schreiber-Klassen zugreifen. Diese Leser und Schreiber bilden die Schnittstelle der Datenbertragung von und zu einer Datei.

554

Dateistrme

11.5

Entscheidet, ob Datei gelesen, beschrieben oder beiden wird

Dateistrom ffnen

Entscheidet, ob Text- oder Binr-Datei

Dateistrom an einen Reader/Writer koppeln

Daten lesen/ schreiben

Reader/Writer (und damit auch den Dateistrom) schlieen


Abbildung 11.1 Der Vorgang des Lesens und Beschreibens von Dateien

11.5.1

FileStream

Wie Abbildung 11.1 zeigt, besteht der erste Schritt zum Datenaustausch mit Dateien in der Erzeugung eines Dateistroms.
Konstruktoren

Von den ber zwei Dutzend FileStream-Konstruktoren sollen hier nur die wichtigsten besprochen werden.
FileStream(String^ pfad, FileMode modus); FileStream(String^ pfad, FileMode modus, FileAccess zugriff); FileStream(String^ pfad, FileMode modus, FileAccess zugriff, FileShare fremdzugriff);

Der Parameter pfad spezifiziert den Pfad der zu ffnenden Datei. Die Aufzhlung FileMode (Tabelle 11.9) legt fest, wie die Datei geffnet werden soll.

555

11

Dateiverwaltung

Die Aufzhlung FileAccess (Tabelle 11.10) definiert die Art des Dateizugriffs. ber FileShare schlielich kann mitgeteilt werden, ob noch andere FileStream-Objekte auf die Datei zugreifen knnen. Eine andere Mglichkeit, einen Dateistrom zu erzeugen, bietet die statische Methode Open der Klasse File. Sie besitzt als Parameter den Dateipfad sowie die hier besprochenen FileMode, FileAccess und FileShare und liefert den geffneten Dateistrom zurck.
FileMode
Append

Beschreibung ffnet eine Datei (oder erstellt eine neue), und setzt den Dateipositionszeiger an das Dateiende. Erstellt eine neue Datei. Eine bereits vorhandene wird auf Lnge 0 reduziert (Kombination von CreateNew und Truncate). Erstellt eine neue Datei. Die Datei darf noch nicht existieren. ffnet eine Datei, und setzt den Dateipositionszeiger an den Dateianfang. Die Datei muss existieren. Wie Open, nur das gegebenenfalls eine Datei erstellt wird. Eine vorhandene Datei wird geffnet und auf die Lnge 0 reduziert.
Die Elemente von FileMode

Create

CreateNew Open

OpenOrCreate Truncate

Tabelle 11.9

FileAccess
Read Write ReadWrite

Beschreibung Die Datei wird zum Lesen geffnet. Die Datei wird zum Schreiben geffnet. Die Datei wird zum Lesen und Schreiben geffnet.
Die Elemente von FileAccess

Tabelle 11.10

FileShare
None Read Write ReadWrite Delete

Beschreibung Auf die Datei besteht exklusiver Zugriff. Andere Anwendungen drfen die Datei nur zum Lesen ffnen. Andere Anwendungen drfen die Datei nur zum Schreiben ffnen. Andere Anwendungen drfen die Datei beliebig ffnen. Die Datei kann anschieend gelscht werden.
Die Elemente von FileShare

Tabelle 11.11

556

Binrstrme

11.6

Eigenschaften

Informationen ber den Dateistrom sind ber einige in Tabelle 11.12 aufgefhrte Eigenschaften abfragbar.
Eigenschaft
CanRead

Beschreibung Liefert true, wenn aus dem Dateistrom gelesen werden kann. Liefert false, wenn der Strom geschlossen oder nur zum Schreiben geffnet ist. Liefert true, wenn ber den Strom beliebige Dateipositionen anwhlbar sind. Liefert true, wenn in den Dateistrom geschrieben werden kann. Liefert false, wenn der Strom geschlossen oder nur zum Lesen geffnet ist. Liefert die Lnge des Stroms als long long. Liefert oder setzt die aktuelle Position des Stroms in der Datei.
Die Eigenschaften von FileStream

CanSeek

CanWrite

Length Position

Tabelle 11.12

Ob ein Dateistrom als Text- oder Binrdatei interpretiert wird, hngt von dem verwendeten Reader oder Writer ab.

11.6

Binrstrme

In diesem Abschnitt werden die Klassen fr Binrstrme besprochen, sie heien BinaryReader und BinaryWriter. Obwohl wir sie in diesem Kapitel im Zusammenhang mit Dateistrmen behandeln, knnen sie auch auf jeden anderen Strom angewendet werden (andere Strme knnten z. B. auf einem String, einer Datenbankabfrage oder einer Netzwerkverbindung basieren).

11.6.1

BinaryWriter

Der Konstruktor von BinaryWriter erwartet einen zum Schreiben geffneten Strom. Es gibt noch einen weiteren Konstruktor, dem zustzlich eine Kodierung bergeben wird. Nheres erfahren Sie in der .NET-Dokumentation. Zustzlich kann noch eine Kodierung angegeben werden:
BinaryWriter(Stream^ strom); BinaryWriter(Stream^ strom, Encoding^ kodierung);

557

11

Dateiverwaltung

Abgesehen von der Eigenschaft BaseStream, die den verwendeten Dateistrom liefert, gibt es noch weitere ntzliche Methoden, die in Tabelle 11.13 aufgelistet werden.
Methode
Close Flush Seek Write

Beschreibung Writer schlieen Gepufferte Daten speichern Position im Strom verndern Daten schreiben
Die Methoden von BinaryWriter

Tabelle 11.13

Close Writer schlieen


void Close();

Diese Methode schliet den Writer und den mit ihm verbundenen Strom.
Flush gepufferte Daten speichern
void Flush();

Die Methode Flush schreibt alle noch gepufferten Daten in den Strom.
Seek Position im Strom verndern
long long Seek(int offset, SeekOrigin ausgangsposition);

Mit Seek kann die Position im Strom verndert werden. Der Offset bezieht sich auf die ber ausgangsposition bestimmte Ausgangsposition, deren Mglichkeiten in Tabelle 11.14 aufgefhrt sind.
SeekOrigin
Begin Current End

Beschreibung Der Offset bezieht sich auf den Anfang des Stroms. Der Offset bezieht sich auf die aktuelle Position. Der Offset bezieht sich auf das Ende des Stroms.
Die Elemente von SeekOrigin

Tabelle 11.14

Write Daten schreiben


void Write(Typ wert);

558

Binrstrme

11.6

Die Methode Write ist fr alle CLS-Datentypen (oben stellvertretend mit Typ bezeichnet) berladen und schreibt einen Wert in den Strom.

11.6.2

BinaryReader

Die Klasse BinaryReader ist das Gegenstck zu BinaryWriter und erlaubt das Lesen eines Dateistroms. Dazu muss ihrem Konstruktor ein zum Lesen geffneter Dateistrom bergeben werden (und optional eine Kodierung):
BinaryReader(Stream^ strom); BinaryReader(Stream^ strom, Encoding^ kodierung);

Der mit dem Reader verknpfte Dateistrom kann ber die Eigenschaft BaseStream ausgelesen werden. Zustzlich existieren noch Methoden, die in Tabelle 11.15 aufgefhrt sind.
Methode
Close PeekChar Read ReadBytes ReadChars ReadBoolean, ReadInt32, etc.

Beschreibung Schliet den Reader. Sieht das nchste Zeichen an. Liest Daten. Liest Bytes. Liest Zeichen. Liest Daten eines bestimmten CLS-Typs.

Tabelle 11.15

Die Methoden von BinaryReader

Close Reader schlieen


void Close();

Diese Methode schliet den Reader und den mit ihm verbundenen Strom.
PeekChar nchstes Zeichen ansehen
int PeekChar();

Liefert das nchste verfgbare Zeichen, ohne den Dateipositionszeiger zu verndern. Sollte kein Zeichen mehr verfgbar sein, wird 1 zurckgegeben.
Read Daten lesen
int Read(); int Read(array<unsigned char>^ puffer,

559

11

Dateiverwaltung

int index, int anzahl);

Die parameterlose Variante besitzt die gleiche Funktionsweise wie PeekChar, nur dass der Dateipositionszeiger weiter bewegt wird. Die zweite Version liest anzahl Zeichen in das bergebene Array puffer, beginnend bei Index index. Zurckgegeben wird die Anzahl der tatschlich gelesenen Zeichen.
ReadBytes Bytes lesen
array<unsigned char>^ ReadBytes (int anzahl);

Die Methode liest anzahl Bytes aus dem Strom und liefert sie als Array zurck.
ReadChars Zeichen lesen
array<wchar_t>^ ReadChars(int anzahl);

Die Methode liest anzahl Zeichen aus dem Strom und liefert sie als Array zurck.
ReadCLSTyp Datentypen lesen
CLSTyp ReadCLSTyp();

Fr jeden CLS-Datentyp existiert eine eigene Read-Methode, mit der dieser Datentyp aus dem Strom gelesen wird. Um den Datentyp bool aus dem Strom zu lesen, wird beispielsweise ReadBoolean verwendet.

11.7

Zeichenstrme

Hufig ist es sinnvoll, Strme und damit auch Dateien als Folge von Zeichen zu betrachten. Dazu dienen die Klassen StreamReader und Stream Writer.

11.7.1

StreamWriter

Die fr uns interessantesten Konstruktoren sehen so aus:


StreamWriter(Stream^ strom); StreamWriter(Stream^ strom, Encoding^ kodierung);

560

Zeichenstrme

11.7

Sie koppeln einen Strom an den neu erzeugten StreamWriter. ber AutoFlush kann ausgelesen oder bestimmt werden, ob der Ausgabepuffer bei jedem Schreibvorgang direkt in den Strom geschrieben wird (true) oder nicht (false). Wie auch bei dem BinaryWriter liefert BaseStream den zum Schreiben verwendeten Strom.
Close Writer schlieen
void Close();

Diese Methode schliet den Writer und den mit ihm verbundenen Strom.
Flush gepufferte Daten schreiben
void Flush();

Die Methode Flush schreibt alle noch gepufferten Daten in den Strom.
Write Daten schreiben
void Write(CLSTyp wert);

Die Methode Write ist fr alle CLS-Datentypen (oben stellvertretend mit CLSTyp bezeichnet) berladen und schreibt einen Wert in den Strom.
void Write(String^ format, array<Object^>^ argumente);

Mit dieser Variante von Write kann die Ausgabe formatiert werden, wie wir es von der Write-Methode von Console gewohnt sind.
WriteLine Zeile schreiben
void WriteLine(Typ wert); void WriteLine(String^ format, array<Object^>^ argumente);

Die Methoden besitzen dieselbe Funktionalitt wie Write, nur dass eine Zeilenendekennung mit in den Strom geschrieben wird.

11.7.2

StreamReader

Zum Lesen von Text aus einem Strom dient die Klasse StreamReader. Mit den bisherigen Abschnitten im Hinterkopf ist es nicht weiter verwunderlich, dass die Konstruktoren einen Strom erwarten und im zweiten Fall noch eine Kodierung:
StreamReader(Stream^ strom); StreamReader(Stream^ strom, Encoding^ kodierung);

561

11

Dateiverwaltung

Auer der bekannten Eigenschaft BaseStream, ber die der eingesetzte Strom ermittelt werden kann, steht noch EndOfStream bereit, die einen wahren Wert liefert, wenn das Ende des Stroms erreicht ist.
Close Reader schlieen
void Close();

Diese Methode schliet den Reader und den mit ihm verbundenen Strom.
Peek nchstes Zeichen ansehen
int Peek();

Liefert das nchste verfgbare Zeichen, ohne den Dateipositionszeiger zu verndern. Sollte kein Zeichen mehr verfgbar sein, wird 1 zurckgegeben.
Read Zeichen lesen
int Read();

Funktionsweise wie Peek, nur dass die Streamposition verndert wird.


ReadLine Zeile lesen
String^ ReadLine();

Liest eine Zeile aus dem Strom, und liefert sie als String zurck. Wurde bereits das Ende des Stroms erreicht, wird nullptr zurckgeliefert.
ReadToEnd bis zum Dateiende lesen
String^ ReadToEnd();

Liest von der aktuellen Position aus alle Zeichen bis zum Ende des Stroms, und liefert sie als String zurck. Wurde bereits das Ende des Stroms erreicht, wird ein leerer String zurckgegeben.

11.8

Serialisierung

Unter Serialisierung versteht man die Konvertierung eines Objekts in ein Format, welches sich speichern oder bertragen lsst. Die Umkehrung dieses Vorgangs wird Deserialisierung genannt. Diese Vorgnge werden von .NET ausgesprochen komfortabel untersttzt und bieten eine einfache Mglich-

562

Serialisierung

11.8

keit, Objekte oder ganze Objektsammlungen in einen Strom zu schreiben oder sie aus einem Strom zu lesen. Dreh- und Angelpunkt der Serialisierung sind die Formatter-Klassen, von denen wir uns die Klasse BinaryFormatter herauspicken, die Objekte in ein binres Format serialisiert, bzw. aus einem binren Format deserialisiert. Die Klasse befindet sich im Namensbereich System::Runtime::Serialization:: Formatters::Binary. Die Klasse besitzt u. a. einen parameterlosen Konstruktor
BinaryFormatter();

und die zwei folgenden wichtigen Methoden.


Serialize
void Serialize(Stream^ strom, Object^ objekt);

Die Methode serialisiert das Objekt objekt sowie alle Objekte, auf die objekt verweist, in ein binres Format und schreibt es in den Strom strom.
Deserialize
Object^ Deserialize(Stream^ strom);

Aus dem Strom strom werden binre Daten gelesen, deserialisiert und als Objekt des Typs Object zurckgeliefert.

11.8.1

Das SerializableAttribute

Damit eine Klasse serialisiert werden kann, muss sie mit dem .NET-Attribut SerializableAttribute versehen werden.
.NET-Attribute Unter den .NET-Attributen sind nicht Attribute zu verstehen, wie wir sie bei den Klassen kennengelernt haben. Es handelt sich vielmehr um zustzliche, auslesbare Beschreibungen von Programmcode.

Im Falle der altbekannten Klasse Becher sieht das so aus:

563

11

Dateiverwaltung

[System::SerializableAttribute] ref class Becher { };


Listing 11.2 Die serialisierbare Klasse Becher

Die so ausgezeichnete Klasse ist nun serialisierbar (und nicht vergessen, den Namensbereich von BinaryFormatter mittels using namespace global verfgbar zu machen ):
Getraenke::Becher^ b; b=gcnew Getraenke::Becher("Brause", 300,50); BinaryFormatter^ formatter=gcnew BinaryFormatter; FileStream^ strom=gcnew FileStream("test.dat", FileMode::Create, FileAccess::Write); formatter->Serialize(strom, b); strom->Close();
Listing 11.3 Die Serialisierung eines Bechers

Deserialisiert wird der Becher, wie folgt:


Getraenke::Becher^ b; BinaryFormatter^ formatter=gcnew BinaryFormatter; FileStream^ strom=gcnew FileStream("test.dat", FileMode::Open, FileAccess::Read); b=dynamic_cast<Getraenke::Becher^> (formatter->Deserialize(strom)); strom->Close(); Console::WriteLine(b);
Listing 11.4 Die Deserialisierung des Bechers

11.9

Praktische Anwendung

Dieser Abschnitt soll nach der geballten Theorie des bisherigen Kapitels anhand typischer Situationen die Dateiverwaltung in der Praxis demonstrieren. Um alle Eventualitten zu bercksichtigen, mssten potenziell auftretende Ausnahmen aufgefangen werden. Bei den folgenden Beispielen steht jedoch die grundstzliche Vorgehensweise im Vordergrund. Wir verzichten daher auf eine Ausnahmebehandlung.

564

Praktische Anwendung

11.9

11.9.1

Einlesen einer Textdatei

Zur Bewltigung dieser Aufgabe gibt es verschiedene Mglichkeiten.


Komplettes Einlesen mittels ReadAllText

Die Methode ReadAllText der Klasse File liest die gesamte Textdatei in einen String:
String^ inhalt=File::ReadAllText("Readme.txt", System::Text::Encoding::UTF7); Console::WriteLine(inhalt);
Listing 11.5 Textdatei einlesen mit ReadAllText

Die automatisch erzeugte Readme-Datei im Projektverzeichnis ist in UTF-7 kodiert. Eine automatische Erkennung funktioniert nur bei UTF-8 und UTF-32, deshalb ist eine explizite Angabe der Kodierung notwendig.
Komplettes Einlesen mittels ReadAllLines

Diese Methode von File liest die Textdatei zeilenweise ein und liefert ein String-Array zurck:
array<String^>^ inhalt; inhalt=File::ReadAllLines("Readme.txt", System::Text::Encoding::UTF7); for each(String^ s in inhalt) Console::WriteLine(s);
Listing 11.6 Textdatei einlesen mit ReadAllLines

Zeichenweises Einlesen mit einem StreamReader


FileStream^ dat=gcnew FileStream("Readme.txt", FileMode::Open, FileAccess::Read); StreamReader^ rdr; Rdr==gcnew StreamReader(dat, System::Text::Encoding::UTF7); while(!rdr->EndOfStream) { wchar_t c=rdr->Read(); Console::Write(c); } rdr->Close();
Listing 11.7 Textdatei zeichenweise einlesen

565

11

Dateiverwaltung

Die beiden Anweisungen in der while-Schleife htten auch zu einer zusammen gefasst werden knnen:
Console::Write(static_cast<wchar_t>(rdr->Read()));

11.9.2

Beschreiben einer Log-Datei

Eine Log-Datei ist eine Textdatei, in die entsprechende Informationen ber aufgetretene Ereignisse whrend der Laufzeit hineingeschrieben werden. Zum Beschreiben einer solchen Datei bietet sich die statische Methode AppendAllText der Klasse File an, die einen String an eine Datei hngt:
String^ s="Programm luft"; File::AppendAllText("log.txt",s);
Listing 11.8 Text an eine Datei hngen

11.9.3 Datei byteweise kopieren


Fr das byteweise Lesen und Schreiben kommen die Klassen BinaryReader und BinaryWriter zum Einsatz. Leider besitzt BinaryReader keine Eigenschaft EndOfStream wie StreamReader, so dass vor dem konkreten Lesevorgang mit PeekChar nachgesehen werden muss, ob noch Daten im Strom enthalten sind:
FileStream^ sin; sin=gcnew FileStream("Readme.txt", FileMode::Open, FileAccess::Read); FileStream^ sout; sout=gcnew FileStream("Readme.bak.txt", FileMode::Create, FileAccess::Write); BinaryReader^ bin=gcnew BinaryReader(sin); BinaryWriter^ bout=gcnew BinaryWriter(sout); while(bin->PeekChar()!=-1) bout->Write(bin->ReadByte()); bin->Close(); bout->Close();
Listing 11.9 Datei byteweise kopieren

566

In diesem Praxiskapitel soll die Idee des Telefonbuchs noch einmal aufgegriffen werden, allerdings unter .NET-Gesichtspunkten und einem objektorientierten Ansatz.

12

Praxis objektorientiertes Telefonbuch

Der objektorientierte Ansatz soll darin liegen, dass wir die Funktionalitt des Telefonbuchs von der Darstellung trennen. Es wird eine Klasse Telefonbuch geben, welche die gesamte Verwaltung der Kontakte in einer Datenstruktur bernimmt. Davon leiten wir dann eine Klasse TelefonbuchTxt ab, welche die Ein- und Ausgabe ber die Konsole implementiert. Die gleiche Beziehung besteht zwischen den Klassen Kontakt und KontaktTxt. Weil das .NET-Framework nur Einfachvererbung untersttzt, ist es sinnvoll, eine Vererbungshierarchie immer mit einer Schnittstelle zu beginnen, welche die minimalen Anforderungen an die Klassen stellt. Da jede Klasse beliebig viele Schnittstellen implementieren kann, hat eine andere Klassenhierarchie immer die Mglichkeit, sich ber die Schnittstelle einzuklinken.

12.1

Die Schnittstelle IKontakt

Die Grundlage aller Kontakte wird die Schnittstelle IKontakt bilden. Sie definiert die minimalen Anforderungen an einen Kontakt: Der Vorname, Nachname und die Telefonnumme mssen auslesbar und beschreibbar sein. Zwei Kontakte mssen verglichen werden knnen. Abbildung 12.1 stellt diese Anforderungen als UML-Klassendiagramm dar. Den Zugriff auf die Attribute implementieren wir unter .NET als Eigenschaften. Fr diese gibt es in der UML aber keine spezielle Notation, deshalb werden sie als die entsprechenden Get- und Set-Methoden aufgefhrt.

567

12

Praxis objektorientiertes Telefonbuch

interface IKontakt +GetVorname() : String^ +GetNachname() : String^ +GetTelefonnummer() : String^ +SetVorname(ein s : String^) +SetNachname(ein s : String^) +SetTelefonnummer(ein s : String^) +Vergleichen(ein k : IKontakt^) : int
Abbildung 12.1 Die Schnittstelle IKontakt als UML-Klassendiagramm

Der Programmcode ist ziemlich einfach:


interface class IKontakt { int Vergleichen(IKontakt^ k); property System::String^ Vorname { System::String^get(); void set(System::String^ s); } property System::String^ Nachname { System::String^get(); void set(System::String^ s); } property System::String^ Telefonnummer { System::String^get(); void set(System::String^ s); } };
Listing 12.1 Die Schnittstelle IKontakt

Da eine Schnittstelle nur Anforderungen definiert, aber keine Implementierungen besitzt, sind alle Methoden auch die get- und set-Blcke der Eigenschaften lediglich deklariert.

12.2

Die Klasse Kontakt

Die Klasse Kontakt ist nun die erste Stufe der Implementierung des Kontakts. Es ist nicht ungewhnlich, dass erst nach mehreren Ableitungsstufen alle von der Schnittstelle geforderten Elemente implementiert sind. In unserem Fall

568

Die Klasse Kontakt

12.2

implementiert aber bereits die Klasse Kontakt alles Notwendige. Obwohl sie damit keine abstrakten Methoden mehr besitzt, wird sie dennoch als abstract deklariert, weil Objekte von ihr keinen Sinn machen. Abbildung 12.2 zeigt die Klasse im UML-Diagramm.
Kontakt interface IKontakt +GetVorname() : String^ +GetNachname() : String^ +GetTelefonnummer() : String^ +SetVorname(ein s : String^) +SetNachname(ein s : String^) +SetTelefonnummer(ein s : String^) +Vergleichen(ein k : IKontakt^) : int -vorname : String^ -nachname : String^ -telnummer : String^ +GetVorname() : String^ +GetNachname() : String^ +GetTelefonnummer() : String^ +SetVorname(ein s : String^) +SetNachname(ein s : String^) +SetTelefonnummer(ein s : String^) +Vergleichen(ein k : Kontakt^) : int

Abbildung 12.2 Die Klasse Kontakt

12.2.1

Klassendefinition

Obwohl der Standardkonstruktor keine Anweisungen enthlt, wurde er der Klassendefinition hinzugefgt. Die set-Blcke der Eigenschaften verzichten auf jede Prfung auf Gltigkeit der bergebenen Zeichenketten. Es reicht an dieser Stelle, zu wissen, dass wir mit dieser Technik die Mglichkeit einer berprfung htten, wenn wir wollten. Damit Kontakt von IKontakt abgeleitet werden kann, muss zum Zeitpunkt des Ableitens die Basisklasse definiert sein. Deshalb ist ein Einbinden von IKontakt.h ber include notwendig:
#include "IKontakt.h" ref class Kontakt System::String^ System::String^ System::String^ public: Kontakt() { } virtual property System::String^ Vorname { System::String^ get() { return (vorname); } abstract : public IKontakt { vorname; nachname; telnummer;

569

12

Praxis objektorientiertes Telefonbuch

void set(System::String^ s) { vorname = s; } } virtual property System::String^ Nachname { System::String^ get() { return (nachname); } void set(System::String^ s) { nachname = s; } } virtual property System::String^ Telefonnummer { System::String^ get() { return (telnummer); } void set(System::String^ s) { telnummer = s; } } virtual int Vergleichen(IKontakt^ k); };
Listing 12.2 Die Klassendefinition von Kontakt

12.2.2

Die externen Definitionen

Die Klasse definiert nur die Methode Vergleichen extern.


Vergleichen
int Kontakt::Vergleichen(IKontakt^ k) { int v = nachname->CompareTo(k->Nachname); if (v != 0) return v; v = vorname->CompareTo(k->Vorname); if (v != 0) return v; return (telnummer->CompareTo(k->Telefonnummer)); }
Listing 12.3 Die Methode Vergleichen von Kontakt

570

Die Klasse KontaktTxt

12.3

Die Methode verwendet die CompareTo-Methoden von String, um Nachnamen, Vornamen und Telefonnummer zu vergleichen.

12.3

Die Klasse KontaktTxt

Die Klasse KontaktTxt erweitert die von Kontakt geerbte Verwaltungsfunktionalitt um die Ein- und Ausgabe auf Konsole. .NET-konform berschreiben wir fr die Ausgabe die ToString-Methode. Abbildung 12.3 zeigt die Klassenbeziehungen.
Kontakt -vorname : String^ -nachname : String^ -telnummer : String^ +GetVorname() : String^ +GetNachname() : String^ +GetTelefonnummer() : String^ +SetVorname(ein s : String^) +SetNachname(ein s : String^) +SetTelefonnummer(ein s : String^) +Vergleichen(ein k : Kontakt^) : int

KontaktTxt +Einlesen() +ToString() : String^

interface IKontakt

Abbildung 12.3 Die Klasse KontaktTxt

12.3.1

Die Klassendefinition

Die Klassendefinition besteht nur aus der Deklaration der beiden Methoden:
#include "Kontakt.h" ref class KontaktTxt : Kontakt { public: void Einlesen(); virtual System::String^ ToString() override; };
Listing 12.4 Die Klassendefinition von KontaktTxt

12.3.2

Die externen Definitionen

Kommen wir zu den Methodendefinitionen.


Einlesen
void KontaktTxt::Einlesen() { Console::Write("Vorname :");

571

12

Praxis objektorientiertes Telefonbuch

Vorname = Console::ReadLine(); Console::Write("Nachname:"); Nachname = Console::ReadLine(); Console::Write("Telefon :"); Telefonnummer = Console::ReadLine(); }


Listing 12.5 Die Methode Einlesen von KontaktTxt

Einlesen liest die Strings ber Console::ReadLine ein und weist sie den

Eigenschaften zu.
ToString
System::String^ KontaktTxt::ToString() { return (Nachname + ", " + Vorname + ", " + Telefonnummer); }
Listing 12.6 Die ToString-Methode von KontaktTxt

12.4

Die Schnittstelle ITelefonbuch

Analog zur Kontakt-Hierarchie beginnt das Telefonbuch mit einer Schnittstelle, in der die minimalen Anforderungen formuliert werden, zu sehen in Abbildung 12.4.
interface ITelefonbuch +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen(ein k : IKontakt^) : bool +Entfernen(ein i : int) : bool +Sortieren()

interface IKontakt

Abbildung 12.4 Die Schnittstelle ITelefonbuch

Wir beschrnken die Funktionalitt auf das Hinzufgen, Entfernen und Sortieren. Erweiterte Mglichkeiten, wie das ndern vorhandener Datenstze oder das Suchen nach Kontakten, knnen als bung spter hinzugefgt werden. Die Funktionalitt arbeitet mit IKontakt als Datentyp, wegen der Polymorphie knnen damit die Objekte aller Klassen, die direkt oder indirekt von IKontakt abgeleitet sind, mit diesen Methoden verwaltet werden:

572

Die Klasse Telefonbuch

12.5

#include "IKontakt.h" interface class ITelefonbuch { bool Hinzufuegen(IKontakt^ k); bool Entfernen(int pos); bool Entfernen(IKontakt^ k); void Sortieren(); };
Listing 12.7 Die Schnittstelle ITelefonbuch

12.5

Die Klasse Telefonbuch

Die Klasse Telefonbuch implementiert die von der Schnittstelle geforderten Methoden, wird aber aus dem gleichen Grund wie Kontakt als abstrakt deklariert. Als Datencontainer wird die Klasse list aus der STL/CLR verwendet. Um kontrollierten Zugriff von auen auf die Elemente zu erlauben, werden der begin- und end-Iterator ber Eigenschaften zur Verfgung gestellt.
interface IKontakt * 1 Telefonbuch -liste : list<IKontakt^> interface ITelefonbuch +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen(ein k : IKontakt^) : bool +Entfernen(ein i : int) : bool +Sortieren() -Vertauschen(ein/aus k1 : iterator&, ein/aus k2 : iterator&) +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen(ein k : IKontakt^) : bool +Entfernen(ein i : int) : bool +Sortieren() +GetAnzahl() : int +GetBeginIterator() : iterator +GetEndOperator() : iterator

Abbildung 12.5 Die Klasse Telefonbuch

12.5.1

Die Klassendefinition

#include <cliext/list> #include "ITelefonbuch.h" ref class Telefonbuch abstract : ITelefonbuch { public:

573

12

Praxis objektorientiertes Telefonbuch

// // Typdefinitionen // typedef cliext::list<IKontakt^> containertyp; typedef containertyp::iterator iterator; // // Private Elemente // private: containertyp liste; void Vertauschen(iterator& i1, iterator& i2); public: // // Eigenschaften // property int Anzahl { int get() { return (liste.size()); } } property iterator BeginIterator { iterator get() { return (liste.begin()); } } property iterator EndIterator { iterator get() { return (liste.end()); } } // // Methoden // Telefonbuch() { } virtual virtual virtual virtual };
Listing 12.8 Die Klassendefinition von Telefonbuch

bool bool bool void

Hinzufuegen(IKontakt^ k); Entfernen(int idx); Entfernen(IKontakt^ k); Sortieren();

574

Die Klasse Telefonbuch

12.5

12.5.2 Die externen Definitionen


Interessant wird es wieder mit den aufwndigeren Methoden, die extern definiert wurden, obwohl dank der STL/CLR die Arbeitsweise der Methoden identisch ist mit denen aus dem Beispiel in Kapitel 7, Praxis Adressbuch.
Hinzufuegen
bool Telefonbuch::Hinzufuegen(IKontakt^ k) { liste.push_back(k); return (true); }
Listing 12.9 Die Methode Hinzufuegen von Telefonbuch

Entfernen ber Index


bool Telefonbuch::Entfernen(int idx) { // // Negativer Index ist ungltig // if(idx<0) return(false); // // Mit Index und Iterator den Container durchlaufen, // bis entweder der Index des zu lschenden Elements // oder das Ende des Containers erreicht ist // iterator it=liste.begin(); int akt=0; while(it!=liste.end() && akt!=idx) { ++it; ++akt; } // // Wenn Index gefunden, dann lschen // if(it!=liste.end()) { liste.erase(it); return(true); }

575

12

Praxis objektorientiertes Telefonbuch

else { return(false); } }
Listing 12.10 Die Methode zum Entfernen ber einen Index

Enfernen ber Objekt


bool Telefonbuch::Entfernen(IKontakt^ k) { // // Mit Iterator den Container durchlaufen, // bis entweder das Objekt gefunden // oder das Ende des Containers erreicht ist // iterator it=liste.begin(); while(it!=liste.end() && *it!=k) { ++it; } // // Wenn Objekt gefunden, dann lschen // if(it!=liste.end()) { liste.erase(it); return(true); } else { return(false); } }
Listing 12.11 Die Methode zum Entfernen ber ein Objekt

Vertauschen
void Telefonbuch::Vertauschen(iterator& i1, iterator& i2) { IKontakt^ tmp=*i1; *i1=*i2; *i2=tmp; }
Listing 12.12 Die Methode Vertauschen von Telefonbuch

Die Methode vertauscht im Container zwei IKontakt-Verweise ber ihre Iteratorpositionen.

576

Die Klasse TelefonbuchTxt

12.6

Sortieren
void Telefonbuch::Sortieren() { // // Nur Listen mit mehr als einem Element sortieren // if(liste.size()>1) { bool vertauscht; // // Wiederhole, solange vertauscht wurde // do { vertauscht=false; iterator cur=liste.begin(); iterator suc=cur; ++suc; // // Container durchlaufen, paarweise vergleichen // und ggfs. vertauschen // while(suc!=liste.end()) { if(cur->Vergleichen(*suc)>0) { Vertauschen(cur, suc); vertauscht=true; } ++cur; ++suc; } } while(vertauscht); } }
Listing 12.13 Die Methode Sortieren von Telefonbuch

12.6

Die Klasse TelefonbuchTxt

Die Klasse TelefonbuchTxt erweitert ihre Basisklasse Telefonbuch um die Mglichkeit, Kontakte ber die Konsole einzugeben und das Telefonbuch auf der Konsole auszugeben. Abbildung 12.6 zeigt die Beziehungen.

577

12

Praxis objektorientiertes Telefonbuch

interface IKontakt

interface ITelefonbuch

* Kontakt

1 Telefonbuch

TelefonbuchTxt KontaktTxt +Auflisten() +Hinzufuegen() : bool +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen() : bool

Abbildung 12.6 Die Klasse TelefonbuchTxt

12.6.1

Die Klassendefinition

#include "Telefonbuch.h" #include "KontaktTxt.h" ref class TelefonbuchTxt : Telefonbuch { public: virtual bool Hinzufuegen(IKontakt^ k) override { return(Telefonbuch::Hinzufuegen( dynamic_cast<KontaktTxt^>(k))); } void Auflisten(); bool Hinzufuegen(); bool Entfernen(); };
Listing 12.14 Die Klassendefinition von TelefonbuchTxt

Am interessantesten ist vermutlich die berschriebene Methode Hinzufuegen (IKontakt^ k). Sie erwartet als Parameter einen Objektverweis vom Typ IKontakt, dazu ist sie verpflichtet, weil die Basisklassenmethode diesen Parametertyp ebenfalls besitzt und die Typen beim berschreiben bereinstimmen mssen. Die Methode wandelt dann den Typ ber einen Downcast in den Typ KontaktTxt zurck, um ihn dann der Basisklassenmethode zu bergeben, die daraus wieder einen IKontakt macht.

578

Die Klasse TelefonbuchTxt

12.6

Es stellt sich die Frage, warum berhaupt der Zwischenschritt des Downcasts eingefgt wurde, wo die Typen doch so schon perfekt passen. Der Hintergrund ist folgender: TelefonbuchTxt arbeitet ausschlielich mit KontaktTxtObjekten oder Subklassenobjekten davon. Die Methode Hinzufuegen erwartet aber lediglich einen IKontakt. Da die Methode ffentlich ist, kann sie jeder aufrufen und theoretisch der Kontaktliste ein Objekt unterjubeln, das zwar ein IKontakt, nicht aber ein KontaktTxt ist. Weil aber jetzt die Methode berschrieben ist, kann von auen nur noch die Methode Hinzufuegen von TelefonbuchTxt aufgerufen werden. Und der Downcast wird eben nur erfolgreich ausgefhrt, falls es sich tatschlich um ein KontaktTxt-Objekt oder ein Subklassenobjekt handelt.

12.6.2 Die externen Definitionen


Kommen wir zu den extern definierten Methoden von TelefonbuchTxt.
Auflisten
void TelefonbuchTxt::Auflisten() { // // Bei leerer Liste Text ausgeben // if (Anzahl == 0) Console::WriteLine("Liste leer!"); else { // // ber die Iteratoreigenschaften die Liste durchlaufen // iterator it=BeginIterator; int akt=0; while(it!=EndIterator) { KontaktTxt^ k = dynamic_cast<KontaktTxt^>(*it); Console::WriteLine("{0}: {1}", akt, k); ++akt; ++it; } } }
Listing 12.15 Die Methode Auflisten von TelefonbuchTxt

579

12

Praxis objektorientiertes Telefonbuch

Hinzufuegen

Die Klasse TelefonbuchTxt bekommt noch eine weitere HinzufuegenMethode, dieses Mal ohne Parameter. Sie erzeugt das hinzuzufgende KontaktTxt-Objekt selbst und ruft dafr die Einlesen-Methode auf:
bool TelefonbuchTxt::Hinzufuegen() { KontaktTxt^ k = gcnew KontaktTxt; k->Einlesen(); return (Hinzufuegen(k)); }
Listing 12.16 Eine Hinzufuegen-Methode von TelefonbuchTxt

Entfernen
bool TelefonbuchTxt::Entfernen() { Console::Write("Zu lschende Position:"); int pos = Convert::ToInt32(Console::ReadLine()); return (Entfernen(pos)); }
Listing 12.17 Die Methode Entfernen von TelefonbuchTxt

Die neu in TelefonbuchTxt dazugekommene Methode Entfernen ohne Parameter fragt den Anwender nach dem Index des zu lschenden Elements und ruft damit die geerbete Methode Entfernen auf.

12.7

Das Hauptprogramm

Das Hauptprogramm mit dem Men ist jetzt nur noch ein Klacks:
int main(array<System::String ^> ^args) { // // Objekte anlegen // TelefonbuchTxt^ telbuch = gcnew TelefonbuchTxt; int eingabe; // // TextMen // do { Console::WriteLine("Telefonbuch CLR V0.01 \n---------------------");

580

Das Hauptprogramm

12.7

Console::WriteLine("1 Console::WriteLine("2 Console::WriteLine("3 Console::WriteLine("4 Console::WriteLine("0

Kontakt hinzufgen"); Kontakte auflisten"); Kontakt entfernen"); Kontakte sortieren"); Programm beenden");

Console::Write("Ihre Wahl:"); eingabe=Convert::ToInt32(Console::ReadLine()); // // Hinzufgen // if(eingabe==1) { telbuch->Hinzufuegen(); } // // Auflisten // if(eingabe==2) { telbuch->Auflisten(); Console::WriteLine("\n"); } // // Entfernen // if(eingabe==3) { if(telbuch->Entfernen()) Console::WriteLine("Kontakt entfernt!"); else Console::WriteLine("Entfernen nicht moeglich!"); Console::WriteLine("\n"); } // // Sortieren // if(eingabe==4) { telbuch->Sortieren(); } } while(eingabe!=0); }
Listing 12.18 Das Hauptprogramm des Telefonbuchs

581

12

Praxis objektorientiertes Telefonbuch

12.8

Datei-IO

Im nchsten Schritt wollen wir das Telefonbuch mit der Mglichkeit ausstatten, sich in einen Datenstrom speichern und aus einem Datenstrom laden zu knnen. Der Einfachheit halber gehen wir davon aus, dass wir die bereits implementierten Klassen verndern knnen. Abgewickelt werden soll der Datei-IO ber die Klassen BinaryWriter und BinaryReader.

12.8.1

Die angepasste IKontakt-Schnittstelle

interface class IKontakt { void Laden(System::IO::BinaryReader^ br); void Speichern(System::IO::BinaryWriter^ bw); /* Hier steht der Rest */ };
Listing 12.19 Die zustzlichen Methoden fr IKontakt

Wir erweitern die Schnittstelle IKontakt um die Methoden Laden und Speichern. Dadurch wird fr jeden Kontakt gefordert, sich speichern und laden zu knnen.

12.8.2 Die angepasste Kontakt-Klasse


Die Klasse Kontakt wird nun um die tatschlichen Implementierungen der beiden Methoden ergnzt. Das Auflisten der Deklarationen in der Klassendefinition sparen wir uns hier. Die Definitionen sollten ausreichen:
void Kontakt::Speichern(BinaryWriter^ bw) { bw->Write(vorname); bw->Write(nachname); bw->Write(telnummer); } void Kontakt::Laden(BinaryReader^ br) { vorname = br->ReadString(); nachname = br->ReadString(); telnummer = br->ReadString(); }
Listing 12.20 Die Methoden Laden und Speichern von Kontakt

582

Datei-IO

12.8

12.8.3 Die angepasste ITelefonbuch-Schnittstelle


Die ITelefonbuch-Schnittstelle bekommt ebenfalls die Deklaration der Methoden Laden und Speichern.
interface class ITelefonbuch { void Speichern(System::IO::BinaryWriter^ bw); void Laden(System::IO::BinaryReader^ br); /* Hier steht der Rest */ };
Listing 12.21 Die ergnzte ITelefonbuch-Schnittstelle

12.8.4 Die abstrakte Fabrik


Spielen wir das Speichern und Laden des Telefonbuchs einmal durch. Um das Telefonbuch zu speichern, durchluft das Telefonbuch einfach alle Kontakte und ruft fr diese die Methode Speichern auf. Beim Laden eigentlich das Gleiche, wir rufen fr den Kontakt die Methode Laden auf, und der Kontakt ldt sich selbst. Und genau hier mssten Sie stutzig werden. Wenn das Telefonbuch geladen wird, ist es hchstwahrscheinlich leer, besitzt also keine Kontakte. Fr welchen Kontakt soll dann Laden aufgerufen werden? Die Laden-Methode von Telefonbuch muss also fr jeden zu ladenden Kontakt zuerst ein KontaktObjekt erstellen und fr dieses dann Laden aufrufen. Nur was fr ein Kontakt-Objekt genau soll erstellt werden? Eines vom Typ Kontakt? Oder KontaktTxt? Was, wenn jemand die Basisklassen spter verwenden will, um eine KontaktForms-Klasse zu schreiben? Woher soll Telefonbuch also wissen, von welchem Typ es Objekte erzeugen soll? Die Klasse kann es nicht wissen. Das liegt in der Natur der Erweiterbarkeit. Eine Basisklasse kann nicht wissen, welche Klassen irgenwann noch von ihr abgleitet werden. Wir mssen also den Datentyp variabel halten, der von Telefonbuch zum Erzeugen von Objekten verwendet wird. Und genau dazu dient eine abstrakte Fabrik. Eine abstrakte Fabrik ist eine Klasse, die von ihren Subklassen fordert, eine Methode Erzeuge zu besitzen.

583

12

Praxis objektorientiertes Telefonbuch

12.8.5 Die Schnittstelle IKontaktFabrik


interface class IKontaktFabrik { IKontakt^ ErzeugeKontakt(); };
Listing 12.22 Die Schnittstelle IKontaktFabrik

Die Schnittstelle fordert nur eine Methode, nmlich ErzeugeKontakt, die einen IKontakt zurckgibt. Von dieser Schnittstelle kann jetzt fr jeden gewnschten Kontakt-Typ eine konkrete Fabrik abgeleitet werden.

12.8.6 Die Klasse KontaktFabrikTxt


Fr unsere Anwendung erstellen wir die Klasse KontaktFabrikTxt, deren Methode ErzeugeKontakt Objekte des Typs KontaktTxt erzeugt:
ref class KontaktFabrikTxt : IKontaktFabrik { public: virtual IKontakt^ ErzeugeKontakt() { return (gcnew KontaktTxt()); } };
Listing 12.23 Die Klasse KontaktFabrikTxt

12.8.7 Die angepasste Klasse Telefonbuch


Die Klasse Telefonbuch macht nun Gebrauch von der abstrakten Fabrik. Ihr Konstruktor erwartet eine solche und speichert sie im Attribut fabrik. Die Eigenschaft Fabrik gibt lesenden Zugriff auf die vom Telefonbuch verwendete Fabrik. Die Deklarationen von Laden und Speichern sind ebenfalls enthalten. Die Definitionen sind ausgelagert und werden anschlieend besprochen.
ref class Telefonbuch abstract : ITelefonbuch { /* Typdefinitionen */ private: containertyp liste; IKontaktFabrik^ fabrik; void Vertauschen(iterator& i1, iterator& i2); public: Telefonbuch(IKontaktFabrik^ f) : fabrik(f) { }

584

Datei-IO

12.8

property IKontaktFabrik^ Fabrik { IKontaktFabrik^ get() { return(fabrik); } } virtual void Speichern(System::IO::BinaryWriter^ bw); virtual void Laden(System::IO::BinaryReader^ br); /* weitere Eigenschaften und Methodendeklarationen */ };
Listing 12.24 Die angepasste Klasse Telefonbuch

Speichern
void Telefonbuch::Speichern(BinaryWriter^ bw) { bw->Write(liste.size()); iterator it=liste.begin(); while(it!=liste.end()) (it++)->Speichern(bw); }
Listing 12.25 Die Methode Speichern von Telefonbuch

Damit die Methode Laden spter wei, wie viele Kontakte gespeichert wurden und wie viele sie dementsprechend laden muss, wird die Anzahl der zu speichernden Kontakte als erster Wert in den Binrstrom geschrieben. Anschlieend wird ber einen Iterator die gesamte Liste traversiert und fr jeden Kontakt die Methode Speichern aufgerufen.
Laden
void Telefonbuch::Laden(BinaryReader^ br) { int anz = br->ReadInt32(); liste.clear(); for (int i = 0; i < anz; ++i) { IKontakt^ k = fabrik->ErzeugeKontakt(); k->Laden(br); Hinzufuegen(k); } }
Listing 12.26 Die Methode Laden von Telefonbuch

585

12

Praxis objektorientiertes Telefonbuch

Die Methode ldt zuerst die Anzahl der gespeicherten Kontakte, erzeugt dann ber eine Schleife mithilfe der Fabrik die Kontakt-Objekte und ruft fr diese Laden auf. Anschlieend werden sie dem Telefonbuch hinzugefgt.

12.8.8 Die angepasste Klasse TelefonbuchTxt


Die Klasse TelefonbuchTxt schlielich bekommt nur noch zwei Konstruktoren dazu, einen Standardkonstruktor, der automatisch ein KontaktFabrikTxtObjekt erzeugt und es an den Basisklassenkonstruktor weiterleitet, und einen
interface IKontakt +GetVorname() : String^ +GetNachname() : String^ +GetTelefonnummer() : String^ +SetVorname(ein s : String^) +SetNachname(ein s : String^) +SetTelefonnummer(ein s : String^) +Vergleichen(ein k : IKontakt^) : int +Speichern(ein bw : BinaryWriter^) +Laden(ein br : BinaryReader^)

interface ITelefonbuch +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen(ein k : IKontakt^) : bool +Entfernen(ein i : int) : bool +Sortieren() +Speichern(ein bw : BinaryWriter^) +Laden(ein br : BinaryReader^)

Telefonbuch Kontakt -vorname : String^ -nachname : String^ -telnummer : String^ +GetVorname() : String^ +GetNachname() : String^ +GetTelefonnummer() : String^ +SetVorname(ein s : String^) +SetNachname(ein s : String^) +SetTelefonnummer(ein s : String^) +Vergleichen(ein k : Kontakt^) : int +Speichern(ein bw : BinaryWriter^) +Laden(ein br : BinaryReader^) -liste : list<IKontakt^> -fabrik : IKontaktFabrik -Vertauschen(ein/aus k1 : iterator&, ein/aus k2 : iterator&) +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen(ein k : IKontakt^) : bool +Entfernen(ein i : int) : bool +Sortieren() +GetAnzahl() : int +GetBeginIterator() : iterator +GetEndOperator() : iterator +Speichern(ein b : BinaryWriter^) +Laden(ein b : BinaryReader^) +Telefonbuch(ein f : IKontaktFabrik^) +GetFabrik() : IKontaktFabrik^

TelefonbuchTxt KontaktTxt +KontaktTxt() +Einlesen() +ToString() : String^ +Auflisten() +Hinzufuegen() : bool +Hinzufuegen(ein k : IKontakt^) : bool +Entfernen() : bool +Telefonbuch() +TelefonbuchTxt(ein f : IKontaktFabrik^)

KontaktTxtFabrik +ErzeugeKontakt() : IKontakt^

interface IKontaktFabrik +ErzeugeKontakt() : IKontakt^

Abbildung 12.7 Das Telefonbuch mit Datei-IO

586

Datei-IO

12.8

Konstruktor mit IKontaktFabrik als Parameter, falls jemand weiter ableiten mchte und deshalb eine eigene Fabrik an das Telefonbuch bergeben muss.
ref class TelefonbuchTxt : Telefonbuch { public: TelefonbuchTxt() : Telefonbuch(gcnew KontaktFabrikTxt()) { } TelefonbuchTxt(IKontaktFabrik^ f) : Telefonbuch(f) { } /* Der Rest der Klasse */ };
Listing 12.27 Die angepasste Klasse TelefonbuchTxt

In Abbildung 12.7 sehen Sie die Klassen mit Datei-IO-Funktionalitt als UMLDiagramm.

587

TEIL III .NET-Klassenbibliothek

Dieses Kapitel fhrt in die grundlegenden Klassen der Windows Forms ein und bespricht die ereignisorientierte Nachrichtenbehandlung.

13

Einfhrung in Windows Forms</