23.08.2013 Aufrufe

Delphi-Crashkurs - Ernst-Reuter-Schule 1

Delphi-Crashkurs - Ernst-Reuter-Schule 1

Delphi-Crashkurs - Ernst-Reuter-Schule 1

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

dsdt dsdt.info dsdt<br />

Warum <strong>Delphi</strong>?<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 1/81<br />

<strong>Delphi</strong> vereint mehrere Vorteile in einem Programm. Dabei ist einer der Vorteile, die<br />

sofort ins Auge fallen, <strong>Delphi</strong>s unglaublich ausgeklügelte RAD-Umgebung (RAD steht für<br />

„Rapid Application Development“). Damit ist es möglich, innerhalb kürzester Zeit das<br />

Äußere eines Programmes und auch einige der Abläufe zu erstellen. Das System arbeitet<br />

dabei grafisch und als WYSIWYG. Die Entwicklungszeit von Programmen wird durch die<br />

sehr ausgereifte Umgebung dabei minimiert.<br />

Ein weiterer Vorteil von <strong>Delphi</strong> ist seine Sprache. Das frühere Object Pascal, welches<br />

inzwischen in <strong>Delphi</strong> Language umbenannt wurde, ist für Anfänger aufgrund seiner klaren<br />

Strukturen und einprägsamen Befehlen sehr leicht zu lernen, bietet Fortgeschrittenen und<br />

Profis jedoch eine Vielzahl an Möglichkeiten. Dabei bietet <strong>Delphi</strong> einen sehr schnellen<br />

Compiler an. <strong>Delphi</strong> Language ist dabei eine Sprache, welche die besten Möglichkeiten zur<br />

objektorientierten Programmierung (demnächst kurz: OOP) bietet.<br />

Um nicht nur die vorgegebenen Komponenten zu nutzen, ist es in <strong>Delphi</strong> möglich,<br />

eigene Komponenten zu entwickeln und sie dem Repertoire der vorgegeben Komponeten<br />

hinzuzufügen. Aber selbstverständlich ist dies nicht nur mit eigenen Komponenten möglich,<br />

sondern Sie können auch die Komponenten anderer Programmierer in Ihre Programme<br />

einbinden.<br />

Last but not least bietet <strong>Delphi</strong> ein sehr gutes Hilfesystem. Dies besteht zum einen aus<br />

einer kontextbezogenen Hilfe, die es Ihnen erlaubt, zu jedem <strong>Delphi</strong>befehl, den sie mit dem<br />

Cursor markieren, einen Hilfetext anzuzeigen. Zum anderen bietet <strong>Delphi</strong> die Möglichkeit,<br />

dass während Sie einen Quelltext schreiben, eine Auswahl von zum Objekt gehörigen<br />

Methoden und Eigenschaften angezeigt wird.


dsdt dsdt.info dsdt<br />

Die RAD-Umgebung<br />

Die Vorstellung<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 2/81<br />

Wenn Sie <strong>Delphi</strong> (in den Versionen bis <strong>Delphi</strong> 7) das erste Mal starten, werden Sie ein<br />

Bild vor sich sehen, welches in etwa so aussehen sollte. Zwischen den Versionen bis zur<br />

Siebten gibt es da nur geringe Unterschiede:<br />

Dies ist die RAD-Umgebung, das Aushängeschild von <strong>Delphi</strong>, wenn man so will. Was<br />

nicht bedeuten soll, dass dies das Wichtigste an <strong>Delphi</strong> ist, aber es ist auch nicht ganz<br />

unwichtig und außerdem das, was die Leute von <strong>Delphi</strong> wirklich sehen.<br />

Damit Sie einen Eindruck von der Umgebung erhalten, werde ich nun kurz die<br />

einzelnen Bereiche ansprechen. Wie man mit diesem Bereich arbeitet, wird jedoch erst im<br />

Laufe des <strong>Crashkurs</strong>es deutlich werden. Hier soll nur ein kurzer Überblick gegeben werden.<br />

1. Dies ist eine Form. Forms (englisch) sind die Fenster, welche der Nutzer später im<br />

Programm zu sehen bekommt. In der RAD-Umgebung können Sie diese Forms so


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 3/81<br />

gestalten, wie sie hinterher im Programm aussehen sollen. So zu sagen WYSIWYG<br />

für Programmierer. Beim Start eines neuen Projektes ist immer schon eine Form<br />

vorhanden, es können beliebig viele hinzugefügt werden.<br />

2. Die Funktionen dieses Fensters mit dem Titel ʺObjekt Hierarchieʺ wird erst später<br />

deutlich werden. Hier sei nur kurz gesagt, dass ein Button (<strong>Delphi</strong>-Begriff für einen<br />

normalen Windows-Schalter) z.B. zu einer Form gehört, wenn er dort platziert wird,<br />

in der Hierarchie also eine Stufe darunter erscheint.<br />

3. Der Objektinspektor ist einer der wichtigsten Bestandteile der RAD-Umgebung. Mit<br />

ihm können Sie die Eigenschaften eines jeden Objektes, welches Sie auf der Form<br />

platzieren bearbeiten. So zum Beispiel die Farbe oder auch die Schriftart eines Edit-<br />

Feldes (<strong>Delphi</strong>-Begriff für ein einzeiliges Windows-Eingabefeld). Auch die<br />

Eigenschaften der Form selbst können darüber geändert werden, wie z.B. die Größe<br />

und die Hintergrundfarbe.<br />

4. Die Komponentenpalette beinhaltet das Repertoire an Komponenten, welche <strong>Delphi</strong><br />

Ihnen bietet, um Ihre Anwendung zu gestalten. Aus dieser Komponentenpalette<br />

wählen Sie z.B. ein Edit-Feld oder einen Button aus um ihn auf der Form zu<br />

platzieren. Die Palette kann von Ihnen durch fremde oder eigene, neue Komponenten<br />

ergänzt werden.<br />

5. In dieser Symbolleiste werden die am meisten verwendeten Aktionen als Symbole<br />

angezeigt. Dazu gehören wie bei jedem Programm das Speichern und Laden von<br />

Dateien, aber auch Dinge, die man in einem normalen Programm nicht findet, wie<br />

das Starten der selbst geschriebenen Anwendung (das macht man mit dem grünen<br />

Pfeil) oder das schrittweise Ausführen. Die genaue Funktionen der Schaltflächen<br />

erfahren Sie über die Hinweise, die wie in jedem anderen Programm auch angezeigt<br />

werden.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 4/81<br />

Ein kleines Programm zur Demonstration<br />

Um Ihnen zu zeigen, wie schnell Sie – selbst ohne sonderliche Programmierkenntnisse<br />

– ein Programm in <strong>Delphi</strong> gestalten können, werde ich in diesem Abschnitt die Erstellung<br />

eines solchen Programmes Schritt für Schritt beschreiben.<br />

Bevor Sie überhaupt daran gehen können, ein Programm in <strong>Delphi</strong> zu gestalten (und<br />

dabei beziehe ich mich vorerst nur auf das Äußere), müssen Sie sich genau überlegen, wie<br />

Ihr Programm aufgebaut sein soll. Es geht fast nie gut, wenn man ʺeinfach so drauf los<br />

klicktʺ und ohne sich vorher ein Konzept zurecht gelegt zu haben, die Komponenten auf der<br />

Form platziert.<br />

Ein Konzept ist immer programmspezifisch. Will heissen: man muss zuerst die<br />

geplanten Funktionen des Programmes kennen und dann ein Konzept entwickeln, welches<br />

dem Benutzer diese Funktionen übersichtlich und effektiv präsentiert bzw. zur Verfügung<br />

stellt. Dabei gibt es keine festen Regeln, sondern jeder Programmierer entwickelt mit der Zeit<br />

seinen eigenen Stil und sammelt auch Erfahrungen durch Feedback von Benutzern oder<br />

Kollegen.<br />

Das Programm, welches in diesem Abschnitt erstellt werden soll, ist so einfach, dass es<br />

wenig Sinn hat, sich dafür ein Konzept zu überlegen. Es soll lediglich demonstrieren, wie<br />

mächtig die RAD-Umgebung von <strong>Delphi</strong> ist. Erstellt werden soll ein Browser für die<br />

Festplatte. Der Nutzer soll den Inhalt aller Verzeichnisse auf seinem Computer anzeigen<br />

können.<br />

Zu kompliziert für den Einstieg? Keineswegs! Mit <strong>Delphi</strong> ist dies in einigen wenigen<br />

Schritten erledigt:<br />

1. Klicken Sie in der Komponentenpalette auf ʺSamplesʺ (oder ʺBeispieleʺ).<br />

2. Machen Sie die Komponente ʺShellComboBoxʺ ausfindig, indem Sie mit dem<br />

Mauszeiger der Reihe nach auf jede in diesem Abschnitt verfügbare Komponente<br />

zeigen und warten, bis das gelbe Hinweisfeld (ʺHintʺ in <strong>Delphi</strong>) erscheint.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 5/81<br />

3. Klicken Sie dann zuerst auf das Symbol der ShellComboBox in der<br />

Komponentenpalette und dann ...<br />

4. ... auf die Stelle auf der Form, wo diese Komponente hin soll: ziemlich weit oben und<br />

zwar zentriert.<br />

5. Machen Sie nun die Komponente ʺShellListViewʺ in derselben Kategorie ausfindig<br />

und platzieren Sie sie unter der ShellComboBox auf der Form.<br />

6. Nun ist die ShellComboBox jedoch schmaler als die ShellListView, das muss noch<br />

geändert werden. Klicken Sie auf die ShellComboBox. An jeder Ecke und an jeder<br />

Seite der Komponente erscheinen nun kleine, schwarze Quadrate. Wenn Sie den<br />

Mauszeiger über eines dieser Quadrate bewegen, so ändert sich dieser in einen<br />

Doppelpfeil. Wenn Sie eines der Quadrate nun anklicken und ziehen, ändern Sie<br />

damit die Größe der Komponente. Sollte Ihnen auch die ShellListView zu klein sein,<br />

so ändern Sie auch deren Größe. Am Ende sollte die ʺKompositionʺ in etwa so<br />

aussehen:<br />

Damit ist das Aussehen des Programmes schon fertig. Und wenn Sie glauben, jetzt<br />

würden Sie einen Quelltext schreiben müssen, dann liegen Sie falsch. Dem Nutzer eine<br />

Möglichkeit zu bieten, die Dateien des Systems anzuzeigen, ist eine so alltägliche Aufgabe,<br />

dass dies in <strong>Delphi</strong> nicht mehr vom Programmierer erledigt werden muss. Er kann sich auf<br />

die wirklichen Herausforderungen konzentrieren.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 6/81<br />

Das einzige, was nun noch eingestellt werden muss, ist die Verbindung zwischen den<br />

beiden Komponenten, damit das in der ShellComboBox ausgewählte Verzeichnis auch in der<br />

ShellListView angezeigt wird. Dies ist Dank des Objektinspektors ebenfalls sehr einfach:<br />

1. Wählen Sie die ShellComboBox aus.<br />

2. Suchen Sie im Objektinspektor die Eigenschaft ShellListView, sie sollte in roter Schrift<br />

dargestellt werden. (Warum das so ist, wird später erklärt.)<br />

3. Klicken Sie in dem Feld, welches neben dem Eigenschaftenamen steht, auf den<br />

kleinen Pfeil nach unten. Eine Liste der verfügbaren ShellListViews angezeigt, in<br />

unserem Fall gibt es natürlich nur eine: ShellListView1. Wählen Sie diese aus.<br />

Damit ist dieser Abschnitt schon fast fertig. Nun müssen Sie das Programm nur noch<br />

ausprobieren. Klicken Sie dafür auf den grünen Pfeil nach rechts, den Sie in der linken<br />

Symbolleiste finden. Das Programm startet, und Sie können die Komponenten, wie Sie es aus<br />

anderen Programmen gewohnt sind, benutzen. Durch Klick auf den grünen Pfeil wird das<br />

Programm kompiliert, also aus dem von Ihnen geschrieben Quelltext ein ausführbares<br />

Programm gemacht (eine ʺnormaleʺ EXE-Datei), welche auch außerhalb von <strong>Delphi</strong> nutzbar<br />

ist! Alternativ zum grünen Pfeil können Sie auch die Taste ʺF9ʺ drücken.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 7/81<br />

Variablen und Variablentypen<br />

Was sind Variablen?<br />

Das Wichtigste in jeder Programmiersprache sind Variablen. Sie versetzen den<br />

Programmierer in die Lage, Informationen im Programm abzuspeichern und später wieder<br />

abzurufen. Die Quellen für diese Informationen sind dabei vielfältig. Oft werden diese<br />

Informationen vom Benutzer (auf einer Vielzahl von Wegen) eingeholt oder aber sie werden<br />

(auf verschiedenste Arten) aus bestehenden Informationen gewonnen.<br />

Doch was sind Variablen? Ihr Kontostand ist eine Variable! Eine Variable ist ein<br />

Platzhalter, der für einen sich ständig verändernden Wert steht. Ihr Kontostand ändert sich<br />

ständig. Und trotzdem wissen Sie genau, welche Zahl ich meine, wenn ich von Ihrem<br />

Kontostand rede. In einem Brief, in dem steht ʺÜberweisen Sie mir die Hälfte Ihres aktuellen<br />

Kontostandes!ʺ werden Sie an Stelle des Platzhalters ʺKontostandʺ den aktuellen Wert<br />

einsetzen.<br />

Genauso arbeiten Variablen. Wenn ich eine Gleichung aufschreibe, wie z.B.<br />

a = b + c<br />

so sind die drei Buchstaben Variablen. Ich kann für ʺbʺ den Wert 2 und für ʺcʺ den<br />

Wert 3 einsetzen und ich erhalte für ʺaʺ den Wert 5. Aber ich kann für ʺbʺ und ʺcʺ genauso<br />

gut zwei andere Werte einsetzen. An der Gleichung ändert das nichts. Die drei Buchstaben<br />

fungieren als Platzhalter für irgendwelche Werte, es sind Variablen.<br />

Was sind Variablentypen?<br />

In <strong>Delphi</strong> kann nicht jede Variable jeden beliebigen Wert annehmen. Eine Variable<br />

kann zum Beispiel immer nur für eine ganze Zahl stehen oder immer nur für einen<br />

Buchstaben. Niemals wird eine Variable für beides stehen können. <strong>Delphi</strong> ist in dieser<br />

Hinsicht sehr strikt und der Programmierer muss bei jeder Variable genau angeben, von<br />

welchem Typ sie sein soll. Es muss also vorher festgelegt werden, ob die Varibale für eine<br />

Zahl, einen Buchstaben oder ein Datum steht. Und das gilt dann für diese Variable immer.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 8/81<br />

Daraus resultierend gibt es eine strikte Trennung des Text-Inhaltes eines Edit-Feldes<br />

und dem Wert den dieser Text eventuell repräsentiert. Wenn in einem Programm die<br />

Aufforderung erscheint, dass der Benutzer seinen aktuellen Kontostand in ein Edit-Feld<br />

eingeben soll, dann ist der Inhalt dieses Edit-Feldes erst einmal eine Zeichenkette und keine<br />

Zahl. Denn prinzipiell kann ja jedes beliebige Zeichen in ein Edit-Feld eingegeben werden.<br />

Um diesen Text aber als Zahl zu verarbeiten, um also damit zu rechnen, braucht man<br />

eine zweite Variable. Denn einen Text kann man nicht multiplizieren und auch sonst keine<br />

Rechenoperationen damit durchführen. Man muss also den Wert, den dieser Text<br />

repräsentiert, herausfinden und in einer Variable speichern, um damit dann zu rechnen.<br />

Aber um das zu demonstieren, muss man erst einmal ein paar Variablentypen<br />

einführen. Grundlegende Aufgaben, die in jedem Programm vorkommen, sind zum einen<br />

das Speichern und Verarbeiten von Zahlen und das Speichern und Verarbeiten von Text<br />

bzw. Zeichenketten. Und die Variablentypen, die für diese Aufgaben verwendet werden,<br />

werden im Folgenden hier vorgestellt.<br />

Die üblichen Verdächtigen (und ein "richtiges" Programm)<br />

Wenn in <strong>Delphi</strong> ein Text gespeichert und verarbeitet werden soll, wird dafür in den<br />

allermeisten Fällen der Variablentyp String verwendet. Dies ist eine Aneinanderreihung von<br />

bis zu 2^31 Zeichen. Diese Zeichen sind beliebig, es kann sich also um Buchstaben, Zahlen,<br />

Leerzeichen oder jedes andere Zeichen, das Sie auf der Tastatur finden (und ein paar mehr)<br />

handeln. Sie können auf einen String sowohl als Zeichenkette zugreifen, als auch jedes<br />

einzelne Zeichen eines Strings verändern. Wie das geht, wird später noch demonstriert.<br />

Bei Zahlen muss man erst einmal entscheiden, ob man eine ganze Zahl oder eine Zahl<br />

mit Nachkommastellen speichern und verarbeiten möchte. Für ersteres verwendet man den<br />

Variablentyp Integer und für letzteres den Variablentyp Real. Für beides gibt es je nach<br />

Anforderung auch noch andere Datentypen, die beiden genannten sind jedoch sehr<br />

gebräuchlich und werden erst einmal ausreichen. Das Speichern von Zahlen ist sehr viel<br />

komplizierter als es auf den ersten Blick aussieht, darauf wird jedoch noch später<br />

eingegangen.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 9/81<br />

Um die Verwendung von Variablen und besonders die Verwendung dieser drei Typen<br />

deutlich zu machen, eignen sich am Besten ein paar Beispiele. Dazu beginnen Sie ein neues<br />

Projekt, dies tun Sie mit ʺDatei -> Neu -> Anwendungʺ.<br />

Platzieren Sie folgende Komponenten (die Sie alle im Abschnitt ʺStandardʺ der<br />

Komponentenpalette finden) auf der Form:<br />

1. zwei Edit-Felder nebeneinander<br />

2. zentriert darunter ein Button<br />

3. unter dem Button ein Label<br />

So sollte es dann aussehen:<br />

Führen Sie nun einen Doppelklick auf den Button aus. Nun sehen Sie ein Fenster vor<br />

sich, in dem Text steht und in dem Sie Text eingeben können. Dies ist der Code-Editor und<br />

hier schreiben Sie Ihre Quelltexte. Was es mit dem Doppelklick auf sich hat und mit dem,<br />

was <strong>Delphi</strong> dort schon geschrieben hat, das wird sich später noch klären.<br />

Was dieses Programm tun soll ist Folgendes: zuerst sollen zwei Zahlen ermittelt<br />

werden, wobei diese in jeweils einer der beiden Edit-Boxen stehen. Aber sie tun dies<br />

natürlich erst einnmal als Text, dieser Text muss dann in die Zahlen umgewandelt werden.<br />

Sind die Zahlen bekannt, so soll die Summe dieser beiden Zahlen berechnet werden<br />

und diese in dem Label ausgegegeben werden. Da diese Ausgabe aber wieder ein Text ist,<br />

muss die berechnete Summe erst wieder in einen Text umgewandelt werden.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 10/81<br />

Folgender Quelltext tut dies:<br />

procedure TForm1.Button1Click(Sender: TObject);<br />

var zahl1, zahl2, summe : Integer;<br />

begin<br />

zahl1 := StrToInt(edit1.text);<br />

zahl2 := StrToInt(edit2.text);<br />

summe := zahl1+zahl2;<br />

label1.caption := IntToStr(summe);<br />

end;<br />

Bitte beachten Sie, dass die erste, die dritte und die letzte Zeile bereits von <strong>Delphi</strong><br />

generiert wurden, also nicht von Ihnen eingegeben werden müssen. Nachdem Sie diesen<br />

Quelltext in Ihrem Programm haben, wird nun geklärt, was im einzelnen gemacht wird.<br />

Die erste Zeile, welche einer Erklärung bedarf, ist die zweite Zeile. Die erste wurde von<br />

<strong>Delphi</strong> generiert und hat also nichts mit unserem speziellen Problem zu tun. Sie ist<br />

allgemeiner und wird später erklärt. Also, die zweite Zeile:<br />

var zahl1, zahl2, summe : Integer;<br />

Hier wird aufgelistet, welche Variablen im restlichen Quelltext verwendet werden.<br />

Damit <strong>Delphi</strong> weiß, dass eine solche Auflistung folgt, wird sie mit dem Schlüsselwort var<br />

eingeleitet. Selbst dann, wenn die Auflistung über mehr als eine Zeile gehen sollte, wird das<br />

Schlüsselwort nur einmal geschrieben.<br />

Schlüsselwörter sind Wörter, welche in der Programmiersprache an sich bereits eine<br />

Bedeutung besitzen. Sie dürfen vom Programmierer im Allgemeinen nicht in anderer<br />

Bedeutung verwendet werden. Es gibt ein paar Wörter, die nur in einem bestimmten<br />

Kontext Schlüsselwörter sind, welche an anderer Stelle dann auch vom Programmierer<br />

verwendet werden dürfen. Schlüsselwörter werden von <strong>Delphi</strong> fett angezeigt.<br />

Nach dem Schlüsselwort ʺvarʺ folgen die Namen der Variablen, getrennt durch<br />

Kommata. Gleichzeitig muss der Typ der Variablen angegeben werden, dies wird durch<br />

einen Doppelpunkt und den Namen des Types gemacht. Jede Anweisung in <strong>Delphi</strong> wird mit<br />

einem Semikolon abgeschlossen, so auch diese Aufzählung.<br />

Noch ein paar Worte zu den Namen von Variablen: Sie sollten die Namen von<br />

Variablen immer so wählen, dass Sie genau erkennen können, wozu diese verwendet


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 11/81<br />

werden. Dabei sollten Sie sich nicht vor langen Namen scheuen, jedoch können sinnvolle<br />

Abkürzungen oft durchaus zu mehr Übersichtlichkeit führen.<br />

Die Möglichkeiten der Namensgebung für Variablen in <strong>Delphi</strong> sind groß, aber sie sind<br />

nicht unbeschränkt. So dürfen Variablennamen beispielsweise nicht mit einer Zahl beginnen,<br />

keine Leerzeichen enthalten und auch nicht mit Ausdrücken der Sprache identisch sein.<br />

Übrigens sind auch keine Umlaute oder andere Sonderzeichen erlaubt! Diese Einschränkung<br />

