9 Übungsaufgaben

9.0.1 Übungsaufgaben zum Thema Datentypen, sizeof und Typumwandlung

  1. Grundlegende Datentypen
  2. Verwendung von sizeof
  3. Typumwandlung
  4. Arithmetik und Typumwandlung

9.0.2 Musterlösungen

  1. Grundlegende Datentypen

    int i = 42;
    float f = 3.14f;
    double d = 2.71828;
    char c = 'G';
    bool b = true;
    
    std::cout << i << " " << f << " " << d << " " << c << " " << b << std::endl;
  2. Verwendung von sizeof

    std::cout << "int: " << sizeof(int) << " bytes\n";
    std::cout << "float: " << sizeof(float) << " bytes\n";
    std::cout << "double: " << sizeof(double) << " bytes\n";
    std::cout << "char: " << sizeof(char) << " byte\n";
    std::cout << "bool: " << sizeof(bool) << " byte(s)\n";
  3. Typumwandlung

    double dValue = 9.7;
    int iValue = static_cast<int>(dValue);
    
    std::cout << "Double: " << dValue << "\nInt: " << iValue << std::endl;
    
    char ch = 'A';
    int chAsInt = static_cast<int>(ch);
    std::cout << "Char: " << ch << "\nInt: " << chAsInt << std::endl; // Gibt den ASCII-Wert von 'A' aus, welcher 65 ist.
  4. Arithmetik und Typumwandlung

    int a = 5, b = 2;
    double result = static_cast<double>(a) / b;
    
    std::cout << "Result: " << result << std::endl;  // Das korrekte Ergebnis ist 2.5.

9.0.3 Übungsaufgaben zu auto

  1. Verwendung von auto
  2. Iterieren mit auto
  3. Funktionsrückgabetyp mit auto
  4. Komplexe Typen und auto

9.0.4 Musterlösungen

  1. Verwendung von auto

    auto var = 5.0;
    std::cout << typeid(var).name() << std::endl; // Dies wird "double" sein.
  2. Iterieren mit auto

    std::vector<int> numbers = {1, 2, 3, 4, 5};
    for (auto num : numbers) {
        std::cout << num << " ";
    }
  3. Funktionsrückgabetyp mit auto

    auto multiply(auto a, auto b) {
        return a * b;
    }
    
    std::cout << multiply(2, 5) << std::endl;      // 10
    std::cout << multiply(2.5, 3.0) << std::endl;  // 7.5
  4. Komplexe Typen und auto

    std::map<std::string, std::vector<int>> data = {
        {"Alice", {1, 2, 3}},
        {"Bob", {4, 5, 6}}
    };
    
    for (auto const& pair : data) {
        std::cout << pair.first << ": ";
        for (auto num : pair.second) {
            std::cout << num << " ";
        }
        std::cout << std::endl;
    }

9.0.5 Übungsaufgaben zum Thema Variablen und Arrays

  1. Variablen-Grundlagen
  2. Einfache Array-Operationen
  3. Berechnung des Durchschnitts

9.0.6 Musterlösungen

  1. Variablen-Grundlagen

    int alter = 25;
    float durchschnittsnote = 3.2f;
    std::cout << "Alter: " << alter << "\nDurchschnittsnote: " << durchschnittsnote << std::endl;
  2. Einfache Array-Operationen

    int zahlen[] = {5, 10, 15, 20};
    zahlen[2] = 25;
    for(int i = 0; i < 4; i++) {
        std::cout << zahlen[i] << " ";
    }
    std::cout << std::endl;
  3. Berechnung des Durchschnitts

    double noten[] = {1.5, 2.0, 2.5, 3.0, 4.0};
    double summe = 0.0;
    for(int i = 0; i < 5; i++) {
        summe += noten[i];
    }
    double durchschnitt = summe / 5;
    std::cout << "Durchschnitt: " << durchschnitt << std::endl;

9.0.7 Übungsaufgaben zu C++-Strings

  1. Grundlagen der String-Manipulation
  2. String-Länge
  3. Substring und Suche
  4. String-Konkatenation
  5. Umkehrung eines Strings

