home *** CD-ROM | disk | FTP | other *** search
-
- (( Listings: Zeilenumfang ))))))))))))))))))
-
- ,,&TIMETEST.CPP 42
- ,,&TIMECLSS.H 88
- ,,&TIMECLSS.CPP 94
- ,,&BUG1.CPP 34
- ,,&BUG2.CPP 45
- ,,&BUG3.CPP 57
- ,,&BUG4.CPP 57
-
-
- ,,dHöhere Weihen für C-Programmierer
- ,,uC++, die Krone der Schöpfung
- ,,avon Thole Groeneveld
-
- ,,eDie Programmiersprache C++ ist
- Anfang der achtziger Jahre bei den
- AT&T Laboratorien von Bjarne
- Stroustrup entwickelt worden. Sie
- stellt eine Weiterentwicklung von C
- dar, mit dem Ziel, syntaktische
- Ungereimtheiten zu beseitigen sowie
- dem Anfänger den Einstieg durch
- einfachere Ein- und Ausgabefunktionen
- zu erleichtern. Außerdem sollte die
- Fehlersuche durch verbesserte
- Typenüberprüfung vereinfacht werden.
- C++ bietet also nicht nur erweiterte
- Funktionen sondern darüber hinaus im
- bisher bekannten Sprachumfang die
- elegantere Möglichkeit, große
- Programm-Projekte zu gestalten.
-
- ,,gW,,neiter, schneller, höher. Diese
- Schlagworte beziehen sich in
- zunehmenden Maße nicht nur auf
- Sportliche Leistungen, sondern sind
- symptomatisch für die Compiler-
- entwicklung der letzten Jahre. C++
- bietet jedoch nicht nur mehr, sondern
- präsentiert altgewohntes dem Anfänger
- wesentlich mundgerechter. Eine
- einfachere Ein- und Ausgabe machen
- beispielsweise komplizierte Format-
- anweisungen gänzlich überflüssig.
- Zeichenketten können über sogenannte
- "Streams" bequem und geräteunabhängig
- zwischen Modulen ausgetauscht und
- ausgegeben werden.
- C++ ist keine neue Sprache, sondern
- versteht sich vielmehr als Obermenge
- von C. Im Prinzip läßt sich jedes C
- Programm von einem C++ Compiler
- übersetzen. Da der C++ Compiler eine
- sehr strenge Typenüberprüfung
- vornimmt, werden sich die wenigsten C
- Programme jedoch ohne "Warnings"
- compilieren lassen.
-
- ,,zSicherheit durch Prototyping
- ,,nDiese genaue Typenüberprüfung
- und auch das Autoprototyping sorgen
- dafür, daß durch schlampiges
- Programmieren entstandene und im
- allgemeinen schwer lokalisierbare
- Fehler erst gar nicht auftreten.
- Autoprototyping bedeutet, daß die
- Typen der Übergabe-- und Rückgabe-
- parameter vor dem Aufruf der Funktion
- bekannt sein müssen.
-
- ,,zVon SIMULA zu C++
- ,,nC++ ist aber nicht nur ein ver-
- bessertes C, sondern beinhaltet in
- letzter Konsequenz eine ganz andere
- Programmierphilosophie, das objekt-
- orientierte Programmieren. Dieses
- Konzept wurde erstmals für die 1967 in
- Norwegen entwickelte Sprache SIMULA
- benutzt, die, wie der Name schon sagt,
- hauptsächlich zur Programmierung von
- Simulationen verwendet wird. Leider
- blieb SIMULA und damit auch die OOP -
- Technik lange relativ unbekannt. Das
- änderte sich erst durch das Aufkommen
- von SMALLTALK in den siebziger Jahren.
- Dabei erregte aber weniger die
- Programmiersprache als die aufwendig
- mit einer Maus bedienbare graphische
- Benutzeroberfläche die Aufmerksamkeit
- der Öffentlichkeit. Außerdem sind die
- meisten SMALLTALK-Systeme so rechen-
- und speicherintensiv, daß sie Work-
- stations mit einigen MIPS und Mega-
- bytes brauchen und damit für die
- Mehrheit der einfachen PC-Benutzer
- uninteressant bleiben.
-
- ,,zHybridsprachen
- ,,nWirklich populär wurden objekt-
- orientierte Sprachen erst durch die
- sogenannten Hybridsprachen. Darunter
- versteht man herkömmliche Programmier-
- sprachen wie Pascal und C, die um
- objektorientierte Sprachelemente
- erweitert wurden.
- Der große Vorteil liegt darin, daß der
- Neuling keine vollkommen neue Sprache
- lernen muß und ihm damit der Einstieg
- erleichtert wird. Zudem sinkt die
- Effizienz, auf die bei der
- Implementierung eines C++ Compilers
- sehr großen Wert gelegt wird, kaum im
- Vergleich zu herkömmlichen Sprachen.
- Diese Tatsache wird besonders für
- Hersteller von professioneller Soft-
- ware wichtig sein und die Entscheidung
- für ihren Umstieg auf C++ erleichtern.
-
- ,,zWieso, weshalb, warum?
- ,,nDoch was ist überhaupt objekt-
- orientiertes Programmieren und braucht
- man so etwas überhaupt?
- Sicherlich macht es keinen Sinn,
- kleine übersichtliche Programme durch
- den geheimnisvollen Anflug des
- gefürchteten Zeitgeistes nun
- objektorientiert zu programmieren.
- Wirklich effizient ist dieses Konzept
- erst bei größeren und unübersicht-
- lichen Programmen. Ziel ist es, daß
- komplexe Gesamtproblem in einzelne
- Einheiten, sogenannte Objekte, auf-
- zuspalten. Diese Objekte bearbeiten
- dann völlig selbstständig die ihnen
- zugeteilte Aufgabe und kommunizieren
- mit ihrer Umgebung über eine genau
- dokumentierte Schnittstelle.
-
- ,,zModularität durch Objekte
- ,,nIn einem solchen Objekt sind nicht
- nur die Funktionen, sondern auch die
- von ihnen manipulierten Daten
- zusammengefaßt. Diese Daten stellen so
- etwas wie den inneren Status des
- Objektes dar. Normalerweise sollte auf
- sie nur über den Umweg einer Funktion
- zugegriffen werden, um fehlerhafte
- Angaben abfangen zu können. C++
- erlaubt aber aus Effizienzgründen auch
- den direkten Zugriff. Deklariert
- werden Objekte in einer sogenannten
- Klassendeklaration. Der Programmierer
- legt hier die verwendeten Variablen
- und ihre Typen fest. Funktionen, die
- diese Daten manipulieren sowie deren
- Übergabe-- und Rückgabeparametern
- werden ebenfalls hier festgelegt.
- Daten und Funktionen können außerdem
- noch gegen Zugriff von außerhalb
- geschützt werden. Gegen brutalen
- Pointerzugriff bewahren diese
- Mechanismen zwar nicht, doch wer auf
- geschützte Objekte direkt zugreift
- weiß entweder was er tut oder sollte
- die Finger von der objektorientierten
- Programmierung lassen.
-
- ,,zDie Privatsphäre von Objekten
- ,,nAuf Daten und Funktionen, denen das
- Schlüsselwort "public" voransteht,
- kann von außen zugegriffen werden. Sie
- sind die Schnittstelle, durch die sich
- das Objekt mit der Außenwelt
- unterhält. Alles, was nach dem
- Schlüsselwort "private" steht, bleibt
- in der Privatsphäre des Objektes, zu
- der niemand anders als die Mitglieder
- des Objektes Zutritt haben.
- Zusätzlich darf eine Klassen-
- deklaration Konstruktoren und
- Destruktoren enthalten. Konstruktoren
- werden zu Beginn der Lebenszeit von
- Objekten automatisch aufgerufen. Sie
- garantieren eine korrekte
- Initialisierung der Objekte.
- Destruktoren werden automatisch am
- Ende der Lebenszeit, also beim
- Verlassen einer Funktion, aufgerufen.
-
- ,,zMüllabfuhr im Speicher
- ,,nDestructoren beseitigen praktisch
- den übrig gebliebenen Speichermüll,
- den das Objekt während seiner Lebens-
- zeit angesammelt hat. Durch das
- freigeben angeforderter System-
- resourcen wird so etwas wie eine
- halbautomatische Garbage Collection
- durchgeführt.
- Als einfaches Beispiel für
- Konstruktoren, Destruktoren,
- öffentliche und private Bereiche habe
- ich die Klasse Time geschrieben.
- Darüber hinaus zeigt sie die
- Möglichkeiten von C++ zur Ein- und
- Ausgabe von Objekten, den Gebrauch von
- sogenannten statischen Daten und
- Funktionen und die Definition von
- arithmetischen und vergleichenden
- Operatoren.
-
- ,,zVon Freunden und Mitgliedern
- ,,nObjekte der Klasse Time speichern
- Uhrzeiten ab und können diese mit
- Hilfe ihrer Member- und Friend-
- funktionen manipulieren. Als Daten
- besitzt Time drei Integervariablen zur
- Speicherung von Stunde, Minute und
- Sekunde. Diese Daten sollen gleich zu
- Beginn ihrer Lebenszeit mit korrekten
- Werten initialisiert werden. Dazu
- werden im öffentlichen Teil der Klasse
- drei Konstruktorfunktionen mit dem
- Klassennamen "Time" deklariert. Sie
- unterscheiden sich nur durch ihre
- Parameter, die in Klammern bei der
- Definition der Variablen im Programm-
- text angegeben werden. Für einen C++
- Compiler ist es übrigens kein Problem,
- zwischen gleichnamigen Funktionen zu
- unterscheiden. Lediglich der
- Funktionstyp und die Anzahl der
- Übergabeparameter müssen
- unterschiedlich sein.
- Man bezeichnet diese Methode auch als
- "Überladen von Funktionen".
-
- ,,zFamilienidylle im Speicher
- ,,nDie erste der drei beschriebenen
- Konstruktorfunktionen hat keine
- Parameter, sondern weißt den drei
- Variablen Defaultwerte zu. Aus diesem
- Grund heißt ein solcher Konstruktor
- auch Defaultkonstruktor. Die zweite
- Konstruktorfunktion hat drei Parameter
- für Stunde, Minute und Sekunde. Dabei
- sind die letzten beiden Parameter
- optional und können bei der Definition
- von Variablen der Klasse Time weg-
- gelassen werden. Der Compiler setzt
- die in der Funktionsdeklaration
- angegebenen Defaultparameter ein. Die
- dritte Konstruktorfunktion schließlich
- verwendet zur Initialisierung eine
- bereits vorhandene Variable von Typ
- Time, die der Funktion als Referenz
- übergeben wird.
-
- ,,zHaben sie Referenzen?
- ,,nStroustrup hat den Referenztyp neu
- in die Sprachdefinition eingeführt. Er
- entspricht in etwa der VAR Deklaration
- der Übergabeparameter in einem PASCAL
- Programm. Dabei wird der Funktion
- nicht die Variable sondern ein
- versteckter Zeiger auf sie übergeben.
- Durch die Verwendung eines Zeigers
- entsteht besonders bei größeren
- Objekten ein deutlicher Zeitgewinn.
- Allerdings bleiben Manipulationen an
- dem übergebenen Objekt auch nach der
- Beendigung der Funktion bestehen. Um
- diese zu verhindern, wurde bei der
- Deklaration der Übergabeparameter das
- Schlüsselwort "const" hinzugefügt. Der
- Destruktor einer Klasse ist durch eine
- Tilde (~) und dem Klassennamen
- gekennzeichnet. In unserem Fall hat er
- nichts weiter zu tun als einen
- lapidaren Kommentar von sich zu geben.
- Man könnte ihn auch weglassen, aber
- dann hätten wir ja kein Beispiel mehr
- für unseren Destructor.
-
- ,,zMit der Zeit gehen
- ,,nEin Mitglied (Memberfunktion) der
- Klasse Time stellt die Funktion
- ,,kvoid Time::SetTo (int, int, int)
- ,,n dar. Ihre Aufgabe ist es, während
- des Programmablaufes die Werte von
- Stunde, Minute und Sekunde zu ändern.
- Wenn nun beispielsweise "t" ein Objekt
- der Klasse Time ist, so würde der
- Aufruf ,,kt.SetTo (12,30); ,,n t.hour
- auf 12, t.min auf 30 und t.sec auf 0
- setzen. Ein direkter Zugriff auf diese
- Variablen ist nicht möglich, da sie im
- privaten Bereich der Klasse Time
- deklariert wurden. Einem alten C Hasen
- mag diese Art des Funktionsaufrufes
- ein wenig fremd vorkommen.
- Verständlicher wird ihm die Sache
- vielleicht, wenn man die interne
- Interpretation des Ausdrucks offen-
- bart. Die Funktionsdeklaration wird
- dabei zu ,,kvoid SetTo (Time*
- this,int,int,int) ,,n und der
- Funktionsaufruf zu ,,kSetTo
- (&t,12,30); ,,n
-
- ,,zDer this-Operator
- ,,nUnserer Funktion wird ein
- impliziter Zeiger auf das Objekt als
- erstes Argument übergeben. Dieser
- Zeiger, auf den der Programmierer
- durch das Schlüsselwort "this"
- zugreifen kann, ermöglicht die
- Manipulation von Daten in statischen
- Funktionen wie CheckFlag und
- CheckParam. Als Konsequenz daraus
- können statische Funktionen keine
- Daten verändern, die speziell ein
- bestimmtes Objekt betreffen. Ihnen ist
- es nur möglich, auf sogenannte
- Klassenvariablen zuzugreifen.
- Klassenvariablen sind allen Objekte
- einer Klasse gemeinsam zugänglich. Die
- Klasse Time hat eine Klassenvariable
- vom Typ TimeFlag. Sie gibt nach einer
- mathematischen Operation zwischen zwei
- Objekten an, ob die Operation korrekt
- verlaufen ist oder ob es einen Über-
- oder Unterlauf gegeben hat. Time::flag
- muß extra noch einmal global definiert
- (Klassendeklarationen stellen keinen
- Speicherplatz bereit!) und
- initialisiert werden.
-
- ,,zObjektorientierte Parameterprüfung
- ,,nCheckParam überprüft nur die ihr
- übergebenen Parameter darauf hin, daß
- sie in einem zulässigen Bereich
- liegen. Dabei bedient es sich keiner
- in der Klassendeklaration benutzten
- Variablen. ChekParam wurde als
- statische Funktion deklariert, da
- solche Funktionen wegen des fehlenden
- impliziten Arguments schneller sind.
- Ein wirklich interessantes Feature ist
- die Möglichkeit von C++ zum Überladen
- von mathematischen Operatoren.
-
- ,,zÜberladen von Operatoren
- Time benutzt zwei arithmetische
- Operatoren zur Summen- und
- Differenzbildung und mehrere
- Vergleichsoperatoren. Bei allen diesen
- Funktionen, die nichts anderes sind
- als selbstdefinierte Operatoren,
- handelt es sich um "friend" -
- Funktionen. Sie sind kein Bestandteil
- der Klasse, haben aber die volle
- Zugriffsberechtigung auf den privaten
- Teil der ihnen übergebenen Objekte.
- Jede mit der Klasse Time befreundete
- Operatorfunktion besitzt zwei
- Parameter. Der erste bezeichnet das
- Objekt, das in einem Ausdruck links
- vom Operator steht, während der zweite
- für den rechts vom Operator stehenden
- Ausdruck gilt.
- Den Ausdruck "t3 = t1 + t2;" wandelt
- der Compiler also um in "t3 = operator
- + (t1, t2);" und ruft die ent-
- sprechende Funktion auf.
- Selbst der Ausdruck "t3 = 12 - t1;"
- wird akzeptiert.
-
- ,,zImplizite Typenkonversation
- ,,nDies mag zuerst verwundern, ver-
- langt doch die Deklaration anstatt der
- Zahl "12" den Typ "const Time&". Der
- Compiler gibt an dieser Stelle jedoch
- nicht auf.
- Er versucht vielmehr, einen zu der
- Klasse Time gehörigen Konstruktor zu
- finden, der eine Integerzahl in ein
- Objekt der Klasse Time verwandeln
- kann. Da es diesen in der Tat gibt,
- wird der Compiler eine solche Code-
- zeile ohne zu meckern korrekt
- übersetzen. Diese Technik der Typ-
- umwandlung nennt sich implizite Typen-
- konversion. Etwas verdächtig sieht
- auch die Deklaration der Funktion
- ,,kConvertToSeconds,,n aus, an deren
- Ende das Schlüsselwort "const" steht.
- Es sagt aus, daß der Programmcode des
- Funktionskörpers keinerlei
- Veränderungen an den Daten des Objekts
- vornehmen darf. Sein Status bleibt
- also konstant. Der tiefere Sinn für
- diese Deklaration liegt in dem Aufruf
- der Funktion in den Operator-
- funktionen. Diese dürfen selbst die
- Ihnen übergebenen Objekte der Klasse
- Time nicht verändern. Rufen diese
- Objekte aber eine ihrer Member-
- funktionen auf, so könnten sie ihre
- Daten verändern. Aus diesem Grunde
- erlaubt der Compiler den Operator-
- funktionen nur solche Funktionen
- anzuwenden, die den Status der Time-
- objekte auf keinen Fall verändern.
-
- ,,zErsatz für printf und scanf
- ,,nDie Ein- und Ausgabe hat sich in
- C++ gründlich geändert und ver-
- einfacht. Man ist nicht mehr
- ausschließlich auf komplizierte und
- fehleranfällige Formatanweisungen
- angewiesen. Als Alternative werden die
- sogenannten "streams" (=Ströme)
- angeboten, die den Datentransport
- zwischen den Variablen und den I/O -
- Objekten regeln. Fließen die Daten von
- einer Variable zu einem I/O - Objekt,
- so ist das eine Ausgabe und anders-
- herum eine Eingabe. Die in der
- Includedatei <iostream.h> definierte
- Variable cout ist ein Objekt der
- Klasse ostream_withassign und für die
- Ausgabe zuständig. Dazu hat die Klasse
- ostream_withassign (bzw. ihre
- Elternklasse ostream) einige "<<"
- Operatoren für die gängigsten Daten-
- typen überladen.
- Der Compiler sucht sich dann die
- passende Operatorfunktion heraus. Das
- Interessante an diesen Funktionen ist,
- daß beliebig viele auch vom Typ
- verschiedene Variablen aneinander
- gehängt werden können. Möglich wird
- diese Technik durch den Rückgabe-
- parameter der Funktionen, der per
- Referenz stehts auf cout verweist.
- Dabei wird die Funktion, die den
- nächsten Datentyp behandelt, als
- Ausgabeobjekt weiterverwendet.
- Cout wird also praktisch von einer
- Funktion zur nächsten weiter gereicht.
-
- ,,zVereinfachte Eingabefunktion
- ,,nFür die Eingabe gilt im Prinzip das
- Obengesagte. Als Operator wird ">>"
- statt "<<" verwendert, um die
- umgekehrte Richtung des Datenflußes zu
- verdeutlichen.
- In unserer Beispielklasse Time sind
- natürlich auch die I/O - Operatoren
- nach dem gleichen Schema überladen.
- Damit wird eine wesentlich komfort-
- ablere und einheitliche Ein- und
- Ausgabe erhalten. Dazu wurden zwei
- friend Operatorfunktionen deklariert,
- die als ersten Parameter eine Referenz
- auf das Ein- bzw. Ausgabeobjekt und
- als zweiten eine Referenz auf ein
- Objekt vom Typ Time haben. Rückgabe-
- parameter ist die Referenz auf den
- ersten Eingangsparameter, also das I/O
- - Objekt.
- Der Ausgabeoperator benutzt die
- Streammanipulatorfunktion setw(int),
- um die minimale Feldbreite für die
- nächste Ausgabe auf zwei zu setzen.
- Eventuell auftretende Leerstellen
- werden durch eine '0' aufgefüllt. Der
- Eingabeoperator liest Stunde, Minute
- und Sekunde zunächst in temporäre
- Variablen ein. Die istream Member-
- funktion get() filtert Trennzeichen
- zwischen den einzelnen Eingaben
- heraus. Der Aufruf des Konstruktors
- Time::Time(int,int,int) sorgt dafür,
- daß das einlesende Timeobjekt auf
- jeden Fall korrekte (wenn bei fehler-
- hafter Eingabe auch nicht die
- gewünschten) Werte erhält.
-
- ,,zMit Inline-Code gegen Overhead
- ,,nUm den Overhead des Funktions-
- aufrufes zu verringern, wurden einige
- Funktionen als inline definiert. Sie
- müssen dann allerdings mit in das
- Includefile hinein geschrieben werden.
- Der Compiler baut sie dann, solange
- sie nicht zu groß werden, wie Makros
- aber mit voller Typenüberprüfung in
- den Programmtext ein. Der Vorteil der
- höheren Geschwindigkeit wird aber
- durch den Nachteil des größeren
- Programmcodes erkauft. Es liegt also
- am Programmierer, Schwerpunkte zu
- setzten.
- Leider compilierte der Turbo C++
- Compiler die Timeklasse nur, wenn bei
- den Compileroptionen das "Out-of-line
- Inline Functions" Flag gesetzt wird.
- In diesem Fall werden alle Inline-
- funktionen wie ganz normale Funktionen
- aufgerufen, um das Programm leichter
- debuggen zu können. Setzt man dieses
- Flag nicht, steigt der Compiler beim
- Übersetzen der Ausgabeoperatorfunktion
- mit einer falschen Fehlermeldung aus.
-
- ,,zBugs im Turbo C++
- ,,nGrund dafür ist die Stream-
- manipulatorfunktion ,,ksetw(int),,n
- aus <iomanip.h>. Diese Funktion hat
- als Rückgabeargument ein Objekt der
- Klasse smanip_int, die den "<<"
- Operator mit den Übergabeparametern
- ostream& und smanip_int& und dem Rück-
- gabeparameter ostream& überladen hat.
- Auf diese Weise sollte sich die
- Funktion setw(int) schön in die Kette
- der Ausgabevariablen einfügen lassen.
- Der Ausgabeoperator von smanip_int
- gibt keine Zahl aus, sondern bringt
- das ihm übergebene Ausgabeobjekt
- (cout) dazu, die minimale Feldbreite
- auf das Argument von setw zu setzen.
- Wie gesagt, das alles passiert nur,
- wenn das entsprechende Compilierflag
- gesetzt ist.
- Grund des Fehlers ist aber nicht die
- Klasse smanip_int und auch nicht
- ostream(_withassign). Es scheint
- vielmehr ein Bug im Compiler zu sein,
- der dafür sorgt, daß dieser nicht mit
- der Zeilenexpansion zurecht kommt.
- Abhilfe kann die Verwendung der
- Memberfunktion width(int) schaffen.
-
- ,,zProbleme mit Zeilenexpansion
- ,,nStatt "os << setw(2) << t.hour <<"
- schreibt man dann "os.width(2); os <<
- t.hour <<"
- Das sieht zwar nicht so elegant aus,
- erfüllt aber auch seinen Zweck. Eine
- andere Möglichkeit wäre bei der
- Übersetzung des Implementationsmoduls
- TIMECLSS. CPP auf die Zeilenexpansion
- zu verzichten und sie bei der
- Compilierung von TIMETEST.CPP wieder
- einzuschalten. Die dritte Möglichkeit
- besteht darin, mit einer "#pragma
- Option -vi-" Direktive die Expansion
- kurzfristig für den Ausgabeoperator
- von Time auszuschalten und danach
- wieder einzuschalten. Leider
- unterstütz Turbo C++ im Augenblick
- diese #pragma-Option noch nicht.
-
- ,,zBorlands Compiler
- ,,nZum Turbo C++ bleibt ansonsten noch
- zu sagen, daß ich den Compiler für ein
- hervorragendes Werkzeug zur objekt-
- orientierten Programmierung halte. In
- der vorliegenden Form wird er sicher-
- lich die Primitivimplementation der
- OOPS-Technik von Turbo Pascal (kein
- automatischer Aufruf von Konstruktoren
- und Destruktoren, keine privaten und
- öffentlichen Bereiche, kein Überladen
- von Funktionen und Operatoren usw.
- usw.) entweder ins Abseits drängen
- oder Borland dazu anregen, Turbo
- Pascal mit ähnlichen Features aus-
- zustatten.
- (us)