Hallo und herzlich willkommen. Nach dem kleinen Intermezzo über Texteditoren steigen wir mit diesem Beitrag wieder voll in C++ ein und folgen dem Pfad meiner primären Programmiersprache.

Diesmal geht es um das Grundgerüst, das du in jedem C++-Programm wiederfinden wirst.

Anatomy eines C++-Programms

Vielleicht erinnerst du dich noch an unser erstes gemeinsames „Hello World!“. Das möchte ich nun gemeinsam mit dir genauer betrachten. Es kann zwar nicht mehr als die Zeichenkette „Hello World!“ ausgeben, hat aber die fundamentalen Bestandteile eines C++-Programms.

Das Ziel ist zu verstehen, was in jeder Zeile geschieht und welche Zeilen ein funktionierendes Programm zwingend erfordert.

// Preprocessor Directive
#include <iostream>

// The Body - Start of the Program
// main() Function
int main()
{
    // Output
    std::cout << "Hello World!" << std::endl;

    // Return Value
    return 0;
}

// listing1: HelloWorld.cpp

Du findest den Quellcode auch in meinem Gitlab Repository zu dieser Serie an Blogbeiträgen.

Preprocessor Directive

Du kannst den Quellcode in zwei funktionale Bereiche einteilen. Der erste Teil sind die Anweisungen an den Präprozessor. Du erkennst sie an dem voran gesetzten #. Ihnen schließt sich beginnend mit main() der Korpus des Programms an.

Wie du sicherlich richtig vermutest, ist der Präprozessor ein Werkzeug, das vor der eigentlichen Kompilierung läuft. Präprozessoranweisungen sind Befehle an den Präprozessor und beginnen immer mit dem Rautenzeichen #.

Nein, ein #Hashtag ist was ganz anderes!

Der Befehl #include <filename> weist den Präprozessor an, den Inhalt der Datei, in unserem Fall <iostream>, zu übernehmen und in die Zeile einzufügen. <iostream>ist eine Headerdatei aus der C++ Standard Library, die die Verwendung von z.B. std::cout ermöglicht. Mit anderen Worten, der Compiler versteht std::cout erst, nachdem der Präprozessor die Definition von std::cout in den Quellcode aufgenommen (aufnehmen - engl. to include) hat.

Der Name der hinzuzufügenden Datei wird bei Dateien aus Standardbibliotheken in < > Klammern gesetzt. Möchtest du allerdings eigene Dateien einfügen, wird der Name mit relativem Dateipfad in " " geschrieben.

#include "../relative/path/filename"

// listing2: include own header files

Der Body - Beginn des Programms

Nach den Präprozessoranweisungen folgt der Korpus des Programms. Der Body besteht aus deinen Funktionen und ist durch die main() Funktion charakterisiert. Diese markiert den Einstieg jedes Programms. Das bedeutet, dass die Ausführung jedes Quellcodes an dieser Stellen beginnt. Egal, wie viele Funktionen du auch davor schreibst.

In den C++ Core Guidelines ist als Standard festgelegt, dass der main() Funktion ein int vorausgeht. int steht für Integer und bezeichnet den Datentypen des Return Values der Funktion.

In einigen C++ Applikation wirst du auch eine andere Variante der main() Funktion finden (siehe listing3).

int main (int argc, char* argv[])
{
    return 0;
}

// listing3: main() function parameters

Dies geht auch mit dem Standard konform. Hier werden lediglich in den Klammern noch zwei Parameter übergeben. Mit Hilfe der beiden Argumente kannst du dem Benutzer deines Programms ermöglichen die Applikation mit zusätzlichen Command Line Befehlen zu starten.

$ yourProgrmm --MachDies --TuDas

// listing4: calling an executable with arguments

Damit sagst du dem Betriebssystem deinProgramm zu öffen und der main(int argc, char* argv[]) dabei die zwei Parameter MachDies und TuDas zu übergeben.