9.0.8 Musterlösungen

  1. Grundlagen der String-Manipulation

    std::string name = "Anna";
    std::string begrüßung = "Hallo, " + name + "!";
    std::cout << begrüßung << std::endl;
  2. String-Länge

    std::string wort = "Programmieren";
    std::cout << wort.size() << std::endl;
  3. Substring und Suche

    std::string satz = "C++ ist eine wunderbare Programmiersprache.";
    size_t position = satz.find("wunderbare");
    if (position != std::string::npos) {
        std::cout << "Position: " << position << std::endl;
        std::cout << "Substring: " << satz.substr(position, 10) << std::endl;
    }
  4. String-Konkatenation

    std::string string1 = "Hello";
    std::string string2 = "World";
    std::cout << string1 + " " + string2 << std::endl;
  5. Umkehrung eines Strings

    std::string umkehrung = "abcd";
    std::reverse(umkehrung.begin(), umkehrung.end());
    std::cout << umkehrung << std::endl;

9.0.9 Übungsaufgaben zum Thema Funktionen

  1. Einfache Funktion
  2. Funktion mit Parameter (Call by Value)
  3. Funktion mit Referenzparameter (Call by Reference)
  4. Überladene Funktionen
  5. Rekursion

9.0.10 Musterlösungen

  1. Einfache Funktion

    void gruß() {
        std::cout << "Hallo, Welt!" << std::endl;
    }
    
    int main() {
        gruß();
        return 0;
    }
  2. Funktion mit Parameter (Call by Value)

    int quadrieren(int num) {
        return num * num;
    }
    
    int main() {
        int zahl = 5;
        std::cout << quadrieren(zahl) << std::endl;  // Sollte 25 ausgeben
        return 0;
    }
  3. Funktion mit Referenzparameter (Call by Reference)

    void tausche(int &a, int &b) {
        int temp = a;
        a = b;
        b = temp;
    }
    
    int main() {
        int x = 5, y = 10;
        tausche(x, y);
        std::cout << "x: " << x << ", y: " << y << std::endl;  // Sollte "x: 10, y: 5" ausgeben
        return 0;
    }
  4. Überladene Funktionen

    void druckeDaten(int num) {
        std::cout << "Integer: " << num << std::endl;
    }
    
    void druckeDaten(std::string text) {
        std::cout << "Text: " << text << std::endl;
    }
    
    int main() {
        druckeDaten(5);       // Sollte "Integer: 5" ausgeben
        druckeDaten("C++");  // Sollte "Text: C++" ausgeben
        return 0;
    }
  5. Rekursion

    int faktorial(int n) {
        if (n <= 1) {
            return 1;
        }
        return n * faktorial(n - 1);
    }
    
    int main() {
        std::cout << faktorial(5) << std::endl;  // Sollte 120 ausgeben
        return 0;
    }

9.0.11 Übungsaufgaben Arithmetik

  1. Grundlegende Rechenoperationen
  2. Modulo-Operator
  3. Berechnung des Durchschnitts
  4. Zinsberechnung
  5. Quadrat und Wurzel

9.0.12 Musterlösungen

  1. Grundlegende Rechenoperationen

    int a = 5, b = 3, summe, differenz;
    summe = a + b;
    differenz = a - b;
    std::cout << "Summe: " << summe << ", Differenz: " << differenz << std::endl;
  2. Modulo-Operator

    int x = 6;
    if (x % 2 == 0) {
        std::cout << x << " ist gerade." << std::endl;
    } else {
        std::cout << x << " ist ungerade." << std::endl;
    }
  3. Berechnung des Durchschnitts

    double zahlen[] = {10.5, 20.5, 30.5};
    double summe = 0.0;
    for (int i = 0; i < 3; i++) {
        summe += zahlen[i];
    }
    double durchschnitt = summe / 3;
    std::cout << "Durchschnitt: " << durchschnitt << std::endl;
  4. Zinsberechnung

    double P = 1000.0, r = 0.05, t = 2.0;
    double zins = P * r * t;
    std::cout << "Zins: " << zins << "€" << std::endl;
  5. Quadrat und Wurzel

    #include <cmath>
    
    double num = 5.0;
    double quad = pow(num, 2);
    double wurzel = sqrt(quad);
    std::cout << "Quadrat: " << quad << ", Wurzel: " << wurzel << std::endl;

9.0.13 Übungsaufgaben zu logischen und bitweisen Operationen

  1. Logische Operatoren
  2. Bitweise UND-Operation
  3. Bitweise Rechtsverschiebung
  4. Bitweise ODER- und XOR-Operationen
  5. Eins-Komplement

