Informatik – Zusammenfassung von Norman Juchler, D-MAVT, Informatik SS04. Verfasst anhand der Vorlesungsfolien von Prof. Dr. Markus Bläser Schlüsselwörter in C++ Allgemeines C++: Maschinennahe Sprache, ermöglicht imperative Programmierung. asm, auto, bool, break, case, catch, char, class, Bestehend aus Folge von “elementaren” Operationen, mit const, continue, default, delete, do, double, else, enum, extern, float, for, friend, goto, if, inline, Variablenkonzept Algortihmus: Rechenvorgang, der nach einem bestimmten [sich wiederholenden] Schema abläuft Aufbau eines C++-Programms: int, long, new, operator, private, protected, public, register, return, short, signed, sizeof, static, struct, switch, template, this, throw, try, typedef, union, unsigned, virtual, void, volatile, while Importliste Programmteil mathematische Operatoren Syntax + „Grammatik“. Beschreibt korrekten Aufbau in C++. Schlüsselwörter - (Einheiten) “elementaren Grundbausteine” von C++: Semantik Interpretation. Beschreibt Bedeutung und Inhalt eines (syntaktisch korrekten) C++-Programms. Beispiel Header: #include<iostream> using namespace std; Einbindung einer Headerdatei, Zugang zu (vordef.) Hilfsprogrammen, Compiler ersetzt Zeile durch Inhalt d. Datei iostream. Funktionsdeklaration: void main(void){ Ausführung des Programms: Beginn immer bei main. Semantik: 1. void: kein Wert wird zurückgegeben 2. void: main erhält keine Werte von aussen Variablendeklaration: double a, s, t; Syntax: Typname Variablennamen-Liste; Typname: vordef. Typen: int, float, double, Addition * Multiplikation % modulo Subtraktion / Division (nur int!) Für weitere mathematische Funktionen: #include math.h: fabs Absolutbetrag, sqrt Q.wurzel, exp, log, cos, acos Beispiel #include<iostream> using namespace std; void main(void){ double a, t, s; cout << "a? "; cin >> a; cout << "t? "; cin >> t; s = 0.5 * a * t * t; cout << "s = " << s << endl; } Beispiel Rechnen mit einfachen Datentypen int: Bemerkung: float: 3.0f, 3.0F char: char c, d; c = ’A’; d = ’+’ Einige Ersatzdarstellungen für char: Physikalisch wird Variable durch Folge von Speicherzellen realisiert. Erste Zelle ist die Adresse, der Inhalt der Wert der Variablen. Namensgebung: Nur aus Buchstaben, Ziffern und ‚_’. 1. Zeichen: Buchstabe oder ‚_’. bool: Variabeln müssen initialisiert/dekl. werden!!! Wertzuweisung Syntax: s = 0.5 * a * t * t; Variablenname = Ausdruck ; Semantik: Bemerkung Auswertung: v. links nach rechts, übliche Prioritäten Typ der Variable = Typ des Ausdrucks! Sonst: Konvertierung (cast), passiv oder aktiv. (x / y) * y + (x % y) = x double: 3.14, -1.5, .3, -1.1E-5, 1.1e6, .3 steht für 0.3 char, abgeleitete Typen (structs) oder Pointer. Semantik: es gilt allgemein: log. Verknüpfungen: Und (&&), Oder (||), Negation (!). Einfache Datentypen int: [-2147483648,2147483647] N : (232 Werte) es gilt: (x / y) * y + (x % y) = x float, double: modelliert reelle Zahlen, Gleitkommadarstell. Speicher: float 4 Byte, double 8 Byte char: speichert einzl. Buchstaben, Ziffern, Zeichen bool: Wahrheitswerte true, false. Verknüpfungen Vergleichsoperatoren Vergleiche von int, double, etc. Typ des Rückgabewerts: bool Rechenregeln für boolesche Operatoren Inkrement, Dekrement Vereinfachung: Statt i = i + 1; kann man i++ schreiben. Auch in Ausdrücken verwenden: y = x * (i++); Semantik: Explizite Typumwandlung (cast) Syntax: Typname ( Ausdruck ) oder (Typname ) Ausdruck Nicht expl. Umwandlung möglich: 8ung! Rundung! Beispiele: x wird mit i multipliziert und y zugewiesen. Danach wird i um 1 erhöht. Umgekehrt: y = x * (++i); Analog: i--, --i while-Schleife: Syntax: while ( Bedingung ) Block Semantik: Durchlauf des Blocks, wenn Bedingung true Nach jedem Durchlauf des Rumpfs erneuter Test der Bedingung, Kontrollstrukturen do-Schleife: if-Anweisung Syntax: do Block1 while ( Bedingung ) Syntax: if ( Bedingung ) B1 else B2 Semantik: Block B1 wird ausgeführt, wenn Bedingung true Semantik: …ist semantisch identisch mit: ist, sonst B2. Block1 ; while ( Bedingung ) Block1 Block1 wird vorher ausgeführt! Block : Anweisung ; oder { Anweisungsfolge ; } Bemerkungen: Bedingung muss Typ bool haben. break, continue else bezieht sich auf das letztgenannte if, zu dem break verlässt die innere, momentane Schleife (for, while, donoch kein else gehört. Schachtelung möglich. while) unmittelbar Fallunterscheidung: Syntax: switch ( Ausdruck ) { continue überspringt den Rest des Rumpfs der Schleife und geht zur nächsten Auswertung der Bedingung case Konst1 : Anw.folge1 ; break; ... Arrays (Felder) case Konstn : Anw.folgen ; break; Syntax: Typname Variablenname [ Feldgröße ]; Semantik: Feldgrösse konstant w (muss). w viele Variablen default: Anweisungsfolgen+1 ; } vom Typ Typname werden angelegt. Semantik: Ausdruck muss eine ganze Zahl ergeben. Konst1,…, Konstn paarweise verschieden! Zugriff: Konst1,…, Konstn ganzzahlig default kann fehlen, muss nicht am Ende stehen. Vereinfachung: break beendet switch-Anweisung. Fehlt break nach Anw.folgei , so wird auch Schleifen: for-Schleife: for ( Initialisierung ; Bedingung ; Anhang ) „Dynamik“: Initialisierung wird einmal vor erstem Durchlauf der Schleife ausgeführt. Dynamische Deklaration von Arrays: a = new int[n]; Block Semantik: Statt: int a[10]; besser: const int max_groesse = 10; int a[max_groesse]; max_groesse wird als symb. Konstante dekl. Vorteil: Änderungen einfacher und effizienter. Symb. Konstanten angewendet wie Variablen, Initialisierung zwingend, nie links bei einer Zuweisung! Anw.folgei+1 ,…, ausgeführt, bis zum nexten break Syntax: Variablenname [0],…, Variablenname [w -1] Achtung: Arraygrenzen werden nicht kontroll. Arrays werden mit Pointer realisiert! Bemerkungen: Mehrdimensionale Arrays möglich: double matrix[100][100]; double tensor[30][20][50]; Bedingung wird vor jedem Durchlauf ausgewertet. Wird Schleife nicht verlassen Block ausgeführt. for-Schleifen gut geeignet, um eine voher bekannte int a[5] = {1, 2, 3, 4, 5}; int a[5] = {1, 2, 3}; int a[] = {1, 2, 3, 4, 5}; Anzahl von Durchläufen durch eine Schleife zu (Größe des Arrays wird auf 5 festgelegt.) realisieren. Ist Bedingung leer Endlosschleife int a[3][2] = {{1, 2}, {3, 4}, {5, 6}}; Am Ende jedes Durchlaufs Anhang ausgeführt. Bemerkungen: Initialisierung: Dynam. Arrays können nicht initialisiert werden! Structs (Rekord) Daten haben heterogenen Charakter, gehören aber zusammen. Synatx: Semantik: Beispiel: Bemerkungen: struct Name { Komponentenliste }; Geltungsbereiche: Block, Programmdatei, Funktion, Klasse. Block Ein Block wird durch geschweifte Klammern { ... } begrenzt. Blöcke können geschachtelt sein. Ist ein Block A innerhalb eines Blocks B, so ist A innerer Block und B äußerer Block. Neuer Typ struct Name wird deklariert. Zugriff auf Komponenten mit Punktoperator Globale und lokale Namen struct point { int x, y; double intensity; }; struct point p; i = p.x; p.intensity = 0.65; p.x = p.y; Geltungsbereich eines Namens beginnt im Deklarationspunkt und endet am Blockende. Variablen direkt mit struct deklarieren: struct point { ...} p, q; oder point p; Aber: Innerhalb Rekorddekl. keine Initialisierung! Keine rekursive Typdekl.! Aber: mit Pointer können Rekordtypen aufeinander Bezug nehmen. Structs sind schachtelbar. global: wichtig: Name kommt in keinem Block, usw. vor. Name nie mehrfach deklarieren in einem Block, ausser in einem inneren Block. Lokale (innere) Namen haben Vorrang Verdeckung Bezugsoperator: unärer Bezugsoperator :: Bezug auf gleichnamige globale Variable. Sichtbarkeitsbereich = Geltungsbereich ohne inneren Teile, in denen der Name überdeckt wird. Lokale Variablen existieren nur in dem Block, in dem sie deklariert sind. Bei Blockende gehen lokale Werte verloren. Beispiele: Funktionen (Unterprogramme) Initialisierung: struct point p = {12, 27, 0.75}; struct point q = {7, 8}; Zuweisung: p = q; ist gleichbedeutend mit p.x = q.x; p.y = q.y; p.intensity = q.intensity; Übersichtlichkeit steigern durch Funktionen. Erste Strukturierung. Aufspaltung des Programms in Unterprogramme. Lokalitätsprinz.: (Achtung, bei Arrays ist das nicht so!) Eine Funktion bildet eine funktionelle Einheit. Interne Berechnungen ohne (unspezifizierten) Konsequenzen nach aussen. Funktionsaufruf: v = f(x, y) Anw.folge unterbrochen Parameterübergabe interne Berechnung Union Rückgabe des Funktionswerts Zuweisung an v Heterogene Daten speichern. Hier aber nur jeweils ein Datum. Unions selten verwendet. Vorteil: Speicherplatzersparnis. Nur Param.überg.: anwendbar, wenn man weiss, dass jeweils nur 1 Komponente benötigt wird. Fortsetzung der Anweisungsfolge. Beispiel: union Zahl { int ganz; double reell; }; union Zahl z; z.ganz = 3; speichert 3 als int ab. z.reell = 3.14; überschreibt die 3 formale Parameter aktuelle Parameter. S.u. Argumente der Fkt. bei Fkt.aufruf. Jeder formale Parameter muss einem aktuellen Parameter gleichen Typs entsprechen! Zuordnung gemäß Reihenfolge im Fkt.kopf. Funktionsdeklaration Syntax: Typ Name ( Parameterliste ) Block Semantik: Typ Name ( Parameterliste ) ist Funktionskopf Enum Parameterliste vereinbart formalen Parameter. Syntax: enum Name { Identifierliste }; Beipspiel: enum Farbe { rot, gruen, blau }; enum Farbe f; Techn. Realis.: rot wird durch 0, gruen durch 1, blau durch 2 Block spezifiziert Berechnung der Funktion Typ legt Typ des Funktionswerts fest Parameterliste: repräsentiert. Enums int Sichtbarkeitsbereiche Namen von Variablen haben Sichtbarkeitsbereich (= Geltungsbereich), Typen: damit Namensgebung übersichtlich bleibt. formale Param.: Sichtbarkeitsbereich einer Namensdeklaration: Programmteil, in dem der Name verwendet werden kann und sich auch auf die Deklaration bezieht. Funktionswert mittels return zurückgegeben. (return kann mehrmals vorkommen) Legt fest: Anzahl der Argumente, deren Typ, deren Namen im Funktionsrumpf, Reihenfolge der aktuellen Parameter. Eine Parameterliste kann leer sein. _ einfacher Typ, Structs, Pointer, KEIN einf. Array verhalten sich im Rumpf wie lokale Variablen. Sichtbarkeitsbereich: Funktionsrumpf Geltungsbereich: Kopf und Rumpf Unterscheidung: Werteparameter (call by value) Variablenparameter (call by reference) Beispiele: Zuweisungen (SS 107f) Pointer versus Array, Strings Signatur: Weglassen der Parameternamen def. Prototyp. int f(int, double); Sichtbarkeit von f ab ‚;’ Prozedur: Fkt. kann auch keinen Wert zurückliefern: Typ Arrays werden in C++ durch Pointer realisiert! int a[10]; deklariert a als Pointer auf int und reserviert dann 10 Speicherplätze. So gesehen können Arrays als Argumente von Fkt. auftreten. (Call by Reference mit Pointern). void . return; (ohne Argument) nicht zwingend Zuweisung: Referenzen Variablen können mehrere Namen haben (alias) Bedingungen: Referenztypen müssen initialisiert werden. Nach Initialisierung keine Änderung möglich. Beispiel: Ist a Pointer und b Array, so bewirkt a = b;, dass a nun auf gleichen Platz zeigt wie b. Keine echte Kopie Effizienz! Auch Strings werden in C als Pointer char* realisiert. Dynamische Variabeln int n; int& nr = n; n und nr können nun new-Operator: als Synonyme verwendet werden. zus’en mit Pointer Variablen dyn. erschaffen: new Typ ; erzeugt neue Variable vom Typ Typ und liefert Adresse zurück. Zuweisung zu einem Variablenparameter (Call by Reference) Bisher: void f(int y); { ... } Durch Fkt.aufruf f(x) wird x nicht verändert. Jetzt: Pointer, z.B.: int* p = new int; Bemerkungen: Programmstruktur ab ( Speicherplatz wird nicht automatisch freigegeben!!). Dynamische Variable nicht explizit benannt, nur über Adresse abrufbar! void f(int& y) { ... } Durch Fkt.aufruf f(x) wird x verändert, da nicht Wert, sondern Adresse von x übergeben! Vorteile: Umgekehrt: Datenrückgabe Veränderung beabsichtigt. Effizienter gegenüber „Call by value“ (= Wert wird kopiert, aufwändig bei grossen Structs). Keine Veränderung: int f (const int& y); Geltungsbereich und Lebensdauer hängen nicht von delete: werden Variablen nicht mehr benötigt: Freigabe mittels: delete p;. Beispiel: dynamische Arrays: int* p = new int[10]; Speicherverwaltung Pointer Im Wesentlichen drei Speicherbereiche: Pointer (Zeiger) ist ein Datentyp, der eine Adresse im Hauptspeicher speichern kann (Verweis). (Variable = Folge von Speicherzellen, Adresse = Nummer der ersten Speicherzelle) Statische Obj.: Beispiel: int* p; (alternativ int *p;) p hat Typ Pointer auf int, int Basistyp von p Operatoren: Beispiel: vorteilhaft…: Beispiel: Dereferenzierungsop. *: Zugriff auf Inhalt der Speicherzelle, auf die Pointer verweist. Manipulation einer Variablen über ihren Namen oder über *p (Pointer, der auf Variable zeigt). Referenzierungsop. & Ermittlung der Adresse einer Variablen. int* p; ... int i = *p; ... int i; ... int* p = &i; ... Lokale Blockvariablen: auf dem Stack gespeichert. Solche Variablen sind im Programmtext deklariert. Der Stack ist wichtig zur Abarbeitung von Fkt. Bei Verlassen des Blocks: Speicher freigegeben. Bei Laufzeit angelegte Variablen im Heap gespeichert. Reservierung mit new, Lebensdauer bis zur expliziten Freigabe durch delete. Arbeiten mit Arrays Gegeben: bei Konstruktion kompl. Datenstrukturen, beim Unterscheide: Anlegen dyn. Variablen, für Call by Reference Statt Call by Reference mittels int f(int& x) { ... }auch möglich: Arithmetik: struct element { int key; ... }; Statisches Array: struct element list[max_length]; Dynamisches Array: struct element* list = new struct element[length]; int f(int* x) { ... } Unterschied: Zugriff auf Wert von x im 1. Fall: x, 2. Fall: *x z.B. globale Variablen, haben festen Speicherbereich für den ganzen Ablauf des Programms Lineare Suche: auf Pointern möglich, z.B: p = p + 2; Pointer wird um 2 mal Anzahl Speicherzellen Einfügen: weitergeschoben, die zum Speichern des Basistyps von p benötigt werden. Weitere Beispiel: p++; q = p[6]; entspricht: q = *(p + 6); Elemente der Reihe nach durchgehen. Überprüfen, ob list[i].key == k. Falls Daten sortiert: Binäre Suche Neues Element an Stelle i. Letzter Platz voll mit new neues Array anlegen, umkopieren. Löschen: Alle Einträge an Stellen i+1,..., length-1 um eine Position nach links schieben. Aufwand: Worst Case bei Arrays. S.u. Vergleich Arbeiten mit linearen Listen Definition: Lineare Listen besten aus: Listenkopf, Elementen, Verkettung der Elemente, Listenende. Gegeben: struct element { struct element* next; int key; ... }; struct element* list; Bilanz: Worst Case Aufwand: (Aufwand = Kopieren) Bemerkung: „length“ bei V. Listen muss nicht bekannt sein! Binäre Suchbäume Definition: Bemerkung: Listenkopf mit globaler Variable element* head; festlegen! Listenende: p->next == 0 Bemerkung: Zugriff auf nächstes Element: (*list).next (Klammern notwendig!) oder: list->next Begriffe: Listenende: angezeigt durch next == 0. Lineare Suche: Einfügen: Elemente heissen Knoten (node). Sind Komponenten left und right beide Nullpointer Bemerkungen: Geg.: Pointer p auf Element in Liste. Neues Element mit Key k hinter dem Element, auf das p zeigt: Suche: ( kein Nachfolger), so heisst der Knoten Blatt (leaf ). “Startknoten” heisst Wurzel (root). Globale Variable Zeiger auf root: element* root; Suche Knoten mit Key k. struct element *p = root; while(p != 0) { if (p->key == k) return p; if (p->key < k) p = p->left; else p = p->right; } return 0; Achtung: Einfügen: Wie löscht man das erste Element? Lösung: Sonderbehandlung (eigener Code) Erstes Element wird nie benutzt (Dummy) Doppelt verkettete Listen: struct element { struct element *prev, *next; int key; ... }; Füge Knoten mit Key k ein. struct element** p = root; while(*p != 0) { if ((*p)->key<k) p = &((*p)->left); else p = &((*p)->right); } *p = new struct element ... Geg.: Pointer p, der auf Element in Liste zeigt. Lösche das Element, auf das p->next zeigt: void DeleteAfter( element* p) { if (p->next == 0) return; element* q = p->next; p->next = p->next->next; delete q; } Höhe eines Baumes: maximaler Abstand (= Anzahl Knoten) eines Blattes zur Wurzel. Elemente zufällig: height log(size) void InsertAfter( int k, element* p ) { element* q = new element; q->key = k; q->next = p->next; p->next = q; } e.next = p->next; Löschen: Jedes Element hat zwei mögliche Nachfolger: struct element { struct element *left, *right; int key; ... }; Suche nach Element mit Key k: p = list; while (p != 0) { if (p->key == k) return p; p = p->next; } return 0; p->next = &e; ( Grad γ+ = 2) p als Pointer auf Pointer, um am Schluss new zu ermöglichen. Lösche Knoten, auf den p->r. bzw. p->l. zeigt. Mindestens ein Teilbaum ist leer: Löschen: 1. Fall: struct element* &q = p->right; // bzw. p->left if (q->right == 0 || q->left == 0) { struct element* h = q; if (q->right == 0) q = q->left; else q = q->right; delete h; } Bemerkungen: q ermöglicht gleiches Behandeln der Fälle p- >right bzw. p->left (hier p->right). 2. Fall: Beide Teilbäume sind nicht leer. Im rechten Teilbaum Element mit kleinstem Schlüssel suchen. (links: grössten Schlüssel.) Kopiere dessen Inhalt nach *q. Lösche dieses Element wie in 1. Fall. (Bemerkung: kann kein linkes Kind haben) Bilanz: Worst Case Aufwand: Suche, Einfügen, Löschen in Bisher: height Schritten. Typisch: height log(size) Rekursion Jetzt: Funktionen dürfen andere Funktionen aufrufen, auch sich selbst! wichtig: explizite Abbruchbedingung struct complex { double real, imag; }; struct complex add(struct complex struct complex b); ... a, “Integration” der Fkt.en wie add in die Klasse nur auf Klassenelemente anwendbar Kein direkter Zugriff auf real, imag Rekursion versus Iteration Rekursion und Iteration funktional äquivalent: Rekursion Schleife. Iterativer Ansatz oft schneller, rekursiver Ansatz oft klarer. Beispiel: iterativ: Beispiel: Deklaration: complex c; Zugriff auf c.real und c.imag ist verboten (private). Stattdessen: Zugriff per c.re() und c.im() (public). Extern: Definition Methoden auch ausserhalb Klassendefinition möglich (mittels scope-Operator ::): effiziente Ausgabe eines binären Suchbaums. Ausgabe zwischen den Aufrufen: inorder Durchlauf. (preorder = vor, postorder = danach) void inorder(struct element* p) { if (p->left != 0) inorder(p->left); cout < < p->key; if (p->right!=0) inorder(p->right); } void complex::addto(complex d) { real += d.real; imag += d.imag; } Aufruf: c.addto(d); Zugriffskontrolle 3 Zugriffsarten: Speicherorganisation Aktive Fkt. belegen Speicherplatz im Stack. Bei rekursivem Aufruf: aufrufende Funktion noch im Stack; aufgerufende Funktion kommt Syntax: dazu. Inkarnationen: Gleichzeitig im Stack vorhandene Funktionsblöcke derselben Funktion. Rekursionstiefe = Anzahl Inkarnationen. Lösung: globales Gedächnis anlegen: Array, der Zwischenergebnisse speichert (wie iterativer Fall). Klassen Eine Klasse ist eine (abstrakte) Datenstruktur: verwaltet Daten, bietet Funktionen (Methoden) an, um die Daten zu manipulieren. Verwaltung der Daten nur über angebotene Fkt. Fkt. sind nur auf Beispiel: Elemente (Objekte) der Klassen anwendbar. Erweiterung des structs um Funktionen. Klasse = Menge aller Objekte eines spez. Typs Instanz = Variable des Typs. Besteht aus: Datenelementen, Elementfkt.en (Methoden) Syntax: Klassenart Klassenname { Elementliste }; von Variablen- Funktionsdeklarationen. Beispiel: Speichern kompl. Zahlen mit Klasse complex. die Zugriffsart der Methoden und Variablen bis zum nächsten Zugriffsart : fest. Default bei class: private gleicher Name wie Klasse, kein Rückgabewert, wird in der Klassendekl. dekl., kann fehlen. Der Konstruktur ohne Parameter heißt Standardkonstruktor. class complex { ... complex() {real = 0; imag = 0}; complex(double a, double b) {real = a; imag = b}; ...} c; deklariert c und ruft Standardkonstruktor auf. (In c steht dann die Null.) complex c(1,0); deklariert c und ruft zweiten Konstruktor auf. (In c steht dann die Eins.) Klassenart: class, struct, union. Folge Zugriffsart : Legt innerhalb der Klassendeklaration complex (Meistens class. struct, union sind legacy.) Elementliste: private (kein Zugriff von aussen möglich) public (Zugriff möglich) protected Konstruktoren Konstruktor: Begriff: können Bemerkungen: rekursiv: Rand: 0! = 1, Allgemein: n! = n(n-1)! int fak_rek(int n) { if (n == 0) return 1; return n * fak_rek(n-1); } Daten class complex { double real, imag; public: double re() { return real }; double im() { return imag }; void addto(complex); }; Fakultätsfunktion n! int fak_it(int n) { int erg = 1; for (int i = 1; i <= n; i++) erg = erg * i; return erg; } Konsistenzbedingungen an eingehalten werden. und Destruktoren Ist Ende des Gültigkeitsbereichs einer Instanz erreicht oder wird sie mit dem delete-Operator gelöscht, so wird der Destruktor der Klasse aufgerufen. Deklaration: classname(); Bemerkungen: kann fehlen. Wichtig für dynamische Datenstrukturen: In einer Suchbaum-Klasse können alle Elemente der Klasse gelöscht werden. Beispielprogramm: Matrixmultiplikation Bsp.: Mischen zweier sortierter Listen Gegeben sind zwei sortierte (geordnete) Listen a und b ( Listenköpfe). Die Elemente sind vom Typ struct element. Die neue List soll so vereinigt werden, dass sie wieder sortiert ist. #include <iostream>; int main() { const int N = 10; int a[N][N], b[N][N], c[N][N]; ... // a, b einlesen for( int i = 0; i < N; i++ ) { for( int j = 0; j < N; j++ ) { c[i][j]= 0.0; for ( int k = 0; k <10; k++) c[i][j] = c[i][j] + a[i][k] * b[k][j]; } } } element* Mischen ( element *a, element *b) { element *p, *q; // festlegen des Listenkopfs if ( a->key <= b->key ) { p = a; a = a->next; } else { p = b; b = b->next; } q = p; //Kopf der Ergebnisliste in q // berechne der neuen Liste durch // Umstrukturierung der beiden alten while ((a != 0) && (b != 0)) { if ( a->key <= b->key ) { p->next = a; a = a->next; } else { p->next = b; b = b->next; } p = p->next; } Bsp.: Rekursion – grösster gemeinsamer Teiler int ggt( int n, int m ) { if (m == 0) return n; return ggt (m, n%m); } // falls eine Liste zu Ende ist // entweder a == 0 oder b == 0 if ( a != 0) p->next = a; else p->next = b; Bsp.: Rekursion – Fibonacci-Zahlen int fib( int n ) { if (n == 0) return 0; if (n <= 2) return 1; return fib(n-1) + fib(n-2); } Bsp.: Rekursion – Quicksort void Quicksort( double S[], int l, int r ) { if ( l >= r) return; int m = Umordne ( S, l, r); Quicksort ( S, l, m-1); Quicksort ( S, m+1, r); } //Rückgabe des Zeigers auf Listenkopf return q; }