Die Übersichtlichkeit der Seite wird durch Javascript erhöht. Ist dies aktiviert, werden die Texte unter den Überschriften durch Anklicken der Überschriften ein- und ausgeblendet.
Um aus einem C++-Quellcode ein lauffähiges Programm zu erzeugen, muss es zum ein kompiliert und zum anderen gelinkt werden. Beides wird durch das Komando:
g++ quellcodeName.cpp
erreicht. In diesem Fall wird aus der angegeben Quellcodedatei ein Programm mit dem Namen a.out erzeugt.
Mit dem Standardbefehl werden nicht alle Warnungen angezeigt. Da diese allerdings in den meisten Fällen sehr hilfreich sind, sollte die Option -Wall mit angegeben werden. Der Befehl lautet dann.
g++ -Wall quellcodeName.cpp
Gegebenen falls noch mehr Warnungen mit der zusätzlichen Option -Wextra.
g++ -Wall -Wextra prog.cpp
Um den Namen des Programms abweichend von a.out festzulegen wird die Option -o genutzt:
g++ quellcode.cpp -o Wunschprogrammname
Hat man mehrere Dateien, möchte mann diese ggf. unabhängig von einander kompilieren und später verlinken. Um g++ anzuweisen, eine Quellcode-Datei nur zu kompilieren und damit eine quellcodename.o Datei zu erstellen gibt es die Option -c
g++ -c quellcode.cpp -o quellcode.o
Um grafische programme mit der gtkmm-Bibliothek zu erstellen, benötigt man als ergänzende Option `pkg-config gtkmm-3.0 --libs --cflags`
g++ -Wall `pkg-config gtkmm-3.0 --libs --cflags` -o Programmname
Um grafische Beniutzeroberflächen mit gtk3 zu erstellen wird der Code wie folgt kompiliert:
g++ -Wall `pkg-config gtk+-3.0 --libs --cflags`
Es ist zu empfehlen in der C++-Programmierung nicht alles in eine Datei zu schreiben. Zum einen ist es möglich auf diese Weise bestimmten Code auch in anderen Programmen auf einfache Weise wiederzuverwenden, zum anderen muss wegen einer kleinen Änderung an einer Stelle nicht das komplette Programm neu kompiliert werden.
Ich will ein Beispiel zeigen, wie ein solches aufgeteiltes Programm aussehen kann und wie es dann mit Hilfe von make auf einfache Weise kompiliert werden kann.
Bibliothek mit der Klassendeffinition
Eine Header-Datei, die nichts außer der Definiton einer Klasse, mit Ihren Variablen und den Funktionsprototypen sowie den Konstruktorfunktionen beinhaltet. Diese Datei wird später in der Datei mit der main-Funktion per include Eingefügt, ähnlich dem Einbinden der C++-Standard-Header, wie beispielsweise <iostream>, nur dass statt der spitzen Klammern Anführungszeichen gesetzt werden.
rational.h
#ifndef rational_h #define rational_h rational_h class Rational { long zaehler, nenner; public: Rational(); Rational(long z, long n); Rational(long); long getZaehler() const; long getNenner() const; void add(const Rational& r); void sub(const Rational& r); void mul(const Rational& r); void div(const Rational& r); void def(long zaehler, long nenner); void eingabe(); void ausgabe(); void kehrwert(); void kuerzen(); }; inline Rational::Rational() : zaehler(1), nenner(1){} inline Rational::Rational(long z, long n) : zaehler(z), nenner(n){} inline Rational::Rational(long N) : zaehler(N), nenner(1){} inline long Rational::getZaehler() const{return zaehler;} inline long Rational::getNenner() const{return nenner;} Rational add(const Rational& a,const Rational& b); Rational sub(const Rational& a,const Rational& b); Rational mul(const Rational& a,const Rational& b); Rational div(const Rational& a,const Rational& b); #endif
Datei mit den Funktionsdefinitionen
Hier werden jetzt alle Funktionen, deren Prototypen in der Klasse bekannt gemacht wurden deffiniert. Die Methoden (Klassen-Funktionen) werden durch voranstellen des Klassennamens gefolgt von 2 Doppelpunkten gekennzeichnet.
Diese Datei wird nirgends includiert, includiert allerdings ihrerseits die Datei mit der Klassendeffinition. Um Sie in das Programm einzubinden, wird der Quellcode zunächst mit der Option -c nur kompiliert und die daraus entstehende Objektdatei mit der Endung .o wird später mit der Datei, die die main-Funktion enthält verlinkt. Das hat den Vorteil, dass diese Datei bei Änderungen am Hauptprogramm nicht erneut kompiliert werden muss.
#include "rational.h" #include <iostream> #include <cassert> #define cerr std::cerr #define cout std::cout #define cin std::cin #define endl std::endl void Rational::add(const Rational& r) { zaehler = zaehler * r.nenner + r.zaehler * nenner; nenner = nenner * r.nenner; kuerzen(); } void Rational::sub(const Rational& r) { zaehler = zaehler * r.nenner - r.zaehler * nenner; nenner = nenner *r.nenner; kuerzen(); } void Rational::mul(const Rational& r) { zaehler = zaehler * r.zaehler; nenner = nenner + r.nenner; kuerzen(); } void Rational::div(const Rational& r) { zaehler = zaehler * r.nenner; nenner = nenner * r.zaehler; kuerzen(); } void Rational::def(long z, long n) { zaehler = z; nenner = n; assert(nenner != 0); kuerzen(); } void Rational::eingabe() { cerr << "Zähler: "; cin >> zaehler; cerr << "Nenner: "; cin >> nenner; assert(nenner !=0); kuerzen(); } void Rational::ausgabe() { cout << zaehler << '/' << nenner; } void Rational::kehrwert() { long temp = zaehler; zaehler = nenner; nenner = temp; assert(nenner != 0); } long ggt(long a, long b) { long rest; while (b >0) { rest = a % b; a = b; b = rest; } return a; } void Rational::kuerzen() { // Vorzeichen merken und Betrag bilden int vorzeichen = 1; if (zaehler < 0) { vorzeichen *= -1; zaehler *=-1; } if (nenner < 0) { vorzeichen *= -1; nenner *=-1; } long teiler = ggt(zaehler,nenner); // Vorzeichen wiederherstellen und kuerzen zaehler /=teiler*vorzeichen; nenner /=teiler; } Rational add(const Rational& a,const Rational& b) { Rational r = a; r.add(b); return r; } Rational sub(const Rational& a,const Rational& b) { Rational r = a; r.sub(b); return r; } Rational mul(const Rational& a,const Rational& b) { Rational r = a; r.mul(b); return r; } Rational div(const Rational& a,const Rational& b) { Rational r = a; r.div(b); return r; }
Die Datei mit der main-Funktion stellt das Hauptprogramm dar. Mit der Main-Funktion beginnt jedes C++-Programm. In dieser Datei wird die Header-Datei mit der Klassendeffinition per #include "name.h" eingebunden. Die Datei mit der Funktionsdeffinition wird entweder direkt mit der main-Datei an den Compiler übergeben und direkt kompiliert und verlinkt. Dazu würde folgender Befehl genügen:
g++ main.cpp Funktionsdeffinitionsdatei.cpp -o Programmname
Dies hätte den Nachteil, dass bei jeder Änderung alle Programmteile erneut kompiliert werden müssten. Der bessere Weg ist, zunächst für beides eine Objektdatei zu erstellen. So hat mann von beiden Dateien das Kompilat und verlinkt diese Dateien nur noch durch Übergabe an den Compiler:
g++ -c -Wall bruchrechnung.cxx rational.cpp g++ *.o -o bruchrechnung
Wenn sich im Nachgang an der Datei bruchrechnung.cxx etwas ändert, muss nur noch diese neu kompiliert werden und kann dann direkt neu verlinkt werden:
g++ -c -Wall bruchrechnung.cxx g++ *.o -o bruchrechnung
Beispiel Source-Code bruchrechnung.cxx mit der main-Funktion
#include "rational.h" #include <iostream> #define cout std::cout #define cin std::cin #define endl std::endl void rechne(Rational& a, Rational& b); char op(); int main(int argc, char* argv[] ) { Rational a, b, Ergebnis; char fortsetzen='y'; while (fortsetzen !='n') { a.eingabe(); b.eingabe(); rechne(a,b); cout << "Noch einmal (j/n)?" << endl; cin >> fortsetzen; } } char op() { // Fragt die gewünschte Rechenoperation ab // gibt das Operatorzeichen der Rechenoperation zurück char a; bool eingabeOK= false; while (not eingabeOK) { cout << " Was soll gerechnet werden (+,-,*,/) ? " << endl; cin >> a; switch(a) { case '+': case '-': case '*': case '/': eingabeOK = true; break; default: cout << "Bitte nur einen gültigen Rechenoperator auswählen (+,-,* oder /)"<< endl; } } return a; } void rechne(Rational& a, Rational& b) { Rational Ergebnis; switch(op()) { case '+': Ergebnis = add(a,b); a.ausgabe(); cout << " + "; b.ausgabe(); cout << " = "; Ergebnis.ausgabe(); cout << endl; break; case '-': Ergebnis = sub(a,b); a.ausgabe(); cout << " - "; b.ausgabe(); cout << " = "; Ergebnis.ausgabe(); cout << endl; break; case '*': Ergebnis = mul(a,b); a.ausgabe(); cout << " * "; b.ausgabe(); cout << " = "; Ergebnis.ausgabe(); cout << endl; break; case '/': Ergebnis = div(a,b); a.ausgabe(); cout << " / "; b.ausgabe(); cout << " = "; Ergebnis.ausgabe(); cout << endl; } }
Auf Dauer und jeh mehr Dateien im Bunde sind wird das manuelle kompilieren über die händische Eingabe der Befehle zu aufwendig. Mit dem Programm make läst sich diese Aufgabe automatisieren. Zusätzlich und worauf ich zunächst nicht eingehen möchte ist es möglich mit Hilfe von Variablen flexibel Flags zu setzten, die Einfluss auf den Kompiliervorgang haben. Das Programm sucht nach einer Datei namens makefile und kompiliert und linkt alle geänderten oder neuen Dateien entsprechend den in dieser Datei festgelegten Regeln.
Dabei wird immer zunächst das Ziel gefolgt von einem Doppelpunkt und da hinter dann die Dateien, die für die Erstellung der Zieldatei benötigt werden. In der Zeile darunter wird mit einem Tab eingerückt der Kompiler-Befehl angegeben. Darunter folgt dann nach dem selben Aufbau die nächste Regel zur Erstellung ggf. zuvor benötigter Dateien. Um also ein Programm zu verlinken, wird zunächst der gewünschte Programmname gefolgt von einem Doppelpunkt und den Objektdateinamen (.o) angegeben. In der Zeile darunter eingerückt mit einem Tab der dafür benötigte Kompilerbefehl. Darunter folgt nach dem gleichen Muster die Regel für die Erstellung einer benötigten Objektdatei, also dem Compilat aus dem Quelltext. Und darunter die weiteren Regeln für die Erstellung eventueller weiterer Objektdateien. Dies ist beliebig erweiterbar. Sind alle Dateien in einem Verzeichnis, genügt ein einfaches make, um alle notwendigen Schritte auszuführen. Ein erneuter Aufruf wird nur geänderte Dateien neu compilieren und die Dateien neu verlinken. Das makefile für das Beispiel sieht wie folgt aus:
bruchrechnung: bruchrechnung.o rational.o g++ bruchrechnung.o rational.o -Wall -g -o bruchrechnung bruchrechnung.o: bruchrechnung.cxx g++ -c -Wall -g bruchrechnung.cxx -o bruchrechnung.o rational.o: rational.cpp g++ -c -Wall -g rational.cpp -o rational.o