schließt <strong>Delphi</strong> 8, aber nicht <strong>Delphi</strong> 2005 mit ein. Dort gelten andere Regeln, auf die ich hier<br />

nicht eingehen möchte.<br />

Nun zu den nächsten beiden Zeilen, von denen ich aber nur eine beschreiben werde,<br />

weil die andere praktisch identisch ist:<br />

zahl1 := StrToInt(edit1.text);<br />

Der Doppelpunkt mit einem anschließenden Gleichheitszeichen ist der<br />

Zuweisungsoperator in <strong>Delphi</strong>. Dies sollte nicht mit eine Gleichung verwechselt werden.<br />

Nach Anwendung eines Zuweisungsoperators, hat die linke Seite den gleichen Wert wie die<br />

rechte Seite. In Worten hieße diese Anweisung also ʺNimm das, was auf der rechten Seite<br />

steht, und schreibe es in die linke Seite.ʺ. Eine Gleichung wäre in Worten eher ʺVergleiche<br />

die linke und die rechte Seite.ʺ<br />

Die linke Seite dieser Zuweisung ist bereits beschrieben worden: es handelt sich um die<br />

Integervariable ʺzahl1ʺ, also eine Variable, die nur ganze Zahlen speichert. Doch was ist die<br />

rechte Seite? Man kann die rechte Seite mal anders aufschreiben:<br />

STRing TO INTeger (edit1.text);<br />

Dies ist die Umwandlung eines Textes in eine Zahl, die ja schon vorher beschrieben<br />

wurde. StrToInt ist eine so genannte Funktion. Wie die genau funktionieren kommt später,<br />

hier sei nur gesagt, dass man in eine Funktion Werte hineinsteckt, diese irgendwie<br />

verarbeitet werden und die Funktion einen Wert zurückgibt.<br />

Bei StrToInt stecken wir den in Edit1 enthaltenen Text hinein. Diesen erreichen wir<br />

über den Befehl ʺedit1.textʺ (dazu mehr im Abschnitt über OOP). StrToInt verarbeitet diesen<br />

Text und gibt dann einen Integer zurück. Dabei wird die Funktion StrToInt zu einem


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 12/81<br />

Platzhalter für ihr Funktionsergebnis, wie eine Variable zum Platzhalter für ihren Wert wird.<br />

Am Ende beinhaltet zahl1 den Wert, den der Text in Edit1 repräsentiert.<br />

Mit ʺzahl2ʺ funktioniert das genauso, es folgt nun also die Zeile mit der Addition. Die<br />

braucht wohl nicht ausführlich erklärt werden; die Werte von ʺzahl1ʺ und ʺzahl2ʺ werden<br />

addiert und der Variable ʺsummeʺ zugewiesen, also in ihr gespeichert.<br />

Das Ergebnis soll ja in Label1 angezeigt werden. Der in einem Label, welches z.B. den<br />

Namen ʺlabel1ʺ trägt, enthaltene Text wird über<br />

label1.caption<br />

abgerufen und kann auch darüber (über eine Zuweisung) verändert werden. Dies wird<br />

in dieser Zeile gemacht:<br />

label1.caption := IntToStr(summe);<br />

Dabei bedarf die rechte Seite wohl nicht mehr ganz so langer Erklärungen: die<br />

Funktion IntToStr macht genau das Gegenteil von StrToInt. In IntToStr stecken wir eine<br />

(ganze) Zahl, also einen Integer, hinein und bekommen einen Text, also einen String, heraus.<br />

In obiger Zeile wird dieser dann dem Inhalt von Label1 zugewiesen: das Ergebnis der<br />

Addition wird in Label1 angezeigt.<br />

Damit ist das Programm aber noch nicht ganz fertig. Es hat noch ein paar<br />

Schönheitsfehler. Zum einen sind beim Start des Programmes die beiden Edit-Felder nicht<br />

leer, das Label ist sichtbar, selbst wenn noch gar nichts berechnet wurde und der Button<br />

trägt die Beschriftung ʺbutton1ʺ.<br />

Um all dies zu ändern, rufen Sie zuerst wieder die Form1 auf, sodass Sie die<br />

Komponenten wieder vor sich sehen. Klicken Sie dann eines der Edit-Felder an. Suchen Sie<br />

nun im Objektinspektor nach der Eigenschaft ʺTextʺ. Diese ist wahrscheinlich schon<br />

standardmäßig markiert. Der Wert dieser Eigenschaft (also das, was rechts neben ihrem<br />

Namen steht) sollte momemtan ʺEdit1ʺ sein. Markieren Sie diesen Text und löschen Sie ihn.<br />

Gehen Sie genauso bei Edit2 vor. Nun sollten die Edit-Felder beide leer sein.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 13/81<br />

Beachten Sie, dass Sie gerade nur den Text, der in den beiden Edit-Feldern enthalten<br />

ist, geändert haben. Dieser ist völlig unabhängig von ihren Namen. Diese haben sich nicht<br />

geändert und sind somit immer noch ʺEdit1ʺ und ʺEdit2ʺ.<br />

Als nächstes kümmern Sie sich um den Button. Klicken Sie auch ihn an und suchen Sie<br />

nach der Eigenschaft ʺCaptionʺ. Dies ist die Beschriftung und auch sie sollte im<br />

Objektinspektor standardmäßig ausgewählt sein. Ändern Sie sie in ʺAddierenʺ. Auch hier<br />

bleibt der Name des Buttons unverändert!<br />

Zu guter Letzt muss auch noch der in Label1 enthaltene Text geändert werden, der,<br />

wie schon erwähnt, auch hier in ʺCaptionʺ enthalten ist. Ändern Sie den Text in ʺkeine<br />

Summeʺ oder etwas ähnlich sinnvolles. Nun sollte Ihr Programm fertig sein und Sie können<br />

es ausführen.<br />

Ich habe in diesem Abschnitt natürlich vieles nur unzureichend erklärt. Dies ist jedoch<br />

nicht anders zu machen, da man einfach nicht alles auf einmal erklären kann, aber vieles,<br />

was noch nicht richtig erklärt wurde, für ein laufendes Programm notwendig ist. Man<br />

könnte dieses Problem umgehen, indem man bis zum bitteren Ende nur Theorie macht und<br />

erst dann anfängt, zu programmieren, aber das würde schnell sehr langweilig werden und<br />

Ihnen den Spaß verderben.<br />

Ein weiterer wichtiger Datentyp ist übrigens der Datentyp Boolean, dieser wird im<br />

Kapitel ʺExkurs - Boolʹsche Ausdrückeʺ näher beleuchtet.


dsdt dsdt.info dsdt<br />

Kontrollstrukturen<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 14/81<br />

Kontrollstrukturen sind eine der wichtigsten Bestandteile jeder Programmiersprache.<br />

Kontrollstrukturen bieten dem Programmierer die Möglichkeit, den Fluss des Programmes<br />

zu beeinflussen. Ein Programm kann mittels Kontrollstrukturen gewisse Passagen mehrmals<br />

ausführen, ohne dass diese Passagen mehrmals geschrieben werden müssen. Es ist auch<br />

möglich, Verzweigungen in ein Programm einzubauen, sodass unter bestimmten Bedingen<br />

mal die eine und mal die andere Passage ausgeführt wird.<br />

Bevor man jedoch eine dieser Kontrollstrukturen vorstellen kann, muss man sich erst<br />

einmal anschauen, wie man überhaupt eine solche ʺPassageʺ definiert, denn das Programm<br />

kann ja nicht wissen, welche Programmzeilen man als Einheit betrachtet. Dies macht man<br />

mit den Befehlen ʺbeginʺ und ʺendʺ.<br />

“begin” und “end”<br />

Zu diesem Zeitpunkt des <strong>Crashkurs</strong>es wird Ihnen die Verwendung von „begin“ und<br />

„end“ wahrscheinlich noch sehr abstrakt vorkommen, da die Kontrollstrukturen, welche auf<br />

diese beiden Befehle angewiesen sind, noch nicht klar sind. Aber das sollte kein Problem<br />

darstellen, denn die beiden Befehle erklären sich sowieso fast von alleine.<br />

Wenn man ein Programm schreibt, arbeit man oft mit Programmteilen, die man gerne<br />

als Einheit betrachten möchte. Dies sieht man z.B. an den Quelltexten, welche Sie für die<br />

Button-Klicks geschrieben haben. Dort erscheint erst einmal die Deklaration der Variablen<br />

und dann sollen alle nachfolgenden Befehle in einem ausgeführt werden.<br />

Und dort kommen auch schon „begin“ und „end“ zum Einsatz. Denn mit ihnen macht<br />

man dem Programm klar, welche Programmzeilen zu dem Button-Klick gehören. Dies macht<br />

man, indem man einen Bereich definiert, in dem sich die zugehörigen Programmzeilen<br />

befinden. Den Anfang dieses Bereichs markiert man mit einem „begin“ und das Ende dieses<br />

Bereichs mit einem „end“.<br />

Und so macht man es mit allen Quellcode-Passagen, die man bestimmen will. Anfang<br />

und Ende werden mit „begin“ und „end“ markiert. Dabei sieht der Quelltext so aus:


dsdt dsdt.info dsdt<br />

begin<br />

befehl1;<br />

befehl2;<br />

{...}<br />

befehl21;<br />

befehl22;<br />

end;<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 15/81<br />

Bitte beachten Sie, dass niemals ein Semikolon hinter ein „begin“ kommt. Eine so klare<br />

Aussage kann man für das Semikolon hinter dem ʺendʺ nicht treffen. In den allermeisten<br />

Fällen macht man ein Semikolon dahinter, in Ausnahmen jedoch nicht. Auf diese<br />

Ausnahmen wird an den jeweiligen Stellen hingewiesen.<br />

Nachdem „begin“ und „end“ nun klar sein sollten, sollen nun ein paar der wichtigsten<br />

Kontrollstrukturen hier vorgestellt werden. Den Anfang macht dabei die Verzweigungen.<br />

Diese werden primär mittels einer if-Anweisung realisiert, manchmal auch mit der case-<br />

Anweisung.<br />

if, case und Bool'sche Ausdrücke<br />

if-Anweisung - Die Erste<br />

Zuerst ein bisschen Praxis. Bauen Sie noch einmal ein Programm zusammen, welches<br />

dem obigen äußerlich exakt gleicht, tippen Sie jedoch noch keinen Quelltext ein. Der kommt<br />

erst gleich. Dieses mal soll nämlich keine Addition der beiden Werte erfolgen, sondern es<br />

soll herausgefunden werden, welche der beiden Zahlen die Größere ist.<br />

Dazu müssen zuerst einmal wieder die Zahlen deklariert werden (also die Auflistung<br />

nach ʺvarʺ). Dies können Sie schonmal erledigen, die Summe brauchen wir diesmal nicht. Sie<br />

sollten die Variablen dieses mal auch nicht als Integer deklarieren, sondern als Real, denn<br />

dieses Mal sollen auch nicht-ganze Zahlen verarbeitet werden. Und auch die Zeilen, um die<br />

Zahlen aus den Edit-Felder einzulesen, können Sie schonmal hinschreiben. Der Befehl, den<br />

Sie statt StrToInt verwenden sollten, lautet für nicht-ganze Zahlen StrToFloat. Die<br />

Verwendung ist die Gleiche.<br />

So, nachdem das erledigt ist, geht es an die Fallunterscheidung. Wie soll diese genau<br />

aussehen? So!


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 16/81<br />

÷! Wenn die beiden Zahlen gleich sind, soll das Label ʺgleichʺ anzeigen.<br />

÷! Ist ʺzahl1ʺ kleiner als ʺzahl2ʺ, so soll das Label ʺkleinerʺ anzeigen.<br />

÷! Ist ʺzahl2ʺ kleiner als ʺzahl1ʺ, so soll das Label ʺgrößerʺ anzeigen.<br />

Im Grunde genommen soll im Label also das Verhältnis des ersten Wertes zum<br />

Zweiten angezeigt werden. Dies kann man so machen:<br />

procedure TForm1.Button1Click(Sender: TObject);<br />

var zahl1, zahl2 : Real;<br />

begin<br />

zahl1 := StrToFloat(edit1.text);<br />

zahl2 := StrToFloat(edit2.text);<br />

if zahl1 = zahl2 then<br />

label1.caption := 'gleich';<br />

if zahl1 < zahl2 then<br />

label1.Caption := 'kleiner';<br />

if zahl2 < zahl1 then<br />

label1.Caption := 'größer';<br />

end;<br />

Hier können Sie auch gleich mal kontrollieren, ob die Deklaration und das Einlesen der<br />

Werte bei Ihnen richtig ist. ;-)<br />

Die ersten Zeilen müssen nicht mehr erklärt werde, da sie im Grunde genommen die<br />

selben wie im vorigen Abschnitt sind, mit ganz kleinen Änderungen. Der erste (und einzige)<br />

Code-Abschnitt, der erklärt werden solllte, ist folgender:<br />

if zahl1 = zahl2<br />

then label1.caption := 'gleich';<br />

Dies ist eine if-Anweisung. In der momentanen Fassung kann man dabei noch nicht<br />

von einer Verzweigung, sondern nur von einer Bedingung sprechen, doch die Verzweigung<br />

wird im Laufe dieses Abschnitts noch eingeführt. Um eine if-Anweisung zu verstehen, muss<br />

man sich erst einmal mit den so genannten „Boolʹschen Ausdrücken“ beschäftigen.<br />

Exkurs - Bool'sche Ausdrücke<br />

Ein Boolʹscher Ausdruck ist ein Wahrheitswert (in <strong>Delphi</strong> durch den Variablentyp<br />

„Boolean“ vertreten). Er ist entweder „wahr“ oder „falsch“. Dazwischen gibt es nichts. Die<br />

Aussage „Es regnet im Moment!“ ist entweder wahr oder falsch. Aber nichts dazwischen.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 17/81<br />

Genauso ist die Aussage „Ich habe genauso viel Geld im Portemonnaie wie Sie!“ wahr oder<br />

falsch. Nun kann man das Geld in meinem Portemonnaie mit „zahl1“ bezeichnen und das in<br />

Ihrem mit „zahl2“. Dann erhält man aus obiger Aussage die Gleichung: „zahl1 = zahl2“.<br />

Boolʹsche Ausdrücke sind jedoch nicht immer so einfach. Denn man kann aus zwei<br />

Boolʹschen Ausdrücken wiederum einen dritten Boolʹschen Ausdruck bilden, indem man die<br />

beiden verknüpft. Wie man das macht ist völlig intuitiv verständlich:<br />

procedure TForm1.Button1Click(Sender: TObject);<br />

var newBoolean, boolean1, boolean2 : Boolean;<br />

begin<br />

newBoolean := boolean1 and boolean2;<br />

newBoolean := boolean1 or boolean2;<br />

newBoolean := not boolean1;<br />

newBoolean := boolean1 xor boolean2;<br />

end;<br />

Wie man sieht, kann man Boolʹsche Ausdrücke über „und“, „oder“ und „nicht“<br />

miteinander verknüpfen. Dies geschieht, wie wir es im Alltag gewohnt sind und muss wohl<br />

nicht erklärt werden. Das einzige, was eventuell kurz angerissen werden sollte, ist „xor“.<br />

Dies steht für „exclusive or“, also „ausschließendes oder“. Im Alltag nutzt man es durch die<br />

Satzkonstruktion „entweder ... oder“, was deutlich macht, wie es funktioniert: „newBoolean“<br />

ist nur dann wahr, wenn entweder „boolean1“ oder „boolean2“ wahr sind, aber nicht beide.<br />

Selbstverständlich kann man auch zusammengesetzte Boolʹsche Ausdrücke wieder<br />

zusammensetzen, oder Konstruktionen mit mehreren Verknüpfungsoperatoren machen.<br />

Dabei sollte man jedoch immer darauf achten, dass klar ist, welche Ausdrücke in welcher<br />

Reihenfolge verknüpft werden sollen. Zum Beispiel ist folgender Ausdruck<br />

missverständlich:<br />

boolean1 and boolean2 or boolean3<br />

Dies wird von <strong>Delphi</strong> zwar akzeptiert, ist aber für einen menschlichen Leser<br />

verwirrend und dem Programmverständnis nicht förderlich. Es sollte daher vermieden<br />

werden. Man sollte obigen Ausdruck mit Klammern schreiben, sodass klar ist, welche<br />

Verknüpfungen hier gewünscht werden:


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 18/81<br />

(boolean1 and boolean2) or boolean3<br />

Aber wie hätte <strong>Delphi</strong> diesen Ausdruck ausgewertet, wäre er ohne Klammern<br />

geschrieben? Für <strong>Delphi</strong> ist jeder Ausdruck eindeutig auswertbar, dafür sorgen zwei Regeln:<br />

Zum einen ist das die Präzedenz der Operatoren, sozusagen die Stärke der „Bindung“ eines<br />

Operators. Zum anderen die Reihenfolge der Auswertung, bei Operatoren gleicher<br />

Präzedenz. Sie erfolgt von links nach rechts.<br />

Die Präzendenz (oder auch „Rangfolge“) von Operatoren mag Ihnen zuerst etwas<br />

merkwürdig erscheinen, jedoch kennt sie jeder, der weiß, dass Punkt- vor Strichrechnung<br />

geht. Damit wird nämlich genau angegeben, dass die Punktrechnung (also die Operatoren<br />

der Multiplikation und Division) eine höhere Präzedenz haben als die Strichrechnung (also<br />

die Operatoren der Addition und Subtraktion).<br />

Die Präzedenz der Boolʹschen Operatoren ist wie folgt: not, and, or, xor. Bei<br />

Operatoren gleicher Präzedenz wird von links nach rechts ausgewertet, ansonsten hat die<br />

Präzedenz Vorrang vor der Reihenfolge (wie bei „Punkt vor Strich“). Folgende Schreibweise<br />

wäre also äquivalent zu obigem Ausdruck:<br />

boolean3 or boolean1 and boolean2<br />

Die Auswertung von links nach rechts kann man sich beim so genannten<br />

Kurzschlussverfahren zu Nutze machen. Dieses wird bei der Auswertung von and- und or-<br />

Ausdrücken verwendet. Dabei wird ein solcher Ausdruck von links nach rechts ausgewertet<br />

und abgebrochen, sobald das Ergebnis fest steht. Zum Beispiel: Wenn bei einer and-<br />

Verknüpfung von zwei Ausdrücken der erste Ausdruck bereits ʺfalschʺ ist, so muss auch der<br />

Gesamtausdruck „falsch“ sein. Der zweite Ausdruck muss gar nicht mehr kontrolliert<br />

werden und <strong>Delphi</strong> lässt das dann auch sein.<br />

Dies ist sehr nützlich, wenn man diese Ausdrücke über eine Funktion bezieht. Dann<br />

nähme man als ersten Ausdruck die Funktion, welche besonders schnell ist und als zweiten<br />

Ausdruck die Funktion, welche langsamer ist. Der Vorteil: wenn die erste Funktion ʺfalschʺ<br />

zurückgibt, dann wird die Zweite (langsamere) gar nicht mehr ausgeführt, weil <strong>Delphi</strong><br />

erkennt, dass dies nicht nötig ist. Bei der vollständigen Auswertung würde dagegen ein


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 19/81<br />

Ausdruck auch dann vollständig ausgewertet, selbst wenn sein Ergebnis bereits fest steht.<br />

Dies kann auch nützlich sein, zum Beispiel dann, wenn ein Teil des Ausdrucks Auswirkung<br />

auf das Programm hat, also z.B. eine Funktion ist, die eine globale Variable ändert. Man<br />

redet dann auch von einer Funktion mit Nebeneffekten.<br />

Nun wurde genug über Boolʹsche Ausdrücke geredet, jetzt wird noch einmal die if-<br />

Anweisung beleuchtet.<br />

if-Anweisung - Die Zweite<br />

Die Struktur der if-Anweisung, die bisher verwendet wurde, kann man also so<br />

schreiben:<br />