9.0.14 Musterlösungen

  1. Logische Operatoren

    bool a = true, b = false;
    std::cout << (a && b) << std::endl; // Ausgabe: 0
    std::cout << (a || b) << std::endl; // Ausgabe: 1
    std::cout << (!a) << std::endl;     // Ausgabe: 0
  2. Bitweise UND-Operation

    int x = 5, y = 3;
    std::cout << (x & y) << std::endl;  // Ausgabe: 1
  3. Bitweise Rechtsverschiebung

    int z = 16;
    std::cout << (z >> 2) << std::endl;  // Ausgabe: 4
  4. Bitweise ODER- und XOR-Operationen

    int m = 12, n = 25;
    std::cout << (m | n) << std::endl;  // Ausgabe: 29
    std::cout << (m ^ n) << std::endl;  // Ausgabe: 21
  5. Eins-Komplement

    int p = 8;
    std::cout << (~p) << std::endl;  // Ausgabe: -9

9.0.15 Übungsaufgaben zu Kontrollstrukturen

  1. If-Else-Struktur
  2. Switch-Case-Struktur
  3. While-Schleife
  4. For-Schleife
  5. Ternärer Operator
  6. Do-While-Schleife

9.0.16 Musterlösungen

  1. If-Else-Struktur

    int num = 5;
    if (num > 0) {
        std::cout << "Positiv" << std::endl;
    } else if (num < 0) {
        std::cout << "Negativ" << std::endl;
    } else {
        std::cout << "Null" << std::endl;
    }
  2. Switch-Case-Struktur

    int auswahl;
    std::cin >> auswahl;
    switch (auswahl) {
        case 1:
            std::cout << "Option 1 gewählt!" << std::endl;
            break;
        case 2:
            std::cout << "Option 2 gewählt!" << std::endl;
            break;
        case 3:
            std::cout << "Option 3 gewählt!" << std::endl;
            break;
        default:
            std::cout << "Ungültige Option!" << std::endl;
            break;
    }
  3. While-Schleife

    int i = 1;
    while (i <= 10) {
        std::cout << i << std::endl;
        i++;
    }
  4. For-Schleife

    int n = 5;
    int fakultät = 1;
    for (int j = 1; j <= n; j++) {
        fakultät *= j;
    }
    std::cout << "Fakultät von " << n << " ist " << fakultät << std::endl;
  5. Ternärer Operator

    int a = 4, b = 7;
    int größer = (a > b) ? a : b;
    std::cout << "Größerer Wert: " << größer << std::endl;
  6. Do-While-Schleife

    char eingabe;
    do {
        std::cout << "Geben Sie 'y' oder 'n' ein: ";
        std::cin >> eingabe;
    } while (eingabe != 'y' && eingabe != 'n');

9.0.17 Übungsaufgaben zum Thema Initialisierung

  1. Einfache Initialisierung und Zuweisung
  2. Mehrfache Zuweisung
  3. Listenzuweisung
  4. Initialisierung mit auto
  5. Listenzuweisung für Strukturen

9.0.18 Musterlösungen

  1. Einfache Initialisierung und Zuweisung

    int a = 10;
    a = 20;
  2. Mehrfache Zuweisung

    int b, c, d;
    b = c = d = 5;
  3. Listenzuweisung

    int e[5] = {1, 2, 3, 4, 5};
  4. Initialisierung mit auto

    auto f = 3.14;
  5. Listenzuweisung für Strukturen

    struct Person {
        std::string name;
        int alter;
    };
    
    Person p = {"Max", 25};

9.0.19 Übungsaufgaben zu Namespaces und Union

  1. Einfacher Namespace
  2. Verschachtelte Namespaces
  3. Union
  4. Namespaces mit using-Direktive

