Home > Informatik > Stufe Q1 > Referenzen

15.1 Referenzen, Allgemeines

Allgemeines - dynamischer Stack - sortierte Liste - NRW-Liste - doppelt verkettete Liste

"Die Schülerinnen und Schüler erläutern Operationen dynamischer (linearer oder nichtlinearer) Datenstrukturen."

Zitat aus " Kompetenzerwartungen und inhaltliche Schwerpunkte in der Qualifikationsphase", Kernlehrplan Informatik NRW.

Zeiger sind nichts Neues!

Wussten Sie eigentlich, dass wir bisher schon immer mit Zeigern gearbeitet haben, wenn wir Java-Programme geschrieben haben? Immer dann, wenn Sie ein Objekt einer Klasse deklarieren, verwenden Sie einen Zeiger! Schauen wir uns mal die einfache Klasse Bruch an:

Klasse Bruch
public class Bruch
{
    int zaehler, nenner;
    
    public Bruch(int z, int n)
    {
        zaehler = z;        
        nenner  = n;
    }
    
    public void show()
    {
       System.out.println(zaehler + "/" + nenner);
    }
}

Diese Klasse hatten wir bereits in der letzten Folge bei der Implementation des ADT List verwendet.

Die beiden Attribute zaehler und nenner dieser Klasse sind ganz normale primitive Datentypen, keine Zeiger. Das heißt, wenn wir mit einem Supermikroskop in die Speicherzellen von zaehler und nenner hineinschauen könnten, würden wir dort tatsächlich int-Zahlen entdecken, die den Zähler und den Nenner eines Bruches darstellen.

Klasse ZeigeZeiger

Wir machen nun ein kleines Experiment. Betrachten Sie dazu den folgenden Quelltext:

public class ZeigeZeiger
{
    Bruch b1, b2;
    
    public ZeigeZeiger()
    {
       b1 = new Bruch(3,4);
       b2 = new Bruch(5,6);
       
       System.out.println(b1);
       System.out.println(b2);
    }
}

Im Konstruktor dieser Klasse werden zunächst zwei Bruch-Objekte b1 und b2 erzeugt, das eine Objekt repräsentiert den Bruch $\frac{3}{4}$, das andere Objekt den Bruch $\frac{5}{6}$. Anschließend sollen die Werte der Brüche in der Konsole angezeigt werden. Unser Software-Entwickler Karl Schlau hat sich jetzt aber etwas besonderes ausgedacht: Was passiert, wenn man nicht b1.show() und b2.show() aufruft, wie es sich eigentlich gehört, sondern die beiden Objekt direkt mit System.out.println() anzeigen lässt?

Hier das Ergebnis eines Testdurchlaufs:

asdf

Konsolenausgabe für die Anzeige der Objekte b1 und b2

Es wird tatsächlich etwas angezeigt, allerdings nicht die Werte der Brüche, sondern es erscheint zweimal eine sehr "kryptische" Ausgabe, bestehend aus dem Wort "Bruch", einem at-Zeichen und einer achtstelligen Hexadezimalzahl wie 43d10ad7 oder 5b4c6337.

Wie sie diese beiden kryptischen Ausgaben zu interpretieren?

Das at-Zeichen kennen Sie von E-Mail-Adressen, es besagt so viel wie "befindet sich an". Das Wort "Bruch" in den Ausgaben bezieht sich auf die Bruch-Objekte b1 und b2. Und die lange Zahl ist nichts anderes als die Adresse im Arbeitsspeicher, an der sich die Inhalte der beiden Objekte b1 und b2 befinden. Das Objekt b1 befindet sich an der Speicheradresse 43d10ad7, und das Objekt b2 an der Speicheradresse 5b4c6337.

Schauen wir uns dazu mal eine kleine Graphik an:

asdf

Eine modellhafte Darstellung des Arbeitsspeichers

Die Abbildung 2 soll ein Modell des Arbeitsspeichers eines Rechners zeigen. Die einzelnen Speicherzellen werden hier durch kleine quadratische Kästchen dargestellt. Wenn die Klasse ZeigeZeiger ein Objekt b1 der Klasse Bruch deklariert, dann wird irgendwo im Speicher eine Speicherzelle für die Variable b1 reserviert. Diese Speicherzelle befindet sich in unserem Modell in dem Kasten mit der Adresse 4.  Entsprechend ist die Variable b2 in dem Kasten mit der Adresse 7 untergebracht.

In diesen beiden Kästen steht jetzt aber nicht der Wert des Bruches. Bei der Deklaration

Bruch b1, b2

ist noch gar kein Wert angegeben worden, das geschieht ja erst bei der Initialisierung:

b1 = new Bruch(3,4);
b2 = new Bruch(5,6);

