44
1/44 Objektorientiert Programmieren III Spezialit¨ aten Stephan Neuhaus Lehrstuhl Softwaretechnik Universit¨ at des Saarlandes, Saarbr¨ ucken

Objektorientiert Programmieren III Spezialit¨aten file1/44 Objektorientiert Programmieren III Spezialit¨aten Stephan Neuhaus Lehrstuhl Softwaretechnik Universit¨at des Saarlandes,

  • Upload
    lethuan

  • View
    221

  • Download
    0

Embed Size (px)

Citation preview

1/44

Objektorientiert Programmieren IIISpezialitaten

Stephan NeuhausLehrstuhl SoftwaretechnikUniversitat des Saarlandes, Saarbrucken

2/44

Letzte Vorlesung

• Beispiel von Datentypen: Verkettete Liste

• Vererbung (Shapes)

• Sichtbarkeit: public und private inheritance

• Virtuelle (abstrakte) Methoden

• Vererbung in Java

3/44

Klassenmethoden, Klassenattribute I

Manche Attribute oder Methoden betreffen keine Instanzen,sondern die Klasse als Ganzes:

• Zahler, wie oft eine Klasse instanziiert wurde

• Klassenglobale Defaulteinstellungen

• Logger

Solche Attribute oder Methoden heißen Klassenattribute oder-methoden und werden (in Java und C++) mit staticbezeichnet

4/44

Klassenmethoden, -attribute II

#include <iostream>

class MyClass {public:

MyClass() { constructCounter++; }˜MyClass() { destructCounter++; }static int constructInfo() { return constructCounter; }static int destructInfo() { return destructCounter; }

private:static int constructCounter;static int destructCounter;

};int MyClass::constructCounter = 0;int MyClass::destructCounter = 0;int main(int argc, const char *argv[]) {

MyClass a, b; { MyClass c[10]; }if (MyClass::destructInfo() != MyClass::constructInfo()) {std::cerr << "constructed: " << MyClass::constructInfo()

<< ", deleted: " << MyClass::destructInfo() << std::endl;}

}

5/44

Klassenmethoden, -attribute III

% g++ -Wall -O -o Static1 Static1.cc% ./Static1constructed: 12, deleted: 10%

Warum eigentlich -O im Zusammenhang mit -Wall?

Weil einige Warnungen nur durch Datenflußanalyse erzeugtwerden konnen und die Datenflußanalyse nur beieingeschalteter Optimierung ablauft

6/44

Exceptions I

Maßnahme fur die Behandlung von Fallen, die den normalenProgrammablauf unterbrechen

Beispiel: open() zum Offnen einer Datei

Fall 1: Alles lauft wie geplant, die Datei kann erfolgreichgeoffnet werden. Ruckgabewert ret ≥ 0

Fall 2: Ein Fehler tritt auf. In dem Fall ist ret < 0 und dasglobale Symbol errno enthalt einen Fehlercode.

Problem: Ruckgabewert wird fur zwei verschiedene Sachengenutzt: Ergebnis (file descriptor) und Fehlersignal

Das geht nur, wenn beide Wertebereiche disjunkt sind:Ergebnis von tan(), bei Argument, das bis aufMaschinengenauigkeit π/2 entspricht?

7/44

Exceptions II

Fehlerbehandlung umstandlich (und wird deshalb seltenvollstandig gemacht) (Achtung: Bug!)

int fd = open("/etc/passwd", O_RDONLY);if (fd >= 0) {

char *buf = new char[1024];if (buf != 0) {

ssize_t nbytes = read(fd, buf, 1024);if (nbytes > 0) {if (close(fd) >= 0) {delete[] buf; // <-- Bug!

} elseerror("can’t close /etc/passwd");

} elseerror("can’t read from /etc/passwd");

} elseerror("can’t allocate buffer");

} elseerror("can’t open /etc/passwd");

8/44

Exceptions III

Normaler Programmfluß und Ausnahmebehandlung am Ende

char* buf = 0;try {

File f = File::open("/etc/passwd", O_RDONLY);buf = Buffer::allocBuffer(1024); // throws AllocationErrorssize_t nbytes = f.read(buf, 1024);f.close();

} catch (FileNotFoundError e) {error("can’t find file %s: %s", e.getFileName(), e.getMessage());

} catch (AllocationError e) {error("can’t allocate %lu bytes", e.getSize());

} catch (ReadError e) {error("can’t read %lu bytes: %s", e.getSize(), e.getMessage());

} catch (CloseError e) {error("can’t close file %s: %s", e.getFileName(), e.getMessage());

}if (buf != 0) delete[] buf;

