|
Christian Stelzmann |
|
Crashkurs Delphi |
|
Reloaded |
http://www.christian-stelzmann.de
Stand: 13.10.2007
Begriffsklärung: Was ist Delphi?
Woher nehmen und nicht stehlen?
Der Code-Modus: Ein kurzer Blick
Beispiel: Ein Mini-Taschenrechner
Zahlendarstellung im Rechner (und ein fertiger Taschenrechner)
Objektorientierte Programmierung
Dieser Crashkurs entstand in seiner ersten Version im Jahre 2005 und ist seitdem zu einem sehr beliebten Tutorial für Einsteiger geworden. Seit seiner Veröffentlichung ist er mehrere tausend Mal heruntergeladen oder online betrachtet worden.
Jedoch lebt ein solcher Crashkurs auch von seiner Aktualität und die erste Version des Crashkurses ist nun doch schon etwas in die Jahre gekommen. So basieren alle Screenshots noch auf der Version 7 von Delphi, welche zumindest von Einsteigern wohl nur noch selten benutzt werden wird.
Ich habe mich daher entschlossen, den Crashkurs zu überarbeiten.
Dabei sollen nicht nur die Screenshots angepasst werden, sondern durch mehr Praxis soll der Crashkurs interessanter werden. Das führt auch dazu, dass ich immer mal wieder Einschübe mache, um auf Features der IDE hinzuweisen,auch wenn es gerade eigentlich eher ein Theorie-Teil ist. Das „zerstört“ zwar ein wenig die Ordnung, sorgt aber für Abwechslung. Ich werde an den entsprechenden Stellen den Abschnitts-Titel mit „Beispiel“ , „Einschub“ oder ähnlichem beginnen lassen.
Natürlich werden auch ein paar neue Themen in den Crashkurs aufgenommen.
Nicht zu vergessen ist das neue Design, denn das Auge programmiert mit ;-)
Lange Zeit war diese Frage leicht zu beantworten. Mit der Zeit wurde der Begriff „Delphi“ von den Marketing-Abteilungen der Firmen Borland und später CodeGear aber mehrfach malträtiert und ist so nicht mehr eindeutig.
Viele werden unter „Delphi“ weiterhin eine IDE (Integrated Development Environment) und eine Sprache verstehen. Bis vor kurzem hieß die Sprache „Delphi“ und die IDE wurde mit „Borland Developer Studio“ (BDS) betitelt, welches noch andere Sprachen in sich vereinte.
Leider gibt es in Kürze „Delphi für PHP“, was eine Entwicklungsumgebung für PHP darstellt und die Sprache „Delphi“ nicht mehr beinhaltet. Das hat den Effekt, dass CodeGear nun nach einem neuen Namen für die Sprache sucht, die bisher als „Delphi“ bekannt war.
Dazu eine persönliche Bemerkung:
Die Sprache, über die wir hier sprechen, hatte schon viele Namen. „Object Pascal“, „Delphi Language“ und „Delphi“ wären da unter anderem zu nennen. Die Umbenennungen haben schon immer für Verwirrung gesorgt, waren aber meist logisch nachvollziehbar. Mit der Veröffentlichung von „Delphi für PHP“ hat CodeGear allerdings den Vogel abgeschossen und wohl der Marketingabteilung erlaubt, den Namen „Delphi“ auf unsinnigste Weise zu opfern. Er ist nun vollständig verwässert und steht für nichts mehr.
Wie werde ich das also in diesem Crashkurs handhaben?
Ich werde sowohl die IDE als auch die Sprache mit „Delphi“ bezeichnen. In Ausnahmefällen, nämlich da, wo eine Unterscheidung nicht aus dem Kontext heraus möglich ist, werde ich entsprechend deutlich machen, was von beidem gemeint ist. Dies wird aber nur sehr selten der Fall sein.
Die Vorteile von Delphi liegen sowohl in der Sprache als auch in der IDE. Dies gilt insbesondere für Einsteiger.
Die IDE war von den ersten Versionen an darauf ausgelegt, grafische Oberflächen auch grafisch zu gestalten, ohne dazu eine einzige Zeile Code zu schreiben. Es macht keine Mühe innerhalb weniger Minuten die Oberfläche eines Programmes „zusammen zu klicken“. Die Produktivität, die dadurch ermöglicht wird, ist enorm. In den letzten Versionen wurde die IDE durch viele nützliche Funktionen erweitert, welche das Arbeiten noch deutlich erleichtern. Dazu später mehr.
Die Sprache Delphi ist für Anfänger auf Grund seiner klaren Strukturen und einprägsamen Befehle sehr leicht zu lernen, lässt Fortgeschrittene und Profis aber auch keine Funktionen vermissen. Selbstverständlich bietet Delphi volle Unterstützung für objektorientierte Programmierung (kurz: OOP), in den letzten Jahren wurde die Sprache auch in diesem Bereich um einige Features erweitert.
Delphi hat jedoch in letzter Zeit durch die Sprache C# Konkurrenz bekommen. Eingebettet in das Visual Studio bietet auch C# eine hervorragende IDE und eine sehr elegante Sprache, welche ich ebenfalls gerne nutze.
Für den Einstieg halte ich Delphi aber weiterhin für die bessere Wahl. Nicht nur, dass die Befehle einprägsamer sind (weil sie ausgeschrieben und nicht durch Zeichen abgekürzt werden), sondern Delphi zwingt den Programmierer teilweise zu einem sauberen Programmierstil. Das mag den Fortgeschrittenen schon einmal stören, für den Anfänger ist es aber von großem Nutzen.
Legal und kostenlos an eine Version von Delphi zu kommen, geht erfreulich einfach. Die zum Zeitpunkt der Veröffentlichung dieses Crashkurses aktuellste Version, die kostenlos zu haben ist, ist „Turbo Delphi“ in der Explorer-Edition.
Im Unterschied zu den „großen“ Delphis enthält diese Version nur eine Sprache und kann nicht mit den Produkten von Drittanbietern erweitert werden. Ansonsten bietet die Explorer-Edition keine Einschränkungen und ist für einen Einsteiger genau das Richtige.
Der Download ist auf dieser Seite zu finden: http://www.turboexplorer.com/mirror
Laden Sie dort die Version „Turbo Delphi Explorer“ als ISO-Datei herunter. Der Download ist zwar größer, dafür sind aber auch alle vorausgesetzten Programme dabei, sodass Sie sich darum keine Sorgen mehr machen müssen. Die ISO-Datei können Sie mit entsprechenden Programmen in ein virtuelles CD-Laufwerk mounten oder einfach auf eine CD brennen.

Anschließend müssen Sie sich einen kostenlosen Registrierungsschlüssel besorgen. Gehen Sie dazu auf diese Seite: http://www.codegear.com/tabid/144/Default.aspx Klicken Sie dort unter „Keys Only (If you have a CD)“ auf „Turbo Delphi Explorer“.

Füllen Sie im nachfolgenden Formular alle Felder aus und sie erhalten kurze Zeit später eine E-Mail, in deren Anhang sich eine Datei befindet. Diese speichern Sie in Ihrem Windows-Profil-Verzeichnis. Unter Windows 2000/XP dürfte das „c:\Dokumente und Einstellungen\{Benutzername}“ sein, unter Windows Vista „c:\users\{Benuzername}“.
Starten Sie nun die Installation, welche sich in der heruntergeladenen ISO-Datei befindet.
Die Entwicklungsumgebung von Delphi hat mit Delphi 8 eine Runderneuerung erfahren, welche mit Delphi 2005 und 2006 noch einmal um einige nützliche Funktionen erweitert wurde. Nach einigen Anlaufschwierigkeiten läuft die IDE („Integrated Development Environment“) nun stabil und schnell.
Ich verwende für den Crashkurs die kostenlos erhältliche „Turbo Delphi Explorer“-Version, was ein kleines Delphi 2006 ist.
Beim ersten Start präsentiert Delphi eine Willkommens-Seite, welche einen mit nützlichen Informationen versorgt und Zugang zu den häufigsten Funktionen von Delphi bereitstellt.
Klicken Sie auf der Willkommens-Seite auf „Neues Projekt“. Es öffnet sich ein Fenster, in dem man verschiedene Arten von Projekten auswählen kann. Lassen Sie sich von der Masse an Projekten nicht verunsichern, im Rahmen des Crashkurses werden wir uns nur mit einem einzigen beschäftigen: der VCL-Formularanwendung.
Wählen Sie dazu in der linken Liste den Punkt „Delphi-Projekte“ aus und dann rechts das Symbol mit der Bezeichnung „VCL-Formularanwendung“. Klicken Sie „OK“ und ein Projekt des entsprechenden Typs wird erstellt.

Hinter der Bezeichnung „VCL-Formularanwendung“ verbirgt sich eine ganz normale Windows-Anwendung, wie Sie sie überall sehen. Das „VCL“ im Namen steht für „Visual Component Library“ und ist eine Bibliothek, welche mit Delphi ausgeliefert wird und die hunderte an grafischen Komponenten bereitstellt, die dem Programmierer die Arbeit erleichtern. Wir werden diese später noch näher betrachten.
Nach Erstellung einer VCL-Formularanwendung zeigt sich Delphi im so genannten Design-Modus, welcher dazu da ist, die grafische Oberfläche einer Anwendung zu erstellen. Zentraler Bestandteil ist der Form-Designer in der Mitte der IDE. Er zeigt, wie das Fenster (in Delphi „Form“ oder „Formular“ genannt) Ihrer Anwendung hinterher aussehen wird. Auf ihm platzieren Sie alle Komponenten (also Schaltflächen, Textfelder, etc.). Das wird schon bald beschrieben.
Auf der linken Seite sehen Sie die Boxen „Struktur“ und „Objektinspektor“.
Die Struktur zeigt Ihnen eine Hierarchie der Objekte, welche sich auf Ihrer Form befinden. Wie bereits gesagt, kann Ihre Form Schaltflächen und Textfelder enthalten. Es gibt jedoch auch Objekte, welche wiederum andere Objekte enthalten können. Sie können sich das also wie die Anzeige im Windows Explorer vorstellen, wo ja auch Ordner in Ordnern in Ordnern angezeigt werden. Einen entsprechenden „Baum“ zeigt auch die Struktur-Box.
Der Objektinspektor ist eine der wichtigsten Teile des Design-Modus. Mit ihm können Sie die Eigenschaften von Objekten verändern. Mit anderen Worten: Mit ihm können Sie festlegen, ob Ihr Fenster einen grünen oder blauen Hintergrund haben soll, welchen Text eine Schaltfläche zeigen soll und ob ein Textfeld in Schriftgröße 30 ausgefüllt wird. Die Benutzung wird später noch beschrieben.
Auf der rechten Seite sehen Sie die Boxen „Projektverwaltung“ und „Tool-Palette“.
Die Projektverwaltung zeigt eine Baumansicht Ihrer Projektgruppe. Diese kann mehrere Projekte enthalten. Jedes dieser Projekte enthält mehrere Dateien. Eine Projektgruppe kann z.B. sein „Crashkurs Demo-Projekte“. In dieser Projektgruppe sammelt man dann verschiedene Programme, z.B. „Demo_Einfuehrung“, „Demo_Fortgeschritten“, etc. Jedes Projekt besteht dann aus mehreren Dateien, also z.B. mehreren Formularen.
Die Tool-Palette ist genauso wichtig wie der Objektinspektor. Wenn Sie sich die ganze Zeit fragen „Ja, aber wo kommen die Schaltflächen und Textfelder denn nun her?“, dann ist das die Antwort: aus der Tool-Palette. Geordnet in Kategorien wie z.B. „Standard“ finden Sie hunderte Komponenten (wie die sichtbaren Objekte auf einer Form auch genannt werden) der oben erwähnten Visual Component Library (VCL). Im folgenden Abschnitt wird beschrieben, wie Sie die Komponenten auf die Form bekommen.
Stopp! Bevor Sie auch nur eine Komponente auf die Form packen, sollten Sie sich gut überlegen, wie Ihre Form aussehen soll. Überlegen Sie zuerst, was Ihr Programm machen soll und anschließend, wie Sie diese Funktionen präsentieren wollen. Achten Sie dabei auch darauf, dass ein extravagantes Layout Ihr Programm zwar zu etwas Besonderem macht, Benutzer aber weitestgehend ein Standard-Windows-Layout gewohnt sind.
So, genug gemahnt, jetzt geht es los! Wir möchten eine kleine Anwendung für den Einstieg schreiben, die uns einen Text als Hinweismeldung anzeigt. Dazu müssen wir ein paar Komponenten auf die Form bringen.
Um eine Komponente auf der Form zu platzieren, gehen Sie wie folgt vor:
1. Komponente in der Tool-Palette ausfindig machen
2. Komponente anklicken
3. Den Punkt auf der Form anklicken, wo die Komponente eingefügt werden soll
Nun wurde ja bereits erwähnt, dass die Tool-Palette von Werk an bereits ein paar hundert Komponenten enthält. Wenn man also nicht gerade eine der Standard-Komponenten, wird man eine Weile mit Suchen beschäftigt sein. Aber Delphi wäre nicht Delphi, wenn es dafür nicht eine Lösung parat hätte:
Klicken Sie in die Tool-Palette oder drücken Sie die Tastenkombination STRG+ALT+P. Tippen Sie dann einen Teil des Namens der Komponente, die Sie haben möchten. Also z.B. „But“, wenn Sie die Komponente „TButton“ haben wollen. Das ist übrigens die Standard-Schaltfläche, welche Sie überall in Windows sehen.
Während Sie tippen, werden die Komponenten in der Tool-Palette weniger: sie werden gefiltert. Es bleiben „TButton“ und „TButtonGroup“ übrig. Sie können nun, wie oben beschrieben, die Komponente mit der Maus positionieren, oder einfach die Eingabetaste drücken. Dadurch wird die gewählte Komponente in der Mitte der Form positioniert. Die Filterung der Tool-Palette wird aufgehoben.
Ich beschreibe das so ausführlich, weil es für mich eine der genialsten Funktionen in neueren Delphi-Versionen ist. Wenn ich eine Programmoberfläche gestalte, gehe ich nun so vor: In der Tool-Palette Namen tippen, Eingabetaste, anderen Namen tippen, Eingabetaste, usw. Dann habe ich alle Komponenten in der Mitte der Form und muss sie nur noch an die richtigen Positionen schieben.
Holen Sie sich auf eine der beschriebenen Arten nun einen TButton (Standardschaltfläche von Windows), ein TLabel (einfacher Text z.B. zur Beschriftung von Textfeldern) und ein TEdit (Standardtextfeld von Windows) auf die Form.
Klicken Sie nun das TLabel an und schieben Sie es mit der Maus in die obere linke Ecke der Form. Sie werden bemerken, dass, sobald Sie sich der Ecke näher, das TLabel „festgehalten“ wird und zwei schwarze Striche zu jeweils einer Kante der Form laufen. Diese Funktion soll Ihnen die Positionieren erleichtern, weil so ein Mindestabstand (und immer derselbe Mindestabstand!) zum Rand der Form eingehalten wird.
Nun sollte sich das TLabel also in angemessenem Abstand von der oberen linken Ecke der Form befinden. Nehmen Sie sich nun das TEdit vor. Schieben Sie dieses nun unter das TLabel und zwar so, dass beide linksbündig sind. Sie werden sehen, dass Delphi Ihnen auch bei dieser Aufgabe hilft, indem es im richtigen Augenblick Hilfslinien einblendet. Sowohl für den Abstand vom TLabel als auch für die Bündigkeit.

Das TEdit ist für unsere Zwecke etwas klein. Es müsste nach der Positionierung noch markiert sein (zu erkennen an den unschönen, blauen Kügelchen an den Ecken und Kanten). Falls es das nicht ist, klicken Sie es an.
Bewegen Sie die Maus über das Kügelchen, welches sich in der Mitte der rechten Kante befindet, drücken Sie die Maustaste herunter, ziehen Sie die Maus nach rechts und lassen Sie die Maustaste los. Sie haben damit das TEdit breiter gemacht. Lassen Sie aber rechts noch Platz für den TButton, denn dort kommt er hin.
Markieren Sie also den TButton und schieben Sie ihn neben das TEdit. Auch dabei hilft Ihnen Delphi:

Es werden Hilfslinien eingeblendet: zum einen für den Abstand zwischen TButton und TEdit, außerdem eine Hilfslinie, damit der Text von TEdit und TButton auf derselben Höhe ist. Da der TButton etwas höher als das TEdit ist, wäre es nämlich nicht sinnvoll, die Kanten beider Komponenten aneinander auszurichten.
So, wie die Oberfläche aussieht, kann sie natürlich nicht bleiben. Wir müssen dem TLabel einen anständigen Text geben, das TEdit sollte auch einen sinnvolleren Text enthalten und der TButton eine Beschriftung bekommen, welche sagt, was er tut. Allen drei Komponenten müssen wir auch noch aussagekräftigere Namen geben, damit wir besser damit arbeiten können. (Wenn Ihnen das zu diesem Zeitpunkt nichts sagt, macht das nichts. Das kommt noch.)
Bei dieser Aufgabe kommt der Objektinspektor zum Einsatz. Markieren Sie das TLabel und werfen Sie einen Blick in den Objektinspektor. Die Standardansicht finde ich für den Anfang sehr verwirrend, daher wollen wir diese erst einmal ändern. Klicken Sie im Objektinspektor mit der rechten Maustaste auf das Wort „Eigenschaften“. Im erscheinenden Kontextmenü wählen Sie „Anordnen“ und dann „Nach Name“.
Der Objektinspektor zeigt nun eine einfache Liste mit den Eigenschaften, welche das TLabel besitzt. In der linken Spalte steht der Name einer Eigenschaft, in der rechten Spalte steht der Wert, den diese Eigenschaft momentan hat.
Zuerst interessiert uns die Eigenschaft Caption. Diese Eigenschaft enthält den Text, den das TLabel anzeigen soll. Klicken Sie nun in die rechte Spalte der Caption-Zeile. Sie sehen, dass Sie den Wert, der dort steht (im Moment „Label1“) ändern können. Wir möchten, dass das TLabel den Text „Anzuzeigender Text“ enthalten soll. Geben Sie also diesen Text dort ein, wo jetzt noch „Label1“ steht.

