Workshops / Hintergrundberichte / Buchvorstellungen
---------------------------------------------------

1. Workshop: Programmierung in C++ - Teil 1 (von Thomas Richter)

1. Anstelle einer Einführung: Wozu C++?

Nun ja, vor allen Dingen ist C++ eine Programmiersprache. Meistens hört man
bei C++ auch gleich Dinge wie "objektorientiert" - und üblicherweise gehört
C++ zum guten Ton, will man sich als Programmierer verdingen. Der Name
"C++" ist eigentlich auch schon ein Wortspiel: C++ wurde von Bjarne
Stroustrup als Nachfolger der Programmiersprache C entwickelt und das
angehängte "++" bedeutet in C sowie auch in C++: "Erhöhe um eins". "C++"
ist damit "C+1", also das nächste "C". Der Name "C" wiederum ist ein
ähnlicher Witz, ist doch C die Fortentwicklung von B, und B geht wiederum
auf die Programmiersprache BCPL zurück.

Warum erzähle ich das alles? Nun, sowohl C als auch BCPL spielen eine große
Rolle im Betriebssystem des Amiga. Fast alle Komponenten des OS sind in C
geschrieben, mit Ausnahme des DOS - das sind die Befehle und Programme im
Verzeichnis C: und was dazu gehört. Diese wurden zunächst in BCPL
geschrieben. Einige kleinere Teile schließlich sind in Assembler.

Üblicherweise folgt dann bei Einführungen in C++ an dieser Stelle etwas wie
"Warum Objektorientierung?", es folgen Aufzählungen von Vorteilen dieser
Programmiertechnik bei großen Projekten, die von mehreren Entwicklern
betreut werden, es folgen Designregeln, wie man so etwas richtig
aufzieht... und nach weiteren zwei Seiten langweilt sich der Leser zu Tode.

Also machen wir's anders: Hinein in die Praxis! All' das Zeug soll am
"lebenden Objekt" studiert werden.


2. Vor die Freude haben die Götter...

Ja, und da geht das Problem schon los. Für C++ benötigt man einen sog.
"Compiler", der ein menschenlesbares - jedenfalls behaupten Programmierer,
es sei menschenlesbar - Quell-Programm in denjenigen Code übersetzen soll,
den der Rechner auch wirklich versteht. Für den Amiga einen brauchbaren
C++-Compiler zu finden, ist leider nicht so leicht. Zumindest einige
Alternativen habe ich finden können:

SAS C

Der SAS ist der klassische C-Compiler für den Amiga. Das sieht man allein
daran, dass der Großteil des OS eben mit dem SAS compiliert wurde.
Qualitativ einer der besten Compiler, wird er aber leider nicht mehr weiter
entwickelt. Die neueren Versionen des SAS C beinhalteten auch eine
"Annäherung" an einen C++-Compiler. Annäherung deshalb, weil die
Unterstützung für C++ eher mau und recht unvollständig ausfällt. Wenn man
einen SAS zuhause hat, wird er für unsere Beispielprogramme ausreichen.
Mehr aber auch nicht. Zu kaufen gibt's ihn eh' nicht mehr.

Storm C 3.0

Dieser Compiler unterstützt zumindest den Großteil des C++-Sprachumfanges,
wenn auch manchmal mit einigen Macken. Die Version ist nicht ganz
taufrisch, aber der Compiler findet sich auf der Developer-CD 2.1, die man
für sinnvolles Arbeiten auf dem Amiga sowieso mal braucht. Beim Testen des
Compilers fielen einige Programmfehler auf - sog. "Hits" - die auf schlecht
getesteten Code hindeuten. Nun ja, für unsere Zwecke dennoch ausreichend.
Je nach Geschmack ist die graphische Benutzeroberfläche entweder hilfreich,
oder einfach ein Klotz am Bein. "Real Men" werden natürlich einen von der
Shell aus bedienbaren Compiler vorziehen, für Einsteiger kann die
Oberfläche aber hilfreich sein. Ich werde hier aufgrund der leichten
Verfügbarkeit erst mal beim Storm C 3.0 bleiben.

Gnu C++ 2.95

Der GNU C++ ist der von der Free Software Foundation bereitgestellte
Compiler. Er ist auf allen möglichen Plattformen, inklusive PC und Amiga,
verfügbar. Allein von der Compilerleistung lässt sich GNU C++ kaum
übertreffen und ist dem SAS ebenbürtig. Der Sprachumfang entspricht
detailgenau dem Standard und auch sonst gäbe es wenig zu klagen, wäre der
Compiler in einem benutzbaren und installierbaren Zustand. Leider wird nur
der fortgeschrittene Experte einen GNU C++ auf dem Amiga installieren und
benutzen können, und einige Stunden für die Installationsarbeit wird man
schon rechnen müssen. GNU ist eben "expertenfreundlich".

