Klassen, Structs & Kapselung
Verstehe den Unterschied zwischen struct und class, Zugriffsspezifizierer, Kapselung, const-Memberfunktionen, statische Member und moderne Aggregat-Initialisierung.
Was ist eine Klasse?
Eine Klasse in C++ ist ein benutzerdefinierter Typ, der Daten (Membervariablen) und Operationen auf diesen Daten (Memberfunktionen) zu einer einzigen Einheit bündelt. Dieses Bündeln nennt man Kapselung — eine der vier Säulen der OOP.
Die zentrale Erkenntnis: Eine Klasse definiert eine Invariante, also eine Menge von Regeln, die über ihren internen Zustand immer gelten müssen. Eine Date-Klasse könnte zum Beispiel verlangen, dass der Monat immer zwischen 1 und 12 liegt. Kapselung erlaubt dir, solche Invarianten durchzusetzen, indem du den Zugriff auf den internen Zustand kontrollierst.
struct vs. class
In C++ sind struct und class nahezu identisch. Der einzige Unterschied ist der voreingestellte Zugriffsspezifizierer: Bei struct ist er public, bei class ist er private. Konvention: Verwende struct für reine Datenaggregate und class, wenn du Invarianten zu wahren hast.
#include <iostream>
#include <string>
// struct: members are public by default
struct Point {
double x; // public
double y; // public
};
// class: members are private by default
class BankAccount {
std::string owner; // private
double balance; // private
public:
BankAccount(const std::string& name, double initial)
: owner{name}, balance{initial} {}
double get_balance() const { return balance; }
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
};
int main() {
Point p{3.0, 4.0}; // aggregate initialization
std::cout << p.x << ", " << p.y << '\n';
BankAccount acct{"Alice", 1000.0};
acct.deposit(500.0);
std::cout << acct.get_balance() << '\n'; // 1500
// acct.balance = -999; // ERROR: private member
return 0;
}Zugriffsspezifizierer & der this-Zeiger
C++ bietet drei Zugriffsebenen: public (jeder darf zugreifen), private (nur die Klasse selbst und ihre friends) und protected (die Klasse, ihre friends und abgeleitete Klassen). Der this-Zeiger ist ein impliziter Zeiger auf das aktuelle Objekt, der in jeder nicht-statischen Memberfunktion zur Verfügung steht.
#include <iostream>
#include <string>
class Employee {
private:
std::string name_;
int id_;
protected:
double salary_; // accessible to derived classes
public:
Employee(const std::string& name, int id, double salary)
: name_{name}, id_{id}, salary_{salary} {}
// const member function: promises not to modify the object
const std::string& get_name() const { return name_; }
int get_id() const { return id_; }
// 'this' pointer usage — return *this for method chaining
Employee& set_name(const std::string& name) {
this->name_ = name; // 'this->' disambiguates
return *this;
}
Employee& set_id(int id) {
this->id_ = id;
return *this;
}
void print() const {
std::cout << "Employee{" << name_ << ", #" << id_
<< ", $" << salary_ << "}\n";
}
};
int main() {
Employee e{"Bob", 42, 75000.0};
e.set_name("Robert").set_id(43); // method chaining via *this
e.print(); // Employee{Robert, #43, $75000}
return 0;
}Statische Member & friend
Statische Member gehören zur Klasse, nicht zu einer einzelnen Instanz. Verwende sie für gemeinsam genutzten Zustand oder für Hilfsfunktionen. Das Schlüsselwort friend gewährt einer Nicht-Member-Funktion oder einer anderen Klasse Zugriff auf private Member — setze es sparsam ein, denn es durchbricht die Kapselung.
#include <iostream>
class Widget {
int value_;
// C++17: inline static — define in header, no .cpp needed
inline static int count_ = 0;
public:
explicit Widget(int v) : value_{v} { ++count_; }
~Widget() { --count_; }
static int get_count() { return count_; }
// friend function can access private members
friend std::ostream& operator<<(std::ostream& os, const Widget& w);
};
std::ostream& operator<<(std::ostream& os, const Widget& w) {
return os << "Widget(" << w.value_ << ')'; // accesses private value_
}
int main() {
Widget a{10}, b{20};
std::cout << Widget::get_count() << '\n'; // 2
std::cout << a << '\n'; // Widget(10)
{
Widget c{30};
std::cout << Widget::get_count() << '\n'; // 3
}
std::cout << Widget::get_count() << '\n'; // 2
return 0;
}Aggregat-Initialisierung & designierte Initialisierer (C++20)
Ein Aggregat ist eine Klasse oder ein Struct ohne benutzerdeklarierte Konstruktoren, ohne private oder protected nicht-statische Datenmember, ohne virtuelle Funktionen und ohne virtuelle Basisklassen. C++20 ergänzt designierte Initialisierer, mit denen du benennen kannst, welche Member du gerade initialisierst.
#include <iostream>
#include <string>
struct Config {
std::string host = "localhost";
int port = 8080;
bool use_tls = false;
int max_connections = 100;
};
int main() {
// Traditional aggregate initialization
Config c1{"example.com", 443, true, 200};
// C++20 designated initializers — much more readable
Config c2{
.host = "api.example.com",
.port = 443,
.use_tls = true
// .max_connections uses default value 100
};
std::cout << c2.host << ':' << c2.port << '\n';
std::cout << "TLS: " << std::boolalpha << c2.use_tls << '\n';
std::cout << "Max: " << c2.max_connections << '\n'; // 100
return 0;
}Mache Datenmember standardmäßig private und gib über eine minimale öffentliche Schnittstelle nur das nach außen, was wirklich gebraucht wird. Hat eine Klasse keine Invariante zu wahren (ist sie also nur ein Bündel von Daten), verwende stattdessen ein struct mit öffentlichen Membern. Schreibe nicht für jedes Feld Getter und Setter — das führt den Sinn der Kapselung ad absurdum. Biete stattdessen aussagekräftige Operationen wie deposit() und withdraw() an statt set_balance().
Wenn eine Memberfunktion den Zustand des Objekts nicht verändert, markiere sie als const. Ohne const kannst du die Funktion weder auf einem const-Objekt noch über eine const-Referenz aufrufen — und genau das passiert ständig, wenn Objekte per const& übergeben werden. Ein fehlendes const an einem Accessor ist in größeren Codebasen eine häufige Quelle von Kompilierfehlern.
structundclassunterscheiden sich nur im voreingestellten Zugriff:structist public,classist private- Kapselung schützt Klasseninvarianten — verwende
private-Daten mit einer aussagekräftigen öffentlichen Schnittstelle - Markiere nicht-verändernde Memberfunktionen mit
const, damit sie auch mitconst-Referenzen funktionieren - Verwende
inline static(C++17) für statische Member, die in Headern definiert werden - Die designierten Initialisierer aus C++20 machen die Aggregat-Initialisierung lesbar und selbsterklärend
friendsollte sparsam eingesetzt werden — typischerweise für Operatorüberladungen, die Zugriff auf private Member brauchen
Quiz — Teste dein Wissen
(15 XP)1. Was ist der EINZIGE Unterschied zwischen `struct` und `class` in C++?
2. Warum solltest du Accessor-Memberfunktionen als `const` markieren?
3. Was bewirkt `inline static int count_ = 0;` in C++17?