Das TLabel spiegelt diese Änderung übrigens sofort wieder und zeigt schon den neuen Text an!
Nun muss noch der Name des TLabel geändert werden. Momentan heißt es noch „Label1“, worunter man sich als Programmierer nicht viel vorstellen kann. Suchen Sie im Objektinspektor die Eigenschaft Name. Geben Sie dort, wo im Moment noch „Label1“ steht, den neuen Namen des TLabel ein. In diesem Fall „laDisplayText“.
An dieser Stelle ein paar Worte zur Benennung von Komponenten: Jede Komponente, welche Sie auf der Form platzieren, hat die Name-Eigenschaft. Standardmäßig ist das einfach der Typ der Komponente (Button, Label, Edit, …) und eine fortlaufende Nummer. Wollen Sie später mit diesen Komponenten arbeiten, werden Sie dutzende Komponenten haben. Da wissen Sie nicht mehr, was denn „Edit12“ war oder „Button15“.
Geben Sie Ihren Komponenten daher beschreibende Namen. Ich habe die Erfahrung gemacht, dass ein Präfix sinnvoll ist, wie in diesem Fall das „la“, welches natürlich auf ein TLabel hinweist. Anschließend einen guten Namen. Ich verwende immer englische Namen, da die Sprache Delphi auch in Englisch ist und man dann kein Gemisch aus deutschen und englischen Bezeichnern im Quelltext hat.
Ändern Sie nun auf die gleiche Weise wie beim TLabel auch die Eigenschaften des TButton. Auch dieser besitzt die Eigenschaft Caption, welche wir mit dem Text „Anzeigen“ belegen wollen. Als Namen wählen wir „btDisplayText“. Der Name unterscheidet sich nur durch das Präfix vom Namen des TLabel, sodass man sofort erkennen kann, dass beide zusammen gehören.
Auch das TEdit bedarf noch unserer Aufmerksamkeit. Hier muss allerdings nicht die Eigenschaft Caption geändert werden. Mangels einer Beschriftung besitzt das TEdit eine solche Eigenschaft nämlich nicht. Stattdessen widmen wir uns der Eigenschaft „Text“. Diese enthält den Text, der in das TEdit eingegeben wurde. Standardmäßig soll sie den Wert „Hallo, Welt!“ haben. Der Name des TEdit soll „edDisplayText“ lauten.
Die GUI (Abkürzung für „Graphic User Interface“, also die grafische Oberfläche) sollte nun so aussehen:

Bisher haben wir keine einzige Zeile Quelltext („Code“) schreiben müssen. Doch nun kommen wir nicht mehr drum herum, denn wir müssen festlegen, was beim Klick auf Anzeigen passieren soll. Der Klick auf einen TButton ist sein Standard-Ereignis (was Ereignisse genau sind, kommt später, im Moment reicht die umgangssprachliche Bedeutung). Daher reicht ein Doppelklick auf unseren TButton, damit wir zum Quelltext für dieses Ereignis gelangen.
Sie sehen nun den Quelltext-Editor vor sich, auch wieder eingerahmt von verschiedenen Boxen. Diese sind für das Schreiben von Quelltext erst bei größeren Programmen von Nutzen, daher möchte ich sie an dieser Stelle vorerst nicht weiter beschreiben. Wir konzentrieren uns auf den Editor.
Der Cursor steht an der Stelle, wo Sie den Quelltext zum Anzeigen einer Meldung mit dem vom Nutzer eingegebenen Text eingeben müssen. Dieser Quelltext wird erst einmal um zwei Leerzeichen eingerückt. Einrückungen sind wichtig, um die Struktur eines Quelltextes klar zu machen. Also: Zwei Leerzeichen tippen.
Die Anzeige einer Meldung geht am einfachstes mittels des Befehls ShowMessage. Tippen Sie „Show“ und drücken Sie dann die Tastenkombination STRG+LEERTASTE. Eine Liste mit Funktionen wird angezeigt, die alle mit dem Text „Show“ anfangen. Das ist die „Code-Vervollständigung“. Wählen Sie mit der Cursortaste ShowMessage aus und drücken Sie die Eingabetaste. Der Befehl wird eingefügt, außerdem ein Klammerpaar dahinter.
In dieses Klammerpaar muss nun eingefügt werden, was angezeigt werden soll: der Text, der sich in edDisplayText befindet. Die Code-Vervollständigung funktioniert auch hier wieder. Tippen Sie „ed“ und lassen Sie dann automatisch edDisplayText einfügen.
Das reicht aber nicht, denn wir wollen nicht das TEdit selber anzeigen (das geht ja nicht), sondern seinen Inhalt. Von oben wissen wir, dass der Inhalt in der Text-Eigenschaft gelagert wird. Tippen Sie einen Punkt. Erneut springt die Code-Vervollständigung an und zeigt Ihnen eine Liste mit den Eigenschaften des TEdit. Wählen Sie die Text-Eigenschaft aus.
Der Quelltext sollte nun so aussehen:
procedure TForm1.btDisplayTextClick(Sender: TObject);
begin
ShowMessage(edDisplayText.Text);
end;
Beachten Sie: Befehle werden in Delphi immer mit einem Semikolon abgeschlossen. Warum dann hinter dem begin keines steht, wird im Abschnitt „begin und end“ erklärt.
Wir sind nun soweit, dass wir das Programm starten können. Dies geht ganz einfach über den grünen Pfeil, den Sie in der Symbolleiste unter dem Hauptmenü von Delphi finden.

Das Programm startet und Sie können durch den Klick auf Anzeigen den Text, der im TEdit steht als Meldung ausgeben. Ihr erstes Delphi-Programm ist fertig!
Egal wieviel Design-Arbeit einem Delphi abnimmt, man kann kein Programm schreiben,ohne die Programmiersprache zu beherrschen. Es ist ein viel gemachter Fehler unter Einsteigern, dass sie sich viel zu wenig und viel zu spät mit Sprache auseinander setzen.
Es ist verständlich, dass man schnelle Erfolge sehen will und Delphi macht das auch möglich. Das Lernen einer Programmiersprache beinhaltet aber auch Durststrecken, denn wie immer im Leben ist es auch hier so: vor der Praxis kommt die Theorie.
Ich werde versuchen, immer mal wieder ein wenig Praxis einzuschieben, damit es nicht allzu trocken wird.
Eine Programmiersprache ohne Variablen ist undenkbar. Erst Variablen machen es möglich, dass ein Programm überhaupt auf Benutzereingaben reagieren kann und flexibel Aufgaben erledigen kann. Es gibt kaum einen wichtigeren Bestandteil einer Programmiersprache als Variablen.
Was kann man sich unter Variablen vorstellen? Sehr anschaulich ist es, sie sich als Platzhalter vorzustellen. Er wird dort verwendet, wo ein sich von Mal zu Mal ändernder Wert steht. Nehmen Sie zum Beispiel den Satz „Überweisen Sie mir die Hälfte Ihres Kontostandes!“. Dann ist hier der Kontostand ein Platzhalter, in den Sie den tatsächlichen Wert in Euro einsetzen müssen. Der Kontostand ist eine Variable.
Variablen kennen Sie mit Sicherheit auch aus dem Mathematik-Unterricht. Nehmen Sie zum Beispiel folgende Gleichung:
a = b + c
Die drei Buchstaben sind in diesem Fall Variablen. So kann a den Wert 5 haben, b den Wert 3 und c den Wert 2. So, wie es bisher da steht, könnte man aber auch für a den Text foobar einsetzen, für b den Text foo und für c den Text bar. Es ist ja nicht festgelegt, ob die Variablen für Zahlen oder für Texte stehen.
In Delphi muss man aber genau das für Variablen von vorneherein festlegen. Eine Variable kann entweder für eine Zahl, ein Zeichen, einen Text, etc. stehen, aber nicht für unterschiedliche Typen. Delphi ist da sehr strikt, der Programmierer muss immer genau festlegen, welchen Typ eine Variable haben soll.
Für verschiedene Aufgaben stellt Delphi verschiedene Typen bereit. Man kann auch eigene Typen definieren, dazu aber später mehr. Für den Anfang reichen uns die vorgegebenen Typen. Für Texte stellt Delphi den Typen String zur Verfügung, welches 231 Zeichen enthalten kann. Die Zeichen sind dabei fast beliebig, lediglich bei exotischeren Zeichen aus anderen Sprachen muss man manchmal auf anderes zurückgreifen.
Die Typen für Zahlen teilen sich in zwei Kategorien: Typen für ganze Zahlen und Typen für Zahlen mit Nachkommastellen. Erstere finden oft in Variablen vom Typ Integer Platz, letztere oft in Variablen vom Typ Real. Für beide Kategorien gibt es auch noch andere Typen, abhängig davon ob man nur positive oder auch negative Zahlen speichern will und wie groß die Zahlen werden können, die man speichern will. Der Datentyp Byte, der tatsächlich 1 Byte Speicher verbraucht, speichert z.B. ganze Zahlen von 0 bis 255.
Um ein bisschen vertraut mit der Verwendung von Variablen zu werden und auch damit, was es bedeutet, dass eine Variable einen festen Typ hat, soll im Folgenden ein kleines Beispielprogramm erstellt werden. Für den Einstieg gut geeignet ist ein kleiner Taschenrechner, der für den Anfang nur addieren soll.
Zuerst einmal: Legen Sie ein neues Projekt an. Dies geht am einfachsten über die Willkommens-Seite, welche über einen Klick auf die „Karteikarte“ noch erreichbar ist. Klicken Sie dort dann wieder auf „Neues Projekt“ und erstellen Sie eine VCL-Formularanwendung. Sie werden gefragt werden, ob Sie das aktuelle Projekt speichern möchten.
![]()
Falls Sie das erste Demo-Projekt behalten wollen, klicken Sie auf „Ja“ und ein Dialog zum Speichern einer Datei erscheint. Erstellen Sie einen neuen Ordner, in dem Sie das Projekt ablegen wollen. Es ist wichtig, dies zu tun, weil ein Projekt aus mehreren Dateien besteht und die in einem separaten Ordner einfach ordentlicher untergebracht sind. Speichern Sie die Datei also im erstellten Ordner. Anschließend werden Sie zum Speichern einer weiteren Datei aufgefordert, speichern Sie diese im selben Ordner. Fertig. Die neue, leere Anwendung sollte erscheinen.
Erstellen Sie nun bitte mit dem Wissen, welches Sie bereits über den Design-Modus besitzen, eine GUI dieses Aussehens:

Geben Sie dabei den Komponenten folgende Namen:
· TLabel „Erste Zahl:“: laFirstNumber, TEdit „Erste Zahl:“: edFirstNumber
· TButton „Addieren“: btAdd
· TLabel „Ergebnis“: laResult, TEdit „Ergebnis“: edResult
Bitte setzen Sie außerdem die Eigenschaft ReadOnly von edResult auf True. Suchen Sie dazu die Eigenschaft im Objektinspektor, wählen Sie sie aus und klicken Sie auf den kleinen Pfeil ganz rechts in der Eigenschaftszeile. Eine kleine Liste klappt aus, in der Sie bequem den neuen Wert auswählen können. Der Effekt: Der Benutzer kann nichts mehr in das TEdit eingeben, sondern es wird nur noch zur Anzeige verwendet.
Wechseln Sie durch einen Doppelklick auf Addieren zum Klick-Ereignis des TButton, wie Sie es vom vorigen Programm kennen. Was hier passieren soll, ist Folgendes: Die beiden Zahlen, die der Benutzer in die beiden TEdits eingegeben hat, sollen addiert werden. Das Ergebnis soll in edResult ausgegeben werden.
Hier schlägt aber schon die Typensicherheit von Delphi zu, wie man die Tatsache, dass eine Variable einen genau festgelegten Typ haben muss, auch nennt. Denn der Inhalt von TEdit-Felder ist ein Text, also vom Typ String. Addieren in dem Sinne, wie das in diesem Fall gewollt ist, kann man aber nur Zahlen.
Das Vorgehen sieht daher so aus:
1. Umwandlung der Benutzer-Eingaben in Zahlen. Diese Zahlen werden in Variablen vom Typ Integer gespeichert. Wir wollen erst einmal nur mit ganzen Zahlen rechnen.
2. Addieren der beiden Zahlen (mittels der Variablen) und speichern des Ergebnisses in einer weiteren Variable vom Typ Integer.
3. Umwandlung des Ergebnisses von einer Zahl in einen Text und Anzeige des Textes in edResult.
Bevor man Variablen benutzt, muss man Delphi den Namen und den Typ mitteilen, man muss die Variablen deklarieren. Um unsere drei Variablen zu deklarieren, ändern Sie den von Delphi bereits generierten Code so ab, dass er wie folgt aussieht:
procedure TForm1.btAddClick(Sender: TObject);
var
firstNumber, secondNumber, resultNumber : Integer;
begin
end;
Damit sind die Variablen mit Namen firstNumber, secondNumber und resultNumber als Integer deklariert und können benutzt werden.
Variablen müssen in Delphi immer am Anfang des Quelltextabschnittes deklariert werden, in dem sie benutzt werden sollen. Zwischendurch geht das nicht mehr, was für den Anfang sehr praktisch ist und zu sauberen Code führt, bei mehr Erfahrung aber auch mal lästig sein kann. Die Deklaration von Variablen beginnt immer mit dem Schlüsselwort var.
Schlüsselworte sind Worte, die grundlegende Bedeutung in der Programmiersprache an sich haben, man könnte sie auch „Steuerwörter“ nennen (was aber nicht der korrekte Fachbegriff ist). Sie dienen dazu, grundlegende Kontrukte der Sprache aufzubauen. Sie dürfen vom Programmierer im Allgemeinen nicht anderweitig verwendet werden. Schlüsselworte werden von Delphi fett geschrieben.
Nach einem var fangen Sie eine neue Zeile an und rücken um zwei Zeichen ein. Sie können die Deklaration auch über mehere Zeilen verteilen, dann wird jede weitere Zeile ebenfalls um zwei Zeichen eingerückt:
var
firstNumber,
secondNumber,
resultNumber : Integer;
Das hat aber den Nachteil, dass Sie erst in Zeile 4 den Typ sehen, den die Variablen haben sollen. Sollten Sie die Deklaration also über mehrere Zeilen strecken wollen, dann geben Sie besser in jeder Zeile den Typen an (der nur in unserem Beispiel immer gleich ist, es aber nicht sein muss):
var
firstNumber : Integer;
secondNumber : Integer;
resultNumber : Integer;
Beachten Sie: Das Schlüsselwort var müssen Sie nicht jedes Mal von Neuem angeben.
Es macht aber Sinn, zusammen gehörige Variablen auch in einer Zeile zu deklarieren, daher lassen wir es in unserem Quelltext auch so.
Sie sollten Namen von Variablen immer so wählen, dass Sie leicht erkennen können, für was diese verwendet werden. Scheuen Sie sich dabei nicht vor längeren Namen. Aber auch Abkürzungen, sofern sie leicht verständlich und sofort erkennbar sind, können zur Übersichtlichkeit beitragen. Sie werden da Ihren eigenen Stil entwickeln.
Variablen dürfen in Delphi nicht mit einer Zahl beginnen oder Leerzeichen enthalten. Sonderzeichen sind nur sehr beschränkt erlaubt, erst ab Delphi 2005 sind Umlaute erlaubt. Ich empfehle aber, diese nicht zu verwenden, falls Sie Ihre Quelltexte mal mit nicht deutschsprachigen Programmierern austauschen möchten oder mit Programmierern, die eine ältere Delphi-Version verwenden.
Übrigens: Die Komponenten, die Sie auf die Form ziehen, sind auch Variablen. Wo diese deklariert sind, wird im Laufe des Crashkurses noch klar werden. Ihre Typen kennen Sie aber schon: TButton, TLabel und TEdit. So ist also die Addieren-Schaltfläche eine Variable mit Namen btAdd vom Typ TButton.
An dieser Stelle sei erst einmal der vollständige Quelltext gezeigt und anschließend erklärt:
procedure TForm1.btAddClick(Sender: TObject);
var
firstNumber, secondNumber, resultNumber : Integer;
begin
firstNumber := StrToInt(edFirstNumber.Text);
secondNumber := StrToInt(edSecondNumber.Text);
resultNumber := firstNumber + secondNumber;
edResult.Text := IntToStr(resultNumber);
end;
Nun betrachten Sie bitte die erste Zeile nach dem begin. Die Zeile hat eine linke Seite und eine rechte Seite, getrennt durch „:=“. Das ist der so genannte Zuweisungsoperator. Er bedeutet, dass der linken Seite der Wert der rechten Seite zugewiesen wird. Nehmen wir folgenden Quellext:
a := 5;
a := a + 5;
Nach Ausfürhung der ersten Zeile, enthält die Variable a den Wert 5. Damit steht auf der rechten Seite der zweiten Zeile 5+5, was nach Adam Riese 10 ist. Nach Ausführung der zweiten Zeile hat a also den Wert 10.
In Falle des Taschenrechners sieht die rechte Seite etwas komplizierter aus. Da haben wir erst einmal StrToInt. Das ist eine Funktion. Diese werden später noch genauer beschrieben, hier sei nur gesagt: in Funktionen steckt man etwas hinein und bekommt etwas raus. In StrToInt („String to Integer“) steckt man einen aus Ziffern bestehenden Text hinein und heraus bekommt man die Zahl, die diesem Text entspricht. Der Text „123“ wird also in die Zahl 123 umgewandelt.
Hier wird der Text, der im ersten TEdit steht, in die Funktion hineingesteckt und heraus kommt die Zahl, welche diesem Text entspricht. Es ist also die Umwandlung von Text in Zahl, welche wir weiter oben „geplant“ hatten. Der Zuweisungsoperator steckt die ermittelte Zahl dann in die Variable firstNumber, sodass wir sie später verwenden können.
Falls man die Umwandlung vergessen sollte und der Integer-Variable direkt den Text (also einen String) zuweist, beschwert sich Delphi übrigens, wenn man versucht, das Programm zu starten. Die Zeile, die diesen Fehler enthält, wird rot unterlegt und am unteren Rand der IDE wird eine Fehlermeldung ausgegeben. Wundern Sie sich bitte nicht, dass dort etwas von „TCaption“ steht. Das ist identisch mit String und nur ein Alias-Name.