Storm C 4.x

Als Compiler verbirgt sich dahinter der GNU C++, allerdings hat man ihm
eine bedienbare Oberfläche spendiert. Insofern gibt es in Puncto
Compilerleistung bestimmt nichts zu meckern. Ich besitze den Storm 4 nicht
und kann deswegen keine Aussagen über die Qualität der Oberfläche machen,
aber will man schon einen C++ kaufen, so ist dies hier wohl die richtige
Wahl. Sehr viele andere Optionen gibt's so wie so nicht.


3. Ein erstes C++-Progrämmchen

Allein aus Traditionsgründen gehört es sich, als allererstes Programm ein
"Hallo Welt" zu schreiben - das Programm soll nichts weiter anstellen, als
diesen Text auf den Bildschirm auszugeben.

Bleiben wir also erst mal beim Storm C 3.0: Die Installation von CD erfolgt
problemlos und soll hier nicht beschrieben werden. Ein Doppelklick auf das
Storm-C-Programmicon startet dann auch schon die Oberfläche.

Als erstes müssen wir ein neues Projekt aufsetzen: Dazu wähle man im
"Projekt"-Menü den Menüpunkt "Neu". Im daraufhin erscheinenden Fenster
markiere man zunächst "C++ Programm" durch Klicken auf das zugehörige
Feld, denn so ein Programm wollen wir ja schreiben. Oben rechts neben
"Projekt-Schublade" wähle man mit dem Dateirequester noch einen geeigneten
Aufbewahrungsort für das Projekt, als Name wählen wir einfach mal "Hallo".
Danach dieses Fenster mit "OK" verlassen.

Das Projekt ist jetzt erstellt, muss aber noch geöffnet werden - das
geschieht hier leider nicht automatisch. Also im "Projekt"-Menü den Punkt
"Öffnen..." auswählen, und dort die Projektdatei heraussuchen. Sie sollte
"Hallo.P" heißen, mit einem spiegelverkehrten "P". Leider muss man hier
eventuell etwas suchen, der Requester zeigt nicht unbedingt die richtige
Schublade an. Danach erhält man ein erst mal recht leeres Fenster, in das
die Bestandteile des Programmes eingefügt werden müssen.

Wieso Bestandteile, gibt's denn mehr als einen? Ja, doch! Die Sprache C++
verfügt über keine eingebauten "Befehle" wie etwa BASIC oder ARexx. Einem
C++-Programm muss alles beigebracht werden, und sei es nur das Drucken
eines Textes auf den Bildschirm. Zum Glück gehört zum Lieferumfang eines
Compilers eine sogenannte "Standardbibliothek", die vorgefertigte Lösungen
für derlei Aufgaben bereitstellt. Ach ja, unser Code ist natürlich auch ein
Bestandteil des Programmes, wie könnte es anders sein?

Aus dem "Projekt"-Menü wähle man also nun "Datei(en) hinzufügen". Daraufhin
erscheint ein Dateirequester, in den man als Dateinamen "Hallo.cpp"
eintrage. Nein, diese Datei gibt's noch nicht, aber wir werden sie gleich
erstellen; folgerichtig fragt die Compileroberfläche auch nach, ob diese
Datei erstellt werden soll. Soll sie, das wird ja das Hauptprogramm. Als
Zweites müssen wir noch die Standardbibliothek hinzufügen: "Projekt"-Menü,
dort "Bibliothek(en) hinzufügen" auswählen. Als Bibliothek wählen wir die
"storm.lib", sie enthält alles notwendige. Über die Bedeutung weiterer
Bibliotheken braucht jetzt kein Wort verloren werden, dazu später mehr.
Dieses Projekt sollte man nun mittels Speichern auf Platte sichern.

Ein Doppelklick auf "Hallo.cpp" öffnet endlich einen Editor; hier kommt der
Programmcode hinein - das wurde auch Zeit. Folgendes bitte genau so in
dieses Fenster eintippen:


/* Ein allererstes klitzekleines Progrämmchen */

// Das folgende bringt dem Compiler einige übliche
// Methoden bei, unter anderem das Drucken auf den Bildschirm.
// Die dazugehörigen Funktionen befinden sich in der "storm.lib"

#include <iostream.h>

// und jetzt zur ersten Programmfunktion: Sie heißt "main" - und muss
// so heißen. Sie wird immer als allererstes aufgerufen.
// Mehr dazu im Artikel.

int main(int argc,char **argv) {
        // gibt einen Text auf dem Bildschirm aus.
        // Das erledigt das cout-Objekt für uns.

        cout << "Hallo, Welt!\n";

        return 0;
}
// und das war's auch schon, Leute.


