Rekursion in der Programmierung

Die Rekursion und Iteration sind zwei konkurrierende Art und Weisen der Wiederholung eines Vorgangs in der Programmierung.
Während die Iteration eine Menge an Vorgängen (typischerweise eingerückt in eine Schleife) wiederholt und ggf. die Anzahl der Wiederholungen zählt (For-Schleife), ist die Rekursion ein Funktionsaufruf der selben Funktion – d.h. eine Funktion ruft sich selbst auf.

I.d.R. wird Rekursion für Suchalgorithmen verwendet. Die rekursiv arbeitende Funktion wird dabei so oft von sich selbst aufgerufen, bis ein Suchmuster gefunden wurde oder die Suche aus anderen Gründen endet.
Genauso wie bei der Iteration, ist auch bei der Rekursion darauf zu achten, dass es eine Abbruchbedingung gibt – Sonst droht eine Endlosschleife.

Die Rekursion hat gegenüber der Iteration einige Nachteile. Die Rekursion ist langsamer, da bei jedem rekursiven Schritt eine Methode aufgerufen wird. Aus diesem Grund belegt die Rekursion außerdem mehr Speicherplatz.

Dennoch haben sich rekursive Methoden in bestimmten Bereichen durchgesetzt. Z.B. als eleganter Lösungsweg, um ein mathematisches Problem zu lösen. Auch bei der Suche in Binären Bäumen hat sich die Rekursion durchgesetzt, da über eine iterative Vorgehensweise sehr viele Schleifen notwendig wären.

Die Rekursion erleichtert die Nachvollziehbarkeit von komplexen Suchalgorithmen, für Neulinge ist die Rekursion jedoch anfangs nur schwer vorstellbar, jedoch auch nur eine Sache der Gewohnheit. Aber auch für geübte Programmierer, können besonders verschachtelte, rekursive Methoden schnell sehr verwirrend sein.

→ WEITERLESEN

Wertetyp vs Refernztyp

In der prozeduralen und objektorientierten Programmierung wird zwischen Wertetypen und Referenztypen unterschieden.
Beispielsweise sind in den Programmiersprachen C# und Java primitive Datentypen, Enumeratoren und Strukturen Wertetypen. Klassenobjekte sind hingegen Referenztypen.
In C/C++ dienen Pointer (Zeiger) als Refernztyp, welche die Adresse speichert, die auf einen veränderbaren Wert verweist. Über den Pointer kann die Adresse dann von Funktion zu Funktion überreicht werden.
So kann aus jeder Funktion heraus, die den Zeiger zur Verfügung hat, der Wert verändert werden, auf den die Adresse zeigt. Der geänderte Wert ist über diese Adresse wieder abrufbar, von allen Funktionen aus, die die Refernz bekommen haben. Ein Zeiger in C ist also eine Referenz.

Ähnlich verhält es sich bei Klassenobjekten in C# und Java. Für diese wird ein Speicherplatz im Heap des Arbeitsspeichers angefordert, welcher sich über die Speicheradresse ansprechen und verändern (bzw. überschreiben) lässt. Auch die Klassenobjekte in C++ werden im Heap gespeichert.
Der Heap kann beliebig angesprochen werden, jedoch muss der Speicher bei Bedarf geordert und spätestens bei Programmende wieder freigegeben werden.
In C++ muss sich darum der Programmierer selbst kümmern; C#, Java und die meisten objektorientierten Skriptsprachen haben hierfür eine automatische Speicherbereinigung (Garbage Collector).

Wertetypen werden nicht über eine Speicheradresse an eine Funktion übergeben, sondern als Wert. Diese Werte werden kopiert und auf den Stack im Arbeitsspeicher gelegt.
Je mehr die Funktionsaufrufe verschachtelt sind, um so größer wird auch der Stack.
Diese Vorgehensweise ist durchaus sinnvoll, denn auf den Stack kann zwar nicht beliebig, sondern nur über das aktuellste (oberste) Stack-Element zugegriffen werden, da jedoch auch vor allem die
Funktionsvariablen (Parameter) benötigt werden, erfolgt der Zugriff daher i.d.R. sehr schnell.
Bei jeder Parameterübergabe mit Wertetypen werden die Werte daher kopiert und der Stack wächst. Die Werte sind (zumindest noch bei Funktionsaufruf) redundant auf dem Stack.
Werden die Parameter in der Funktion verändert, hat dies keine Auswirkungen auf die Variablen, welche außerhalb der Funktion als Parameter übergeben wurden.

