Unterprogramme - Kekziie/Informatik-II-SS-2017 GitHub Wiki

Unterprogramme

Motivation

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)

Allgemeine Regeln

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

Schnittstelle

  • 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;
 } 

Typen von Unterprogrammen

  • ohne Rückgabe
    • Prozeduren ohne Ergebniswert
  • mit Rückgabe
    • Funktionen

Definition von Unterprogrammen

  • 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

Struktur

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

Parameter-Liste

  • 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) { ... }

Rumpf

  • 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) { ... }

Modifizierer

  • 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 Unterprogramm

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()

Wo wird ein Unterprogramm definiert?

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

Allgemeiner Aufbau

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
}

Parameterübergabe

Mechanismen

  • 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

Abarbeitung des Methodenaufrufs

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);

Substitutionsregel

Möglichkeiten der Parameterübergabe:

  • Wertübergabe ("call-by-value")
  • Referenzübergabe ("call-by-reference")

Werte

  • 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

Adressen (Zeiger) bei Arrays

  • 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

Werübergabe ("call-by-value")

  • 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

Referenz- /Adressübergabe ("call-by-reference")

  • 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

Gültigkeitsbereiche und Zugriffe

lokale Variablen und Klassen-Variablen

  • 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

Klassen-Variable

  • 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, Zugriff und Gültigkeitsbereich

  • 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

Statische Methoden und Variablen

  • 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) { ... } }

Verwendung statischer Methoden anderer Klassen

  • 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.*;

Variablen in Einzelprogrammen

Lokale Variablen

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

Globale Variablen

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;
          ...
     }
}

Verwendung statischer Variablen anderer Klassen

  • 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.*;

Lebensdauer von Variablen

  • 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

Unterprogrammaufrufe

  • 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

Rückgabewerte

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

Termination

  • 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 }

Ergebnisse

  • 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

Vorsicht mit Seiteneffekten

  • Unterprogramm können Einfluss auf Zustand des Objekts/ Klasse nehmen
  • Einflüsse über Ein- und Ausgabeparameter
  1. 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

  2. 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
⚠️ **GitHub.com Fallback** ⚠️