Sollten Sie jemals eine Fehlermeldung bezüglich inkompatibler Typen erhalten, schauen Sie zuerst einmal nach, ob alle Typen auch die sind, die sie sein sollten oder ob Sie irgendwo Umwandlungen einbauen müssen.
Die zweite Zeile brauchen wir wohl nicht weiter betrachten, dort wird dasselbe nur mit der zweiten Zahl gemacht.
Die dritte Zeile ist wiederum etwas Neues. Wieder steht dort eine Zuweisung, aber die rechte Seite sieht anders aus. Sie ist aber eigentlich sofort zu verstehen: Statt der Umwandlung von Text in Zahl steht dort nun eine simple Addition. resultNumber wird einfach die Summe der beiden anderen Zahlen zugewiesen.
Die vierte Zeile ist vom Prinzip her die genaue Umkehrung der ersten (oder zweiten) Zeile. Um das Ergebnis der Addition in einem TEdit anzeigen zu können, muss es zuerst wieder in einen Text umgewandelt werden. Dazu wird die Funktion IntToStr („Integer to String“) verwendet. In diese steckt man eine Zahl hinein und erhält den entsprechenden Text zurück.
Hier wird also der Text-Eigenschaft von edResult das in einen Text umgewandelte Ergebnis der Addition zugewiesen.
Nun ist unser Mini-Taschenrechner fertig und kann über den bereits bekannten grünen Pfeil gestartet werden. Achten Sie darauf, dass wir keine Abfrage eingebaut haben, ob der Benutzer auch wirklich Zahlen in die TEdits eingibt, daher würde das noch zu Fehlern führen. Ansonsten wird die Addition von ganzen Zahlen mit diesem Code perfekt durchgeführt.
Den Taschenrechner werden wir an vielen Stellen in diesem Crashkurs noch brauchen, daher wäre es eine gute Idee, ihn zu speichern. Wählen Sie dazu im Datei-Menü den Punkt „Alles speichern“ aus. Gehen Sie beim Speichern vor, wie ich es unter „Ein neues Projekt anlegen“ beschrieben habe.
Kontrollstrukturen sind wie Variablen essenzielle Bestandteile jeder Programmiersprache. Teilweise gehören sie zu den Kriterien, wann etwas überhaupt eine Programmiersprache ist.
Sie bieten dem Programmierer die Möglichkeit, den Fluss des Programmes zu beeinflussen. Ein Programm kann mittels Kontrollstrukturen gewisse Passagen mehrmals ausführen, ohne dass sie mehrmals geschrieben werden müssen. Es ist auch möglich, Verzweigungen in ein Programm einzubauen, sodass unter bestimmten Bedingungen mal die eine und mal die andere Passage ausgeführt wird.
Bevor solche Kontrollstrukturen aber zur Anwendung kommen, muss man sich überlegen, wie man klar macht, was eine Passage ist. Eine Passage ist für den Menschen leicht verständlich, es ist ein zusammenhängender Teil Quelltext. Im Programm muss man aber ganz klar markieren, wo ein solcher Teil beginnt und wo er endet.
Genau für diesen Zweck gibt es die Schlüsselworte begin und end. Gesehen haben Sie die beiden schon, nämlich bei dem Code, der bei Klick auf einen TButton ausgeführt wird. Zuletzt sah der ja so aus, dass erst einmal die Variablen deklariert wurden und dann alles dananch in einem Rutsch ausgeführt werden sollte. Und dieses „alles“ war in begin und end eingefasst.
Das funktioniert mit allen Quelltext-Passagen so. Erst schreibt man ein begin, dann alle Befehle dieser Quelltext-Passage und dann ein end. Wichtig: hinter das begin kommt kein Semikolon, hinter das end sehr wohl! Das hat den Grund, weil der begin-end-Block für Delphi ein Befehl ist, der mit dem end abgeschlossen wird. Daher steht nur hinter dem end ein Semikolon, nicht hinter dem begin. Der begin-end-Block enthält wiederum Befehle, die nach den üblichen Regeln jeder mit einem Semikolon abgeschlossen werden.
Der Mini-Taschenrechner soll um eine neue Funktion erweitert werden. Fügen Sie dazu einen neuen TButton ein, platzieren Sie ihn neben dem vorhandenen TButton und nennen ihn „btDivide“. Als Caption-Eigenschaft setzen Sie „Dividieren“ ein. Wechseln Sie mit einem Doppelklick in das Klick-Ereignis des TButton.
Die Deklaration der Variablen und den auszuführenden Code können sie praktisch aus dem Addieren-Ereignis übernehmen und müssen ihn nur an einer Stelle ändern: Aus dem + machen Sie ein div, was der Befehl für eine ganzzahlige Divison (also ohne Rest) in Delphi ist. Eine Division für Komma-Zahlen geht mit /, aber im Moment beschäftigen wir uns nur mit Integer-Zahlen.
Starten Sie das Programm über den grünen Pfeil, geben Sie als erste Zahl mal eine 5 ein und als zweite Zahl eine 0. Klicken Sie auf „Dividieren“. Prompt springt Ihnen eine Fehlermeldung entgegen:

Delphi informiert Sie darüber, dass eine Ausnahme („Exception“) aufgetreten ist. Das kommt daher, dass eine Division durch Null nicht definiert ist und daher das Ergebnis ebenso wenig. Delphi löst daher eine Exception vom Typ EDivByZero aus mit dem Meldungstext „Division durch Null“. Exceptions werden wir später noch genauer unter die Lupe nehmen
Klicken Sie auf Fortsetzen, bestätigen Sie die nochmals erscheinende Fehlermeldung und schließen Sie das Programm. Wir wollen uns nun überlegen, wie man mit diesem Fehler umgeht.
Der beste Weg ist zu schauen, ob der Benutzer als zweite Zahl eine Null eingegeben hat. Falls dem so ist, gibt man in edResult eine Meldung aus, falls dem nicht so ist, gibt man das Ergebnis aus. Und da ist sie auch schon, die if-Anweisung. Wir wollen, dass je nach Eingabe des Benutzers verschiedener Code ausgeführt wird.
Und das geht wie folgt:
firstNumber := StrToInt(edFirstNumber.Text);
secondNumber := StrToInt(edSecondNumber.Text);
if secondNumber = 0 then
edResult.Text := 'Division durch Null';
if secondNumber <> 0 then
begin
resultNumber := firstNumber div secondNumber;
edResult.Text := IntToStr(resultNumber);
end;
Die ersten beiden Zeilen sind bekannt und haben sich nicht geändert. Danach kommt etwas Neues, besagte if-Anweisung. Sie ist eigentlich fast selbst erklärend, ihre Struktur ist:
if {Bedingung} then
{Anweisung};
Die Bedingung ist in diesem Fall, dass die zweite Zahl gleich Null ist. Die Anweisung ist, dass der Text-Eigenschaft von edResult der String „Division durch Null“ zugewiesen wird.
Hier sehen Sie übrigens, wie man Strings im Quelltext schreibt: Vorne ein Apostroph (kein Akzent-Zeichen!), dann der Text und hinten nochmal ein Apostroph.
Die zweite if-Anweisung ist recht ähnlich. Die Bedingung ist hier, dass die zweite Zahl ungleich Null ist. Der Vergleichsoperator für „ungleich“ ist in Delphi <>. Die Anweisung ist in diesem Fall ein Block, abgegrenzt durch begin und end, wie es weiter oben beschrieben ist. Achten Sie immer darauf: Sollen bei einer if-Anweisung mehr als eine Zeile Quelltext ausgeführt werden, müssen diese mit begin und end in einen Block gefasst werden!
Sie können obigen Quelltext jetzt einfach per Copy & Paste in Ihr Projekt übernehmen. Um Ihnen aber ein paar nette Funktionen des Delphi-Editors näher zu bringen, tippen Sie ihn (bis auf die beiden ersten Zeilen) ab.
Sobald Sie das Wort „if“ getippt haben, wird Delphi den Rest der if-Anweisung für Sie ergänzen.

Sie müssen nur noch den Text „secondNumber = 0“ tippen und dann die Eingabetaste drücken. Delphi bietet dieses „Templates“ genannte Feature bei einigen Schlüsselwörtern.
Nachdem Sie (mit zwei führenden Leerzeichen) „edResult.“ getippt haben, springt die bereits bekannte Code-Vervollständigung an und erleichtert Ihnen das Leben.
Die zweite if-Anweisung bietet nichts Neues. Wenn Sie jedoch „begin“ tippen und dann die Eingabetasten drücken, wird automatisch das passende „end;“ ergänzt und außerdem der Cursor an die richtige, schon eingerückte Stelle gesetzt. Die restlichen zwei Zeilen bieten keine neuen Editor-Features.
Ich werde an anderer Stelle nochmals ein paar Features vorstellen.
Wenn Sie mit diesem Quelltext noch einmal die Division durch Null versuchen, werden Sie sehen, dass keine Fehlermeldung mehr kommt, sondern nur der Text in edResult ausgegeben wird.
Man kann sich ein wenig Schreibarbeit sparen, indem man sich des Schlüsselwortes else bedient. Da die Bedingung der zweiten if-Anweisung die exakte Umkehrung der Bedingungen der ersten if-Anweisung ist, haben wir es mit einem wenn-dann-sonst-Konstrukt zu tun. Das kann man auch in Delphi haben:
firstNumber := StrToInt(edFirstNumber.Text);
secondNumber := StrToInt(edSecondNumber.Text);
if secondNumber = 0 then
edResult.Text := 'Division durch Null'
else
begin
resultNumber := firstNumber div secondNumber;
edResult.Text := IntToStr(resultNumber);
end;
Die Bedeutung ist klar: Wenn die Bedingung wahr ist, führe den if-Zweig (also den ersten Quelltext) aus, ansonsten führe den else-Zweig (also den zweiten Quelltext) aus.
Wichtig: Vor einem else kommt niemals ein Semikolon! Das gilt auch für den Fall, dass ein end vor dem else steht.
Man kann if-Anweisungen natürlich auch verschachteln:
if {Bedingung} then
if {Bedingung} then
{Anweisung};
Das macht keine Probleme und man erkennt auf Anhieb, was passiert. Für Verwirrung kann die Verwendung von else-Zweigen sorgen, wenn man nicht korrekt einrückt:
if {Bedingung} then
if {Bedingung} then
{Anweisung}
else
{Anweisung};
Das ist falsch. Die Einrückung suggeriert, dass der else-Zweig zur ersten if-Abfrage gehört. Das stimmt nicht. Ein else-Zweig gehört immer zur nächst innersten if-Anweisung. Korrekt ist also diese Verschachtelung:
if {Bedingung} then
if {Bedingung} then
{Anweisung}
else
{Anweisung};
Soll der else-Zweig zur ersten if-Anweisung gehören, muss man einen begin-end-Block verwenden:
if {Bedingung} then
begin
if {Bedingung} then
{Anweisung};
end
else
{Anweisung};
Beachten Sie auch, dass die Semikola nun anders gesetzt sind.
An dieser Stelle möchte ich noch einmal darauf eingehen, was man in einer if-Anweisung als Bedingung einsetzen kann. Die Bedingung einer if-Anweisung ist ein Wahrheitswert, in Delphi also ein Wert von Typ Boolean. Dieser kann nur die Werte true (wahr) oder false (falsch) annehmen.
Ein Vergleich, ob zwei Zahlen gleich sind, wie im Mini-Taschenrechner, hat als Ergebnis einen Boolean und kann somit in einer if-Anweisung verwendet werden. Weitere Vergleiche sind übrigens kleiner (<) und größer (>).
So ist die Aussage „Ich habe weniger Geld im Portemonaie als Sie.“ entweder wahr oder falsch. Nun kann man das Geld in meinem Portemonaie als zahl1 bezeichnen, das Geld in Ihrem als zahl2. Damit lässt sich die Aussage als zahl1 < zahl2 formulieren.
In einem booleschen Ausdruck (benannte nach George Boole) werden Wahrheitswerte miteinander verknüpft, das Ergebnis dieser Verknüpfung ist wieder ein Wahrheitswert. In Delphi benutzt man dies, um komplexere Bedingungen zu formulieren.
var newBoolean, boolean1, boolean2 : Boolean;
begin
newBoolean := boolean1 and boolean2;
newBoolean := boolean1 or boolean2;
newBoolean := not boolean1;
newBoolean := boolean1 xor boolean2;
end;
Die ersten beiden Ausdrücke dürften selbst erklärend sein. Beim Ersten ist newBoolean dann wahr, wenn sowohl boolean1 als auch boolean2 wahr sind. Beim Zweiten ist newBoolean dann wahr, wenn boolean1 wahr ist oder boolean2 wahr ist oder wenn beide wahr sind.
Der dritte Ausdruckt zeigt die Umkehrung eines Wahrheitswertes. Ist also boolean1 wahr, so ist newBoolean falsch und umgekehrt.
Im vierten Ausdruck handelt es sich um ein „ausschließendes oder“ („exclusive or“). In der Umgangssprache findet man dieses in Form von „entweder … oder“. newBoolean ist wahr, wenn entweder boolean1 oder boolean2 wahr ist, aber nicht beide!
Da jede dieser Verknüpfungen wieder einen Boolean ergibt, kann man auch diese wieder miteinander verknüpfen:
boolean1 and boolean2 or boolean3
Diese Quelltextzeile ist zwar gültig und absolut eindeutig auswertbar, aber nicht sehr schön zu lesen. Man muss erst einen Moment drüber nachdenken, welcher Ausdruck zuerst ausgewertet wird. Daher empfehle ich, auch dann Klammern zu setzen, wenn es nicht unbedingt nötig ist:
(boolean1 and boolean2) or boolean3
Delphi hätte diesen Ausdruck von vorneherein so ausgewertet. Delphi wertet Ausdrücke an Hand der Operatorpräzedenz und der Reihenfolge aus. Operatorpräzedenz kann man sich „Stärke der Bindung“ eines Operators vorstellen. Sie begegnet einem im Alltag bei dem Satz „Punkt- vor Strichrechnung“. Dieser sagt aus, dass die Punktrechung (der Multiplikations- und Divisionsoperator) eine höhere Präzedenz besitzt als die Strichrechnung (der Additions- und Subtraktionsoperator).
Die Präzedenz der Booleschen Operatoren ist wie folgt:
1. not
2. and
3. or
4. xor
Folgender Ausdruck liefert daher dasselbe Ergebnis wie der Obige:
boolean3 or boolean1 and boolean2
Bei Operatoren gleicher Präzendenz wertet Delphi von links nach rechts aus.
Die Auswertungsreihenfolge kann man sich im so genannten „Kurzschlussverfahren“ zu Nutze machen. Dabei wird ein Boolscher Ausdruck nur so weit ausgewertet, bis sein Ergebnis fest steht, der Rest wird nicht mehr beachtet.
Hat man zum Beispiel eine and-Verknüpfung und bereits der erste Teil ist falsch, so steht das Gesamtergbnis auch fest und der zweite Teil muss gar nicht mehr beachtet werden. Denn selbst wenn dieser wahr wäre, würde das bei einer and-Verknüpfung am Gesamtergebnis nichts mehr ändern.
Das ist sehr praktisch, wenn die einzelnen Ausdrücke Funktionen sind. Dann würde man als ersten Ausdruck eine Funktion benutzen, die sehr schnell arbeitet und als zweiten Ausdruck die langsamere Funktion. Wenn man Glück hat, muss die langsame Funktion dann gar nicht mehr ausgewertet werden und das spart Zeit.
Bei der vollständigen Auswertung würde dagegen ein Ausdruck auch dann vollständig ausgewertet, selbst wenn sein Ergebnis bereits fest steht. Dies kann auch nützlich sein, zum Beispiel dann, wenn ein Teil des Ausdrucks Auswirkung auf das Programm hat, also z.B. eine Funktion ist, die eine globale Variable ändert. Man redet dann auch von einer „Funktion mit Nebeneffekten“.
Für Spezialfälle gibt es eine elegantere Möglichkeit als die if-Anweisung, um Verzweigungen in ein Programm einzubauen: die case-Anweisung. Betrachten Sie folgenden Quelltext:
if zahl = 0 then
anweisung1;
if zahl = 1 then
anweisung1;
if zahl = 2 then
anweisung1;
if zahl = 3 then
anweisung2;
if zahl = 4 then
anweisung3;
if zahl = 5 then
anweisung1;
Das sieht natürlich nicht sehr schön aus. Die case-Anweisung kann diesen Umstand beheben:
case zahl of
0: anweisung1;
1: anweisung1;
2: anweisung1;
3: anweisung2;
4: anweisung3;
5: anweisung1;
end;
Wie diese Anweisung funktioniert, sollte klar sein: Es wird geschaut, ob der Wert von zahl mit einem der Werte in der case-Anweisung übereinstimmt. Wird der Wert gefunden, wird die entsprechende Anweisung (und nur die Anweisung, nicht auch noch die Nachfolgenden, wie in anderen Sprachen) ausgeführt. Statt anweisung1 o.ä. kann dort natürlich auch ein begin-end-Block stehen.
Die case-Anweisung kann aber noch mehr:
case zahl of
0..2, 5: anweisung1;
3: anweisung2;
else
anweisung3;
end;
Wie Sie sehen, können Sie Wertebereiche angeben und auch mehrere Werte mittels Kommata aufzuzählen. Und einen else-Zweig bietet die case-Anweisung auch noch.
Es gibt leider zwei Haken an der Sache:
1. Es können nur konstante Ausdrücke als Vergleichswerte angegeben werden. Anstatt der Zahlen dürfte man oben also keine Variablennamen eintragen.
2. Man kann mittels einer case-Anweisung nur die Werte von Variablen abfragen, welche einen ordinalen Typ besitzen. Ein Typ ist dann ordinal, wenn man seine Werte aufzählen kann. Jeder Wert eines solchen Typs muss einen eindeutigen Vorgänger (Ausnahme: der erste Wert) und einen eindeutigen Nachfolger (Ausnahme: der letzte Wert) besitzen. Ein Integer ist daher ein ordinaler Typ, denn ganze Zahlen kann man auf diese Weise aufzählen. Ein String ist z.B. nicht ordinal, denn es ist nicht eindeutig, was der Nachfolger von „aaa“ sein soll.
Schleifen bieten dem Programmierer die Möglichkeit, gewisse Teile seines Quelltextes mehrmals hintereinander ausführen zu lassen. Dabei muss zum Zeitpunkt, zu dem das Programm geschrieben wird, nicht festgelegt werden, wie oft das sein soll. Das kann sich erst im Laufe der Benutzung des Programmes herausstellen.
Dabei bieten Schleifen verschiedene Möglichkeiten, anzugeben, wann die Ausführung der Anweisung gestoppt werden soll, so z.B. neben der Angabe einer Anzahl von Schleifendurchläufen auch eine Bedingung, welche zum Abbruch führt.
Zur Demonstration einer for-do-Schleife erweitern wir den Mini-Taschenrechner um eine weitere Funktion. Dazu wird wieder ein TButton eingefügt. Dieses Mal handelt es sich um den TButton btFaculty mit der Aufschrift „Fakultät“.
Hier sei kurz erklärt, was die Fakultät eine Zahl ist. Die Fakultät von 5 wird geschrieben als 5! und ist: 5*4*3*2*1. Das Prinzip sollte klar sein. Häufige Anwendung findet die Fakultät in der Wahrscheinlichkeitsrechnung.
Wechseln Sie in das Klick-Ereignis von btFaculty, indem Sie doppelt auf den TButton klicken. Dieses Mal wird nur firstNumber und resultNumber deklariert und auch nur firstNumber aus dem entsprechenden TEdit eingelesen.
Außerdem muss resultNumber dieses Mal mit dem Wert 1 initialisiert werden. Unter der Initialisierung einer Variable versteht man ihre erstmalige Belegung mit einem Wert. Als Grundregel sollten Sie sich merken: Benutzen Sie nicht den Wert einer nicht initialisierten Variable. In einigen Fällen geht das gut, aber es ist auch für den Leser des Quelltextes angenehmer, wenn er sieht, mit welchem Wert initialisiert wird.
Ihr Quelltext sollte bisher so aussehen:
procedure TForm1.btFacultyClick(Sender: TObject);
var
firstNumber, resultNumber : Integer;
begin
firstNumber := StrToInt(edFirstNumber.Text);
resultNumber := 1;
end;
Tippen Sie nun das Wort „for“ und drücken Sie die Leertaste. Wieder springt ein Template ein und hilft Ihnen dabei, die for-do-Schleife anzulegen:

