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.


Rahmen1

Mit “printf()” kann man Variable aller vordefinierten Datentypen ausgeben.


Rahmen3


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.

Probleme mit “printf()”

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ß.


Rahmen5


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.


Rahmen7


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


Rahmen9

Und so sieht das dann am Bildschirm aus:


Rahmen11

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.


Rahmen13

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).


Rahmen15

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++


opcoutcpp

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

opoutrei.JPG Im nächsten Kapitel

opoutreigem

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++