9/44

Exceptions IV: Nesting

class XXII { /* ... */ };void f() {

try {} catch (XXII) {try {// Something complicated

} catch (XXII) {// Complicated handler failed

}}

}

10/44

Exceptions V

Ausnahmebehandlung in Java verbindlich: Programm ubersetztnicht, wenn Ausnahmen nicht behandelt werden

Wenn Ausnahmen geworfen werden, muß das in Java deklariertwerden

Ausnahmebehandlung in C++ optional (im Sinn von: Kompiliertauch ohne Ausnahmebehandlung)

11/44

Templates I

Hier ein Stack fur int-Werte:

class IntStack {public:

static const int stackSize = 100;IntStack() { t = 0; mem = new int[stackSize]; }˜IntStack() { delete[] mem; }void push(int item) { if (top < stackSize) mem[t++] = item; }int pop() { if (t > 0) return mem[--t]; else return -1; }int top() { if (t > 0) return mem[t-1]; else return -1; }

private:int t;int* mem;

};

12/44

Templates I

Hier ein Stack fur float-Werte:

class FloatStack {public:

static const int stackSize = 100;FloatStack() { t = 0; mem = new float[stackSize]; }˜FloatStack() { delete[] mem; }void push(float item) { if (top < stackSize) mem[t++] = item; }float pop() { if (t > 0) return mem[--t]; else return -1; }float top() { if (t > 0) return mem[t-1]; else return -1; }

private:int t;float* mem;

};

13/44

Generischer Stack

class StackEmpty {};class StackFull {};

template<class C> class Stack {public:

static const int stackSize = 100;Stack() { t = 0; mem = new C[stackSize]; }˜Stack() { delete[] mem; }void push(const C& item) {if (t < stackSize) mem[t++] = item; else throw StackFull();

}C &pop() {if (t > 0) return mem[--t]; else throw StackEmpty();

}const C& top() const {if (t > 0) return mem[t-1]; else throw StackEmpty();

}private:

int t;C* mem;

};

14/44

”Generischer“ Stack, Java

public class Stack {public static final int stackSize = 100;public Stack() { t = 0; mem = new Object[stackSize]; }public void push(Object item) throws StackFullException {if (t < stackSize)mem[t++] = item;

elsethrow new StackFullException();

}public Object pop() throws StackEmptyException {

if (t > 0) return mem[--t]; else throw new StackEmptyException();}public Object top() throws StackEmptyException {

if (t > 0) return mem[t-1]; else throw new StackEmptyException();}

private int t;private Object[] mem;

};

15/44

Templates in C++

• “C++ templates are the first language feature to requiremore intelligence from the environment than one usuallyfinds on a UNIX system.” (GCC 3.3 Manual)

• Templates haben lokale Sichtbarkeit, d.h., sie mussen injeder Translation Unit neu definiert werden. Das kann zuaufgeblahtem Code fuhren, wenn in vielen Dateien gleicheTemplates verwendet werden. Abhilfe: TemplateRepositories (CFront), Common Blocks (Borland)

16/44

Template-Instanziierung

• Common Blocks: Templates werden in jedes .o-Filehineinkompiliert. Der Linker sortiert Duplikate aus. Vorteil:Keine externe Komplexitat. Nachteil: Kompilierzeit steigt,jedes Template wird mehrfach (redundant) ubersetzt.

• Template Repository: Ein Ort, an dem Templatesuntergebracht werden. Management automatisch.Templates werden erst im Repository gesucht und erstinstanziiert, wenn sie dort nicht gefunden werden. Vorteil:Bessere Ubersetzungsgeschwindigkeit. Nachteil: HoherAufwand zur Wartung des Repositories.

• GCC unterstutzt das Borland-Modell automatisch aufGNU/Linux, ansonsten gibt es Optionen

17/44

Vor- und Nachteile Java/C++

• Templates bieten dem Compiler die Moglichkeit zuOptimierungen, die der Java-Compiler nicht hat

