learnxinyminutes-docs/de-de/c++-de.html.markdown
2022-08-01 22:39:47 +02:00

35 KiB
Raw Blame History

language filename contributors lang
C++ learncpp-de.cpp
Steven Basart
http://github.com/xksteven
Matt Kline
https://github.com/mrkline
Geoff Liu
http://geoffliu.me
Connor Waters
http://github.com/connorwaters
Ankush Goyal
http://github.com/ankushg07
Jatin Dhankhar
https://github.com/jatindhankhar
Maximilian Sonnenburg
https://github.com/LamdaLamdaLamda
caminsha
https://github.com/caminsha
de-de

C++ ist eine Systemprogrammiersprache die,

laut dem Begründer Bjarne Stroustrup entworfen wurde um,

  • "besseres C" zu sein
  • Datenabstraktion zu unterstützen
  • Objektorientierung zu unterstützen
  • generische Programmierung zu unterstützen

Durch seine Syntax kann sie durchaus schwieriger und komplexer als neuere Sprachen sein.

Sie ist weit verbreitet, weil sie in Maschinen-Code kompiliert, welcher direkt vom Prozessor ausgeführt werden kann und somit eine strikte Kontrolle über die Hardware bietet und gleichzeitig High-Level-Features wie generics, exceptions und Klassen enthält.

Diese Kombination aus Geschwindigkeit und Funktionalität bildet C++ und ist eine der weitverbreitesten Programmiersprachen.

//////////////////
// Vergleich zu C
//////////////////

// C ist fast eine Untermenge von C++ und teilt sich grundsätzlich die
// Syntax für Variablen Deklarationen, primitiven Typen und Funktionen.

// Wie in C ist der Programmeinsprungpunkt eine Funktion, welche "main" genannt wird und
// einen Integer als Rückgabetyp besitzt.

// Dieser Wert fungiert als Beendigungsstatus des Programms.
// Siehe: https://de.wikipedia.org/wiki/Return_Code für weitere Informationen
int main(int argc, char** argv)
{
    // Kommandozeilen Argumente werden genauso wie in C über argc und argv übergeben
    // argc entspricht der Anzahl von Argumenten und argv ist ein Array von C-style
    // strings (char*), welche die Argumente repräsentieren.
    // Das erste Argument ist der Name des Programms, welches aufgerufen wird.
    // Argc und argv können, wenn nicht benötigt, weg gelassen werden, indem
    // die Funktionssignatur "int main()" verwendet wird.

    //  Ein Rückgabewert von 0 repräsentiert die erfolgreiche Ausführung.
    return 0;
}

// C++ unterscheidet sich in einigen Punkten von C:

// In C++ sind Zeichen-Literale char´s
sizeof('c') == sizeof(char) == 1

// In C sind Zeichen-Literale int´s
sizeof('c') == sizeof(int)

// C++ verwendet striktes prototyping
void func(); // Funktion ohne Argumente

// In C
void func(); // Funktion mit beliebiger Anzahl von Argumenten

// Verwende nullptr, anstatt von NULL!!!
int* ip = nullptr;

// C standard header sind in C++ verfügbar.
// C header enden mit .h, während
// C++ header das Präfix "c" besitzen und kein ".h" Suffix verwenden.

// Die C++ Standard Version:
#include <cstdio>

// Die C Standard Version:
#include <stdio.h>

int main()
{
    printf("Hello, world!\n");
    return 0;
}

///////////////////////
// Funktionsüberladung
///////////////////////

// C++ unterstützt Funktionsüberladung
// Jede Funktion kann unterschiedliche Parameter erhalten.
void print(char const* myString)
{
    printf("String %s\n", myString);
}

void print(int myInt)
{
    printf("My int is %d", myInt);
}

int main()
{
    print("Hello"); // Wird aufgelöst zu "void print(const char*)"
    print(15); // Wird aufgelöst zu "void print(int)"
}

/////////////////////////////
// Standard Funktionsargumente
/////////////////////////////

// Argumente können per Standard für eine Funktion gesetzt werden,
// wenn diese beim Aufruf nicht bereitgestellt werden.
void doSomethingWithInts(int a = 1, int b = 4)
{
    // führe Anweisungen mit "int´s" aus.
}

int main()
{
    doSomethingWithInts();      // a = 1,  b = 4
    doSomethingWithInts(20);    // a = 20, b = 4
    doSomethingWithInts(20, 5); // a = 20, b = 5
}

// Standard-Argumente müssen am Ende der Liste der Argumente stehen.
void invalidDeclaration(int a = 1, int b) // Fehler!
{
}


/////////////
// Namespaces (Namensräume)
/////////////

// Namespaces stellen einen getrennten Gültigkeitsbereich für Variablen,
// Funktionen und andere Deklarationen zur Verfügung.
// Namespaces können geschachtelt werden.
namespace First
{
    namespace Nested
    {
        void foo()
        {
            printf("This is First::Nested::foo\n");
        }
    } // Ende des Namespace "Nested"
} // Ende des Namespace "First"

namespace Second
{
    void foo()
    {
        printf("This is Second::foo\n");
    }
}