if {Bool'scher Ausdruck} then<br />

{Block1}<br />

ʺ{Block1}ʺ bedeutet, dass dort eine Anweisung stehen kann (wie im obigen Beispiel)<br />

oder aber ein kompletter Anweisungsblock, der, wie oben beschrieben, durch ein ʺbeginʺ<br />

und ein ʺendʺ beschränkt wird. Dieser Block wird genau dann ausgeführt, wenn der<br />

Boolʹsche Ausdruck ʺwahrʺ ist. Ist er ʺfalschʺ, wird bei dieser Anweisung nichts ausgeführt.<br />

Dieses Verhalten ist jedoch nicht immer gewünscht. Oft möchte man, dass auch für<br />

den Fall, dass der Boolʹsche Ausdruck ʺfalschʺ ist, eine Anweisung oder ein Block von<br />

Anweisungen ausgeführt wird. Und dies wäre dann die Verzweigung, von der ich eingangs<br />

geschrieben habe. Dies kann man wohl auch wieder am Besten anhand eines Beispiels<br />

erklären:<br />

procedure TForm1.Button1Click(Sender: TObject);<br />

var zahl1, zahl2 : Real;<br />

begin<br />

zahl1 := StrToFloat(edit1.text);<br />

zahl2 := StrToFloat(edit2.text);<br />

if zahl1 < zahl2 then<br />

label1.capion := 'kleiner'<br />

else<br />

label1.caption := 'größer oder gleich';<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 20/81<br />

Dieser Quelltext ähnelt dem aus dem ersten Teil zu if-Anweisungen, ist von der<br />

Funktion her jedoch nicht komplett identisch. Folgendes tut dieser Quelltext: wenn ʺzahl1ʺ<br />

kleiner als ʺzahl2ʺ ist, so wird dies im Label1 angezeigt, sonst wird in Label1 angezeigt, dass<br />

ʺzahl1ʺ größer oder gleich ʺzahl2ʺ ist. Und dies ist die gesuchte Verzweigung, da immer nur<br />

einer der beiden Befehle ausgeführt wird.<br />

Bitte beachten Sie Folgendes: der letzte Befehl vor einem ʺelseʺ wird niemals mit einem<br />

Semikolon abgeschlossen. Dies gilt auch für den Fall, dass der letzte Befehl das<br />

abschließende ʺendʺ eines Codeabschnitts ist!<br />

Nun ist der obige Quelltext jedoch nicht äquivalent mit dem vorherigen Quelltext. Er<br />

gibt dem Benutzer weniger Informationen, da er nicht zwischen ʺgrößerʺ und ʺgleichʺ<br />

unterscheiden kann. Dies kann man, wenn man mit else arbeiten will, mit einer<br />

verschachtelten if-Anweisung lösen:<br />

if zahl1 < zahl2 then<br />

label1.capion := 'kleiner'<br />

else<br />

if zahl2 < zahl1 then<br />

label1.caption := 'größer'<br />

else<br />

label1.caption := 'gleich';<br />

Hieran sieht man zweierlei: zum einen die oben genannte Verschachtelung und zum<br />

anderen, dass die if-Anweisung ab dem ʺifʺ bis inklusive zum Block hinter dem ʺelseʺ als<br />

eine Anweisung gilt und sie somit nicht mit ʺbeginʺ und ʺendʺ als Abschnitt gekennzeichnet<br />

werden muss. Nicht ganz klar, wie das gemeint ist? Anhand eines Beispiels wird es klarer.<br />

Folgendes muss man nicht machen:<br />

if zahl1 < zahl2 then<br />

label1.capion := 'kleiner'<br />

else begin<br />

if zahl2 < zahl1 then<br />

label1.caption := 'größer'<br />

else<br />

label1.caption := 'gleich';<br />

end;<br />

Dies liegt daran, dass folgendes als eine Anweisung verstanden wird:<br />

if zahl2 < zahl1 then<br />

label1.caption := 'größer'<br />

else


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 21/81<br />

label1.caption := 'gleich';<br />

Zusammenfassend kann man die if-Anweisung also so charakterisieren: Die if-<br />

Anweisung ist ein Anweisungsgerüst, welches als eine einzige Anweisung betrachtet wird<br />

und wie folgt aufgebaut ist.<br />

if {Bool'scher Ausdruck} then<br />

{Block1}<br />

else<br />

{Block2}<br />

Dabei ist die letzte Zeile optional, wird sie jedoch verwendet, so darf der letzte Befehl<br />

von ʺ{Block1}ʺ nicht mit einem Semikolon abgeschlossen werden.<br />

Ich habe mich nun sehr lange mit der if-Anweisung und dem drum herum aufgehalten<br />

und den ein oder anderen hat inzwischen die Langeweile gepackt. Jedoch ist dies eine der<br />

wichtigsten Dinge, die es in <strong>Delphi</strong> gibt und Sie werden selten ein Programm schreiben, in<br />

dem keine if-Anweisung vorkommt. Und damit es dort keine Probleme gibt, bin ich lieber<br />

auf Nummer sicher gegangen und war lieber zu ausführlich als zu knapp.<br />

Die case-Anweisung<br />

Nun ist jedoch die if-Anweisung nicht die einzige Kontrollstruktur, welche eine<br />

Verzweigung des Programmes erlaubt.Es gibt auch noch die so genannte ʺcase-Anweisungʺ.<br />

Was versteht man darunter? Wozu braucht man die?<br />

Eigentlich braucht man sie nicht wirklich. Aber sie ist sehr praktisch. Stellen Sie sich<br />

vor, Sie lassen den Benutzer eine ganze Zahl zwischen Null und Fünf eingeben. Und je<br />

nachdem, welche Zahl eingegeben wurde, möchten Sie eine Aktion ausführen lassen. Dies<br />

könnten Sie so lösen:<br />

if zahl = 0 then<br />

anweisung1;<br />

if zahl = 1 then<br />

anweisung2;<br />

if zahl = 2 then<br />

anweisung3;<br />

if zahl = 3 then<br />

anweisung4;<br />

if zahl = 4 then<br />

anweisung5;<br />

if zahl = 5 then<br />

anweisung6;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 22/81<br />

Offensichtlich ist dies nicht sehr elegant, es kommt sehr viel Code darin vor, der sich<br />

ähnelt und das ist etwas, das einem guten Programmierer die Augen bluten lässt. Eine<br />

Lösung bietet eine Case-Anweisung:<br />

case zahl of<br />

0: anweisung1;<br />

1: anweisung2;<br />

2: anweisung3;<br />

3: anweisung4;<br />

4: anweisung5;<br />

5: anweisung6;<br />

end;<br />

Dies sieht doch sehr viel besser aus, oder? Man kann es auch noch auf die Spitze<br />

treiben und der Case-Anweisung Mengen anstatt nur Zahlen übergeben. Ein Beispiel wie<br />

dies geht, folgt auf dem Fuße und zeigt auch die Verwendung eines Else-Zweiges in Case-<br />

Anweisungen:<br />

case zahl of<br />

0..3, 5: anweisung1;<br />

4: anweisung2;<br />

else<br />

anweisung3;<br />

end;<br />

Doch alles, was so viele Vorteile besitzt, besitzt meist auch Nachteile. So leider auch die<br />

Case-Anweisung:<br />

1. Es können nur konstante Ausdrücke als Vergleichswerte angegeben werden. Anstatt<br />

der Zahlen dürfte man oben also keine Variablennamen eintragen.<br />

2. Man kann mittels der Case-Anweisung nur die Werte von Variablen überprüfen,<br />

welche einen ʺordinalenʺ Typ haben. Ein Datentyp ist dann ʺordinalʺ, wenn man in<br />

aufzählen kann. Alle Werte dieses Datentyps müssen einen eindeutigen Vorgänger<br />

(Ausnahme: der erste Wert) und einen eindeutigen Nachfolger (Ausnahme: der letzte<br />

Wert) besitzen. Ein ordinaler Typ ist zum Beispiel der Integer. Dies ist einsichtig,<br />

Integer enthält nur ganze Zahlen in einer definierten Reihenfolge, z.B. ...,4,5,6,7,...<br />

Dagegen nicht aufzählbar ist der Datentyp String, daher kann ein String nicht mit der<br />

Case-Anweisung verwendet werden.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 23/81<br />

Übrigens müssen in der case-Anweisung nicht einzelne Anweisungen stehen, sondern<br />

man kann auch mehrere durch ʺbeginʺ und ʺendʺ eingeschlossene Anweisungen einfügen.<br />

Schleifen<br />

Schleifen sind extrem wichtige und praktische Kontrollstrukturen einer<br />

Programmiersprache. Sie bieten einem Programmierer die Möglichkeit, eine Aktion<br />

mehrmals auszuführen, ohne zu der Zeit, zu der er das Programm schreibt, schon zu wissen,<br />

wie oft diese Aktion genau ausgeführt werden soll.<br />

Dabei bieten Schleifen verschiedene Möglichkeiten anzugeben, wann die Ausführung<br />

der Anweisung gestoppt werden soll, so z.B. neben der Angabe einer Anzahl von<br />

Schleifendurchläufen auch eine Bedingung, welche zum Abbruch führt.<br />

Die for-do-Schleife<br />

Die for-do-Schleife stellt eine Möglichkeit dar, festzulegen, wie oft eine Anweisung<br />

ausgeführt wird. Dies wird mit einem so genannten ʺZählerʺ in Form einer<br />

ʺSchleifenvariableʺ gemacht. Erst einmal ein Beispiel:<br />

for i:=0 to 5 do<br />

anweisung;<br />

Hierbei ist ʺiʺ eine Variable vom Typ Integer. Die for-do-Schleife macht Folgendes:<br />

Zuerst wird ʺiʺ der Wert 0 zugeordnet. Dann wird die Anweisung ausgeführt. Dann wird ʺiʺ<br />

um 1 erhöht, besitzt nun also den Wert 1. Die Anweisung wird wiederrum ausgeführt, usw.<br />

Die letzte Ausführung der Anweisung findet statt, wenn ʺiʺ den Wert 5 hat. Zu diesem<br />

Zeitpunkt wurde die Anweisung sechs Mal ausgeführt. Und zwar für die Werte von ʺiʺ:<br />

0,1,2,3,4,5.<br />

Dabei ist ʺiʺ aber nicht auf die Rolle des Platzhalters zur Angabe der Anzahl<br />

beschränkt. Die Wertzuweisung zu ʺiʺ macht durchaus Sinn, denn während die Anweisung<br />

ausgeführt wird, kann man innerhalb dieser Anweisung (die natürlich wie immer auch aus<br />

mehreren Anweisung, welche durch ʺbeginʺ und ʺendʺ eingeschlossen sind, bestehen kann)<br />

auf den aktuellen Wert von ʺiʺ zugreifen.<br />

var i : Integer;


dsdt dsdt.info dsdt<br />

myString : String;<br />

begin<br />

myString := '';<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 24/81<br />

for i:=0 to 5 do<br />

myString := myString + IntToStr(i);<br />

end;<br />

Dieser Quellcode macht folgendes: er legt zwei Variablen an, ʺiʺ und ʺmyStringʺ. Dann<br />

wird ʺmyStringʺ auf einen leeren Wert gesetzt. Schließlich kommt die Schleife: in dieser<br />

durchläuft ʺiʺ die oben bereits genannten Werte. In jedem Schleifendurchlauf wird der<br />

aktuelle Wert von ʺiʺ in einen String umgewandelt und an ʺmyStringʺ angehängt.<br />

Das Addieren von Strings geschieht dabei ganz intuitiv, sodass die Addition von ʹaʹ<br />

und ʹbʹ den String ʹabʹ ergeben würde. Nach dem die Schleife komplett durchlaufen wurde,<br />

wurden also alle Zahlen von 0 bis 5 an einen zu Anfang leeren String angehängt. Also hat<br />

ʺmyStringʺ nun den Wert ʹ012345ʹ.<br />

Nachdem das Prinzip der for-do-Schleife nun klar sein sollte, folgen nun noch ein paar<br />

Anmerkungen:<br />

1. Die Zeile ʺfor ... doʺ nennt man den Schleifenkopf, die Anweisungen, welche<br />

ausgeführt werden, den Schleifenrumpf. Diese Bezeichnungen werden auch bei<br />

allen anderen Schleifen verwendet!<br />

2. Im Schleifenrumpf darf die Zählervariable (in unserem Fall das ʺiʺ) nicht verändert<br />

werden. (Der Compiler verhindert das, wenn man ihn nicht austrickst.)<br />

3. Sowohl Anfangs- als auch Endwert einer Schleife dürfen Variablen sein.<br />

4. Es gibt zwei Arten for-do-Schleifen: eine for-to-do-Schleife und eine for-downto-do-<br />

Schleife. Die „to“-Variante zählt hoch, die „downto“-Variante zählt herunter. Dies<br />

beinhaltet, dass der Rumpf einer Schleife der „to“-Variante nur dann ausgeführt<br />

wird, wenn der Startwert kleiner als der Endwert ist. Das leuchtet ein: eine<br />

heraufzählende Schleife kann nur dann arbeiten, wenn man den Endwert durch<br />

erhöhen des Anfangswertes erreichen kann. Analog wird der Rumpf der „downto“-<br />

Variante nur ausgeführt, wenn der Startwert größer als der Endwert ist.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 25/81<br />

5. Wird die Schleife regulär verlassen, also durch erreichen des Endwertes, so ist der<br />

Wert der Zählervariable im Allgemeinen nicht mehr definiert. Bei einem nicht-<br />

regulären Verlassen (wie das geht kommt am Ende dieses Abschnittes) enthält die<br />

Zählervariable den Wert, den sie zuletzt in der Schleife besaß.<br />

Die repeat-until-Schleife<br />

Nachdem Sie nun die Schleife kennen gelernt haben, welche den Abbruch über die<br />

Anzahl der Schleifendurchläufe regelt, wird nun eine der beiden Schleifen beschrieben,<br />

welche den Abbruch an eine Bedingung knüpfen.<br />

Dabei sagte die Übersetzung des Namens bereits, wie die Schleife aufgebaut ist:<br />

ʺwiederhole ... bisʺ. Und dort, wo momentan noch drei Punkte stehen, kommt die<br />

Anweisung hin. Und auch hier muss nicht unbedingt eine einzelne Anweisung stehen,<br />

sondern es können auch mehrere sein. Aber Sie brauchen kein ʺbeginʺ und ʺendʺ. Weshalb<br />

das so ist, sehen Sie im nachfolgenden Beispiel.<br />

var i, wert, max : Integer;<br />

begin<br />

{...}<br />

i := 0;<br />

wert := 1;<br />

repeat<br />

i := i + 1;<br />

wert := wert * i;<br />

until wert >= max;<br />

{...}<br />

end;<br />

Dieser Quelltext berechnet das ʺiʺ, mit dem ʺi!ʺ (i! = i*(i-1)*...*1) größer oder gleich<br />

ʺmaxʺ ist. Dabei werden die Befehle, welche zum Schleifenrumpf gehören, bereits durch die<br />

Schlüsselwörter ʺrepeatʺ und ʺuntilʺ begrenzt, ʺbeginʺ und ʺendʺ sind also nicht mehr nötig.<br />

Die Anweisungen werden mindestens einmal ausgeführt, denn die Überprüfung, ob<br />

die Bedingung erfüllt ist oder nicht, steht ja am Ende. Die Schleifenbedingung ist ein<br />

Boolʹscher Ausdruck, wie er bereits besprochen wurde. Sobald dieser Boolʹsche Ausdruck<br />

wahr ist, wird die Schleife verlassen.


dsdt dsdt.info dsdt<br />

Die while-do-Schleife<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 26/81<br />

Die while-do-Schleife (ʺwährend ... mache ...ʺ) ist der repeat-until-Schleife sehr ähnlich<br />

und bietet praktisch die gleichen Funktionen. Daher werde ich hier nur auf die Unterschiede<br />

eingehen:<br />

1. Die Bedingung wird am Schleifenkopf geprüft. Die Schleife wird also nicht unbedingt<br />

einmal ausgeführt, sondern es kann auch sein, dass die Schleife kein Mal ausgeführt<br />

wird. Dies passiert dann, wenn die Bedingung schon ganz am Anfang nicht mehr<br />

erfüllt ist.<br />

2. Sollen mit einer while-do-Schleife mehrere Anweisungen ausgeführt werden, müssen<br />

diese mit ʺbeginʺ und ʺendʺ eingegrenzt werden.<br />

3. Die Schleifenbedingung ist ein Boolʹscher Ausdruck, jedoch wird im Gegensatz zur<br />

repeat-until-Schleife die Schleife dann verlassen, wenn der Ausdruck nicht wahr ist.<br />

Und weil es so schön ist, gibt es auch noch ein Beispiel:<br />

var i, max : Integer;<br />

richtig : Boolean;<br />

begin<br />

{...}<br />

i := 2;<br />

richtig := true;<br />

while richtig do<br />

begin<br />

i := i*i;<br />

richtig := i < max;<br />

end;<br />

{...}<br />

end;<br />

An diesem Beispiel kann man auch noch einnmal die Verwendung von Boolʹschen<br />

Ausdrücken sehen: ʺi < maxʺ ist entweder wahr oder falsch und kann deswegen auch einer<br />

Variable von Typ Boolean zugewiesen werrden. Damit es keine Verwirrung gibt: der Code<br />

hat nichts mit ʺi!ʺ zutun!


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 27/81<br />

Prozeduren und Funktionen<br />

Wie schon während der Beschreibung der case-Anweisung gesagt wurde, sind<br />

Codeteile, welchen eine ähnliche oder gar gleiche Funktion besitzen und oft vorkommen,<br />

nicht gerne gesehen. Wieso dies so ist, wird nach Einführung der Prozeduren noch einmal<br />

besprochen. Jetzt wird erst einmal beschrieben, wie Prozeduren und Funktionen eigentlich<br />

funktionieren.<br />

Prozeduren<br />

Die Einführung in die Prozeduren hat für Sie den Vorteil, dass es wieder etwas zu<br />

programmieren gibt. Das haben Sie übrigens Michael ʺLuckieʺ Puff zu verdanken, der mir<br />

mit diesem Beispiel aus der Klemme geholfen hat.<br />

Um zu sehen, wie eine Prozedur funktioniert, bauen Sie sich jetzt erst einmal besagtes<br />

Beispiel zusammen. Dazu legen Sie ein neues Projekt an und platzieren zwei Labels auf der<br />

Form. Der Ort ist eigentlich egal, am besten aber nebeneinander. Schließlich muss noch ein<br />

Button platziert und mit ʺTauschenʺ beschriftet werden.<br />

Führen Sie einen Doppelklick auf die Schaltfläche aus, um den Quelltext so<br />

einzufügen, dass es hinterher so aussieht wie hier:<br />

procedure TForm1.Button1Click(Sender: TObject);<br />

var s1, s2 : String;<br />

begin<br />

s1 := Label1.Caption;<br />

s2 := Label2.Caption;<br />

tauschen(s1,s2);<br />

Label1.Caption := s1;<br />

Label2.Caption := s2;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 28/81<br />

Dabei fällt Ihnen sicher der Befehl ʺtauschen(s1, s2)ʺ auf. Das ist die Prozedur, die es<br />

noch zu schreiben gilt. Sie sieht folgendermaßen aus:<br />

procedure tauschen(var s1, s2 : String);<br />

var temp : String;<br />

begin<br />

temp := s1;<br />

s1 := s2;<br />

s2 := temp;<br />

end;<br />

Diese fügen Sie über den Zeile mit ʺprocedure TForm1....ʺ ein. Starten Sie nun das<br />

Programm und betätigen Sie den Button. Achten Sie dabei darauf, was mit der Beschriftung<br />

der Labels passiert! Wie Sie sehen, werden die Beschriftungen vertauscht.<br />

Und nun nehmen Sie eine Änderung am Kopf der Prozedur ʺtauschenʺ vor. Entfernen<br />

Sie das ʺvarʺ vor ʺs1, s2 ...ʺ. Starten Sie das Projekt erneut und klicken Sie erneut. Sie sehen,<br />

was passiert: nichts. Und anhand dieses ʺPhänomensʺ wird im Folgenden erklärt, wie eine<br />

Prozedur arbeitet.<br />

Dazu werde ich zuerst die Variante beschreiben, die nicht funktioniert. Die ist<br />

einfacher. Sie ahnen sicher schon, was eine Prozedur macht: sie fasst Befehle unter einem<br />

Namen zusammen. Wenn Sie die Prozedur ʺtauschenʺ aufrufen, dann werden die Befehle<br />

ausgeführt, die Sie dort festgelegt haben. Die Prozedur ist im Grunde genommen ein kleines<br />

Programm für sich.<br />

Aber ein solches Programm wäre sinnlos, wenn es ohne Kontakt zum Rest immer<br />

dieselben Befehle ausführen würde. Daher hat man es so eingerichtet, dass man einer<br />

Prozedur so genannte Parameter mitgeben kann.<br />

Parameter, das sind die Dinger, die hinter dem Namen der Prozedur in Klammern<br />

stehen. Grob gesagt sind es Variablen, welche man beim Starten der Prozedur mit einem<br />

bestimmten Wert belegen kann. Dieser Wert ist dann in der Prozedur bekannt und man kann<br />

in der Prozedur mit dem Wert arbeiten. Ein Parameter ändert also das Ergebnis und die<br />

Aktionen einer Prozedur: anderer Wert, andere Aktion.<br />

Was passiert nun in der Prozedur mit den Parametern s1 und s2? Sie werden<br />

getauscht. Ein solcher Tausch wird Ihnen während Ihrer Programmierertätigkeit sehr oft


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 29/81<br />

begegnen. Um den Wert zweier Variablen zu tauschen, braucht man immer eine Dritte.<br />

Anstatt dass ich dies nun mit vielen Worten beschreiben, sollten Sie sich einfach die Grafik<br />

ansehen.<br />

Somit ist nun klar, was beim Aufruf von ʺtausche(s1, s2)ʺ passiert. Und da Sie vorher<br />

den Inhalt von Label1 in die Variable ʺs1ʺ geschrieben haben und den Inhalt von Label2 in<br />

die Variable ʺs2ʺ, und nach dem Aufruf von ʺtauscheʺ das Ganze umgekehrt machen, sollten<br />

der Inhalt der Labels hinterher ebenfalls getauscht sein. Man beachte: ʺs1ʺ gehört immer zu<br />

Label1 und ʺs2ʺ gehört immer zu Label2. Wieso geschieht dies nicht?<br />

Dies liegt, wie Sie sicher schon vermuten, an dem Zusatzwort ʺvarʺ, welches ja zuerst<br />

vor den Parametern stand und das Sie entfernt hatten. Ohne dieses Schlüsselwort, wird in<br />

der Prozedur nicht mit den übergebenen Variablen selbst gearbeitet, sondern mit einer Kopie<br />

dieser Variablen. Dinge, die mit den Variablen in der Prozedur angestellt werden, haben also<br />

keine Auswirkung auf die Variablen im aufrufenden Programm. Diese Methode des Aufrufs<br />

nennt man ʺCall by Valueʺ, weil mit dem Wert der Variable, aber nicht mit der Variable<br />

selbst gearbeitet wird.<br />

Wenn Sie das Schlüsselwort ʺvarʺ hinzufügen, wird die Aufrufmethode geändert. Die<br />

Prozedur wird nun mittels ʺCall by Referenceʺ aufgerufen. Das heißt, nun wird nicht mehr<br />

nur mit dem Wert der übergebenenen Variable gearbeitet, sondern mit der Variable selbst.<br />

Es wird also eine Referenz auf die Variable als Parameter übergeben, also sozusagen ein ʺmit<br />

der da musst Du arbeitenʺ.<br />

Und deswegen wird bei der Methode mit ʺCall by Referenceʺ auch die Beschriftung<br />

der Labels geändert, weil nämlich in der Prozedur mit den Variablen gearbeitet wird, die<br />

beim Button-Klick definiert wurden und nicht mit einer Kopie derselbigen.


dsdt dsdt.info dsdt<br />

Funktionen<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 30/81<br />

Funktionen unterscheiden sich im Prinzip nur darin von Prozeduren, dass Funktionen<br />

einen Wert zurückliefern, also ein Ergebnis haben. Folgender Quelltext soll als Beispiel heran<br />

gezogen werden, welches zeigen soll, wie man Funktionen nutzt. Dabei wird das selbe<br />

Ergebnis einmal mit und einmal ohne Funktion realisiert. Zuest einnmal ohne eine Funktion:<br />

var a, b, c, erg1, erg2, erg3: Integer;<br />

begin<br />

erg1 := a*a*a*a + a*a*a + a*a + a;<br />

erg2 := b*b*b*b + b*b*b + b*b + b;<br />

erg3 := c*c*c*c + c*c*c + c*c + c;<br />

erg1 := erg1*erg1*erg1*erg1 + erg1*erg1*erg1 + erg1*erg1 + erg1;<br />

erg2 := erg2*erg2*erg2*erg2 + erg2*erg2*erg2 + erg2*erg2 + erg2;<br />

erg3 := erg3*erg3*erg3*erg3 + erg3*erg3*erg3 + erg3*erg3 + erg3;<br />

end;<br />

Und dann einmal mit Funktion:<br />

function rechnen (wert : Integer) : Integer;<br />

begin<br />

result := wert*wert*wert*wert + wert*wert*wert + wert*wert + wert;<br />

end;<br />

procedure TForm1.Button1Click(Sender: TObject);<br />

var a, b, c, erg1, erg2, erg3: Integer;<br />

begin<br />

erg1 := rechnen(a);<br />

erg2 := rechnen(b);<br />

erg3 := rechnen(c);<br />

erg1 := rechnen(erg1);<br />

erg2 := rechnen(erg2);<br />

erg3 := rechnen(erg3);<br />

end;<br />

Nun wird klarer, wie eine Funktion funktioniert. Die Funktion in diesem Beispiel heißt<br />

natürlich ʺrechnenʺ, was nicht sonderlich kreativ ist, aber für ein Beispiel ausreicht. Wie<br />

funktioniert nun aber diese Funktion?<br />

Wenn man eine Funktion schreibt, gibt man zuerst einnmal an, dass es sich überhaupt<br />

um eine Funktion handelt. Dies macht man mit dem Wort ʺfunctionʺ. Dann bestimmt man<br />

den Namen der Funktion. Und schließlich muss man noch angeben, welche Parameter eine<br />

solche Funktion hat.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 31/81<br />

Auch muss man angeben, was für einen Typ die Funktion zurückgibt, also welchen<br />

Typ das Ergebnis hat. Kommt am Ende ein String heraus, oder vielleicht doch ein Integer?<br />

Diesen Typ gibt man nach den Parametern an und zwar so, als wäre die Funktion eine<br />

Variable: man trennt den Rückgabetyp mit einem Doppelpunkt vom Rest und gibt dann den<br />

Typ an.<br />

Um auf das oben genannte Beispiel zurückzukommen: der Name ist ʺrechnenʺ, der<br />

einzige Parameter ist ʺwertʺ und ist vom Typ ʺIntegerʺ, es wird ein Integer zurückgegeben.<br />

Der Parameter wird entsprechend der Formel mit sich selbst multipliziert (und ein bisschen<br />

addiert) und schließlich dem Ergebnis zugewiesen. Das Ergebnis verwendet man in der<br />

Funktion selbst wie eine Variable, sein Name als solche ist ʺresultʺ. Eine Deklaration ist nicht<br />

erforderlich.<br />

Im Hauptprogramm, welches die Funktion aufruft, kann man die Funktion fast so<br />

benutzen wie eine Variable desselben Typs. Man kann ihr natürlich keine Werte zuweisen.<br />

Außerdem sollte man sich im klaren darüber sein, dass eine Funktion jedesmal, wenn man<br />

sie in einer Formel benutzt, erneut aufgerufen wird. Ein Aufruf folgender Art macht dann<br />

wenig Sinn:<br />

var ergebnis, a : Integer;<br />

begin<br />

ergebnis := rechnen(a)*rechnen(a)*rechnen(a);<br />

end;<br />

Es würde dreimal hintereinander dieselbe Funktion mit demselben Parameter<br />

ausgeführt werden, was Rechenzeit kostet. Sinnvoller ist dieser Quelltext, bei dem das<br />

Ergebnis nur einmal ausgerechnet wird und dann in einer temporären Variable gespeichert<br />

wird.<br />

var temp, ergebnis, a : Integer;<br />

begin<br />

temp:=rechnen(a);<br />

ergebnis := temp*temp*temp;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 32/81<br />

Warum Prozeduren und Funktionen?<br />

Besonders beim letzten Beispiel wird klar geworden sein, weshalb eine Funktion Sinn<br />

macht: sie spart Arbeit. Aber das ist natürlich nicht alles, deswegen hier noch einmal eine<br />

Aufstellung der Vorteile von Funktionen und Prozeduren:<br />

÷! Wie schon gesagt kann eine Funktion oder Prozedur viel Arbeit sparen. Durch das<br />

Zusammenfassen von vielen Zeilen Code in nur einen Befehl reduziert sich die<br />

Tipparbeit enorm.<br />

÷! Viel wichtiger ist aber, dass der Quelltext dadurch auch übersichtlicher wird. Aber<br />

einer gewissen Länge und Verschachtelungstiefe (z.B. durch if-Anweisungen oder<br />

Schleifen) wird ein Quelltext schwer zu lesen und fehleranfällig. Funktionen und<br />

Prozeduren helfen, dies zu vermeiden.<br />

÷! Funktionen und Prozeduren sparen nicht nur beim erstmaligen Einsetzen Arbeit.<br />

Auch, wenn etwas am Programm geändert werden soll, machen sie dies einfacher.<br />

Wenn z.B. obige Berechnung oder der Vertauschungsalgorithmus falsch wären,<br />

müssten beide nur an einer Stelle geändert werden. Der Effekt würde überall dort<br />

eintreten, wo Funktion oder Prozedur aufgerufen werden. Das ist sehr wichtig, weil<br />

man dann nicht einen riesigen Haufen Code (okay, das Wortspiel war schlecht ...)<br />

durchsuchen muss und evtl. doch eine Stelle übersieht.<br />

÷! Und zu guterletzt steigern Funktionen und Prozeduren die Wiederverwendbarkeit.<br />

Durch sie muss man einen Quelltext nur einmal schreiben und kann ihn dann in<br />

verschiedenen Programmen immer wieder einsetzen. So könnte man sowohl die<br />

Beispielprozedur als auch -funktion in anderen Programmen einsetzen, ohne sie noch<br />

einmal neu schreiben zu müssen.<br />

Ich hoffe, durch diese Aufstellung ist klarer geworden, was Prozeduren und<br />

Funktionen zu einem unheimlich mächtigen Werkzeug macht. Und ebenso hoffe ich, dass<br />

Sie zu Ihrem und dem Wohl der Leute, die einmal ihren Quelltext lesen werden, reichlich<br />

gebrauch davon machen.


dsdt dsdt.info dsdt<br />

Arrays<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 33/81<br />

Ein Array ist in praktisch jeder Programmiersprache eine wichtige Möglichkeit, Daten<br />

strukturiert abzulegen. Ein Array besteht aus beliebig vielen Elementen. Diese Elemente<br />

besitzen alle eine eindeutige ʺAdresseʺ in diesem Array, welche ein Tupel aus n Zahlen ist.<br />

Dabei ist n die Dimension des Arrays.<br />

Mit dieser Information kann man sich ein Array natürlich noch nicht wirklich<br />

vorstellen. Daher möchte ich hier ein paar einfache Beispiel nennen. Zuerst die einfachste<br />

Variante, das eindimensionale Array: das kann man sich vorstellen, wie eine Tabelle mit nur<br />

einer Spalte:<br />

Wert [1]<br />

Wert [2]<br />

Wert [3]<br />

Wert [4]<br />

So sähe ein eindimensionales Array aus. Jedem Element ist genau eine Zahl<br />

zugeordnet, also ein Tupel aus nur einer Zahl. Ein zweidimensionales Array kann man sich<br />

auch noch gut vorstellen. Das ist einfach eine Tabelle mit Zeilen und Spalten:<br />

Wert [1,1] Wert [1,2] Wert [1,3]<br />

Wert [2,1] Wert [2,2] Wert [2,3]<br />

Wert [3,1] Wert [3,2] Wert [3,3]<br />

Für dieses Array benötigt man bereits ein Paar von Zahlen, um einen Wert eindeutig<br />

zu identifizieren. Man sieht leicht, dass die Anzahl der Elemente, die ein Array enthält,<br />

unabhängig von der Dimension ist. Man könnte in obiger ʺTabelleʺ beliebig viele Spalten<br />

und Zeilen hinzufügen und sie bliebe immer noch zweidimensional. Die Dimension eines<br />

Array gibt einfach nur an, wieviel Zahlen ich brauche, um ein Element eindeutig zu<br />

identifizieren.<br />

Die Elemente eines Arrays spricht man an, indem man zuerst den Namen des Arrays<br />

schreibt und dann in eckigen Klammern dahinter das Zahlentupel, welches dem Element


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 34/81<br />

entspricht. Dabei werden die Zahlen durch Kommata getrennt. Im Prinzip also so, wie es<br />

schon in obigen Beispielen getan wurde, nur dass man die Leerzeichen weglässt.<br />

Dynamisch oder statisch?<br />

In <strong>Delphi</strong> unterscheiden sich Arrays nicht nur nach ihrer Dimension, sondern auch<br />

danach, ob sie ʺstatischʺ oder ʺdynamischʺ sind. Bei einem statischen Array sind sowohl<br />

Dimension als auch die Anzahl der Elemente von Anfang an bekannt und können während<br />

des Programmablaufs nicht mehr geändert werden. Dynamische Arrays sind dagegen in der<br />

Lage, ihre Größe (aber nicht ihre Dimension) zu ändern.<br />

Wenn man von einem zweidimensionalen Array ausgeht, muss ich bei einem<br />

statischen Array vorher festlegen, wieviele Zeilen und Spalten dieses Array hat. Bei einem<br />

dynamischen Array muss ich nur festlegen, dass das Array Zeilen und Spalten besitzt. Die<br />

Anzahl der Zeilen und Spalten (also die Größe des Arrays) ist veränderbar: dynamisch.<br />

Ich möchte im folgenden zuerst die Verwendung von dynamischen Arrays<br />

demonstrieren und hinterher nur noch kurz die Unterschiede zwischen der Verwendung<br />

von dynamischen und statischen Arrays aufzeigen.<br />

Dynamische Arrays<br />

Deklaration<br />

Um ein dynamisches Array zu benutzen, muss man es, wie jede andere Variable auch,<br />

erst einmal deklarieren. Die Deklaration eines Arrays besteht aus der Angabe, dass es sich<br />

überhaupt um ein Array handelt und aus der Angabe, von welchem Typ die Elemente sind.<br />

Dabei ist als Typ der Elemente jeder Datentyp erlaubt. Die Deklaration eines dynamischen,<br />

eindimensionalen Integer-Array sieht dann so aus:<br />

var myIntArray : Array of Integer;<br />

Ein mehrdimensionales Array wird ähnlich deklariert. Jedoch deklariert man es so,<br />

dass man z.B. für ein zweidimensionales Array ein Array in einem Array deklariert. Die<br />

Deklaration sieht dann so aus:<br />

var my2DIntArray : Array of Array of Integer;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 35/81<br />

Wie muss man sich das vorstellen? Um das zu klären, möchte ich noch einmal das<br />

ʺtabellenartigeʺ Array von oben herauskramen. Dies lässt sich auch so schreiben:<br />

Array [1] | Array [1] = Wert [1] Wert [2] Wert [3]<br />

Array [2] | Array [2] = Wert [1] Wert [2] Wert [3]<br />

Array [3] | Array [3] = Wert [1] Wert [2] Wert [3]<br />

Also ist ein zweidimensionales Array nichts anderes, als ein eindimensionales Array,<br />

welches als Elemente wieder Arrays hat. Der Datentyp der Elemente ist ganz einfach ʺArray<br />

of ...ʺ.<br />

Damit wäre das Array auch schon deklariert. Mehr ist nicht nötig. Bei statischen<br />

Arrays geht es etwas anders, aber darauf gehe ich, wie gesagt, später noch ein.<br />

Die Größe eines Arrays ändern<br />

Um ein dynamisches Array zu benutzen, muss man erst einmal die Größe festlegen,<br />

welche zu Anfang in jeder Dimension Null ist. Die Größe eines solchen Arrays legt man mit<br />

dem Befehl SetLength fest. Als Parameter übergibt man der Prozedur als erstes den Namen<br />

des Arrays, dessen Größe geändert werden soll und als zweites die neue Länge des Arrays:<br />

SetLength(myIntArray, 2);<br />

Mit diesem Quelltext setzt man die Länge des Arrays ʺmyIntArrayʺ auf zwei, das heißt,<br />

es hat zwei Elemente. Wichtig: die Indizierung eines dynamischen Arrays beginnt immer bei<br />

Null! Das heißt, die Elemente des obigen Arrays haben die Nummern 0 und 1.<br />

Um mit einem zweidimensionalen, dynamischen Array zu arbeiten, muss man dessen<br />

Größe in beiden Dimensionen festlegen, da die Größe zu Anfang in beiden Dimensionen<br />

Null ist. Dies geht ganz analog zum Vorgehen bei nur einer Dimension, wenn man sich<br />

erinnert, wie dieses aufgebaut ist.<br />

SetLength(my2DIntArray, 3);<br />

SetLength(my2DIntArray[0], 1);<br />

SetLength(my2DIntArray[1], 5);<br />

SetLength(my2DIntArray[2], 7);<br />

Dieser Quelltext setzt in der ersten Zeile die Größe in der obersten Ebene des Arrays<br />

(die linke Seite bei der Darstellung des 2D-Arrays von oben) auf drei und setzt dann die


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 36/81<br />

Größe der drei Elemente dieser Ebene, also den dynamischen Arrays, die in my2DIntArray<br />

enthalten sind (die rechte Seite bei der Darstellung des 2D-Arrays von oben). Jedoch wird<br />

hier kein ʺrechteckigesʺ Array erstellt, sondern die einzelnen Zeilen sind nicht gleich lang.<br />

Das mit diesem Quelltext erstellte Array sieht so aus:<br />

x<br />

x x x x x<br />

x x x x x x x<br />

Möchte man ein rechteckiges Array haben, so gibt es eine sehr praktische Überladung<br />

der Prozedur ʺSetLengthʺ. Sie erhält ein Argument mehr, nämlich die Länge der<br />

ʺuntergeordnetenʺ Arrays. Folgender Quelltext erstellt ein ʺrechteckigesʺ Array mit zehn<br />

Zeilen und zehn Spalten:<br />

SetLength(mySecond2DIntArray, 10, 10);<br />

Die Größe eines Arrays abfragen<br />

Da bei einem dynamischen Array die Größe eine Variable ist, muss es, damit man<br />

vernünftig damit arbeiten kann, eine Möglichkeit geben, die Größe herauszufinden. Dafür<br />

bietet <strong>Delphi</strong> zwei Funktionen. ʺLengthʺ und ʺHighʺ, wobei ʺLengthʺ die Größe des Arrays<br />

ist und ʺHighʺ der höchste, zulässige Index. Bei einem dynamischen Arrys ist ʺLengthʺ also<br />

immer um eines Größer als ʺHighʺ.<br />

High(my2DIntArray);<br />

Dieser Aufruf liefert 2 zurück, denn das Array enthält ja drei Zeilen! Ein Aufruf von<br />

ʺLengthʺ würde 3 zurück geben. Um den höchsten zulässigen Index der ʺUnterarraysʺ<br />

herauszufinden, wendet man High einfach auf sie an:<br />

High(my2DIntArray[0]);<br />

liefert 0 zurück (weil es ja nur ein Element enthält) und<br />

High(my2DIntArray[3]);<br />

liefert 6 zurück. ʺLengthʺ würde 1 bzw. 7 zurückliefern.


dsdt dsdt.info dsdt<br />

Arrays kopieren<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 37/81<br />

Ein Array zu kopieren ist nicht so trivial, wie dies bei einer Variable ist. Bei einer<br />

Variable könnte man diese einfach einer anderen zuweisen. Bei einem Array funktioniert<br />

eine solche Zuweisung nicht! Eine Zuweisung wie diese<br />

array1 := array2;<br />

hätte ganz einfach den Effekt, dass nun ʺarray1ʺ ein Synonym für ʺarray2ʺ ist, dass<br />

heißt, dass ʺarray1ʺ und ʺarray2ʺ das selbe Array nur mit einem anderen Namen sind.<br />

ʺarray1ʺ ist dann eine Referenz auf ʺarray2ʺ. Erinnern Sie sich an ʺcall by referenceʺ!<br />

Aber natürlich gibt es eine Möglichkeit, auch ein Array zu kopieren und zwar mit der<br />

Funktion ʺcopyʺ. Diese erhält wahlweise einen oder drei Parameter. In der ersten Variante ist<br />

der einzige Parameter das zu kopierende Array, bei der zweiten Variante wird angegeben ab<br />

welchem Index wie viele Elemente kopiert werden sollen. Zwei Beispiele machen das<br />

deutlicher:<br />

array1 := Copy(array2); //kopiert array2 komplett in array1<br />

array1 := Copy(array2, 3, 4); //kopiert 4 Elemente beginnend mit<br />

// Index 3<br />

Aber Vorsicht: wenn ein Array einen Refernzdatentyp enthält, müssen die Elemente<br />

einzeln kopiert werden, dann erstellt auch Copy nur ein Array, welches Referenzen auf die<br />

selben Objekte enthält.<br />

statische Arrays<br />

Deklaration<br />

Die Deklaration von statischen Arrays ist nicht viel schwerer als die Deklaration von<br />

dynamischen Arrays. Man muss jedoch bei statischen Arrays die Größe mit angeben.<br />

Außerdem kann man auch den kleinsten Index angeben, dieser muss also nicht unbedingt 0<br />

sein. Den kleinesten Index eines Arrays erhalten Sie übrigens über die Funktion ʺLowʺ.<br />

my2DIntArray : Array[5..17, 9..20] of Integer;<br />

my2DIntArray : Array[5..17] of Array[9..20] of Integer;<br />

Beide Deklarationen ergeben das selbe Array. Eine Aufruf von ʺSetLengthʺ ist weder<br />

nötig noch möglich.


dsdt dsdt.info dsdt<br />

Arrays kopieren<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 38/81<br />

Bei statischen Arrays gibt es eine gute und ein schlechte Nachricht. Die Schlechte wie<br />

immer zuerst: der Copy-Befehl funktioniert nicht. Man muss also im Allgemeinen jedes<br />

Element einzeln kopieren. Und nun die Gute: manchmal braucht man das doch nicht. Denn<br />

dann, wenn das Arrays ausschließlich primitve Datentypen enthält (also Boolean, Integer,<br />

Char, ...), erzeugt eine einfache Zuweisung bei einem statischen Array eine Kopie des Arrays<br />

und nicht nur eine Referenz! In diesem Fall kann man ein Array also wie eine normale<br />

Variable kopieren.


dsdt dsdt.info dsdt<br />

Nochmal Variablen<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 39/81<br />

Die Benutzung von Variablen wurde ja schon besprochen und auch angewendet.<br />

Jedoch war das noch nicht alles. Bei einer so wichtigen Einrichtung wie Variablen muss noch<br />

ein wenig mehr beschrieben werden. So gibt es verschiedene Möglichkeiten, Variablen zu<br />

deklarieren. Ebenfalls wichtig und noch nicht besprochen ist die Speicherung von Zahlen.<br />

Diese zu kennen ist wichtiger, als man zuerst denken mag.<br />

Globale und lokale Deklaration<br />

Lokale Deklaration<br />

Wir haben bereits Prozeduren und Funktionen kennen gelernt. Auch in diesen kann<br />

man Variablen deklarieren. Diese dort deklarierten Variablen nennt man ʺlokal deklariertʺ.<br />

Sie gelten nur in dieser Prozedur bzw. Funktion (ich werde demnächst nur noch von<br />

Prozeduren sprechen, wenn nichts anderes gesagt wird, ist das Gesagte auch auf Funktionen<br />

anzuwenden!). Sobald das Programm diese Prozedur verlassen hat, gibt es die Variablen<br />

nicht mehr und sie sind nicht mehr bekannt. Auch ihren Wert verlieren sie. Dieser Wert<br />

taucht auch nicht mehr auf, wenn die Methode erneut aufgerufen wird. Ihr Wert ist dann<br />

wieder undefiniert.<br />

Hier ein Beispiel:<br />

procedure myProz;<br />

var i,a : Integer;<br />

begin<br />

for i:=0 to 10 do<br />

a:=a+1;<br />

end;<br />

Hier sind sowohl die Variable ʺiʺ und die Variable ʺaʺ nur lokal definiert. Sie sind also<br />

außerhalb dieser Prozedur nicht bekannt und man kann auf sie nicht zugreifen. Noch dazu<br />

ist der Wert der Variable a nicht definiert, da sie deklariert wurde, ihr aber vor<br />

Schleifeneintritt kein Wert zugewiesen wurde. Der <strong>Delphi</strong>-Compiler wird Sie jedoch darauf<br />

hinweisen.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 40/81<br />

Wenn Sie diese Prozedur nun zweimal hintereinander aufrufen, so hat a beim zweiten<br />

Durchlauf keinesfalls den Wert, den es nach dem ersten Durchlauf hatte. Der Wert ist wieder<br />

undefiniert, so als hätte es den ersten Aufruf der Prozedur nie gegeben.<br />

Globale Deklaration<br />

Sie haben aber auch die Möglichkeit, Variablen so zu deklarieren, dass diese in jeder<br />

Prozedur der Datei sichtbar und verwendbar ist. Diese global deklarierten Variablen<br />

verlieren ihren Wert niemals. Um eine solche Variable zu deklarieren, tun Sie dies außerhalb<br />

jeder Prozedur entweder direkt vor oder direkt nach dem Wort implementation. Die Position<br />

der Deklaration hat ebenfalls eine Bedeutung, auf die ich jedoch später eingehen werde.<br />

Die Deklaration erfolgt genau wie die Deklaration innerhalb einer Prozedur. Einfach<br />

mit dem Schlüsselwort ʺvarʺ einleiten und dann die Deklaration wie gehabt. Sie sollten<br />

jedoch dreimal überlegen und dann noch einmal, bevor Sie eine Variable global deklarieren.<br />

Denn dies widerspricht dem Prinzip der objektorientierten Programmierung, auf welche ich<br />

im nächsten Kapitel noch eingehen werde.<br />

Sollten Sie nach reiflicher Überlegung immer noch zu dem Schluss kommen, dass Sie<br />

eine Variable global deklarieren möchten, dann achten Sie darauf, dass Sie einen Namen<br />

wählen, der nicht mit einer lokalen Deklaration identsich und gut unterscheidbar ist. Für<br />

<strong>Delphi</strong> ist das zwar kein Problem, aber es beugt Missverständinssen auf Seiten des<br />

Programmierers vor. Sollten eine lokale und eine globale Variable aber doch einmal den<br />

gleichen Namen haben, so wird – falls man sich in der Prozedur mit der lokalen Variable<br />

befindet – die lokale Variable verwendet.<br />

Die Deklaration einer globalen Variable unterscheidet sich in einer Möglichkeit von der<br />

Deklaration einer lokalen Variable. Und zwar kann man einer globalen Variable direkt bei<br />

der Deklaration einen Wert zuweisen, einen Initialwert. Dies tut man folgendermaßen (als<br />

Beispiel):<br />

var myVar : Integer = 5;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 41/81<br />

Zahlendarstellung im Rechner<br />

Darstellung ganzer Zahlen<br />

Äußerst wichtig aber leider kaum beachtet ist die Zahlendarstellung im Rechner. Um<br />

zu verstehen, wie die Darstellung im Rechner funktioniert, muss man sich erst noch einmal<br />

klar machen, wie die ʺrichtigeʺ Zahlendarstellung funktioniert. Nehmen wir als Beispiel die<br />

Zahl 1234.<br />

Die Zahl ist<br />

4 mal 1 = 4*10 0<br />

plus 3 mal 10 = 3*10 1<br />

plus 2 mal 100 = 2*10 2<br />

plus 1 mal 1000 = 1*10 3<br />

Dabei nennt man die 10 in diesem Beispiel die ʺBasisʺ des Zahlensystems. Aus diesem<br />

Grund heißt unser Zahlensystem auch das ʺDezimalʺsystem. Die Zahl vor der 10 kann aus<br />

einem Bereich von 0 bis 9 stammen. Die Potenz an der Zehn kann eine beliebige ganze Zahl<br />

sein (für ganze Zahlen).<br />

Eine Darstellung zur Basis 10 ist in einem Computer jedoch nicht praktisch. Als viel<br />

praktischer erweist sich die Darstellung zur Basis 2, auch das ʺBinärʺsystem genannt. Der<br />

Computer kennt in seinem Innersten nur zwei Zustände, ʺanʺ und ʺausʺ. Oder auch ʺkein<br />

Stromʺ oder ʺStromʺ. In der Informatik: Null oder Eins. Und weil dies genau zwei Zustände<br />

sind, ist eine Zahlendarstellung zur Basis 2 bei einem Computer viel besser als zur Basis 10.<br />

Die Darstellung funktioniert genauso wie die Darstellung zur Basis 10, nur dass man<br />

diese Darstellung nicht gewohnt ist und man daher etwas länger braucht, um sich zu<br />

überlegen, wie sich eine Zahl zusammensetzt. Als Beispiel (auf Grund der Schwierigkeiten<br />

dabei auch etwas kürzer ;-)):<br />

12 =<br />

0 mal 1 = 0*2 0<br />

plus 0 mal 2 = 0*2 1


dsdt dsdt.info dsdt<br />

= (1100)2<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 42/81<br />

plus 1 mal 4 = 1*2 2<br />

plus 1 mal 8 = 1*2 3<br />

Dabei bedeutet die Zahl in Klammern mit dem Index 2, dass man die Zahl zur Basis 2<br />

darstellt. Man lässt diese Schreibweise weg, wenn die Zahl im Dezimalsystem dargestellt<br />

wird. Die Basis wird immer in Dezimaldarstellung angegeben.<br />

Ebenfalls wichtig im Bereich des Computers ist das Hexadezimalsystem, also die<br />

Schreibweise zur Basis 16. Da wir aber nur zehn arabische Ziffern kennen, benutzt man für<br />

die Ziffern 10, 11, 12, 13, 14, 15 (und im Hexadezimalsystem sind die Ziffern) die Buchstaben<br />

von A bis F. Die Zahlen von 0 bis 15 lauten im Hexadezimalsysten dann:<br />

0,1,...,A,B,C,D,E,F<br />

Die Darstellung im Hexadezimalsystem wir sehr gerne im Bereich der Farbangaben<br />

verwendet. Um für jede Grundfarbe (Rot, Grün, Blau) eine Abstufung von 255 Schritten zu<br />

erreichen (und damit ca. 1,6 Millionen verschiedene Farben), bräuchte man normalerweise<br />

(also im Dezimalsystem) neun Stellen. Für jede Farbe drei.<br />

Schreibt man eine solche Farbangabe jedoch im Hexadezimalsystem, so benötigt man<br />

nur noch sechs Stellen, da gilt:<br />

255 = (FF)16<br />

Darstellung rationalen Zahlen<br />

Bisher wurde nur beschrieben, wie man ganze Zahlen darstellen kann. Nun braucht<br />

man in genauso vielen Fällen aber rationale Zahlen, sprich ʺKommazahlenʺ. Nur eine (wenn<br />

auch große) Teilmenge davon ist im Rechner darstellbar: die rationalen Zahlen, also Zahlen,<br />

die sich als Bruch darstellen lassen.<br />

Prinzipiell wäre es möglich, rationale Zahlen analog zu den ganzen Zahlen<br />

darzustellen, indem man die Potenzen an der Basis einfach in den negativen Bereich<br />

erweitert. Als Beispiel:<br />

2 3 + 2 1 + 2 0 + 2 -1 + 2 -3 + 2 -4 + 2 -7 =


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 43/81<br />

8 + 2 + 1 + 1/2 + 1/8 + 1/16 + 1/128 =<br />

11,6953125<br />

Dies nennt man eine ʺFestkommadarstellungʺ, da sich das Komma immer an einer<br />

beliebigen, aber festen Stelle befindet.<br />

Es hat sich jedoch die so genannte ʺFließkommadarstellungʺ oder auch<br />

ʺGleitkommadarstellungʺ durchgesetzt. Dies ist eine Darstellung von der Form:<br />

z = m*b e<br />

also z.B.:<br />

300 = 3*10 2<br />

Dabei heißt m die ʺMantisseʺ (dies ist in der Regel eine Festkommazahl), b ist die<br />

ʺBasisʺ und e der ʺExponentʺ. Die Basis muss dabei nicht mit der Basis der<br />

Zahlendarstellung übereinstimmen. Als Beispiel:<br />

1228,8 = 2,4*8 3 b=8, e=3, m=2,4<br />

Da man bei der Darstellung in der Fließkommadarstellung sehr viel Freiheiten hat,<br />

eine Zahl darzustellen, hat man sich auf einen Standard geeinigt. Dieser Standard ist in der<br />

Norm IEEE 754 festgeschrieben. Zum einen wird festgelegt, dass die Basis immer 2 ist, aus<br />

den gleichen Gründen, die schon bereits bei der Binärdarstellung erleutert wurden. Zum<br />

anderen wird festgelegt, wie die Bits bei einer 32bit-Darstellung (64bit-Darstellung) verteilt<br />

werden:<br />

1. 1 Bit wird für das Vorzeichen verwendet.<br />

2. 8 Bits (11 Bits) werden für den Exponenten verwendet. Dabei wird der Exponent als d<br />

– 127 dargestellt und nur das d gespeichert.<br />

3. 23 Bits (52 Bits) werden für die Mantisse verwendet. Dabei wird festgelegt, dass die<br />

Mantisse in der normalen Darstellung die Form 1,f hat und nur die Darstellung des f<br />

wird gespeichert.<br />

4. In der subnormalen Darstellung (für sehr kleine Zahlen) gelten andere Regeln, um<br />

eine größere Genauigkeit zu erreichen:


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 44/81<br />

1. Der Exponent wird als -126 angenommen.<br />

2. Ein Bit wird auf das Vorzeichen verwendet.<br />

3. Die restlichen Bits werden für die Mantisse verwendet, wobei davon<br />

ausgegangen wird, dass die Mantisse die Form 0,f hat und nur das f<br />

gespeichert wird.<br />

5. Um pos. oder neg. Unendlich darzustellen, setzt man das Bit für das Vorzeichen<br />

entsprechend, setzt den Wert für die Mantisse auf Null und den Wert für den<br />

Exponenten auf 255.<br />

6. Um den Wert ʺNot a Number (NaN)ʺ darzustellen, setzt man das Bit für das<br />

Vorzeichen auf einen beliebigen Wert, den Wert für den Exponenten auf 255 und den<br />

Wert für die Mantisse auf einen Wert größer als Null.<br />

Im Gegensatz zur Darstellung von ganzen Zahlen, ist es bei der Darstellung von<br />

rationalen Zahlen nicht möglich, einen Zahlenbereich komplett abzubilden. Es wird immer<br />

ʺLückenʺ geben, Zahlen können nur mit einer gewissen Genauigkeit dargestellt werden.<br />

Auch Rechenoperationen an diesen Zahlen führen zu Ungenauigkeiten.<br />

So muss zum Beispiel für die Addition zweier Fließkommazahlen der Exponent der<br />

kleiner Zahl dem Exponenten der größeren Zahl angeglichen werden. Dies führt zu einem<br />

enormen Verlust von Genauigkeit.<br />

Alle diese Probleme kann man nicht beheben, aber es ist sehr wichtig, sie zu kennen.<br />

So kann man seine Berechnungen so aufbauen, dass man möglichst wenig Genauigkeit<br />

verliert. Auch ist es wichtig, dass man weiß, dass bei der Darstellung als ganze Zahl diese<br />

Darstellung exakt ist, bei der Darstellung als Fließkommazahl jedoch nicht.<br />

Welche Darstellung verwendet wird, hängt davon ab, welchen Datentyp Sie<br />

verwenden.<br />

Umwandlung von Zahlen und Darstellungsbereiche<br />

Vorweg ein paar Fachbegriffe: Die Umwandlung eines Datentyps in einen anderen<br />

(egal ob Zahentyp oder nicht) heißt ʺcastʺ. Dabei gibt es explizite und implizite casts. Ein


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 45/81<br />

impliziter cast wird vom Compiler durchgeführt, ohne dass der Programmierer etwas<br />

merkt. Er funktioniert wie eine Zuweisung zwischen zwei Variablen des selben Typs, nur<br />

dass sie bei einem impliziten cast nicht den selben Typ haben. Bei einem expliziten cast<br />

muss der Programmierer explizit angeben, dass er den einen Typ in den anderen<br />

umwandeln will.<br />

Seien Sie beim casten immer sehr vorsichtig. Nicht immer weist Sie der Compiler<br />

darauf hin, wenn durch eine Umwandlung Daten verloren gehen können. Es liegt dann in<br />

der Verantwortung des Programmierers – Ihrer Verantwortung – sich darüber Gedanken zu<br />

machen. Wenn man jedoch die nötige Vorsicht walten lässt, kann ein cast ein sehr<br />

praktisches und mächtiges Werkzeug sein.<br />

In diesem Abschnitt sei kurz auf das casten zwischen Zahlentypen eingegangen und<br />

auch auf den Darstellungsbereich dieser Typen. Dabei unterscheiden wir strikt zwischen den<br />

Integertypen und den Floattypen. Wie bereits gesagt, bilden Integertypen einen Bereich der<br />

natürlichen Zahlen lückenlos ab.<br />

Dabei bildet der Datentyp ʺIntegerʺ je nach Compiler immer unterschiedliche<br />

Zahlenbereiche ab. Und zwar ist dieser Zahlenbereich immer dadurch definiert, dass er auf<br />

einem 32bit-System immer 32 Bit an Speicher belegt, auf einem 16bit-System 16 Bit an<br />

Speicher, usw. Natürlich nur, wenn der Compiler für das jeweilige System geschrieben<br />

wurde. Dies führt dazu, dass der Integertyp immer eine optimale Geschwindigkeit<br />

bereitstellt.<br />

In aktuellen Compiler-Versionen von <strong>Delphi</strong> ist der Integer ein 32bit-Datentyp und<br />

identisch mit dem LongInt. Für größere Zahlen stellt <strong>Delphi</strong> auch noch einen 64bit-<br />

Integertypen, den Int64 zur Verfügung. Auch kleinere Integertypen, wie den SmallInt oder<br />

ShortInt gibt es. Am schnellsten ist jedoch immer der Integer.<br />

Dies waren bisher alles Integertypen, die vorzeichenbehaftete Zahlen darstellen.<br />

Rechnet man immer nur mit nicht-negativen oder nicht-positiven Zahlen, so muss das<br />

Vorzeichen nicht mitgespeichert werden und statt den negativen Zahlenbereich abzubilden,<br />

kann man einen doppelt so großen positiven Zahlenbereich darstellen.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 46/81<br />

Auch hierfür bietet <strong>Delphi</strong> Datentypen und sogar einen ʺgenerischenʺ Datentypen<br />

analog zum Integer. Dies ist der Datentyp ʺCardinalʺ, dessen Bereich auch wieder vom<br />

Compiler abhängt. Momentan nimmt auch er 32 Bit in Anspruch und stellt damit einen<br />

Zahlenbereich von 0 bis 4294967295 dar und ist damit identisch mit dem Datentyp<br />

ʺLongwordʺ. Hier kann <strong>Delphi</strong> jedoch nichts Größeres anbieten, nur die kleineren Typen<br />

Word und Byte stellt es zur Verfügung.<br />

Damit sei auch genug zur Darstellung von ganzen Zahlen gesagt. <strong>Delphi</strong> bietet<br />

logischer Weise auch Datentypen an, um reelle Zahlen darzustellen. Im Unterschied zu der<br />

Darstellung von Ganzzahlen liegt darin, dass bei der Darstellung Floats nicht nur auf den<br />

Darstellungsbereich, sondern auch auf die Genauigkeit acht gegeben werden muss.<br />

Der Datentyp mit dem größten Darstellungsbereich ist der Extended. Er bietet<br />

außerdem eine sehr hohe Genauigkeit. Dafür genehmigt er sich aber auch satte 10 Byte<br />

Speicherplatz. Auf 32bit-Systemen am schnellsten ist der Datentyp ʺSingleʺ, da er 4 Byte also<br />

32 Bit in Anspruch nimmt und somit optimal verarbeitet werden kann. Dafür ist der<br />

Darstellungsbereich wesentlich kleiner und auch die Genauigkeit ist nicht mit der des<br />

Extended zu vergleichen.<br />

Ein guter Mittelweg, welcher auch für die meisten wissenschaftlichen Anwendungen<br />

ausreicht, ist der ʺDoubleʺ. Er bietet eine ausreichende Genauigkeit und Größe für so<br />

ziemlich jede Anwendung. Der generische Datentyp Real ist in seiner gegenwärtigen<br />

Implementation identisch mit dem Double.<br />

Ein Spezialfall stellt der Typ ʺCurrencyʺ dar. Er ist kein Fließkomma-Datentyp, also<br />

kein Float! Beim Datentyp Currency handelt es sich um einen Festkomma-Datentypen,<br />

welcher für finanz-mathematische Anwendungen entworfen wurde, da die Festkomma-<br />

Darstellung Rundungsfehler minimiert. Weitere Informationen dazu sollten in der <strong>Delphi</strong>-<br />

Hilfe unter ʺReelle Typenʺ nachgeschlagen werden.<br />

Nun möchte ich wie versprochen jedoch auch noch kurz auf die Umwandlung von<br />

Datentypen ineinader eingehen. Nehmen wir folgendes Beispiel:<br />

var a : Integer;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 47/81<br />

b : ShortInt;<br />

begin<br />

a := 204;<br />

b := a;<br />

ShowMessage(IntToStr(b));<br />

end;<br />

Hier weisen wir einem Integer zuerst den Wert 204 zu, anschließend weisen wir diesen<br />

Wert einem ShortInt zu. Die 204 sprengt jedoch den Darstellungsbereich eines ShortInts.<br />

Und was macht der Rechner: er fängt wieder von vorne an zu zählen, was in diesem Fall<br />

heißt, dass er im negativen Bereich weitermacht. Daher wird auch -52 ausgegeben und nicht<br />

204.<br />

Hierbei gibt <strong>Delphi</strong> keine Warnung aus! Solche Aktionen liegen in der Verantwortung<br />

des Programmierers!


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 48/81<br />

Objektorientierte Programmierung<br />

<strong>Delphi</strong> Language war früher ʺObject Pascalʺ. Dieser Name impliziert eine<br />

Funktionalität, welche in der Welt der Programmierung nicht mehr wegzudenken ist: die<br />

objektorientierte Programmierung, kurz OOP. Ich kann diese Art der Programmierung hier<br />

nicht in der Ausführlichkeit besprechen, wie sie es verdient hätte, man kann ganze Bücher<br />

über OOP schreiben.<br />

Jedoch wäre dies kein <strong>Crashkurs</strong> über <strong>Delphi</strong>, wenn die OOP keinen Platz darin hätte.<br />

Und so werde ich im Folgenden eine kurze Einfühung zur OOP im Allgemeinen geben und<br />

dann aufzeigen, wie sie in <strong>Delphi</strong> umgesetzt wurde und verwendet werden kann. Der<br />

Anfang mag etwas theoretisch sein, jedoch bietet die OOP dem Programmierer enorme<br />

Möglichkeiten, sein Programm übersichtlicher und auch zeitsparender zu schreiben.<br />

Ein bisschen Theorie<br />

Nehmen wir als Objekt ein Rechteck her. Dieses Rechteck hat verschiedene<br />

Eigenschaften, wie z.B. Höhe und Breite oder auch seine Position im Raum. Auch kann man<br />

mit einem Rechteck bestimmt Aktionen verbinden: Man kann es verschieben oder auch<br />

seinen Flächeninhalt berrechnen.<br />

Diese Darstellung von Objekten wurde auf die Informatik übertragen. Man ordnet<br />

einem Objekt in der Informatik Methoden (das sind an eine Klasse gebundene Prozeduren<br />

oder Funktionen) und Eigenschaften (auch ʺFelderʺ genannt) zu (in <strong>Delphi</strong> gibt es noch<br />

ʺPropertiesʺ, das ist aber etwas anderes, daher lasse ich das im Englischen). Dadurch wird<br />

ein erster Vorteil der OOP deutlich: sie schafft Ordnung, weil sofort klar wird, welche<br />

Methode und welche Information wohin gehören.<br />

Objekte gleicher Art (ʺRechteckeʺ) fasst man als ʺKlasseʺ zusammen. Eine Klasse<br />

beschreibt also, welche Methoden und Eigenschaften ein solches Objekt habe muss. Ein<br />

ʺwirklichesʺ, verwendbares Objekt nennt man auch eine ʺInstanzʺ einer Klasse. Man kann<br />

beliebig viele Instanzen einer Klasse anlegen. Logisch: Es gibt ja auch beliebig viele<br />

Rechtecke. ;-)


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 49/81<br />

