Loading [MathJax]/extensions/tex2jax.js
Programmierpraktikum NPDGL I

C++/g++ Referenz

Referenz zum Nachschlagen
Zusammengeh�rigkeiten von C++/g++ Referenz:

Dies ist eine Sammlung der im Praktikum gesammelten Erkenntnisse zur Programmiersprache C++ im Allgemeinen und zum GNU Compiler g++ im Speziellen.

Links


Verwendung von Bibliotheken

Für viele Programmieraufgaben gibt es bereits Lösungen im Internet, die in sogenannten Bibliotheken zusammengefasst sind. Diese lassen sich meistens kostenlos herunterladen, manchmal muss man aber auch erst eine Lizenz kaufen.

Es wird zwischen zwei Arten von Bibliotheken unterschieden:

Reine Header Bibliotheken bestehen nur aus Header-Dateien, die mittels #include befehlen in den eigenen Code eingebunden werden.

Bei vorkompilierten Bibliotheken befindet sich in den Header-Dateien keine Implementierung, sondern nur die Deklaration der Funktionen und Daten. Der eigentliche Code befindet sich in vorkompilierten Archiven, die auf .a oder .so enden. Über diese Archive muss der Compiler mittels der Option

 -lbibliotheksname 

informiert werden, damit er ganz am Ende (in der Linke-Phase), die vorkompilierten Codeteile in das Programm einbauen kann. Ein Beispiel für eine solche Bibliothek ist das MyGrid. Die Header-Dateien befinden hier sich im Verzeichnis include/grid.hh und die Archivdatei lib/libmygrid.a wird aus der Datei lib/libmygrid.cc erstellt. Vergleiche auch mit Makefile Referenz.


Referenzen

Wird eine Variablen in C++ bei ihrer Definition mit einem Ampersand (&) versehen, so wird sie als Referenz gekennzeichnet, d.h. sie referenziert auf eine andere Variable.

Beispiel:

  int variable = 3;
  int &refAufVariable = variable;
  variable = 4;
  std::cout << refAufVariable << std::endl;

gibt die Zahl \( 4 \) aus. Ohne das Ampersand Zeichen, wäre eine Kopie der Variablen angelegt worden, und deswegen die Zahl \( 3 \) ausgegeben worden.

Es gibt zwei Hauptverwendungen für Referenzen:

  1. Zur Vermeidung von unnötigen Kopien großer Objekte
  2. Ändern von Funktionsargumenten ("Return by reference")

Der erste Punkt sollte relativ klar sein, im zweiten Fall geht es darum einen Funktionsaufruf der Art

  op.apply(arg, res);

realisieren zu können, bei dem das Ergebnis der Funktionsauswertung in die Variablen res geschrieben wird. Dazu muss die Variable res als Referenz übergeben werden, d.h. die apply Methode muss von der Art

  void apply(const DofVectorType & arg, DofVectorType & res);

sein.

Siehe auch:
Wrong return values.


Objekt-orientierte Programmierung (Klassen und Objekte)

C++ ist eine objekt-orientierte Programmiersprache, d.h. dass Daten und Funktionen in sogenannten Objekten zusammengefasst werden. Eine detaillierte Beschreibung des Konzepts findet sich in oben verlinktem Wikipedia Artikel. Die wichtigsten Begriffe, mit denen man sich vertraut machen muss sind hier kurz erklärt und mit ein wenig C++ Code veranschaulicht:

cpp_class_syntax

In C++ sieht eine Klasse folgendermaßen aus:

 class JollyJumperClass      // JollyJumper ist der Klassenname
    : public Pferd           // hinter einem Doppelpunkt folgen Klassen,
                             // deren Funktionalit&auml;t und Methoden geerbt
                             // werden
 {
 public:                     // Zugriffs-Spezifizierer: public = &uuml;berall sichtbar
                             //  private = nur in dieser Klasse sichtbar
                             //  protected = nur in dieser und abgeleiteter Klasse sichtbar

    // Konstruktor
    JollyJumperClass (Besitzer & besitzer)
       : besitzer_(besitzer),          // hinter einem Doppelpunkt werden die Daten
         zahnzustand_(10),             // siehe: ganz unten
         besonderheit_("kann sprechen")
    {
      // Dieser Code-Block wird bei der Konstruktion des Objekts ausgef&uuml;hrt.
    }


    // Weiter Methoden
    void sprechen()
    {
    }

 private:
    // Daten
    Besitzer besitzer_;
    int zahnzustand_;
    std::string besonderheit_;
    // std::stack<Erlebnis> wichtige_erlebnisse;
 };

Namenskonvention f�r Klassen und Objekte

Zur besseren Lesbarkeit des Codes wollen wir Klassennamen mit einem Großbuchstaben beginnen, und Objektnamen mit einem Kleinbuchstaben.

Interface - Klassen

Durch die Möglichkeit der Vererbung, kann eine Hierarchie von Klassen erstellt werden. Beim Design dieser Hierarchie kann man mit Hilfe von Interfaces Gemeinsamkeiten von Klassen definieren, die für eine bestimmte Funktionalität benötigt werden. Möchte man beispielsweise eine Methode

  void reiten(Pferd & pferd);

