Beruflich Dokumente
Kultur Dokumente
3. Jahrgang
p: &c * … Inhaltsoperator
(Dereferenzierungs-
c: 'a' operator)
& … Adressoperator
Zeiger
• Weitere Beispiele:
• Bemerkung:
– Ein Zeiger ist eine Variable.
– Sein Wert muss irgendwo im Speicher gespeichert
werden.
Zeiger: Beispiel
• Annahme:
– Der Wert von x ist im Speicher an der Adresse 100
gespeichert,
– y an der Adresse 200 und
– ip an der Adresse 1000.
Zeiger: Beispiel
Zeiger: Beispiel
Zeiger: Beispiel
Zeiger: Beispiel
Zeiger: Beispiel
-> … Strukturzeiger
Zeiger dereferenzieren
void fi(char v[ ])
{
for (int i=0; v[i]!=0; i++) cout << v[i];
}
void fp(char v[ ])
{
for (char* p = v; *p!=0; p++) cout << *p;
}
Zeiger
• Anwendungsbeispiel voriger Funktionen:
int main()
{
char f[]={'Z','e','i','g','e','r','\0'};
fi(f);
cout << endl;
fp(f);
cout << endl << endl;
}
Zeiger
• Ausgabe:
Zeiger
• Warum also Zeiger?
– Das Übergeben von Daten wird stark beschleunigt, da
nur eine Adresse anstatt der komplette Datensatz
übergeben wird.
– Es lassen sich auf schnelle Art und Weise Felder
verwalten und manipulieren.
– Auch viele Funktionen liefern einen Zeiger als
Funktionswert zurück (Rückgabe von mehreren
Werten).
– Behandlung komplexer Datenstrukturen wie z.B.
verkettete Listen.
Zeiger: Übungsbeispiel
• Schreibe ein Unterprogramm, welches "Werte"
vertauscht (Swap-Funktion selbstgemacht):
int i = 1;
int& r = i; // r und i beziehen sich auf denselben
int
int x = r; // x = 1
r = 2; // i = 2
Referenzen
• Referenzen werden hauptsächlich verwendet,
um Argumente an Funktionen zu übergeben
(call-by-reference).
• Gegenüber der Wertübergabe hat das den
großen Vorteile, dass die übergebenen
Variablen verändert werden können.
• Gegenüber der Verwendung von Zeigern ist
neben der einfacheren Syntax der Hauptvorteil,
dass die Existenz der übergebenen Variable
garantiert ist. (Ein Zeiger könnte dagegen
nirgendwohin oder auf einen bereits wieder
freigegebenen Speicherbereich zeigen.)
Referenzen
• Wo das Referenzierungszeichen (&) steht bleibt
dem Benutzer überlassen.
• Alle folgenden Zeilen haben also dieselbe
Bedeutung:
#include <string>
…
struct adresse
{
string name;
string vorname;
int PLZ;
string ort;
};
struct: Übungsbeispiel
• Mögliche Lösung:
…
adresse freunde[99];
…
for (i=0;i<anzahl;i++)
{
cout << freunde[i].vorname << " ";
cout << freunde[i].name << endl;
cout << freunde[i].PLZ << " ";
cout << freunde[i].ort << endl << endl;
}
struct: verschachtelte Strukturen
• Definition der Strukturen:
struct adresse
{
int PLZ;
char* ort;
};
struct person
{
char* name;
char* vorname;
adresse wohnen;
int alter;
};
struct: verschachtelte Strukturen
• Deklaration der Strukturen lehrer und einem
Feld von Strukturen schueler[]:
schueler[0].name = "Meier";
schueler[0].vorname = "Hans";
schueler[0].wohnen.PLZ = 1160;
schueler[0].wohnen.ort = "Wien";
schueler[0].alter = 15;
struct: verschachtelte Strukturen
• Ansprechen von Strukturelementen:
struct koordinate
{
int x,y;
};
struct: Zeiger auf Strukturen
• Direkte Verwendung einer Struktur:
koordinate oben;
oben.x = 0;
oben.y = 15;
koordinate* p;
p = &oben; // Zeiger muß initialisiert werden, da er zunächst nirgends
hinzeigt!!!
struct adresse
{
char* name;
char* vorname;
int PLZ;
char* ort;
};
ip = 0; // Zeiger sichern
fp = 0;
Dynamische Speicherverwaltung
• Übungsbeispiel:
– Lege eine Struktur dynamisch an!
– Verwende dazu die schon bekannte Struktur
"Koordinate".
– Weise den beiden Koordinaten Werte zu, gib diese
dann aus.
– Anschließend ist der Speicher wieder frei zu geben
und die Zeiger zu "sichern".
Dynamische Speicherverwaltung
koordinate* p = 0;
p = new koordinate;
p->x = 1;
p->y = 2;
delete p;
p=0;
Dynamische Datenstrukturen
• Dynamische Datenstrukturen ändern ihre Struktur
und den von ihnen belegten Speicherplatz
während der Programmausführung.
• Sie sind aus einzelnen Elementen, den so
genannten 'Knoten', aufgebaut, zwischen denen
üblicherweise eine bestimmte Nachbarschafts-
Beziehung (Verweise) besteht.
• Die Dynamik liegt im Einfügen neuer Knoten,
Entfernen vorhandener Knoten und Änderung der
Nachbarschaftsbeziehung.
Dynamische Datenstrukturen
• Der Speicherplatz für einen Knoten wird erst bei
Bedarf zur Programmlaufzeit allokiert.
• Es handelt sich hierbei um Strukturen, die als
'Listen' oder 'Bäume' bezeichnet werden.
• Die Beziehung der einzelnen Knoten
untereinander wird sinnvollerweise über Zeiger
hergestellt, die jeweils auf den Speicherort des
"logischen Nachbarn" zeigen.
• Jeder Knoten wird daher neben den jeweils zu
speichernden Nutzdaten mindestens einen Zeiger
auf die jeweiligen "Nachbar"-Knoten enthalten.
Dynamische Datenstrukturen
• Die wichtigsten dieser "verketteten
Datenstrukturen" oder auch "selbstbezüglichen
(rekursiven) Datenstrukturen" sind :
– Lineare Listen
• einfach verkette Listen
• doppelt verkette Listen
– Bäume
• Binärbäume (zwei Nachfolger/Nachbarn)
• Vielweg-Bäume (mehr als zwei Nachfolger/Nachbarn)
Dynamische Datenstrukturen
• Die wichtigsten Operationen mit dynamischen
Datenstrukturen sind:
– Erzeugen eines neuen Elements
– Einfügen eines Elements
– Entfernen eines Elements
– Suchen eines Elements
Dynamische Datenstrukturen
• Im Folgenden werden wir uns mit dem Aufbau
verketteter Datenstrukturen mittels rekursiver
Strukturen beschäftigen.
Listen
• Eine Liste ist eine Sammlung von sequenziell
angeordneten Elementen.
• Eine Liste kann ihren Benutzern eine Vielzahl
unterschiedlicher Operationen anbieten.
• Typisch für Listen ist, dass
– die Liste nicht auf eine bestimmte Größe festgelegt ist
und Elemente nach Belieben eingefügt und
herausgenommen werden können, sowie dass
– zum Zugriff auf ein Listenelement die Liste vom
Anfang an durchlaufen werden muss. (Im Gegensatz
zu Feldern gibt es keinen "Listen-Index").
Verkettete Listen
• Das Konzept "Verkettete Liste" bezieht sich auf
die Implementierung einer Liste in Form von
einzelnen Knoten, die über Zeiger miteinander
verbunden sind.
• Verkettete Listen können in unterschiedlicher Art
realisiert werden:
– Einfach verkettete Listen: Bei ihnen enthält jeder
Knoten neben dem Listenelement einen Verweis auf
den Nachfolgerknoten.
– Doppelt verkettete Listen: Jeder Knoten beinhaltet
einen Verweis auf den Nachfolger und den
Vorgänger.
Einfach verkettete Liste
Doppelt verkettete Liste
Einfach verkettete Liste
• Die Basis einer verketteten Liste ist eine
Struktur, die einerseits die eigentlichen Daten
und andererseits einen Zeiger enthält, um auf
das nächste Element der Liste zu verweisen:
Einfach verkettete Liste
• Etwas verblüffend ist die Verwendung des Typs
ListenKnoten innerhalb der Deklaration des Typs
ListenKnoten.
• Dem Compiler muss an dieser Stelle das
genaue Aussehen des Typs ListenKnoten noch
nicht bekannt sein, da hier lediglich ein Zeiger
darauf definiert wird.
• Ein Zeiger ist aber immer gleich groß, ganz
gleich, auf was er zeigt.
Einfach verkettete Liste
node old
Anfang
Einfach verkettete Liste
• Die Variable Anfang ist ein Zeiger auf den Typ
ListenKnoten und bildet die Basis für den Zugriff
auf die verkettete Liste vom Programm aus.
• Über den Anfang erreicht man den ersten
Listenknoten.
• Dort enthält das Element next den Verweis auf den
nächsten Listenknoten. So kann sich das
Programm durch die Liste hangeln, bis next einmal
0 ist. Damit wird das Ende der Liste angezeigt.
• Ist die gesamte Liste leer, muss die Variable
Anfang 0 enthalten.
Einfach verkettete Liste
• Ein neuer Listenknoten wird durch Aufruf von
new erzeugt.
• Dabei muss darauf geachtet werden, dass der
Zeiger next gleich korrekt gesetzt wird.
• Die Variable old ist ein Zeiger auf einen zu
löschenden Listenknoten.
Einfach verkettete Liste
• Übungsbeispiel:
– Fülle eine einfach verkette Liste vom Typ
ListenKnoten mit Zahlen – solange bis 0 eingegeben
wird.
– Füge die jeweils neuen Elemente am Anfang ein.
– Gib anschließend die Liste in umgekehrter
Reihenfolge aus und lösche die ausgegebenen
Elemente.
Einfach verkettete Liste
Einfach verkettete Liste
Einfach verkettete Liste
ListenKnoten* Anfang = 0;
node
Anfang
data NULL
NULL
node
Anfang
node
Anfang
old
old = Anfang;
Anfang = Anfang->next;
delete old;
Einfach verkettete Liste
• Übungsbeispiel:
– Die Funktionsweise des vorhergehenden Programms
soll erhalten bleiben.
– Schreibe nun aber folgende Funktionen zum
Bearbeiten der Liste:
void ausgabe (void)
void loesche_alles (void)
void voranstellen (int daten)
Einfach verkettete Liste
Einfach verkettete Liste
Einfach verkettete Liste
Einfach verkettete Liste
• Übungsbeispiel:
– Erweitere das Programm um eine Menüführung.
– Beispiel siehe nächste Folie:
Einfach verkettete Liste
Einfach verkettete Liste
• Übungsbeispiel:
– Erweitere das Programm um die Funktion:
void anhaengen (int daten)
Anfang
node
ListenKnoten* node = Anfang;
while(node->next != NULL) node = node->next;
node->next = new ListenKnoten; data NULL
node = node->next;
node->data = daten;
node->next = NULL;
Einfach verkettete Liste
• Übungsbeispiel:
– Erweitere das Programm um die Funktion:
void sortiert_einfuegen (int daten)
Anfang
ListenKnoten* previous;
start end
next
previous
Doppelt verkettete Liste
• Übungsbeispiel:
– Adaptiere das Programm inklusive aller Funktionen
für eine doppelt verkettete Liste.
– Achte auch auf den Einsatz des Zeigers auf das Ende
der Liste (z.B. bei der Funktion "anhaengen").
Doppelt verkettete Liste
struct datum
{
int tag;
int monat;
int jahr;
};
Doppelt verkettete Liste
• Übungsbeispiel:
struct angestellter
{
char name[20];
char vorname[20];
datum geburt;
datum eingestellt;
float gehalt;
angestellter* next;
angestellter* previous;
};
Doppelt verkettete Liste
• Übungsbeispiel - Hinweise:
– Befehle für Stringmanipulation verwenden (strncpy,
strcmp).
– #include <string> nicht vergessen!
Tipps zur Arbeit mit Listen
• Darauf achten, dass Zeiger immer auf einen
gültigen Speicherbereich (Adresse) zeigen.
– Häufiges Missverständnis: Mit zeiger2=zeiger1 wird
kein Wert an zeiger2 übergeben, sondern nur die
Adresse auf die zeiger1 verweist.