int, char), Datenstrukturen (struct) und Funktionen sind getrenntJetzt: Neue Sichtweise auf die Programmierung
â Objektorientierte Programmierung (OOP)
Keine Sorge! OOP ist ânurâ eine Erweiterung des Bekannten.
Anwendungsproblem \(\Rightarrow\) Modellierung \(\Rightarrow\) Reduzieren auf das âWesentlicheâ
âWesentlichâ im Sinne unserer Sicht auf die Dinge bei diesem Problem
â Es existieren verschiedene Sichten auf dasselbe Problem!
Programmierung bis jetzt
Â
AuffĂ€llige Unterschiede: Neue SchlĂŒsselwörter class und public, sonst gleich
public und privateprivate wird bei Klassen automatisch gesetztget() â Abrufenset() â SetzenBeliebiges Mischen von private und public ist prinzipiell möglich
Aber: Schlecht lesbar und unĂŒbersichtlich
â Möglichst vermeiden!
Punkt p = {2.0, 3.1}; // Errorset()-Methode â sehr umstĂ€ndlichPunkt() {}Punkt(double nx, double ny) : x(nx), y(ny) {}Punkt(const Punkt& p) {}Punkt(Punkt&& p) {}Wird bei jeder Klassendefinition automatisch eingefĂŒgt,
falls kein anderer Konstruktor definiert wird
Ist ein parameterloser Konstruktor
Die folgenden beiden Definitionen sind identisch:
#include <iostream>
using std::cout;
class Punkt {
double x = 1.0; // Inline
double y = 1.0;
public:
Punkt() {}
double get_x() { return x; }
double get_y() { return y; }
};
int main() {
Punkt p;
cout << p.get_x() << " " << p.get_y();
}
#include <iostream>
using std::cout;
class Punkt {
double x;
double y;
public:
Punkt() { x = 2.0; y = 2.0; }
double get_x() { return x; }
double get_y() { return y; }
};
int main() {
Punkt p;
cout << p.get_x() << " " << p.get_y();
}
Initialisierung der Member ĂŒber eine separate Stelle im Code
Eigenschaft: wird vor dem Body der Funktion ausgefĂŒhrt
Aufbau:
<class name>(<function parameters>) : <initializer list> { <function body> }
Initialisierung der Member mit beliebigen Werten
Prinzip bereits bekannt, jetzt mit Funktionsparametern
Wichtig: Beliebig viele, verschiedene Konstruktoren erlaubt
â Nennt sich Ăberladung des Konstruktors
#include <iostream>
using std::cout, std:: endl;
class Punkt {
double x_;
double y_;
bool g_; // Geheim
public:
Punkt() : x_(2.0), y_(1.0), g_(false) {}
Punkt(double x, double y) : x_(x), y_(y), g_(false) {}
Punkt(double x, double y, bool g) : x_(x), y_(y), g_(g) {}
void print_punkt() {
cout << "X: " << x_ << " Y: " << y_ << " G: " << g_ << endl;
}
};
int main() {
Punkt p1;
Punkt p2( 1.5, 2.5);
Punkt p3{ 3.14, 47.11, true };
p1.print_punkt();
p2.print_punkt();
p3.print_punkt();
}
Lösung: SchlĂŒsselwort delete löscht automatisch erzeugte Konstruktoren
â Punkt P() = delete;
Analog dazu: Konstruktoren auch explizit als default markierbar
â Punkt P() = default;
delete und default sollen die Intention des Programmierers verdeutlichen
â Macht AuĂenstehendem sofort deutlich, dass die Konstruktoren korrekt sind
int, double geschieht dies automatisch~) vor dem Klassennamen â ~Punkt() {}#include <iostream>
class DataHandle {
void * data;
int size_;
public:
DataHandle() = delete;
DataHandle(int size): size_(size) {
data = malloc(size * sizeof(void*));
std::cout << "Constructor called" << std::endl;
}
~DataHandle() {
free(data);
std::cout << "Destructor called" << std::endl;
}
};
int main() {
std:: cout << "Entering main()" << std::endl;
DataHandle dh{3}; // Instanziierung
std:: cout << "Exiting main()" << std::endl;
}
#include <iostream>
class DataHandle {
void * data;
int size_;
public:
DataHandle() = delete;
DataHandle(int size): size_(size) {
data = malloc(size * sizeof(void*));
}
~DataHandle() {
std::cout << "Destructor called" << std::endl;
free(data);
}
};
int main() {
DataHandle dh1{4};
{
DataHandle dh2 = dh1;
}
}
dh2 ist eine bitweise Kopie von dh1 (Shallow Copy)datadh1.data und dh2.data zeigen auf gleichen Speicherfree() fĂŒhrt zu Absturz des Programms đ€ŻDeshalb gibt es den Copy-Konstruktor
DataHandle(const DataHandle& d) {}const verbietet Modifikation des Originals#include <iostream>
#include <cstring>
class DataHandle {
void * data;
int size_;
public:
DataHandle() = delete;
DataHandle(int size): size_(size) {
data = malloc(size * sizeof(void*));
std::cout << "Constructor called" << std::endl;
}
DataHandle(const DataHandle & d) : size_(d.size_) {
data = malloc(d.size_);
std::memcpy(data, d.data, d.size_);
std::cout << "Copy Constructor called" << std::endl;
}
~DataHandle() {
free(data);
std::cout << "Destructor called" << std::endl;
}
};
int main() {
std:: cout << "main(): Enter" << std::endl;
DataHandle dh1{4};
{
DataHandle dh2 = dh1;
}
std:: cout << "main(): Exit" << std::endl;
}
An dieser Stelle kommt der Move-Konstruktor ins Spiel!
Beispiel Zeiger: Verschieben des Zeigerwerts zum neuen Objekt
Wichtig: Originales Objekt sollte danach keinen Zugriff mehr auf die Ressource haben
â Bester Weg: Alter Zeiger wird nullptr
Spezielle Signatur:
<Type>(<Type>&& var_name);
DataHandle(DataHandle&& dh);&& dh eine Referenz auf einen Rvalue3 + 3;)Rvalues sollen hier nicht weiter interessieren!
std::moveIrrefĂŒhrender Name, std::move bewegt eigentlich nichts
Stattdessen nur Type-Casting zu Referenz auf Rvalue
â DataHandle wird zu DataHandle &&
Gecasteter Wert wird anschlieĂend als Parameter im Konstruktor-Aufruf verwendet
Move-Konstruktor hat grundsĂ€tzlich Ăhnlichkeit zum Copy-Konstruktor
Wichtiger Unterschied: Ressourcen beim alten Objekt unzugÀnglich machen
Grund: Alte Objekte existieren nach Move weiterhin
(und könnten prinzipiell auch Ressourcen manipulieren)
#include <iostream>
using std::cout, std::endl;
class A {
public:
A() { cout << "A: Constructor" << endl; }
~A() { cout << "A: Destructor" << endl; }
};
class B {
public:
B() { cout << "B: Constructor" << endl; }
~B() { cout << "B: Destructor" << endl; }
};
int main() {
A a;
B b;
}
#include <iostream>
#include <fstream>
using std::cout, std::endl;
using std::ios;
class FileHandler {
std::fstream file; // File Handle
public:
FileHandler(const std::string& filename) {
file.open(filename, ios::in | ios::app);
if (file.is_open()) {
cout << "File opened successfully" << endl;
}
}
~FileHandler() {
if (file.is_open()) {
file.close();
cout << "File closed" << endl;
}
}
/* Implementierung (read(), write(), ...) */
};
int main() {
cout << "main(): enter" << endl;
{
FileHandler fh("test.txt");
/* Lese-/Schreiboperationen der Datei... */
}
cout << "main(): exit" << endl;
}
RAII ist eine extrem nĂŒtzliche Programmiertechnik. Nutzt sie!
ZunÀchst einmal kommen aber noch einige allgemeine Konzepte!
<<-Operator bei std::coutstd::cout << "Echo";operator
operator+=(int i); // Ăberlade += fĂŒr int-Parameter. (bzw. .*) â Member-Zugriff (bzw. Member-Zugriff ĂŒber Zeiger):: â Scope Resolution Operator?: â TernĂ€rer Operator#include <iostream>
class Integer {
int val;
public:
Integer() = delete;
Integer(int i) : val(i) {}
int operator+(int summand) { return val + summand; }
void operator+=(int summand) { val += summand; }
int value() { return val; }
};
int main() {
Integer I{3};
std::cout << "Sum: " << I + 5 << std::endl;
I += 10;
std::cout << "New value: " << I.value() << std::endl;
}
Operator-Ăberladung ist sehr nĂŒtzlich, wenn es an den richtigen Stellen eingesetzt wird!
Betrachtung einer wichtigen Regel in C++: Rule of Three/Five
Rule of Three
operator=(const &)) zu erstellen, sollen* alle drei erstellt werdenRule of Five
operator=(&&))friendMit den aktuellen Möglichkeiten geht das nicht!
â Lösung: Neues SchlĂŒsselwort friend đ
friendfriend gestattet selektiven Zugriff fĂŒr externe Klassen und Funktionen
â friend ostream& operator<<(ostream& os, Punkt& p);
Randbemerkung: Aufruf von externen Funktionen ohne Namespace-PrÀfix
#include <iostream>
using std::cout, std::endl;
using std::ostream;
class Punkt {
double x;
double y;
public:
Punkt(double x, double y) : x(x), y(y) {}
friend void print_punkt(Punkt& p);
friend ostream& operator<<(ostream& os, Punkt& p);
};
ostream& operator<<(ostream& os, Punkt& p) { // Extern definiert
cout << "Member: X=" << p.x << " Y=" << p.y << endl;
return os;
}
void print_punkt(Punkt& p) {
cout << "Member: X=" << p.x << " Y=" << p.y << endl;
}
int main() {
Punkt p{1.5, 2.3};
cout << "Ostream: \t" << p; // Jetzt erlaubt
cout << "print_punkt: \t";
print_punkt(p);
}
thisthis->print();*this; // Objekt selbststatic in Klassenstatic deklariert werden#include <iostream>
using std::cout, std::endl;
class A {
public:
static int _count;
A() { _count++; }
~A() { _count--; }
static int count() { return _count; }
};
int A::_count = 0; // Initialisierung *auĂerhalb* der Klasse in *.cpp-Datei
int main() {
A a1, a2;
{
cout << A::count() << endl;
A a3;
cout << A::count() << endl;
}
cout << A::count();
return 0;
}
new/delete)C++ erweitert die aus C bekannten Funktionen malloc und free
Operatoren ersetzen die Funktionen
malloc â new (dyn. Allokation)free â delete (dyn. Deallokation)Im globalen Namespace enthalten
â kein Einbinden von Headern notwendig
Ansonsten gleiche Eigenschaften wie malloc und free
#include <iostream>
using std::cout, std::endl;
class Foo {
public:
int bar;
Foo(int val) : bar(val) {}
};
int main() {
Foo * foo = new Foo(1);
int * i = new int(5);
cout << "Foo: " << foo->bar << endl;
cout << "Int: " << *i << endl;
delete i;
delete foo;
}
new/delete)new und delete können auch mit Arrays umgehendelete[]#include <iostream>
using std::cout, std::endl;
class Foo {
public:
int bar;
Foo() : bar(0) {}
Foo(int val) : bar(val) {}
};
int main() {
// 10 Foo-Objekte, default-initialisiert
Foo * foo = new Foo[10];
int * i = new int[5] { 1, 2, 4, 8, 16 };
foo[2].bar = 4;
cout << "Foo: " << foo[2].bar << endl;
cout << "Int: " << i[4] << endl;
delete[] i;
delete[] foo;
}
new/delete)new und delete haben die gleichen SchwĂ€chen wie free und malloc đIst es also am Ende in C++ genauso schlimm wie in C?
â Nein!
unique_ptrshared_ptrunique_ptr vollstĂ€ndig an den aktuellen Scope gebunden (â RAII)unique_ptr zerstörtBenutzung von Unique Pointer ist sehr einfach
â Deklaration: std::unique_ptr<int> i_ptr; // Zeiger auf int *
std::make_unique#include <memory>
#include <iostream>
using std::unique_ptr;
using std::cout, std::endl;
class Foo {
public:
Foo() { cout << "Foo constructor called\n"; }
~Foo() { cout << "Foo destructor called\n"; }
void print() { cout << "Hello Foo\n"; }
};
int main() {
cout << "main(): Enter" << endl;
Foo * f = new Foo();
{
cout << "Scope: Enter" << endl;
unique_ptr<Foo> f_ptr(f); // Konstruktoraufruf, Ăbernahme von f
f_ptr->print();
cout << "Scope: Exit" << endl;
} // delete f
cout << "main(): Exit" << endl;
}
Unique Pointer sind deutlich besser als hÀndisches Verwalten! Nutzt sie, wenn ihr könnt.
unique_ptr gibt es noch shared_ptr
unique_ptr: Bei neuem Kopieren wird ZĂ€hler inkrementiert (ZĂ€hlen der Referenzen)0 annimmt, wird der gehaltene Zeiger zerstörtĂberdenkt euer Design, falls der Einsatz von shared_ptr jemals notwendig erscheinen sollte shared_ptr ist hauptsĂ€chlich fĂŒr nebenlĂ€ufige Programmierung gedacht!
0 hat, wird das zugehörige Objekt entferntnew / delete)