void foo()
{
    printf("This is global foo\n");
}

int main()
{
    // Fügt alle Symbole aus dem namespace Second in den aktuellen Gültigkeitsbereich (scope).
    // "foo()" wird nun nicht länger funktionieren, da es nun doppeldeutig ist, ob foo aus
    // dem namespace foo oder darüberliegenden aufgerufen wird.
    using namespace Second;

    Second::foo(); // Gibt "This is Second::foo" aus.
    First::Nested::foo(); // Gibt "This is First::Nested::foo" aus.
    ::foo(); // Gibt "This is global foo" aus.
}

///////////////
// Eingabe/Ausgabe
///////////////

// C++ verwendet für die Eingabe und Ausgabe streams.
// cin, cout und cerr repräsentieren stdin, stdout und stderr.
// << ist der Einfügeoperator und >> ist der Extraktionsoperator.

#include <iostream> // Include für Eingabe/Ausgabe (I/O) streams

using namespace std; // Streams befinden sich im std namespace (standard library)

int main()
{
   int myInt;

   // Ausgabe auf stdout (oder Terminal/Bildschirm)
   cout << "Enter your favorite number:\n";

   // Empfängt Eingabe
   cin >> myInt;

   // cout kann ebenfalls formatiert werden
   cout << "Your favorite number is " << myInt << "\n";
   // Gibt "Your favorite number is <myInt>" aus

    cerr << "Used for error messages";
}

//////////
// Zeichenketten (Strings)
//////////

// Strings in C++ sind Objekte und haben diverse member-functions
#include <string>

using namespace std; // Strings sind ebenfalls im namespace std (Standard Bibliothek)

string myString = "Hello";
string myOtherString = " World";

// + wird für das Anhängen von strings verwendet.
cout << myString + myOtherString; // "Hello World"

cout << myString + " You"; // "Hello You"

// C++ strings sind mutable.
myString.append(" Dog");
cout << myString; // "Hello Dog"


/////////////
// Referenzen
/////////////

// Zusätzlich zu Pointern, wie jene in C.
// C++ besitzt _Referenzen_.
// Diese sind Pointer-Typen, welche nicht erneut zugewiesen werden können
// und nicht Null sein können.
// Sie besitzen den selben Syntax wie Variablen.
// Für die Dereferenzierung ist kein * notwendig und
// & (die Adresse) wird nicht für die Zuweisung verwendet.

using namespace std;

string foo = "I am foo";
string bar = "I am bar";


string& fooRef = foo; // Erzeugt eine Referenz auf foo.
fooRef += ". Hi!"; // Verändert foo durch die Referenz
cout << fooRef; // Gibt "I am foo. Hi!" aus.


// Weist "fooRef" nicht erneut zu. Dies ist dasselbe, wie "foo = bar" und 
// foo == "I am bar"
// nach dieser Zeile 
cout << &fooRef << endl; // Gibt die Adresse von foo aus
fooRef = bar;
cout << &fooRef << endl; // Gibt ebenfalls die Adresse von foo aus
cout << fooRef;  // Gibt "I am bar" aus

// Die Adresse von fooRef verbleibt die selbe, sie verweist immer noch auf foo

const string& barRef = bar; // Erzeugt konstante Referenz auf bar.
// Wie in C, können konstante Werte ( und Pointer bzw. Referenzen) nicht verändert werden.

barRef += ". Hi!"; // Fehler: konstante Referenzen können nicht verändert werden.

// Hinweis: bevor wir genauer Referenzen besprechen, schauen wir uns zuerst ein Konzept an,
// welches als "temporäres Objekt" bezeichnet wird. Gehen wir von folgenden Code aus:
string tempObjectFun() { ... }
string retVal = tempObjectFun();

// Was passiert nun in der zweiten Zeile:
//  - ein String Objekt wird von "tempObjectFun" zurückgegeben
//  - ein neuer String wird mit dem zurückgegebenen Objekt als Argument für den Konstruktor erzeugt.
//  - das zurückgegebene Objekt wird zerstört
// Das zurückgegbene Objekt wird temporäres Objekt genannt. Temporäre Objekte werden erzeugt
// wann immer eine Funktion ein Objekt zurückgibt. Zerstört werden diese am Ende der Auswertung des Ausdrucks
// (dies schreibt der Standard vor, aber Compiler sind berechtigt dieses Verhalten zu ändern. Siehe "return value optimization"
// für Details). Wie in diesem Code:
foo(bar(tempObjectFun()))

// Nehmen wir an, foo und bar existieren. Das Objekt wird von "tempObjectFun" zurückgegeben,
// wird an bar übergeben und ist zerstört bevor foo aufgerufen wird.

// Zurück zu Referenzen. Die Annahme, dass die "am Ende des Ausdrucks" Regel gültig ist,
// wenn das temporäre Objekt an eine konstante Referenz gebunden ist, ist der Fall, wenn die Lebensdauer
// auf den aktuellen Gültigkeitsbereich erweitert wird.

