GNU-Pascal in Beispielen: Units

zurück zu GNU-Pascal in Beispielen

Units dienen dazu, Quelltexte zu modularisieren und wiederverwertbare Konstanten, Typen und Routinen zu gruppieren. Sie ersparen es, in alten Programmen nach bestimmten Routinen zu suchen oder Ideen doppelt zu entwickeln. Solche Units, auch Module [1] genannt, werden mit der uses-Anweisung eingebunden. Werden mehrere Units eingebunden, so werden die einzelnen Module durch Kommas getrennt angegeben.

Der Aufruf des Compilers zum Erzeugen des Programms lautet:

gpc --automake dateiname.pas -o programmname

Die Option --automake sorgt dafür, dass alle benötigten Module eingebunden werden.


Die Standardunit GPC

Bearbeiten

Es existieren eine Reihe von Standardunits, die mit dem GNU-Pascal Compiler mitgeliefert werden. Eine von ihnen ist die Unit GPC, die im Folgenden in Auszügen präsentiert wird. GPC enthält viele nützlicher Routinen, darunter auch solche zum Thema „Datum und Zeit“:

Programm: Zeit

Bearbeiten
program Zeit;

uses GPC;

type
  TTagesString = String (10);

var
  Tag, Monat, Jahr: Integer;
  Time: TimeStamp;
  NullString: array [Boolean] of String (1) = ('', '0');
  KeinString: array [Boolean] of String (4) = ('kein', 'ein');

function TagName (t, m, j: Integer): TTagesString;
var
  TagNummer: Integer;
  TagNamen: array [0..6] of String (10) = (
    'Sonntag',    'Montag',  'Dienstag', 'Mittwoch',
    'Donnerstag', 'Freitag', 'Samstag');
begin
  TagNummer := GetDayOfWeek (t, m, j);
  TagName   := TagNamen[TagNummer]
end;

begin
  GetTimeStamp (Time);
  with Time do
    begin
      Tag   := Day;
      Monat := Month;
      Jahr  := Year
    end;
  WriteLn (TagName (Tag, Monat, Jahr),' ',
    NullString[Tag < 10], Tag, '.',
    NullString[Monat < 10], Monat, '.', Jahr);
  WriteLn ('Dieses Jahr ist ', KeinString[IsLeapYear (Jahr)],
    ' Schaltjahr.');
  WriteLn ('Es ist der ', GetDayOfYear (Tag, Monat, Jahr),
    '. Tag des Jahres.')
end.

Erklärung

Bearbeiten

Dieses Programm gibt den Tagnamen gefolgt vom Datum aus. Dazu kommen Informationen darüber, ob das gegenwärtige Jahr ein Schaltjahr ist und welcher Tag des Jahres gerade ist. Die drei Funktionen GetDayOfWeek, IsLeapYear und GetDayOfYear sind Bestandteil der Unit GPC.

Die Funktion TagName liefert uns den Namen des Tages. Da die Funktion GetDayOfWeek " Sonntag" als nullten Tag ansieht, wurde das Array TagNamen so gestaltet, wie es ist. GetTimeStamp liefert uns Informationen über das aktuelle Datum im Record Time zurück. Drei der Felder sind Day, Month und Year. NullString dient dazu, bei Werten kleiner als 10 eine führende Null auszugeben. IsLeapYear ist True, wenn das angegebene Jahr ein Schaltjahr ist. KeinString[True] hat den Wert " ein". GetDayOfYear gibt die aktuelle Tagesnummer des Jahres zurück.

Eine weitere Gruppe von Funktionen innerhalb der GPC-Unit beschäftigt sich mit dem Thema Kommandozeilenoptionen. Kommandozeilenoptionen sind Parameter, die einem Programms auf der Kommandozeile mitgegeben werden können. Die Option -o des GNU-Pascal Compilers ist ein solcher Parameter.

Programm: Kommandozeile

Bearbeiten
program Kommandozeile;

uses GPC;