Elementarer Bestandteil der OOP ist die Vererbung. Um dies deutlich zu machen,<br />

ziehen wir ein weiteres Beispiel heran. Man kann sich eine Klasse ʺgeometrische Formʺ<br />

vorstellen. Diese geometrische Form wird eine Position im Raum besitzen und eine Fläche<br />

(welche natürlich auch Null sein kann) und man kann sie auch verschieben. Dabei können<br />

wir über die Funktion der Flächenberechnung nur sagen, dass es sie geben wird, aber sie<br />

wird für die Klasse der geometrischen Formen noch keine Bedeutung haben. Man bezeichnet<br />

sie als ʺabstraktʺ.<br />

Nun kann man von einer Klasse (wie z.B. der geometrischen Formen) weitere Klassen<br />

ʺableitenʺ. Das heißt, man bildet eine Klasse als Spezialfall einer anderen Klasse. So ist ein<br />

Rechteck ein Spezialfall einer geometrischen Form. Dabei ʺerbtʺ ein Rechteck die Eigenschaft<br />

ʺPositionʺ und die Methode zur Verschiebung. Es besitzt die neuen Eigenschaften ʺHöheʺ<br />

und ʺBreiteʺ. Außerdem füllt es die Funktion zur Flächenberechnung mit Leben.<br />

