Sie sind auf Seite 1von 33

Wieviele Gleitpunktdatentypen gibt es in C und wie

unterscheiden sich diese?


Float, double, long double.
3.7 (aus dem englischen) => Punktzahl
m • be Ist der Exponent fix vorgegeben, so spricht man vom
Festpunkt-
m…Mantisse Zahlensystem. Beim Gleitpunkt-Zahlensystem ist der
Exponent
b…Basis nicht vorgegeben. Punktzahlen in C sind im Gleitpunk-
E…Exponent Zahlensystem mit der Basis 2 implementiert.
Norm für binäre Gleitpunkt-Arithmetik:
vorzeich mantiss
Typ en exp. e
float 1 Bit 8 Bit 23 Bit
double 1 Bit 11 Bit 52 Bit
Spezielle Bitkombinationen für -∞,+∞ und NaN (Not a number)
die normalisierte Mantisse mN = .1011101 abgespeichert (originale
Mantisse wird solange nach links bzw. rechts geschoben [bzw. mit 2
multipl. od. divid.], bis eine 1 hinter dem kommapkt. steht)
=> demnach steht auf jeden Fall eine 1 hinter kompakt.! => das erste Bit
der Mantisse kann eingespart werden
Float ist auf 7 Stellen genau, 10^7 Nullen log10-> 7 Stellen

Wie arbeitet Bubblesort? Wie oft muss man dabei die zu


sortierenden Daten durchlaufen?
Der Algorithmus vergleicht der Reihe nach zwei benachbarte Elemente
und vertauscht sie, falls sie in der falschen Reihenfolge vorliegen. Dieser
Vorgang wird solange wiederholt, bis keine Vertauschungen mehr nötig
sind. Hierzu sind in der Regel mehrere Durchläufe erforderlich. Je
nachdem, ob auf- oder absteigend sortiert wird, steigen die kleineren oder
größeren Elemente wie Blasen im Wasser (daher der Name) immer weiter
nach oben, d.h. an das Ende der Reihe.

Was sind mehrdimensionale Felder und wie werden diese im


Speicher repräsentiert?
Sind Felder mit 2 oder mehr Indizes, mehr als 2 schlecht da
unübersichtlich, name[ ][ ] bei Funktionsaufruf muss 2.Länge angegeben
werden. Werden im Speicher hintereinander abgelegt.

Wie funktioniert die Parameterübergabe in C? Welche Arten der


Parameterübergabe gibt es? Wie werden Felder üblicherweise
an Funktionen übergeben? Wie kann man ein Feld als Kopie
elegant an eine Funktion übergeben?
Parameter können als Kopien ( call bei Value) oder im Originalen (call by
reference) übergeben werden. Bei call by value werden Kopien erzeugt
und in der Funktion bearbeitet die Originalvariablen werden nicht
verändert. Bei call by reference wird die Adresse übergeben und in der
Funktion arbeitet mit den Originaldaten. Felder werden in call by reference
übergeben da sonst das gesamte Feld kopiert werden müßte. Elegant kann
die Übergabe in einer Struktur erfolgen.

Was ist der Unterschied der Definition und der Deklaration einer
Funktion? Kann es eine Definition einer Funktion ohne eine
Deklaration geben?
Bei der Definition wird der Speicherplatz erzeugt und reserviert. Bei der
Deklaration wird nur der Typ und der Name dem Compiler bekannt
gemacht, aber nichts angelegt.

bei Variablen, Funktionen und abgeleiteten Datentypen:


DEFINITION = DEKLARATION + Reservierung des Speicherplatzes
Variablen:
Definition: der Datentyp, Name und Speicherklasse festgelegt
(Unterschied zu Deklaration: es wird Speicher für die Variable reserviert)
Deklaration: es können nur globale externe Variablen deklariert werden,
Initialisierung: Setzen eines Wertes, gleich bei der Definition.
Funktion:
Definition: Angabe des Fkt.namens, des Rückgabetyps, der Parameter und
des kompletten Fkt.rumpfs
Deklaration: Bekanntmachung an den Compiler, dass eine Funktion mit
bestimmten Rückgabewert und Parametern von bestimmten Datentypen
existiert.
abgeleiteten Datentypen:

Definition: mit Schlüsselwort (struct, union, enum), Name für den Datentyp,
Rumpf in geschwungenen Klammern (Beschreibung der Elemente)

Deklaration: zB nur struct Punkt_s;


wird verwendet wenn Zeiger auf Objekte des Datentyps verwendet werden, die
'Innereien' jedoch nicht interessant sind. es muss der Datentyp aber trotzdem an
irgendeiner Stelle im Projekt definiert sein!

Was sind rekursive Funktionen? Was passiert wenn die


Abbruchbedingung erfüllt ist?
Funktionen, die sich selbst anrufen, heißen „rekursiven Funktionen“.
Kennzeichen für solch einen Algorithmus ist, dass die Gesamtlösung durch
gleichartige Teilprobleme erreicht wird. Diese Teilprobleme werden rekursiv
gelöst! Jede Rekursion trägt zur Gesamtlösung bei. Wichtig ist dabei die
Angabe einer Bedingung(bei bestimmter Konfiguration der
Funktionsparameter), bei der die Rekursion abgebrochen wird, da der
Algorithmus sonst nicht mehr terminieren würde -> Endlosrekursion! (wäre
damit auch kein Algorithmus mehr). Wenn die Abbruchbedingung erfüllt ist
wird der Rekursionsbaum rückwärts durchlaufen und die einzelnen Teile
ausgewertet. Die Daten werden nacheinander vom Stack geholt und an
die aufrufende Funktion über geben.

Was ist eine einfach verkettete Liste und wie ist diese
aufgebaut?
In der Prog. versteht man unter Listen eine sequenzielle
Aneinanderreihung von Elementen, wobei die Reihenfolge explizit
festgehalten wird. Sie speichern zwei Arten von Informationen: Die
Elemente und eine Reihenfolge. Liste gestatten in einfachster Weise das
Verändern der Reihenfolge von Elementen sowie das Einfügen und
Entfernen von Elementen.Das Suche von Elementen ist jedoch schwierig
Die grundlegendste Art der Listen ist die einfach verkettete Liste (single
linked list; SLL). Eine einfach Verkettete Liste besitzt 2 Elemente 1. Den
Eintrag 2.Der Verweis auf das nächste Element,

Was wissen Sie über die Eigenschaften und Terminologie von


Listen?
Jedes Element einer Liste ist ein Teil eines Knotens(node),der auch die
Verkettung zum
nächsten Knoten enthält. (das Element mit einer Verkettung ergibt einen
Knoten); Jeder
Knoten enthält damit zwei Informationen, damit muss das Ende der Liste
gekennzeichnet werden, dieser Knoten hat keinen Nachfolger. Die
Reihenfolge der Verkettung ist dabei durch die Knoten gegeben. Soll diese
verändert werden, muss nur die Verkettung geändert werden.

Wie kann eine Liste implementiert werden?

Ein Knoten kann mit einer Struktur beschrieben werden. Die Verkettung
wird durch einen Index realisiert (entspricht Feldindex des verwendeten
Feldes). Der Kopf der Liste ist ebenfalls ein Index, und zwar der erste
Knoten der Liste. Das Ende ist mit einem ungültigen Index (-1) vereinbart.
Zweite Methode für das Markieren des Endes wäre ein Knoten, der auf sich
selbst zeigt. Erstere Methode hat den Vorteil, dass kein eigener Knoten für
das Listenende benötigt wird, aber den Nachteil, dass -1 ja ungültig ist.
Der Nachteil der Implementierung mit Feldern ist, dass ein eigenes
Speichermanagement für die Verwaltung der Knoten im Feld verwendet
werden muss, wegen eventueller Löcher.

Wie kann ein neues Element in eine Liste eingefügt werden?

