Objekte zur Ein- und Ausgabe
Neben dem internen Ablauf, dem Anlegen und Bearbeiten von Variablen, ist die Kommunikation des Programms mit seiner Umgebung ein wichtiges Element der Programmierung. Wir wollen zuerst einen Blick auf die gewohnte C-Umgebung werfen. Dabei diskutieren wir Probleme mit "printf()" und stellen die Lösung mit Ein- und Ausgabeklassen vor.
Ein- und Ausgaben bei C
Der Benutzer kommuniziert mit Hilfe der Tastatur und dem Bildschirm. Das Programmiert arbeitet mit den Dateien, in denen die benötigten Daten stehen, und beliebigen anderen Geräten, wie Drucker oder Maus. Der Erfolg von C beruht neben der Eleganz der Sprache auch auf der umfangreichen Standardbibliothek, die viele fertige Routinen u. a. zur Ein- und Ausgabe beinhaltet.
Diese Sammlung von Routinen hat einen beachtlichen Umfang angenommen. Manche Compiler liefern bis zu 500 Routinen in der Standardbibliothek mit. Viele allgemein verwendbare Funktionen braucht der Programmierer nicht mehr selbst schreiben. Die Routinen sind notwendig, da C und C++ im Gegensatz zu anderen Sprachen, wie z. B. Pascal, keine Schlüsselworte zur Ein- und Ausgabe kennt. Dies ist auch vernünftig, da es sich dabei nicht um eine Aufgabe der Sprache, sondern um eine Aufgabe des Betriebssystems handelt.
Zusätzlich zu den Routinen der Standardbibliothek erwartet ein C-Programm bestimmte Dienstleistungen durch die sogenannte Programmumgebung. C-Programme bestehen nur aus Funktionen. Auch "main()" ist eine Funktion. Die Umgebung muß also das C-Programm aufrufen. Eine wichtige Dienstleistung sind die vor Beginn eines Programms geöffneten Standardkanäle stdin, stdout und stderr. Mit diesen Kanälen arbeiten viele Funktionen der Bibliothek.
Die wohl bekannteste Funktion zur Ausgabe dürfte "printf()" sein. Im Buch von Kernighan und Ritchie (K&R) über die Sprache C stellen die Autoren das bekannte Hello world Beispiel vor. Die Funktion printf() gibt dabei einen kurzen Text aus. Es ist ein sehr einfaches Beispiel. Aber wenn es abläuft, hat man zumindest den Compiler und den Editor richtig bedient.
|
Objekte zur Ein- und Ausgabe Seite 117 |
Schauen wir uns das Originalbeispiel in einer leicht verbesserten Form an. Mit der #include-Anweisung lesen wir in das Programm die Informationsdatei stdio.h mit ein. Sie enthält alle Funktionsdeklarationen, die Prototypen, der Standard-Ein- und Ausgabe-Funktionen sowie einige nützliche symbolische Konstante.
Mit printf() kann man Variable aller vordefinierten Datentypen ausgeben.
|
Seite 118 Einsteigerseminar C++ |
Dazu benötigt printf() einen Steuertext als ersten Parameter, der angibt, wie die nachfolgenden Parameter auszugeben sind.
In dem obigen Beispiel (Bild 8-2) hat sich der Programmierer offensichtlich vertippt. Die "printf()"-Anweisung sollte vermutlich zwei Werte und einen Text ausgeben. Anstelle des für Formatelemente notwendigen %-Zeichens hat der Programmierer die Taste daneben, das &, erwischt. Und der Compiler kann diesen Fehler nicht finden, obwohl nun die Parameteranzahl nicht stimmt. Schöner wäre es, wenn der Compiler einen solchen Tippfehler finden würde.
Damit haben wir als erstes Problem die mangelnden Fehlererkennung gefunden. Ein zweites ist die Auswertung des Steuertextes zur Laufzeit. Alle Auswertungen zur Laufzeit kosten Zeit. Hier bleibt zwar der Aufwand in Grenzen. Trotzdem wäre es günstiger, wenn zur Übersetzungszeit festgestellt werden könnte, wie ein Wert ausgegeben werden soll. Und noch ein drittes Problem gibt es mit "printf(). Da erst zur Laufzeit entschieden wird, wie ein bestimmter Parameter auszugeben ist, muß printf() vorsorglich alle möglicherweise notwendigen Konvertierungsroutinen mit einbinden. Erst zur Laufzeit wird ja bei der Interpretation des Steuertextes klar, daß eine int-Zahl in Hex auszugeben ist oder eine Fließkommazahl mit Exponentendarstellung. Die printf()-Funktion ist daher zusammen mit den ganzen Konvertierungen recht groß.
|
Objekte zur Ein- und Ausgabe Seite 119 |
Diesen drei Problemen versuchte man mit Hilfe einer Klassenbibliothek zu begegnen. Sie wird in der Informationsdatei iostream.h für normale serielle Aus- und Eingaben und fstream.h für Dateiverbindung beschrieben. Wir haben damit im Bild 8-3 das Hello world-Beispiel umgeschrieben.
Standardobjekte: cout, cin, cerr
Für die Ein- und Ausgabe wurden bei C++ eigene Klassen definiert. Für die Eingabe wird istream benutzt, für die Ausgabe ostream. Sollten mit den E/A-Objekten auch Zuweisungen durchgeführt werden, kann man die abgeleiteten Klassen ostream_withassign und istream_withassign verwenden.
Mit diesen Klassen werden automatisch drei Objekte angelegt, die die Aufgabe der bisherigen Kanäle übernehmen. Die Objekte sind außerhalb der "main()- Funktion vordefiniert. Sie heißen cout, cin und cerr. cout ist dabei an die Stelle von stdout getreten, cin ersetzt stdin und cerr wird statt stderr verwendet. In den Klassen sind eine Vielzahl von Operatorfunktionen definiert, die die Symbole << und >> überlagern.
Die Ausgabe ruft mit Hilfe des Symbols << eine Methode der Klasse von cout. Diese Methode führt dann die eigentliche Ausgabe durch.
|
Seite 120 Einsteigerseminar C++ |
In der Klassendefinition liest sich das wie im Bild 8-4. Für alle vordefinierten Datentypen, nicht nur für die im Bild gezeigten, wird das Operatorsymbol << überlagert.
Die Implementierungsdetails unterscheiden sich derzeit noch zwischen den Bibliotheksversionen. Für den Anwender sollten jedoch die grundlegenden Arbeitsweisen gleich bleiben. Zumindest die Ausgabe von Hello world. sollte wie beschrieben auf allen Compilern ablaufen. In einem späteren Kapitel kommen wir noch einmal auf die Spezialitäten zurück.
Die Probleme, die wir bei "printf()" gefunden haben, scheinen mit der Definition der ostream- und der istream-Klassen überwunden. Nun kann der Compiler zur Übersetzungszeit ermitteln, welche Ausgaben gewünscht werden. Tipp- (oder besser Typ-) Fehler werden erkannt. Die Interpretation des Steuertextes entfällt damit. Aus der Menge der überlagerten Operatorfunktionen wählt er an Hand des Typs des Parameters die benötigte Funktion aus. Und es werden nur die wirklich verwendeten Ausgabe- und Konvertierungsroutinen später hinzu gebunden.
Werfen wir noch einen Blick auf die Größe des erzeugten Codes. Leider ist hier der Wunsch nach kompakten Programmen nicht unbedingt in Erfüllung gegangen. Die ersten Sprachversionen hatten eine kompakte E/A-Bibliothek, die tatsächlich zumeist kürzeren Code ergaben. Die Bibliothek nach dem Standard ist wieder umfangreicher geworden.
Arbeiten mit den Standardobjekten
Wollen Sie die Standardobjekte verwenden, benötigen Sie die angegebene Informationsdatei. Sie enthält die Klassendefinitionen, die extern-Vereinbarungen für die Standardobjekte und die Definitionen der immer noch gültigen Konstanten, wie NULL oder EOF. Sollten Sie sich an dieser Stelle die Informationsdatei(en) ansehen, dann werden Sie einige, noch nicht behandelte Möglichkeiten von C++ sehen. Wir werden den Aufbau der E/A-Klassenbibliothek noch einmal im Kapitel 15 betrachten, wenn wir die Vererbung besprochen haben.
Innerhalb einer Funktion dürfen Sie ohne weitere Vereinbarungen die Standardobjekte benutzen. Rechts vom <<-Operator dürfen Variable und Konstante aller vordefinierter Typen angegeben werden. Da Texte (engl. strings) in C als Felder aus char verstanden werden, benötigt die Textausgabe als Typ einen Zeiger auf char.
|
Objekte zur Ein- und Ausgabe Seite 121 |
Und so sieht das dann am Bildschirm aus:
In der Zeile 4 (Bild 8-5) holen wir die Informationsdatei für die E/A-Klassen. Danach legen wir in der Zeile 6 einen kurzen Text an, dem ein großes G am Anfang fehlt. In "main() können wir nun cout benutzen. Die erste Ausgabe ist eine Stringkonstante. Der Compiler ermittelt hier den Typ Zeiger auf char. Aufgerufen wird daher die Operatormethode operator <<, die einen Zeiger auf char akzeptiert.
|
Seite 122 Einsteigerseminar C++ |
Gebunden wird die Operatorfunktion wieder an das links stehende Objekt, an "cout". In der Zeile 11 verwenden wir den Operator mehrfach. Derartige Ketten entsprechen der normalen C-Syntax. Denken Sie nur an eine Kette von Additionen oder Zuweisungen. In der Kette finden wir zwei Texte und eine int-Konstante. Die Zahl wird standardmäßig mit der kleinstmöglichen Stellenzahl ausgegeben. Die zur Trennung benötigten Leerzeichen wurden den beiden Texten hinzugefügt.
In der Zeile 12 wird schließlich eine Zeilenschaltung, ein einzelnes Zeichen und direkt daran der globale Text ausgeben.
Statt cout könnte man auch cerr für den Fehlerkanal nehmen. Die gewohnten Dienstleistungen der Aufrufumgebung, wie Umleitung der Standardkanäle, bleiben auch bei der Ausgabe mit Objekten erhalten.
Im Bild 8-7 wurde "cin" und cout zusammen verwendet. Die überlagerten Winkel zeigen einmal zum Objekt; dann ist es die Ausgabe und es wird etwas zum Objekt gesendet. Bei cin handelt es sich um Eingaben. Die Winkel zeigen vom Objekt weg zu einer Variablen. Hier holen wir etwas vom Objekt ab. Die Eingabe liefert übrigens nur das erste Wort der Eingabezeile. Ein Begrenzer (engl. delimiter) beendet die Eingabe. Leerzeichen, Tabulatoren oder Zeilenschaltungen sind Begrenzer.
|
Objekte zur Ein- und Ausgabe Seite 123 |
Aufbau einer Operatorfunktion
Die überlagerten Operatorfunktionen für die Symbole << und >> sind alle sehr ähnlich aufgebaut. Sie akzeptieren einen Parameter eines bestimmten Typs und liefern ein Objekt der Klasse zurück, zu der sie gehören (ostream oder istream).
Wie auch bei der Zuweisung aus dem vorhergegangenen Kapitel wird der Rückgabewert nicht immer verwendet.
Die Operatorfunktion wird an cout gebunden, d. h. sie kann während des Ablaufs auf die Eigenschaften von cout zugreifen. Am Ende liefert sie das einzige ostream-Objekt zurück, das die Operatorfunktion kennt: cout. Zur schnellen Rückgabe wird die Referenzmethode verwendet. Im Falle von cin wird ein istream-Objekt zurückgegeben.
Wie im Falle der fortgesetzten Addition oder der wiederholten Zuweisung wird der Rückgabewert erst bei mehrfacher Anwendung des Ausgabeoperators nützlich. Das per Referenz zurückgegebene Objekt "cout" kann im nächsten Verarbeitungsschritt weiter verwendet werden. Mit Hilfe der Anweisung return *this in den Operatorfunktionen wird cout von einem Verarbeitungsschritt zum nächsten weitergereicht.
|
Seite 124 Einsteigerseminar C++ |
Hinweise zur Weiterarbeit
1) Schreiben Sie die Klassen "ratio" und "Zeile" so um, daß sie statt "printf()" nur noch Ausgaben mit Objekten vornehmen.
2) Was würde passieren, wenn das Ergebnis des Typwandlungsoperators ("cast") statt "void *" ein "int" wäre? Wie würde sich dann das Objekt bei einer Ausgabe mit "cout" verhalten? Gibt es eine Definition von "<<" für die Ausgabe von Zeigern? (siehe fstream.h / iostream.h)
3) Schreiben Sie eine Klasse "Bildschirm", deren Methoden den Bildschirm steuern können (Löschen, Positionieren, Farben einstellen,...). Verwenden Sie in den Methoden die Ausgabe mit Objekten ("cout") und die ANSI-Steuersequenzen (aus den Betriebssystem-Beschreibungen / Hilfedateien).
|
Objekte zur Ein- und Ausgabe Seite 125 |
Im nächsten Kapitel
Diese elegante Methode für die Ausgabe wollen wir nun im nächsten Kapitel auf selbst definierte Klassen anwenden. Dies ist mit den bisher besprochenen Möglichkeiten nicht möglich.
Die Operatorfunktionen werden an das links vom Symbol stehende Objekt gebunden. Die Methode muß also bei der Klasse ostream definiert sein. Da aber der Autor von ostream nicht ahnen konnte, welche Klassen wir erfinden werden, und wir in die Klasse nicht mehr eingreifen können, ist eine Überlagerung des Ausgabeoperators für selbst definierte Klassen nicht möglich. In solchen Fällen helfen die befreundeten Funktionen des folgenden Kapitels.
|
Seite 126 Einsteigerseminar C++ |