Zurück zu unserem Codebeispiel aus listing1. Nach der Deklaration der main() Funktion folgt die Definition, eingeleitet durch eine geschwungene Klammer {.

cout steht für console out und ist die Anweisung, den String Hello World in der Konsole auszugeben. std::cout ist eine Funktion aus der C++ Standard Library und nutzt dessen Namespace std.

Der Datentyp von std::cout ist ein Stream. Und du siehst in dieser Zeile, wie der Text Hello World in diesen Stream mit dem Operator << eingefügt wird. Die Anweisung std::endl beendet eine Zeile. Du kannst es als einen Carriage Return der guten alten analogen Schreibmaschine verstehen, der an den Zeilenanfang der Textseite springt. Bedenke, dass du jede Einheit (Entity) mit dem Operator << einzeln in einem Stream einfügen musst.

Bestimmt ist dir aufgefallen, dass die Zeilen innerhalb der main() Funktion mit einem Semikolon ; abschließen. Der Compiler benötigt diese Zeichen, damit er weiß, dass deine Anweisung hier endet. Vergisst du das Semikolon bei einem Statement einer Funktion, wird dein Programm nicht fehlerfrei kompiliert.

Return Value

Eben haben wir gesehen, dass die main() Funktion eine Integer-Zahl als Return Value hat. Der Wert wird an das Betriebssystem zurückgegeben und dient als Status dafür, ob ein Programm ordnungsgemäß beendet wurde oder ob ein Fehler das Programm ungewollt beendet hat.

Normalerweise wird der Wert 0 für den erfolgreichen Ablauf und -1 als Error-Indikator verwendet. Da der Return Value dem Datentypen Integer entspricht, kannst du jede beliebige Ganzzahl verwenden. Damit hast du die Flexibilität, viele verschiedene Zustände von Erfolg oder Fehlerschlag zurückzumelden.

In vielen Fällen wird eine Applikation von einer anderen Applikation aufgerufen. Die beiden stehen dann in einer Parent-Child Beziehung zueinander. Der Return Value zeigt der Parent Applikation an, ob die Child Applikation ihre Aufgabe erfolgreich erledigt hat.

Wichtig ist noch zu wissen, dass C++ case-sensitive ist. Das bedeutet, dass Groß- und Kleinschreibung erkannt und unterschiedlich ausgewertet werden. Deshalb wird dir der Compiler mit Sicherheit Fehler melden, wenn du Int anstellen von int oder Std::Cout anstatt std::cout schreibst.

Kommentare

So, nun haben wir alle Zeilen des Quellcodes besprochen… aber halt, was ist mit denen, die mit // beginnen und Text in gesprochener Sprache folgt?

Das sind Kommentare.

Kommentare werden vom Compiler während des Build-Prozesses ignoriert und beeinflussen die Ausgabe des Programms nicht. Sie sind also nicht für eine vollständig funktionierende Applikation notwendig. Du kannst sie allerdings dazu verwenden, Anmerkungen oder Erklärungen an den entsprechenden Stellen deines Codes zu ergänzen. Und damit andere verstehen können, was du meinst, schreibst du einen Kommentar bestenfalls in einer von Menschen natürlich lesbaren Sprache.

C++ unterstützt zwei Arten von Kommentaren.

// This is a comment; the compiler will ignore it

/* This is a comment
and it spans two lines */

// listing5: comment styles

// markiert den Beginn eines Kommentars, der bis zum Ende der Zeile gültig ist. Möchtest du längeren Text schreiben, der über eine Zeile hinausgeht, musst du an einer geeigneten Stelle unterbrechen und in der folgenden Zeile wieder beginnend mit // weiterschreiben.

Alternativ kannst du auch die zweite Art von Kommentaren verwenden. Du beginnst deinen Text mit /* und alles folgende ist auskommentiert. Auch über Zeilenenden hinaus. Der Kommentar wird erst durch */ wieder geschlossen.

Dir mag es seltsam erscheinen, deinen Code zu erklären, doch je größer ein Programm wird oder je größer die Anzahl der beteiligten Programmierer, die an einem bestimmten Modul arbeiten, desto wichtiger ist es, leicht verständlichen Code zu schreiben. Kommentare helfen dir zu dokumentieren, was und warum es in dieser Weise getan wird.

Doch du solltest den Einsatz von Kommentaren gut überlegen, denn die Übersicht und Verständlichkeit deines Quellcodes können darunter leiden.

Füge Kommentare hinzu, die die Funktionsweise komplizierter Algorithmen und komplexer Teile deines Programms erklären. Schreibe in einem einfachen, verständlichen Stil. Aber wiederhole dich nicht und beschreibe nicht das Offensichtliche.

Kommentare sind nicht dazu gedacht, unnötig komplizierten und unverständlichen Code zu retten. Sie sind eher eine sinnvolle Ergänzung. Du bist also nicht davon entbunden, guten Code zu schreiben.

Und vergiss nicht, bei Änderungen auch deine Kommentare zu aktualisieren.

Der Namespace

Ich hatte eben schon einmal erwähnt: Der Grund, warum std::cout benutzt wurde, ist, dass sich die Stream Klasse cout im Namespace std befindet.

Doch was bedeutet das?

Stelle dir vor, cout wäre ein Name, der häufig von anderen Entwicklern für ihre Funktionen verwendet würde. Du hast zwei Libraries in deinem Programm eingebunden und beide beinhalten eine Funktion mit diesem Namen. Woher weiß der Compiler nun, welche der beiden du verwenden möchtest? Dies führt zu einem Konflikt und die Kompilierung schlägt natürlich fehl.

Genau für diesen Fall ist ein Namespace nützlich.

Mit Hilfe von Namespaces kannst du Teilen deines Codes Namen geben und solche Konflikte vermeiden. Durch den Aufruf von std::cout weist du den Compiler an, diesen einen eindeutigen cout zu verwenden, der im std Namensraum verfügbar ist.

Vielen ist es zu umständlich, ständig den Namespace Specifier zu schreiben, wenn sie wiederholt auf diese oder weitere Funktionen einer Bibliothek mit Namespace zugreifen. Darum kannst du auch am Anfang des Bodys deines Programms den Namensraum bekannt machen.

// Preprocessor Directive
#include <iostream>

// Body
// namespace
using namespace std;

// main() function with parameters
int main(int argc, char* argv[])
{
    // Output
    cout << "Hello World!" << endl;

    // Return Value
    return 0;
}

//  listing6: namespace

In listing5 wird dem Compiler mitgeteilt, dass wir den Namespace std für Klassen und Funktionen aus der C++ Standard Library verwenden möchten. Namensräume sind ebenso wie die Funktionen durch die Präprozessoranweisung bekannt. Jetzt brauchst du nicht mehr jedesmal, wenn du std::cout oder std::endl verwendest, den Namespace beifügen.

Das soll hängenbleiben

Uff! Da haben wir aber einiges besprochen. Doch stresse dich nicht, alles sofort behalten zu müssen. Einige Dinge wurden auch nur angesprochen und wir werden uns diese ein anderes Mal genauer betrachten.

Das solltest du auf jeden Fall aus diesem Beintrag mitnehmen:

  • Ein C++ Programm besteht aus den Preprocessor Directives und dem Body
  • Der Body beinhaltet die main() Funktion und damit den Enstieg in den Programmablauf
  • Die main() Funktion sollte immer einen Integer als Statusmeldung zurückgeben
  • C++ ist case-sensitive

Zudem hast du jetzt eine Vorstellung davon, was ein Namespace ist, kennst den Unterschied zwischen einem //, sowie /* Kommentar und weißt, wie du einen Text auf der Konsole ausgeben kannst.

Ich wünsche dir maximalen Erfolg!


Quellen

  • [1] B. Stroustrup, A Tour of C++. Pearson Education, 2. Auflage, 29. Juni 2018.
  • [2] B. Stroustrup, Programming: Principles and Practice Using C++. Addison Wesley, 2. Auflage, 15. Mai 2014.
  • [3] U. Breymann, Der C++ Programmierer. C++ lernen – professionell anwenden – Lösungen nutzen. Aktuell zu C++17. München: Carl Hanser Verlag GmbH & Co. KG; 5. Auflage, 6. November 2017