9.0.20 Musterlösungen

  1. Einfacher Namespace

    namespace Math {
        int quadrat(int x) {
            return x * x;
        }
    }
    
    int main() {
        std::cout << Math::quadrat(5) << std::endl; // 25
        return 0;
    }
  2. Verschachtelte Namespaces

    namespace Geometrie {
        namespace Zweidimensional {
            float flaecheRechteck(float breite, float hoehe) {
                return breite * hoehe;
            }
        }
    }
    
    int main() {
        std::cout << Geometrie::Zweidimensional::flaecheRechteck(5.0f, 4.0f) << std::endl; // 20
        return 0;
    }
  3. Union

    union Daten {
        int integerWert;
        float floatWert;
    };
    
    int main() {
        Daten d;
        d.integerWert = 42;
        std::cout << d.integerWert << std::endl; // 42
        d.floatWert = 3.14;
        std::cout << d.floatWert << std::endl; // 3.14
        return 0;
    }
  4. Namespaces mit using-Direktive

    namespace Math {
        int quadrat(int x) {
            return x * x;
        }
    }
    
    using namespace Math;
    
    int main() {
        std::cout << quadrat(6) << std::endl; // 36
        return 0;
    }

9.0.21 Übungsaufgaben zu Zeigern, Referenzen und Smart Pointern

  1. Zeiger und dynamische Speicherzuweisung
  2. Referenzen
  3. std::unique_ptr
  4. std::shared_ptr
  5. std::weak_ptr

9.0.22 Musterlösungen

  1. Zeiger und dynamische Speicherzuweisung

    int* ptr = new int;
    *ptr = 42;
    std::cout << *ptr << std::endl;  // 42
    delete ptr;
  2. Referenzen

    void vertausche(int &a, int &b) {
        int temp = a;
        a = b;
        b = temp;
    }
    
    int main() {
        int x = 5, y = 10;
        vertausche(x, y);
        std::cout << x << " " << y << std::endl;  // 10 5
    }
  3. std::unique_ptr

    std::unique_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});
    for(int i = 0; i < 5; i++) {
        std::cout << arr[i] << " ";
    }
  4. std::shared_ptr

    std::shared_ptr<int> sp1 = std::make_shared<int>(10);
    std::shared_ptr<int> sp2 = sp1;
    *sp2 = 20;
    std::cout << *sp1 << std::endl;  // 20
  5. std::weak_ptr

    std::shared_ptr<int> sp3 = std::make_shared<int>(30);
    std::weak_ptr<int> wp = sp3;
    
    // Nachdem der shared_ptr gelöscht wurde, überprüfen, ob der weak_ptr noch zugreifen kann
    sp3.reset();
    
    if (auto spt = wp.lock()) {
        std::cout << *spt << std::endl;
    } else {
        std::cout << "sp3 ist nicht mehr verfügbar." << std::endl;
    }

9.0.23 Übungsaufgaben Move-Semantik und Perfect Forwarding

  1. Move-Semantik mit std::string
  2. Eigene Klasse mit Move-Konstruktor
  3. Perfect Forwarding mit Funktionen

9.0.24 Musterlösungen

  1. Move-Semantik mit std::string

    std::string str1 = "Hallo Welt!";
    std::string str2 = std::move(str1);
    std::cout << "str2: " << str2 << std::endl;  // "Hallo Welt!"
  2. Eigene Klasse mit Move-Konstruktor

    class DynamischesArray {
        int* data;
        size_t size;
    public:
        DynamischesArray(size_t s) : size(s), data(new int[s]) {}
    
        // Move-Konstruktor
        DynamischesArray(DynamischesArray&& other) noexcept : data(other.data), size(other.size) {
            other.data = nullptr;
            other.size = 0;
        }
    
        // Move-Zuweisungsoperator
        DynamischesArray& operator=(DynamischesArray&& other) noexcept {
            if (this != &other) {
                delete[] data;
                data = other.data;
                size = other.size;
                other.data = nullptr;
                other.size = 0;
            }
            return *this;
        }
    
        ~DynamischesArray() { delete[] data; }
    };
  3. Perfect Forwarding mit Funktionen

    template<typename T>
    void ziel(T&& arg) {
        // Hier können Sie mit arg arbeiten
        std::cout << arg << std::endl;
    }
    
    template<typename T>
    void weiterleiten(T&& arg) {
        ziel(std::forward<T>(arg));
    }
    
    int main() {
        std::string test = "Test String";
        weiterleiten(test);           // Lvalue
        weiterleiten("Temp String");  // Rvalue
    }

9.0.25 Übungsaufgaben Aufzählungen

  1. Grundlegende Enums
  2. Enumeratoren mit spezifischen Werten
  3. Verwendung von enum class
  4. Vergleich zwischen enum und enum class