void constReferenceTempObjectFun() {
  // constRef erhält das temporäre Objekt und ist gültig bis ans Ende der Funktion
  const string& constRef = tempObjectFun();
  ...
}

// Eine andere Art von Referenzen wurde in C++11 eingeführt und ist speziell für
// temporäre Objekte. Es ist nicht möglich Variablen des Typs zu besitzen, aber
// Vorrechte bei der Auflösung zu besitzen.

void someFun(string& s) { ... }  // Reguläre Referenz
void someFun(string&& s) { ... }  // Referenz auf ein temporäres Objekt

string foo;
someFun(foo);  // Ruft die Funktion mit der regulären Referenz auf
someFun(tempObjectFun());  // Ruft die Funktion mit der temporären Referenz auf

// Zum Beispiel existieren diese zwei Varianten von Konstruktoren für
// std::basic_string:
basic_string(const basic_string& other);
basic_string(basic_string&& other);

// Nehmen wir an, wir erzeugen einen neuen String eines temporären Objekts (welches später
// zerstört wird), hierbei existiert ein effizienterer Konstruktor. Dieses Konzept wird
// als "move semantics" bezeichnet (bewegen eines Objekts in ein anderes in C++).

/////////////////////
// Enumerations (Aufzählungstypen)
/////////////////////

// Enums sind eine einfachere Art und Weise einen Wert einer Konstante zu zuweisen.
// Häufig wird dies verwendet, um den Code lesbarer zu gestalten bzw. zu visualisieren.
enum ECarTypes
{
  Sedan,
  Hatchback,
  SUV,
  Wagon
};

ECarTypes GetPreferredCarType()
{
	return ECarTypes::Hatchback;
}

// Mit C++11 existiert eine einfache Möglichkeit einem Typ dem Enum zuzuweisen. Dies
// kann durchaus sinnvoll bei der Serialisierung von Daten sein, oder bei der Konvertierung
// zwischen Typen bzw. Konstanten.
enum ECarTypes : uint8_t
{
  Sedan, // 0
  Hatchback, // 1
  SUV = 254, // 254
  Hybrid // 255
};

void WriteByteToFile(uint8_t InputValue)
{
    // Serialisierung von "InputValue" in eine Datei
}

void WritePreferredCarTypeToFile(ECarTypes InputCarType)
{
    // Das enum wird implizit zu einem "uint8_t" konvertiert. Bedingt dadurch, dass
    // es sich um ein "enum" handelt.
	WriteByteToFile(InputCarType);
}

// Nicht immer ist es gewünscht, dass enum´s zu einem Integer oder zu einem anderen
// enum umgewandelt werden. Daher ist es möglich eine enum-Klasse zu erzeugen, welche
// nicht implizit umgewandelt wird.
enum class ECarTypes : uint8_t
{
  Sedan, // 0
  Hatchback, // 1
  SUV = 254, // 254
  Hybrid // 255
};

void WriteByteToFile(uint8_t InputValue)
{
    // Serialisierung von InputValue in eine Datei
}

void WritePreferredCarTypeToFile(ECarTypes InputCarType)
{
  // Wird nicht kompilieren, da "ECarTypes" ein "uint8_t" ist, da das enum
  // als "enum class" deklariert wurde!
	WriteByteToFile(InputCarType);
}

//////////////////////////////////////////
// Klassen und objekorientierte Programmierung
//////////////////////////////////////////

// Erstes Beispiel einer Klasse
#include <iostream>

// Deklaration einer Klasse.
// Klassen werden üblicherweise im header (.h oder .hpp) deklariert.
class Dog
{
    // Member Variablen und Funktionen sind private per default (standard).
    std::string name;
    int weight;

// Alle nachfolgenden member sind "public" bis
// "private:" oder "protected:" auftritt.
public:

    // Standard Konstruktor
    Dog();

    // Member-Funktionsdeklaration (Implementierung folgt).
    // Bemerkung: std::string statt der Verwendung von namespace std;
    // "using namespace" sollte niemals in einem header verwendet werden.
    void setName(const std::string& dogsName);

    void setWeight(int dogsWeight);

    // Funktionen, die Objekte nicht ändern, sollten mit const deklariert werden.
    // Funktionen müssen explizit als "virtual" deklariert werden, um in einer
    // abgeleiteten Klassen überschrieben zu werden.
    // Aus performance Gründen sind Funktionen nicht per default virtual.
    virtual void print() const;

    // Funktionen können ebenfalls im class body definiert werden.
    // Derart definierte Funktionen sind automatisch "inline".
    void bark() const { std::cout << name << " barks!\n"; }

    // Neben Konstruktoren, bietet C++ Destruktoren.
    // Diese werden aufgerufen, wenn ein Objekt freigegeben wird oder
    // seinen Wertebereich verlässt.
    // Dies ermöglicht mächtige Paradigmen, wie auch RAII.
    // Destruktoren sollten virtual sein, wenn eine Klasse von ihr
    // abgeleitet wird. Ist dieser nicht virtual, dann wird der
    // Destruktor der abgeleiteten Klasse nicht aufgerufen, insofern
    // das Objekt durch eine Referenz/Pointer der Basisklasse entfernt wird.
    virtual ~Dog();

}; // Ein Semikolon schließt die Definition der Klasse ab.  

