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;
}Mia: 150 1 0 120 Fehler: Betrag muss positiv sein
public/private/protectedsteuern die Sichtbarkeit. Beiclassist alles standardmäßig privat, beistructöffentlich- Der Konstruktor hat den Namen der Klasse; die Initialisierungsliste (
: a_(a), b_(b)) initialisiert Member direkt consthinter der Methode: Sie verändert das Objekt nicht und ist auch fürconst-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ö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;
}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;
}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;
}(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::moveverschiebt statt zu kopieren- Operatoren lassen sich überladen (
+,==,<<)
Aufgabe
Schreibe eine Klasse Bruch mit Zähler und Nenner (gekürzt), operator+, operator* und operator<<.