var
  Optionen: array [1..3] of OptionType = (
    ('date',      NoArgument,        nil, 'd'),
    ('leapyear',  RequiredArgument,  nil, 'l'),
    ('time',      OptionalArgument,  nil, 't'));
  Index: Integer;
  Ch: Char;

procedure DatumAusgeben;
var
  Time: TimeStamp;
begin
  GetTimeStamp (Time);
  with Time do
    WriteLn (Day, '.', Month, '.', Year)
end;

procedure SchaltjahrAusgeben (Jahr: Integer);
begin
  if IsLeapYear (Jahr) then
    WriteLn (Jahr, ' ist ein Schaltjahr.')
  else
    WriteLn (Jahr, ' ist kein Schaltjahr.')
end;

procedure ZeitAusgeben (Arg: TString);
var
  Argument: TString = Arg;
  Time: TimeStamp;
begin
  GetTimeStamp (Time);
  if Argument <> '' then
    begin
      LoCaseString (Argument);
      if Argument <> 'jetzt' then
        WriteLn ('Option für ''--time'': ''jetzt''!')
      else
        with Time do
          WriteLn (Hour, ':', Minute, ':', Second)
    end
  else
    with Time do
      WriteLn (Hour, ':', Minute, ':', Second)
end;

procedure ParseArgumente (Opt: Char; Arg: TString);
var
  Zahl, Fehler: Integer;
begin
  case Opt of
    'd':  DatumAusgeben;
    'l':  begin
            Val (Arg, Zahl, Fehler);
            if Fehler = 0 then
              SchaltjahrAusgeben (Zahl)
            else
              WriteLn ('Fehlerhafte Option von --leapyear: ', Arg)
          end;
    't':  ZeitAusgeben (Arg)
  end
end;

begin
  repeat
    Ch := GetOptLong ('+-', Optionen, Index, False);
    case Ch of
      EndOfOptions: { Ende }
      otherwise     ParseArgumente (Ch, OptionArgument)
    end;
  until Ch = EndOfOptions
end.

Erklärung

Bearbeiten

Mit Optionen definieren wir ein Array auf OptionType-Argumente, welches die Optionen date, leapyear und time mit den dazugehörigen kurzen Schreibweisen d, l und t auflistet. Es wird beim Aufruf des Programms möglich sein, den Shellbefehl

kommandozeile --date

oder

kommandozeile -l 2002

auszuführen. Der Option date darf kein Argument mitgegeben werden, leapyear benötigt das Jahr als Argument und time darf ein optionales Argument "jetzt" haben. Die Prozeduren DatumAusgeben, SchaltjahrAusgeben und ZeitAusgeben tun genau das, was ihr Name vermuten lässt.

Im Hauptprogramm sorgt die Funktion GetOptLong dafür, dass die dem Programm übergebenen Optionen entgegen genommen werden. Gibt diese Funktion den Wert der vordefinierten Konstanten EndOfOptions zurück, so wird die dazugehörige repeat...until-Schleife beendet. Der erste Parameter bedeutet, dass keine Kommandozeilenoptionen gelesen werden, die nicht im Array Optionen enthalten sind. Das Abschließende - innerhalb des Parameters sorgt dafür, dass nur die kurzen Optionen übergeben werden, auch wenn eine lange Option auf der Kommandozeile angegeben wurde. Der Parameter False verhindert, dass nur lange Optionen berücksichtigt werden. Dieses Vorgehen ist insgesamt sehr praktisch, da wir auf diese Weise kurze und lange Optionen nicht getrennt auswerten müssen.

ParseArgumente übernimmt die ausgewählte kurze Option und den Wert einer globalen Variablen OptionArgument, welche je nach Option ein zusätzliches Argument der Option beinhaltet. Im Fall von -l zum Beispiel das Jahr. Der Prozedurkörper dieser Routine hat nur noch die Aufgabe, die entsprechenden Aufrufe der einzelnen Programmteile vorzubereiten.

Die Standardunit CRT

Bearbeiten

