Konzepte der Programmiersprachen Sommersemester 2010 4. Übungsblatt Besprechung am 9. Juli 2010 http://www.iste.uni-stuttgart.de/ps/Lehre/SS2010/V_Konzepte/ Aufgabe 4.1: Klassen in C++ Das folgende C++-Programm berechnet Primzahlen. Nach welchem Algorithmus funktioniert dieses Programm? Beschreiben Sie die verwendete Klassenhierarchie. Welche Regeln gibt es in C++ den Zugriff auf die Komponenten einer Klasse? # i n c l u d e <iostream > c l a s s Knoten { public : Knoten ∗ Eingabe ; Knoten ( Knoten ∗ s ) { Eingabe = s ; } v i r t u a l i n t Ausgabewert ( ) { r e t u r n 0 ; } }; c l a s s Generator : p u b l i c Knoten { i n t Wert ; public : i n t Ausgabewert ( void ) { r e t u r n Wert ++; } Generator ( i n t S t a r t w e r t ) : Knoten ( 0 ) { Wert = S t a r t w e r t ; } }; c l a s s F i l t e r : p u b l i c Knoten { i n t Faktor ; public : i n t Ausgabewert ( ) ; F i l t e r ( Knoten ∗ s , i n t f ) : Knoten ( s ) { F a k t o r = f ; } }; i n t F i l t e r : : Ausgabewert ( ) { int n; do { n = Eingabe−>Ausgabewert ( ) ; } while ( ! ( n % F a k t o r ) ) ; return n ; } i n t main ( void ) { Generator C ( 2 ) ; Knoten ∗ S i e b = &C ; i n t Primzahl , max ; 1 s t d : : cout << "Primzahl-Sieb in C++\n\nWieviele Primzahlen? " ; s t d : : c i n >> max ; f o r ( i n t i = 0 ; i < max ; i ++) { Primzahl = Sieb −>Ausgabewert ( ) ; s t d : : cout << Primzahl << ’ ’ ; S i e b = new F i l t e r ( Sieb , Primzahl ) ; } s t d : : cout << "\n" ; return 0 ; } Aufgabe 4.2: Abstrakte Datentypen Vergleichen Sie die Möglichkeiten des Verbergens von privaten Eigenschaften eines Typs in Java und Ada. • Welche Möglichkeiten bietet Ada? • Welche Möglichkeiten bietet Java? • Welche Unterschiede ergeben sich für den Programmierer? Wie können voneinander abhängige Deklarationen auf Dateien/Pakete aufgeteilt werden? Fördern diese Möglichkeiten das Verständnis des Programm-Entwurfs? • In Ada 95 dürfen Pakete nicht zyklisch voneinander abhängen, welche Entscheidungen macht dies für den Entwurf von abstrakten Datentypen notwendig? • In Ada 2005 werden limited with clauses eingeführt, die nur eine incomplete view der darin deklarierten Typen sichtbar machen, auf diese Weise jedoch zyklische Paket-Abhängigkeiten ermöglichen. Dadurch entstehen zusätzliche Freiheiten, welche? Aufgabe 4.3: Vererbung, Varianz In der objektorientierten Programmierung gibt es die Begriffe der Kovarianz, Invarianz und Kontravarianz der Signaturen von Methoden. 1. Charakterisieren Sie mit einigen Sätzen jeden dieser Begriffe. 2. Welche Zusammenhänge gibt es mit der Präfix- und der Parameternotation für Methodenaufrufe? 3. Welche Vor- und Nachteile hat Kovarianz für ererbte Methodenimplementierungen? 2 Aufgabe 4.4: Wertesemantik und lokale Variablen In einer Programmiersprache sei folgende Semantik gegeben: • Es gibt Klassen, die Unterklassen besitzen können. Unterklassen können gegenüber ihren Oberklassen zusätzliche Datenattribute und Operationen besitzen. • In Unterprogrammen sind lokale Variablen zugelassen, deren deklarierter Typ eine Klasse ist. Klassen sind auch als deklarierter Typ von globalen Variablen und Parametern sowie Funktionsresultaten erlaubt. • Die Zuweisung hat Wertesemantik. Den obigen Variablen können Instanzen einer beliebigen Unterklasse zugewiesen werden, wobei alle Datenattribute der Unterklasse erhalten bleiben. Daraus ergeben sich allerdings einige Probleme. 1. Welches Implementierungsproblem tritt bei der Realisierung solcher lokaler Variablen auf? Wie kann es ohne Änderung der Sprachsemantik gelöst werden? Gibt es unvermeidbare Nachteile? 2. Können die Schwierigkeiten aus der vorigen Teilaufgabe durch Änderung der Sprachsemantik vermieden werden, ohne dabei die „typischen“ Charakteristika objektorientierter Sprachen zu verlieren? Aufgabe 4.5: Namensbindung Gegeben sind folgende Deklarationen und Definitionen in der objektorientierten Programmiersprache X++: class C is method M is begin ... end; method M1 (P : integer) is begin ... end; method M2 is begin ... end; end C; class C1 inherits C is method M (P : integer) is begin ... end; method M1 (P : integer) is begin ... end; method M2 return integer is begin ... end; end C1; class C2 inherits C is method M is begin ... end; method M1 is begin ... end; method M3 (P : integer) is begin ... end; 3 end C2; class C12 inherits C1 is method M2 is begin ... end; method M3 (P : integer) is begin ... end; end C12; 1. Welche Notation erwarten Sie für den Aufruf einer Methode in der Sprache X++? Wäre die andere Notation auch möglich? Wenn ja, was bedeuten dann die Methoden? 2. Nehmen Sie an, die Sprache benutze für Methodenaufrufe Präfix-Notation (also O.M() anstatt M(O)). Diskutieren Sie, welche Zuweisungen und Methodenaufrufe unter welchen Umständen legal sind und welche Methode tatsächlich aufgerufen wird. OC : C; OC12 : C12; OC := OC12; OC.M3(1); OC.M1(1); x : integer := OC.M2; 3. Welche Methoden werden in welcher Klasse redefiniert? Für welche Methoden erwarten Sie eine Überladung? Warum? 4. Welches weitere Problem kann es in diesem Zusammenhang mit der Parameterliste einer Methode noch geben? Hinweis: Versuchen Sie zu klären, was der folgende Code bedeutet. class A; class B inherits A; class C is method M1 (P : A); method M2 (P : B); method M3 (P : C); end; class D inherits C method M1 (P : B); method M2 (P : A); method M3 (P : D); end; 4 Aufgabe 4.6: Implementierung der dynamischen Bindung In dieser Aufgabe soll die Implementierung von Vererbung und dynamischer Bindung von Methoden veranschaulicht werden. Empfinden Sie die Klassenhierarchie aus dem folgenden kurzen C++ Programm in reinem C (ohne C++-Erweiterungen) oder einer anderen Sprache nach. Beachten Sie die Hinweise von Folie 7-23 des Skripts. DispatchVektoren können als Arrays von Funktionszeigern dargestellt werden. # i n c l u d e < s t d i o . h> class A { public : v i r t u a l void printname ( ) { p r i n t f ( "Klasse: A\n" ) ; } }; c l a s s B : public A { public : v i r t u a l void printname ( ) { p r i n t f ( "Redefinition in: B\n" ) ; } }; c l a s s C : public A { public : v i r t u a l void printname ( ) { p r i n t f ( "Redefinition in: C\nAufruf an Original:\n" ) ; A : : printname ( ) ; p r i n t f ( "Zurueck in C.\n" ) ; } }; i n t main ( i n t argc , char ∗ argv [ ] ) { f o r ( i n t i = 0 ; i < a r g c ; ++ i ) { s w i tc h ( argv [ i ] [ 0 ] ) { c a s e ’A’ : (new A)−>printname ( ) ; break ; c a s e ’B’ : (new B)−>printname ( ) ; break ; c a s e ’C’ : (new C)−>printname ( ) ; break ; } } } 5