Im Gegensatz zum Template der if-Anweisung gibt es hier drei Stellen, an denen Sie eigene Text einfügen müssen.
Die erste Stelle ist der Schleifenzähler (auch kurz „Zähler“ genannt). Dort steht im Moment noch ein großes I, machen Sie ein kleines i daraus. Drücken Sie nun dieTabulator-Taste. Geben Sie als Wert für die Untergrenze eine 1 ein, wo im Moment noch eine 0 steht. Drücken Sie erneut die Tabulator-Taste. Geben Sie als Obergrenze der Schleife „firstNumber“ ein und entfernen Sie das „- 1“ durch mehrmaliges Drücken der Entfernen-Taste. Drücken Sie nun die Eingabetaste.
Achten Sie darauf, was mit dem Deklarationsabschnitt passiert ist. Dort wurde die Variable i als Integer deklariert. Ergänzen Sie Ihren Quelltext noch so, dass er wie folgt aussieht:
procedure TForm1.btFacultyClick(Sender: TObject);
var
firstNumber, resultNumber : Integer;
i: Integer;
begin
firstNumber := StrToInt(edFirstNumber.Text);
resultNumber := 1;
for i := 1 to firstNumber do
resultNumber := resultNumber * i;
edResult.Text := IntToStr(resultNumber);
end;
Was passiert nun dort?
Eine for-do-Schleife führt einen Quelltext eine begrenzte Anzahl von Malen aus. Dies wird durch die Verwendung eines Schleifenzählers erreicht, das ist in diesem Fall i. Der Quelltext, welches von der for-do-Schleife ausgeführt wird, steht direkt dahinter. Hier ist es nur eine Zeile, es kann aber natürlich auch ein begin-end-Block sein. Den Quelltext, den die Schleife ausführt, nennt man Schleifenrumpf.
Die for-do-Schleife macht folgendes: Zu Anfang wird i der Wert 1 zugewiesen. Dann wird der Schleifenrumpf (unter Verwendung des Wertes 1 als i) ausgeführt. Nach Ausführung wird der Variabel i der Wert 2 zugewiesen und der Schleifenrumpf erneut (mit i = 2) ausgeführt. Dies geht so lange, bis i der Wert von firstNumber zugewiesen wurde. Mit diesem Wert wird der Rumpf noch aufgerufen, danach ist die Schleife beendet.
Wenn man sich also mal die Anweisungen, die ausgeführt werden, nacheinander aufschreibt, sieht das so aus:
resultNumber := 1*1; //da resultNumber = 1 und i = 1
resultNumber := 1*2; //da resultNumber = 1 und i = 2
resultNumber := 2*3; //da resultNumber = 2 und i = 3
resultNumber := 6*4; //da resultNumber = 6 und i = 4
resultNumber := 24*5; //da resultNumber = 24 und i = 5
Und damit wird die Fakultät der Zahl in firstNumber berechnet.
Die grünen Teile des Quelltextes nennt man übrigens Kommentare. Sie werden von Delphi ignoriert und dienen dem Programmierer dazu, sich Anmerkungen zu seinem Quelltext zu machen. Zu einem späteren Zeitpunkt wird noch kurz beschrieben, welche Arten von Kommentaren es gibt. Diese dort oben werden durch // eingeleitet und gehen bis zum Ende der Zeile.
Noch ein paar Bemerkungen zur for-do-Schleife:
1. Im Schleifenrumpf darf die Zählvariable (in diesem Fall als das i) nicht verändert werden. Delphi verhindert das, wenn man es nicht austrickst.
2. Sowohl Anfangs- als auch Endwert der Variable dürfen Variablen sein.
3. Die vorliegende for-do-Schleife zählt nur hoch. Wäre der Endwert kleiner als der Anfangswert, würde der Schleifenrumpf kein Mal ausgeführt. Möchte man runterzählen (also i=5,4,3,..), so muss man das to in der for-do-Schleife durch ein downto ersetzen.
Oft ist die Anzahl der Schleifenläufe nicht so einfach zu bestimmen wie bei einer for-do-Schleife, sondern man muss andere Bedingungen finden, wann eine Schleife beendet werden soll. Dazu wird zum Beispiel die repeat-until-Schleife verwendet.
Der Name sagt eigentlich schon, was die Schleife macht. Sie wiederholt etwas, bis eine Bedingung eintritt. Die Bedingung wird, wie bei einer if-Anweisung, mittels eines Booleschen Ausdrucks angegeben. Die Fakultätsberechnung von oben sähe dann zum Beispiel so aus:
resultNumber := 1;
i := 1;
repeat
resultNumber := resultNumber*i;
i := i+1;
until i > 5;
Hier wird die Zählvariable i vom Programmierer selber erhöht und entsprechend abgefragt, ob sie größer als 5 geworden ist und die Schleife somit beendet werden muss. Die Schleife wird also beendet, wenn die Bedingung wahr ist.
Zwei Dinge sollten noch zur repeat-until-Schleife gesagt werden: Zum einen ist wichtig, dass der Schleifenrumpf (also das, was zwischen repeat und until steht), mindestens einmal ausgeführt wird, weil die Bedingung ja erst am Fuß der Schleife geprüft wird. Zum anderen fällt auf, dass kein begin und end nötig ist. Die auszuführenden Anweisungen sind ja bereits durch das repeat und until begrenzt.
Die while-do-Schleife ist der repeat-until-Schleife sehr ähnlich. Obiger Quelltext mit einer while-do-Schleife sähe so aus:
resultNumber := 1;
i := 1;
while i <= 5 do
begin
resultNumber := resultNumber*i;
i := i+1;
end;
Folgendes sind die Unterschiede zwischen der repeat-until- und der while-do-Schleife:
1. Die Bedingung der while-do-Schleife wird im Kopf geprüft. Es kann also sein, dass die Schleife auch keinmal ausgeführt wird.
2. Soll mehr als eine Anweisung ausgeführt werden, muss ein begin-end-Block verwendet werden.
3. Die Schleife läuft so lange, wie die Bedingung wahr ist. Sie bricht (als genaues Gegenteil der repeat-until-Schleife) ab, sobald die Bedingung falsch ist.
Es kann vorkommen, dass man eine Schleife entweder vollständig abbrechen möchte, oder dass man den aktuellen Schleifendurchlauf abbrechen und direkt mit dem näcshten fortfahren möchte. Für die erste Möglichkeit bietet Delphi den break-Befehl, für die Zweite den continue-Befehl.
Ein Beispiel für die Verwendung könnte so aussehen:
for i := 0 to 10 do
begin
if i = 5 then
continue;
if i = 8 then
break;
end;
Die erste if-Abfrage sorgt dafür, dass für i = 5 der
aktuelle Schleifendurchlauf beendet wird un direkt mit dem nächsten (also i =
6) weiter gemacht wird. Die zweite if-Abfrage hat den Effekt, dass die Schleife
nie bis i = 10 laufen wird, sondern bereits bei i = 8 komplett abbricht.
Prozeduren und Funktionen sind vor allem dazu da, dass man Code, der an mehreren Stellen vorkommt, nicht auch mehrmals schreiben muss. Sicherlich könnte man diesen auch per Copy & Paste übernehmen, aber das ist nicht sinnvoll. Etwas weiter unten wird näher darauf eingegangen, weshalb das so ist.
Um anschaulich zu machen, wie Prozeduren funktionieren, fügen wir eine weitere Funktion zum Taschenrechner hinzu: Die Eingaben sollen getauscht werden. Es soll also die Zahl aus dem ersten TEdit ins Zweite verschoben werden und umgekehrt. (Dank an Michael „Luckie“ Puff, der mir die Idee zu diesem Beispiel gab.)
Zu Anfang kommt mal wieder ein neuer TButton hinzu: btSwap mit dem Titel „Tauschen“. Und natürlich auch wieder ein Klick-Ereignis.
Bevor es dann weiter geht, muss man sich kurz klar machen, wie man allgemein zwei Variablen tauschen kann. Dazu braucht man eine Hilfsvariable, denn wenn man den Inhalt der ersten Variable in die Zweite übertragen hat, ist der alte Inhalt der zweiten Variable weg. Der muss also vorher gesichert und anschließend in die erste Variable geschrieben werden.

