slides-pdf

Werbung
Kapitel 4.2
Dynamische Datenstrukturen
Michael Hoffmann <[email protected]>
• Nächste Woche: 4. und letzte Schnellübung.
• Übungsaufgaben: 71-74, 2-aus-4, Rest Bonus.
Expression Funktionalität
• Expression e = 1.5;
• Expression f = 3 * x + 4;
• Expression g = e (+-*/) f;
• -e * 2; // == -3
• f(2); // == 10
• f.derive() // == 3
Expression Repräsentation
class Expression {
...
private:
int op; // encodes operator/expression
double val;
type
// stores constants
Expression left;
Expression right;
};
// left subexpression
// right subexpression
Problem: Nicht endende Rekursion!
Expression Repräsentation
class Expression {
...
private:
int op; // encodes operator/expression
double val;
type
// stores constants
Expression* left;
Expression* right;
};
// left subexpression
// right subexpression
Null-Zeiger bricht Rekursion auf.
Expression Funktionalität
class Expression {
public:
Expression();
// POST: Set to primary expression x (symbolic variable).
Expression(double v);
// POST: Set to primary constant expression with value v.
Expression(int o, Expression a);
// PRE: o is unary operator.
// POST: Set to o(a).
Expression(int o, Expression a, Expression b);
// PRE: o is binary operator.
// POST: Set to o(a, b).
};
Copy Semantik
Expression::Expression(int o, Expression a)
: left( &a
?), …
{…}
Problem: a ist temporäres Objekt!
Copy Semantik
Expression::Expression(int o, Expression& a)
: left(&a), …
{…}
Expression f; // f == x
Expression e(1, f, f); // f + f
f = e;
f == e == (1, ?, &f, &f)
1.) Nicht endende Rekursion.
2.) f = e hat e verändert.
Copy Semantik
• Wir brauchen eine andere Semantik als
member-weises Kopieren.
• „Wahre Kopie“: für einen Zeiger kopiere nicht
den Wert (Adresse) sondern deren Inhalt.
In C++ kann die Kopiersemantik einer Klasse
frei definiert werden.
Æ Copy Konstruktor.
Const Initialisierung
• Const Objekte müssen initialisiert werden.
const int i;
// error: uninitialized const object
• Auch indirekt keine Änderung möglich.
const int i = 0;
int* iptr = &i;
// error: address of const object cannot be
//
assigned to pointer to a non-const type
Zeiger auf Const Typen
• const T*
• Qualification conversion: T* Æ const T*
const int i = 0;
const int* ciptr = &i; // OK
int j = 1;
ciptr = &j; // OK: int* -> const int*
*ciptr = 2; // error: ciptr is pointer to
//
const type
Const Zeiger
• T* const
• Qualification conversion: T* Æ T* const
• const T* const
int i = 1;
int* const icptr = &i; // OK: int* -> int* const
int* const jcptr;
// error: uninitialized
*icptr = 2;
// OK: only icptr const
const int* const cicptr = &i;
// OK: int* -> const int* const
cicpter = icptr; // error: cicpter unmodifiable
*cicptr = 3;
// error: *cicptr unmodifiable
Const Referenzen
• const T&
• Initialisierung durch R-Wert vom Typ T1 mit T1Æ T
(im Ggs. zu nicht-const Referenzen!)
double d = 1.0;
int& iref = d;
// error: type mismatch
float& fref = 1.0f;
// error: needs lvalue
const int& ciref = d; // OK: double -> int
const float& cfref = 1.0f;
// OK: initialization from rvalue
Const Member Funktionen
• Member Funktionen können den Wert des
Objektes verändern.
Was, wenn es const ist?
Der Compiler lässt es nicht zu!
• Für const Objekte dürfen nur const Member
Funktionen aufgerufen werden.
class Rational {
...
// POST: return numerator
int n() ;const
// POST: return denominator
int d() ;const
...
};
Der this Zeiger
• Jede Member Funktion einer Klasse T hat Zugriff auf
das Objekt, für das sie aufgerufen wird.
• Über einen Zeiger this vom Typ T*.
• this ist R-Wert, *this ist L-Wert.
class Rational {
...
int n(Rational* const this);
int d(Rational* const this);
Rational& operator*=(
Rational* const this, Rational s);
...
};
Der this Zeiger
• Const Member Funktion von T: this ist vom Typ
const T* const.
• Da this kein expliziter Parameter ist, muss sein const
qualifier anderswo erscheinen Æ nach den
Parametern.
class Rational {
...
int n(const Rational* const this);
int d(const Rational* const this);
Rational& operator*=(
const Rational* const this, Rational s);
...
};
Function Parameters
• Grosse Objekte by-value zu übergeben, ist
teuer.
Objekte die grösser als eine Speicheradresse
sind, per (const) Referenz übergeben!
• Fundamentale Typen und Zeigertypen: byvalue.
Const und Überladen
• T versus const T
• by-value Parameter: irrelevant.
• Referenzen und Pointer: verschiedene Typen.
void foo(int i) { ... }
void foo(const int i) { ... }
// error: redefinition of foo(int)
void foo(int& i) { ...}
void foo(const int& i) { ...} // OK: distinct
...
int a;
foo(a); // calls foo(int&)
foo(2); // calls foo(const int&)
Const Korrektheit
Die konsequente Verwendung von const bei…
• Referenzparametern von Funktionen, die innerhalb der
Funktion nicht modifiziert werden;
• Member Funktionen, die das Objekt, für das sie
aufgerufen werden, nicht modifizieren.
wird als Const Korrektheit eines Programms
bezeichnet.
Warum Const?
• Dokumentiert nicht nur, der Compiler überprüft
auch.
• Probleme zur Compile-Zeit sind besser als
Probleme zur Laufzeit. Æ Der Compiler ist des
Programmierers bester Freund.
• Const Referenzen sind flexibler, da von RWerten initialisierbar.
void foo(const Expression& x) { ... }
…
foo(1); // int -> double -> Expression
Dynamische Speicherverwaltung
• Der „wahre“ copy Konstruktor braucht mehr als den
konstanten Speicher im Expression Objekt.
• Speicherbedarf eines Programms muss nicht zur
Compile-Zeit bekannt sein (siehe Rekursion).
• Zur Laufzeit kann Speicher vom Heap alloziert
werden.
int* i = new int;
int* j = new int(6);
delete j;
// *i undefined
// *j == 6
Expression Konstruktoren
Expression::Expression() : op(
??)…
• Kodierung der Operatoren ist mühsam.
• Æ Konstanten mit passenden Namen.
// Codes for different
const int VAR = -1; //
const int VAL = 0; //
const int ADD = 1; //
const int SUB = 2; //
const int MUL = 3; //
const int DIV = 4; //
const int UMI = 5; //
types of expressions
symbolic variable x
constant value
binary addition
binary subtraction
binary multiplication
binary division
unary minus
Primärausdrücke
Expression::Expression()
: op(VAR), val(0), left(0), right(0)
{}
Expression::Expression(double v)
: op(VAL), val(v), left(0), right(0)
{}
Copy Konstruktor
Expression::
Expression(const Expression& f)
: op(f.op),
val(f.val),
left(0),
right(0)
{
if (f.left != 0)
left = new Expression(*(f.left));
if (f.right != 0)
right = new Expression(*(f.right));
}
Zusammengesetzte Ausdrücke
Expression::
Expression(int o, const Expression& a)
: op(o),
val(0),
left(new Expression(a)),
right(0)
{ assert(op == UMI); }
Zusammengesetzte Ausdrücke
Expression::
Expression(int o, const Expression& a,
const Expression& b)
: op(o),
val(0),
left(new Expression(a)),
right(new Expression(b))
{
assert(op == ADD || op == SUB ||
op == MUL || op == DIV);
}
Destruktor
• new und delete erscheinen immer
paarweise...
• Konstruktoren werden „automatisch“ aufgerufen.
• Wo erscheinen die entspr. delete Ausdrücke?
Cname::~Cname() { … }
Destruktor
Expression::~Expression()
{
delete left;
delete right;
}
Zuweisungen
Expression e = 1;
e = 3;
Copy Konstruktor
Zuweisung
Sollten gleiche Semantik haben!
Î Zuweisungs-Operator für Expression
muss auch definiert werden.
Expression&
Expression::operator=(const Expression&)
{ … }
Zuweisung Ù Copy Konstruktor
Copy Konstruktor: initialisiert unberührten gerade
allozierten Speicher.
Zuweisungs-Operator: Objekt, an das zugewiesen wird,
enthält i.a. Daten.
Î Zuweisungs-Operator “=“
Destruktor + Copy Konstruktor
Zuweisung
Expression&
Expression::operator=(const Expression& f)
{
delete left;
delete right;
op = f.op;
val = f.val;
if (f.left != 0)
left = new Expression(*(f.left));
if (f.right != 0)
right = new Expression(*(f.right));
return *this;
}
Selbst-Zuweisung
Expression e;
…
e = e;
Problem: Erst wird gelöscht,
und der Inhalt von e ist verloren!
Î Verhindere Selbst-Zuweisung.
Zuweisung
Expression&
Expression::operator=(const Expression& f)
{
if (this != &f) {
wie vorher
}
return *this;
}
Datenkapselung
• Der öffentliche Teil von Expression ist frei von
Zeigern.
• Die Representation ist vollständig nach aussen
gekapselt.
• Nach aussen präsentiert sich die Klasse mit
klassischer statischer Copy Semantik.
• Interna können geändert werden, ohne dass
Benutzer-Code angepasst werden muss.
Expression Arithmetik
Expression x;
Expression x2(MUL, 5, x); // 5x
Expression y(ADD, x2, 1); // 5x+1
Wir wollen:
Expression x;
Expression y = 5 * x + 1;
Arithmetische Operatoren
Expression&
Expression::
operator+=(const Expression& a)
{ return *this =
Expression(ADD, *this, a); }
Zweimal kopiert Æ etwas ineffizient.
Expression operator+(const Expression& a,
const Expression& b)
{
Expression f = a;
return f += b;
}
Differenzieren
class Expression {
...
Expression derive() const;
// POST: Returns the derivative of *this
//
interpreted as a real function in x.
...
};
Differenzieren
Expression Expression::derive() const
{
// no arguments
if (op == VAL) return 0.0;
if (op == VAR) return 1.0;
assert(left != 0);
Expression l = left->derive();
// unary operator
if (op == UMI) return -l;
…
}
Differenzieren
Expression Expression::derive() const
{ …
// binary operator
assert(right != 0);
Expression r = right->derive();
if (op == ADD) return l + r;
if (op == SUB) return l - r;
if (op == MUL)
return l * *right + r * *left;
assert(op == DIV);
return (l * *right - r * *left) /
(*right * *right);
}
Die Goldene Zeiger Regel
Wann immer ein Zeiger z dereferenziert
wird, fang den Fall z==0 ab!
Î Assertions...
Ausgabe Operator
std::ostream&
operator<<(std::ostream& o,
const Expression& f)
{ … }
Problem: Als nicht-Member Funktion kein Zugriff
auf private Daten.
Lösung: Deklariere Funktion als friend innerhalb
von Expression.
Ausgabe Operator
class Expression {
...
friend std::ostream&
operator<<(std::ostream&,
const Expression&);
...
};
Eine als friend deklarierte Funktion hat Zugriff auf
private Daten und Member Funktionen der
Klasse.
Ausgabe Operator
std::ostream&
operator<<(std::ostream& o,
const Expression& f)
{
// no arguments
if (f.op == VAL) return o << f.val;
if (f.op == VAR) return o << "x";
// unary operator
assert(f.left != 0);
if (f.op == UMI)
return o << "-(" << *(f.left) << ")";
…
}
Ausgabe Operator
std::ostream&
operator<<(std::ostream& o,
const Expression& f)
{ …
// binary operator
o << "(" << *(f.left);
assert(f.right != 0);
if (f.op == ADD) o << "+";
else if (f.op == SUB) o << "-";
else if (f.op == MUL) o << "*";
else o << "/"; // assert …
return o << *(f.right) << ")";
}
Zugehörige Unterlagen
Herunterladen