Bei der Initialisierung von b1 passiert folgendes: Es wird eine freie Stelle im Arbeitsspeicher gesucht, in die die beiden int-Attribute zaehler und nenner eines Bruch-Objektes "hineinpassen". Ist dies der Fall, so werden die beiden Attributwerte des Bruches in diese Speicherzellen hineingeschrieben. In die eine Speicherzelle wird der Wert 3 geschrieben, in die andere Speicherzelle der Wert 4. Das Gleiche passiert bei der Initialisierung von b2. In die eine Speicherzelle wird der Wert 5 geschrieben, in die andere der Wert 6.

In der Abbildung 2 sieht man auch, welche Werte in den Speicherzellen 4 und 7 für die Variablen b1 und b2 gespeichert sind: 229 und 334. Das sind die Adressen in unserem Modell-Arbeitsspeicher, an denen sich die beiden Bruch-Objekte befinden b1 und b2befinden.

In Wirklichkeit ist ein Arbeitsspeicher natürlich viel, viel größer als in unserem Modellsystem, und für die Speicherung von Speicheradressen reichen drei Ziffern lange nicht aus. Das erklärt dann die kryptisch langen Speicheradressen, die wir in unserem Experiment gesehen haben.

Für Profis

Wie groß ist eigentlich der Speicherbereich, den man mit Adressen wie 43d10ad7 ansprechen kann?

Rechnen wir einmal nach. Die Adresse besteht aus acht Ziffern im Bereich 0, 1, 2, ..., 9, a, b, ... , f. Es handelt sich ja um Hexadezimalwerte, also um Zahlen, die auf dem 16er System beruhen, und nicht auf dem üblichen 10er System.

8 Stellen mit je 16 Werten führt zu einem Bereich von 168 = 4.294.967.296. Das sind rund 4 Milliarden Byte, oder 4 Millionen Kilobyte, 4000 Megabyte oder 4 Gigabyte.

Das hört sich erst mal nach sehr viel an, ist es aber nicht. Moderne Rechner besitzen einen deutlich größeren Arbeitsspeicher von 16, 32 oder gar 64 Gigabyte.

Die Abbildung 2 zeigt uns auch, warum man ständig von Zeigern spricht, wenn man meint, dass eine Variable wie b1 die Adresse eines Speicherbereichs speichert. Der Inhalt dieser Variable "zeigt" sozusagen auf den eigentlichen Speicherbereich. Oft wird auch der Begriff Referenz für einen Zeiger verwendet oder der englische Ausdruck Pointer. Referenzen und Zeiger sind nicht unbedingt das Selbe, aber um diese feinen Unterschiede müssen wir uns hier nicht kümmern.

Wenn Sie selbst einmal die Verhältnisse im Arbeitsspeicher graphisch darstellen müssen, können Sie das übrigens einfacher machen als in der Abbildung 2. Betrachten Sie dazu die folgende Abbildung:

asdf

Ein Kästchenschema

Was Sie hier sehen, bezeichnet man als Kästchenschema. Die Speicherbereiche werden als einfache oder doppelte Kästchen dargestellt. Die Speicheradressen werden nicht angegeben, weil sie erstens nicht reproduzierbar und zweitens auch völlig unwichtig sind. Sie lesen dieses Kästchenschema wie folgt: Die Variable b1 zeigt auf den Bruch 3/4, und die Variable b2 zeigt auf den Bruch 5/6.

Vergleichen wir dieses Kästchenschema einmal mit dem, was uns der BlueJ-Objektinspektor anzeigt:

Beschreibung siehe folgenden Text

Der Objektinspektor von BlueJ zeigt ebenfalls Zeiger

Die Zeiger b1 und b2 werden im Objektinspektor ganz korrekt durch gebogene Pfeile dargestellt, um anzudeuten, dass es sich nicht um normale Variablen handelt, sondern um Referenzen auf einen anderen Speicherbereich. Durch Anklicken eines dieser Pfeile kommt man dann auf den eigentlichen Inhalt an der referenzierten Stelle:

Beschreibung siehe folgenden Text

Der eigentliche Inhalt der Variable b1

Damit wären wir auch schon am Ende der allgemeinen Einführung zum Thema Referenzen bzw. Zeiger angekommen.

Aufgabe 15.1-1

Auf der Seite "7.4 Objekt-Arrays besser verstehen" ist genau erklärt, wie die Speicherbelegung eines Objektarrays aussieht. Auch hier ist ständig von Referenzen bzw. Zeigern die Rede; allerdings haben Sie in der Stufe EF vielleicht noch nicht so ganz richtig verstanden, um was es hier überhaupt geht. Jetzt sollten Sie dazu aber in der Lage sein.

Ihre Aufgabe ist es also, einen kleinen mündlichen Vortrag vorzubereiten, in dem Sie erklären, wie ein Objektarray aufgebaut ist und welche Rolle dabei Referenzen spielen.

Als Nächstes wollen wir den ADT Stack mit Hilfe von Zeigern implementieren. Ein sehr anspruchsvolles Unterfangen!

Seitenanfang -
Weiter mit der dynamischen Implementation eines Stacks...