Als Prozedur sieht das dann (für Variablen vom Typ String) so aus:
procedure DoSwap(var left, right: String);
var
temp : String;
begin
temp := right;
right := left;
left := temp;
end;
Hier wurde eine Prozedur angelegt (eingeführt mit dem Schlüsselwort procedure) mit dem Namen „DoSwap“. Diese Prozedur erwartet zwei Parameter vom Typ String. Diese Parameter stehen innerhalb der Prozedur als Variablen zur Verfügung, welche mit dem Wert vorbelegt sind, den sie durch den Aufruf der Prozedur erhalten.
Fügen Sie diese Prozedur über dem Klick-Ereignis von btSwap ein, also über der Zeile „procedure TForm1…“.
Der Aufruf sieht (im Klick-Ereignis von btSwap) so aus:
procedure TForm1.btSwapClick(Sender: TObject);
var
firstString, secondString: String;
begin
firstString := edFirstNumber.Text;
secondString := edSecondNumber.Text;
DoSwap(firstString, secondString);
edFirstNumber.Text := firstString;
edSecondNumber.Text := secondString;
end;
Es wird also das, was in den beiden TEdits steht, erst in Variablen vom Typ String übertragen und dann wird unsere neue Prozedur DoSwap mit diesen beiden Variablen als Parameter aufgerufen. Anschließend wird der (getauschte) Inhalt der Variabeln zurück in die TEdits geschrieben.
Das sieht sehr umständlich aus und wirft die Frage auf, weshalb man nicht direkt die Text-Eigenschaften der TEdits in den Prozeduraufruf schreibt. Das hat man mit dem Wörtchen var in der Parameterliste var left, right: String zu tun.
Werden Parameter mit dem Schlüsselwort var versehen, so hat das den Effekt, dass alles, was man in der Prozedur mit diesem Parameter macht, sich auch auf die Variable auswirkt, die man als Parameter übergeben hat. Im obigen Beispiel heißt das, dass eine Änderung von left in der Prozedur DoSwap sich direkt auf die Variabel firstString im Klick-Ereignis auswirkt.
Man nennt diese Art der Parameterübergabe „Call by Reference“. Man gibt der Prozedur nur eine Referenz auf eine Variable. Mit anderen Worten: Man teilt ihr beim Aufruf mit, welche externe Variable geändert werden soll. Würde man als in DoSwap dem Parameter left den Wert „foo“ zuweisen, so hätte auch firstString im Klick-Ereignis den Wert „foo“.
Und genau deshalb kann man nicht die Text-Eigenschaften der TEdits direkt der Prozedur übergeben, weil diese nämlich keine Variablen im herkömmlichen Sinne sind, wie Sie sie deklarieren können, sondern Eigenschaften. Und bei diesen funktioniert Call by Reference nicht. Sollten Sie einmal versuchen, eine Eigenschaft als var-Parameter zu übergeben, wird Delphi Sie mit dieser Fehlermeldung beglücken:
![]()
Möchte man nicht, dass die Änderungen der Paramter in der Prozedur auch auf Auswirkungen auf die entsprechenden externen Variablen haben, so kann man die Parameter mittels „Call by Value“ übergeben. Dazu lässt man einfach das var vor der Parameterliste weg.
Dann werden nur die Werte der Parameter an die Prozedur übergeben. In der Prozedur wird dann mit Kopien der Parameter gearbeitet und die Originale bleiben unverändert. Würden Sie also in DoSwap das var aus der Parameterliste entfernen, würde ein Aufruf der Prozedur überhaupt nichts mehr bewirken, weil firstString und secondString nicht geändert würden.
Zuerst einmal der Hinweis: Alles, was bei Prozeduren zu Call by Reference und Call by Value gesagt wurde, gilt völlig identisch auch für Funktionen.
Der Unterschied zwichen Prozeduren und Funktionen ist sehr klein, daher seien Funktionen hier nur noch kurz erklärt. Funktionen sind Prozeduren, die noch einen Wert zurückgeben können. Um Ihnen Fuktionen näher zu bringen, soll nun die Berechnung der Fakultät im Mini-Taschenrechner neu geschrieben werden. Dabei lernen Sie noch ein weiteres, wichtiges Konzept der Programmierung kennen: die Rekursion.
Eine Rekursion liegt dann vor, wenn eine Funktion sich selbst aufruft. Nehmen wir als Beispiel die Fakultät. Die Fakultät von 5 ist: 5! = 5*4*3*2*1. Das kann man schreiben als 5! = 5*4! Die Funktion ruft sich also wieder selber auf, weil 5! brechnet wird, indem man 4! berechnet. Das ganze kann man als Funktion in Delphi umsetzen und damit das bisherige Klick-Ereignis für btFaculty ersetzen:
function faculty(n : Integer) : Integer;
begin
if n <= 0 then
result := 0
else
if n <= 2 then
result := n
else
result := n*faculty(n-1);
end;
procedure TForm1.btFacultyClick(Sender: TObject);
var
firstNumber, resultNumber : Integer;
begin
firstNumber := StrToInt(edFirstNumber.Text);
resultNumber := faculty(firstNumber);
edResult.Text := IntToStr(resultNumber);
end;
Die Funktion faculty erhält einen einzigen Parameter n mittels Call by Value. Sie berechnet die Fakultät von n und gibt diese in Form eines Integer-Wertes zurück. Dies wird durch das „: Integer“ hinter der Parameterliste deutlich gemacht, auf diese Weise bestimmt man, welchen Typ die Funktion als Ergebnis haben soll.
Das Ergebnis einer Funktion wird über die automatisch vorhandene, nicht zu deklarierende Variable result gesetzt. Mit result kann man arbeiten wie mir jeder anderen Variable. Der Wert von result ist der Rückgabewert der Funktion.
Die Fakultät einer Zahl kleiner oder gleich Null wurde hier als Null definiert. Eigentlich ist die Fakultät für Zahlen kleiner Null nicht definiert, aber für dieses Beispielprogramm reicht es so. Die zweite if-Abfrage schaut, ob die Zahl 1 oder 2 ist, dann wird die Zahl selber zurück gegeben, da 1! = 1 und 2! = 2.
Trifft keiner dieser Fälle zu, wenn die Fakultät also wirklich berechnet werden muss, so wird dies wie weiter oben beschrieben getan: faculty ruft sich selbst mit n-1 auf und multipliziert das Ergebnis mit n. Aus 5! wird 5*4!.
Achten Sie immer darauf, dass ihre Rekursion auch irgendwann endet. Es muss bei einer Rekursion irgendwann dazu kommen, dass die Funktion sich nicht mehr selber aufruft, sondern das Ergebnis anderweitig ermittelt wird. Hier ist das der Fall, wenn n den Wert 2 oder kleiner besitzt. Da n von Rekursion zu Rekursion kleiner wird (weil ja immer n-1 als Parameter übergeben wird), wird dieser Fall erreicht und die Rekursion endet.
Prozeduren und Funktionen zu verwenden ist kein Selbstzweck, sondern hat wichtige und auch sehr leicht einsichtige Gründe:
· Sie sparen Arbeit! Wenn man mehrmals im Programm die Fakultät einer Zahl berechnen will oder zwei Variablen tauschen will, dann muss man nicht jedes Mal den ganzen Quelltext schreiben, sondern ruft einfach nur die entsprechende Prozedur bzw. Funktion auf.
· Der Quelltext wird übersichtlicher! Ab einer gewissen Verschachtelungstiefe (durch if-Abfragen und Schleifen) und einer gewissen Länge wird ein Quelltext einfach unübersichtlich und schwer zu lesen. Das macht ihn auch fehleranfällig.
· Prozeduren und Funktionen haben Namen! Man erkennt am Namen, was gemacht wird. Wählt man sprechende Namen, so sieht man immer sofort, was gemacht wird.
· Der Quelltext wird wartbarer! Wenn es einen Fehler in einer Routine gibt, brauchen Sie diese nur an einer Stelle ändern, nämlich in Prozedur oder Funktion. Würde man keine Funktion oder Prozedur nutzen, müsste man alle Stellen suchen, wo die Routine verwendet wird.
· Sie steigern die Wiederverwendbarkeit! Man kann Prozeduren und Funktionen meist einfach in andere Programme übernehmen und auch dort wieder benutzen. Einmal eine Funktion / Prozedur geschrieben, hat man die Arbeit nie wieder.
Arrays bieten die Möglichkeit, Daten strukturiert abzulegen und sind ein wichtiges Mittel für jeden Programmierer. Ein Array besteht aus beliebig vielen Elementen eines Datentyps. Jedes Element besitzt eine eindeutige „Adresse“ innerhalb des Arrays, welche ein Tupel aus n Zahlen ist, wobei n die Dimension des Arrays ist.
Eine Vorstellung eines Arrays erhält man mit dieser Beschreibung natürlich noch nicht. Daher möchte ich hier einfach mal ein paar Beispiele geben. Ein eindimensionales Array kann man sich wie eine Tabelle mit nur einer Zeile vorstellen:
Wert [0] Wert [1] Wert [2] Wert [3]
Die Adresse besteht in diesem Fall aus einer einzelnen Zahl (0…3), welche die Position des Elementes im Array vollständig beschreibt.
Ein zweidimensionales Array kann man sich auch noch gut vorstellen, nämlich als Tabelle mit Zeilen und Spalten:
Wert [0,0] Wert [0,1] Wert [0,2]
Wert [1,0] Wert [1,1] Wert [1,2]
Wert [2,0] Wert [2,1] Wert [2,2]
Für ein zweidimensionales Array benötigt man schon ein Tupel aus zwei Zahlen, um ein Element im Array eindeutig zu identifizieren, nämlich die Nummer der Spalte und die Nummer der Zeile.
Man sieht hier auch schon den Unterschied zwischen Größe und Dimension eines Arrays. Die Größe obiger Tabelle ist beliebig, man kann noch mehr Spalten und Zeilen einfügen. Die Dimension bleibt aber gleich, weil es weiterhin nur Spalten und Zeilen gibt, man also weiterhin ein Tupel aus zwei Zahlen braucht, um ein Element zu identifizieren.
Man kann natürlich auch noch Arrays mit mehr Dimensionen als zweien anlegen.
Die Elemente eines Arrays spricht man in Delphi übrigens fast so an, wie in den Beispieltabellen weiter oben. Erst der Name des Arrays und dann in eckigen Klammern dahinter die „Adresse“. Lediglich das Leerzeichen zwischen Name und Klammern lässt man in Delphi weg.
Bei einem eindimensionalen Array nennt man den Wert in den Klammern auch „Index“.
Es gibt drei Eigenschaften, die ein Array auszeichnen: seine Dimension, seine Größe und der Typ seiner Elemente (also ob in der „Tabelle“ Daten vom Typ Integer, String, Boolean, etc. stehen sollen). Während man das Programm schreibt muss man zwingend die Dimension und den Typ der Elemente festlegen, beides kann im Programmverlauf nicht mehr geändert werden. Bei der Größe sieht die Sache anders aus:
Bei einem statischen Array wird auch die Größe von Anfang an festgelegt und kann nicht mehr geändert werden. Es wird also von vorneherein festgelegt: das Array hat eine Zeile und vier Spalten. Bei einem dynamischen Array dagegen legt man nur fest, dass es Zeilen und Spalten hat (also die Anzahl der Dimensionen zwei ist), aber nicht wieviele Zeilen oder Spalten es hat. Das kann man während des Programmverlaufs ändern.
An einer neuen Erweiterung des Mini-Taschenrechners sollen zuerst einmal dynamische Arrays demonstriert werden. Statische Arrays erklären sich im Anschluss daran ganz schnell.
Bei der Erweiterung des Mini-Taschenrechners handelt es sich um die Berechnung von Fibonacci-Zahlen. Eigentlich bräuchte man dafür gar kein Array, aber wenn es mit einem Array macht ist es sehr anschaulich und außerdem ein gutes Beispiel zur Erklärung dynamischer Arrays.
Fibonacci-Zahlen sind eine Folge von Zahlen, welche sich wie folgt bildet: die erste Zahl ist Null, die zweite Zahl ist 1. Alle weiteren Zahlen werden errechnet, indem man die vorhergehende Zahl und die Zahl davor addiert. Die dritte Zahl wäre also wieder 1. Die vierte Zahl wäre 1+1=2. Die fünfte Zahl 1+2 =3. Die Folge sieht dann so aus: 0,1,1,2,3,5,8,…
Legen Sie nun bitte im Taschenrechner-Projekt einen neuen TButton mit dem Namen btFibonacci und einer passenden Beschriftung an. Lassen Sie außerdem wie üblich das Klick-Ereignis anlegen. Lesen Sie außerdem wie bisher auch die Zahl aus dem ersten TEdit in eine Integer-Variable mit Namen numberCount ein.
Was nun als nächstes deklariert werden muss, ist das Array, welches die Zahlen enthalten soll. Die Elemente des Arrays sollen vom Typ Integer sein. Es ist eine einfache Liste von Zahlen, hat also nur eine Dimension. Die Länge wird vom Benutzer in das erste TEdit eingegeben (landet also in numberCount).
Deklariert wird also ein eindimensionales Integer-Array. Und das sieht als Deklaration dann so aus:
var
numbers : Array of Integer;
Da es sich um ein dynamisches Array handelt, wird in der Deklaration die Größe noch nicht festgelegt. Das wird erst erledigt, wenn die Größe (bei einem eindimensionalen Array auch Länge genannt) überhaupt bekannt ist. In diesem Fall also, nachdem der Wert von numberCount ermittelt wurde, denn das ist ja die Länge unseres Arrays.
Fügen Sie also nach dem Einlesen der Zahl in numberCount folgende Quelltextzeile ein:
SetLength(numbers, numberCount);
Die Prozedur SetLength ist in Delphi eingebaut und dient dazu, die Länge von Arrays festzulegen. Als ersten Parameter erhält sie das dynamische Array, dessen Länge festgelegt werden soll. Als zweiten Parameter die neue Länge des Arrays. Die Prozedur reserviert dann den nötigen Speicher und macht das Array somit nutzbar.
Ihr Quelltext sollte nun so aussehen:
procedure TForm1.btFibonacciClick(Sender: TObject);
var
numberCount : Integer;
numbers : Array of Integer;
begin
numberCount := StrToInt(edFirstNumber.Text);
SetLength(numbers, numberCount);
end;
Der nächste Schritt ist es, den ersten beiden Elementen des Arrays den Wert 0 bzw. 1 zuzuweisen. Das erste Element eines dynamischen Arrays hat immer den Index 0. Die beiden nötigen Zuweisungen sehen also so aus:
numbers[0] := 0;
numbers[1] := 1;
Mittels einer for-Schleife (einfach von Delphi durch eingeben des Wortes „for“ anlegen lassen) werden nun alle anderen Zahlen berechnet:
for i := 2 to numberCount - 1 do
numbers[i] := numbers[i-1] + numbers[i-2];
Die Zählvariable i sollte von Delphi automatisch angelegt worden sein, ansonsten muss sie noch als Integer deklariert werden. Die Schleife beginnt mit 2, da die Elemente 0 und 1 ja bereits belegt worden sind. Sie endet mit numberCount-1, weil das der höchste Index im Array ist (also eins weniger als die Länge, weil ja ab 0 gezählt wird).
Innerhalb der Schleife wird das Element (also ein normaler Integer) an der Position i-1 genommen und zu dem Element an der Position i-2 addiert. Mit anderen Worten: die vorhergehende Zahl und die Zahl davor werden addiert. Das Ergebnis wird in das aktuelle Element geschrieben.
Sie sehen: Man kann mit den Elementen eines Arrays wie mit normalen Variablen arbeiten.
Nach Beendigung der Schleife stehen in dem Array die Fibonacci-Zahlen. Diese müssen nun noch ausgegeben werden. Damit die Verwendung eines Arrays an dieser Stelle wenigstens etwas Sinn hat, sollen sie aber in umgekehrter Reihenfolge ausgegeben werden, jeweils durch ein Komma getrennt.
edResult.Text := '';
for i := High(numbers) downto 0 do
begin
edResult.Text := edResult.Text + IntToStr(numbers[i]);
if i > 0 then
edResult.Text := edResult.Text + ', ';
end;
Neu an diesem Quelltext ist der Aufruf der Funktion High. Übergibt man dieser Funktion ein Array, so gibt sie den höchsten, zulässigen Index zurück. In unserem Fall ist das also identisch mit numberCount-1. Man kann auch die Funktion Length verwenden, welche die Länge eines Arrays zurückgibt, was in diesem Fall also numberCount wäre.
Der Taschenrechner sollte nun eine beliebige Menge an Fibonacci-Zahlen erzeugen können.
Ein mehrdimensionales Array kann man sich am besten als ein eindimensionales Array vorstellen, welches als Elemente wieder Arrays hat. Also so:
Array [1] | Array [1] = Wert [1] Wert [2] Wert [3]
Array [2] | Array [2] = Wert [1] Wert [2] Wert [3]
Array [3] | Array [3] = Wert [1] Wert [2] Wert [3]
Links ist ein eindimensionales Array, welches in jedem Element wieder ein Array enthält. Die Elemente sind rechts aufgeführt, wieder eindimensionale Arrays, mit „normalen“ Elementen.
Die Deklaration eines solchen Arrays sieht dann z.B. so aus:
my2dArray : Array of Array of Integer;
Auch hier zeigt sich wieder oben beschriebene Vorstellung, wie ein mehrdimensionales Array aufgebaut ist. Wenn man daran denkt, so erscheint auch das Festlegen der Größe eines solchen Arrays ganz logisch.
Zuerst muss man die Größe des äußeren Arrays, welches die anderen Arrays enthält, festlegen:
SetLength(my2dArray, 3);
Und nun setzt man die Größe der inneren Arrays, also der Elemente des äußeren Arrays:
SetLength(my2dArray [0], 1);
SetLength(my2dArray [1], 5);
SetLength(my2dArray [2], 7);
Hier zeigt sich, dass ein solches zweidimensionales Array nicht „rechteckig“ wie eine Tabelle sein muss. Oben erzeugtes Array hat z.B. folgende Form:
[0,0]
[1,0] [1,1] [1,2] [1,3] [1,4]
[2,0] [2,1] [2,2] [2,3] [2,4] [2,5] [2,6]
Um die Länge einer Zeile herauszufinden, benutzt man auch wieder High oder Length. Man muss nur dran denken, dass die Elemente des äußeren Arrays auch Arrays sind:
High(my2dArray); //ergibt 2
High(my2dArray[0]); //ergibt 0
High(my2dArray[1]); //ergibt 4
High(my2dArray[2]); //ergibt 6
Möchte man ein rechteckiges Array erzeugen, so kann man auch eine erweiterte Version von SetLength verwenden:
SetLength(another2dArray, 3, 3);
Das würde ein Array mit drei Spalten und drei Zeilen erzeugen.
Oft sieht man Konstrukte dieser Art:
SetLength(myArray, 2);
for i := 0 to veryBigNumber do
begin
SetLength(myArray, Length(myArray)+1);
doSomething;
end;
Ein solches Vorgehen ist nicht ratsam, weil der belegte Speicher dadurch extrem anwachsen wird. Grund dafür ist eine Besonderheit, wie Delphi mit dem Speicher umgeht: Zuerst ist Speicher der Größe 2 (fiktiven Speichereinheiten) belegt.
Nun wird das Array um eins vergrößert. Da man nicht einfach Speicher anhängen kann (der kann ja bereits belegt sein), muss ein neuer Speicher der Größe 3 reserviert und das Array dort hinein kopiert werden.
Delphi gibt den alten Speicher aber nicht wieder frei, sondern hebt ihn für den Fall auf, dass nochmal ein Speicher dieser oder einer kleineren Größe benötigt wird. Dann müsste Delphi nämlich keinen neuen Speicher anfordern, sondern könnte den alten Speicher verwenden. Das gibt einen großen Geschwindigkeitsvorteil.
Es hat aber auch den Nachteil, dass der Speicherverbrauch enorm ansteigt, wenn man ein Array in einer Schleife vergrößert. Es wird immer neuer Speicher angefordert, der alte Speicher aber nicht freigegeben.
Das, und die Tatsache, dass das Array jedes Mal komplett kopiert wird, ist der Grund, weshalb man die Größe eines Arrays nur einmal setzen sollte. Das ist einfach, wenn man von vorne herein weiß, wie groß es sein soll. In obigem Fall sollte man also direkt vor der Schleife ein SetLength(myArray, veryBigNumber+2) ausführen.
Doch was ist, wenn man die neue Größe nicht direkt kennt? Dann sollte man abschätzen, wie groß das Array im schlimmsten Fall sein muss, und das Array auf diese Größe setzen. Ist man fertig und weiß, wieviel Platz man wirklich verbraucht hat, verkleinert man das Array einmalig auf die passende Größe.
Auch möglich ist es, dass Array in größeren Schritten zu erweitern. Wenn die Größe nicht mehr ausreicht, erweitert man es nicht um ein Element, sondern z.B. direkt um 20. Damit kann man die Anzahl der SetLength-Aufrufe reduzieren. Auch hier kann man hinterher noch eventuell zu viel reservierten Speicher „abschneiden“, indem man die korrekte Länge setzt.
Ein Array zu kopieren ist leider nicht so einfach, wie bei den bisher vorgestellten anderen Variablen. Bei einer normalen Variable würde man einfach eine Zuweisung machen und schon hätte man den Wert der Variable kopiert.
Bei Arrays würde eine Zuweisung wie array1 := array2 aber leider nur dazu führen, dass array1 nun ein Synonym für array2 ist. Jeder Zugriff auf array1 würde auch array2 ändern und umgekehrt. array1 ist dann eine Referenz auf array2. Erinnern Sie sich an Call by Reference!
Delphi bietet jedoch die Möglichkeit, ein Array zu kopieren. Das macht man mit der Funktion Copy. Diese nimmt als ersten Parameter das zu kopierende Array. Als zweiten, optionalen Parameter erwartet Copy den Index, ab dem das Array kopiert werden soll. Als dritten, ebenfalls optionalen Paramter erhält Copy die Anzahl der Elemente, die kopiert werden sollen.
Um ein Array komplett zu kopieren, würde man also array1 := Copy(array2) aufrufen. Wenn Sie ein Array erst ab dem dritten Element kopieren wollen, dann rufen Sie array1 := Copy(array2, 2) auf. Um aus einem Array vier Elemente ab dem dritten Element kopieren wollen, geht das mit array1 := Copy(array2, 2, 4).
Aber Vorsicht: Enthält das an Copy übergebene Array einen Referenzdatentyp (also ein Datentyp, der sich bei Zuweisung wie ein Array verhält), so wird zwar das Array kopiert, aber nicht jedes einzelne Element kopiert. Das neue Array enthält dann Referenzen auf dieselben Elemente wie das alte Array. Man nennt das auch eine „flache Kopie“ des Arrays.
Die Deklaration eines statischen Arrays unterscheidet sich nicht sehr von der Deklaration eines Dynamischen. Sie wird lediglich um die Angabe der Größe erweitert. Eine Spezialität von Delphi ist dabei, dass man bei einem statischen Array sowohl Unter- als auch Obergrenze angibt. Man definiert die Größe eines statischen Arrays also darüber, dass man den kleinst- und den größtmöglichen Index festlegt.
Eine Deklaration sieht dann z.B. so aus:
myArray : Array[2..9] of Integer;
Dieses Array besitzt 8 Elemente. Das erste Elemente wird mit dem Index 2 angesprochen, das letzte Element mit dem Index 9.
Ein zweidimensionales Array wird wie folgt deklariert:
my2dArray : Array[2..9, 1..3] of Integer;
Um bequem mit einem Array arbeiten zu können, dessen kleinster Index nicht automatisch 0 ist, gibt es die Funktion Low. Übergibt man dieser Funktion ein Array, gibt die Funktion den kleinstmöglichen Index zurück. Die Funktion ist also das genaue Gegenstück von High.
Es gibt eine gute und eine schlechte Nachricht. Die schlechte Nachricht ist, dass die Copy-Funktion bei statischen Arrays nicht funktioniert. Man müsste also jedes Element von Hand in einer Schleife kopieren.
Die gute Nachricht ist, dass man Copy in vielen Fällen bei statischen Arrays gar nicht braucht. Besteht das Array aus primitiven Datentypen (also Integer, Boolean, etc.), so kann man ein statisches Array durch einfache Zuweisung kopieren!
Zum Einstieg in diesen Abschnitt möchte ich, dass Sie sich noch einmal dem Quelltext zuwenden, welcher für die Berechnung der Fakultät einer Zahl zuständig ist. Diese wird ja durch die Funktion faculty berechnet, welche im Moment genau über dem entsprechenden Klick-Ereignis steht.
Bitte vertauschen Sie einmal die Plätze von faculty und dem Klick-Ereignis, sodass die Funktion unter dem Ereignis steht. Wenn Sie nun versuchen, das Programm zu starten, wird Delphi Ihnen mitteilen, dass die Funktion faculty nicht bekannt ist.
Dies liegt daran, dass der Delphi-Compiler (also der Teil von Delphi, der für die Übersetzung des Quelltextes in ein richtiges Programm zuständig ist) den Quelltext von oben nach unten „liest“. Er kennt also immer nur das, was er schon gelesen hat. Und durch das Vertauschen der Positionen von faculty und dem Klick-Ereignis kennt der Compiler die Funktion noch nicht.
In diesem Fall könnte man natürlich einfach die Posititonen wieder in die alte Reihenfolge bringen. Oft kommt es aber vor, dass das nicht geht. An diesem Punkt kommt der interface-Bereich zum Tragen:
Delphi-Dateien sind zwei geteilt: Es gibt den interface-Bereich und den implementation-Bereich. Den interface-Bereich können Sie sich am Besten als Inhaltsverzeichnis vorstellen, der aufzählt, was alles in der Unit (so werden Delphi-Dateien auch genannt) enthalten ist. Er steht am Anfang jeder Unit und wird mit dem Schlüsselwort interface eingeleitet.
Im Falle der Taschenrechner-Unit enthält der interface-Bereich einige Dinge, die im Rahmen dieses Crashkurses noch nicht erklärt wurden. So finden Sie als Erstes die uses-Liste:
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;
Diese Liste enthält alle Units, welche im vorliegenden Quelltext benutzt werden sollen. Es sind also andere Delphi-Dateien, welche Funktionen enthalten, die Sie benutzen wollen. So finden Sie z.B. die Funktion IntToStr in der Unit „SysUtils“. Den TButton finden Siewiederrum in der Unit „StdCtrls“.
Sie werden schnell (noch in diesem Crashkurs) an den Punkt kommen, wo Ihr eigenenen Programme aus mehr als einer Unit bestehen, sie in einer Unit aber Dinge aus der anderen Unit benutzen wollen. Dann werden Sie auch den Namen Ihrer selbst geschriebenen Unit in die uses-Liste schreiben.
Sie können nur Dinge benutzen, die entweder in derselben Unit stehen oder in einer Unit, welche in der uses-Liste steht.
Unterhalb der uses-Liste finden Sie eine Typdeklaration für den Typen TForm1.
type
TForm1 = class(TForm)
{...}
end;
Das ist eine Art Inhaltsverzeichnis des Formulars, mit dem wir momentan arbeiten. Ich möchte an dieser Stelle nicht zu sehr darauf eingehen, da im Kapitel zu objektorientierter Programmierer noch ausführlich darauf eingegangen wird.
Daher nur soviel: TForm1 ist ein Variablentyp, genauer gesagt eine Klasse. Auch von diesem Variablentyp kann man Variablen anlegen. Im Interface-Abschnitt im Moment, welche Komponenten sich auf dieser Form befinden und welche Prozeduren zu dieser Form gehören. Die Komponenten sind gerade die, die wir im Designer auf der Form platziert haben, die Prozeduren sind für unsere Ereignisse zuständig.
Direkt unter der Typdeklaration wird auch schon eine Variable dieses Typs deklariert, das ist dann die Form die tatsächlich angezeigt wird.
var
Form1: TForm1;
Eine Deklaration außerhalb einer Funktion oder Prozedur ist uns bisher nicht unter gekommen. Eine solche Deklaration nennt man global. Eine auf diese Weise deklarierte Variable ist in allen Prozeduren und Funktionen derselben Unit sichtbar und nutzbar, ohne dort erneut deklariert werden zu müssen. Man kann ihr also in Prozedur A einen Wert zuweisen und dann in Prozedur B damit weiter rechnen.
Globale Variablen sind ziemlich aus der Mode gekommen und das zu Recht. Globale Variablen sind für den Programmierer sehr bequem, haben aber das Zeug dazu, ein Programm hoffnungslos unübersichtlich und fehleranfällig zu machen.
Es gibt oft hunderte Stellen, an denen eine solche Variable genutzt und geändert werden könnte. Es gibt keinerlei Zugriffsbeschränkungen. Jede Mini-Unter-Prozedur kann eine globale Variable ändern. Auch bekommt man Probleme mit den Namen, denn ein Variablenname ist dann schonmal für alle Prozeduren aus dem Rennen.
Der momentan favorisierte Weg Programme ohne diese Probleme zu schreiben, ist die objektorientierte Programmierung, die später in diesem Crashkurs ausführlich behandelt werden wird. Sicherlich wäre es zu viel gesagt, dass man globale Variablen überhaupt nicht mehr benutzen soll, es gibt noch Einsatzgebiete (Delphi ist teilweise drauf angewiesen, wie bei der Form weiter oben), aber man sollte sich dreimal überlegen, ob man eine globale Variable einsetzen will.
Was im interface-Abschnitt ganz offensichtlich nirgendwo vorkommt, ist eine Deklaration der Funktion faculty. Und genau das ist der Grund, weshalb man diese Funktion nicht an einer beliebigen Stelle im implementation-Teil unterbringen kann. Da die Funktion nicht im „Inhaltsverzeichnis“ steht, kennt der Compiler die Funktion erst, wenn er sie „gelesen“ hat.
Fügen Sie nun im interface-Teil nach der Typdeklaration (nach dem end;) der Form folgende Zeile ein:
function faculty(n : Integer) : Integer;
Damit haben Sie die Funktion ins Inhaltsverzeichnis eingetragen. Nun wird auch die Quelltext-Version mit den getauschten Versionen problemlos laufen, denn nun muss der Compiler die Funktion nicht gelesen haben, um zu wissen, dass sie da ist.
Nachdem Form1 deklariert wurde, folgt der implementation-Teil des Programmes. Wenn man den interface-Teil mit einem Inhaltsverzeichnis vergleicht, so ist der implementation-Teil der wirkliche Text. Er enthält die Kapitel, welche im Inhaltsverzeichnis aufgelistet wurden.
So finden sich beim Taschenrechner-Programm die Implementationen der Ereignis-Prozeduren im implementation-Teil. Und auch die selbst geschriebenen Prozeduren bzw. Funktionen wie swap und faculty. An diesen beiden sieht man, dass nicht alles, was im implementation-Teil steht , vorher auch im interface-Teil erwähnt worden sein muss. Weder swap noch faculty werden bzw. wurden bis eben dort erwähnt.
Wie im Abschnitt zu uses-Klausel erklärt, können auch von Ihnen geschriebe Units in anderen Units verwendet werden, wenn sie in deren uses-Klausel aufgenommen werden. Es gibt jedoch eine Einschränkung, welche Ihrer Funktionen, Prozeduren, etc. in anderen Units verwendet werden dürfen:
Nur Dinge, die im interface-Teil deklariert sind, sind in anderen Units sichtbar!
Übrigens werden auch globale Variablen, die im interface-Abschnitt deklariert wurden, in anderen Units sichtbar und nutzbar! Ein weiterer Grund, sie entweder gar nicht zu nutzen (das wird dann wirklich unübersichtlich) oder global am Anfang des implementation-Teil zu deklarieren, was genauso geht wie im interface-Teil. Nur sind die dort deklarierten Variablen nur noch in der Unit selber zu sehen.
In diesem Kapitel werden wir den inzwischen arg strapazierten Taschenrechner als Programm beenden und ein neues Beispielprogramm erstellen. Ein ordentlicher Taschenrechner sollte jedoch nicht nur mit ganzen Zahlen rechnen können, sondern auch mit rationalen Zahlen („Kommazahlen“).
Bei der Arbeit mit diesen Zahlen gibt es einige Tücken zu beachten. Diese hängen damit zusammen, wie Zahlen im Rechner gespeichert werden. Und obwohl ich diesem Crashkurs eigentlich auf Grundlagen dieser Tiefe verzichten möchte, ist dies für den praktischen Einsatz so wichtig, dass ich an dieser Stelle eine Ausnahme mache.
Um einen leichten Einstieg in die Darstellung von Zahlen im Rechner zu bekommen, sollte man sich erst noch einmal klar machen, wie Zahlen im Alltag dargestellt werden. Wir benutzen es zwar täglich, aber das System dahinter brauchen wir kaum noch. Wir machen das „einfach so“.
Nehmen wir als Beispiel die Zahl 1234. Diese Folge von Ziffern steht eigentlich für folgende Rechnung:
4 mal 1 = 4*100
plus 3 mal 10 = 3*101
plus 2 mal 100 = 2*102
plus 1 mal 1000 = 1*103
Die Ziffern stehen also für die Vielfachen von 10er-Potenzen, welche addiert werden, um die Zahl zu erhalten. Die Zahl, deren Potenzen verwendet werden (also hier die 10) nennt man auch Basis einer Zahlendarstellung bzw. eines Zahlensystems. Da bei uns die Basis 10 ist, nennt man unser System auch Dezimalsystem.
Die Darstellung von Zahlen zur Basis 10 erweist sich für die Darstellung im Rechner als unpraktisch. Daten im Rechner werden im wesentlich durch die beiden Zustände „an“ und „aus“ bzw. „Strom“ und „kein Strom“ gespeichert. In der Informatik macht man daraus dann 0 und 1.
Man kann für die Darstellung im Rechner also die Ziffern 0 und 1 verwenden, die Basis ist dann 2. Daher spricht man auch von der Binärdarstellung. Es ist ein wenig ungewohnt, eine Zahl in Binärdarstellung zu schreiben, daher sei das Beispiel an dieser Stelle auch etwas kürzer ;-)
12 =
0 mal 1 = 0*20
plus 0 mal 2 = 0*21
plus 1 mal 4 = 1*22
plus 1 mal 8 = 1*23
= (1100)2
Um deutlich zu machen, zu welcher Basis eine Zahl dargestellt wird, setzt man die Zahl in Klammern und schreibt als Index die Basis dazu: (…)b . Wird die Basis nicht angegeben, heißt das, dass Dezimaldarstellung verwendet wird. Die Basis wird immer in Dezimaldarstellung geschrieben.
Neben der Binärdarstellung hat auch die Hexadezimaldarstellung, also die Darstellung zur Basis 16, einen hohen Stellenwert in der Informatik erlangt. Wer schonmal mit Grafikprogrammen gearbeitet hat, dem werden sicherlich Farbwerte in dieser Form aufgefallen sein: FF33AA.
Hierbei handelt es sich um eine Zahl in hexadezimaler Darstellung. Da die Ziffer 0 bis 9 für diese Darstellung nicht ausreichen, hat man sie um die Buchstaben A bis F ergänzt, welche für die (dezimalen) Zahlen 10 bis 15 stehen.
Jeweils zwei Stellen des obigen Farbwertes machen dann einen Farbanteil für Rot, Grün und Blau aus. Die Darstellung in hexadezimaler Schreibweise bietet sich an, weil normalerweise für jeden Farbanteil 255 Abstufungen reserviert sind, also exakt zwei Stellen in hexadezimaler Schreibweise. Perfekte Platzausnutzung also.
Die Darstellung rationaler Zahlen im Rechner funktioniert in den meisten Fällen nach einem anderen System, als wir es im Alltag tun. Im Alltag setzen wir das System der Darstellung der ganzen Zahlen einfach „hinter dem Komma“ fort.
42,13 =
4 mal 101
+ 2 mal 100
+ 1 mal 10-1
+ 3 mal 10-2
Die Exponenten an der Basis werden einfach ins negative weitergeführt, sodass man mit Zehnteln, Hundertsteln, etc. rechnet.
Eine solche Darstellung findet man auch in der Informatik, sie wird aber selten genutzt. Nimmt man an, dass man zum Speichern einer Zahl eine feste Anzahl Stellen (=Speicher) zur Verfügung hat, so befindet sich das Komma in dieser Darstellung an einer festen Stelle. Man legt also vorher fest, wieviel Stellen vor dem Komma stehen können (wie groß die Zahlen werden dürfen) und wieviele hinter dem Komma (wie genau die Zahlen gespeichert werden). Daher nennt man diese Darstellung Festkommadarstellung.
Für die meisten Anwendungen ist diese Art der Darstellung aber zu unflexibel. Man greift daher in den meisten Fällen zu einer so genannten Fließ- oder Gleitkommadarstellung, also zu einer Darstellung, bei der das Komma nicht an einer festen Stelle steht. Die Darstellung erfolgt auf diese Weise:
z = m* be
z.B. 300 = 3*102
Dabei wird m die Mantisse genannt, b ist die Basis und e der Exponent. Die Basis muss dabei nicht mit der Basis der Zahlendarstellung übereinstimmen!
1228,8 = 2,4*83 b=8 e=3 m=2,4
Man sieht sofort, dass die Fließkommadarstellung große Freiheiten beinhaltet, wie man eine Zahl darstellen will. Damit dies aber nicht in einem Chaos endet, hat man sich auf einen Standard geeinigt und den in der Norm IEEE 754 festgeschrieben, an welche sich auch Delphi bei seiner Zahlendarstellung für Fließkommazahlen hält.
Zuerst wird festgelegt, dass die Basis immer 2 ist. Die Gründe dafür sind dieselben, wie sie bereits bei der Darstellung von Ganzzahlen erläutert wurden. In einem zweiten Schritt wird die Aufteilung des Speichers für eine 32 Bit (64 Bit) große Darstellung festgeschrieben:
1. 1 Bit wird auf das Vorzeichen der Zahl verwendet.
2. 8 Bit (11 Bit) werden auf die Speicherung des Exponenten verwendet.Dabei wird der Exponent als d – 127 dargestellt und nur das (immer positive) d gespeichert. Damit spart man sich die Speicherung eines weiteren Vorzeichens.
3. 23 Bit (52 Bits) werden auf die Darstellung der Mantisse verwendet. Dabei hat die Mantisse in der normalen Darstellung die Form 1,f und es wird nur das f gespeichert.
4. Für die subnormale Darstellung, die zur Darstellung sehr kleiner Zahlen mit hoher Genauigkeit verwendet wird, gelten andere Regeln:
a. Der Exponent wird als -126 angenommen, somit braucht man für ihn keinen Speicher.
b. 1 Bit wird auf das Vorzeichen verwendet.
c. Die restlichen Bits werden auf die Mantisse verwendet. Dabei wird angenommen, dass diese die Form 0,f besitzt und es wird nur das f gespeichert.
5. Um die Zahlen positiv oder negativ Unendlich darzustellen, setzt man das Vorzeichen entsprechend, den Wert für die Mantisse setzt man auf 0 und den für den Exponenten auf 255.
6. Um den Wert „Not a Number (NaN)“ darzustellen, setzt man das Bit für das Vorzeichen auf einen beliebigen Wert, den Wert für den Exponenten auf 255 und den Wert für die Mantisse auf einen Wert größer als Null.
Damit auch mal wieder ein bisschen Praxis in den Crashkurs kommt, soll nun der Taschenrechner so umgerüstet werden, dass er auch mit rationalen Zahlen arbeiten kann. Den Anfang soll die Addition machen. Also per Doppelklich auf den entsprechenden TButton in das Klick-Ereignis.
Im Moment haben die Variablen dort den Datentypen Integer, also eine ganze Zahl. Das muss sich ändern. Delphi bietet für rationale Zahlen verschiedene Datentypen an. Da wäre der 32 Bit große Single. Er ist auf einem 32bit-System am Schnellsten und bietet für viele Anwendung einen ausreichend großen Darstellungsbereich und ist ausreichend genau.
Will man größere Zahlen und genauere Darstellung haben, greift man zum 64 Bit großen Double. Der reicht meistens sogar für wissenschaftliche Anwendungen. Wem das noch nicht reicht, kann auf Extended zurückgreifen. Der speichert riesige Daten mit enormer Genauigkeit, genehmigt sich aber auch satte 10 Byte.
Im Taschenrechner soll der Double zum Einsatz kommen. Also erst einmal das Integer durch Double ersetzen.
Wie in den meisten anderen Funktionen des Taschenrechners werden nach der Variablendeklaration erst einmal die Texte, welch ein den TEdits stehen, in Zahlen umgewandelt. Dazu wurde bisher die Funktion StrToInt verwendet. Für Fließkommazahlen benötigt man eine andere Funktion, nämlich StrToFloat. Sie wird genauso benutzt wie StrToInt, nur dass sie eine Fließkommazahl zurückliefert.
Also nur das StrToInt durch ein StrToFloat ersetzen. Das kommt in zwei Zeilen untereinander vor, was eine gute Gelegenheit bietet, noch ein nettes Feature des Delphi-Editors zu präsentieren: den Sync-Bearbeitungsmodus.
Markieren Sie die beiden Zeilen, in denen das StrToInt steht und klicken Sie dann auf das Symbol mit den beiden Bleistiften links neben den Zeilennummern.