9.0.26 Musterlösungen

  1. Grundlegende Enums

    enum Wochentag {Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag, Sonntag};
    
    void printTag(Wochentag tag) {
        switch(tag) {
            case Montag: std::cout << "Montag"; break;
            // ... Andere Tage entsprechend ...
            case Sonntag: std::cout << "Sonntag"; break;
        }
    }
  2. Enumeratoren mit spezifischen Werten

    enum Monat {Januar = 31, Februar = 28 /* ... und so weiter ... */};
  3. Verwendung von enum class

    enum class Ampel {Rot, Gelb, Grün};
    
    void ampelStatus(Ampel a) {
        switch(a) {
            case Ampel::Rot: std::cout << "Stoppen"; break;
            case Ampel::Gelb: std::cout << "Vorsicht"; break;
            case Ampel::Grün: std::cout << "Weiterfahren"; break;
        }
    }
  4. Vergleich zwischen enum und enum class

    enum Farben1 {Rot, Blau, Grün};
    enum Farben2 {Gelb, Blau, Schwarz}; // Kompilierungsfehler wegen "Blau"
    
    enum class FarbenA {Rot, Blau, Grün};
    enum class FarbenB {Gelb, Blau, Schwarz}; // Kein Problem hier

Beim letzten Beispiel sehen Sie, dass bei der Verwendung von enum Namenskonflikte auftreten können, während enum class solche Konflikte verhindert. Dies macht enum class sicherer und ist einer der Gründe, warum es in modernem C++ bevorzugt wird.

9.0.27 Übungsaufgaben Objektorientierung

  1. Grundlegende Klasse
  2. Konstruktor-Überladung
  3. Destruktor
  4. Vererbung
  5. Schutzmodifizierer

9.0.28 Musterlösungen

  1. Grundlegende Klasse

    class Person {
    private:
        std::string name;
        int age;
    public:
        void setName(const std::string& n) { name = n; }
        void setAge(int a) { age = a; }
        std::string getName() const { return name; }
        int getAge() const { return age; }
    };
  2. Konstruktor-Überladung

    Person() : name(""), age(0) {}
    Person(const std::string& n, int a) : name(n), age(a) {}
  3. Destruktor

    ~Person() {
        std::cout << "Person " << name << " wurde zerstört." << std::endl;
    }
  4. Vererbung

    class Student : public Person {
    private:
        int matriculationNumber;
    public:
        void setMatriculationNumber(int m) { matriculationNumber = m; }
        int getMatriculationNumber() const { return matriculationNumber; }
    };
  5. Schutzmodifizierer

    protected:
        std::string name;

    Indem das name-Attribut auf protected gesetzt wird, erhalten abgeleitete Klassen wie Student direkten Zugriff darauf, ohne die Getter- oder Setter-Methoden verwenden zu müssen.

9.0.29 Übungsaufgaben zu static

static in C++ kann in verschiedenen Kontexten verwendet werden: für Klassenmember, in Funktionen und für globale Variablen in Dateien. Die folgenden Übungsaufgaben helfen Ihnen, dieses Schlüsselwort besser zu verstehen:

  1. Statische Klassenvariablen
  2. Statische Klassenmethoden
  3. Statische Funktionen in Funktionen
  4. Statische globale Variablen

9.0.30 Musterlösungen

  1. Statische Klassenvariablen

    class Auto {
    private:
        int id;
        static int zaehler;
    public:
        Auto() {
            id = zaehler++;
        }
        int getID() const {
            return id;
        }
    };
    int Auto::zaehler = 0;
  2. Statische Klassenmethoden

    class Auto {
        // ... (wie oben)
    public:
        static int getZaehler() {
            return zaehler;
        }
    };
  3. Statische Funktionen in Funktionen

    void zaehleAufrufe() {
        static int aufrufe = 0;
        aufrufe++;
        std::cout << "Diese Funktion wurde " << aufrufe << " Mal aufgerufen." << std::endl;
    }
  4. Statische globale Variablen

    static int verborgeneVariable = 5;
    
    int manipuliereVariable(int wert) {
        verborgeneVariable += wert;
        return verborgeneVariable;
    }
    extern int verborgeneVariable; // Dies wird einen Kompilierungsfehler verursachen.

