Unterprogramme - Kekziie/Informatik-II-SS-2017 GitHub Wiki
Unterprogramme als Black Boxes
- Unterprogramme sind Instruktionsfolgen
- lösen eine bestimmte Aufgabe
- identifiziert unter einen bestimmten Namen
- Details der Implementierung vor äußeren Einblicken verborgen
- Interaktion des Unterprogramms mit Umgebung erfolgt mit Hilfe von Schnittstellen
- Angabe der Eingabe und Ausgabe
- Funktionsweise des Unterprogramms angeben (Kommentar)
1.) Schnittstelle möglichst einfach
- klar strukturiert
- verständlich 2.) Alles was zur Verwendung notwendig, sollte über Schnittstelle bekannt gemacht werden 3.) Entwickler sollte nichts über das größere System wissen müssen
- Festlegung was die Black Box leistet
- wie sie durch Elemente der Schnittstelle (Parameter) gesteuert werden kann
- Vertrag des Unterprogramms: semantische/ syntaktische Definition der Schnittstelle
Beispiel:
Kommentare müssen über die Wirkung von Unterprogrammen und Benutzung von Parametern aufklären
/** erzeugt eine zufaellige Zahl zwischen 2 und 11 (einschließlich)
* @return eine zufaellige Zahl zwischen 2 und 11
*/
public static int draw() {
java.util.Random random = new java.util.Random();
return random.nextInt(10) + 2;
}
- ohne Rückgabe
- Prozeduren ohne Ergebniswert
- mit Rückgabe
- Funktionen
- jedes Unterprogramm muss innerhalb einer Klasse definiert werden
- Ziel:
- Gruppierungen von zusammengehörigen Unterprogrammen/ Variablen in Klassen zur besseren Strukturierung
- objektorientierte Sprache, (Unterprogramm = Methoden) definieren Verhalten und beschreiben Zustand von Objekten
Allgemeine Form einer Unterprogramm-Definition
<Modifizierer>
<Rueckgabe-Datentyp> <Unterprogramm-Name> (<Parameter-Liste>) {
<Anweisungen> // Rumpf des Unterprogramms
}
Beschreibung:
- :
- können zu Beginn der Definition auftreten
- können leer bleiben
- legen Zugriff auf Unterprogramm fest
- :
- legt Datentyp fest, den das Unterprogramm als Ergebnis liefert
- kein Ergebniswert -> Anzeige durch void (in imperative Programmierung: Bezeichnung dieses Unterprogramms als Prozedur)
- Ergebniswert -> Schlüsselwort des Datentypes (Unterprogramm mit Resultat bezeichnet als Funktion)
Beispiel: main-Methode
public static void main(String[] args) { ... }
Beschreibung:
- : public und static
- public regelt, wer Methode aufrufen darf
- static regelt, ob die Methode an eine Instanz der Klasse/ an die Klasse selbst gebunden ist
- : void
- leer, d.h. es wird kein Ergebnis geliefert
- <(Unter-)Programm-Name>: main
- : String[]
- ein Argument: Arraytyp String[] mit dem Bezeichner args
- Unterprogrammparameter werden syntaktisch stets durch die "()" identifiziert
- Parameter-Liste kann
- leer sein ( Unterprogramm enthält keine Eingabe), oder
- eine oder
- mehrer Parameterdeklarationen besitzen
Form einer Parameterdeklaration:
<Typ> <Parameter-Name>
- mehrere Deklarationen werden jeweils einzeln definiert, durch Komma getrennt
- Zusammenfassungen sind nicht erlaubt!
Beispiel: 2 Parameter vom Typ double + 1 Parameter vom Typ int
... (double x, double y, int a) { ... }
nicht erlaubt:
... (double x,y, int a) { ... }
- ein Rumpf besteht aus:
- Deklaration lokaler Variablen und Konstanten (vor ersten Anwendung initialisiert)
- formale Parameter werden wie lokale Variablen verwendet
- Initialisierung dieser Größen mit Werten erfolgt durch Übergabe aktueller Parameter
- Anweisungen legen Funktionalität fest
- Anweisungen:
- Einzelanweisungen
- zusammengesetzte Anweisungen
- Wiederholungsanweisungen
- Programmverzweigungen
- Aufrufe von Unterprogramme
Beispiele: Deklaration
playGame: Unterprogramm ohne Parameter und ohne Rückgabewert
public static void playGame() { ... }
getNextNumber: Unterprogramm mit einem Parameter number vom Typ int und Rückgabewert vom Typ int, kein
int getNextNumber(int number) { ... }
lessThan: Unterprogramm 2 Parameter x und y jeweils vom Typ double, Rückgabewert vom Typ boolean
static boolean lessThan(double x, double y) { ... }
- Unterprogramme definieren Methoden, mit denen der Zustand von Objekten verändert/angezeigt werden kann
- Modifizierer legen Eigenschaften der Unterprogramme fest:
- wo sie aufgerufen werden können
- ob sie mehrfach mit der Instanz einer Klasse existieren oder
- durch die Klassendefinition existieren
Beispiele:
- public/ private
- spezifizieren Zugriff für die Funktion/Methode
- public: Unterprogramm von überall aufrufbar, auch außerhalb der Klasse in der es definiert wurde
- private: Methode nur innerhalb der Klasse aufrufbar
Aufruf aus derselben Klasse:
<Unterprogramm-Name>(<Parameter>)
Aufruf außerhalb der Klasse:
<Klassen-Name>.<Unterprogramm-Name>(<Parameter>)
Hinweis:
- Liste der Parameter kann leer sein
- Klammern müssen sowohl bei der Deklaration als auch beim Aufruf angegeben werden
- Bsp. siehe oben playGame()
1.) in der selben Klasse wie die main() Methode definiert werden 2.) kann nicht innerhalb von main() oder anderen Funktionen definiert werden
Hinweis:
- Blöcke {...} dürfen innerhalb von Funktionen oder anderen einfachen Blöcken definiert werden (auch mehrfach verschachtelt)
- Reihenfolge der Definition von Funktionen ist beliebig
import ... ; // Funktionen und Variablen externer Klassen
// die hier verwendet werden sollen
public class <Klassen-Name> { // Klasse (Dateiname: <Klassen-Name>.java enthält Methode/Variablen)
public static void main(String[] args) {
<Anweisungen>
} // main() Methode: enthält das "Hauptprogramm" mit Anweisungen
static void <Name-1>(...) {
<Anweisungen>
} // Unterprogramm (Methode) mit Anweisungen ohne Ergebniswert
static int <Name-2>(...) {
<Anweisungen>
} // Unterprogramm (Methode) mit Anweisungen mit Ergebniswert vom Typ int
}
- bisherige Varianten:
- mit Eingabe
- ohne Eingabe
- Stellen, an denen die Parameter im Rumpf des Unterprogramms verwendet werden, dienen als Platzhalter für konkrete Werte
- beim Aufruf zur Verwendung der Methode mit aktuellen Werten belegt (Parameter-Substitution)
- erfolgt wie bei einer Initialisierung lokaler Variablen mit Anfangswerten
- Parameterübergabe erfolgt beim Aufruf der Methode
1.) aktuelle Parameter werden der Reihe nach ausgewertet
- Parameter können Variablen, Konstanten oder Ergebnisse der Auswertung von Ausdrücken sein 2.) berechnete Parameterwerte werden den formalen Parametern zugewiesen
- wichtig: Datentypen müssen kompatibel sein 3.) Rumpf der aufgerufenen Methode wird ausgeführt
- Block mit Anweisungen an Aufrufstelle in Programmablauf eingeschoben 4.) Ausführung des Methoden-Rumpfes liefert entweder:
- kein Ergebniswert (void) oder
- sie liefert ein Ergebniswert ()
- Rückgabe durch Steueranweisung return definiert 5.) Ergebniswert kann an Aufrufstelle in:
- einer Zuweisung oder
- einem Ausdruck verwendet werden
Beispiel: für 4.
static double readPosFloat(int counter) {
double length;
length = 4;
...
return length;
}
für 5.
val = scan.next.Double();
hyp = sin(ang) * sin(ang) + cos(ang) * cos(ang);
Möglichkeiten der Parameterübergabe:
- Wertübergabe ("call-by-value")
- Referenzübergabe ("call-by-reference")
- Variablen (/Konstanten) der einfachen Datentypen assoziieren mittels ihrer Namen direkt den Wert
- Wert ist im Speicher abgelegt
- Speicheradresse der Variable ist in Java nicht zugreifbar
- Array-Variable ist eine Referenz- oder Zeigervariable
- enthält nur die Adresse des Speichersegments, an der das Datenfeld beginnt, nach expliziten Reservierung
- Speicherplatz für Referenzvariable wird in Java angefordert mit Operator: new
- Aufsuchen eines adressierten Datenobjekts nennt man De-Referenzierung
- bei Werteübergabe werden die Werte der aktuellen Parameter in die Platzhalter der formalen Parameter kopiert
- Java verwendet call-by-value Mechanismus:
- Werte der aktuellen Parameter werden nach deren Auswertung an die Platzhalter im aufgerufenen Unterprogramm kopiert
- wichtige Eigenschaften:
- alle Änderungen der Variablen innerhalb des Unterprogramms für das aufrufende Programm verborgen
- Rückgabewert wird nur nach außen geliefert
- Adressen der aktuellen Parameter an die formalen Parameter gebunden
- im Rumpf der Methode wird eine Referenz auf den Wert des aktuellen Parameters benutzt
- Methode kann dadurch die Werte der übergebenen Variablen ändern
- Vorteile:
- durch Verwendung einer Adresse der zugreifbaren Objekte wird u.U. Aufwand reduziert -> Effizienz erhöht
- gerade bei großen Datenmengen müssen sonst alle Daten bei der Übergabe zunächst kopiert werden
- Variablen können innerhalb einer Methode (Unterprogramm) deklariert werden
- nur innerhalb des umschließenden Blocks gültig/ zugreifbar
- lokale Variablen müssen explizit mit einem Wert initialisiert werden
- Java Programme sind in Form von Klassen aufgebaut und strukturiert
- man kann Variablen als Teil einer Klasse deklarieren -> Klassen-Variablen
Beispiel:
public class <Klassen-Name> {
// Klassen-Variablen
// im Block der Klasse definiert/ aus der Methode heraus zugreifbar
static String usersName;
public static int numberOfPlayers;
private static double velocity,
time;
public static void main(...) {
...
}
static double <Methoden-Name>(...) {
...
}
}
- Deklaration einer Klassen-Variable analog zu lokalen Variablen
- Klassen-Variablen können statisch/ nicht-statisch sein
- statische Klassen-Variablen gehören zu der Klasse, in der sie deklariert wurden
- existieren, solange ein Programm ausgeführt wird
- Inhalt (Wert) einer statische Klassen-Variable kann von jeder Methode (Unterprogramm) der Klasse gelesen/ verändert werden
- statische Klassen-Variablen werden von allen (staitschen) UNterprgrammen der Klasse gemeinsam verwendet -> globale Variablen der Klasse
- Festlegung der Eigenschaften von Klassen-Variablen durch Modifizierer
- static:
- Klassen-Variable existiert so lange, wie die Klasse ausgeführt wird
- d.h. solange Speicher allokiert ist
- public:
- Inhalt der Klassen-Variable ist auch von einer Methode in einer anderen Klasse aus lesbar/ änderbar
- private:
- Inhalt der Klassen-Variable ist nur innerhalb derselben Klasse aus lesbar/veränderbar
- Klassen-Variable werden bei der Deklaration automatisch mit einem default-Wert initialisiert
- Klasse definiert sog. Namensraum:
Zugriff aus einer Methode innerhalb der Klasse
<Variablen-Name>
Zugriff aus einer Methode außerhalb der Klasse (wenn Variable nicht als private deklariert)
<Klassen-Name>.<Variablen-Name>
- Variablen, die außerhalb einer Methode deklariert werden, sind aus Sicht eines Unterprogramms global
- da nicht lokal im Block des Unterprogramms deklariert
- bisher verwendete Programm verwenden statische Unterprogramme (Methoden)
- Methode können von jeder Stelle der main(...) oder anderen Unterprogrammen (Funktionen) aufgerufen werden
Beispiel: public class Programm { public static void main(String[] arg) { ... a = readPosFloat(1); ... hypotenuse1 = computeRectTriangleHypotenuse(a,b); } static double readPosFloat(int counter) { ... } static double computeRectTriangleHypotenuse(double length1, double length2) { ... } }
- in objektorientierter Programmierung können durch Klassenbildung und Definition von Paketen große Bibliotheken entwickelt werden
- verschieden Methoden und Eigenschaften gruppiert
- auf Elemente kann über ihre jeweiligen Namen in der Hierarchie zugegriffen werden
- Import-Anweisungen
- Klassen- / Funktionsnamen im Namensraum der aufrufenden Klasse bekannt gemacht
- dient zur Vereinfachung der Struktur
Beispiel: Mathematische Funktion
Auswahl mit Selektoren "." a = java.lang.Math.cos(...);
Importieren (import) von Funktionen aus Paketen und Klassen:
individuell: import static java.lang.Math.cos;
oder alle: import static java.lang.Math.*;
Sie werden innerhalb eines Unterprogramms definiert. Die Deklaration innerhalb einzelner Blöcke möglich.
- Lebenszeit: während der Laufzeit des Unterprogramms/ Blocks, in dem die Variable gültig ist, kann auf Variable zugegriffen werden
- Zugriff: nur innerhalb des jeweiligen Unterprogramms oder in der main(...)
Beispiel: static double readPosFloat(int counter) { double length; // local variable ... length = sc.nextDouble(); ... }
Einordnung: Lokale Variablen sind nicht-statische Variablen, da sie jeweils beim Aufruf einer Funktion neu instanziiert und nach Beendigung der Ausführung zerstört werden
Sie werden außerhalb aller Unterprogramme definiert.
- Deklaration erfolgt innerhalb der Klasse
- Lebenszeit: Variablen mit einem eindeutigen Namen existiert einmal pro Klasse während der gesamten Ausführungszeit des Programms, solange Variable durch die Deklaration mit Modifizierer static erzeugt wird (-> statische Variable)
- Zugriff: vom jedem Unter- / Hauptprogramm auf Variable zugreifbar
- Änderung des Wertes wirkt sich auf alle Unterprogramme aus, in denen die Variable verwendet wird
Beispiel: public class Program { static int counter = 0; // globale Variable
public static void main(String[] args) {
int var = 5; // lokale Variable
...
counter = counter + 1;
...
}
}
- wie beim Import von Funktionen
- mit Hilfe der Hierarchie kann über ihre jeweilige Namen zugegriffen werden
Beispiel: Systemausgabe
Auswahl mit Selektoren java.lang.System.out.<...>;
Importieren (import) von (statischen) Funktionen aus Paketen/ Klassen
individuell: import static java.lang.System.*;
oder alle: import static java.lang.*;
- dynamische Eigenschaft von Variablen
- hängt vom Programmablauf ab
Programmstart:
- Klassen-Variablen werden zu Beginn der Programmausführung angelegt
- Speicherplatz wird reserviert
- Zuweisung eines default-Wert
- nach Beendigung des Programms werden Variablen vernichtet
Aufruf eines Unterprorgramms (Methode):
- Anlegung formaler Parameter
- Wert der formalen Parameter ist gleich dem Wert der korrespondierenden aktuellen Parameter
- bei Ausführung eines Unterprogramms werden ihre Anweisungen ausgeführt
- lokale Variablen werden bei Deklaration angelegt, deren Initialwert ist undefiniert oder muss explizit zugewiesen werden
- bei Beendigung des Unterprogramms werden formale Parameter und ihre Inhalte vernichtet
- Anzahl der aktuellen Parameter beim Aufruf immer genau mit der Liste der formalen Parameter übereinstimmen
- in Java können u.U. bei verschiedenen Aufrufen eines Unterprogramms eine unterschiedliche Anzahl von aktuellen Parametern angegeben werden
Format und Verwendung:
Syntax für variable Parametermengen (Bsp.) public static double average (double... numbers) {
Hinweis: "..." (nach dem Datentyp) bedeutet, dass beim Aufruf des Unterprogramms eine beliebige Anzahl von Parameter des angegebenen Typs übergeben werden können
Beispiele average(3.14, 2.45, 3.2) average(0.485) average() // legaler Aufruf average(1, 2, 3, 4) // legaler Aufruf mit automatischer Typenkonvertierung
Ausführung:
- alle aktuellen Parameter, die mit dem Datentyp des "variable arity" Parameters korrespondieren, werden in einem Array zusammengefasst
- Array wird an Unterprogramm übergeben
- Konsequenz:
- die ...-Parameterliste wird in dem Unterprogramm (Methode) als gewöhnlicher Parameter vom Typ [] behandelt
- die Länge des Arrays gibt an, wie viele aktuelle Parameter übergeben wurden
Beispiel: public static double average(double... numbers) { double sum;
sum = 0.0;
for (int i = 0; i < numbers.length; i++)
sum = sum + numbers[i];
return (sum / numbers.length);
}
Hinweise:
- die ...-Parameterliste kann nur als letzter formaler Parameter in der Definition eines Unterprogramms angegeben werden
- anstelle der Liste individueller Argumente kann auch ein Array übergeben werden
Beispiel: double[] salesData;
av = average(salesData); // Mittelwert der Array Elemente
Funktionen - Unterprogramme mit Rückgabewert
Schema: static () { return ; }
Unterprogramm veranlasst die Rückgabe eines Ergebniswertes an die aufrufende Umgebung mittels return-Anwesiung return ;
Hinweis:
- Datentyp vom muss zum des Unterprogramms kompatibel sein
- ggf. erfolgt eine explizite Datentypanpassung
- können beliebige Java-Typen sein
- Ausführung einer return-Anweisung bewrikt die sofortige Termination der Unterprogramm-Ausführung und die unmittelbare Rückkehr zur Aufrufstelle
- es können return-Anweisungen an verschiedenen Stellen im Rumpf auftreten
- das Ergebnis einer Funktion kann:
- an eine Variable zugewiesen werden
- in einem Ausdruck weiter verwendet werden
- direkt als Parameter für ein (anderes) Unterprogramm genutzt werden
Beispiel: static int match(int[] arr, int elem) { for (int i = 0; i < arr.length; i++) { if (elem == arr[i]) return (i); // Element mit vorhandenen Index } return (-1); // Element nicht gefunden }
- bei manchen Unterprogrammen ist man nur an der Ausführung des Rumpfes interessiert
- nicht speziell an Ergebniswerte
- in solchen Fällen void angegeben
- der in return-Anweisung muss entfallen oder
- return-Anweisung entfällt insgesamt
- Unterprogramm können Einfluss auf Zustand des Objekts/ Klasse nehmen
- Einflüsse über Ein- und Ausgabeparameter
-
Wird aus einer Methode auf (staische) Klassen-Variablen zugegriffen und deren Wert verändert, dann bleiben deren Änderungen auch anch der Termination der Methode für das Gesamtprogramm weiterhin sichtbar
-
Erhält ein Unterprogramm eine Zeiger-(Referenz-) Variable als Parameter, so kann das Unterprogramm durch Änderung der über die Zeiger-Variable referenzierte Speicherinhalte ebenfalls Seiteneffekte erzielen
Vorsicht:
- Seiteneffekte vom ersten Typ sollen sehr gut dokumentiert sein, denn man sieht sie dem Unterprogramm nicht an
- unter Umständen können kaum vorhersehbare Effekte auftreten
Beispiel: public class TestClass { static int number;
public static void main(String[] args) {
number = 1;
System.out.println(number); // Ergebnis: 1
sideEffect();
System.out.println(number); // Ergebnis: 5
}
static void sideEffect() {
number = 5;
}
}
Bemerkung:
- aus Sicht von main() ist anhand des Aufrufs von sideEffect() nicht ersichtlich, dass number verändert wird
- Seiteneffekte können in komplexen Programmen zu Problemen führen, da etwaige Fehler hierbei nur schwer zu finden sind