Wir werden später sehen, dass die Vererbung sehr praktisch sein kann. So muss man<br />

z.B. die Methode zur Verschiebung einmal schreiben und kann sie dann für alle<br />

geometrischen Formen (Rechteck, Kreis, Dreieck, ...) verwenden, ohne sie nochmals neu<br />

schreiben zu müssen. Auch die Position muss man nicht neu implementieren.<br />

Ein weiterer wichtiger Bestandteil der OOP ist die ʺSichtbarkeitʺ. So werden oft<br />

Informationen in Objekten ʺgekapseltʺ, das heißt, sie sind nur innerhalb des Objektes<br />

sichtbar und können von außen nur über einen festen Satz von Methoden manipuliert<br />

werden. Dieser Satz von Methoden stellt dann eine Schnittstelle zwischen der Außenwelt<br />

und den Informationen dar.<br />

Dabei gibt es in jeder Programmiersprache verschiedene Stufen der Sichtbarkeit (nicht<br />

alle werden in <strong>Delphi</strong> verwendet). Es gibt Methoden und Eigenschaften, welche nur in der<br />

betreffenden Klasse sichtbar sind. Dann gibt es Methoden und Eigenschaften, welche nur in<br />

der betreffenden Klasse und in allen abgeleiteten Klassen sichtbar sind. Und es gibt<br />

Methoden und Eigenschaften, welche öffentlich sichtbar sind.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 50/81<br />

Verschiedene Programmiersprachen bieten ja nach ihren Eigenarten noch weitere<br />

Arten der Sichtbarkeit und manche Programmiersprachen bieten im Gegenzug auch manche<br />

der oben genannten Sichtbarkeiten nicht oder nur in angewandelter Form.<br />

Dies war wirklich nur ein winziges bisschen Theorie, eigentlich nur eine Einleitung,<br />

damit die Praxis etwas besser zu verdauen ist. Und mit der möchte ich nun weiter machen,<br />

weil dann vieles von dem, was ich oben beschrieben habe, klarer werden wird.<br />

Deklaration und Implementation<br />

Hier gibt es wieder etwas für Sie zu tun, sie können folgende Quelltexte in Ihrem<br />

<strong>Delphi</strong> mitschreiben. Legen Sie dazu erst einmal eine neues Projekt an und speichern Sie es<br />

unter einem sinnvollen Namen (also nicht gerade ʺProject1ʺ ;-)). Wählen Sie dann im Menü<br />