Durch die Verwendung des Schlüsselworts static auf eine globale Variable in datei1.cpp wird die Sichtbarkeit dieser Variable auf diese Datei beschränkt, sodass sie nicht von anderen Dateien, wie datei2.cpp, gesehen oder darauf zugegriffen werden kann.

9.0.31 Übungsaufgaben zu Vererbung, virtual und Polymorphie

  1. Grundlegende Vererbung
  2. Polymorpher Methodenaufruf
  3. Verwendung von virtual
  4. Reine virtuelle Methoden und abstrakte Klassen
  5. Destruktoren und virtual

9.0.32 Musterlösungen

  1. Grundlegende Vererbung

    class Fahrzeug {
    protected:
        std::string marke;
        std::string modell;
    public:
        // Getter und Setter...
    };
    
    class Auto : public Fahrzeug {
        // Spezifische Eigenschaften und Methoden für Auto...
    };
    
    class Motorrad : public Fahrzeug {
        // Spezifische Eigenschaften und Methoden für Motorrad...
    };
  2. & 3. Polymorpher Methodenaufruf & Verwendung von virtual

    class Fahrzeug {
        // ... wie oben
    public:
        virtual void anzeigen() {
            std::cout << "Dies ist ein Fahrzeug der Marke " << marke << std::endl;
        }
    };
    
    class Auto : public Fahrzeug {
        // ... wie oben
    public:
        void anzeigen() override {
            std::cout << "Dies ist ein Auto der Marke " << marke << std::endl;
        }
    };
    
    class Motorrad : public Fahrzeug {
        // ... wie oben
    public:
        void anzeigen() override {
            std::cout << "Dies ist ein Motorrad der Marke " << marke << std::endl;
        }
    };
    
    Fahrzeug* f = new Auto();
    f->anzeigen();  // Gibt nun die spezifische Nachricht für ein Auto aus.
    delete f;
  3. Reine virtuelle Methoden und abstrakte Klassen

    class Fahrzeug {
        // ... wie oben
    public:
        virtual void anzeigen() = 0;  // Reine virtuelle Methode
    };

    Die Klasse Fahrzeug ist nun eine abstrakte Klasse und kann nicht instanziiert werden.

  4. Destruktoren und virtual

    class Fahrzeug {
        // ... wie oben
    public:
        virtual ~Fahrzeug() {
            std::cout << "Fahrzeug-Destruktor aufgerufen!" << std::endl;
        }
    };

    Ein virtual-Destruktor stellt sicher, dass beim Löschen eines Zeigers auf ein Basisobjekt der richtige abgeleitete Destruktor aufgerufen wird.

9.0.33 Übungsaufgaben zur Operatorüberladung

  1. Einfache Operatorüberladung
  2. Vergleichsoperatoren
  3. Ein- und Ausgabeoperatoren
  4. Zuweisungsoperator
  5. Erweiterte arithmetische Operatoren

9.0.34 Musterlösungen

  1. Einfache Operatorüberladung

    class Zahl {
    private:
        int wert;
    
    public:
        Zahl(int w) : wert(w) {}
    
        Zahl operator+(const Zahl& z) {
            return Zahl(wert + z.wert);
        }
    };
  2. Vergleichsoperatoren

    bool operator==(const Zahl& z) const {
        return wert == z.wert;
    }
    
    bool operator!=(const Zahl& z) const {
        return !(*this == z);
    }
  3. Ein- und Ausgabeoperatoren

    friend std::istream& operator>>(std::istream& in, Zahl& z) {
        in >> z.wert;
        return in;
    }
    
    friend std::ostream& operator<<(std::ostream& out, const Zahl& z) {
        out << z.wert;
        return out;
    }
  4. Zuweisungsoperator

    class Vektor {
    public:
        double x, y;
    
        Vektor& operator=(const Vektor& v) {
            if (this != &v) {
                x = v.x;
                y = v.y;
            }
            return *this;
        }
    };
  5. Erweiterte arithmetische Operatoren

    Vektor operator+(const Vektor& v) const {
        return Vektor(x + v.x, y + v.y);
    }
    
    Vektor operator-(const Vektor& v) const {
        return Vektor(x - v.x, y - v.y);
    }
    
    double operator*(const Vektor& v) const {
        return x * v.x + y * v.y;
    }
    
    Vektor operator/(double scalar) const {
        return Vektor(x / scalar, y / scalar);
    }

