Teile diese Seite mit anderen

Lerne X in Y Minuten

Wobei X=C++

C++ ist eine Systemprogrammiersprache die,

laut dem Begründer Bjarne Stroustrup entworfen wurde um,

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:


Du hast einen Verbesserungsvorschlag oder einen Fehler gefunden? Erstelle ein Ticket im offiziellen GitHub Repo, oder du erstellst einfach gleich einen pull request!

Originalversion von Steven Basart, mit Updates von 12 contributor(s).