ʺDateiʺ->ʺNeuʺ->ʺUnitʺ. Das Gerüst einer leeren Unit sollte erscheinen, wahrscheinlich mit<br />

Namen ʺUnit2ʺ. Speichern Sie auch diese Datei und zwar unter dem Namen<br />

ʺgeomForm.pasʺ, denn genau das wird diese Datei enthalten: die Klasse für geometrische<br />

Objekte. Der neue Name sollte nun auch automatisch im Quelltext verewigt sein.<br />

Zuerst einmal sei hier die Definiton einer Klasse in <strong>Delphi</strong> gezeigt. Diese Deklaration<br />

erfolgt – wie jede andere auch – im interface-Teil des Programmes:<br />

type<br />

TgeomForm = class<br />

end;<br />

Dies ist das mindeste, was man für die Definition einer Klasse in <strong>Delphi</strong> braucht.<br />

Damit kann man natürlich noch nicht viel anfangen, denn außer des Namens wurde noch<br />

nichts festgelegt. Es sollen nun im folgenden die verschiedenen Eigenschaften und<br />

Methoden für eine geometrische Form hinzugefügt werden.<br />

Dabei hat <strong>Delphi</strong> (bis zu Version 7) die Eigenart, dass die geringste Sichtbarkeit nicht<br />

die ist, in der der Elemente nur innerhalb einer Klasse sichtbar sind, sondern die geringste<br />

Sichtbarkeit ist die, in der Elemente nur in der Klasse und in anderen Klassen der gleichen Unit<br />

sichtbar sind. Diese Sichtbarkeit nennt sich ʺprivateʺ.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 51/81<br />

Entsprechend gibt es auch die Sichtbarkeit für die Klasse selbst und abgeleitete<br />

Klassen, sondern diese Elemente sind auch für alle anderen Klassen in der selben Unit<br />

sichtbar! Diese Sichtbarkeit nennt sich ʺprotectedʺ. Dies ist ein deutlicher Unterschied zu<br />

anderen Programmiersprachen, wie C++ oder Java, wo die Sichtbarkeiten denselben Namen<br />

haben, die Erweiterung der Sichtbarkeit auf die gleiche Unit jedoch nicht vorhanden ist!<br />

Um dieses Problem zu umgehen, kann man einfach für jede Klasse eine Unit<br />

reservieren. Dies ist auf Grund der übersichtlichkeit sowieso sehr nützlich und man sollte es<br />

sich schon sehr gut überlegt haben, bevor man zwei Klassen in einer Unit anlegt.<br />

Nun zurück zum Beispiel einer geometrischen Form: Die Position soll als private<br />

deklariert werden und aus einer x- und einer y-Koordinate bestehen. Diese Koordinaten<br />

sollen als Integer deklariert werden und die Pixel auf der Zeichenfläche des Bildschirms<br />

darstellen.<br />

Die Definition der Klasse sieht nun so aus:<br />

type<br />

TgeomForm = class<br />

private<br />

Fx : Integer;<br />

Fy : Integer;<br />

end;<br />

Man sieht, dass für ʺFyʺ nicht erneut der Bezeichner ʺprivateʺ voran gestellt werden<br />

musste. Dies liegt daran, dass <strong>Delphi</strong> immer die Sichtbarkeit des vorangehenden Elementes<br />

verwendet, wenn vom Programmierer nichts anderes festgelegt wird. Wird für das erste<br />

Element keine Sichtbarkeit angegeben, so wird Standardmäßig ʺpublicʺ verwendet, das<br />

heißt, das Element ist öffentlich sichtbar.<br />

Es ist übrigens kein Schreibfehler, dass den Koordinaten ein ʺFʺ voran gestellt wurde:<br />

es ist üblich, als ʺprivateʺ deklarierte Eigenschaften (nicht die Methoden) mit einem<br />

vorangestellten ʺFʺ (für ʺFeldʺ bzw. ʺfieldʺ) zu kennzeichnen. Wozu dies gut ist, wird später<br />

noch deutlich werden, wenn die Properties beschrieben werden.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 52/81<br />

Als nächstes soll die Methode zur Verschiebung einer geometrischen Form eingebaut<br />

werden. Dieses Beispiel zeigt auch, wie man die Methoden eines Objektes konkret<br />

implementiert. Dazu muss man sie zuerst deklarieren:<br />

type<br />

TgeomForm = class<br />

private<br />

Fx : Integer;<br />

Fy : Integer;<br />

public<br />

procedure verschieben(dx, dy : Integer);<br />

end;<br />

Achten Sie bitte darauf, dass die Prozedur zum Verschieben öffentlich verwendbar<br />

sein soll und daher mit einem vorangestellten ʺpublicʺ ausgestattet wurde. Ansonsten wird<br />

sie wie eine ganz normale Prozedur deklariert.<br />

Nun muss diese Prozedur noch mit Leben gefüllt werden. Dies tut man natürlich im<br />

implementation-Teil des Programmes.<br />

procedure TgeomForm.verschieben(dx, dy: Integer);<br />

begin<br />

self.Fx := self.Fx + dx;<br />

self.Fy := self.Fy + dy;<br />

end;<br />

Dieser Codeabschnitt wird nun etwas genauer betrachtet. Die erste Zeile gibt an,<br />

welche Methode hier implementiert werden soll. Dabei wird die volle Deklaration der<br />

Methode (inkl. der Angabe, ob es sich um eine Prozedur oder Funktion handelt) um die<br />

Angabe erweitert, zu welcher Klasse die Methode gehört, in diesem Fall ʺTgeomFormʺ.<br />

Die Implementation einer Methode folgt ansonsten den gleichen Regeln wie eine<br />

normale Prozedur oder Funktion. Jedoch wird oben schon deutlich, dass für die<br />

Verwendung innerhalb von Klassen noch ein paar Bezeichner hinzugefügt wurden, so z.B<br />

das Wort ʺselfʺ. Dieses Wort bezeichnet die Instanz, zu der die Methode gehört. Beachten<br />

Sie: es bezeichnet wirklich das konkrete Objekt (die Instanz) und nicht nur die Klasse!<br />

Somit wird auch deutlich, wie man auf die Eigenschaften eines Objektes zugreift,<br />

sofern sie sichtbar sind: der Name des Objektes und dann – durch einen Punkt getrennt – der


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 53/81<br />

Name der Eigenschaft. Entsprechend ruft man übrigens auch die entsprechenden Methoden<br />

auf.<br />

Verwendung – Teil 1<br />

Bevor nun die Methode zur Flächenberechnung implementiert will, soll erst einmal die<br />

Verwendung einer solchen Klasse demonstriert werden und dann auch noch die Erstellung<br />

einer abgeleiteten Klasse vorgestellt werden. Denn das ist die Voraussetzung, um die<br />

abstrakte Methode zur Flächenberechnung korrekt einzuführen.<br />

Um eine Klasse zu verwenden, müssen Sie dem Compiler sagen, wo er diese Klasse<br />

findet, also in welcher Unit sie deklariert und implementiert wurde. Solche Angaben macht<br />

man im ʺusesʺ-Abschnitt einer Unit. Wählen Sie die Unit, in welcher sich Ihre Form befindet<br />

(wahrscheinlich ʺUnit1ʺ) und suchen Sie dort den uses-Abschnitt. Erweitern Sie ihn so, dass<br />

er wie folgt aussieht:<br />

uses<br />

Windows, Messages, SysUtils, Variants, Classes, Graphics,<br />

Controls, Forms, Dialogs, geomForm;<br />

Nachdem der Compiler nun weiß, wo er zu suchen hat, kann man die Klasse<br />

verwenden. Es ist egal, wo Sie das tun, man kann es auch direkt bei Erstellung der Form<br />

machen. Klicken Sie also doppelt auf ʺForm1ʺ, um die Methode für das entsprechende<br />

ʺOnCreateʺ-Ereignis anzulegen. Bauen Sie dann folgenden Quelltext ein, er wird nur ein<br />

wenig weiter unten erklärt.<br />

(Folgender Quelltext ist noch nicht ganz korrekt, es fehlt etwas, das sich<br />

ʺSpeicherschutzblockʺ nennt, darauf werde ich im Kapitel über Exceptions noch eingehen. Es<br />

funktioniert auch ohne und für unsere Zwecke reicht das erst einmal.)<br />

procedure TForm1.FormCreate(Sender: TObject);<br />

var geomForm : TgeomForm;<br />

begin<br />

geomForm := TgeomForm.Create;<br />

geomForm.verschieben(10,10);<br />

geomForm.Free;<br />

end;<br />

Was passiert hier? Zuerst einmal wird eine Variable ʺgeomFormʺ vom Typ<br />

ʺTgeomFormʺ wie jede andere Variable auch deklariert. Doch dies reicht bei Objekten jedoch


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 54/81<br />

nicht. Um ein Objekt zu verwenden, muss man es erst noch erstellen. Bei diesem Vorgang<br />

erst wird für das Objekt Speicher reserviert. Dies ist übrigens auch der Vorgang, den man als<br />

ʺInstanz erstellenʺ bezeichnet.<br />

Das Erstellen eines Objektes funktioniert über einen Konstruktor, was auch nur eine<br />

besondere Methode ist und daher genauso verwendet wird. Diese Methode ist in diesem Fall<br />

ʺCreateʺ. Nun stellt sich die Frage, woher diese Methode kommt, denn sie wurde in<br />

TgeomForm weder implementiert noch deklariert.<br />

Sie kommt aus der Klasse ʺTObjectʺ. Denn alle Klassen, bei denen nicht explizit<br />

angegeben wird, von welcher sie abgeleitet werden sollen, werden implizit von TObject<br />

abgeleitet und erben somit auch alle Methoden und Eigenschaften von TObject. Um hier mal<br />

die <strong>Delphi</strong>-Hilfe zu zitieren: ʺTObject ist der Ausgangspunkt der Klassenhierarchie,<br />

sozusagen der Urahn aller Objekte und Komponenten.ʺ<br />

Nachdem das Objekt erstellt wurde, wird als Beispiel die Methode zum Verschieben<br />

aufgerufen. Hier sollten keine weiteren Überraschungen zu sehen sein, der Aufruf ist ansich<br />

wie der jeder anderen Methode, nur das man noch das Objekt (nicht die Klasse) angeben<br />

muss, zu dem die Methode gehört.<br />

In der nächsten Zeile wird das Objekt ʺfreigegebenʺ. Das führt dazu, dass das Objekt<br />

wieder aus dem Speicher entfernt und und der entsprechende Speicherbereich wieder<br />

freigegeben wird. Auch diese Methode stammt von ʺTObjectʺ und wird von dort<br />

übernommen. Es gibt auch noch die Methode ʺDestroyʺ. Diese tut im Prinzip dasselbe,<br />

jedoch prüft ʺFreeʺ noch, ob das Objekt überhaupt existiert, bevor ʺDestroyʺ aufgerufen wird.<br />

Dadurch werden Fehler vermieden. Rufen Sie also immer ʺFreeʺ auf.<br />

Nun ist klar, wie man eine Klasse und die daraus instanzierten Objekte verwendet.<br />

Nun soll gezeigt werden, wie man von einer Klasse eine andere ableitet. Im folgenden wird<br />

dies am Beispiel einer Klasse für Rechtecke demonstriert.


dsdt dsdt.info dsdt<br />

Ableiten von Klassen<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 55/81<br />

Das Ableiten einer Klasse von einer anderen ist nicht sehr schwer. Die Deklaration<br />

erfolgt fast genauso wie bei einer ʺnormalenʺ Deklaration, mit der Ausnahme, dass man noch<br />

angibt, von welcher Klasse die deklarierte Klasse abgeleitet wird. Erstellen Sie eine weitere<br />

Unit und speichern Sie als ʺrechteck.pasʺ ab. Erstellen Sie einen uses-Abschnitt, der<br />

ausschließlich den Eintrag ʺgeomFormʺ enthält. Schreiben Sie außerdem folgendes in den<br />

interface-Teil:<br />

type<br />

TRechteck = class(TgeomForm)<br />

end;<br />

Damit ist das Ableiten einer Klasse von einer anderen eigentlich schon fertig.<br />

ʺTRechteckʺ ist nun ein Nachfahre von ʺTgeomFormʺ und besitzt somit auch die Methode<br />

ʺverschiebenʺ. Nun kann man aber über ein Rechteck ein bisschen mehr aussagen, als man<br />

über eine allgemeine, geometrische Form sagen kann. So hat ein Rechteck bespielsweise<br />

Höhe und Breite! Und diese beiden Werte sollen in der Klasse ʺTRechteckʺ gespeichert<br />

werden.<br />

type<br />

TRechteck = class(TgeomForm)<br />

private<br />

Fhoehe : Integer;<br />

Fbreite : Integer;<br />

end;<br />

Diese Eigenschaften (die wiederrum nur in ʺTRechteckʺ und Klassen in der gleichen<br />

Unit sichtbar sind) kommen zu den Eigenschaften hinzu, welche schon von ʺTgeomFormʺ<br />

geerbt wurden. Nur gewinnen wir damit nichts, weil wir mit ʺTRecheckʺ noch nicht mehr<br />

anfangen können als mit ʺTgeomFormʺ.<br />

Abstrakte Methoden<br />

Um einen Gewinn zu erreichen, erweitern wir nun ʺTgeomFormʺ erst einmal um eine<br />

abstrakte Methode, also eine Methode, welche in ʺTgeomFormʺ zwar deklariert aber noch<br />

nicht implementiert wird. Sie ahnen es sicherlich schon, es geht um die Flächenberechnung<br />

für ein geometrisches Objekt. Erweitern Sie die Klasse ʺTgeomFormʺ so, dass sie wie folgt<br />

aussieht:


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 56/81<br />

type<br />

TgeomForm = class<br />

private<br />

Fx : Integer;<br />

Fy : Integer;<br />

public<br />

procedure verschieben(dx, dy : Integer);<br />

function flaeche : Integer; virtual; abstract;<br />

end;<br />

Das Bedarf nun doch einiger Erklärung. Der erste Teil sollte klar sein: Die Klasse wird<br />

um eine Funktion mit Namen ʺflaecheʺ erweitert, welche einen Integer zurückliefert - eben<br />

die Fläche. Aber was bedeuten die beiden Schlüsselworte dahinter?<br />

Zuerst einmal zum Wort ʺvirtualʺ. Dies bedeutet, dass diese Methode in von<br />

ʺTgeomFormʺ abgeleiteten Klassen überschrieben (also mit einer neuen Bedeutung versehen)<br />

werden darf. Die Methode aus der Mutterklasse ist in der abgeleiteten Klasse dann nicht<br />

mehr zu sehen, nur die ʺneueʺ Methode gleichen Namens. Mehr dazu gibt es noch im<br />

nächsten Abschnitt.<br />

Das Wort ʺabstractʺ sollte nach dem Theorie-Teil schon fast selbst erklärend sein. Es<br />

signalisiert dem Compiler, dass es sich bei der Methode ʺflaecheʺ um eine abstrakte Methode<br />

handelt, sie also in ʺTgeomFormʺ nur deklariert, aber nicht implementiert (mit Leben gefüllt)<br />

wird. Das wird dann in einer der abgeleiteten Klassen gemacht, was ja auch der Plan war, als<br />

wir anfingen, die Klasse ʺTgeomFormʺ zu erweitern. ;-)<br />

Und diesen Plan werden wir nun vervollständigen, indem wir schlussendlich die<br />

Methode zur Flächenberechnung in der Klasse ʺTRechteckʺ deklarieren und implementieren.<br />

Die Deklaration sieht so aus:<br />

type<br />

TRechteck = class(TgeomForm)<br />

private<br />

Fhoehe : Integer;<br />

Fbreite : Integer;<br />

public<br />

function flaeche : Integer; override;<br />

end;<br />

Eine Mehtode, welche in der Mutterklasse als ʺabstractʺ deklariert wurde, muss also in<br />

der abgeleiteten Klasse, sofern sie dort implementiert werden soll, nochmals deklariert


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 57/81<br />

werden. Jedoch reicht eine einfache Deklaration nicht aus, sondern es muss auch noch das<br />

Schlüsselwort ʺoverrideʺ hinzugefügt werden. Das signalisiert dem Compiler, dass die<br />

Methode der Mutterklasse überschrieben werden soll.<br />

Nun muss diese Methode noch implementiert werden, natürlich im implemenation-<br />

Teil der Unit:<br />

function TRechteck.flaeche: Integer;<br />

begin<br />

result := Fhoehe*Fbreite;<br />

end;<br />

Diese Funktion würde nun also die Fläche des Rechtecks zurück geben. Wenn nur<br />

irgendwo jemals definiert worden wäre, welche Höhe und Breite das Rechteck hat. Denn so,<br />

wie die Klasse momentan aussieht, kann man das gar nicht bestimmen, Höhe und Breite<br />

sind immer Null! Darum müssen Sie sich also noch kümmern. Fürs Erste wird es ausreichen,<br />

wenn man beim Erstellen (Sie erinnern sich: ʺCreateʺ) angeben kann, wie groß das Rechteck<br />

sein muss. Doch bevor wir uns darum kümmern, noch ein kleiner Exkurs zum<br />

Überschreiben von Methoden.<br />

Methoden überschreiben - Ein bisschen ausführlicher<br />

Im vorigen Abschnitt wurde gezeigt, wie man eine abstrakte Methode überschreiben<br />

kann. Dies geht jedoch auch mit nicht-abstrakten Methoden, also mit Methoden, die in der<br />

Mutterklasse implementiert wurden. Das macht die ganze Sache etwas kompliziert.<br />

Sehen wir uns die folgende Verwendung an, dieses Mal mit einem Beispiel, welches<br />

nicht in den Rest passt:<br />

type<br />

TAuto = class(TObject)<br />

private<br />

public<br />

function drive : String;<br />

end;<br />

{...}<br />

function TAuto.drive : String;<br />

begin<br />

result := 'TAuto.drive';<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 58/81<br />

type<br />

TOpel = class(TAuto)<br />

private<br />

public<br />

function drive : String;<br />

end;<br />

{...}<br />

function TOpel.drive: String;<br />

begin<br />

result := 'TOpel.drive';<br />

end;<br />

Und dazu folgende Verwendung der beiden Klassen:<br />

var auto : TAuto;<br />

begin<br />

auto := TOpel.Create;<br />

ShowMessage(auto.drive());<br />

end;<br />

Beachten Sie dabei, dass ʺautoʺ zwar als ʺTAutoʺ deklariert wurde, aber der<br />

Konstruktor von ʺTOpelʺ aufgerufen wird, außerdem sollten Sie beachten, dass die Methode<br />

in ʺdriveʺ in der Klasse ʺTAutoʺ nicht als ʺvirtualʺ deklariert wurde.<br />

Führt man diesen Code nun aus, sieht man, dass die Methode ʺdriveʺ aus der Klasse<br />

ʺTAutoʺ aufgerufen wird, obwohl wir eine Instanz der Klasse ʺTOpelʺ erzeugt haben.<br />

Methoden mit diesem Verhalten nennt man ʺstatischʺ, es ist die Standardeinstellung für<br />

Methoden. Bei statischen Methoden wird also immer die Methode der Klasse aufgerufen, die<br />

deklariert (ʺauto : TAutoʺ) wurde und nicht die Methode der Klasse, die instanziert wurde.<br />

Deklariert man die Methode ʺdriveʺ in der Klasse ʺTAutoʺ nun als ʺvirtualʺ und<br />

ergänzt man die Methode ʺdriveʺ in der Klasse ʺTOpelʺ um ein ʺoverrideʺ, so ergibt obiger<br />

Quelltext ein anderes Ergebnis: Dann wird nämlich die Methode aus der Klasse ʺTOpelʺ<br />

ausgeführt, also der Klasse, die wir instanziert haben! Die ʺvirtuelleʺ Methode aus der Klasse<br />

ʺTAutoʺ wurde also ʺüberschriebenʺ (override).<br />

Es gibt übrigens außer virtuellen Methoden auch noch ʺdynamischeʺ Methoden,<br />

welche mittels des Schlüsselwortes ʺdynamicʺ an der Stelle von ʺvirtualʺ deklariert werden.<br />

Dynamische Methoden unterscheiden sich in der Verwendung nicht von virtuellen, der<br />

Unterschied liegt lediglich in der internen Umsetzung: Virtuelle Methoden sind auf eine


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 59/81<br />

hohe Geschwindigkeit optimiert, dynamsiche Methoden auf einen geringen<br />

Speicherverbrauch.<br />

Nun sollte der Quelltext aus dem vorigen Abschnitt klarer werden: Die Methode<br />

ʺflaecheʺ in TgeomForm ist virtuell, weil sie von den abgeleiteten Klassen überschrieben<br />

werden muss. Dies macht Sinn, denn sie ist auch abstrakt (also nicht implementiert) ein<br />

Aufruf dieser Methode würde daher nicht nur keinen Sinn machen, sondern einen Fehler<br />

produzieren. Was nicht da ist, kann man nicht aufrufen. Daher müssen abstrakte Methoden<br />

auch immer als virtuell oder dynamisch deklariert werden.<br />

Und nun weiter mit unserem bisherigen Beispiel!<br />

Der Konstruktor<br />

Wie bereits gesagt, ist der Konstruktor der Teil einer Klasse, welcher neue Instanzen<br />

eben dieser Klasse erzeugt. Er ist also nicht an ein Objekt, sondern an die entsprechende<br />

Klasse gebunden. Der ʺUrʺ-Konstruktor ist in der Urklasse ʺTObjectʺ deklariert, aber er kann<br />

in jeder Klasse neu deklariert und implementiert werden, solange man daran denkt, jeweils<br />

den Konstruktor der Vorfahrklasse auch noch auszurufen. Dies muss man tun, damitr auch<br />

das, was in den Muttterklassen im Konstruktor gemacht wird, erledigt wird.<br />

In unserem Fall möchten wir, dass der Konstruktor auch noch das Setzen von Höhe<br />

und Breite mit erledigen soll. Dazu soll der Konstruktor nicht ʺnormalʺ aufgerufen werden,<br />

sondern direkt noch die beiden Maße als Parameter mitgegeben bekommen. Dazu muss man<br />

den Konstruktor erst einmal wieder deklarieren. Dabei wird ein Konstruktor weder als<br />