Es werden in den markierten Zeilen alle Textstücke markiert, die mehr als einmal vorkommen. Hier sind das also „StrToInt“ und „Text“. An letzterem soll nichts geändert werden, wohl aber an dem „StrToInt“. Wenn Sie nun an einer Stelle den Text „StrToInt“ durch „StrToFloat“ ersetzen, ändern sich alle anderen Stellen gleich mit. Eine sehr praktische Funktion!
Wenn Sie mit der Bearbeitung fertig sind, drücken Sie die Esc-Taste oder klicken Sie erneut auf das Stifte-Symbol, um den Sync-Bearbeitungsmodus zu verlassen.
Es bedarf wohl keiner weiteren Erklärung, wenn ich Ihnen nun sage, dass das IntToStr am Ende der Prozedur durch ein FloatToStr ersetzt werden muss. Wenn Sie das erledigt haben, funktioniert die Addition auch schon mit rationalen Zahlen.
Auch die Division muss entsprechend angepasst werden. Neben der Änderung der Umwandlungsfunktionen (genauso wie bei der Addition), muss bei Fließkommazahlen auch ein anderer Befehl zur eigentlich Division verwendet werden.
Aktuell wird der Befehl div verwendet, welcher eine ganze Zahl durch eine andere ganze Zahl teilt und auch eine ganze Zahl als Ergebnis zurückgibt. Das funktioniert bei Fließkommazahlen nicht. Dort wird der (allgemeinere) Divisionsoperator / verwendet. Der gibt immer eine Fließkommazahl zurück und kann sowohl auf ganze Zahlen als auch Fließkommazahlen angewendet werden.
Wenn Sie also alle Umwandlungsfuntionen ersetzt haben und auch div durch / ersetzt haben, dann beherrscht Ihr Taschenrechner auch die Divison mit Fließkommazahlen.
Alle anderen Funktionen des Taschenrechners arbeiten entweder nur mit ganzen Zahlen (Fibonacci, Fakultät) oder arbeiten sowieso nur an den Texten der TEdits (Tauschen). Damit ist der Taschenrechner fertig. Wenn Sie möchten, können Sie ihn ja noch um Subtraktion und Multiplikation erweitern, in diesem Crashkurs hat er als Beispiel ausgedient.
Den vollständigen Quelltext finden Sie übrigens als Download (10 KB) auf meiner Internetseite.
Man kann Variablen von einem Datentyp in den anderen Umwandeln. So kann man beispielsweise einen Single in einen Double umwandeln und (mit Einschränkungen) umgekehrt. Die Umwandlung von einem Typ zum anderen nennt man cast (Nein, Dieter Bohlen spielt hier keine Rolle).
Man unterscheidet zwischen einem impliziten und einem expliziten cast. Ein impliziter cast wird vom Compiler durchgeführt, ohne dass der Programmierer etwas davon bemerkt oder etwas dafür tun muss. Dies funktioniert immer dann, wenn ein kleinerer Datentyp in einen größeren Datentyp umgewandelt werden soll und so keine Informationen verloren gehen können.
Es gibt auch noch den expliziten cast, bei dem der Programmierer angibt, in welchen Datentyp eine Variable umgewandelt werden soll. Er kann dabei die verrücktesten Umwandlungen erzwingen. Was dann dabei herauskommt, ist seine Sache.
Ein expliziter cast kann z.B. so aussehen:
var
aBoolean : Boolean;
aInteger : Integer;
begin
aInteger := 42;
aBoolean := Boolean(aInteger);
if aBoolean then
ShowMessage('Wahr');
end;
Was dabei herauskommt, können Sie einfach mal in einem kleinen Testprojekt herausfinden. Wie man das erstellt, sollten Sie ja inzwischen wissen. Sie werden feststellen, dass jede Zahl außer 0 in true umgewandelt wird, nur wenn aInteger 0 war, wird aBoolean false sein.
Manchmal führt Delphi auch implizite casts klaglos aus. So zum Beispiel bei diesem Quelltext:
var a : Integer;
b : ShortInt;
begin
a := 204;
b := a;
ShowMessage(IntToStr(b));
end;
Ein ShortInt ist ein ganzzahliger Datentyp, der aber nur kleine Zahlen darstellen kann, nämlich von -128 bis +127. Weist man einer Variable diesen Typs nun eine größere Zahl zu, so kann man sich die Umwandlung so vorstellen:
b wird von 0 bis 127 „aufgefüllt“. Von a bleiben dann noch 76 übrig. Mit diesen fängt man bei b am unteren Ende (also -128) an. Damit kommt man bis -52, was dann der endgültige Wert von b ist. Man spricht bei diesem Vorgang von einem Überlauf.
Delphi meldet das nicht, da muss der Programmierer selber drauf achten!
Für dieses Kapitel wird ein neues Projekt gebraucht. Sollten Sie noch den Taschenrechner oder ein anderes Projekt offen haben, dann klicken Sie im Menü „Datei“ auf „Alle schließen“. Es sollte nun nur noch die Willkommens-Seite offen sein.
Klicken Sie dort auf „Neues Projekt“ und wählen Sie wie schon beim Taschenrechner die „VCL-Formularanwendung“. Anstatt nun etwas an der Form zu machen, fügen Sie dem Projekt eine neue Unit hinzu.