// Klassen-Member-Funktionen sind üblicherweise in der .cpp Datei implementiert.
Dog::Dog()
{
    std::cout << "A dog has been constructed\n";
}

// Objekte sollten als Referenz übergeben werden und wenn diese nicht
// verändert werden sollen, sollte das Objekt als const Referenz übergeben werden.
void Dog::setName(const std::string& dogsName)
{
    name = dogsName;
}

void Dog::setWeight(int dogsWeight)
{
    weight = dogsWeight;
}

// "Virtual" wird nur bei der Deklaration benötigt und nicht bei der Definition.
void Dog::print() const
{
    std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";
}

Dog::~Dog()
{
    std::cout << "Goodbye " << name << "\n";
}

int main() 
{
    Dog myDog; // Ausgabe: "A dog has been constructed"
    myDog.setName("Barkley");
    myDog.setWeight(10);
    myDog.print(); // Ausgabe: "Dog is Barkley and weighs 10 kg"
    return 0;
} // Ausgabe: "Goodbye Barkley"

// Diese Klasse erbt alles was public bzw. protected ist von der Dog-Klasse
// und darüber hinaus auch private Methoden/Attribute, jedoch kann auf diese
// nicht direkt zugegriffen werden. Lediglich über public/procted getter/setter.
class OwnedDog : public Dog {

public:
    void setOwner(const std::string& dogsOwner);

    // Überschreibt das Verhalten der "print" Funktion für alle "OwnedDogs".
    // Siehe: http://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Subtyping
    // für eine grundlegende Einführung in "Subtype Polymorphismus".
    // Das "override" Schlüsselwort ist optional, aber stellt sicher, dass die
    // Methode der Basisklasse tatsächlich überschrieben wurde.
    void print() const override;

private:
    std::string owner;
};

// Die zugehörige .cpp Datei
void OwnedDog::setOwner(const std::string& dogsOwner)
{
    owner = dogsOwner;
}

void OwnedDog::print() const
{
    Dog::print(); // Ruft die "print" Funktion der Basisklasse auf.
    std::cout << "Dog is owned by " << owner << "\n";
    // Ausgaben: "Dog is <name> and weights <weight>"
    //           "Dog is owned by <owner>"
}

//////////////////////////////////////////
// Initialisierung und Operatorüberladung
//////////////////////////////////////////

// In C++ können Operatoren wie: +, -, *, / etc. überladen werden.
// Dies wird umgesetzt, indem eine entsprechende Funktion definiert wird,
// welche immer dann aufgerufen wird, sobald der Operator verwendet wird.
#include <iostream>
using namespace std;

class Point
{
public:
    // Member Variablen können mit einem default Wert initialisiert werden.
    double x = 0;
    double y = 0;

    // Definition des Standard Konstruktor, welcher nichts tut
    // außer den Punkt auf den default Wert (0,0) zu setzen.
    Point() { };

    // Die nachfolgende Syntax ist bekannt als "initialization list"
    // und ist eine gängige Art Klassen-Member zu initialisieren.
    Point (double a, double b) :
        x(a),
        y(b)
    { /* Außschließliche Initialisierung der Werte */ }

    // Überladung des "+" Operator.
    Point operator+(const Point& rhs) const;

    // Überladung des "+=" Operator
    Point& operator+=(const Point& rhs);

    // Sinnhaft wäre es an dieser Stelle den "-" und "-=" Operator
    // ebenfalls zu überladen.
};

Point Point::operator+(const Point& rhs) const
{
    // Erzeugung eines neuen Punkts, welcher die Summe aus sich
    // selbst und "rhs" bildet
    return Point(x + rhs.x, y + rhs.y);
}

Point& Point::operator+=(const Point& rhs)
{
    x += rhs.x;
    y += rhs.y;
    return *this;
}

int main ()
{
    Point up (0,1);
    Point right (1,0);

    // Ruft den + Operator mit den entsprechenden Parametern auf.
    Point result = up + right;
    // Ausgabe: "Result is upright (1,1)"
    cout << "Result is upright (" << result.x << ',' << result.y << ")\n";
    return 0;
}

/////////////////////
// Templates
/////////////////////

// Templates in C++ werden in erster Linie dafür verwendet generisch zu programmieren.
// Sie unterstützen explizite und partielle Spezialisierung und darüber hinaus können
// sie für funktionale Klassen verwendet werden.
// Tatsächlich bilden Templates die Turing-Vollständigkeit
// (universelle Programmierbarkeit) ab.


// Zu Beginn ein einführendes Beispiel der generischen Programmierung.
// Die Definition einer Klasse bzw. Funktion, welche mit dem Typ T parametriert wird.
template<class T>
class Box
{
public:
    // T repräsentiert an dieser Stelle einen beliebigen Typen.
    void insert(const T&) { ... }
};