Funktion noch als Prozedur deklariert, sondern als ... Konstruktor. ;-)<br />

type<br />

TRechteck = class(TgeomForm)<br />

private<br />

Fhoehe : Integer;<br />

Fbreite : Integer;<br />

public<br />

constructor create(hoehe, breite : Integer);<br />

function flaeche : Integer; override;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 60/81<br />

Bis auf das Schlüsselwort ʺconstructorʺ sieht der Konstruktor in der der Deklaration<br />

also aus, als wäre er eine ganz normale Methode. Was er nicht ist, denn, wie bereits erwähnt,<br />

er ist nicht an eine Instanz, sondern an die Klasse gebunden. Nun zur Implementation, die<br />

eigentlich auch keine Überraschungen bereit hält:<br />

constructor TRechteck.create(hoehe, breite: Integer);<br />

begin<br />

inherited create;<br />

FHoehe := hoehe;<br />

FBreite := breite;<br />

end;<br />

Das einzig wirklich neue an diesem Quelltext ist die Zeile, welche mit den<br />

Schlüsselwort ʺinheritedʺ beginnt. Leitet man den Aufruf einer Methode (in diesem Fall<br />

ʺcreateʺ) mit dem Schlüsselwort ʺinheritedʺ ein, so signalisiert man dem Compiler damit,<br />

dass in diese Fall die entsprechende Methode der Mutterklasse aufgerufen werden soll und<br />

nicht die Methode der aktuellen Klasse. Hier heißt das also: es wird der Konstruktor von<br />

ʺTgeomFormʺ aufgerufen.<br />

Der Rest des Konstruktors ist einfach, es werden die per Parameter übergebenen Maße<br />

in den entsprechenden Eigenschaften gespeichert, damit sie später zur Verfügung stehen. Ich<br />

habe an dieser Stelle aus Absicht darauf verzichtet, über ʺself.FHoeheʺ bzw. ʺself.Fbreiteʺ auf<br />

die Eigenschaften zuzugreifen, um Ihnen zu zeigen, dass es bei Eindeutigkeit der Bezeichner<br />

auch anders geht.<br />

Objekte sind Referenzdatentypen<br />

An dieser Stelle möchte ich noch auf einen Fehler hinweisen, der sehr gerne begangen<br />

wird, nämlich dann, wenn es um die Zuweisung von Objekten geht. Nehmen wir nur für das<br />

nachfolgende Beispiel an, die Höhe und Breite eines Rechtecks könnten von außen (über<br />

ʺhoeheʺ und ʺbreiteʺ) manipuliert werden. Nehmen wir weiterhin an, es wurden zwei<br />

Rechtecke deklariert, eines wurde erzeugt:<br />

var r1, r2 : TRechteck;<br />

begin<br />

r1 := TRechteck.Create(50, 10);<br />

r2 := r1;<br />

r2.hoehe := 10;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 61/81<br />

r2.breite := 10;<br />

ShowMessage(IntToStr(r1.flaeche));<br />

end;<br />

Die Meldung zeigt in diesem Fall nicht ʺ500ʺ, wie man zuerst denken würde, da ʺr1ʺ ja<br />

mit den Maßen 50 und 10 erzeugt wurde, sondern die Meldung zeigt ʺ100ʺ, was der Fläche<br />

von ʺr2ʺ entspricht. Der Grund dafür ist, dass Objekte ʺReferenzdatentypenʺ sind und sich<br />

bei Zuweisungen so verhalten, wie ich es auch schon bei Arrays beschrieben habe. Die<br />

Zuweisung ʺr2 := r1ʺ erzeugt kein neues Objekt, sondern ʺr2ʺ verweist auf dasselbe Objekt<br />

wie ʺr1ʺ, ist sozusagen nur ein anderer Name für dasselbe Objekt, weshalb die Fläche<br />

hinteher auch 100 und nicht 500 ist.<br />

Der Konstuktor ist nun beschrieben, jetzt werfen wir nochmals einen Blick auf die<br />

Verwendung der neu erstellten Objekte. Allerdings nicht bevor wir uns nicht das Gegenteil<br />

des Konstruktors angesehen haben. ;-)<br />

Der Destruktor<br />

Obwohl in diesem Beispiel nicht benötigt, sei hier noch kurz auf das Gegenstück zum<br />

Konstruktor, den ʺDestruktorʺ eingegangen. Er ist dafür zuständig, dass das Objekt, welches<br />

zuvor durch den Konstruktor erzeugt wurde, nach Verwendung auch wieder aus dem<br />

Speicher entfernt wird. Dabei wird der Destruktor als Methode der Instanz aufgerufen,<br />

welche ʺvernichtetʺ werden soll.<br />

Der Destruktor wird fast wie der Konstruktor deklariert, mit den Ausnahmen, dass<br />

anstatt des Schlüsselwortes ʺconstructorʺ das Schlüsselwort ʺdestructorʺ verwendet wird<br />

und er den Destruktor der Mutterklasse überschreibt, also mit einem ʺoverrideʺ deklariert<br />

wird. Dies ist nur beim Destruktor nötig, da der Konstruktor ja über die Klasse und nicht<br />

über eine Instanz aufgerufen wird und somit eindeutig ist, welcher Konstruktor aufgerufen<br />

wird. Der Name des Destruktors ist immer ʺdestroyʺ.<br />

destructor destroy; override;<br />

Wichtig ist, dass im Destruktor ebenfalls der Destruktor der Vorwahrklasse aufgerufen<br />

wird, jedoch am Ende des eigenen Destruktors:


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 62/81<br />

destructor TmyClass.destroy;<br />

begin<br />

{...}<br />

inherited destroy;<br />

end;<br />

In ʺIhremʺ Destruktor sollten Sie vorm Aufruf des Destruktors der Mutterklasse allen<br />

Speicher freigeben, den Sie innerhalb der Instanz, welche freigegeben werden soll, belegt<br />

haben. Meist wird es sich dabei um weitere Objekte handeln, welche Sie in Ihrer Klasse<br />

instanzieren.<br />

Überschreiben und implementieren Sie immer den Destruktor ʺdestroyʺ, aber niemals<br />

die Methode ʺfree ʺ. Diese wird zwar immer aufgerufen, wenn man ein Objekt freigeben<br />

möchte, diese ruft aber wiederrum ʺdestroyʺ auf. Die Methode ʺfreeʺ enthält lediglich noch<br />

eine Überprüfung, ob das Objekt, welches freigegeben werden soll, überhaupt noch existiert<br />

und vermeidet somit Fehler. Also: Finger davon, die eigentliche Arbeit wird in ʺdestroyʺ<br />

erledigt.<br />

Da wir in diesem Beispiel keinerlei Objekte innerhalb unserer Klassen instanzieren,<br />

sondern lediglich primitive Datentypen verwenden, brauchen wir in diesen Klassen auch<br />

keine Destruktoren.<br />

Bitte beachten Sie zum Freigeben von Objekten auch den Teil dieses <strong>Crashkurs</strong>es<br />

über Exceptions!<br />

Verwendung - Teil 2<br />

Die Verwendung soll noch einmal die ʺVerwandschaftʺ von Klassen verdeutlichen.<br />

Eine kleine Vorbereitung müssen Sie jedoch noch vornehmen, platzieren Sie bitte eine Label,<br />

zwei Editfelder und einen Button auf der Form. Ändern Sie den Namen der Editfelder über<br />

den Objektinspektor auf ʺed_hoeheʺ und ʺed_breiteʺ (die entsprechende Eigenschaft heißt<br />

ʺnameʺ) und sorgen Sie dafür, dass beide Editfelder beim Programmstart leer sind, indem Sie<br />

die Eigenschaft ʺtextʺ entsprechend ändern. Geben Sie dem Label den Namen ʺla_flaecheʺ, es


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 63/81<br />

soll anfangs keinen Text anzeigen (Eigenschaft ʺCaptionʺ ändern). Den Button nennen Sie<br />

ʺbt_flaecheʺ, seinen Titel ändern Sie auf ʺFläche berechnenʺ.<br />

Klicken Sie nun doppelt auf den Button, um die OnClick-Methode aufzurufen.<br />

Deklarieren Sie dort die Variablen ʺhoeheʺ, ʺbreiteʺ und ʺflecheʺ als Integer. Lesen Sie mittels<br />

ʺStrToIntʺ die Höhe und die Breite aus den entsprechenden Editfeldern ein! Deklarieren Sie<br />

außerdem noch eine Variable ʺrechteckʺ vom Typ ʺTRechteckʺ. Bei einem Objekt wie<br />

ʺrechteckʺ reicht es, wie inzwischen bekannt sein sollte, jedoch nicht aus, dieses nur<br />

deklariere, Sie müssen es auch noch erzeugen. Somit sieht die OnClick-Methode bisher so<br />

aus:<br />

procedure TForm1.bt_flaecheClick(Sender: TObject);<br />

var hoehe, breite, flaeche : Integer;<br />

rechteck : TRechteck;<br />

begin<br />

hoehe := StrToInt(ed_hoehe.Text);<br />

breite := StrToInt(ed_breite.text);<br />

rechteck := TRechteck.create(hoehe, breite);<br />

Wie man an die Fläche herankommt, sollte klar sein:<br />

flaeche := rechteck.flaeche;<br />

Schließlich noch das Objekt freigeben und das Ergebnis ausgeben:<br />

rechteck.free;<br />

la_flaeche.Caption := IntToStr(flaeche);<br />

end;<br />

Ein Rechteck ist auch eine geometrische Form<br />

Hier wird jedoch noch nicht so ganz deutlich, welchen Vorteil der Vererbung liefert.<br />

Das wird erst deutlich, wenn wir noch eine weitere Klasse einführen, nämlich die Klasse<br />

ʺTKreisʺ. Wie das geht, sollte klar sein. Ein Kreis hat einen Radius anstatt Höhe und Breite,<br />

entsprechend müssen Eigenschaften, Konstruktor und die Methode zur Berechnung der<br />

Fläche angepasst werden.<br />

Ist das geschehen, kann man sich folgende Situation vorstellen: Man schreibt ein<br />

Grafikprogramm und möchte geometrische Formen verwalten, wobei es egal sein soll, ob es<br />

sich dabei um Rechtecke, Kreise oder noch andere Formen handelt. Eine Anwendung,


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 64/81<br />

welche das Grafikprogramm bieten soll, ist, die Gesamtfläche aller Formen zu errechnen. Mit<br />

OOP und Vererbung kein Problem!<br />

interface<br />

type<br />

TgeomFormArray = Array of TgeomForm;<br />

implementation<br />

function gesamtflaeche (formen : TgeomFormArray) : Integer;<br />

var i : Integer;<br />

begin<br />

result := 0;<br />

for i:=0 to High(formen) do<br />

result := result + formen[i].flaeche;<br />

end;<br />

Ich habe hier nur einen Codeschnipsel aufgeschrieben, dessen Hauptaussage aber klar<br />

sein sollte. Zuerst wird ein neuer Typ definiert, ein Array aus geometrischen Formen. Dies<br />

ist nötig, damit man hinterher eine Variable diesen Typs an eine Funktion übergeben kann,<br />

definiert man dafür keinen neuen Typ, macht <strong>Delphi</strong> Probleme. Eine Variable diesen Typs<br />

wird dann der Funktion ʺgesamtflaecheʺ übergeben, diese Variable soll alle Formen<br />

enthalten, die im Programm verwendet werden (also z.B. fünf Rechtecke und drei Kreise).<br />

Sie bemerken: das Array ist ein Array von ʺTgeomFormʺ, enthält aber Daten der Typen<br />

ʺTRechteckʺ und ʺTKreisʺ! Dies wird nochmals in der Schleife deutlich, welche alle Elemente<br />

des Arrays durchläuft: Durchlaufen wird ein Array von geometrischen Formen, für jede<br />

dieser Formen wird die Funktion ʺflaecheʺ aufgerufen. Dies geht, weil die Funktion ʺflaecheʺ<br />

abstrakt in ʺTgeomFormʺ deklariert wurde und somit bekannt ist, dass jedes dieser Elemente<br />

sie besitzt. Der Code, welcher beim Aufruf ausgeführt wird, ist jedoch jener der Datentypen<br />

ʺTRechteckʺ bzw. ʺTKreisʺ, je nachdem, das die aktuelle geometrische Form ist!<br />

Nun sollte klar sein, welcher Vorteil die Vererbung bietet: Eine abgeleitete Klasse kann<br />

die Methoden der Mutterklasse überschreiben und ihnen somit eine völlig neue Bedeutung<br />

geben. Dies nennt man übrigens auch ʺPolymorphieʺ. Bei einer abstrakten Methode, wie in<br />

diesem Fall, wird durch der Methode das Überschreiben überhaupt erst eine Implementation


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 65/81<br />

gegeben, man könnte aber auch eine bereits implementierte Methode überschreiben, sofern<br />

auch sie mit dem Schlüsselwort ʺvirtualʺ deklariert wurde.<br />

Properties<br />

Bisher wurde nur gezeigt, wie man von außen Methoden verwendet, jedoch nicht, wie<br />

man von außen die Eigenschaften von Objekten manipuliert. Es hat sich durchgesetzt, dass<br />

man auch die Eigenschaften, welche eigentlich von außen manipuliert werden dürfen (also<br />

nicht ʺprivateʺ oder ʺprotectedʺ wären), nicht einfach in den public-Teil zu schreiben,<br />

sondern sie der Außenwelt durch so genannte ʺpropertiesʺ zur Verfügung zu stellen. Damit<br />

behält der Programmierer der entsprechenden Klasse die Kontrolle darüber, was mit den<br />

Eigenschaften geschieht.<br />

Im Folgenden soll erst einmal die Deklaration einer property gezeigt werden, wieder<br />

anhand der Klasse ʺTgeomFormʺ.<br />

type<br />

TgeomForm = class<br />

private<br />

Fx : Integer;<br />

Fy : Integer;<br />

public<br />

procedure verschieben(dx, dy : Integer);<br />

function flaeche : Integer; virtual; abstract;<br />

property x : Integer read Fx write Fx;<br />

property y : Integer read Fy write Fy;<br />

end;<br />

Die Deklaration ist recht einfach zu verstehen: Das Schlüsselwort ʺpropertyʺ<br />

signalisiert dem Compiler, was nun auf ihn zukommt, dann folgen Name und Typ der<br />

property. Anstatt hier jedoch Schluss zu machen, benötigt der Compiler nun die Information,<br />

wohin er die Aufrufe der property ʺumleitenʺ soll, denn eine property ansich enthält keinen<br />

Wert, sie holt ihn nur woanders her bzw. setzt ihn woanders.<br />

Woher eine property ihren Wert holen oder wo sie einen neuen Wert setzen soll, das<br />

wird über die Schlüsselwörter ʺreadʺ und ʺwriteʺ festgelegt. In diesem Fall wird der Wert für<br />

die property ʺxʺ aus der privaten Eigenschaft ʺFxʺ geholt und wenn ʺxʺ ein Wert zugewiesen<br />

wird, wird diese Zuweisung auch dorthin ʺweitergeleitetʺ. Hier wird übrigens auch klar,


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 66/81<br />

weshalb man bei privaten Eigenschaften noch ein ʺFʺ vor den Namen schreibt: damit man<br />

hinterher eine property mit dem richtigen Namen einführen kann. Verwendet wird eine<br />

solche property übrigens wie folgt:<br />

var myGeomForm : TgeomForm;<br />

{...}<br />

myGeomForm.x := 5;<br />

Also nichts anderes als bei den privaten Eigenschaften auch. Jetzt stellen Sie sich<br />

wahrscheinlich die Frage, wo denn nun der Vorteil von properties liegt. Das wird deutlich,<br />

wenn man die Deklaration mal ändert:<br />

type<br />

TgeomForm = class<br />

private<br />

Fx : Integer;<br />

Fy : Integer;<br />

procedure setX(const value : Integer);<br />

function getX : Integer;<br />

public<br />

procedure verschieben(dx, dy : Integer);<br />

function flaeche : Integer; virtual; abstract;<br />

property x : Integer read getX write setX;<br />

property y : Integer read Fy write Fy;<br />

end;<br />

Hier wurde nur die Deklaration für die property ʺxʺ verändert: Es wird von ʺxʺ aus<br />

nun nicht mehr auf die private Eigenschaften ʺFxʺ zugegriffen, sondern die Aufrufe werden<br />

an die Funktion ʺgetXʺ und die Prozedur ʺsetXʺ weitergeleitet. Wird also ʺxʺ wieder (wie<br />

oben bereits gezeigt) der Wert 5 zugewiesen, wird nun die Prozedur ʺsetXʺ aufgerufen und<br />

als Parameter ʺvalueʺ die 5 übergeben. Wird ʺxʺ abgefragt, so wird die Funktion ʺgetXʺ<br />

aufgerufen und das Ergebnis als ʺxʺ zurückgegeben.<br />

Der Vorteil dieser Technik zeigt sich in der Implementation der Methode ʺsetXʺ:<br />

procedure TgeomForm.setX(const value: Integer);<br />

begin<br />

if value >= 0 then<br />

Fx := value;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 67/81<br />

In dieser Prozedur wird also zuerst geprüft, ob ʺxʺ auf einen plausiblen Wert gesetzt<br />

werden soll (in diesem Fall sollen alle Werte größer oder gleich Null sein), und nur wenn<br />

dies der Fall ist, wird der Wert im privaten ʺFxʺ auch wirklich gesetzt, der Wert also letzten<br />

Endes geändert.<br />

Das Verwenden von get- und set-Methoden gibt dem Programmierer einer Klasse also<br />

Möglichkeiten der Kontrolle, zusätzlich zu der Möglichkeit, den Hinterbau einer Eigenschaft<br />

(z.B. ʺxʺ) komplett zu verändern (in diesem Fall wurde der Hinterbau auf die Methoden<br />

ʺgetXʺ und ʺsetXʺ umgestellt), ohne dass derjenige, der die Klasse nur verwendet, etwas<br />

bemerkt.<br />

Array-Properties<br />

Eine Erweiterung der normalen Properties stellen die Array-Properties dar. Mit ihnen<br />

hat man die Möglichkeit, beim Zugriff auch noch einen oder mehrere Indizies anzugeben,<br />

also auf die Property zuzugreifen wie auf ein Array. Folgendes Beispiel soll den Zugriff auf<br />

x- und y-Position einer geometrischen Form mittels Indizes aufzeigen, dabei soll man die x-<br />

Position mit dem Index ʺ0ʺ erreichen und die y-Koordinate mittels ʺ1ʺ:<br />

type<br />

TgeomForm = class<br />

private<br />

Fx : Integer;<br />

Fy : Integer;<br />

procedure setPos(index : Integer; const value : Integer);<br />

function getPos(index : Integer) : Integer;<br />

public<br />

{...}<br />

property position[index : Integer] : Integer read getPos write setPos;<br />

end;<br />

Die get- und set-Methoden erhalten also einen zusätzlichen Parameter, der den<br />

übergebenen Index angibt. Eine Implementation sähe dann (am Beispiel der set-Methode) so<br />

aus:<br />

procedure TgeomForm.setPos(index : Integer; const value : Integer);<br />

begin<br />

case index of<br />

0: Fx := value;<br />

1: Fy := value;<br />

end;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 68/81<br />

Ein Zugriff auf diese Property könnte so aussehen:<br />

myGeomForm.position[0] := 3;<br />

Es kann sinnvoll sein, mittels der ʺdefaultʺ-Direktive eine Kurzform einzuführen und<br />

direkt über den Namen der Klasseninstanz auf eine Property zuzugreifen:<br />

property position[index : Integer] : Integer read getPos write<br />

setPos; default;<br />

myGeomForm[0] := 3;<br />

Obiger Zugriff ist identisch mit dem Zugriff auf ʺpositionʺ, da die Property ʺpositionʺ<br />

als Default-Property festgelegt wurde. Selbstverständlich kann es nur eine Default-Property<br />

pro Klasse geben!<br />

Ereignisse<br />

Ich möchte an dieser Stelle eine besondere Art von Properties aufzeigen, nämlich die<br />

Ereignisse. Dabei möchte ich nur recht oberflächlich vorgehen, eine tiefergehende<br />

Beschreibung ist im Rahmen dieses <strong>Crashkurs</strong>es nicht sinnvoll.<br />

Ereignisse sind in der Programmierung unter Windows sehr wichtig, weil sie die beste<br />

Möglichkeit sind, auf die Eingaben des Nutzers zu reagieren. Ein Ereignis wird<br />

bespielsweise ausgelöst, wenn der Nutzer auf einen Button klickt: Das Ereignis ʺOnClickʺ<br />

des entsprechenden Buttons wird ausgelöst.<br />

Nun könnte man in einer Klasse einfach eine Methode einbauen, welche ausgeführt<br />

wird, wenn ein Ereignis eintritt. Jedoch ist dies nicht wirklich praktikabel, weil dann bei<br />

jedem Button, auf den geklickt wird, immer dasselbe passieren würde, was natürlich Unsinn<br />

wäre. Man braucht also eine Methode, welche für jede Instanz unterschiedlich ist. Und<br />

genau das ist ein Ereignis: eine Property, welche eine Methode enthält!<br />

Dabei ist genau festgelegt, wie eine Methode, die einem Ereignis zugewiesen wird,<br />

auszusehen hat. So muss die Methode, welche einem OnClick-Ereignis zugewiesen wird,<br />

eine Prozedur mit einem Parameter vom Typ TObject sein. Wird nun das Ereignis ausgelöst,<br />

wird die Methode ausgeführt, welche dem Ereignis zugewiesen wurde.<br />

Das ist sich erst einmal merkwürdig anhören und soll daher am nachfolgenden<br />

Beispiel genauer erläutert werden. Erstellen Sie dazu eine neue <strong>Delphi</strong>-Anwendung und


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 69/81<br />

platzieren Sie einen Button auf der Form. Klicken Sie nun doppelt auf eine freie Stelle der<br />

Form. Sie sehen nun den Codeeditor vor sich, der Cursor blinkt in der Methode<br />

ʺFormCreateʺ der Form1: diese Methode wurde intern dem OnCreate-Ereignis von Form1<br />

zugewiesen, wird also aufgerufen, wenn die Form erzeugt wird.<br />

Fügen Sie unter der ʺFormCreateʺ-Methode eine neue Methode ein:<br />

procedure TForm1.doSomething(Sender: TObject);<br />