Um ein neues Element, also einen neuen Knoten einzufügen, ist die
Verkettung an der
Einfügestelle aufzubrechen und auf den neuen Knoten zu verbiegen.
Weiters muss die
Verknüpfung des eingefügten Objekts auf den Rest der Liste gesetzt
werden. Vorteil
gegenüber Feldern ist die Einfachheit und Geschw. des Vorgangs. Es ist
egal wo der Knoten im Speicher liegt, die Reihenfolge ergibt sich aus der
Verkettung und nicht aus der Pos. im Speicher. Beim Einfügen des neuen
Elements muss der Vorgänger auf den neuen Knoten zeigen und dieser
muss auf den richtigen Nachfolger zeigen. Da der Vorgänger aber nicht
bekannt ist, wird nach einem bestehenden Knoten eingefügt. Dazu muss
die Position des einzufügenden Elements definiert werden. Dies kann
durch einen Index erfolgen, oder mit der Angabe des Elements im
Vorgänger bzw. Nachfolger geschehen. Es ist darauf zu achten, dass ein
gültiger Index übergeben wird, der auch wirklich frei ist. Komfortabler wäre
es den einzufügenden Datensatz als Parameter zu übergeben und die Fkt
muss sich selbst um die Verwaltung des Feldes kümmern(freier
Speicherplatz, Überprüfen der Feldlänge, verwalten von gelöschten
Einträgen);
Wie kann ein Element einer Liste entfernt werden?

Dazu wird die Verknüpfung im Vorgängerknoten auf den Nachfolger des zu


entfernenden Elements gesetzt. Damit befindet sich das Element nicht
mehr in der Liste, da es keinen Verweis mehr auf diesen Eintrag gibt ⇒ es
wurde aus der Verkettung genommen. Um das Objekt tatsächlich zu
löschen, muss es nach dem Entfernen zerstört werden, bzw. als ungültig
markiert werden. Diese Methode ist schneller, als das Löschen eines
Elements eines Feldes, da dort die entstandene Lücke durch das
Nachschieben der folgenden Einträge im Feld geschlossen werden muss.
Langwieriger ist das Suchen des zu löschenden Elements, da die Liste von
Anfang an durchsucht werden muss. Bei mehreren gleichen Elementen
wird das Erste entfernt.
Ablauf: (Entfernen des nächsten Knotens): Element suchen, den
Nachfolger im Knoten auf den Nachfolger des Nachfolgers setzten. Den
Eintrag, der dann zerstört werden soll, muss als ungültig markiert werden
(-2), dann break; while Schleife wird bis zum Listenende durchgelaufen.

Was ist ein Zirkularpuffer und wie wird er implementiert?


Ein Zirkularpuffer ist eine Implementierung der Schlange und arbeitet nach
dem Prinzip
FIFO. Sie werden in Feldern implementiert. Neue Elemente werden von
hinten eingefügt und von vorne wieder entnommen(od. umgekehrt⇒Put
und Get müssen auf jeden Fall am jeweiligen entgegengesetzten Ende
stattfinden). Die Einfüge- u.Entnahmepositionen sind durch die Feldindizes
gegeben. Nach dem Einfügen bzw. Entfernen eines Elements müssen die
zugehörigen Indizes erhöht werden. Damit verschieben sich die Positionen
im Feld. Wird die Feldlänge überschritten, wird der jeweilige Index auf den
Feldanfang gesetzt, daher der Name Zirkularpuffer.

Was ist ein Heap und für welche Operationen ist er gut
geeignet?
Eine Heap-Struktur (Heap=Haufen) genannt, ist eine sogenannte
Prioritätswarteschlange. Dies ist eine Datenstruktur, die es ermöglicht, den
größten Eintrag zu finden, zu löschen und neue Elemente einzufügen.
Aufgrund der besonderen Struktur von Heaps können eine Reihe von sehr
effizienten Algorithmen geschaffen werden, wie zB das Sortieren des
Heaps. Heaps sind binäre Bäume die in einem Feld realisiert werden,
wobei jeder Knoten die Heapbedingung erfüllt. Heps werden voar allem
zum Sortiern von Daten eingesetzt. Der Schlüssel jedes Knotens eines
Heaps ist größer als der seiner Nachfolger. Können gleiche Schlüssel
auftreten, so muss der Schlüsssel jedes Knotens größer oder gleich dem
seiner Nachfolger sein. Ein Heap ist ein vollständiger binärer Baum.

Was ist ein Hash und für welche Operationen ist er gut
geeignet? Wie groß sollte die Hash-Länge gewählt werden? Was
wird aus dem Hash, wenn man bei getrennter Verknüpfung
Hashlänge 1 wählt?
Hashes(hashing⇒zerhacken) eignen sich besonders für das Suchen von
Datensätzen und stellen einen guten Kompromiss zw. Zeit- u. Platzbedarf
dar. Das Verfahren beruht im Prinzip auf einem Feld, in dem die
Datensätze geeignet gespeichert werden und einen Index generieren, mit
dem Datensätze gesucht werden können. Dieses Zerhack-Methode läuft so
ab: N...Anzahl derDatensätze; Jedem Schlüssel wird ein geeigneter Index
zugeordnet, dieser Index liegt zw. 0 und N-1; damit reduziert sich die
Suche nach einen Datensatz auf den Zugriff auf das Element mit dem
Index i in einem Feld der Länge N. Diesen Index ermittelt die Hash-Fkt. Da
die Hashfkt aber nicht perfekt ist, (des Klumpat), kann es passieren, dass
die Indizes verschiedener Schlüssel gleich sind, wodurch eine
Kollisionsbeseitigung berücksichtigt werden muss. Datensätze mit
gleichem Index können in einfach verketteten Listen zusammengefasst
werden.

Welche Methoden kennen Sie um eine Kollision von Elementen


mit dem
selbem Schlüssel zu verhindern?

Da verschiedene Schlüssel denselben Index besitzen können, muss diese


Kollision
berücksichtigt werden(beim Einfügen, Suchen u. Entfernen). Dazu eignet
sich die Methode der getrennten Verkettung, wobei in den Feldelementen
die Köpfe einer verketten Liste gespeichert werden. Das Einfügen eines
neuen Datensatzes erfolgt wie bei Listen. Bei selben Indizes wird das neue
Element an die Liste angehängt. Beim Suchen/Entfernen wird aus dem
Schlüssel des gesuchten Datensatzes der Index berechnet und das
Element in der Liste sequentiell gesucht (wie bei Listen). Beim Suchen
werden nur wenige Zugriffe benötigt: Ermittlung der Anzahl der Zugriffe:
erfolglose Suche: Summer der Listeneinträge(Anzahl der vorhandenen
Datensätze) dividiert durch die Feldlänge. Daher ist es Sinnvoll die
Feldlänge groß zu machen.
Erfolgreiche Suche: Summe aller Positionen der Datensätze in den Listen,
dividiert durch die Anzahl der vorhanden Datensätze. Die Listen sollten
daher möglichst kurz sein, was von HashFkt, Feldlänge, Anzahl der
Datensätze und Verteilung der Schlüssle abhängt.
Von welchen Faktoren hängt es hab, das ein Hash Dantesätze
rasch finden
kann?

• Kurze Feldlänge führt zu vielen Kollisionen; Feldlänge sollte 3fache der


Datensätze
betragen. Der Hash soll sich nur max zu 90% füllen.
• Die Hashfkt bestimmt die Verteilung der Indizes für gegebene Schlüssel,
aber auch die
benötigte Zeit bei einem Zugriff. Sie sollte einfach gehalten werden und
die
Verteilung möglichst gleichmäßig erfolgen.
• Wird immer derselbe Schlüssel verwendet, also werden immer idente
Daten
eingeängt, schrumpft der Geschwindigkeitsvorteil des Hashes auf das
Niveau von
Listen.
• Hashes speichern weiters keine Reihenfolge.
• Wird die Feldlänge groß genug gewählt, werden Hashes binären Bäumen
vorgezogen,
da sie einfach sind und kurze Suchzeiten gewährleisten.
• Bäume haben den Vorteil gegenüber Listen, dass die Datenmenge im
Voraus nicht
abgeschätzt werden muss und eine Reihe von Methoden existieren. Ist die
Datenmenge aber im Voraus bekannt, sollten Hashes bevorzugt werden.

Vergleichen Sie die Datenstrukturen: Listen, Stapel, Bäume,


Heaps, Hashes;