// Während der Kompilierung generiert der Compiler Kopien für jedes Template, wobei
// hierbei die Parameter substituiert werden. Somit muss bei jedem Aufruf die gesamte
// Definition der Klasse zur Verfügung stehen. Aus diesem Grund wird ein Template
// komplett im header definiert.

// Erzeugung einer Template-Klasse auf dem Stack:
Box<int> intBox;

// eine der zu erwartenden Verwendungen:
intBox.insert(123);

// Verschachtelungen von Templates sind möglich.
Box<Box<int> > boxOfBox;
boxOfBox.insert(intBox);

// Bis C++11 war es erforderlich ein Leerzeichen zwischen '>' einzufügen,
// andernfalls wurde es als '>>' geparsed (right shift).

// Manchmal ist folgende Notation anzutreffen:
// template<typename T>
// Das 'class' Schlüsselwort und das 'typename' Schlüsselwort
// sind fast identisch hinsichtlich der Funktionalität. Weitere
// Informationen auf: http://en.wikipedia.org/wiki/Typename

// Eine Template-Funktion:
template<class T>
void barkThreeTimes(const T& input)
{
    input.bark();
    input.bark();
    input.bark();
}

// Hierbei ist zu beachten, dass an dieser Stelle nichts über den Typen des Parameters
// definiert wurde. Der Compiler wird bei jedem Aufruf bzw. jeder Erzeugung den Typen
// prüfen. Somit funktioniert die zuvor definierte Funktion für jeden Typ 'T', die die
// const Methode 'bark' implementiert hat.

Dog fluffy;
fluffy.setName("Fluffy")
barkThreeTimes(fluffy); // Gibt "Fluffy barks" dreimal aus.

// Template Parameter müssen keine Klassen sein.
template<int Y>
void printMessage()
{
  cout << "Learn C++ in " << Y << " minutes!" << endl;
}

// Des Weiteren können Templates aus Effizienzgründen genauer spezifiziert werden.
// Selbstverständlich sind reale Probleme, welche genauer spezifiziert werden, nicht
// derart trivial. Auch wenn alle Parameter explizit definiert wurden, muss die
// Funktion oder Klasse als Template deklariert werden.
template<>
void printMessage<10>()
{
  cout << "Learn C++ faster in only 10 minutes!" << endl;
}

printMessage<20>();  // Gibt "Learn C++ in 20 minutes!" aus.
printMessage<10>();  // Gibt "Learn C++ faster in only 10 minutes!" aus.


/////////////////////
// Ausnahme Behandlungen (Exception-Handling)
/////////////////////

// Die Standard Bibliothek bietet einige exceptions.
// Siehe: http://en.cppreference.com/w/cpp/error/exception.
// Grundsätzlich können alle Typen als exception geworfen werden.
#include <exception>
#include <stdexcept>

// Alle exceptions, die in dem "try" Block geworfen werden, können mittels
// "catch" abgefangen werden.
try
{
    // exceptions sollten nicht auf dem heap mithilfe
    // von "new" allokiert werden.
    throw std::runtime_error("A problem occurred");
}

// Exceptions sollten als const Referenz abgefangen werden
// insofern diese Objekte sind.
catch (const std::exception& ex)
{
    std::cout << ex.what();
}

// Abfangen aller Exceptions, welche zuvor nicht abgefangen wurden.
catch (...)
{
    std::cout << "Unknown exception caught";
    throw; // Erneutes werfen der exception
}

///////
// RAII
///////

// RAII steht für "Resource Acquisition Is Initialization".
// Oft wird dies als eines der wichtigsten Paradigmen in C++ betrachtet.
// RAII beschreibt das Konzept, dass der Konstruktor für ein Objekt
// die Ressourcen akquiriert und der Destruktor diese freigibt.

// Zum Verständnis, warum dies sinnvoll ist, nachfolgend
// ein einführendes Beispiel:
void doSomethingWithAFile(const char* filename)
{
    // Wir nehmen an, dass nichts schiefgehen wird.
    FILE* fh = fopen(filename, "r"); // Öffnen der Datei im read-mode.

    doSomethingWithTheFile(fh);
    doSomethingElseWithIt(fh);

    fclose(fh); // Schließen des file-handle.
}

// Unglücklicherweise ist die Fehlerbehandlung äußerst kompliziert.
// Sollte fopen fehlschlagen und "doSomethingWithTheFile" bzw.
// "doSomethingElseWithIt", geben diese einen Fehlercode zurück.
// (Exceptions sind eine bevorzugte Möglichkeit Fehler abzufangen
// , allerdings bei einigen Programmierern, besonders solchen die einen C
// background besitzen, ein unbeliebtes Mittel zur Fehlerbehandlung).
// Nun müssen wir jeden Aufruf auf mögliche auftretende Fehler überprüfen.
bool doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r"); // Öffnet die Datei im read-mode
    if (fh == nullptr) // Der Pointer ist bei einem Fehler NULL .
        return false; // Benachrichtigt den Aufrufer über den Fehler.

    // Wir nehmen an, dass jede Funktion false zurückgibt, in einem Fehlerfall
    if (!doSomethingWithTheFile(fh))
    {
        fclose(fh); // File handle schließen.
        return false; // Fehler "melden".
    }

    if (!doSomethingElseWithIt(fh))
    {
        fclose(fh); // File handle schließen.
        return false; // Fehler "melden".
    }

    fclose(fh); // File handle schließen.
    return true; // Erfolg "melden".
}