In einigen Fällen ist es vom Programmierer erwünscht, dass auch Wertetypen innerhalb der Funktion geändert werden können. Wertetypen können als Referenz übergeben werden. Dann sind diese in C/C++ als Pointer zu übergeben, in C# und Java mit dem Schlüsselwort ref.

Ein Beispiel-Programm mit Funktionsaufrufen und Parameterübergaben als Werte- und Referenztyp:

[csharp] class Program
{
static void Main(string[] args)
{
Program program = new Program();
}

public Program()
{
int a = 45;
int b = 34;

this.Sum(a, b);
Console.WriteLine(„a = “ + a.ToString() + „; b = “ + b.ToString());

this.Sum2(ref a, ref b);
Console.WriteLine(„a = “ + a.ToString() + „; b = “ + b.ToString());
}

// Normalfall: By Value – die Variable a kann nicht überschrieben werden
private void Sum(int param1, int param2)
{
param1 = param1 + param2;
}

// Sonderfall: By Reference – die Variable a wird überschrieben
private void Sum2(ref int param1, ref int param2)
{
param1 = param1 + param2;
}
}
[/csharp]

Ausgabe:

a = 45; b = 34
a = 79; b = 34

Die Ausgabe des Programms zeigt, dass nur die Methode mit der als Referenz übergebenen Parameter die Variablen im Konstruktor verändert.

Alternativ zum Schlüsselwort ref kann auch das Schlüsselwort out verwendet werden.
Während ein mit ref bezeichneter Parameter vor Paramterübergabe definitiv initialisiert werden muss damit er verwendet werden kann, initialisiert sich ein mit out bezeichneter Parameter bei Methodenaufruf selber und muss daher nicht im Vorfeld initialisiert worden sein.
Hiervon abgesehen besteht kein wesentlicher Unterschied zwischen den Schlüsselwörtern ref und out, daher kann auch keine Überladung mit Unterscheidung dieser beiden Schlüsselwörter geschehen.

Boxing / Unboxing

Eine Konvertierung eines Wertetyps zu einem Referenztyp wird als Boxing bezeichnet. Unboxing ist die entgegengesetzte Konvertierung, also einer Referenz zu einem Wert. Dabei ist in C# und Java i.d.R. explizites Casting notwendig (Vorbestimmung des Datentyps zwischen einfachen Klammern).

[csharp] int a = 1548; // Wertetyp
object o = a; // boxing
int b = (int)o; // unboxing (mit Casting)
[/csharp]

Queue Datenstruktur in C#

Eine Queue (Warteschlange) ist eine First-in-First-out-(FIFO) Datenstruktur.
Die Datenstruktur Queue ist ein Stapel, auf welchem nach und nach Daten abgelegt werden.
Allerdings wird auf Elemente der Queue nur von unten zugegriffen. Das unterste Element ist das mit höchster Priorität, nur auf dieses als erstes hinzugefügte Element kann zugegriffen werden.

Stack

Ein Pointer (Zeiger) zeigt intern immer auf das Element, welches (relativ zu den anderen Elementen) als erstes in die Queue aufgenommen wurde.
Ein einfaches „Lösche aus der Queue“ gibt es nicht, ein Element kann aus der Queue mit Peek() herausgelesen und mit Dequeue() herausgelesen und zugleich entfernt werden.
An das Warteschlangenende angereiht werden Elemente mit Enqueue();

Funktionsweise einer Queue an Hand eines praktischen Beispiels:

Stellen Sie sich vor, Sie müssten eine Software für ein Theater schreiben. Die Software soll Ticket-Bestellungen für
Vorführungen bearbeiten. Nun sind die Vorstellungen aber sehr schnell ausgebucht, so dass viele Tickets nicht mehr ausgestellt werden können. Bei der Ticket-Bestellung gilt jedoch „wer zu erst kommt, mal zu erst“.
Die Daten aller eingehenden Bestellungen legen Sie in eine Queue (Warteschlange). Die Daten in der Queue werden vom ersten bis zum letzten Eintrag abgearbeitet, bis die Queue vollständig abgearbeitet wurde oder keine Plätze mehr verfügbar sind.