• Templates sind typsicher. In Java muß man ein Objekt, dasman aus einer generischen Klasse rausholt, erst durchTypumwandlung in den richtigen Typ verwandeln (nicht zurKompilierzeit prufbar)

• Problematisches Template-Management (s.o.)

• In Java “generische” Typen nur fur Ableger vonjava.lang.Object, in C++ auch fur int

• Komplizierte Syntax in einer syntaktisch eh schonkomplizierten Sprache

18/44

Algorithmen und Funktionsobjekte

Die Standard-C++-Bibliothek bietet eine Menge Funktionen an,um mit Collections (also list, vector, set, map etc)Sinnvolles anzustellen

Hier nur einfache Beispiele moglich

#include <algorithm>#include <string>using namespace std;

void f(const list<string>& ls) {list<string>::const_iterator p = find(ls.begin(), ls.end(), "Fred");

if (p == ls.end()) { // "Fred" not found// ...

} else {// p points to "Fred"

}}

19/44

Nichtmodifizierende Operationen

for_each() Operation fur alle Elementefind() Element suchenfind_if() Ersten Treffer fur Pradikat suchenfind_first_of() Wert aus einer Sequenz in anderer suchenadjacent_find() Nebeneinanderliegende Werte findencount() Elemente zahlencount_if() Elemente zahlen, die Pradikat erfullenmismatch() Erster Unterschied zweier Sequenzenequal() true, wenn zwei Sequenzen gleich sindsearch() Erstes Vorkommen als Subsequenzfind_end() Letztes Vorkommen als Subsequenzsearch_n() n-tes Vorkommen finden

20/44

Funktionsobjekte: Beispiele I

void f(vector<int>& vi, list<int>& li) {typedef list<int>::iterator LI;typedef vector<int>::iterator VI;pair<VI,LI> p= mismatch(vi.begin(), vi.end(), li.begin(), less<int>());

// *p.first now points to the first element in li that is not less// than its corresponding element in vi

}

21/44

Funktionsobjekte: Beispiele II

class Person { /* ... */ };

struct Club {string name;list<Person*> members;list<Person*> officers;// ...Club(const string& name);

};

Wir wollen eine list<Club> nach einem Club mit einembestimmten Namen durchsuchen

Der normale ==-Operator ist da keine große Hilfe, weil wirClubs nicht als Ganzes vergleichen wollen, sondern nur dieNamen

Also schreiben wir uns ein eigenes Pradikat

22/44

Funktionsobjekte: Beispiele III

class ClubEqual : public unary_function<Club, bool> {string clubName;

public:explicit ClubEqual(const string& myClubName): clubName(myClubName) {}

bool operator() (const Club& club) { return club.name == s; }};

void findClubWithName(list<Club>& lc) {list<Club>::iterator p= find(lc.begin(), lc.end(), ClubEqual("Dining Philosophers"))

if (p == lc.end()) { // Club not found// ...

} else { // Use *p// ...

}}

23/44

Der C++-Praprozessor

cpp

cpp

cpp

cpp

-

-

-

-

cc1

cc1

cc1

cc1

-

-

-

-

as

as

as

as

ld

��

��

��

����

���������1

PPPPPPPPPq

@@

@@

@@

@@@R

?

Executable

.E .s

.o

Deklarationen werden in Header-Dateien gesammelt.Praprozessor sorgt mit #include dafur, daß dieseDeklarationen auch zur Verfugung stehen

24/44

#include I

Die #include-Anweisung gibt es in zwei Varianten:

• #include <iostream> fugt den Inhalt des Headers<iostream> an der angegebenen Stelle ein.

• Findet sich an einem implementationsabhangigen Ort

• Muß noch nicht einmal eine Datei sein (z.B. alsvorkompilierter Header zur schnelleren Ubersetzung odergleich in Compiler eingebaut)

25/44

#include II

• #include "MyClass.h" fugt den Inhalt der DateiMyClass.h ein

• Wird in einer Reihe von Verzeichnissen gesucht

• Wird typischerweise auch im aktuellen Verzeichnis gesucht

• Verzeichnisse und deren Suchreihenfolge konnen i.d.R.durch Optionen beeinflußt werden (GCC:“-I/usr/X11/include”, “-I../src”)

26/44

Makros I

Makros sind einer der dunklen Kapitel von C++

Kommen aus einer Zeit, als es in C noch keineconst-Deklarationen oder inline-Funktionen gab

