Delphi-Crashkurs - Ernst-Reuter-Schule 1
Delphi-Crashkurs - Ernst-Reuter-Schule 1
Delphi-Crashkurs - Ernst-Reuter-Schule 1
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.