9.0.35 Übungsaufgaben zu RAII (Resource Acquisition Is Initialization)

RAII ist ein Programmierparadigma in C++, bei dem Ressourcen, die während der Lebensdauer eines Objekts benötigt werden, während der Initialisierung des Objekts erworben und beim Zerstören des Objekts freigegeben werden. Es wird oft in Verbindung mit Ressourcen wie Speicher, Dateihandles oder Netzwerksockets verwendet.

  1. Einfacher Speichermanager
  2. Datei-Wrapper
  3. Mutex-Wrapper
  4. Smart Pointer
  5. Dynamisches Array

9.0.36 Musterlösungen

  1. Einfacher Speichermanager

    class SpeicherManager {
        int* daten;
    
    public:
        SpeicherManager(size_t groesse) {
            daten = new int[groesse];
        }
    
        ~SpeicherManager() {
            delete[] daten;
        }
    };
  2. Datei-Wrapper

    #include <fstream>
    
    class Datei {
        std::fstream dateiStream;
    
    public:
        Datei(const std::string& pfad) : dateiStream(pfad, std::ios::in | std::ios::out) {}
    
        ~Datei() {
            if (dateiStream.is_open()) {
                dateiStream.close();
            }
        }
    
        // Weitere Methoden zum Lesen und Schreiben...
    };
  3. Mutex-Wrapper

    #include <mutex>
    
    class MutexWrapper {
        std::mutex& m;
    
    public:
        MutexWrapper(std::mutex& mutex) : m(mutex) {
            m.lock();
        }
    
        ~MutexWrapper() {
            m.unlock();
        }
    };
  4. Smart Pointer

    template <typename T>
    class MeinSmartPointer {
        T* ptr;
    
    public:
        MeinSmartPointer(T* p) : ptr(p) {}
    
        ~MeinSmartPointer() {
            delete ptr;
        }
    
        T& operator*() { return *ptr; }
        T* operator->() { return ptr; }
    };
  5. Dynamisches Array

    class DynArray {
        int* daten;
        size_t groesse;
    
    public:
        DynArray(size_t g) : groesse(g) {
            daten = new int[groesse];
        }
    
        ~DynArray() {
            delete[] daten;
        }
    
        // Weitere Methoden, um auf das Array zuzugreifen...
    };

9.0.37 Übungsaufgaben zur Ausnahmebehandlung

Die Ausnahmebehandlung ist ein Mechanismus, um zur Laufzeit auftretende Probleme oder unerwartete Situationen zu behandeln und dabei eine saubere und geordnete Ressourcenfreigabe zu gewährleisten.

  1. Einfache Division
  2. Dateizugriff
  3. Dynamisches Array
  4. Benutzerdefinierte Ausnahme
  5. Ressourcenverwaltung

9.0.38 Musterlösungen

  1. Einfache Division

    double teile(int zaehler, int nenner) {
        if(nenner == 0) throw std::runtime_error("Teilen durch 0 ist nicht erlaubt!");
        return static_cast<double>(zaehler) / nenner;
    }
  2. Dateizugriff

    #include <fstream>
    #include <sstream>
    
    std::string leseDatei(const std::string& pfad) {
        std::ifstream datei(pfad);
        if(!datei.is_open()) throw std::runtime_error("Datei konnte nicht geöffnet werden!");
    
        std::stringstream ss;
        ss << datei.rdbuf();
        return ss.str();
    }
  3. Dynamisches Array

    class DynArray {
    private:
        int* daten;
        size_t groesse;
    public:
        // Konstruktor, Destruktor, ...
    
        int& at(size_t index) {
            if(index >= groesse) throw std::out_of_range("Ungültiger Index!");
            return daten[index];
        }
    };
  4. Benutzerdefinierte Ausnahme

    class MeineAusnahme : public std::exception {
    public:
        const char* what() const noexcept override {
            return "Eine benutzerdefinierte Ausnahme wurde geworfen!";
        }
    };
    
    void testeFunktion(int zahl) {
        if(zahl < 0) throw MeineAusnahme();
        // weitere Logik...
    }
    
    int main() {
        try {
            testeFunktion(-5);
        } catch(const MeineAusnahme& e) {
            std::cerr << e.what() << std::endl;
        }
        return 0;
    }
  5. Ressourcenverwaltung

    class RessourcenManager {
    private:
        int* ressource;
    public:
        RessourcenManager(size_t groesse) {
            ressource = new int[groesse];
            if(!ressource) throw std::runtime_error("Ressource konnte nicht erworben werden!");
        }
    
        ~RessourcenManager() {
            delete[] ressource;
        }
    };

