EMZETT.
Login

Kurz: Eine Klasse bündelt Daten (Member-Variablen) und Funktionen (Methoden). Von ihr erzeugst du Objekte.

Teil des Kurses C++

Kapitel 6 von 15 im Kurs C++ (Abschnitt „Objektorientierung“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

Eine Klasse bündelt Daten (Member-Variablen) und Funktionen (Methoden). Von ihr erzeugst du Objekte.

#include <iostream>
#include <string>
 
class Konto {
public:
    Konto(std::string inhaber, double startwert = 0)
        : inhaber_(std::move(inhaber)), stand_(startwert) {}      // Initialisierungsliste
 
    void einzahlen(double betrag) {
        if (betrag <= 0) throw std::invalid_argument("Betrag muss positiv sein");
        stand_ += betrag;
    }
    bool abheben(double betrag) {
        if (betrag > stand_) return false;
        stand_ -= betrag;
        return true;
    }
    double stand() const { return stand_; }                       // const: ändert das Objekt nicht
    const std::string& inhaber() const { return inhaber_; }
 
private:
    std::string inhaber_;
    double stand_;
};
 
int main() {
    Konto k("Mia", 100);
    k.einzahlen(50);
    std::cout << k.inhaber() << ": " << k.stand() << '\n';
    std::cout << k.abheben(30) << ' ' << k.abheben(1000) << ' ' << k.stand() << '\n';
    try { k.einzahlen(-5); } catch (const std::exception& e) { std::cout << "Fehler: " << e.what() << '\n'; }
    return 0;
}

Ausgabe:

Mia: 150
1 0 120
Fehler: Betrag muss positiv sein
  • public / private / protected steuern die Sichtbarkeit. Bei class ist alles standardmäßig privat, bei struct öffentlich
  • Der Konstruktor hat den Namen der Klasse; die Initialisierungsliste (: a_(a), b_(b)) initialisiert Member direkt
  • const hinter der Methode: Sie verändert das Objekt nicht und ist auch für const-Objekte aufrufbar
  • Üblich: Member mit Unterstrich (stand_) kennzeichnen

Destruktor und RAII

Der Destruktor (~Klasse()) läuft automatisch, wenn das Objekt ungültig wird. Hier gibst du Ressourcen frei:

#include <iostream>
#include <string>
 
class Datei {
public:
    explicit Datei(std::string name) : name_(std::move(name)) { std::cout << "öffne " << name_ << '\n'; }
    ~Datei() { std::cout << "schließe " << name_ << '\n'; }
    Datei(const Datei&) = delete;                 // nicht kopierbar
    Datei& operator=(const Datei&) = delete;
private:
    std::string name_;
};
 
int main() {
    Datei a("a.txt");
    {
        Datei b("b.txt");
        std::cout << "im Block\n";
    }                                             // b wird hier automatisch geschlossen
    std::cout << "ende von main\n";
    return 0;
}                                                 // a wird automatisch geschlossen

Ausgabe:

öffne a.txt
öffne b.txt
im Block
schließe b.txt
ende von main
schließe a.txt

Dieses Muster heißt RAII (Resource Acquisition Is Initialization): Ressourcen (Speicher, Dateien, Sperren, Verbindungen) gehören einem Objekt, das sie im Konstruktor erwirbt und im Destruktor freigibt. Dadurch kann nichts vergessen werden, auch bei Ausnahmen.

Kopieren und Verschieben (Rule of Zero / Five)

Hat eine Klasse nur Member, die sich selbst verwalten (std::string, std::vector, Smart Pointer), brauchst du nichts zu schreiben. Der Compiler erzeugt korrektes Kopieren, Verschieben und Zerstören (Rule of Zero). Nur wer eine Ressource roh verwaltet, muss alle fünf Spezialfunktionen selbst festlegen: Destruktor, Kopierkonstruktor, Kopierzuweisung, Move-Konstruktor, Move-Zuweisung (Rule of Five).

#include <iostream>
#include <string>
#include <vector>
#include <utility>
 
struct Spieler {
    std::string name;
    std::vector<int> punkte;
};
 
int main() {
    Spieler a{"Mia", {10, 20, 30}};
    Spieler b = a;                          // Kopie: alles wird dupliziert
    b.punkte.push_back(40);
    Spieler c = std::move(a);               // Verschieben: Inhalt wandert, a ist danach leer
    std::cout << a.punkte.size() << ' ' << b.punkte.size() << ' ' << c.punkte.size() << ' ' << c.name << '\n';
    return 0;
}

Ausgabe:

0 4 3 Mia

std::move markiert ein Objekt als “darf ausgeräumt werden”. Danach solltest du es nur noch zuweisen oder zerstören.

static, this, friend

#include <iostream>
 
class Zaehler {
public:
    Zaehler() { ++anzahl_; }
    ~Zaehler() { --anzahl_; }
    static int anzahl() { return anzahl_; }          // gehört der Klasse
    Zaehler& erhoehe() { ++wert_; return *this; }    // this: Zeiger auf das aktuelle Objekt
    int wert() const { return wert_; }
private:
    static inline int anzahl_ = 0;                   // inline: Definition direkt in der Klasse (C++17)
    int wert_ = 0;
};
 
int main() {
    Zaehler a;
    {
        Zaehler b;
        std::cout << Zaehler::anzahl() << '\n';
    }
    std::cout << Zaehler::anzahl() << ' ' << a.erhoehe().erhoehe().wert() << '\n';
    return 0;
}

Ausgabe:

2
1 2

Operatoren überladen

Eigene Klassen können sich wie eingebaute Typen verhalten:

#include <iostream>
#include <compare>
 
struct Vektor {
    double x = 0, y = 0;
    Vektor operator+(const Vektor& o) const { return {x + o.x, y + o.y}; }
    Vektor operator*(double k) const { return {x * k, y * k}; }
    bool operator==(const Vektor&) const = default;                    // automatisch (C++20)
    friend std::ostream& operator<<(std::ostream& os, const Vektor& v) { return os << '(' << v.x << ", " << v.y << ')'; }
};
 
int main() {
    Vektor a{1, 2}, b{3, 4};
    std::cout << a + b << ' ' << a * 3 << ' ' << (a == Vektor{1, 2}) << '\n';
    return 0;
}

Ausgabe:

(4, 6) (3, 6) 1

friend gibt einer Funktion Zugriff auf private Member. Nutze es sparsam.

struct oder class?

Faustregel: struct für reine Daten ohne Regeln (alles öffentlich), class für Typen mit Invarianten (Regeln, die immer gelten müssen) und privaten Daten.

Merke

  • Klasse = Daten + Methoden; public/private; Konstruktor mit Initialisierungsliste
  • const-Methoden ändern das Objekt nicht
  • Destruktoren und RAII geben Ressourcen automatisch frei
  • Rule of Zero: nur Member verwenden, die sich selbst verwalten
  • std::move verschiebt statt zu kopieren
  • Operatoren lassen sich überladen (+, ==, <<)

Übungsaufgabe

Schreibe eine Klasse Bruch mit Zähler und Nenner (gekürzt), operator+, operator* und operator<<.

Quiz zur Selbstkontrolle

Weiter im Kurs

Zurück: Strings und Container der Standardbibliothek

Weiter: Vererbung und Polymorphie

Alle Kapitel: C++ im Überblick