Klicken Sie dazu in der Projektverwaltung mit der rechten Maustaste auf „Project1.exe“, dann wählen Sie „Neue hinzufügen“ und anschließend „Unit“. Unterhalt von „Project1.exe“ sollte es nun eine Datei mit dem sprechenden Namen „Unit2.pas“ geben. Klicken Sie mit rechts auf diese Datei und wählen Sie „Umbenennen“. Geben Sie der Datei den Namen „MyClasses.pas“.
Speichern Sie nun das Projekt über „Datei“ und „Alle speichern“ in einen Ordner Ihrer Wahl. Achten Sie darauf, dass alle Dateien im selben Ordner landen.
Die neue MyClasses.pas sollte nun fast leer sein. Das wird sich ändern. Und zwar soll nun eine neue Klasse angelegt werden, welche ein Rechteck beschreibt. Und damit sind wir schon bei einer der grundlegenden Ideen der objektorientierten Programmierung (kurz OOP):
Man versucht im Programm Dinge aus der Wirklichkeit abzubilden. Also z.B. Rechtecke. Ein Rechteck hat Eigenschaften wie Länge und Breite. Daraus resultiert seine Fläche. Man könnte sich auch vorstellen, dass man ein Rechteck zeichnen kann, es also so etwas wie eine „Draw“-Methode hat.
Sieht man mal von der Methode zum zeichnen ab, sieht damit eine Klasse, die ein Rechteck beschreibt, so aus:
type
TMyRectangle = class
public
Width : Integer;
Height : Integer;
function GetArea : Single;
end;
Eine Klasse („class“) kann man sich als „Konstruktionszeichnung“ für ein Objekt vorstellen. Eine Klasse legt fest, was für Eigenschaften und Methoden ein Objekt (z.B. Rechteck) haben soll. Nach dieser Konstruktionszeichnung kann man hinterher auch tatsächliche Objekte „bauen“.
Width und Height nennt man in diesem Fall „Felder“ einer Klasse. Das kann man sich ganz gut merken, wenn man sich vorstellt, dass Width und Height freie Felder sind, wo man einen Wert „ablegen“ kann.
GetArea ist eine Methode, die einen Wert zurück gibt (also wie eine Funktion, nur dass sie zu einer Klasse gehört). Es würde keinen Sinn machen, für die Fläche ein Feld einzuführen, das sie sich ja aus der Länge und der Breite berechnet.
Ich werde im Folgenden übrigens Prozeduren oder Funktionen, welche zu Klassen gehören, als „Methoden“ bezeichnen, da dies auch der übliche Fachbegriff dafür ist.
Tragen Sie die Klassendefinition bitte direkt unterhalb des Wortes „interface“ und oberhalb des Wortes „implementation“ ein, also im Interface-Bereich ihres Programmes. Nun fehlt natürlich noch die Implementation. Die Felder müssen nicht implementiert werden, sehr wohl aber die Methode GetArea.
Hier kommt wieder eine nette Funktion von Delphi zum Tragen. Setzen Sie den Cursor einfach mal irgendwo in die Klassendeklaration und drücken Sie die Tastenkombination Strg+Shift+C. Schon erstellt Delphi ihnen den Rumpf der GetArea-Methode im Implementationsteil Ihres Programmes.
Wie GetArea aussehen muss, sollte klar sein. Im Implementation-Teil unterscheidet sich die Methode überhaupt nicht von irgendeiner „normalen“ Funktion. Also weist man result einfach den Ergebniswert zu, in diesem Falle Höhe mal Breite.
result := Width * Height;
Aber Vorsicht: In dieser Zeile steckt mehr, als man zuerst glauben mag. Denn Width und Height sind ja nicht einfach irgendwelche Variablen oder Parameter, welche der Funktion übergeben worden wären. Es sind Felder der Rechteck-Klasse.
Nun stellen Sie sich vor, Sie haben zehn Rechtecke in Ihrem Programm. Dann muss die GetArea-Methode natürlich immer die richtigen Maße verwende, um die Fläche zu berechnen. Wenn dort also Width und Height steht, dann ist das immer die Höhe und Breite des Objektes, zu dem man GetArea aufgerufen hat.
Wenn man dieses Objekt (also das, zu dem man GetArea aufgerufen hat) einmal direkt ansprechen will, gibt es das Schlüsselwort self. Damit könnte man obige Zeile auch so schreiben:
result := self.Width * self.Height;
In der Tat ist die erste Variante nur eine Kurzschreibweise der Zweiten. Falls Ihnen die Verwendung von self noch nicht ganz klar ist, denken Sie noch einmal daran, wie man auf die Text-Eigenschaft eines TEdit zugreift. Über seinen Namen, gefolgt von einem Punkt und dem Wort „Text“.
Den Namen bekommt das TEdit über seine Deklaration (es ist ja nichts anderes als eine Variable). Dieser Name ist aber nichts, was dem TEdit bekannt wäre, man kann ein TEdit als „Kuno“ oder „Edgar“ deklarieren, den Unterschied würde es nicht kennen. Damit ein TEdit aber auch auf sich selber zugreifen kann, wurde das Schlüsselwort self eingeführt, was innerhalb einer Klasse (nicht nur bei TEdit ;-)) die Funktion eines Namens übernimmt.
Nun aber genug der Theorie und zurück zu unserem Programm. Am Ende soll es verschiedene geometrische Formen zeichnen können. Erst einmal reicht es aber, wenn das Programm die Fläche eines Rechtecks berechnet.
Wechseln Sie nun also bitte von MyClasses.pas zurück zur Form. Klicken Sie dazu bei den oberen Karteikarten auf „Unit1“ und dann unten auf „Design“, um in den Designmodus zurück zu gelangen.

