Home » Tutorials » Object Pascal/RTL » Delphi-Crashkurs

Delphi-Crashkurs

Der Anfang allen Übels

Hier möchte ich zum Schluss noch auf ein paar Dinge hinweisen, welche Sie im Laufe dieses Crahskurses sicherlich zumindest teilweise mitbekommen haben, auch wenn sie noch nicht explizit ausgeführt wurden. Es geht um den Kopf einer jeden Delphidatei. Dieser hat verschiedene Teile:

Interface und implementation

Eine Delphi-Unit ist in zwei Teile geteilt: „interface“ und „implementation“. Sie haben sicherlich bereits erraten, welche Bedeutung diese Teile haben: Im interace-Teil wird festgelegt, was alles in der Unit zu finden ist, der implementation-Teil stellt den eigentlich Inhalt der Unit dar.
Allerdings muss obige Aussage ein wenig präzisiert werden: Eine Unit kann mehr implementieren, als im interface-Teil enthalten ist, jedoch sind nur Dinge, die im interface-Teil vorkommen, in anderen Units sichtbar. So ist also eine Prozedur nur dann in anderen Units sichtbar, wenn sie auch im interface-Teil deklariert wurde.
Auch innerhalb einer Unit macht es einen Unterschied, ob eine Prozedur (oder auch Funktion) im interface-Teil deklariert wurde, oder nicht. Ist eine Prozedur nicht im interface-Teil deklariert, so kann man sie nur an einer Stelle aufrufen, welche unterhalb dieser Prozedur liegt.

procedure TForm1.FormCreate(Sender: TObject);
begin
 foo;
end;

procedure foo;
begin
  ShowMessage('foo');
end;

Dies produziert einen Fehler, wenn die Prozedur „foo“ nicht im interface-Abschnitt deklariert wurde. Man kann sich das so vorstellen, dass der Delphi-Compiler beim Aufruf von „foo“ noch gar nicht soweit gelesen hat, dass er diese Prozedur kennen könnte. Schreibt man die Prozedur „foo“ über ihren Aufruf, so hat der Compiler sie bereits „gelesen“ und der Aufruf glückt.
Alternativ deklariert man „foo“ im interface-Abschnitt:

interface

uses
  {...}

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    {...}
  end;

procedure foo;

{...}

implementation

Damit kennt der Compiler bereits den Namen der Prozedur „foo“ und produziert keinen Fehler mehr. Jetzt wäre „foo“ auch aus anderen Units heraus aufrufbar.

Forwarding

Es kann vorkommen, dass man doch einmal auf eine Prozedur oder Funktion zugreifen muss, welche im Quelltext erst später erfolgt und dass man die Reihenfolge nicht so ändern kann, dass dieses einfach möglich ist. Für solche Fälle kann man das so genannte „Fowarding“ verwenden. Dabei gibt man dem Compiler an einer früheren Position den Namen einer Prozedur oder Funktion mit, welche erst später implementiert wird. Also so ähnlich wie ein interface-Teil, nur in der Implementation. 😉

procedure foo; forward;

procedure TForm1.FormCreate(Sender: TObject);
begin
 foo;
end;

procedure foo;
begin
  ShowMessage('foo');
end;

Nun würde – auch ohne Deklaration von „foo“ im interface-Teil auch dieser Quelltext funktionieren, denn über der FormCreate-Methode wurde dem Compiler der Prozedur „foo“ bekannt gegeben. Damit klar ist, dass nur der Name bekannt gegeben werden soll, die Prozedur aber noch nicht implementiert werden soll, schreibt man noch das Schlüsselwort „forward“ dahinter.

Forwarding von Klassen

Auch bei Klassen gibt es ab und an die Notwendigkeit des Forwardings, also das eine Klasse eine andere benötigt, die aber erst später eingeführt wird. Dies würde sich dann im interface-Teil abspielen. Die Lösung ist fast identisch mit der Lösung bei Prozeduren oder Funktionen. Im nachfolgenden Beispiel soll eine Klasse „TFotoalbum“ mehre Fotos („Array of TFoto“) speichern, jedes Foto jedoch auch wissen, zu welchem Fotoalbum es gehört („Fparent“).

type
  TFoto = class;

  TFotoalbum = class(TObject)
  private
    Ffotos : Array of TFoto;
  end;

  TFoto = class(TObject)
  private
    Ffilename : String;
    Fparent : TFotoalbum;
  end;

Der einzige Unterschied (außer der Position im interface-Teil) zum Forwarding von Prozeduren und Funktionen besteht darin, dass das Schlüsselwort „foward“ nicht verwendet wird. Stattdessen wird lediglich bekannt gegeben, dass es eine Klasse „TFoto“ geben wird. Dies macht man mit der Anweisung „TFoto = class;“, mehr darf dort auch nicht stehen, so zum Beispiel auch nicht, von welcher Klasse „TFoto“ abgeleitet werden soll. Das wird erst angegeben, wenn die vollständige Deklaration erfolgt.

