<< Inhalt >> Workshops Inhalt

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>

<< >>