Diese Unit enthält eine Sammlung von Routinen, die das Schreiben von Textmodus-Anwendungen erleichtern. So enthält sie Prozeduren, um farbige Fenster und Töne zu erzeugen. Manche der Routinen, so auch solche zum Erzeugen von Tönen, sind nicht auf jeder Plattform vorhanden, insbesondere nicht unter X11. Sie sollten solche Programme wirklich nur im Textmodus benutzen.

Programm: CrtTest

Bearbeiten
program CrtTest;

uses CRT;

var
  Ende: Boolean = False;

procedure Ton;
begin
  { Nicht unter X11! }
  Sound (440);
  Delay (100);
  NoSound
end;

procedure Hilfe;
var
  HilfeSichtbar: Boolean = False; attribute (static)
begin
  if HilfeSichtbar then
    begin
      TextBackground (Black);
      TextColor (Black);
      ClrScr
    end
  else
    begin
      TextBackground (Blue);
      TextColor (White);
      Window (10, 7, 70, 13);
      ClrScr;
      GotoXY (1, 1);
      Write ('Anleitung');
      GotoXY (5, 3);
      Write ('Druecken Sie ''t'', um einen Ton zu erzeugen.');
      GotoXY (5, 4);
      Write ('Druecken Sie ''h'', um diese Hilfe zu benden.');
      GotoXY (5, 5);
      Write ('Druecken Sie ''e'', um das Programm zu verlassen.')
    end;
  HilfeSichtbar := Not HilfeSichtbar
end;

begin
  CRTInit;
  TextColor (LightGray);
  TextBackground (Blue);
  Window (1, 1, 80, 1);
  ClrScr;
  Write ('(t) Ton    (e) Ende    (h) Hilfe');
  repeat
    case ReadKey of
      'e', 'E':  Ende := True;
      't', 'T':  Ton;
      'h', 'H':  Hilfe
    end
  until Ende
end.

Erklärung

Bearbeiten
 
Ein typisches CRT-Programm

Die erste Prozedur, CRTInit, initialisiert die Unit. TextColor legt die Vordergrundfarbe, TextBackground die Hintergrundfarbe fest. Window legt ein farbiges Fenster mit den angegebenen X-, Y-Koordinaten fest, welches durch die Prozedur ClrScr in Blau gezeichnet wird. Der erste Window-Aufruf sorgt für ein Menü in der obersten Zeile. Die folgende Write-Anweisung schreibt in das zuletzt definierte Fenster hinein. ReadKey wartet auf eine Tastendruck, der entsprechend ausgewertet wird.

Innerhalb der Prozedur Ton schaltet Sound den Ton ein. Das Argument der Prozedur ist die Frequenz des Tones. Der Ton bleibt so lange hörbar, bis ein Aufruf von NoSound ihn abschaltet. Delay sorgt für eine kleine Pause von  , in der der Ton hörbar ist.

Hilfe schreibt eine Anleitung auf den Bildschirm. Bei jedem zweiten Aufruf der Hilfe wird das erzeugte blaufarbene Fenster schwarz überschrieben. Die Prozedur GoToXY setzt den Textcursor an die ausgewählte Stelle innerhalb des neu erzeugten Fensters. Die linke obere Ecke eines Fensters ist die Koordinate  .

Schreiben eigener Units

Bearbeiten

Eigene Units bestehen zumeist aus Sammlungen von Prozeduren, Funktionen und Typen die im Laufe einer Programmiertätigkeit anfallen. Sie sollten der Übersichtlichkeit halber thematisch geordnet sein. Die zugehörigen Dateinamen müssen kleingeschrieben werden. Eine Unit enthält mindestens zwei Bereiche, genannt interface- und implementation-Teil. Jeder dieser Bereiche darf seine eigenen Units einbinden, eigene Typen definieren und selbstverständlich auch Konstanten und Variablen deklarieren, wobei dem implementation-Bereich alle Informationen des interface-Teils bekannt sind, umgekehrt jedoch nicht.

Unit: Zeit1

Bearbeiten
unit Zeit1;

interface