• Liste:
speichern Elemente in vorgegebener Reihenfolge; diese ist durch
Verkettung der
Elemente gegeben; Listen eigene sich zum Speichern und Bearbeiten von
Elementen
in bestimmter Reihenfolge; Einfügen u. löschen gestalte sich einfach; für
wahlfreien
Zugriff und Suchen vollkommen ungeeignet;
• Stapel:
speichert Elemente in der Reihenfolge ihres Eintreffens, diese kann nicht
verändert
werden; zwei Operatoren: Push, Pop; Reihenfolge durch FIFO oder LIFO
vorgegeben;
eignen sich zum temporären Speichern von Daten und zum Auflösen von
Rekursionen
in Algorithmen; sie sind für die Datenhaltung völlig ungeeignet, da ein
wahlfreier
Zugang auf ein beliebiges Element nicht möglich ist.
• Bäume:
speichern Hierarchien und Abhängigkeit von Daten; ermöglichen gezielte
Einordnen
und Suchen von Daten; werden zur Datenhaltung herangezogen; Sind
Alleskönner,für
manches aber nur mittelmäßig; Suchen erfolgt schneller als in Listen,
langsamer als in
Hahes; sind die Hierarchien von Daten unwichtig, sollen andere
Datenstrukturen
verwendet werden.
• Heaps:
sind spezielle binäre Bäume, die in einem Feld realisiert werden, wobei für
jeden
Knoten die Heapbedingung erfüllt sein muss. Bietet wenig Operationen,
wie Einfügen
und das Holen eines Elements mit dem größten Schlüssel; Hauptzweck
Prioritätswarteschlange und Suchen von Daten;
• Hashes:
speichern Daten in Feld ab, wobei der Index des Feldelements mit Hashfkt
ermittelt
wird. Qualität hängt von Hashfkt, Feldlänge und den Schlüssel der zu
speichernden
Daten ab. Eignen sich hervorragend zum Suchen von Daten u. zur
Datenhaltung.
Speichern keine Reihenfolge und Hierarchien sowie Abhängigkeit von
Daten.

Was verstehen Sie allgemein unter einem Dynamischen


Speicher?

Die bisher behandelten Datenstrukturen haben eines gemeinsam: Die


Anzahl der Datensätze ist nicht beschränkt, was sie von Felder
unterscheidet. Bei der bisherigen Implementierung von Listen mittels
Felder, hat dies den Nachteil, dass mit der Festlegung der Feldlänge auch
die max. Anzahl der Elemente fixiert ist. Beim Entfernen müssen somit
Lücken geschlossen werden oder Indizes als ungültig markiert werden.
Somit ist die Implementierung der Liste abhängig von der
Implementierung der Speicherverwaltung. Um allerdings Programme zu
ermöglichen, die unabhängig von der konkreten Ausführung der
Speicherverwaltung sind, muss die Implementierung der Datenstrukturen
von der Implementierung der Speicherverwaltung getrennt werden. Durch
diese Trennung ist es möglich, verschiedene Arten von
Speicherverwaltungen einzusetzen. Durch eine eigene, an das jeweilige
Programm angepasste Speicherverwaltung können Programme um den
Faktor 2 oder mehr beschleunigt
werden. (Die vom Betriebssystem angebotene Speicherverwaltung kann
nämlich, aufgrund von Spezialfällen, eventuell zu langsam sein.)

Welche Aufgaben hat die Speicherverwaltung zu bewältigen?

Diese Aufgabe liegt in der Verwaltung des zur Verfügung stehenden