Auf der Form platzieren Sie nun bitte folgende Komponenten:
· 1 TEdit mit Namen „edHeight“, 1TEdit mit Namen „edWidth“
· Passende Labels über den Edits („Höhe“, „Breite“)
· 1 TButton „btArea“ mit der Beschriftung „Fläche“
Wechseln Sie per Doppelklick auf den TButton in das entsprechende Klick-Ereignis.
Wie schon weiter oben erklärt, werden in den uses-Klausel Delphi-Dateien („Units“) angegeben, welche verwendet werden sollen. In diesem Fall muss also noch die Datei „MyClasses.pas“ eingebunden werden. Dazu einfach „MyClasses“ (ohne Endung) in die Liste aufnehmen. Komma nicht vergessen ;-)
Wenn die Klasse TMyRectangle verwendet werden soll, dann muss eine Variable des entsprechenden Typs deklariert werden. Also bitte einmal die Variable myRect : TMyRectangle an der üblichen Stelle deklarieren.
Klassen unterscheiden sich von „primitiven“ Datentypen dahingehend, dass es für ihre Verwendung nicht reicht, eine Variable zu deklarieren. Man muss sie auch noch „erstellen“ oder „instanzieren“, wie es korrekt heisst. Daher nennt man verschiedene Variablen, deren Typ eine Klasse ist, auch „Instanzen“ dieser Klasse. Die Klasse ist die Konstruktionszeichnung, die Instanz das konstruierte Objekt.
Für das instanzieren ist der „Konstruktor“ zuständig. Jede Klasse besitzt einen Standardkonstruktor, auch wenn man ihn nicht deklariert hat. Man kann eigene Konstruktoren schreiben, dazu aber später mehr. Nun soll erst einmal eine Instanz der Klasse TMyRectangle angelegt werden.
Das macht man im Implementations-Bereich einer Methode, also nicht im Deklarationsbereich:
var
myRect : TMyRectangle;
begin
myRect := TMyRectangle.Create;
Man erstellt ein Objekt immer, indem man den Konstruktor über den Klassennamen aufruft, niemals, indem man den Konstruktor über die Variable aufruft. Das hier macht man also nicht:
myRect.Create; //FALSCH!
Ab hier geht eigentlich alles seinen gewohnten Gang: Genauso wie die diversen TButtons und TEdits kann man nun auch dieses TMyRectangle verwenden. Man kann der Höhe und Breite nun Werte zuweisen:
myRect.Width := StrToInt(edWidth.Text);
myRect.Height := StrToInt(edHeight.Text);
Um die Fläche herauszubekommen, ruft man nun einfach die GetArea-Methode von myRect auf:
ShowMessage(FloatToStr(myRect.GetArea));
Damit wäre die Klick-Methode eigentlich fertig. Bis auf eine Kleinigkeit, welche sich in größeren Programmen zu einem Problem auswachsen kann, wenn man sie nicht beachtet:
Wir haben eine Instanz einer Klasse erstellt. Dafür wurde Speicher belegt, in dem sich die Instanz befindet. Brauchen wir eine Instanz nicht mehr, müssen wir den Speicher wieder freigeben. Das erledigt die Methode Free für uns:
myRect.Free;
Diese Methode ruft den Destruktor, also das Gegenstück zum Konstruktor, der Klasse auf, welcher den Speicher der Instanz wieder freigibt.
Vergisst man das Freigeben von Objekten, so kann es zu so genannten „Speicherlecks“ kommen. Das heisst, es wird Speicher zwar reserviert, aber nicht mehr freigegeben. Der Speicher ist nicht mehr nutzbar (da man keine Variablen mehr hat, die auf ihn zugreifen), aber weiterhin durch das Programm belegt. Ihr Programm wird immer mehr Speicher verbrauchen, was kein Nutzer gerne sieht.
An dieser Stelle ist es Zeit, sich vor Augen zu führen, dass alle TEdits, TButtons, etc. die bisher verwendet wurden, einfach nur Instanzen ihrer jeweiligen Klassen waren. So ist zum Beispiel edWidth nur eine Instanz der Klasse TEdit. Es ist nichts besonderes daran.
Und wenn Sie sich einmal den interface-Bereich von Unit1 ansehen, werden Sie noch etwas sehen: Ihre Form ist auch nur die Instanz einer Klasse!
Da wird eine Klasse TForm1 deklariert (das sieht etwas anders aus, dazu später mehr), mit Feldern, die den Komponenten entsprechen. Diese Felder unterscheiden sich eigentlich auch nicht von den Felder in TMyRectangle. Und weiter unten wird eine Variable Form1 vom Typ TForm1 deklariert. Das ist die Form, mit der wir die ganze Zeit arbeiten.
Nur eines fällt auf: Es ist alles genauso wie bei der selbst geschriebenen Klasse und den selbst deklarierten Variablen. Nur dass die „automatisch“ erzeugten Variablen anscheinend nirgendwo instanziert werden. Das scheint tatsächlich nur so zu sein.
Denn in Wirklichkeit werden diese Variablen sehr wohl instanziert, allerdings macht Delphi das im Hintergrund. Dies hat den einfachen Grund, dass ja die Werte wie Höhe, Breite und Beschriftung von TButton & Co. ja noch gesetzt werden und auch irgendwo gespeichert werden müssen. Daher wird die Erstellung dieser Instanzen im Hintergrund vorgenommen.
Fazit: Die „eingebauten“ Klassen funktionieren genauso wie die eigenen Klassen.
Das Programm sollte nun lauffähig sein. Starten Sie es und geben Sie dann als Breite oder Höhe einen negativen Wert ein. Nach Klick auf den „Fläche“-Button wird ein negativer Wert für die Fläche ausgegeben.
Eine negative Breite? Eine negative Fläche? Das macht wenig Sinn. Es ist ein klarer Nachteil von Feldern, dass jeder beliebige Werte zuweisen kann. Daher gibt es für Klassen so genannte Eigenschaften oder Properties, wie sie auf Englisch heissen.
Eine Eigenschaft kann als eine Art „Vermittler“ zwischen dem Feld und dem Nutzer einer Klasse gesehen werden. Die Deklaration einer Property für die Breite sähe dann so aus:
type
TMyRectangle = class
private
FWidth : Integer;
procedure SetWidth(value : Integer);
public
property Width : Integer read FWidth write SetWidth;
end;
Eine Property besitzt im Allgemeinem einen Getter (das, was nach read steht) und einen Setter (das, was nach write steht). Ersterer ist für das Abrufen des Wertes zuständig, letzterer für das Setzen des Wertes. Wenn man also myRect.Width := 5;schreibt wird der Setter aufgerufen, bei FloatToStr(myRect.Width) der Getter.
In diesem Fall ist der Getter sehr einfach: Es ist das Feld FWidth. Dies ist die einfachste Art eines Getters. Man könnte auch eine Methode aufrufen lassen, welche einen Integer zurückliefert. Wie das aussieht wird ein paar Zeilen weiter unten noch gezeigt. Hier wird also beim Abrufen des Wertes von Width einfach der Wert aus FWidth genommen.
Der Setter wiederrum ist eine Methode. Das heisst, die Zuweisung myRect.Width := 5;wird „umgeleitet“ in den Aufruf myRect.SetWidth(5);. Und dort kann man dann z.B. abfragen, ob der Wert, der zugewiesen werden soll, negativ ist:
procedure TMyRectangle.SetWidth(value : Integer);
begin
if value > 0 then
FWidth := value;
end;
Hier würde der zugewiesene Wert also nur übernommen, wenn er größer als Null ist. Im „Einsatz“ würde man wahrscheinlich eher eine Fehlermeldung auslösen (wie das geht wird später noch erklärt), hier soll es reichen, dass ein ungültiger Wert verworfen wird.
Das gleiche macht man dann noch mit der Höhe und schon kann man keine unsinnigen Werte mehr für die Maße eines Rechtecks eingeben.
Bitte ändern Sie die Deklaration Ihrer Klasse, sodass sie so aussieht:
type
TMyRectangle = class
private
FHeight : Integer;
FWidth : Integer;
procedure SetHeight(value : Integer);
procedure SetWidth(value : Integer);
public
property Height : Integer read FHeight write SetHeight;
property Width : Integer read FWidth write SetWidth;
function GetArea : Single;
end;
Wenn Sie nun noch einmal Strg+Shift+C drücken, werden die entsprechenden Methoden angelegt. Ergänzen Sie die Implementation der Methoden wie oben gezeigt, GetArea sollte ja bereits existieren.
Übrigens bieten Properties eine elegante Möglichkeit, die Fläche des Rechtecks zurückzugeben. Anstatt einer Methode GetArea macht man eine Property Area daraus. Das ist viel intuitiver zu benutzen. Und da der Getter einer Property auch eine Methode sein kann, können wir weiterhin den Wert aus Höhe und Breite berechnen:
type
TMyRectangle = class
private
function GetArea : Single;
public
property Area : Single read GetArea;
Dabei muss an der GetArea-Methode kaum etwas geändert werden, sie wird ganz normal aufgerufen, sobald jemand den Wert von Area wissen will. Verwenden Sie dort allerdings anstatt der Properties Width und Height die Felder FWidth und FHeight.
Bitte ändern Sie ihre Deklaration der Klasse entsprechend, die Implementation muss dieses Mal nicht geändert werden.
Eine wichtige Idee bei der OOP ist die Kapselung. Ich werde später noch ausführlich darauf eingehen, aber hier möchte ich kurz darauf eingehen, was das bei Properties bedeutet. Wenn eine Property wie Area im Code verwendet wird, so weiß der Benutzer einer Klasse nicht, wie die Getter und Setter aussehen. Sie sind in der Property Area gekapselt.
Das gibt Ihnen die Möglichkeit, diese im Nachhinein zu ändern, ohne dass der Benutzer Ihrer Klasse seinen Code umschreiben muss. Sie können also anfangs einen Setter direkt auf das Feld „umleiten“ und später durch eine Set-Methode ersetzen. Der Benutzer wird es nicht merken.
An diesen Codestücken sei kurz die Konvention der Benennung von Feldern und Properties erklärt: Felder werden mit einem großen „F“ am Anfang gekennzeichnet, wie in FWidth. Die entsprechende Property ist dann Width, ohne das „F“. Benutzt man als Getter oder Setter Methoden, so heissen diese dann GetWidth oder SetWidth, also der Name der Property mit einem vorangestellten „Get“ bzw. „Set.
Verwenden Sie immer Properties, um Eigenschaften eines Objektes zu definieren. Nutzen Sie dafür keine einfachen Felder, es beraubt ihnen jeglicher Möglichkeiten der Eingabeprüfung. Sollten Sie zum Zeitpunkt der Deklaration noch keine Prüfung benötigen, dann können sie als Getter und Setter jeweils das Feld angeben, und dank der Kapselung später Methoden einführen.
Schauen Sie sich die Klassendeklaration an, wie sie jetzt vollständig aussehen sollte:
type
TMyRectangle = class
private
fHeight : Integer;
fWidth : Integer;
procedure SetHeight(value : Integer);
procedure SetWidth(value : Integer);
function GetArea : Single;
public
property Height : Integer read fHeight write SetHeight;
property Width : Integer read fWidth write SetWidth;
property Area : Single read GetArea;
end;
Es fällt auf, dass es zwei Abschnitte gibt: die Properties stehen alle nach dem Schlüsselwort public, der Rest davor in einem mit private eingeleiteten Abschnitt.
Die OOP bietet das schon erwähnte Prinzip der Kapselung auf Basis verschiedener Sichtbarkeiten. Man teilt die Member einer Klasse in Dinge, die nur intern verwendet werden und in Dinge, die auch Benutzer einer Klasse „kennen“ müssen.
In diesem Fall werden nur die Properties Height, Width und Area dem Benutzer der Klasse bekannt gemacht. Wie diese Properties umgesetzt sind, also was für Getter und Setter sie haben, muss der Benutzer nicht wissen. Im Gegenteil: Er darf es nicht wissen.
Wenn der Benutzer die Interna der Klasse kennen und benutzen könnte, so wäre dem Programmierer der Klasse die Möglichkeit genommen, diese hinterher noch einmal zu ändern. Hätte der Benutzer direkten Zugriff auf FHeight, wäre es uns nicht möglich, dieses hinterher zugunsten einer anderen Implementation zu verwerfen, ohne dass der Benutzer der Klasse Probleme bekommt.
Um dies zu verhindern, wählt man das Vorgehen wie folgt:
Man definiert einen öffentlichen Bereich der Klasse (public). Die Member in diesem Bereich sind für die Benutzung von „Fremden“ bestimmt. Der Benutzer kann sicher sein, dass der für ihn sichtbare Teil nicht mehr geändert wird, höchstens erweitert. Er kann also sicher sein, dass es die Properties Height, Width und Area geben wird und dass die Typen so bleiben, wie sie sind. Die Getter und Setter kennt er nicht, die können sich also ändern.
Für alles andere definiert man einen nicht-öffentlichen Bereich (private). Dort bringt man alles unter, was die eigentliche Arbeit erledigt, aber für den Benutzer der Klasse nicht von Interesse ist. Auf die Member in diesem Bereich hat der Benutzer keinen Zugriff. Wohl aber kann man natürlich aus der Klasse selber auf diese Member zugreifen, so kann die GetArea-Methode die Felder FWidth und FHeight benutzen.
Es gibt noch weitere Sichtbarkeiten, die werde ich jedoch zu gegebenener Zeit einführen.
Ihre Klasse sollte nun so aussehen:
type
TMyRectangle = class
private
FHeight : Integer;
FWidth : Integer;
procedure SetHeight(value : Integer);
procedure SetWidth(value : Integer);
function GetArea : Single;
public
property Height : Integer read FHeight write SetHeight;
property Width : Integer read FWidth write SetWidth;
property Area : Single read GetArea;
end;
…
function TMyRectangle.GetArea: Single;
begin
result := FWidth*FHeight;
end;
procedure TMyRectangle.SetHeight(value: Integer);
begin
if value > 0 then
FHeight := value;
end;
procedure TMyRectangle.SetWidth(value: Integer);
begin
if value > 0 then
FWidth := value;
end;
Sie haben nun also eine Klasse, welche drei Eigenschaften hat. Nun muss auch das Programm angepasst werden, welches diese Klasse benutzt.
Wechseln Sie dazu bitte wieder in die Unit1 und zwar ins Klick-Ereignis des TButton. An der Zuweisung der Werte zu Width und Height muss sich nichts ändern. Jedoch schlägt bei der Ausgabe der Fläche die Kapselung zu:
Die GetArea-Methode ist im private-Bereich der Klasse deklariert und damit uns als Nutzern der Klasse nicht mehr zugänglich. Stattdessen muss nun die Property Area verwendet werden:
ShowMessage(FloatToStr(myRect.Area));
Starten Sie nun das Programm noch einmal und geben Sie noch einmal negative Werte ein. Als Fläche wird Ihnen dann Null ausgegeben werden. Das hat den Grund, dass FWidth und FHeight noch kein Wert zugewiesen wurde. Die Zuweisung der negativen Werte wurde ja von den Settern abgefangen. Damit haben beide immer noch den Wert Null.
Man möchte natürlich noch weitere geometrische Formen darstellen können. Eine Klasse TMyCircle könnte z.B. so aussehen:
TMyCircle = class
private
FRadius : Integer;
procedure SetRadius(value : Integer);
function GetArea : Single;
public
property Radius : Integer read FRadius write SetRadius;
property Area : Single read GetArea;
end;
Wobei die Fläche des Kreises natürlich so berechnet wird:
result := 3.1415926 * FRadius * FRadius;
Eigentlich ist es schlechter Stil, die Zahl Pi direkt in den Code zu schreiben, aber es soll uns an dieser Stelle genügen.
Fügen Sie eine entsprechende Klasse bitte in die Datei „MyClasses.pas“ ein. Dazu einfach die Deklaration von TMyCircle unter die Deklaration von TMyRectangle (aber über dem Wort implementation) einfügen. Mit Strg+Shift+C legen Sie dann die GetArea- und SetRadius-Methoden an und komplettieren Sie.
Die GetArea-Methode gibt es nun zweimal: Einmal bei TMyRectangle und einmal bei TMyCircle. Um sie zu unterscheiden, wird den Methodennamen im implementation-Teil immer der Klassenname voran gestellt, sodass sie TMyRectangle.GetArea und TMyCircle.GetArea heissen.
Sofort fällt auf, dass beide Klassen die Eigenschaft Area besitzen. Und demnächst werden beide Klassen auch eine Methode zum Zeichnen besitzen. Beide Klassen stellen eine geometrische Form dar, nur unterschiedliche Arten einer solchen Form.
Man könnte also eine Klasse TMyShape einführen, welche allgemein geometrische Formen beschreibt. Was weiß man von einer allgemeinen geometrischen Form? Sie hat z.B. eine Fläche, auch wenn man nicht sagen kann, wie diese berechnet wird. Aber sie hat eine.
Rechteck und Kreis sind beides geometrische Formen, aber verschiedene konkrete Umsetzungen der abstrakten Vorstellung einer geometrischen Form. Eine solche Beziehung kennt man auch in der OOP. TMyShape ist eine so genannte Basisklasse. TMyRectangle und TMyCircle Nachkommen der Basisklasse (auch Mutterklasse genannten) TMyShape. Sie erben die Eigenschaften und Methoden der Basisklasse.
„Erben“ bedeutet, dass sie alle Eigenschaften und Methoden haben, die in der Basisklasse definiert sind. Hat die Basisklasse eine Eigenschaft Area, so haben auch die abgeleiteten Klassen diese Eigenschaft. Hat die Basisklasse eine Methode „Draw“, so haben auch die abgeleiteten Klassen eine solche Methode.
Wie sieht diese Basisklasse TMyShape denn nun in diesem Fall aus? So:
TMyShape = class
protected
function GetArea : Single; virtual; abstract;
public
property Area : Single read GetArea;
end;
Fangen wir unten an: Die Basisklasse besitzt die Eigenschaft Area, welche wie bisher auch auf eine Method GetArea verweist.
Weiter oben habe ich gesagt, dass man beieiner allgemeinen, abstrakten geometrischen Form sagen kann, dass sie eine Fläche hat, aber nicht, wie man diese berechnet. Was macht also die GetArea-Methode hier? Sie ist eine abstrakte Methode, welche keine Implementation hat. Sie legt nur Namen („GetArea“), Parameter (hier: keine) und den Rückgabewert (Single) fest. Die Implementation erfolgt in der abgeleiteten Klasse.
Damit Delphi weiß, dass es für diese Methode keine Implementation gibt, kennzeichnet man sie mit den Schlüsselworten virtual; abstract;. Nachdem ich die ganze Zeit so auf dem Wort „abstrakt“ rumgeritten bin, dürfte das Schlüsselwort „abstract“ sich selber erklären. Auf das Schlüsselwort „virtual“ möchte ich an dieser Stelle noch nicht eingehen, dauer aber nicht mehr lange ;-)
Zu guter letzt gilt es noch, zu erklären, was die Sichtbarkeit protected ist: Während private Elemente enthält, die ausschließlich in der Klasse selber (noch nicht einmal in Nachfahren) sichtbar sind, sind Elemente der Sichtbarkeit protected für die Klasse selber und ihre Nachfahren sichtbar. Eine abstrakte Methode, die in der abgeleiteten Klasse implementiert werden soll, muss natürlich protected sein, da man nur implementieren kann, was man sieht.
Wie sehen nun die beiden abgeleiteten Klassen aus? Was Width, Height und Radius angeht, ändert sich nichts. Daher lasse ich die im folgenden Quelltext weg, obwohl sie natürlich noch unverändert mit allen Methoden und Feldern vorhanden sind. Die Eigenschaft Area wird in den abgeleiteten Klassen nicht mehr deklariert, da sie von der Klasse TMyShape geerbt wird.
TMyRectangle = class(TMyShape)
private
protected
function GetArea : Single; override;
public
end;
TMyCircle = class(TMyShape)
private
protected
function GetArea : Single; override;
public
end;
An der Implementation der GetArea-Methoden ändert sich hier nichts, daher beschränke ich mich auf den interface-Teil.
Zuerst fällt auf, dass statt der Angabe, dass es sich bei TMyRectangle / TMyCircle um Klassen handelt, auch noch die Angabe hinzu kommt, von welcher Klasse diese Klassen abgeleitet sind (TMyShape). Außerdem wurde die Methode GetArea aus dem private-Bereich in den protected-Bereich verschoben, weil dort auch die Methode in der Basisklasse steht.
Der einzige, verbleibende Unterschied ist das Schlüsselwort override. Dieses ist das Pendant zu virtual; abstract; und bedeutet, dass die Methode der Basisklasse überschrieben wird, was bei einer abstrakten Methode identisch damit ist, dass sie implementiert wird. Mehr zum Überschreiben von Methoden, wenn auf die tiefere Bedeutung von virtual eingegangen wird.
Die abgeleiteten Klassen besitzen nun die Eigenschaften Width und Height bzw. Radius, welche sie selber definieren und die Eigenschaft Area, welche sie von der Basisklasse erben. Die Eigenschaft ruft den Wert dann aus der GetArea-Methode ab, welche durch die abgeleiteten Klassen implementiert wird.
Da die Vererbung keine einfache Sache ist, nochmal kurz eine Zusammenfassung:
Es gibt in unserem Beispiel eine Basisklasse names TMyShape, welche für die abstrakte Vorstellung einer geometrischen Form steht. Daher hat diese Klasse zwar eine Eigenschaft Area (weil man davon ausgeht, dass eine geometrische Form eine Fläche hat), aber die damit verbundene, abstrakte Methode GetArea hat keine Implementation (daher abstrakt). Denn: die Basisklasse TMyShape legt nur fest, dass eine geometrische Form diese Methode haben muss, aber nicht, wie die Fläche berechnet wird.
Die Klassen TMyCircle und TMyRectangle erben nun von der Klasse TMyShape (man sagt auch: werden von ihr abgeleitet). Sie haben daher auch die Eigenschaft Area, welche sie von TMyShape erben. Außerdem überschreiben sie die abstrakte Methode GetArea und implementieren sie somit.
Die Klassen TMyCircle und TMyRectangle sind Spezialisierungen der Klasse TMyShape. Dabei sind die Beziehungen in Delphi wie die Beziehungen in der Wirklichkeit: ein Kreis ist auch eine geometrische Form (eine Variable vom Typ TMyCircle ist automatisch auch vom Typ TMyShape), genauso beim Rechteck. Aber eine geometrische Form ist nicht unbedingt ein Kreis (eine Variable vom Typ TMyShape ist nicht unbedingt auch vom Typ TMyCircle).
… to be continued …