Die uses-Klausel

Damit eine Unit (z.B. „Unit1“) den Inhalt einer anderen Unit (z.B. „Unit2“) „kennt“, muss man Unit2 in die uses-Klausel von Unit1 aufnehmen. Dadurch wird alles, was in Unit2 im interface-Abschnitt steht, Unit1 bekannt und kann verwendet werden. Wenn Sie sich die Unit ansehen, welche Unit für ein Formular anlegt, werden Sie sehen, dass schon einiges in der uses-Klausel drin steht. Dies sind Units, welche Delphi mitbringt und die für das Darstellen von Formular und Komponenten nötig sind.
Zusätzlich zu der standardmäßigen uses-Klausel im interface-Abschnitt kann man auch noch eine uses-Klauses im implementation-Abschnitt anlegen. Der Inhalt einer dort eingetragenen Unit ist dann nur im implementation-Teil bekannt, nicht aber im interface-Teil. Wozu ist das gut?
Dieses Vorgehen wird verwendet, um einen so genannten „überkreuzenden Bezug“ zu verhindern. Ein überkreuzender Bezug entsteht dann, wenn man Unit2 in die uses-Klausel (interface-Bereich) von Unit1 schreibt, und Unit1 in die uses-Klausel (interface-Bereich) von Unit2. Das würde eine Endlosschleife ergeben, weil jede Unit die andere benutzt.
Passiert dies, kann man sich in vielen Fällen damit retten, dass man eine der beiden Einträge in der uses-Klausel im implementation-Abschnitt vornimmt. Denn die uses-Klausel im implementation-Abschnitt ist wie alles andere dort nur in dieser Unit bekannt, kann also auch keinen überkreuzenden Bezug erzeugen. Leider kann man dadurch die Inhalte der eingebundenen Unit auch nur im implementation-Abschnitt nutzen.

Überladen von Funktionen

Eng mit dem Thema „interface-Abschnitt“ verbunden ist das so genannte „überladen von Funktionen“. Darunter versteht man, dass man verschiedene Funktionen mit gleichem Namen, aber unterschiedlichen Parametern hat. Hier zu nochmal das Beispiel der Rechteck-Klasse:

type
TRechteck = class(TgeomForm)
private
Fhoehe : Integer;
Fbreite : Integer;
public
constructor create(hoehe, breite : Integer); overload;
constructor create(groesse : Integer); overload;
end;
constructor TRechteck.create(hoehe, breite: Integer);
begin
inherited create;

FHoehe := hoehe;
FBreite := breite;
end;

{…}

constructor TRechteck.create(groesse : Integer);
begin
inherited create;

FHoehe := groesse;
FBreite := groesse;
end;
Diese Klasse besitzt nun zwei Konstruktoren: einmal der Konstruktor mit zwei Parametern, wie er bereits bekannt ist (er erzeugt ein Rechteck mit der gegebenen Höhe und Breite) und einmal einen Konstruktor mit nur einem Parameter, welcher ein Quadrat mit der gegebenen Kantenlänge erzeugt.
Damit Delphi weiß, dass man wirklich zwei Methoden gleichen Namens verwenden möchte, muss man bei der Deklaration das Schlüsselwort „overload“ hinter jede dieser Methoden setzen. Wichtig ist, dass Delphi anhand der Anzahl und der Art der Parameter eindeutig bestimmen können muss, welche Methode gemeint ist! Ansonsten ist eine Überladung nicht möglich. In diesem Fall ist die Unterscheidung nicht schwer, sie erfolgt über die Anzahl der Parameter: Wird der Konstruktor mit einem Parameter aufgerufen, wird ein Quadrat erzeugt, bei zwei Parameter ein Rechteck.
Selbstverständlich funktioniert das Überladen auch mit Funktionen und Prozeduren und nicht nur mit Konstruktoren. Eine Überladung ist auch nicht auf Methoden (also an Objekte gebundene Funktionen und Prozeduren) beschränkt, sondern funktioniert auch bei nicht-objektgebundenen Funktionen bzw. Prozeduren. Die Deklaration ist dabei mit der Deklaration bei Methoden identisch, nur halt nicht das restliche Zeugs einer Klassendeklaration drum herum steht.

2 Gedanken zu „Delphi-Crashkurs“

  1. Guten Morgen,

    erst einmal vielen Dank für die ausführlichen Erklärungen!

    Im ersten Beispiel zur Darstellung von Zahlen im Rechner müsste es heißen

    4 mal 1 = 4*10^0

    statt

    4 mal 1    = 4*10

     

    1. Vielen Dank für den Hinweis.
      Ich habe es entsprechend korrigiert.
      Auch Deinen weiteren Hinweis habe ich eingearbeiotet.

Kommentare sind geschlossen.