Auch diesen Text sollte man jetzt auf Platte abspeichern. Jetzt zum
eigentlichen Kompilieren: Im Menü "Kompilieren" findet man den Menüpunkt
"Alle Übersetzen..."; nach Auswahl dieses Menüpunktes ist der Rechner erst
einmal ein kleines Weilchen beschäftigt. Mit "Projekt starten" aus dem
gleichen Menü darf man nun seine Arbeit bewundern: Es sollte sich ein
Fenster öffnen, in dem "Hallo, Welt!" steht, und sonst nichts. Das Fenster
kann man mit dem Schließknopf links oben auch wieder loswerden.

Falls das nicht klappt: Keine Panik! Vermutlich wird dann der Compiler
einige Fehlermeldungen generieren. Durch Doppelklick im daraufhin
erscheinenden Ausgabefenster des Compilers gelangt man an die fehlerhafte
Stelle im Quelltext. Hier muss man sich ganz genau ansehen, ob alles
wirklich richtig geschrieben ist; ein fehlendes Semikolon wird genauso
beanstandet wie eine fehlende Klammer! Üblicherweise sollte man sich auch
einige Zeilen über oder unter der beanstandeten Zeile umsehen, den
Tippfehler korrigieren und den Compiler neu anwerfen.


4. Präprozessor, Compiler, Linker

Nach diesem ersten Test - der ging doch erfolgreich über die Bühne, oder
nicht? - sollte man sich auch fragen, was da eigentlich passiert ist.
Hier nun eine Erklärung des Programmes Zeile für Zeile:

Die erste Zeile ist einfach nur ein Kommentar. Sie wird beim Übersetzen
schlichtweg ignoriert. Genauer gesagt, alles was zwischen einem "/*" und
einem "*/" steht, wird ignoriert, selbst wenn mehrere Eingabezeilen
zwischen diesen beiden Symbolen stehen. Hier ist es nur eine. Genau das
gleiche gilt für die nächsten drei Zeilen: Der doppelte Querstrich "//"
leitet auch einen Kommentar ein, der geht aber jeweils nur bis zum
Zeilenende, im Gegensatz zu den mit "/*" erzeugten Kommentaren.

Was soll das? Warum schreibt man offensichtlich Daten in den Quellcode, die
der Compiler so wie so ignoriert? Zugegeben, bei dem kleinen Programm wäre
es egal gewesen, aber Projekte aus dem wahren Leben eines Programmierers
gehen über mehrere tausend Zeilen, wenn nicht mehr, und da vergisst man
schon mal, was man sich mit diesem oder jenem Programmteil so gedacht hat.
Gute Kommentare an den wichtigen Programmstellen helfen dann weiter, wenn
man den entsprechenden Programmcode seit Wochen oder Monaten nicht mehr
angesehen hat.

Die folgende Zeile mit dem "#" ist genau genommen eigentlich gar keine C++-
Anweisung. Hierbei handelt es sich statt dessen um eine Kontrollzeile für
den sogenannten "Präprozessor". Das ist ein vor dem Compiler auflaufender
Makro-Prozessor, dessen eigentliche Aufgabe nur darin besteht,
Wortersetzungen auszuführen und den Compiler letztendlich mit Eingabedaten
zu füttern. So ist es die Aufgabe des Präprozessors, schon vorsorglich alle
Kommentare zu entfernen - der Compiler braucht sie sowieso nicht. Ferner
sucht der Präprozessor nach allen Zeilen, die mit diesem Doppelkreuz ("#")
beginnen, denn das sind Kontrollanweisungen für ihn und nicht für den
Compiler.

Die #include-Anweisung fügt nun den Inhalt der angegebenen Datei - hier
also den Inhalt von "iostream.h" - an die Stelle in den Quellcode ein, an
der diese Anweisung steht. Mit anderen Worten: Dem Compiler wird ein
Eingabetext serviert, bei dem diese Zeile fehlt, stattdessen ist der
gesamte Inhalt der Datei von "iostream.h" dahin getreten, genau so, als
hätte man ihn mit dem Editor dort von Hand eingefügt.

Warum das denn bloß? Wie gesagt, es gibt in C++ keine eingebauten Befehle,
die irgend etwas auf den Bildschirm drucken. Irgendwo her muss der Compiler
aber trotzdem wissen, wie das geht. Die zugehörigen Definitionen hierfür
werden in "iostream.h" gemacht. Die entsprechenden Funktionen, die
letztendlich die Arbeit ausführen, befinden sich dann in der oben schon
erwähnten "storm.lib". Da das eine ganze Menge sind, will man sie nicht
jedesmal neu übersetzen müssen und darum enthält "iostream.h" zwar die
notwendigen Details, wie mit diesen Funktionen umzugehen ist, aber nicht
die Funktionen selbst.