→ WEITERLESEN

Kategorien C#

Stack Datenstruktur in C#

Ein Stack (Stapel) ist eine Last-in-First-out-(LIFO) Datenstruktur.
Die Datenstruktur Stack sollte dann verwendet werden, wenn Daten gesammelt werden, jedoch immer nur ein Datenelement aktuell bearbeitet werden darf.

Das aktuelle Element ist dann das einzige, auf welches zugegriffen werden kann. Alle anderen Elemente bleiben im
Hintergrund und werden erst (und dann auch jeweils einzeln) bearbeitet, wenn das aktuelle Element abgearbeitet und vom Stapel entfernt wurde.

Ein Pointer (Zeiger) zeigt intern immer auf das Element, welches (relativ zu den anderen Elementen) als letztes auf den Stapel gelegt wurde.
Ein einfaches „Lösche aus dem Stack“ gibt es nicht, ein Element kann aus dem Stack mit Peek() herausgelesen und mit Pop() herausgelesen und zugleich entfernt werden.
Ein Element kann mit Push() auf den Stack gelegt werden.

Stack

Die Funktionsweise des Stack an Hand eines praktischen Beispiels:
Stellen Sie sich einen Stapel Teller vor, welchen sie abwaschen möchten. Sie würden sicherlich beim obersten Teller anfangen, diesen vom Stapel nehmen und abwaschen. Erst dann können Sie den nächsten Teller vom Stapel nehmen.

→ WEITERLESEN

Kategorien C#

Hashtable in C#

Hashtables (Hash-Tabellen) sind Datenstrukturen, die den Arrays sehr ähnlich sind.
Hashtables können in C# ähnlich wie eine ArrayList verwendet werden, der Zugriff erfolgt über einen Index.
Der Index ist ein spezieller Key, welcher eine Zahl (z.B. vom Typ Float oder Integer) genauso wie ein String sein darf.

[csharp] // Beispiele, Syntax: hashtablename[key] = value;

ht_pass[32.3] = „juhu“;
ht_pass[5] = „tobias“;
ht_pass[„zoo“] = 45.3;
[/csharp]

Wozu werden Hashtables benötigt?

Hashtables sind sinnvoll, wenn sich Listen bzw. Arrays verknüpfen bzw. verschachteln lassen. So kann der Wert eines Listenfeldes der Schlüssel zu einem Wert eines anderen Feldes sein. Zwar ließe sich das auch mit einem normalen Array
(und somit Integer-Werten als Index) realisieren, dann wären jedoch einige zusätzliche Listen/Tabellen nötig, um von einer
Zahl auf einen speziellen Schlüssel und von diesem Schlüssel wieder auf eine anderen Zahlenschlüssel zu verweisen.

C# bietet natürlich einen gewissen Komfort bei Benutzung von Hashtables (wie bei vielen anderen Dingen auch). In der Programmiersprache C haben Programmierer eigene Hash-Funktionen entwickelt, um z.B. einen Text als Zahl umzuschlüsseln (z.B. „Hallo“ -> „2342424“, „Schule“ -> „792942“ usw.), um diesen dann als Index (Schlüssel) für ein Array zu benuten. Vorteil: Um einen Wert zu suchen, musste nicht unbedingt das ganze Array durchlaufen und im
schlimmsten Falle alle Felder iteriert werden. Man musste sich nur die Schlüssel für bestimmte Werte merken, diese in eine Zahl konvertieren, auf den Wert im Feld mit dem Schlüssel zugreifen und es auslesen. Daraus ergibt sich ein sehr großer Geschwindigkeitsvorteil.
Der Nachteil liegt in der Größe des Arrays, dieses muss nämlich so groß sein bzw. so einen hohen Index haben, wie eine Zahl, die aus einem Text resultiert, werden kann. Außerdem könnte es vorkommen, dass Werte nicht abgespeichert werden können, da zwei oder mehrere unterschiedliche Texte trotz ausgereiften Algorithmus den selben Index ergeben. Ausweichmöglichkeiten sind zwar vorhanden (den Index verschieben, bis ein freies Feld gefunden wurde), macht die Sache jedoch trotzdem nicht viel unproblematischer.

