Prof. Dr. Malte Schilling
Autonomous Intelligent Systems Group
aus Definition 8.2 (Vahrenhold 2022d), basierend auf (Echtle und Goedicke 2000):
“Ein (korrekt modelliertes) Objekt modelliert ein gedanklich abgegrenztes Gebilde mit allen seinen Eigenschaften und Verhaltensweisen.”
class
eingeleitet.this
ist ein Pointer auf das instanziierte Objekt und in jeder Klasse vorhanden..h
oder .hpp
) und die Definitionen in einer Source-Datei (.cpp
).Bei größeren Projekten trennt man meist Deklarationen und Definitionen in - Header-Dateien und - Implementierungs-Dateien
→ Erlaubt separate Kompilierung/Verwendung von Prebuilt-Libraries.
Beim Kompilieren müssen alle Implementierungsdateien angegeben werden: gcc -Wall -o foo max.c foo.c
Die Aufeilung in Header und Implementierung ist nicht immer einfach. Generell gilt:
public
, private
oder protected
deklariert werden.public
Member zugegriffen werden.private
Member können nur Objekte der Klasse zugreifen.protected
Member können auch Objekte abgeleiteter Klassen zugreifen.private
.class
auch struct
verwendet werden. Dann ist die Standardsichtbarkeit public.const
const
kann bei Member-Funktionen angegeben werden.const
sind (z. B. die Referenz wr
), können nur konstante Member-Funktionen ausführen.delete
auf dem Objekt, Objekt ist statisch und Programm terminiert)._i(i), _j(j)
auf der vorherigen Folie).Initialisiere alle member objects in der member initialization list.
public
-Vererbung vs. private
-Vererbung.public
-Vererbung in C++.Bei einfacher Vererbung besitzt eine Klasse genau eine Oberklasse.
Derived
wird mit : public Base
die Vererbung angegeben.Derived
kann explizit ein Konstruktor von Base
aufgerufen werden (ansonsten automatisch der Standardkonstruktor von Base
).protected
- und public
-Attribute einer Basisklasse behalten ihre Sichtbarkeit in der abgeleiteten Klasse.customer.cpp
an.customer.cpp
gemäß dem UML-Diagramm.deposit
, withdraw
und transfer
.customer.cpp
eine main
-Funktion und testen Sie die Klasse Account.Hinweis: Kompilieren Sie Ihr Programm mit g++ -std=c++20 -Wall -Wextra -o customer customer.cpp
Person
mit einem Namen und einem Alter.Customer
, die von Person
erbt und dazu einen Account
und eine ID (z. B. unsigned int
) besitzt.print
hinzu, die den Zustand des Objekts (Werte aller Attribute) ausgibt.virtual
!int main() {
std::cout << std::boolalpha;
Account a{ 25 };
a.deposit( 75 );
a.deposit( 50 );
std::cout << (a.balance() == 150) << std::endl; // true
std::cout << a.withdraw( 25 ) << std::endl; // 25
std::cout << (a.balance() == 125) << std::endl; // true
std::cout << a.withdraw( 150 ) << std::endl; // 0
Account b{ a };
std::cout << a.transfer( 50, a) << std::endl; // false
std::cout << a.transfer( 126, b ) << std::endl; // false
std::cout << a.transfer( 125, b ) << std::endl; // true
std::cout << (a.balance() == 0) << std::endl; // true
std::cout << (b.balance() == 250) << std::endl; // true
}
Aufgabe zum Anlegen einer Klasse und von Beispielinstanzen
Im jupyter-book ist die Aufgaben angegeben:
Hierüber kann dann direkt auf dem Hub eine Umgebung gestartet werden, in der C++ interpretiert wird (wird die ersten Termine genutzt):
Access | public | protected | private |
---|---|---|---|
members of the same class | yes | yes | yes |
members of derived classes | yes | yes | no |
not members | yes | no | no |
class Person {
public:
Person( const std::string& name, unsigned int age ) :
_name{ name },
_age{ age }
{}
virtual ~Person() = default;
const std::string& name() const { return _name; }
unsigned int age() const { return _age; }
virtual void print() const {
std::cout << _name << ", " << _age << std::endl;
}
private:
std::string _name;
unsigned int _age;
};
class Customer final : public Person {
public:
Customer( const std::string& name, unsigned int age,
unsigned int id ) :
Person{ name, age },
_id{ id },
_account {}
{}
unsigned int id() const { return _id; }
Account& account() { return _account; }
void print() const override {
Person::print();
std::cout << "Balance: " << _account.balance() << std::endl;
}
private:
unsigned int _id;
Account _account;
};
nach (Kölling und Rosenberg 2001), siehe (Vahrenhold 2022d)
Eine Klasse wird üblicherweise auch aufgeteilt auf 2 Dateien: .h
Header und die Implementierung in der .cpp
Sourcedatei.
Account.h
Account.cpp
Base
spezialisieren.Base
als virtuell gekennzeichnet werden!Base
virtuell sein, damit beim Zerstören eines Derived-Objektes der richtige Destruktor aufgerufen wird.Beim Zuweisen eines Derived
-Objektes an ein Base
-Objekt wird der Zuweisungsoperator von Base
aufgerufen.
Base
können daher kopiert werden.Derived
gehen verloren.Dies bezeichnet man als Slicing und sollte unbedingt vermieden werden.
Achtung: d2
enthält nun Attribute von d1
(für den Anteil, der in Base
existiert) und d2
!
interface
in Java).= 0
markiert.Frage: Hat D das Attribut data einmal oder zweimal?
Antwort: Das Attribut ist zweimal vorhanden (als B::data
und C::data
). Bei virtueller Vererbung (wird hier nicht besprochen) wäre das Attribut nur einmal vorhanden.
Widget(const Widget& w) : _i{w._i}, _j{w._j} { /* ... */ }
C++ generiert besondere Funktionen (special member functions) einer Klasse automatisch, wenn man diese nicht explizit selber definiert:
::, ., .*
und ?
können nicht überladen werden.<>
oder &|
).operator!
für !
und operator[]
für []
.Expression | As member function | As non-member function | Example |
---|---|---|---|
@a |
(a).operator@ () |
operator@ (a) |
std::cin calls std::cin.operator!() |
a@b |
(a).operator@ (b) |
operator@ (a,b) |
std::cout << 42 calls std::cout.operator<<(42) |
a=b |
(a).operator= (b) |
cannot be non-member | std::string s; s='abc'; calls s.operator=('abc') |
a[b] |
(a).operator[](b) |
cannot be non-member | std::map<int, int> m; m[1] = 2; calls m.operator[](1) |
a-> |
(a).operator-> () |
cannot be non-member | std::unique_ptr<S> ptr(new S); ptr ->bar() calls ptr.operator->() |
a@ |
(a).operator@ (0) |
operator@ (a, 0) |
std::vector<int>::iterator i = v.begin(); i++ calls i.operator++(0) |
In the table, @
is a placeholder representing all matching operators: all prefix operators in @a
, all postfix operators other than ->
in a@
, all infix operators other then =
in a@b
.
template
kündigt die Template-Klasse an,typename
,T1
und T2
stehen für Datentypen und können (innerhalb) wie normale Datentypen verwendet werden,Pair<int, float> p1{23, 4.2f}; Pair <bool , char > p2{true , ’a’};
Pair<int, float>
als auch Pair<bool, char>
).Pair p; //NICHT möglich
Pair<int,float>
und Pair<bool,char>
) haben keine besondere Beziehung zueinander, sie teilen sich nur einen ähnlichen Namen.Vorstellung verschiedener Paradigmen zur Modellierung und Programmierung.
Grundidee:
Angabe von Beispielen, d. h. ”Was sollte die Funktion für Eingabe X berechnen?“
Erstellen einer Datendefinition.
Benennung der Funktion.
Angabe, welche Art von Eingabe(n) und Ausgabe(n) verwendet wird.
Beschreibung des Zwecks der Funktion.
Angabe des Funktionskopfs.
Wähle einen passenden Namen.
Wähle einen formalen Parameter pro Unbekannter; verwende möglichst Namen aus Aufgabenstellung
Beschreibe unter Verwendung dieser Namen, was die Funktion berechnet.
Formuliere Signatur und Funktionskopf.
Charakterisierung des Zusammenhangs von Eingabe und Ausgabe anhand von Beispielen.
Definition der Funktion.
Definiere die Funktion in C++.
Erkennen von logischen und syntaktischen Fehlern.
Unit Testing ist eine Methode zum Testen kleiner Teile oder “Einheiten” des Quellcodes in einer größeren Software.
Program testing can be used to show the presence of bugs, but never to show their absence!
Testframeworks: Boost.Test, Google Test, Catch
Aufgabe zu Unit Testing über Nutzen der Boost-Library
Im jupyter-book ist die Aufgaben angegeben:
Hierüber kann dann direkt auf dem Hub eine Umgebung gestartet werden, in der C++ interpretiert wird (wird die ersten Termine genutzt):