procedure SchaltjahrAusgeben (Jahr: Integer);
procedure DatumAusgeben;

implementation

uses GPC;

procedure SchaltjahrAusgeben (Jahr: Integer);
begin
  if IsLeapYear (Jahr) then
    WriteLn (Jahr, ' ist ein Schaltjahr.')
  else
    WriteLn (Jahr, ' ist kein Schaltjahr.')
end;

procedure DatumAusgeben;
var
  Time: TimeStamp;
begin
  GetTimeStamp (Time);
  with Time do
    WriteLn (Day, '.', Month, '.', Year)
end;

end.

Erklärung

Bearbeiten

Eine Unit wird deklariert, indem das Schlüsselwort unit an den Anfang der Datei geschrieben wird. Diesem Schlüsselwort folgt der Name der Unit, der mit dem Dateinamen inhaltlich übereinstimmen muss. Die Unit endet mit dem abschließenden end gefolgt von einem Punkt. Im interface-Bereich der Unit werden diejenigen Konstanten, Typen, Variablen und Routinen aufgeführt, welche diese Unit exportiert. Nur die hier aufgeführten Elemente sind einem Programm, welches diese Unit mit uses einbindet, bekannt.

Im implementation-Teil der Unit werden die im interface-Bereich aufgeführten Routinen deklariert. Da wir zur Deklaration der beiden Routinen die Unit GPC einbinden müssen, das Interface jedoch keinen Nutzen von dieser Einbindung hat, wird die entsprechende uses-Anweisung im implementation-Bereich aufgeführt.

Diese Unit alleine ist noch nicht lauffähig. Zu ihr gehört ein Programm, welches die Unit nutzt:

Programm: UnitTest1

Bearbeiten
program UnitTest1;

uses Zeit1;

var
  Jahr: Cardinal;

begin
  Write ('Bitte geben Sie eine Jahreszahl ein: ');
  ReadLn (Jahr);
  SchaltjahrAusgeben (Jahr)
end.

Initialisierung einer Unit

Bearbeiten

Eine Unit zu initialisieren bedeutet, zum Zeitpunkt des Einbindens eine Reihe von Anweisungen auszuführen. Diese Anweisungen werden noch vor denen des Hauptprogramms ausgeführt. Eine Deinitialisierung führt analog Code aus, nachdem das Programm abgearbeitet wurde. Mit diesen Techniken ist es beispielsweise möglich, einen Stapel zu initialisieren und zum Ende des Programms wieder freizugeben. Unser Beispiel zeigt die grundsätzliche Vorgehensweise:

Unit: Zeit2

Bearbeiten
unit Zeit2;

interface

procedure DatumAusgeben;

implementation

uses GPC;

procedure DatumAusgeben;
var
  Time: TimeStamp;
begin
  GetTimeStamp (Time);
  with Time do
    WriteLn (Day, '.', Month, '.', Year)
end;

to begin do
  begin
    WriteLn ('Dies ist die Initialisierung');
    DatumAusgeben
  end;

to end do
  begin
    WriteLn ('Dies ist die Deinitialisierung');
    DatumAusgeben
  end;

end.

Erklärung

Bearbeiten

Die Initialisierung erfolgt im to begin do-Block. Hier werden alle Anweisungen aufgeführt, die vor dem eigentlichen Programmlauf ausgeführt werden sollen. Die Deinitialisierung erfolgt im to end do-Block. Das folgende Programm nutzt diese Unit:

Programm: UnitTest2

Bearbeiten
program UnitTest2;

uses Zeit2;

begin
  WriteLn ('Dies ist das Hauptprogramm')
end.

Erklärung

Bearbeiten

Die Ausgabe des Programms ist:

Dies ist die Initialisierung
1.12.2002
Dies ist das Hauptprogramm
Dies ist die Deinitialisierung
1.12.2002


Anmerkungen

Bearbeiten
  1. Streng genommen ist ein Modul eine weitere Möglichkeit zur Modularisierung, die in dieser Einführung nicht behandelt wird.