Die Hashtables in C# sind weitaus dynamischer, denn intern ist ein Hahstable auch nur so groß, wie es notwendig ist. Der Zugriff ist jedoch absolut schnell, konstant schnell! Unabhängig von der Größe des Hashtable ist die Zugriffszeit konstant.

→ WEITERLESEN

Kategorien C#

Datenbankmanagementsystem (DBMS)

Die Datenmenge einer Datenbank ist nicht nur oft sehr groß, sondern auch verteilt. Die Daten sind in verschiedenen Containern (Tabellen) vorliegend und möglicherweise sogar auf verschiedene Rechner oder gar Netzwerke verteilt. Die Daten werden von mehreren Benutzern aufgerufen, bearbeitet oder gelöscht.

Ein Datenbankmanagementsystem, kurz DBMS oder auch Datenbankverwaltungssystem, organisiert die Datenbankzugriffe (Transaktionen) und verhindert so Probleme, die bei der Arbeit mit Datenbanken auftreten können.

Darstellungsprinzip

Eine Datenbank ist eine Sammlung an Daten, abstrahiert in mehreren Dateien. Datenbanken sind abstrakt und für den Menschen nur schwer, am besten aber bildlich vorstellbar. Ein DBMS stellt die Datenbank durch Objekte dar, es zeigt die Beziehungen der Daten zueinander und die Daten lassen sich auch in der Objektdarstellung bearbeiten.

Schnittstellenprinzip

Ein DBMS trennt im Grunde die Datenbank-Benutzer von der Datenbank. Die Benutzer sprechen die Datenbankgrundsätzlich über das DBMS an, so dass keine Transaktion nicht von der DBMS z.B. auf Validität (Gültigkeit der eingegebenen Daten) geprüft wird.

Des weiteren lassen sich mit einem DBMS häufig Datenbank- und Speichereinstellungen (z.B. in welcher Kodierung Texte abgespeichert werden sollen) benutzerfreundlich und ohne Datenbankabfragesprachen (z.B. SQL) einstellen. Die Benutzer werden daher weitgehenst von Programmiersyntax verschont und verwalten die Daten über ergonomische Benutzeroberflächen.

Sicherheit und Zugriffsbeschränkung

In einem Unternehmen, welches eine Datenbank für Kunden- und Zulieferdaten hat, ist es mit Sicherheit erwünscht, dass nicht jeder Benutzer Zugriff auf die gesamten Daten hat und diese beliebig verändern kann. Beispielsweise sollte ein Mitarbeiter der Abteilung „Einkauf“ nicht die Rechnungsdaten der Endkunden verändern, vielleicht nicht einmal einsehen, dürfen. Die Rechnungsdaten dürfen nur die Mitarbeiter der Abteilung „Verkauf/Vertrieb“ bearbeiten. Die Geschäftsführung soll aber alle Daten einsehen und verändern dürfen. Vielleicht gibt es auch Zulieferer oder Endkunden, die bestimmte Daten aus der Datenbank lesen oder sogar verändern dürfen.

Ein DBMS verwaltet Zugriffsrechte, die von den Datenbank-Administratoren eingerichtet werden. So müssen nicht alle Daten der gesamten Belegschaft oder Dritten anvertraut werden müssen. Da es diesbezüglich sehr viele strenge Rechtsvorschriften gibt, ist ein DBMS mit ausgereiftem Rechtevergabesystem sogar Pflicht!

Ein weiterer Punkt ist die Nachvollziehbarkeit von Datenzugriffen. So kann nachvollzogen werden, welcher Benutzer wann und welche Daten angelegt, verändert oder gelöscht hat.

Inkonsistenz bei Redundanz

Datenbanken sollten grundsätzlich keine redundanten Daten enthalten. Bei Datenbanken, die besonders schnell sein müssen, können diese jedoch in Kauf genommen worden sein. Oder die Datenbank ist derart verteilt oder gesplittet, dass benötigte Daten an einem Zugangspunkt nicht immer zur Verfügung stehen (z.B. im zeitweisen Offline-Betrieb) und die Daten daher zusätzlich lokal angelegt werden und somit datenbankglobal redundant sind.