// C-Programmierer handhaben dies häufig durch goto-Anweisungen:
bool doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r");
    if (fh == nullptr)
        return false;

    if (!doSomethingWithTheFile(fh))
        goto failure;

    if (!doSomethingElseWithIt(fh))
        goto failure;

    fclose(fh); // File handle schließen.
    return true; // Erfolg "melden".

failure:
    fclose(fh);
    return false; // Fehler "melden".
}

// Insofern Funktionen Fehler durch exceptions indizieren,
// ist dies "sauberer", aber immer noch suboptimal.
void doSomethingWithAFile(const char* filename)
{
    FILE* fh = fopen(filename, "r"); // Öffnet die Datei im read-mode
    if (fh == nullptr)
        throw std::runtime_error("Could not open the file.");

    try
    {
        doSomethingWithTheFile(fh);
        doSomethingElseWithIt(fh);
    }
    catch (...)
    {
        // Im Fehlerfall sollte sichergestellt sein, dass die
        // Datei geschlossen wird.
        fclose(fh);
        throw; // Erneutes werfen der exception
    }

    fclose(fh); // Schließen der Datei
}

// Folgendes ist mit der C++ file stream Klasse (fstream) zu vergleichen.
// fstream verwendet den Destruktor, um die Datei zu schließen.
// Der obige Destruktor wird automatisch aufgerufen, sobald das Objekt
// den Gültigkeitsbereich verlässt.
void doSomethingWithAFile(const std::string& filename)
{
    // ifstream entspricht der Kurzform von "input file stream".
    std::ifstream fh(filename); // Öffnen der Datei

    doSomethingWithTheFile(fh);
    doSomethingElseWithIt(fh);

} // Die Datei wird automatisch vom Destruktor geschlossen.

// Diese Vorgehensweise bietet massive Vorteile:
// 1. Egal was passiert, die Ressource (das Datei-Handle) wird aufgelöst,
//    insofern der Destruktor korrekt beschrieben wurde. Es ist möglich
//    zu vergessen das Datei-Handle zu schließen, was zu einem "leak" der
//    entsprechenden Ressource führt.
// 2. Der Code selbst ist wesentlich "sauberer".
//    Der Destruktor wird das Datei-Handle im Hintergrund schließen und der
//    Programmierer muss sich darum keinerlei Sorgen machen.
// 3. Der Code ist "exception sicher".
//    Egal wo die exception geworfen wird, das Aufräumen wird definitiv vollzogen.

// Der gesamte idiomatische C++ Code verwendet RAII für alle Ressourcen.
// Weitere Beispiele:
// - Speicher verwenden "unique_ptr" und "shared_ptr".
// - Container - verkettete Listen (linked list), vector (selbst organisierende
//   Arrays), hash maps, etc., entfernen deren Inhalt, wenn diese außerhalb des
//   Gültigkeitsbereichs laufen.
// - Mutex´s verwenden lock_guard und unique_lock.

/////////////////////
// Container
/////////////////////

// Die Container der Standard template Bibliothek beinhaltet einige vordefinierte Templates.
// Diese verwalten die Speicherbereiche für die eigenen Elemente und stellen Member-Funktionen
// für den Zugriff und die Manipulation bereit.

// Beispielhafte Container:

// Vector (dynamisches array)
// Erlaubt das Definieren von Arrays oder Listen zur Laufzeit
#include <vector>
string val;
vector<string> my_vector; // Initialisierung des Vectors.
cin >> val;
my_vector.push_back(val); // Fügt den Wert "val" zum Vektor "my_vector" hinzu.
my_vector.push_back(val); // Fügt den Wert "val" zum Vektor "my_vector" hinzu (nun zwei Elemente).

// Für die Iteration über Vektoren stehen zwei Methodiken zu Verfügung:
// Entweder die klassische Iteration über den Index:
for (int i = 0; i < my_vector.size(); i++)
{
	cout << my_vector[i] << endl; // Zugriff auf die Elemente des Vektors über den [] Operator
}

// Oder die Verwendung von Iteratoren:
vector<string>::iterator it; // Initialisierng des Iterators.
for (it = my_vector.begin(); it != my_vector.end(); ++it)
{
	cout << *it  << endl;
}

// Set (Mengen)
// Sets sind Container, welche einzigartige Elemente beinhalten die einer
// spezifischen Ordnung folgen.

#include<set>
set<int> ST;    // Initialisierung des Sets mit einem Integer Datentyp.
ST.insert(30);  // Einfügen des Werts 30 in das Set ST
ST.insert(10);  // Einfügen des Werts 10 in das Set ST
ST.insert(20);  // Einfügen des Werts 20 in das Set ST
ST.insert(30);  // Einfügen des Werts 30 in das Set ST
// Folgende Elemente befinden sich nun in dem Set:
//  10 20 30