9.0.39 Übungsaufgaben zu Templates

Templates in C++ bieten die Möglichkeit, generischen Code für verschiedene Datentypen zu schreiben. Hier sind einige Übungsaufgaben, um Ihr Wissen über Templates zu vertiefen:

  1. Generischer Swap
  2. Generische Klasse Stack
  3. Begrenzte Template-Klassen
  4. Generische Summenfunktion
  5. Spezialisierung

9.0.40 Musterlösungen

  1. Generischer Swap

    template <typename T>
    void swap(T& a, T& b) {
        T temp = a;
        a = b;
        b = temp;
    }
  2. Generische Klasse Stack

    template <typename T>
    class Stack {
    private:
        std::vector<T> elements;
    public:
        void push(const T& elem) { elements.push_back(elem); }
        void pop() { elements.pop_back(); }
        T& top() { return elements.back(); }
    };
  3. Begrenzte Template-Klassen

    #include <type_traits>
    
    template <typename T>
    class Numeric {
        static_assert(std::is_arithmetic<T>::value, "Nur numerische Datentypen erlaubt!");
        T value;
    };
  4. Generische Summenfunktion

    template <typename Container>
    auto sum(const Container& c) -> decltype(*c.begin() + *c.begin()) {
        using T = decltype(*c.begin() + *c.begin());
        T result = T();
        for (const auto& elem : c) {
            result += elem;
        }
        return result;
    }
  5. Spezialisierung

    template <typename T>
    class Vektor {
        std::vector<T> data;
        // ...
    };
    
    template <>
    class Vektor<bool> {
        std::vector<uint8_t> data;
        // Implementieren Sie die Speicherung bitweise
    };

9.0.41 Übungsaufgaben zu Lambda-Ausdrücken

Lambda-Ausdrücke sind anonyme Funktionen, die in C++ zum Schreiben von kurzen, in den Code eingebetteten Funktionen verwendet werden können. Hier sind einige Übungsaufgaben, um Ihr Wissen über Lambda-Ausdrücke zu vertiefen:

  1. Grundlegende Lambda
  2. Lambda mit Capture
  3. Lambda als Funktionsargument
  4. Rückgabe von Lambda
  5. Lambda mit mehreren Capture-Varianten

9.0.42 Musterlösungen

  1. Grundlegende Lambda

    auto add = [](int a, int b) -> int {
        return a + b;
    };
    std::cout << add(3, 4);  // Ausgabe: 7
  2. Lambda mit Capture

    int value = 10;
    auto multiply = [value](int a) -> int {
        return a * value;
    };
    std::cout << multiply(5);  // Ausgabe: 50
  3. Lambda als Funktionsargument

    std::vector<int> filter(const std::vector<int>& values, std::function<bool(int)> predicate) {
        std::vector<int> result;
        for (int value : values) {
            if (predicate(value)) {
                result.push_back(value);
            }
        }
        return result;
    }
    
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    auto even = [](int n) { return n % 2 == 0; };
    auto evenNumbers = filter(numbers, even);
  4. Rückgabe von Lambda

    auto multiplier(int factor) {
        return [factor](int x) -> int {
            return x * factor;
        };
    }
    
    auto doubleIt = multiplier(2);
    std::cout << doubleIt(5);  // Ausgabe: 10
  5. Lambda mit mehreren Capture-Varianten

    int x = 5, y = 7;
    
    auto byValue = [=]() { return x + y; };
    auto byReference = [&]() { y *= 2; return x + y; };
    auto mixedCapture = [x, &y]() { y += x; return y; };
    
    std::cout << byValue() << std::endl;       // Ausgabe: 12
    std::cout << byReference() << std::endl;   // Ausgabe: 19 (y wird zu 14 verdoppelt)
    std::cout << mixedCapture() << std::endl;  // Ausgabe: 19 (y wird zu 12 durch Hinzufügen von x)