definieren, so müssen alle Daten und Methoden, die zum "reiten" benötigt werden, bereits in der Klasse Pferd definiert sein. Die Methode kann dann beispielsweise auch mit dem Ojbekt jollyJumper aufgerufen werden:

  reiten(jollyJumper);

Hierzu wird das Objekt jollyJumper zuerst auf die Klasse Pferd downgecastet, d.h. seine Funktionalität auf die eines Pferdes eingeschränkt.

Man beachte, dass Interface-Klassen auch abstrakt sein können, was bedeutet, dass einzelne Methoden keine Implementierung haben. In diesem Fall kann kein Objekt dieser Klasse instantiiert werden. Beispiele für solche abstrakte Klassen sind Function und NumericalFluxIf.


Interfaces mittels abstrakter Klassen

Um in C++ ein Interface zu definieren, können sogenannte abstrakte Klassen verwendet werden, welche nicht selbst instanziert werden können. Solche Klassen enthalten Funktionsdeklarationen, die keine Implementierung enthalten, und mit dem Schlüsselwort virtual versehen sind.

Dies kann beispielsweise so aussehen:

 class Gefaehrt
 {
 public:
   virtual double kosten(Ort start, Ort ziel) = 0;
   virtual double zeit(Ort start, Ort ziel) = 0;
 };

Eine Klasse, die von einem solchen Interface abgeleitet wird, muss alle mit = 0 markierten Methoden des Interfaces implementieren. Andernfalls ist die abgeleitete Klasse selbst wieder abstrakt. Das virtual Schlüsselwort sorgt dafür, dass nach einer Umwandlung (cast) in die &Interfaceklasse, weiterhin die Methoden der abgeleiteten Klassen aufgerufen werden. Dies ist praktisch für Funktionen oder Klassen, die nur auf Interface-Methoden zugreifen, wie in diesem Beispiel für eine Funktion, die Reisekosten und Dauer einer Reise in Abhängigkeit des gewählten Gefährts ausgibt:

 void reisekosten(Gefaehrt & gefaehrt, Ort start, Ort ziel)
 {
     std::cout << "Die Reise kostete " << gefaehrt.kosten(start, ziel)
        << " und dauerte " << gefaehrt.zeit(start, ziel) << std::endl;
 };
 ...
 Auto auto;
 Flugzeug linienMaschine;
 Flugzeug privatJet;
 Fahrrad rennrad;
 Fahrrad klapperkiste;
 reisekosten(auto, M&uuml;nster, Berlin);
 reisekosten(linienMaschine, M&uuml;nster, Berlin);
 reisekosten(privatJet, M&uuml;nster, Berlin);
 reisekosten(rennrad, M&uuml;nster, Berlin);
 reisekosten(klapperkiste, M&uuml;nster, Berlin);

Eine weitere Methode in C++ Interfaces zu definieren, sind Templates.

Overhead

durch virtuelle Methoden

Noch zu erledigen:
Abschnitt muss noch geschrieben werden

Verwendung des Pr�prozessors (Header Files)

Es ist sehr unübersichtlich den ganzen Quellcode für ein Programm in eine einzige Datei zu schreiben, deswegen gibt es den sogenannten Präprozessor. Dieser wird vor dem Kompilieren ausgeführt und ersetzt alle Textstellen mit Befehlen die mit dem Hash-Zeichen (#) beginnen.

Der wichtigste dieser Präprozessor-Befehle ist sicherlich ( #include <dateiname>), welcher einfach die Datei mit dem Namen dateiname an dieser Stelle einfügt. Dadurch ist es möglich den Quellcode auf mehrere Header-Dateien aufzuteilen, und diese an den benötigten Stellen zu "inkludieren".

Damit eine Header-Datei nicht versehentlich mehrfach eingefügt wird, sollte jeder Header von einem sogenannten Guard umgeben werden:

Beispiel: Der Headername ist example.hh, dann sähe ein Guard ungefähr so aus.

 #ifndef EXAMPLE_HH_
 #define EXAMPLE_HH_

 // here something happens

 #endif // end of guard EXAMPLE_HH_

Was passiert hier?

  1. #ifndef EXAMPLE_HH_ überprüft ob das sogenannte Makro EXAMPLE_HH_ definiert wurde. Nur wenn dies nicht der Fall ist, wird der Text zwischen #ifndef und #endif eingefügt.
  2. In diesem Fall wird auch in der zweiten Zeile das Makro EXAMPLE_HH_ definiert, welches also verhindert, das der Inhalt der Datei zweimal inkludiert werden kann.
Achtung:
Die Namen der Guards sollte für alle verwendeten Header eindeutig sein.
Zu beachten:
Der Präprozessor kann auch ohne den Kompiler verwendet werden. Der entsprechende Befehl heißt cpp.


Oft auftretende Compiler-Fehlermeldungen

In diesem Abschnitt findet sich eine Liste häufiger g++ Fehlermeldungen mit Lösungsvorschlägen.


Unbekannter member oder komische Zeichen werden erwartet


Expected semicolon


Wrong class name


No matching function found


Oft auftretende Laufzeit-Fehler

In diesem Abschnitt findet sich eine Liste häufiger Fehler, die beim Ausführen eines C++ Programms auftreten.


Wrong return values

Integer division