Bei solchen Redundanzen muss das DBMS eine Inkonsistenz verhindern oder ggf. nachträglich korrigieren, so dass Datensätze mit gleichen Inhalt immer abgeglichen werden.

Schutz vor Datenverlust

Sollte der Schreibzugriff auf Daten in der Datenbank fehlgeschlagen sein, droht – abhängig von der Datenbank – Datenverlust. Auch andere Gründe, z.B. ein Update, Upgrade oder ein Hardwareausfall, können zum Datenverlust führen. Ein gutes DBMS sorgt nicht nur für ein benutzerfreundliches Backup (Datensicherung), sondern prüft Backups auch auf Aktualität und führt nötigenfalls periodenweise oder unmittelbar vor einem kritischen Zugriff in Eigenregie ein Backup aus.

Mehrbenutzer-Probleme

Daten werden in der Regel von mehreren Benutzern verwaltet. Auf die Kundendaten können z.B. die Mitarbeiter der Rechnungs-, Support-, Vertriebs- und der Reklamationsabteilung zugreifen. Kritisch werden die Transaktionen, wenn mehrere Benutzer zeitgleich die selben Daten bearbeiten wollen.

Ein DBMS hat zur Aufgabe, Daten zu sperren, die in Bearbeitung sind und idealerweise Datenänderungen zwischenzuspeichern, abzugleichen und zu speichern.

Gewährleistung der Integrität

Ein DBMS kontrolliert bei Datenzugriff die Datenintegrität. So kann z.B. kein neuer Rechnungsdatensatz angelegt werden, ohne eine Kundennummer. Wird bei Eintragung von Rechnungsdaten die Kundennummer ausgelassen, wird der Datensatz nicht in die Datenbank gespeichert. Wird eine Kundennummer angegeben, prüft das DBMS, ob die Kundennummer (und damit auch der Kundendatensatz) existiert. Existiert kein Kunde mit der angegebenen Kundennummer, kommt wiederum eine Fehlermeldung durch das DBMS. So können nur Datensätze in die Datenbank gelangen, die komplett in einem Zusammenhang einzubringen sind. Eine Rechnungsdatensatz ohne Kunde ist ein wertloser Datensatz.
Umgekehrt soll auch kein Kundendatensatz gelöscht werden, welcher noch offene Rechnungen hat. Auch diese Prüfung wird vom DBMS durchgeführt und ist sehr wichtig, um die Integrität der Daten in der Datenbank zu gewährleisten.

Datenbanken – Hohe Priorität für die Zukunft

Experten sprechen heute von der Generation der Informationsgesellschaft, die mit den Heim-PC, den mobilen Endgräten und der Großrechnern vieler Firmenzentralen, vor ungefähr 3 Jahrzehnten ihren Aufbruch fand. Vor zwei Jahrzehnten waren die gespeicherten, persönlichen Daten einer Person noch recht überschaubar. Heute und in Zukunft ist eher von einer Informationsflucht zu reden. Das produzierende Gewerbe, Banken, Versicherungen, Möbelgeschäfte, Mietwagengesellschaften, Schulen, Vermieter, Behörden, Berater, Ärzte, Anwälte….. und noch viele Andere speichern und verwalten die Daten ihrer Kunden/Klienten/Patienten/Geschäftspartner.

Vor Jahrzehnten wurden z.B. die Versicherten-Daten der Krankenkarten auf Kärtchen im Karteikartensystem festgehalten. Dieses Karteikartensystem könnte bereits als Datenbank bezeichnet werden. Heute wird in jeder Krankenkasse mit digitalen Datenbanken eine weit größere Datenmenge verwaltet. Dabei haben die digitalen Datenbanksysteme die heutige Datenmenge erst möglich gemacht. Die Datenmenge wird zunehmend größer, die Datentechnologien leistungsfähiger und die dazugehörigen Rechtsvorschriften immer vielfältiger.
Kein mittelgroßes bis großes Unternehmen kann heutzutage noch auf ein großes Datenbanksystem verzichten. Die Datenbank alleine reicht jedoch noch lange nicht. Die Wirtschaft und Verwaltung benötigt gute Datentechnologien, ausgefeilte Datenbankverwaltungssysteme und -natürlich- auch qualifizierte Datenbank-Spezialisten.