Speichers. Die
Speicherverwaltung ist der untersten Ebene im Aufbau des Programms
zuzuordnen. Aus dem Programm wird ein Speicher bestimmter Größe von
der Speicherverwaltung angefordert, im Programm verwendet und nach
der Verwendung an die Speiherverwaltung wieder retourniert. Somit
werden Speicherblöcke angefordert (erzeugt) und dann wieder
zurückgegeben (zerstört). Damit spricht man von dynamischer
Speicherverwaltung. Um jedoch das Zerstückeln des Speichers durch viele
kleine Blöcke zu vermeiden, runden viele Betriebssysteme die geforderte
Länge auf beispielweise durch 8 teilbare Längen auf. Wird also ein
Speicherblock der Länge 2 Byte angefordert, erhält man 8 Byte. Wichtig ist
auch die Behandlung des Falles, dass kein Speicher mehr frei ist (nicht
mehr benötigten Speicher freizugeben, Benutzer über gewünschtes
Vorgehen fragen(speichern, abbrechen,..).

Was macht die Funktion "malloc"? Was ist der Unterschied


zwischen "calloc" und "malloc"?
Mit dem Befehl malloc können Speicherblöcke beliebiger Länge
angefordert werden. Einziger Parameter ist die Länge des Speichers in
Byte. Rückgabewert ist die Adresse des Speicherblocks. Konnte kein
Speicher alloziert werden gibt der Befehl 0 zurück. Speicher muss danach
mit free() wieder freigegeben werden. Der Datentyp wird nicht geprüft!
datentyp = *zeiger;
if((zeiger=malloc(sizeof(datentyp))) != 0)
oder
datentyp = *feldzeiger
if((feldzeiger=malloc(laenge * sizeof(datentyp))) != 0)
calloc:
gleich wie malloc, nur Speicher wird mit 0 vorinitialisiert.
Wie kann die Größe von Speicherblöcken verändert werden
worauf ist zu
achten?

Mit der Funktion realloc() können Speicherblöcke in ihrer Größe verändert


werden.
realloc(alterBlock,neue Länge); Schlägt realloc(), fehl wird der Wert Null
zurückgegeben, was in einer Fehlerbehandlung abzufangen ist. Weiters ist
nicht gewährleistet, dass der Speicherplatz nach dem Verändern der Größe
an der selben Adresse wie vor der Reallizierung steht. Somit dürfen keine
Zeiger verwendet werden, die in einen Speicherblock zeigen, der mit
realloc() verändert wird: Falls der Speicherbereich mit realloc()
verschoben wird, werden diese nämlich ungültig.

Wie kann ich Speicher wieder freigeben? Warum muss dieser


freigegeben werden?

Dynamischer Speicher muss, sobald er nicht mehr benötigt wird, wieder


freigegeben werden. Andernfalls wird Speicher unnötig blockiert (viel
Programme geben den noch nicht freigegebenen Speicher beim Beenden
des Programms frei; darauf aber nicht verlassen). Dies geschieht mit
free(). Sie erwartet als einzige Argument die Adresse des Speicherblocks,
die von malloc(), realloc(), calloc() geliefert wurde. Achtung:
Auf Speicher, der bereits freigegeben wurde, darf nicht mehr zugegriffen
werden. (Zeiger auf Null setzen. Speicher darf auch nicht zweimal
freigeben werden (Absturz des Programmes). Daher ist es wichtig zu
wissen, ob Speicher bereits freigeben wurde oder nicht. Per Konvention
wird ein Zeiger, der auf keinen gültigen Speicher zeigt, auf 0 gesetzt.

Was passiert wenn kein Speicher mehr frei ist?

Wird von mallco, calloc, realloc retournieren Null, wenn kein Speicher
alloziert werden kann. Dieser Fehler sollte immer abgefragt werden und
korrekt behandelt werden. Falls kein Speicher mehr angefordert werde
kann, muss die jeweilige Operation abgebrochen werden
und eine Fehlermeldung an den Benutzer abgegeben werden(nicht mehr
benötigten Speicher
freizugeben, Benutzer über gewünschtes Vorgehen fragen(speichern,
abbrechen,..).

Was ist wenn die Freigabe mit einer falschen Adresse erfolgt?
Char * puffer = malloc(10); free(puffer+1); Schwerer Fehler; eventuell
wird auf Speicher zugegriffen, auf den man nicht zugreifen kann.

Was passiert, wenn ein bereits freigegebener Speicher erneuert


freigegeben wird?

Programm kann abstürzen. Auf Speicher, der bereits freigegeben wurde,


darf nicht mehr
zugegriffen werden. (Zeiger auf Null setzen. Speicher darf auch nicht
zweimal freigeben
werden (Absturz des Programmes). Daher ist es wichtig zu wissen, ob
Speicher bereits
freigeben wurde oder nicht. Per Konvention wird ein Zeiger, der auf keinen
gültigen Speicher zeigt, auf 0 gesetzt.

Was passiert wenn ein reguläres Feld freigegeben wurde?

Wird ein reguläres Feld, dass nicht dynamisch alloziert wurde, freigegeben,
ist das Ganze zwar syntaktisch korrekt, semantisch jedoch falsch, was der
Compiler allerdings nicht erkennen kann.

Was passiert wenn eine reguläre Variable freigegeben wurde?

Syntaktisch korrekt, wird daher von Compiler nicht erkannt, semantisch


falsch. Es darf nur dynamisch allozierter Speicher freigegeben werden.

Was passiert bei der Freigabe eines Nullzeigers?

Nullzeiger dienen nur dazu, zu zeigen, ob ein Zeiger auf einen gültigen
Speicherbereich zeigt, oder nicht. Er darf nicht freigegeben werden, da der
Speicher der Adresse 0 nicht angefordert wurde und auch nicht
angefordert werden kann.

Was passiert bei der Freigabe eines nicht initialisierten Zeigers?

Wird auf die Allozierung des dynamischen Speichers vergessen, der


Zeiger ist ungültig.
Ungültige Zeiger müssen auf Null gesetzt werden. Bei der Freigabe ist auf
Gleichheit mit 0 zu testen.

Was passiert bei Zugriff auf einen ungültigen Speicherbereich?


Erfolgt ein Zugriff auf einen Speicherbereich, dessen Adresse willkürlich
ist, kann man auch von einem ungültigen Zeiger sprechen.

Was passiert bei Zugriff eines bereits freigegebenen Speichers?


Somit wird auf Speicherbereich zugegriffen, der nicht mehr zum Prozess
gehört.

Was passiert bei einem Zugriff mit falschen Indizes?


Auch bei der Allozierung eines Feldes mit dynamischen Feldern gilt
dieselbe Regel wie bei regulären Feldern: Der Index beginnt mit 0 und
endet mit feldlaenge – 1.

Was passiert bei Zugriff auf einen nicht initialisierten Speichers?

Der Speicher wird mit der Fkt malloc nicht initialisiert. Bevor dieser
Speicher gelesen werden kann, muss er initialisert werden, sonst steht
irgend a kas drin. Sicherheitslücke: Der Speicher wird nämlich weder beim
Speichern noch beim Freigeben gelöscht, die Daten stehen noch immer im
Speicher, nur halt jetzt frei verfügbar.

Wie kann es passieren, dass ein Speicher durch Überschreiben


eines Zeigers verloren geht?

Wird einem Zeiger, der die Adresse eines dynamisch allozierten


Speicherbereichs trägt,
überschrieben, geht der allozierte Speicherbereich verloren. Er existiert
nach wie vor, nur kann darauf nicht mehr zugegriffen werden. man spricht
von einem memory leak. Leaks sind ungenutzte Speicherbereiche, die
dem Prozess zugeordnet sind, aber nicht mehr verwendet/gelöscht werden
können, da die Adresse nicht mehr bekannt ist. Somit kann bei vielen
Leaks der Speicherverbrauch in ungeahnte Höhen wachsen, bis zum
Blockieren des Rechners, auch Absturz.

Wie kann es passieren, dass ein Speicher verloren geht durch


den Verlust eines Zeigers?

Oft werden mehrere dynamisch allozierte Speicherbereiche in Feldern von


Zeiger gemerkt. Bei der Freigabe des Feldes gehen dann allerdings die
dynamsich allozierten Speicherbereiche verloren. Es wird nur ein
Speicherblock, der des Feldes, freigegeben. Es kommt zu leaks. Richtig
wäre es, zuerst die Elemente des Feldes freizugeben, dann erst das Feld
selbst. Und dann den Nullzeiger auf die Feldadresse zu setzten.
Was wissen Sie über den Verlust eines Speichers durch
Rücksprung aus einer Fkt?

Auch hier kommt es zu memory leak. Beim Rücksprung aus einer Fkt wird
der lokale Zeiger zwar gelöscht, der allozierende Speicher kann aber
weder gelöscht noch verwendet werden.

Wie kann ein Speicher bei der Rückgabe verloren gehen?

Wird durch eine Fkt die Adresse eines Speichers korrekt zurückgegeben,
dort aber dann
verloren entsteht wieder ein leak.

Was passiert bei viel zu großen Speicherblöcken?

Unsinnig! Der Aufruf scheitert.

Was sind Modellfehler?

Bei jeder Modellbildung wird eine Abstraktion der Realität durchgeführt.


Dabei werden
bekannte und unbekannte Größen vernachlässigt und Vereinfachungen der
tatsächlichen Verhältnisse vorgenommen. Der dadurch entstehende Fehler
wird Modellfehler genannt. Können nur behandelt werden, sofern sie
überhaupt erkannt werden. Oft ist ihre Behandlung aber auch nicht
relevant, da sie durch bewusste Vernachlässigung entstanden sind.
Vielfach ist nur eine Warnung an den Anwender notwendig, wenn der
Modellfehler zu groß wird. So können relativistische Effekte bis zu einer
bestimmte Geschwindigkeit vernachlässigt werden.

Was sind Verfahrensfehler?


Abbruchfehler: Oft werden numerische Näherungsverfahren verwendet,
die sich in einem iterativen Lösungsverfahren an die Lösung herantasten.
Bei hinrechend genauem Ergebnis wird das Verfahren abgebrochen,
dadurch entsteht zwangsläufig der sog. Abbruchfehler.
Diskretisierungsfehler: entstehen beim Übergang von kontinuierlichen auf
diskrete Systeme. Z.B. wird ein Integral als eine Summmation
durchgeführt, bei der gegenüber dem Integral ein Fehler auftritt.
Verfahrensfehler lassen sich durch erhöhten Aufwand oft beliebig
verkleinern. Da aber jedes Verfahren abgebrochen werden muss, entsteht
immer ein gewisser Abbruchfehler, der im kleinsten Fall die
Darstellungsgenauigkeit des internen Datentyps ist.
Was bedeutet der Begriff "Kondition"?
Mit Hilfe eines Modells wird ein reales System abgebildet, wodurch ein
Modellfehler
entsteht. Man erhält nun ein numerisches Problem, wobei die reale Lösung
und die
numerische Lösung nicht unbedingt gleich sein müssen.
xreal...tatsächliche Größe
x.........mathematisch exakte Größe
x’........numerische Lösung
ε..........gibt die Genauigkeit an
Genauigkeitsanforderung der numerischen Lösung: || x’ - x|| < ε
δ...Schranke für Modellfehler: || x- xreal || < δ
Gesamtfehler ||x’ - xreal || < ε + δ
Damit ist ein numerisches Problem allerdings noch nicht genau
beschrieben. Ein
wesentliches Kriterium ist die Empfindlichkeit eines Systems gegenüber
Änderung von
Daten. Dazu wird eben der Begriff der Kondition eingeführt. K ist die
Konditionszahl
D.....Datenmenge die dem numerischen Problem zugrunde liegt
D’....fehlerbehafteten Daten

Kondition K Î Empfindlichkeit eines Systems gegenüber Änderung von


Daten (großes K Î System ist empfindlich (schlecht konditioniert))

Welche Probleme können bei der Summation von Punktzahlen


auftreten?
Grund für den Fehler liegt in der internen Darstellung von Punktzahlen
(double). Exponent (11 bit) sowie Mantisse (52 bit). Bei der Summation
werden die Mantissen addiert. Dazu müssen die Exponenten gleich sein.
Sind diese nicht gleich wird der Exponent der kleiner Zahl auf den der
größeren gebracht. Dabei wird die Mantisse verschoben und es fallen
unter umständen „bits rechts heraus“. Auslöschung: Entstehen durch
Subtraktion bzw. Addition zweier annähernd gleicher Werte. Unterscheiden
sich diese fast nur durch ihre nicht signifikanten Stellen so fallen bei einer
Subtraktion alle signifikanten Stellen heraus und es verbleiben nur die
nicht signifikanten Stellen (Störungen an hinteren Stellen werden zu
Störungen an vorderen Stellen) übrig
DBL_MAX: größte positive Zahl
DBL_MIN: kleinste normalisierte Zahl > 0
DBL_EPSILON: Differenz zwischen 1 und der kleinsten Zahl größer 1

Wie funktioniert die Signalbehandlung in C? Was ist ein Signal?


Prozesse können Signale vom Betriebssystem erhalten. Enthalten in der
„signal.h“.
Empfängt ein Prozess ein Signal, wird der Programmablauf unterbrochen
und eine Funktion zur Behandlung des Signals aufgerufen. Die Funktion
„signal(Signalcode, Zeiger auf Signalbehandlungsfunktion)“ wird zum
registrieren neuer Signalbehandlungsfunktionen verwendet. Z.B. um nicht
gesicherte Daten zu speichern. Die Signalbehandlungsfunktion hat einen
Parameter vom Typ int (Signal das
zum Aufruf geführt hat). Kein Rückgabewert. Siehe Tabelle der Signale
(B.S. 328 ff.)
Statt dem Zeiger auf die Signalbehandlungsfunktion kann auch
SIG_IGN: Auftreten des Signals wird ignoriert
SIG_DFL: Reguläre Signalbehandlungsfunktion wird wieder aktiviert
übergeben werden.
• SIGINT
Unterbrechung; durch Strg-C an Prozess gesandt
• SIGABRT
Abbruch; Wird von abort und assert verwendet
• SIGTERM
Terminieren; Dieses Signal wird beim Herunterfahren des Systems an
jeden Prozess gesandt. Eine Behandlung dieses Systems ist empfohlen;
• SIGILL
Illegaler Maschinenbefehl; wenn ungültiger Maschinenbefehl geladen wird
• SIGFPE
Gleitpunktfehler; wenn ungültige arithmetische Operation durchgeführt
wird, wie Division durch Null oder Overflow;
• SIGSEGV
Unerlaubter Speicherzugriff; wird an Prozess gesandt, wenn er versucht,
auf einen Speicher zuzugreifen, der nicht zum Prozess gehört oder wenn
illegale Adresse auftritt.

Welche Möglichkeiten bietet C zur Fehlerbehandlung und wie


funktionieren diese?
Bei der Implementierung einer Fehlerbehandlung entsteht of die
Diskrepanz, die exakte
Position, an der der Fehler behoben werden soll, zu finden. Prinzipiell ist zu
unterscheiden:
• dem Ort,an dem der Fehler aufgetreten ist,
• dem Ort, an dem der Fehler entdeckt wurde und
• dem Ort, an dem der Fehler behandelt wurde
errno: Variable wird im Fall eines Fehlers auf einen Fehlercode gesetzt. Im
fehlerfreien Fall aber nicht auf 0 gesetzt
perror: Fehlermeldung passend zum Fehlercode in „errno“ kann
ausgegeben werden.
Erwartet als Argument eine Zeichenkette (Ausgabe: Zeichenkette:
Fehlerbeschreibung)
strerror: Es wird nur der Fehlertext ausgegeben. Erwartet Fehlercode aus
„errno“ als
Argument.

Was ist eine rekusrsive struktur?


Ist eine Struktur die Attribute enthält die auf Strukturen desselben Typs
verweisen. Zeiger auf andere Strukturen desselben Typs z. B. Liste

Was verstehen Sie allgemein unter einer Baumstruktur?

Bäume in der Programmierung haben Ähnlichkeiten mit einem


Stammbaum. Es gibt die
verschiedensten Arten von Bäumen, mit verschiedenen Spezialisierungen:
zum suchen,
speichern, sortieren von Datensätzen, zum Lösen von Geometrischen- und
Syntaxanalyseprobleme, oder auch zur Datenkomprimierung. Wir
beschränken uns hier auf binäre Bäume:

Was wissen Sie über die Eigenschaften und die Terminologie von
Bäumen?

Listen stellen eindimensionale Datenstrukturen dar, Bäume können als


mehrdimensionale Erweiterungen gesehen werden. Ein Knoten hat dabei
mehre Nachfolger, die Verknüpfung zweier Knoten wird als Kante
bezeichnet, Konten ohne Nachfolger heißen Endknoten oder Blätter. Als
Wurzel wird jener Knoten bezeichnet, der keine Vorgänger hat. Die Def
eines Baumes kann auch Rekursiv erfolgen, wobei jeder Baum aus
Subbäumen bestehen würde, deren Konten durch einen gemeinsamen
Knoten verkettet wären. Ein Pfad von einem Knoten zu einem anderen ist
eine Liste jener Knoten, die durch Kanten miteinander verbunden sind. In
Bäumen existiert immer genau ein Pfad von einem Knoten zu einem
anderen, sollten mehrer Pfade existieren, spricht man von einem
Graphen(Menge von Knoten u. Kanten). Auch hier gibt es Vorgänger und
Nachfolger. Knoten mit den selben Vorgängern werden als
Geschwister bezeichnet. Nichtendknoten heißen innere Knoten, Endknoten
heißen äußere Knoten. Ein allgemeiner Knoten mit N Knoten hat N-1
Kanten. Als Wald wird eine Menge von Bäumen bezeichnet.
Jeder Knoten enthält wie eine Liste zwei Arten von Infos: Das Element und
die Verkettung. Es kann jedoch auch mehre Verkettungen geben. Die
Verkettungen werden als Indizes in Feldern oder als Zeiger implementiert.
Werden Elemente mit bestimmter Ordnung eingefügt, ist ein Schlüssel
notwendig.

Was versteht man unter einem Schlüssel im Bezug zu Bäumen?

Unter einem Schlüssel wird ein Kriterium verstanden, mit dem ein Element
in einem Baum eingefügt od. gesucht werden kann. Er kann direkt ein
Datensatz oder dessen Attribut sein oder über eine Fkt ermittelt werden.
(Namen, Adresse die durch eine Struktur beschrieben werden).

Was versteht man unter einem binären Baum?

Unter einem binären Baum versteht man Bäume, deren Knoten zwei
Nachfolger besitzen. Die Knoten enthalten das Element und die Verkettung
zum linken und rechten Nachfolger. Der Kopf muss separat gespeichert
werden. Unter einem vollständigen binären Baum versteht man ein
Bäumchen, dass von links nach rechts aufgefüllt wird. Ist eine Reihe
komplett, wird eine neue begonnen. Ein binärer Baum Mit Ni inneren
Knoten hat max. Ni +1 äußere Knoten. Die Höhe eine vollständigen
binären Baumes kann mittels log2(N)+1 ermittelt werden.

Was sind binäre Suchbäume?

Ein binärer Suchbaum ist ein binärer Baum, bei dem alle Knoten, die sich
im linken
Unterbaum eines Knotens befinden(erreichbar über linke Verkettung),
einen kleineren
Schlüssel besitzen. Alle Knoten, die sich im rechten Unterbaum befinden,
haben größere
Schlüssel. Knoten mit gleichem Schlüssel werden per Konvektion auch
links abgelegt. Es ist aber nicht möglich, diesen Baum in einen
vollständigen binären Baum umzuwandeln, wenn
Was versteht man unter dem Traversieren von Bäumen?
Erklären Sie die
verschiedenen Traversierungsarten!
Unter traversieren versteht man, wie ein Baum durchlaufen wird. Diese
Aufgaben sind
besonders schön rekursiv zu lösen.
Es gibt die Level-Order-, die Preorder-, die Inorder-, und die
Postordertraversierung.
Leve-Order-Methode:
Kommt bei Heap Strukturen zum Einsatz. Die Elemente werden von links
nach rechts
durchlaufen.
PreorderTraversierung:
Wird jeder Knote Ebene für Ebene der Reihe nach bearbeitet. Bei heaps-
Strukturen
Zuerst wird der mittlere Knoten bearbeitet, anschließend wird der linke
Knoten und all
seine Subknoten durchlaufen. Im Anschluss daran wird der rechte Knoten
und seine
Subknoten traversiert. Diese Art der Traversierung wird gerne für
Syntaxbäume in der
PrefixNotation verwendet.
Inorder Traversierung:
Hier wird zuerst der linke Subbaum bearbeitet, dann der mittlere Knoten
und zuletzt
der rechte Subbaum. Wird zum Sortieren, Erzeugen und Darstellen von
Syntaxbäumen
in der Infix-Notation verwendet.
Postorder-Traversierung:
Hier wird zuerst der linke Subbaum bearbeitet, dann der rechte um zum
Schluss der
mittlere Knoten. Wird zur Auswertung von Syntaxbäumen verwendet.

Wie kann ein Element in einem binären Suchbaum gesucht


werden?
Dazu muss durch den Baum traversiert werden. Ist der Baum unsortiert,
muss jeder Knoten durchlaufen werden. Dies wäre unsinnig. Zum Suchen
werden binäre Suchbäume verwendet. Dabei wird die Bedingung für
binäre Suchbäume verwendet: Der linke Subbaum enthält nur Knoten mit
kleineren od. gleichen, der rechte Subbaum nur Knoten mit größerem
Schlüsseln. Existieren mehrere Knoten mit dem selbem Schlüssel wird nur
der erste gefunden. (Schlüssel größer als aktueller⇒verzweige nach
rechts, ist er kleiner⇒verzweige nach link; ist er gleich groß⇒Objekt
gefunden); Es werden ca. log2(N)-1 Vergleiche durchgeführt, bis das
Elemente gefunden ist. Die Abfrage auf Gleichheit wird zum
Schlussdurchgeführt, Laufzeitgründe. Wird Element nicht gefunden,
gelangt man also an den Endknoten, wurden etwa log2(N) Vergleiche
durchgeführt.

Wie kann ein neues Element in einem binären Suchbaum


eingefügt werden?

Zum Einfügen wird das Element zuerst erfolglos im Baum gesucht,


wodurch man zum
Endknoten kommt. Auch hier wird Verglichen ob der Schlüssel größer oder
kleiner ist usw. Ist kein Nachfolger vorhanden, wird das Element
angehängt, ist einer vorhanden wird erneuert verglichen. Das erste
Element ist trivial, der Kopf zeigt dann auf dieses Element.

Wie kann ein Element in einem binären Suchbaum gelöscht


werden?

Größerer Aufwand als beim Traversiren, Suchen und einfügen, da der


Schlüssel untrennbar mit der Struktur des Baumes verbunden ist, der
Baum wird beschädigt und muss repariert werden. Wird ein Konten
entfernt, muss also ein anderer Knoten seinen Platz einnehmen, damit die
Baumstruktur erhalten bleibt. Die Bedingungen für den binären Suchbaum
müssen jedoch eingehalten werden. Der gesuchte „Ersatzknoten“ ist also
der Konten mit dem nächst größeren oder dem nächst kleineren Schlüssel.
(existiert rechter Nachfolger? wenn nein, ersetzte ihn durch linken
nachfolger)
(existiert rechter Nachfolger? wenn ja, muss der Knoten durch den Knoten
mit den nächst größeren Schlüssel ersetzt werden: Gehe zum rechten
Nachfolger und finde dessen kleinsten Nachfolger; existiert dieser nicht
wird der Knoten selbst gewählt)

Welche Methoden gibt es für die Verwendung eines Heaps als


Prioritätswarteschlange?

Werden Heaps als Prioritätswarteschlange verwendet, so existieren im


Allgemeinen nur drei Methoden:
-Einfügen eines neuen Elements;
-Traversieren über einen Heap; meisten Level-Order oder Preorder-
traversierung; trivial da arithmetische Operatoren zum Auffinden der
Vorgänger/Nachfolger verwendet werden können.
-Entfernen eines Elements;
Das Einfügen/Löschen eines Elements erfordert eine Reparatur der Heap
Struktur. Dazu
existieren die Methoden UpHeap und DownHeap.
Was verstehen Sie unter der Methode UpHeap?

Wird beim Einfügen von Elementen benötigt. Sie stellt sicher, dass ein
Element das die
Heap Bedingung verletzt(Schlüssel des Vorgängers ist kleiner), richtig
platziert wird und der Heap dadurch repariert wird. Diese Methode arbeitet
sich von unten durch den Heap(bottom up). Ein neues Element wird
anfangs nach dem letzten Eintrag eingefügt(Heap ist ja ein ausreichend
dimensioniertes Feld). Damit wurde die Heapbedingung allerdings verletzt.
Die Methode UpHeap repariert den Heap von unten hinauf, indem es
überprüft ob der Schlüssel des Vorgängers kleiner ist. Ist dies der Fall, so
werden die Elemente vertauscht. Das neue Element steigt den Heap
hinauf. Dies wird solange wiederholt, bis der Vorgänger größer oder gleich
groß ist, dann befindet sich das Element an der richtigen Position. Diese
Methode setzt voraus, dass der Heap vor dem Einfügen korrekt geordnet
war.

Was verstehen Sie unter der Methode DownHeap?

Die Methode DonwHeap arbeitet sich von oben nach unten durch den
Heap(bottom down). Se wird dann verwendet, wenn ein Knoten die Heap-
Bedingung in einem sonst korrekten Heap nicht erfüllt, weil mindestens
einer seiner Nachfolger größer ist. Um zu reparieren, wird der Schlüssel
des Elements mit seinem Nachfolger verglichen. Ist der Schlüssel von
einem der beiden Nachfolgers größer, tauschen die Elemente ihre
Plätze(mit dem größten Nachfolger wird getauscht). Sind sie gleich groß,
wird je nach Konvention der linke oder rechte Nachfolger zum Tauschen
gewählt. Die wird solange fortgesetzt, bis der Heap korrekt ist. Auch hier
wird ein korrekter Heap vorausgesetzt, mit nur einem inkorrekten Element.

Wie kann ein Element eines Heaps entfernt werden?

Beim Entfernen würde eine Lücke in der Struktur des Heaps aufgerissen.
Um das letzte
Element des Heaps zu löschen, wird lediglich die Feldgröße des Heaps um
1 vermindert (also die Anzahl der Elemente des Heaps; nicht die
Feldlänge, die ja starr ist). Im ersten Schritt wird also das zu löschende
Element mit dem an der letzten Stelle getauscht. Danach wird die Länge
des Heaps um 1 reduziert, wodurch das Element entfernt wird. Die
Heapstruktur ist jetzt allerdings nicht mehr korrekt, es muss die Methode
DownHeap für das Tauschobjekt aufgerufen werden, die die verletzte
Struktur wieder repariert.
Wie kann ein Element in einen Heap eingefügt werden?

Das Einfügen eines Elements erfolgt mit der Methode UpHeap. Durch
mehrmaliges
Ausführen des Einfügevorgangs kann ein Heap gefüllt werden. Die
tatsächliche Position des Elements im Heap spielt keine Rolle(ein Element
könnte an mehren Stellen des Heaps sitzen, ohne die Bedingungen zu
verletzen). Der Nachteil dieser intuitiven Methode ist, dass sie langsam ist,
da für jedes eingefügte der Heap repariert werden muss.

Für was braucht man Zeiger auf Funktionen?


Werden in C selten eingesetzt, hauptsächlich bei der Verwendung von
externen Bibliotheken bei GUI. Auch Funktionen besitzen eine Adresse. Um
sie zu erhalten kann der Adressoperator auf die Funktion angewandt
werden: &Funktionname. (Achtung: keine „()“ bei Adresszuweisung). Somit
können auch Zeiger auf Funktionen definiert werden. Verwendung: Ähnlich
zu normalen Zeigern. Z.B. Callback Functions, Menüs
Die Definition eines Zeigers auf eine Funktion sieht wie folgt aus:
Typ (* Funktionsname) (Parameterliste)
Long (*fptr) (long, long)
long summe (long x, long y)
{ return x+y; }
fptr=&summe;

Was ist ein memory leak?


Wird einem Zeiger, der die Adresse eines dynamisch allozierten
Speicherbereichs trägt,
überschrieben, geht der allozierte Speicherbereich verloren. Er existiert
nach wie vor, nur kann darauf nicht mehr zugegriffen werden. man spricht
von einem memory leak.Leaks sind ungenutzter Speicher. Sie existieren
zwar nach wie vor können aber nicht mehr verwendet werden oder
gelöscht werden. (zb bei Verlust des Zeigers). Kann zu blockieren des
Rechners führen.

Extremwertsuche(Min oder max).


Minimumsuche:

Der Algorithmus sucht in einer Folge von Zahlen das kleinste Element und
tauscht es gegen das Element an der Position 0 aus. Danach sucht er das
zweitkleinste Element und tauscht es mit dem an Stelle 1 aus. (jeweils
kleinste Zahl muss gemerkt werden. Danach mittels Dreieckstausch
tauschen). Diese Verfahren ist langsam und einfach, für kleine
Datenmengen ausreichend, meist zum Sortieren von Daten in Dateien
verwendet. 2 Schritte 1. Suche das kleinste Element; 2. Tausche das
Element gegen die erste Position im verbleibenden Feld

variante strukturen---union---warum legt man nicht einfach


beide Auswahlmöglichkeiten explizit an?
Haben große Ähnlichkeit zu regulären Strukturen. Elemente liegen jedoch
„übereinander“ im Speicher. Somit werden beim schreiben auf ein Attribut
alle anderen überschrieben. Der Wert der anderen Attribute ist somit nicht
definiert. Definition mit dem Schlüsselwort „union“. Länge der Struktur
entspricht der Länge des längsten Attributs. Die Information welches
Attribut gerade gültig ist muss separat gespeichert werden. (Definition
eines separaten emun-Datentyps von Vorteil). Um Speicherplatz zu sparen
bei speziellen Anwendungen z. B. geometrische Objekte nur Kreis oder
Rechteck

Was macht typedef?


Mit Typdef kann man einem bestehenden Datentyp einen neuen Namen
geben. Es wird kein neuer Typ erzeugt sondern nur ein weiterer Name für
einen Typ angegeben. Vereinfachung, wenn lange
(Daten)typ(bezeichnung)en öfters verwendet werden

Aufzählungsdatentyp(enum)
Aufzählungen werden in C verwendet, um mehrere Konstanten zu
definieren und zu einem Typ zu kombinieren. Alle Konstanten müssen
ganzzahlig sein. Diese Zahlen werden nacheinander aufsteigend mit 0
beginnend vergeben. Wenn man einen eigenen Wert angeben will muß
man KONSTANTE=Wert schreiben.
Enum e_Farbe{
FARBE_ROT=1,
FARBE_GRUEN=2,
FARBE_BLAU=4};

Wie fügt man ein Element in einen Hash ein?


Zuerst wird mit Hilfe eine Hashfunktion aus dem Eintrag ein Index
berechnet an dem das Element eingefügt wird. Dann wird überprüft ob der
Index bereits belegt ist, wenn ja tritt eine Kollision auf und das neue
Element wird an das alte angehängt; wenn nein wird das neue Element am
Index normal eingefügt.
Was ist eine header - Datei? was darf in einer header - Datei
nicht drinnen stehen?
In Headerdateien sind die Funktionsdeklarationen und
Präprozessorkonstanten. Präprozessorkonstanten, Funktionsdeklarationen
Strukturdefinitionen, Includes von Headerdateien von
Standartbibliotheken. Sie dürfen keine ausführbaren Programmteile
enthalten.

Erkläre die Fkt. "exit"


Beim Auftreten eines Fehlers in der Programmentwicklung kann es
hilfreich sein, das
Programm unmittelbar zu beenden. Durch Aufruf der Fkt. exit wird das
Programm sofort verlassen, egal welche Fkt gerade ausgeführt wird. Sie ist
in stdlib.h inkludiert und erwartet als einzigen Parameter einen Fehlerwert.
Dieser hat dieselbe Bedeutung wie der Rückgabewert von main (0..alles
ok; alle ungleich Null entspricht Fehlercode);

Was hat es mit der Zeiger- Felddualität auf sich?

Softwaretechnisch sind Zeiger und Felder völlig verschiedene Konstrukt


e(Zeiger: variable, die eine Adresse speichert; Feld: Verbundtyp, der
mehrere Daten des selben Typs speichert). Feldindex und Zeiger verweisen
jeweils auf eine Adresse im Speicher in denen sich der gewünschte Inhalt
steht. Bei Feldern mit [ ] Indexoperator bei Zeigern mit * kann auf den
Inhalt zugegriffen werden. Der Name des Feldes kann als konstanter
Zeiger verwendet werden (z. B. in Funktionen)

Wie löscht man ein Element in einer DLL?


Zuerst wird das Element gesucht. Wenn es gefunden wurde wird der next-
Zeiger des vorhergehenden Elements auf das nachfolgende gesetzt, der
prev- Zeiger des Nachfolgers wird auf den Vorgänger gestzt. Abschließend
werden prev und next Zeiger des Elements auf 0 gestezt. Dadurch gibt es
keinen Verweis mehr auf das Element und ist somit gelöscht, bei
dynamischer Speicherverwaltung kann das Element mit free freigegeben
werden. Bei Implementierung in einem Feld ist das aufwendiger da die so
entstandene Lücke durch Nachrücken der Feldelemente wieder
geschlossen werden müßte.
erkläre Quicksort?
Vorteile: Sehr schnell, besonders effizient bei vollkommen unsortierten
Datenmengen,
geringer Speicherverbrauch, relativ einfach zu implementieren. Quicksort
erwartet die zu sortierenden Daten in einem Feld. Das grundlegende
Verfahren arbeitet rekursiv – in jedem Schritt wird die korrekte Position
eines gegebenen Elements gesucht und dieses
eingeordnet. (Die Wahl des Elementes ist beliebig) Nach einem Durchgang
ist aber nicht nur ein Element richtig plaziert, vielmehr wurden auch alle
Elemente rechts und links davon „grobgeordnet“ (links alles kleiner, rechts
alles größer).
QuickSort wählt ein Element aus der zu sortierenden Liste aus
(Pivotelement) und zerlegt die Liste in zwei Teillisten, eine untere, die alle
Elemente kleiner und eine obere, die alle Elemente gleich oder größer dem
Pivotelement enthält. Dazu wird zunächst ein Element von unten gesucht,
das größer (oder gleichgroß) als das Pivotelement und damit für die untere
Liste zu groß ist. Entsprechend wird von oben ein kleineres Element als
das Pivotelement gesucht. Die beiden Elemente werden dann vertauscht
und landen damit in der jeweils richtigen Liste. Der Vorgang wird
fortgesetzt, bis sich die untere und obere Suche treffen. Damit sind die
oben erwähnten Teillisten in einem einzigen Durchlauf entstanden. Suche
und Vertauschung können in-place durchgeführt werden. Die noch
unsortierten Teillisten werden über denselben Algorithmus in noch kleinere
Teillisten zerlegt (z.B. mittels Rekursion) und, sobald nur noch Listen mit je
einem Element vorhanden sind, wieder zusammengesetzt. Die Sortierung
ist damit abgeschlossen.

Erkläre den Begriff „rekursive Algorithmen“


Kennzeichen rekursiver Algorithmen ist, dass die Gesamtlösung durch
Lösen gleichartiger Teilprobleme erreicht wird. zB.: Die Faktorielle von N ist
definiert als N multipliziert mit der Faktoriellen von (N-1). Diese Definition
heißt rekursiv, da sie einen weiteren Aufruf der Funktion Faktorielle
beinhaltet.
Besonders wichtig ist die Angabe einer Bedingung bei der die Rekursion
abgebrochen
wird. Im fall der Faktoriellen ist die Abbruchbedingung, dass N>=0 sein
muss, für N=0 ist
das Ergebnis 1.
• Die Aufgabe wird in gleichartige Teilprobleme zerlegt, welche rekursiv
gelöst werden.
(Im Bsp: Faktorielle geschieht das mit (N-1)!.
• Jede Rekursion trägt zur Gesamtlösung bei. (Im Beispiel Multiplikation
eines rekursiven Aufrufs mit N).
• Es muss eine Abbruchbedingung geben, die auch tatsächlich auftritt
(sonst ist es kein
Algorithmus mehr)

Erkläre "atexit"
Mittels atexit können mehrere Fkt registriert werden, die beim regulären
Programmende aufgerufen werden. Das reguläre Programmende wird
durch den Rücksprung aus der Fkt. main oder durch den Aufruf exit
ausgelöst. Werden mehrer Fkt registriert, erfolgt der Aufruf der Fkt in der
umgekehrten Reihenfolge ihrer Registrierung. Ist in stdlib.h deklariert und
erwarten Zeiger auf eine Fkt, die keinen Parameter als Rückgabewerte hat.

Fehlerarten
Modellfehler:
Bei jeder Modellbildung wird eine Abstraktion der Realität durchgeführt.
Dabei werden
bekannte und unbekannte Größen vernachlässigt und Vereinfachungen der
tatsächlichen Verhältnisse vorgenommen. Der dadurch entstehende Fehler
wird Modellfehler genannt.

Datenfehler:
Entstehen durch Ungenauigkeiten bei der Erfassung von Daten – z.B.
messen physikalischer Größen. (Messfehler – können abhängig von der
Messmethode meist abgeschätzt werden) Beispiele: Messungenauigkeiten,
syntaktisch falsche oder unvollständige Eingabedaten, fehlerhafte
Dateiformate, etc.

Verletzte Vorbedinung:
Zählt eigentlich zu den Datenfehlern. Können nur auf Grund einer
Vorbedingung auf
Korrektheit überprüft werden.

Anwenderfehler:
Entstehen durch fehlerhafte Bedienung, sowie fehlende oder inkorrekte
Eingaben – zählen ebenfalls zu den Datenfehlern. (Hilfestellung für den
Benutzer)

Fehlerhafter Programmaufruf:
Eigentlich ein Anwenderfehler – In vielen Betriebssystemen können
Programme mit einem Set an Parametern oder Optionen gestartet werden.

Numerische Fehler:
Numerische Methoden liefern nur mit einer Fehlerabschätzung eine
sinnvolle Aussage

Speicherproblem:
Speichermangel ist ein sehr leicht zu erkennendes, aber schwer zu
korrigierendes Problem:
1.) Programm beenden
2.) Das Programmwartet bis wieder Speicher frei wird
3.) Wenn möglich, Speicher freigeben.

Fehlerhafter Speicherzugriff:
Besonders kritisch, entweder sofortiger Programmabbruch (SIGSEGV),
oder es werden falsche Daten gelesen oder überschrieben.

Fehlerhafte Module:
Module oder Bibliotheken – Fremd-Software muss nicht fehlerfrei sein...

Dead lock:
Bei der Kommunikation paralleler Prozesse kann es vorkommen, dass zwei
Prozesse auf
Daten des jeweils anderen warten, wodurch der Programmablauf stoppt –
kann während der Laufzeit kaum behoben werden. (Außer es ist erlaubt.
Dann kann z.B. nur eine gewisse Zeit gewartet werden),
Wann verwendet man Zeiger auf Zeiger?
Schlechter Programmierstil sehr unübersichtlich, kann bei Swapfunktionen
angewendet werden.

Welche Sortierverfahren kennen sie (und grob erklären)?


Minimumsuche:

Der Algorithmus sucht in einer Folge von Zahlen das kleinste Element und
tauscht es gegen das Element an der Position 0 aus. Danach sucht er das
zweitkleinste Element und tauscht es mit dem an Stelle 1 aus. (jeweils
kleinste Zahl muss gemerkt werden. Danach mittels Dreieckstausch
tauschen)

Bubblesort:
Der Algorithmus vergleicht der Reihe nach zwei benachbarte Elemente
und vertauscht sie, falls sie in der falschen Reihenfolge vorliegen. Dieser
Vorgang wird solange wiederholt, bis keine Vertauschungen mehr nötig
sind. Hierzu sind in der Regel mehrere Durchläufe erforderlich. Je
nachdem, ob auf- oder absteigend sortiert wird, steigen die kleineren oder
größeren Elemente wie Blasen im Wasser (daher der Name) immer weiter
nach oben, d.h. an das Ende der Reihe.

Heap-Sort:
Der eigentliche Sortieralgorithmus nutzt die Tatsache aus, dass die Wurzel
eines Heaps
stets der größte Knoten ist. Da im fertig sortierten Array der größte Wert
ganz rechts
stehen soll, vertauscht man das erste mit dem letzten Arrayelement. Das
Element am Ende des Arrays ist nun an der gewünschten Position und
bleibt dort. Jetzt
wird die Heaplänge um 1 verringert und die Methode DownHeap
angewandt (Der um 1
verringerte Heap wird repariert). Das größte Element steht jetzt hinten im
Feld. Dieser
Vorgang wird so oft wiederholt bis alle Elemente entfernt wurden.

Erkläre „Quicksort“
Vorteile: Sehr schnell, besonders effizient bei vollkommen unsortierten
Datenmengen,
geringer Speicherverbrauch, relativ einfach zu implementieren. Quicksort
erwartet die zu sortierenden Daten in einem Feld. Das grundlegende
Verfahren arbeitet rekursiv – in jedem Schritt wird die korrekte Position
eines gegebenen Elements gesucht und dieses
eingeordnet. (Die Wahl des Elementes ist beliebig) Nach einem Durchgang
ist aber nicht nur ein Element richtig plaziert, vielmehr wurden auch
alle Elemente rechts und links davon „grobgeordnet“ (links alles kleiner,
rechts alles größer) QuickSort wählt ein Element aus der zu sortierenden
Liste aus (Pivotelement) und zerlegt die Liste in zwei Teillisten, eine untere,
die alle Elemente kleiner und eine obere, die alle Elemente gleich oder
größer dem Pivotelement enthält. Dazu wird zunächst ein Element von
unten gesucht, das größer (oder gleichgroß) als das Pivotelement und
damit für die untere Liste zu groß ist. Entsprechend wird von oben ein
kleineres Element als das Pivotelement gesucht. Die beiden Elemente
werden dann vertauscht und landen damit in der jeweils richtigen Liste.
Der Vorgang wird fortgesetzt, bis sich die untere und obere Suche treffen.
Damit sind die oben erwähnten Teillisten in einem einzigen Durchlauf
entstanden. Suche und Vertauschung können in-place durchgeführt
werden. Die noch unsortierten Teillisten werden über denselben
Algorithmus in noch kleinere Teillisten zerlegt (z.B. mittels Rekursion) und,
sobald nur noch Listen mit je einem Element vorhanden sind, wieder
zusammengesetzt. Die Sortierung ist damit abgeschlossen.

Was versteht man unter „Schlangen“?


Eine Schlange arbeitet nach dem Prinzip FIFO (first in first out). Das
Element das als erstes in die Schlange gestellt wurde, wird auch als erstes
wieder entnommen. Die zugehörigen Fkt heißen Put & Get. Schlangen
werden vorzugsweise mit Listen implementiert, da Elemente am Anfang
bzw Ende der Liste leicht entfernt werden können. Felder eignen sich
daher eher weniger zur Implementierung, mit der Ausnahme des
Zirkularpuffers.

Wie kann man Fehlerbehandlung implementieren?


Nach jedem Fkt-aufruf bzw. nach jedem Schritt im Programm, in dem ein
Fehler auftreten kann, sollte auf diese Fehler abgefragt werden. Das
bedeutet, dass zB ein Fehlerzustand weitergegeben werden muss, und der
ursprüngliche Zustand wieder hergestellt werden muss. Das
wiederherstellen eines gültigen Zustands ist meisten Proplemabhänging,
die Weitergabe eines Fehlers erfolgt einheitlich mit Fehlercodes.

typedef enum
{
ERR_NO_ERROR = 0,
....
....
}ERR_Type_t;

Der Aufzählungstyp garantiert, dass auch beim Hinzufügen weiterer


Fehlercodes für jeden Fehler ein eigener Wert definiert wird, da die Werte
aufsteigend, mit 0 beginnend, vergeben werden. Die Namensgebung
erfolgt nach Schema: ERR bedeutet Fehlercode, Feldnamen
aussagekräftig; Kein Fehler wird durch ERR_NO_ERROR angezeigt; ähnliche
Fehlercodes sind zu vermeiden; Weiters sind den Fehlercodes
aussagekräftige Fehlertexte zuzuordnen:

typedef struct
{
ERR_Type_t errNum;
const char *errStr;
}ERR_t;

In einem Feld von Strukturen werden den einzelnen Fehlercodes


Fehlertexte zugeordnet. Die Fehlercodes werden dabei bewusst als
redundante Info bei der Def der Fehlertexte mit in die Struktur ERR_t
aufgenommen, obwohl ein Feld von Zeichenketten genügt hätte. Der
Grund dieses Vorgehens liegt in der Fehleranfälligkeit eines Feldes von
Fehlercodes; Beim Einfügen bzw. Löschen von Fehlercodes könne sonst
leicht Probleme entstehen.

Was ist eine Ringliste?


Bei der Ringliste, die selten zum Einsatz kommt, speichert der letzte
Knoten einen Verweis auf den Anfang der Liste, wodurch eine Rekursion
erwünscht wird. Diese sind aber im Allgemeinen bei Listen unerwünscht,
wird zB ein Element in der Liste gesucht, das nicht da ist, wird das
Verfahren nicht terminieren, da keine Kennung für das Listenende
gefunden wird.

Wie funktioniert Heap-Sort? Wie kommt man beim Heap auf


einen Nachfolger?
Heap-Sort siehe Buch, es wird einfach der Löschalgorithmus
implementiert, bei dem das größte Element immer mit dem Letzten
getauscht wird, aber ohne die Feldlänge zu verringern; Nachfolger = 2i
und 2i+1

Schlüssel
Unter einem Schlüssel wird ein Kriterium verstanden, mit dem ein Element
in einem Baum eingefügt oder darin gesucht werden kann. Der Schlüssel
kann entweder direkt der Datensatz oder ein Attribut des Datensatzes
sein. Er kann auch mittels einer Funktion aus einem Datensatz errechnet
werden.

Erkläre die Funktion „assert“


Die Funktion assert erwartet einen Paramter. Ist der Paramter gleich 0 gibt
assert eine
Fehlermeldung aus und beendet das Programm durch „abort“. Die
Fehlermeldung enthält den Namen der Quelltextdatei sowie die
Zeilennummer wo der Abbruch stattgefunden hat.
Erkläre die Funktion „abort()“
Im Gegensatz zur Funktion „exit“ beendet die Funktion abort() das
Programm durch
„Abbruch“, dabei wird das Signal SIGABRT an den eigenen Prozess
gesandt, wodurch die
Signalbehandlung für dieses Signal ausgelöst wird. Die Funktion erwartet
kein Argument.

Sortierverfahren "Maximumsuche"

Integrieren von Funktionen?