Java hat (zum Gluck!) keinen Praprozessor!

(Warum, sehen Sie gleich)

Makros geben einem Codestuck einen Namen, damit er unterdiesem Namen eingesetzt werden kann

27/44

Makros II

#define MAX 3

void f() {int i[MAX] = { 1, 2, 3 }; // Means int i[3] after preprocessor is

// done with it}

Besser:

static const int max = 3;

void f() {int i[max] = { 1, 2, 3 }; // Includes type checking

}

28/44

Makros: Fehler I

% cat /tmp/m1.cc#define MAX2 6; // <- oh oh, semicolon probably a mistake

void f() {int j[MAX2] = { 1, 2, 3, 4, 5, 6 };

}% gcc -c /tmp/m1.cc/tmp/m1.cc: In function ‘void f()’:/tmp/m1.cc:4: parse error before ‘;’%

Aber der Teil mit dem Semikolon in Zeile 4 ist dochvollkommen OK. . .

29/44

Makros: Fehler II#define USES_CACHE false#define USES_ASM true

#define USES_OPTIMIZATION USES_CACHE || USES_ASM

bool is_optimized(bool condition) {// return condition && false || true ;return condition && USES_OPTIMIZATION;

}

Klammern hilft hier:

#define USES_CACHE false#define USES_ASM true

#define USES_OPTIMIZATION (USES_CACHE || USES_ASM)

bool is_optimized(bool condition) {// return condition && (false || true) ;return condition && USES_OPTIMIZATION;

}

30/44

Makros mit Argumenten I

#define MAX1(a,b) (a > b ? a : b)

void f(int *a, int& i, int& j) {// means ( a[i] + 1 > a[j] + 1] ? a[i] + 1 : a[j] + 1] ) ;int m = MAX1(a[i] + 1, a[j] + 1]); // oh, oh...

}

Besser:

#define MAX2(a,b) ((a) > (b) ? (a) : (b))

void f(int *a, int& i, int& j) {// means ((a[i] + 1) > (a[j] + 1]) ? (a[i] + 1) : (a[j] + 1])) ;int m = MAX2(a[i] + 1, a[j] + 1]); // ok now

}

31/44

Makros mit Argumenten II

#define MAX2(a,b) ((a) > (b) ? (a) : (b))

void f(int& i, int& j) {// means ((i++) > (j++) ? (i++) : (j++)) ;int m = MAX2(i++,j++); // oh, oh...

}

Eins der beiden Argumente wird zweimal ausgewertet, alsoauch zweimal inkrementiert. Besser:

template<class T> inline const T& max(const T& a, const T& b) {return (a < b) ? a : b;

}

void f(int *a, int& i, int& j) {int m = max(i++, j++); // ok now

}

32/44

Warum das alles?

• Der Praprozessor ist (leider) ein fester Bestandteil von C++,daher sollten Sie ihn kennen

• Sie mussen Code von anderen Leuten lesen, die nicht aufdie Benutzung des Praprozessors verzichten

• Manchmal kann der Praprozessor schon nutzlich sein

33/44

Bedingte Ubersetzung I

Manchmal ist es in C++ ein Fehler, wenn man ein Symbolmehrfach deklariert oder definiert

Man brauchte also eine Moglichkeit, die dafur sorgt, daß einHeader nicht erneut eingefugt wird, wenn er schonmaleingefugt wurde

Dafur gibt es im Praprozessor die bedingte Ubersetzung

34/44

Bedingte Ubersetzung II

#ifdef MAX# define MAX2 (2*MAX)#else# define MAX2 0#endif

Je nachdem, ob das Praprozessor-Symbol MAX definiert waroder nicht, bekommt MAX2 den Wert (2*MAX), bzw. 0

Aquivalent:

#ifndef MAX# define MAX2 0#else# define MAX2 (2*MAX)#endif

35/44

Bedingte Ubersetzung III

Der ifdef-Wrapper um einen Header schutzt vor mehrfachemEinfugen

// File MyClass.h#ifndef _MyClass_h_# define _MyClass_h_

class MyClass {// ...

};

#endif // _MyClass_h_

Bei erneutem Einfugen durch #include "MyClass.h" ist dasSymbol _MyClass_h_ bereits definiert und MyClass wird nichterneut deklariert

36/44

Benamung I

So nicht:

void MrFlo123::grk151a(a_struct *x) {s += x->x;

}

Eher so:

void CheckingAccount::addToBalance(const MonetaryValue& amount) {// Assume this->currency == amount.getCurrency();balance += amount.getAmount();

}

37/44

Benamung II

• Klassen sollten Substantive als Namen haben:CheckingAccount, GiroKonto, XmlElement, AutoKarosseusw., aber nicht GelenkDrehen (besser GelenkDreher,noch besser als drehe()-Methode von Gelenk),FunktionAbleiten (besser Ableitung)

• Methoden sollten in der Regel Verben in der Befehlsformals Namen haben: berechneAbleitung(), drehe(),pruefeZulaessigkeit() usw., aber nichtgelenkDrehen(), testenObZulaessig() etc.

• Boolesche Methoden konnen auch Pradikatsnamen tragen:zulaessig(), kleiner() (das noch besser alsoperator<), usw.

38/44

Benamung III

Je weiter außen (also je globaler) ein Name ist, desto langersollte er sein

class SignalHandler { // Full-length name: file scopeint signal; // Longer name: class scope

public:// Long name for methodint installNewSignal(int& newSignal) {

int t = signal; // Short name OK: short scopesignal = newSignal;newSignal = t;

return t;}

};