Datenbanksysteme sind jedoch nicht nur für die Datenbank-Spezialisten interessant. Neben den meisten Wirtschaftsinformatikern werden auch viele Wirtschaftsingenieure nebenbei mit Datenbanken konfrontiert werden. Nicht umsonst ist die Lehre über Datenbanken und dem Datenbankmanagement fester Bestandteil in den Lehrplänen der meisten Hochschulen und oft auch für Studenten der Betriebswirtschaftslehre oder eines Ingenieurstudiengangs vorgesehen.

Events in C#

Oft ist es notwendig, dass ein Quellcode ausgeführt wird, wenn ein Button geklickt, ein Programm gestartet oder ein Status erreicht wurde. In vielen Fällen liegt es hierbei an „fremden“ Objekten, dass etwas passiert ist, dann soll jedoch entsprechend darauf reagiert werden. Eine Nachricht muss von diesem „fremden“ Objekt empfangen werden, wenn das entsprechende Ereignis eintritt. Dieses Ereignis/Event ist der springende Punkt, es kann vom eigenen Programmcode (im Algorithmus oder durch Interaktion mit Benutzer oder Netzwerk) oder auch vom Betriebssystem ausgehen.

Das Betriebssystem und das Framework stellen sehr viele vorgefertigte Events zur Verfügung, welche nur abonniert werden müssen. Bei der Arbeit mit Windows.Forms wird das Ereignis des Button.Click beispielsweise häufig verwendet. Dieses wird z.B. von einem Button button1 wie folgt von der eigenen Klasse (welche auf das Ereignis reagieren soll) abonniert:

[csharp]

this.button1.Click += new System.EventHandler(this.button1_Click);

[/csharp]

Dabei wird eine Methode einem Delegate (Funktionszeiger) zugewiesen. Diese Methode heißt im Beispiel button1_Click, sie soll ausgeführt werden, wenn der button1 geklickt wird.

[csharp] private void button1_Click(object sender, EventArgs e)
{
\\ tu etwas
}
[/csharp]

Die Methode ist die Event-Reaktion. Wird das Event ausgeführt, also der Button geklickt, führt ein Funktionszeiger, der auf diese Methode zeigt, diese aus. Die Methode hat zwei Parameter (Pflichtparameter bei vom Framework vordefinierten Events), auf welche später weiter eingegangen wird.

Eventhandling

Tritt ein Objekt ein Event los, werden alle abonnierenden Objekte benachrichtigt. Bei Button.Click ist der Button das publizierende Objekt. Die Abonnenten können nach der Benachrichtigung, dass ein Event eingetreten ist, entsprechend reagieren. Wie die empfangenden Objekte auf das Ereignis reagieren, interessiert den Event-Publizierer nicht. Dabei wird eine 1-zu-n Beziehung angewandt, denn ein Publizierer kann sehr viele Abonnenten benachrichten.

→ WEITERLESEN

Kategorien C#

Delegate in C#

C# ist eine objektorientierte Programmiersprache, welche unsichere Elemente, wie etwa Zeiger (Pointer), grundsätzlich vermeidet. Dennoch sind Zeiger nicht nur in Augen von C/C++-Entwicklern in einigen Fällen einfach notwendig, so z.B. die Funktionszeiger.

Delegates sind Funktionszeiger in C#. Sie zeigen auf mindestens eine Funktion bzw. Methode. Benutzt wird ein Delegate in C# zum Beispiel für Events.

Ein Delegate wird in C# nach folgendem Schema definiert:

[Öffentlichkeitsparameter] delegate Rückgabewert Delegatename ([Methodenparametertyp Methodenparametername] );

Das Delegate weiß bereits im Vorfeld über die Parameter und dem Rückgabewert der Methode(n) bescheid, auf die es später verweisen soll.

Im nachfolgendem Beispiel ist ein Delegate in einer Klasse definiert, welches…
[csharp]

public delegate string FPointer (int index);

[/csharp]

→ WEITERLESEN

Kategorien C#