begin<br />

ShowMessage('Foo');<br />

end;<br />

Die Methode sollte über dem ʺend.ʺ am Ende der Unit stehen, und muss natürlich noch<br />

im interface-Abschnitt deklariert werden, im public-Bereich von Form1.<br />

Fügen Sie nun noch in die Methode ʺFormCreateʺ den folgenden Code ein:<br />

Button1.OnClick := doSomething;<br />

Starten Sie nun das Programm und klicken Sie den Button an. Wenn alles korrekt ist,<br />

wird Ihnen nun eine Nachrichtenbox mit dem Text ʺfooʺ entgegen springen. Das, was sonst<br />

<strong>Delphi</strong> intern für Sie erledigt, wenn Sie doppelt auf einen Button klicken (nämlich eine<br />

Methode für das OnClick-Ereignis anlegen und zuweisen), haben Sie nun manuell gemacht:<br />

Sie haben die Methode ʺdoSomethingʺ angelegt und bei Erstellen der Form dem OnClick-<br />

Ereignis des Buttons zugewiesen. Sie wird ausgeführt, wenn der Button geklickt wird.<br />

Sie werden bemerken, dass die Methode ʺdoSomethingʺ einen Parameter ʺSender :<br />

TObjectʺ hat, der überhaupt nicht genutzt wird. Der Grund dafür ist die oben bereits<br />

erwähnte Vorschrift, wie ein Ereignis auszusehen hat. Ein Ereignis hat - wie jede andere<br />

Property - einen Typ, so ist z.B. ein OnClick-Ereignis vom Typ TNotifyEvent, welches wie<br />

folgt deklariert ist:<br />

TNotifyEvent = procedure(Sender: TObject) of object;<br />

Und wie Sie einer Property vom Typ ʺIntegerʺ auch nur einen Integer zuweisen<br />

können, so können Sie einem TNotifyEvent auch nur ein TNotifyEvent zuweisen: Eine<br />

Prozedur mit einem Parameter vom Typ ʺTObjectʺ, welche an eine Klasse gebunden ist (ʺof<br />

objectʺ), also eine Methode ist.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 70/81<br />

Im normalen <strong>Delphi</strong>-Betrieb ist es das einfachste, die IDE die entsprechenden<br />

Methoden anlegen zu lassen und den Ereignissen zuweisen zu lassen. Der Objektinspektor<br />

zeigt Ihnen zu jedem Objekt auf der Form auch die verfügbaren Ereignisse an: Objekt<br />

markieren, im Objektinspektor die Karteikarte ʺEreignisseʺ wählen. Möchten Sie einem<br />

Ereignis eine Methode zuweisen, klicken Sie doppelt auf das leere Feld neben dem Namen<br />

des Ereignisses. Eine Methode wird angelegt und dem Ereignis zugewiesen. Bestehende<br />

Methoden können Sie einem Ereignis zuweisen, indem Sie nicht doppelt klicken, sondern<br />

mittels der DropDown-Liste eine bestehende Methode auswählen.<br />

Mehr soll an dieser Stelle nicht zu Ereignissen gesagt werden, obiges sollte Sie in die<br />

Lage versetzen, einen Großteil der Aufgaben zu erledigen.<br />

Klassenmethoden<br />

Es kann manchmal sinnvoll sein, dass man eine Methode nicht über die Instanz einer<br />

Klasse aufrufen möchte, sondern nur über die Klasse. So könnte man sich vorstellen, dass die<br />

Klasse ʺTgeomFormʺ eine Methode ʺdimensionʺ besitzt, welche zurückgibt, welche<br />

Dimension die geometrischen Objekte besitzen, die durch diese Klasse dargestellt werden.<br />

Das Ergebnis dieser Methode wäre nicht abhängig von einer Instanz, sondern für die<br />

gesamte Klasse identisch.<br />

Möchte man eine solche Methode haben, so leitet man sie bei der Deklaration mit dem<br />

Schlüsselwort ʺclassʺ ein.<br />

type<br />

TgeomForm = class<br />

{...}<br />

public<br />

class function dimension : Integer;<br />

end;<br />

{...}<br />

class function TgeomForm.dimension : Integer;<br />

begin<br />

result := 2;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 71/81<br />

Selbstverständlich kann man in Klassenmehtoden nicht auf die Eigenschaften einer<br />

Klasse zuzugreifen, da diese nur existieren, wenn man mit Instanzen arbeitet!<br />

Vorteile der OOP<br />

Hier möchte ich noch einmal zusammentragen, was die Vorteile der OOP sind. Ich<br />

hoffe, sie haben das bereits selbst erkannt, aber es kann nichts schaden, Ihnen das alles noch<br />

einmal in Erinnerung zu rufen! ;-)<br />

1. Zuerst einmal wäre die Kapselung. Mit ihr hat man die Möglichkeiten,<br />

Informationen innerhalb einer Klasse zu verbergen und nur ausgewählte Methoden<br />

und Eigenschaften nach außen hin sichtbar und somit nutzbar zu machen.<br />

2. Die Vererbung ist der nächste Schritt. Mit ihr ist es möglich, Klassen von anderen<br />

Klassen abzuleiten und alle Methoden und Eigenschaften der Mutterklasse zu<br />

übernehmen. Sie müssen also nur in der Mutterklasse deklariert und implementiert<br />

werden und sind in allen abgeleiteten Klassen vewendbar. Änderungen an den<br />

Methoden und Eigenschaften in der Mutterklasse wirken sich auf alle abgeleiteten<br />

Klassen aus.<br />

3. Abstrakte Methoden bieten die Möglichkeit, einen Prototyp zu schaffen und ihn erst<br />

in abgeleiteten Klassen zu implementieren. Zusammen mit der Polymorphie (also<br />

das Überschreiben von Methoden der Mutterklasse in abgeleiteten Klassen) erhält<br />

man ein mächtiges Werkzeug zur effizienten Verwendung von Klassen<br />

4. Mit properties gewährt man dem Nutzer einer Klasse kontrollierten Zugriff auf<br />

Eigenschaften (die noch nicht einmal existieren müssen, siehe get- und set-<br />

Methoden). Properties zeigen außerdem sehr gut, dass man die Implementation einer<br />

Klasse vollständig ändern kann, ohne dass es der Nutzer eine Klasse bemerkt.<br />

Einzige Bedingung: Das für den Nutzer sichtbare Interface muss unverändert<br />

bleiben. Wie bei den Properties.<br />

Dies sind insgesamt vier Punkte, die hier aufgezählt wurden. Die OOP bietet noch sehr<br />

viel mehr Vorteile, aber da die OOP hier nur sehr knapp beschrieben wurde, können auch


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 72/81<br />

nicht alle Vorteile hier aufgezählt werden. Ich möchte Sie bitten, sich mittels weiterer, auf<br />

OOP spezialisierter Tutorials und evtl. Bücher tiefer in das Thema einzuarbeiten, falls Sie<br />

Interesse daran haben. Zu empfehlen ist es allemal, OOP erleichert die Arbeit enorm!


dsdt dsdt.info dsdt<br />

Exceptions<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 73/81<br />

Eine Exception (zu Deutsch ʺAusnahmeʺ) tritt dann auf, wenn ein unerwarteter Fehler<br />

im Programm auftritt. Unerwartet heißt hierbei, dass z.B. nicht geprüft wurde, ob eine<br />

Variable Null ist und bei einer Divison durch diese Variable ein Fehler eintritt, nämlich die<br />

nicht definierte Division durch Null.<br />

Eine solche Exception würde, wenn man nichts dagegen täte, dem Nutzer als<br />

Fehlermeldung entgegen springen und das ist etwas, das man auf jeden Fall vermeiden<br />

sollte. Nichts verunsichert einen Nutzer mehr. ;-) Das Prinzip von try-except und try-finally<br />

wird oft auch als ʺStructured Exception Handlingʺ (SEH) bezeichnet.<br />

try-except<br />

Man vermeidet solche Ausnahmen, indem man sie behandelt, wenn sie auftreten. Bitte<br />

schalten Sie für das nachfolgende Beispiel die Option ʺBei <strong>Delphi</strong>-Exceptions stoppenʺ unter<br />

ʺTools -> Debugger-Optionen -> Sprach-Exceptionsʺ aus. Ansonsten würden Sie als<br />

Programmierer auch behandelte (und für den Endnutzer abgewendete) Exceptions sehen.<br />

var a, b: Integer;<br />

c : Double;<br />

begin<br />

a := 5;<br />

b := 0;<br />

c := a / b;<br />

ShowMessage(FloatToStr(c));<br />

end;<br />

Sobald Sie diesen Code ausführen, wird Ihnen die Meldung ʺGleitkommadivision<br />

durch Nullʺ entgegen springen. Eine solche Meldung sollte natürlich den Nutzer nie<br />

erreichen, sondern im Programm verarbeitet werden.<br />

try<br />

c := a / b;<br />

except<br />

exit;<br />

end;<br />

Die Syntax ist ganz einfach: Eingeleitet wird die so genannte ʺtry-except-Anweisungʺ<br />

durch das Schlüsselwort ʺtryʺ. Dieses wird gefolgt von den Befehlen, welche ausgeführt


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 74/81<br />

werden sollen und dabei eventuell einen Fehler produzieren. Sie müssen übrigens nicht in<br />

ʺbeginʺ und ʺendʺ verpackt werden.<br />

Anschließend folgt das Schlüsselwort ʺexceptʺ. An dieses Schlüsselwort schließt sich<br />

der Block von Befehlen an, der ausgeführt wird, sobald eine Exception auftritt, auch die<br />

ʺAusnahmebehandlungʺ genannt. In einem richtigen Programm sollten hier Befehle stehen,<br />

welche den korrekten Ablauf des Programmes trotz des Fehlers garantieren. In diesem Fall<br />

wird die Prozedur einfach verlassen.<br />

so:<br />

Es ist auch möglich, gezielt auf bestimmte Arten von Exceptions zu reagieren. Das geht<br />

try<br />

c := a / b;<br />

except<br />

on EZeroDivide do c := 0;<br />

on EOverflow do c := 1000000;<br />

else<br />

exit;<br />

end;<br />

Hier wird bei einer Division durch Null das Ergebnis auf Null gesetzt, bei einem<br />

Überlauf (also wenn die Zahlen für den Datentyp ʺdoubleʺ zu groß werden), wird das<br />

Ergebnis auf eine Million gesetzt. Bei allen anderen Fehlern wird die Prozedur beendet.<br />

try-finally<br />

Eine try-finally-Anweisung wird dann verwendet, wenn es Code gibt, der auf jeden<br />

Fall (also bei einem normalen Programmablauf und bei einer Exception) ausgeführt werden<br />

soll. Dies ist inbesondere dann der Fall, wenn man mit Objekten arbeitet, da diese<br />

freigegeben werden sollten, komme was da wolle. Daher nennt man eine try-finally-<br />

Anweisung der folgenden Art auch einen ʺSpeicherschutzblockʺ:<br />

myStringList := TStringList.Create;<br />

try<br />

macheWas(myStringList);<br />

finally<br />

myStringList.Free;<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 75/81<br />

Hier wird zuerst einmal eine Stringlist erstellt (zur Verwendung dieser bitte die<br />

<strong>Delphi</strong>-Hilfe bemühen). Im try-Abschnitt wird dann einiges damit gemacht. Und egal ob in<br />

diesem Teil ein Fehler auftritt oder nicht, muss der Speicher, welche die Stringliste belegt,<br />

auch wieder freigegeben werden. Dies wird im finally-Abschnitt erledigt.<br />

denken!<br />

Wichtig: Sie sollten bei der Arbeit mit Objekten immer an den Speicherschutzblock


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 76/81<br />

Der Anfang allen Übels<br />

Hier möchte ich zum Schluss noch auf ein paar Dinge hinweisen, welche Sie im Laufe<br />

dieses Crahskurses sicherlich zumindest teilweise mitbekommen haben, auch wenn sie noch<br />

nicht explizit ausgeführt wurden. Es geht um den Kopf einer jeden <strong>Delphi</strong>datei. Dieser hat<br />

verschiedene Teile:<br />

Interface und implementation<br />

Eine <strong>Delphi</strong>-Unit ist in zwei Teile geteilt: ʺinterfaceʺ und ʺimplementationʺ. Sie haben<br />

sicherlich bereits erraten, welche Bedeutung diese Teile haben: Im interace-Teil wird<br />

festgelegt, was alles in der Unit zu finden ist, der implementation-Teil stellt den eigentlich<br />

Inhalt der Unit dar.<br />

Allerdings muss obige Aussage ein wenig präzisiert werden: Eine Unit kann mehr<br />

implementieren, als im interface-Teil enthalten ist, jedoch sind nur Dinge, die im interface-<br />

Teil vorkommen, in anderen Units sichtbar. So ist also eine Prozedur nur dann in anderen<br />

Units sichtbar, wenn sie auch im interface-Teil deklariert wurde.<br />

Auch innerhalb einer Unit macht es einen Unterschied, ob eine Prozedur (oder auch<br />

Funktion) im interface-Teil deklariert wurde, oder nicht. Ist eine Prozedur nicht im interface-<br />

Teil deklariert, so kann man sie nur an einer Stelle aufrufen, welche unterhalb dieser<br />

Prozedur liegt.<br />

procedure TForm1.FormCreate(Sender: TObject);<br />

begin<br />

foo;<br />

end;<br />

procedure foo;<br />

begin<br />

ShowMessage('foo');<br />

end;<br />

Dies produziert einen Fehler, wenn die Prozedur ʺfooʺ nicht im interface-Abschnitt<br />

deklariert wurde. Man kann sich das so vorstellen, dass der <strong>Delphi</strong>-Compiler beim Aufruf<br />

von ʺfooʺ noch gar nicht soweit gelesen hat, dass er diese Prozedur kennen könnte. Schreibt


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 77/81<br />

man die Prozedur ʺfooʺ über ihren Aufruf, so hat der Compiler sie bereits ʺgelesenʺ und der<br />

Aufruf glückt.<br />

Alternativ deklariert man ʺfooʺ im interface-Abschnitt:<br />

interface<br />

uses<br />

{...}<br />

type<br />

TForm1 = class(TForm)<br />

procedure FormCreate(Sender: TObject);<br />

{...}<br />

end;<br />

procedure foo;<br />

{...}<br />

implementation<br />

Damit kennt der Compiler bereits den Namen der Prozedur ʺfooʺ und produziert<br />

keinen Fehler mehr. Jetzt wäre ʺfooʺ auch aus anderen Units heraus aufrufbar.<br />

Forwarding<br />

Es kann vorkommen, dass man doch einmal auf eine Prozedur oder Funktion<br />

zugreifen muss, welche im Quelltext erst später erfolgt und dass man die Reihenfolge nicht<br />

so ändern kann, dass dieses einfach möglich ist. Für solche Fälle kann man das so genannte<br />

ʺFowardingʺ verwenden. Dabei gibt man dem Compiler an einer früheren Position den<br />

Namen einer Prozedur oder Funktion mit, welche erst später implementiert wird. Also so<br />

ähnlich wie ein interface-Teil, nur in der Implementation. ;-)<br />

procedure foo; forward;<br />

procedure TForm1.FormCreate(Sender: TObject);<br />

begin<br />

foo;<br />

end;<br />

procedure foo;<br />

begin<br />

ShowMessage('foo');<br />

end;


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 78/81<br />

Nun würde - auch ohne Deklaration von ʺfooʺ im interface-Teil auch dieser Quelltext<br />

funktionieren, denn über der FormCreate-Methode wurde dem Compiler der Prozedur ʺfooʺ<br />

bekannt gegeben. Damit klar ist, dass nur der Name bekannt gegeben werden soll, die<br />

Prozedur aber noch nicht implementiert werden soll, schreibt man noch das Schlüsselwort<br />

ʺforwardʺ dahinter.<br />

Forwarding von Klassen<br />

Auch bei Klassen gibt es ab und an die Notwendigkeit des Forwardings, also das eine<br />

Klasse eine andere benötigt, die aber erst später eingeführt wird. Dies würde sich dann im<br />

interface-Teil abspielen. Die Lösung ist fast identisch mit der Lösung bei Prozeduren oder<br />

Funktionen. Im nachfolgenden Beispiel soll eine Klasse ʺTFotoalbumʺ mehre Fotos (ʺArray of<br />

TFotoʺ) speichern, jedes Foto jedoch auch wissen, zu welchem Fotoalbum es gehört<br />

(ʺFparentʺ).<br />

type<br />

TFoto = class;<br />

TFotoalbum = class(TObject)<br />

private<br />

Ffotos : Array of TFoto;<br />

end;<br />

TFoto = class(TObject)<br />

private<br />

Ffilename : String;<br />

Fparent : TFotoalbum;<br />

end;<br />

Der einzige Unterschied (außer der Position im interface-Teil) zum Forwarding von<br />

Prozeduren und Funktionen besteht darin, dass das Schlüsselwort ʺfowardʺ nicht verwendet<br />

wird. Stattdessen wird lediglich bekannt gegeben, dass es eine Klasse ʺTFotoʺ geben wird.<br />

Dies macht man mit der Anweisung ʺTFoto = class;ʺ, mehr darf dort auch nicht stehen, so<br />

zum Beispiel auch nicht, von welcher Klasse ʺTFotoʺ abgeleitet werden soll. Das wird erst<br />

angegeben, wenn die vollständige Deklaration erfolgt.


dsdt dsdt.info dsdt<br />

Die uses-Klausel<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 79/81<br />

Damit eine Unit (z.B. ʺUnit1ʺ) den Inhalt einer anderen Unit (z.B. ʺUnit2ʺ) ʺkenntʺ,<br />

muss man Unit2 in die uses-Klausel von Unit1 aufnehmen. Dadurch wird alles, was in Unit2<br />

im interface-Abschnitt steht, Unit1 bekannt und kann verwendet werden. Wenn Sie sich die<br />

Unit ansehen, welche Unit für ein Formular anlegt, werden Sie sehen, dass schon einiges in<br />

der uses-Klausel drin steht. Dies sind Units, welche <strong>Delphi</strong> mitbringt und die für das<br />

Darstellen von Formular und Komponenten nötig sind.<br />

Zusätzlich zu der standardmäßigen uses-Klausel im interface-Abschnitt kann man<br />

auch noch eine uses-Klauses im implementation-Abschnitt anlegen. Der Inhalt einer dort<br />

eingetragenen Unit ist dann nur im implementation-Teil bekannt, nicht aber im interface-<br />

Teil. Wozu ist das gut?<br />

Dieses Vorgehen wird verwendet, um einen so genannten ʺüberkreuzenden Bezugʺ zu<br />

verhindern. Ein überkreuzender Bezug entsteht dann, wenn man Unit2 in die uses-Klausel<br />

(interface-Bereich) von Unit1 schreibt, und Unit1 in die uses-Klausel (interface-Bereich) von<br />

Unit2. Das würde eine Endlosschleife ergeben, weil jede Unit die andere benutzt.<br />

Passiert dies, kann man sich in vielen Fällen damit retten, dass man eine der beiden<br />

Einträge in der uses-Klausel im implementation-Abschnitt vornimmt. Denn die uses-Klausel<br />

im implementation-Abschnitt ist wie alles andere dort nur in dieser Unit bekannt, kann also<br />

auch keinen überkreuzenden Bezug erzeugen. Leider kann man dadurch die Inhalte der<br />

eingebundenen Unit auch nur im implementation-Abschnitt nutzen.<br />

Überladen von Funktionen<br />

Eng mit dem Thema ʺinterface-Abschnittʺ verbunden ist das so genannte ʺüberladen<br />

von Funktionenʺ. Darunter versteht man, dass man verschiedene Funktionen mit gleichem<br />

Namen, aber unterschiedlichen Parametern hat. Hier zu nochmal das Beispiel der Rechteck-<br />

Klasse:<br />

type<br />

TRechteck = class(TgeomForm)


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 80/81<br />

private<br />

Fhoehe : Integer;<br />

Fbreite : Integer;<br />

public<br />

constructor create(hoehe, breite : Integer); overload;<br />

constructor create(groesse : Integer); overload;<br />

end;<br />

constructor TRechteck.create(hoehe, breite: Integer);<br />

begin<br />

inherited create;<br />

FHoehe := hoehe;<br />

FBreite := breite;<br />

end;<br />

{...}<br />

constructor TRechteck.create(groesse : Integer);<br />

begin<br />

inherited create;<br />

FHoehe := groesse;<br />

FBreite := groesse;<br />

end;<br />

Diese Klasse besitzt nun zwei Konstruktoren: einmal der Konstruktor mit zwei<br />

Parametern, wie er bereits bekannt ist (er erzeugt ein Rechteck mit der gegebenen Höhe und<br />

Breite) und einmal einen Konstruktor mit nur einem Parameter, welcher ein Quadrat mit der<br />

gegebenen Kantenlänge erzeugt.<br />

Damit <strong>Delphi</strong> weiß, dass man wirklich zwei Methoden gleichen Namens verwenden<br />

möchte, muss man bei der Deklaration das Schlüsselwort ʺoverloadʺ hinter jede dieser<br />

Methoden setzen. Wichtig ist, dass <strong>Delphi</strong> anhand der Anzahl und der Art der Parameter<br />

eindeutig bestimmen können muss, welche Methode gemeint ist! Ansonsten ist eine<br />

Überladung nicht möglich. In diesem Fall ist die Unterscheidung nicht schwer, sie erfolgt<br />

über die Anzahl der Parameter: Wird der Konstruktor mit einem Parameter aufgerufen, wird<br />

ein Quadrat erzeugt, bei zwei Parameter ein Rechteck.


dsdt dsdt.info dsdt<br />

<strong>Delphi</strong>-<strong>Crashkurs</strong><br />

von Christian Stelzmann 81/81<br />

Selbstverständlich funktioniert das Überladen auch mit Funktionen und Prozeduren<br />

und nicht nur mit Konstruktoren. Eine Überladung ist auch nicht auf Methoden (also an<br />

Objekte gebundene Funktionen und Prozeduren) beschränkt, sondern funktioniert auch bei<br />

nicht-objektgebundenen Funktionen bzw. Prozeduren. Die Deklaration ist dabei mit der<br />

Deklaration bei Methoden identisch, nur halt nicht das restliche Zeugs einer<br />

Klassendeklaration drum herum steht.

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!