39/44

Benamung IV

Bekannte technische Konstanten konnen naturlich mit ihrenbekannten Namen definiert werden. Dann aber bitte in einerKlasse oder in einem geeigneten Namespace.

// Characteristic impedance of vacuum, in Ohmconst double PhysicalConstants::Z_0 = 376.730313461;

// Newtonian constant of gravitation, in mˆ3 kgˆ-1 sˆ-2const double PhysicalConstants::G = 6.673e-11;

// Speed of light in vacuum, in m sˆ-1const double PhysicalConstants::c_0 = 299792458.0;

// Mass of electron (at rest), in kgconst double PhysicalConstants::m_e = 9.10938188e-31;

// Number of printer’s points in an inch, in inˆ-1const double TypographicalConstants::pointsPerInch = 72.27;

40/44

Magic Numbers I

So nicht:

class Board {int board[8][8];

void initialize() {for (int i = 0; i <= 7; i++)for (int j = 0; j <= 7; j++)board[i][j] = 0;

}};

Bedeutet die zweite 7 dasselbe wie die erste 7? Sind beidevielleicht eigentlich 8 - 1?

Bei der Verwendung von Magic Numbers sind Instanzen

”derselben“ Zahl, sowie von verwandten Zahlen (x + 1, x - 1,2*x usw) nur schwer auszumachen

41/44

Magic Numbers II

Besser so:

class Board {enum Piece {

free,whiteKing, whiteQueen, whiteRook, whiteBishop, whiteKnight, whitePawn,blackKing, blackQueen, blackRook, blackBishop, blackKnight, blackPawn,

};static const int boardSize = 8;

Piece board[boardSize][boardSize];

static map<Piece,int> value; // Value of a piece, in pawns

void initialize() {for (int i = 0; i < boardSize; i++)for (int j = 0; j < boardSize; j++)board[i][j] = free;

}// ...

42/44

Magic Numbers III

// ...static void boardInitialize() {

value[free] = 0;value[whiteKing] = value[blackKing] = limits<int>::max();value[whiteQueen] = value[blackQueen] = 9;value[whiteRook] = value[blackRook] = 5;value[whiteBishop] = value[blackBishop] = 3;value[whiteKnight] = value[blackKnight] = 3;value[whitePawn] = value[blackPawn] = 1;

}};

43/44

Magic Numbers IV

Oder so:

class Board {static const int horizontalSize = 8;static const int verticalSize = 9;

int board[horizontalSize][verticalSize];

void initialize() {for (int i = 0; i < horizontalSize; i++)for (int j = 0; j < verticalSize; j++)board[i][j] = 0;

}};

44/44

Benamung: Abschluß

• Verschmutzen Sie den globalen Namensraum nicht unnotig

• Kapseln sie alle sichtbaren Namen in Namespaces oder inKlassen

• Geben Sie Namen nur die Sichtbarkeit, die sie unbedingtbenotigen

• Definieren Sie Namen so lokal wie moglich

• Vermeiden Sie (in der Regel) “magic numbers”