Mit dem Präprozessor kann man noch andere nette Dinge - oder auch viel
Unheil - anrichten; dazu später mehr.

Die nächsten drei Zeilen sind wieder Kommentare. Daraufhin folgt die
Definition einer Funktion. Wenn man sich noch düster an den
Mathematikunterricht erinnert, ist eine Funktion etwas, in das man Zahlen
hineinsteckt, die dann in ihrem Inneren verarbeitet werden, so dass dann
ein Ergebnis hinauspurzelt. Das ist bei C++ nicht viel anders, nur dass die
Argumente nicht nur Zahlen sein dürfen, und dass das Resultat einer
Funktion auch nicht nur eine Zahl zu sein braucht.

Der Resultattyp einer Funktion steht ganz links auf der Zeile, die die
Funktion definiert. Hier ist es ein Resultat vom Typ "int" - das ist
einfach eine ganze Zahl. Der Funktionsname folgt, und lautet schlicht
"main". Jedes C++-Programm benötigt eine Funktion dieses Namens mit genau
diesem Resultat, denn diese Funktion wird beim Programmstart aufgerufen und
muss alles beinhalten, was das Programm ausmacht. Normalerweise würde man
natürlich in "main" andere Funktionen verwenden und nicht einfach das ganze
Programm dort hineinschreiben.

In den Klammern hinter "main" stehen die Argumente, hier sind es zwei:
Eines heißt "argc", das andere "argv". Die Namen sind irrelevant und wir
brauchen diese Argumente auch überhaupt nicht. Trotzdem bekommen wir sie
übergeben. Wenn man dieses Programm von der Shell aufrufen würde, könnte
man hier die Kommandozeilenargumente herauslesen, doch das brauchen wir
nicht. Dementsprechend will ich mir hier auch verkneifen zu erklären, was
ein "char **" ist. "argc" ist hingegen wieder ein int, also einfach eine
ganze Zahl. Sie würde mit der Anzahl der Programmargumente gefüllt werden.

Hinter dieser Zeile folgt eine geschweifte Klammer ("{"). Zwischen ihr und
dem entsprechenden Gegenstück weiter unten steht der eigentliche Rumpf der
main-Funktion. Dieser Programmcode wird ausgeführt, wenn "main()"
aufgerufen wird - und da eben "main" zur Ausführung des Programmes
aufgerufen wird, ist das unser Programm. Die folgenden Zeilen sind wieder
einfach nur Kommentare.

Danach folgt die einzig wirklich wichtige Anweisung in diesem Programm:
Die in Anführungszeichen eingeschlossene Zeichenkette wird mittels "<<" in
das cout-Objekt "hineingeschoben". Das "cout" kommt - wie sollte es anders
sein - aus der Datei "iostream.h". Dort wird dem Compiler auch erklärt, wie
man da eine Zeichenkette hineinschiebt. Wichtig für uns ist nur, dass
solche Zeichenketten wirklich auf dem Bildschirm ausgegeben werden. Alles,
was in "cout" hineinwandert, purzelt dort hinaus.

In der Zeichenkette fällt noch ein "\n" auf, das wir auf dem Bildschirm gar
nicht gesehen haben. Oder doch? Nun, "\n" ist ein Sonderzeichen und bewirkt
einen Zeilenvorschub, ähnlich einem Druck auf die Return-Taste. Das "\n"
dabei bedeutet soviel wie "newline". Es gibt noch einige andere dieser
Sonderzeichen, etwa "\t" für die Tabulator-Taste, oder "\b" für die
Backspace-Taste.

Das Semikolon hinter der Zeichenkette schließt diese Anweisung ab. Es muss
dort stehen, denn es hält Anweisungen auseinander.

Und schließlich gibt's noch ein "return 0;" zu besprechen: Dessen Bedeutung
ist leicht zu erkennen: Es beendet die Funktion und liefert als
Rückgabewert eine Null. Mit dieser Null passiert hier nichts besonderes;
sie ist nur dann von Bedeutung, wenn das Programm von der Shell aus
gestartet werden würde. Dann wäre dies der Rückgabewert an die Shell, wobei
andere Werte als Null im Allgemeinen als Fehler interpretiert werden: Je
größer, desto schlimmer!


5. Und was ist nun mit Objektorientierung?

Na ja, genaugenommen noch nichts! Oder nicht viel! In diesem Progrämmchen
haben wir nur ein einziges Objekt verwendet, und das ist "cout". Das
nächste Mal wollen wir aber wirklich Objekte selbst erstellen und
verwenden.

Thomas Richter <thor@math.TU-Berlin.DE>