// Entfernen eines Elements:
ST.erase(20);

// Set ST: 10 30
// Für das iterieren verwenden wir Iteratoren:
set<int>::iterator it;

for(it=ST.begin();it<ST.end();it++)
{
	cout << *it << endl;
}

// Ausgabe:
// 10
// 30

// Zum leeren des gesamten Containers wird die Methode
// Container._name.clear() verwendet.
ST.clear();
cout << ST.size();  // Ausgabe der Set-Größe

// Ausgabe: 0

// Bemerkung: für mehrdeutige Elemente werden multisets verwendet.
// Für hash-Sets sollten unordered_set´s verwendet werden, da diese
// wesentlich effizienter sind, allerdings keiner Ordnung folgen.
// Verfügbar sind diese Features ab C++11.

// Map
// Maps speichern Elemente, welche einer Kombination aus "Key"
// und "Value" folgen.

#include<map>
map<char, int> mymap;  // Initialisierung der Map: char -> Key, int -> Value.

mymap.insert(pair<char,int>('A',1)); // Einfügen des Werts "1" für den Key "A".

mymap.insert(pair<char,int>('Z',26)); // Einfügen des Werts "26" für den Key "Z".

// Das Iterieren über Maps:
map<char,int>::iterator it;
for (it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << "->" << it->second << '\n';

// Ausgabe:
// A->1
// Z->26

// Für das Finden des dazugehörigen Value des Keys.
it = mymap.find('Z');
cout << it->second;

// Ausabe: 26

// Bemerkung: für "hash maps" sollten die "unordered_map´s" verwendet werden. Diese
// sind effizienter und benötigen keine Reihenfolge. "unordered_maps" sind ab
// C++11 verfügbar.

// Container für nicht-primitive Datentypen benötigen Vergleichsfunktionen im Objekt selbst,
// oder als Funktionspointer. Primitive Datentypen besitzen default-Vergleichsfunktionen.
// Allerdings können diese überschrieben werden.
class Foo
{
public:
    int j;
    Foo(int a) : j(a) {}
};

struct compareFunction
{
    bool operator()(const Foo& a, const Foo& b) const
    {
        return a.j < b.j;
    }
};

// Folgender Code ist nicht valide, könnte aber von einigen Compilern
// als valide angesehen werden:
// std::map<Foo, int> fooMap;
std::map<Foo, int, compareFunction> fooMap;
fooMap[Foo(1)]  = 1;
fooMap.find(Foo(1)); // Wahr


///////////////////////////////////////
// Lambda Ausdrücke (C++11 und höher)
///////////////////////////////////////

// Lambdas sind eine gängige Methodik, um anonyme Funktionen an dem
// Ort der Verwendung zu definieren. Darüber hinaus auch bei der
// Verwendung von Funktionen als Argument einer Funktion.

// Nehmen wir an, es soll ein Vektor von "pairs" (Paaren) mithilfe
// des zweiten Werts des "pairs" sortiert werden.

vector<pair<int, int> > tester;
tester.push_back(make_pair(3, 6));
tester.push_back(make_pair(1, 9));
tester.push_back(make_pair(5, 0));

// Übergabe des Lambda-Ausdrucks als drittes Argument für die nachfolgende Sortierfunktion.
sort(tester.begin(), tester.end(), [](const pair<int, int>& lhs, const pair<int, int>& rhs)
{
        return lhs.second < rhs.second;
});

// Beachte die Syntax von Lambda-Ausdrücken.
// Die [] im Lambda Ausdruck werden für die Variablen verwendet.
// Diese so genannte "capture list" definiert, was außerhalb des Lambdas,
// innerhalb der Funktion verfügbar sein soll und in welcher Form.
// Dies kann folgendes sein:
//     1. ein Wert [x]
//     2. eine Referenz [&x]
//     3. eine beliebige Variable, welche sich im Gültigkeitsbereich durch
//        die Referenz [&] befindet.
//     4. wie bei 3. aber mithilfe des Werts [=]
// Beispiel:

vector<int> dog_ids;

for(int i = 0; i < 3; i++)
{
	dog_ids.push_back(i);
}

int weight[3] = {30, 50, 10};

// Nehmen wir an wir möchten die "dog_ids" gemäß des Gewichts des Hundes sortieren.
// So sollten sich die "dog_ids" wie folgt verhalten: [2, 0, 1]

// Hier werden Lambdas praktisch:
sort(dog_ids.begin(), dog_ids.end(), [&weight](const int &lhs, const int &rhs)
{
        return weight[lhs] < weight[rhs];
});


// Weiterführender Link über Lambda-Ausdrücke:
// http://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11

///////////////////////////////
// Range For (C++11 und höher)
///////////////////////////////

// Range-For Schleifen können verwendet werden, um über Container zu iterieren.
int arr[] = {1, 10, 3};

for(int elem: arr)
{
	cout << elem << endl;
}

// Insofern "auto" verwendet wird, muss der Typ nicht weiter beachtet werden.
for(auto elem: arr)
{
	// Anweisungen ...
}

/////////////////////
// Weiteres:
/////////////////////

// Einige Aspekte von C++ sind für Neueinsteiger häufig überraschend (aber auch für
// C++ Veteranen).
// Der nachfolgende Abschnitt ist leider nicht vollständig:
// C++ ist eine der Sprachen, bei der es ein Leichtes ist, sich selbst ins Bein zu schießen.

// Private-Methoden können überschrieben werden
class Foo
{
  virtual void bar();
};

class FooSub : public Foo
{
  virtual void bar();  // Überschreibt Foo::bar!
};

// 0 == false == NULL
bool* pt = new bool;
*pt = 0; // Setzt den Wert des Pointers 'pt' auf false.
pt = 0;  // Setzt 'pt' auf den "null-pointer". Keine Compiler-Warnung.

// nullptr sollte dieses Problem nicht lösen:
int* pt2 = new int;
*pt2 = nullptr; // Kompiliert nicht.
pt2 = nullptr;  // Setzt pt2 auf null.

// Eine Ausnahme bilden bool´s.
// Dies erlaubt es "null-pointer" zu testen: if(!ptr)
// Die Konsequenz ist jedoch, dass dem nullptr ein bool zugewiesen werden kann.
*pt = nullptr;  // Kompiliert auch, wenn '*pt' ein bool ist!

// '=' != '=' != '='!
// Ruft Foo::Foo(const Foo&) auf, oder den Kopierkonstruktor
Foo f2;
Foo f1 = f2;

// Ruft Foo::Foo(const Foo&) auf, aber kopiert lediglich den "Foo" Teil von
// "fooSub". Alle zusätzlichen Member werden verworfen. Diese eigenartige Verhalten
// wird auch "object slicing" genannt.
FooSub fooSub;
Foo f1 = fooSub;

// Ruft Foo::operator=(Foo&) oder eine andere Variante auf.
Foo f1;
f1 = f2;

///////////////////////////////////////
// Tuple (C++11 und höher)
///////////////////////////////////////

#include<tuple>

// Konzeptionell sind Tupel alten Datenstrukturen sehr ähnlich, allerdings haben diese keine
// bezeichneten Daten-Member, sondern werden durch die Reihenfolge angesprochen.

// Erstellen des Tupels und das Einfügen eines Werts.
auto first = make_tuple(10, 'A');
const int maxN = 1e9;
const int maxL = 15;
auto second = make_tuple(maxN, maxL);

// Ausgabe der Elemente des "first" Tuple.
cout << get<0>(first) << " " << get<1>(first) << "\n"; // Ausgabe : 10 A

// Ausgabe der Elemente des "second" Tuple.
cout << get<0>(second) << " " << get<1>(second) << "\n"; // Ausgabe: 1000000000 15

int first_int;
char first_char;
tie(first_int, first_char) = first;
cout << first_int << " " << first_char << "\n";  // Ausgabe : 10 A

// Tuple können auch wie folgt erzeugt werden:

tuple<int, char, double> third(11, 'A', 3.14141);
// tuple_size  gibt die Anzahl der Elemente in einem Tuple zurück.
// Als "constexpr".

cout << tuple_size<decltype(third)>::value << "\n"; // prints: 3

// tuple_cat fügt die Elemente eines Tupels aneinander (in der selben Reihenfolge).

auto concatenated_tuple = tuple_cat(first, second, third);
// concatenated_tuple wird zu = (10, 'A', 1e9, 15, 11, 'A', 3.14141)

cout << get<0>(concatenated_tuple) << "\n"; // Ausgabe: 10
cout << get<3>(concatenated_tuple) << "\n"; // Ausgabe: 15
cout << get<5>(concatenated_tuple) << "\n"; // Ausgabe: 'A'


///////////////////////////////////
// Logische- und Bitoperatoren
//////////////////////////////////

// Die meisten Operatoren in C++ entsprechen denen aus anderen Sprachen

// Logische Operatoren.
// C++ verwendet so genannte "Short-circuit" Evaluierung für Boolean-Ausdrücke.
// Das zweite Argument wird ausgeführt bzw. evaluiert, wenn das erste Argument genügt,
// um den Ausdruck zu bestimmen.

true && false // Führt **logisches und** aus.
true || false // Führt **logisches oder** aus.
! true        // Führt **logisches nicht** aus.

// Anstelle von Symbolen können auch Schlüsselwörter verwendet werden.
true and false // Führt **logisches und** aus.
true or false  // Führt **logisches oder** aus.
not true       // Führt **logisches nicht** aus.

// Bitoperationen

// **<<** Links-Shift
// **>>** Rechts-Shift

~4    // Führt bitweises nicht aus.
4 | 3 // Führt bitweises oder aus.
4 & 3 // Führt bitweises und aus.
4 ^ 3 // Führt bitweises xor aus.

// Gleichwertige Schlüsselwörter:
compl 4    // Führt bitweises nicht aus.
4 bitor 3  // Führt bitweises oder aus.
4 bitand 3 // Führt bitweises und aus.
4 xor 3    // Führt bitweises xor aus.


Weiterführende Literatur: