18. April 2001

Programmierung

 

Wie kann ich mit dem Typ PChar arbeiten?

von Sven Künzler 30. Mai 1998 [ab D1]

Dieser Variablentyp sorgt immer mal wieder für Verwirrung. Um zum Beispiel Speicher zu sparen, will ein Programmierer in einem Record statt Strings PChars verwenden:

Type TDaten = Record
     Name: PChar;
     Vorname: PChar;
     {usw.}
End;
[...]
Var person: TDaten;
Begin
   person.Name:='Gates';
An dieser Stelle kommt es nun zum Fehler: Delphi löst eine Exception aus und moniert, dass an eine ungültige Adresse geschrieben wird. Warum dies? Der Typ PChar ist ein Zeiger auf ein Array Of Char. Das heißt, in einer Variable vom Typ PChar kann man die Adresse speichern, an der die Zeichenkette abgelegt werden soll, nicht jedoch die Zeichenkette selbst.
Im Beispiel wird nun versucht, an eine Speicherzelle zu schreiben, die durch person.Name bezeichnet wird. Da dieser Zeiger aber nicht initialisiert wurde, zeigt er bestenfalls auf die Speicherzelle 0 (das kann Delphi abfangen), schlimmstenfalls irgendwo in die Wüste. Hier kann es sogar zu einer Schutzverletzung kommen.
Die Lösung: Man definiert statt Pointer echte Arrays im Record:
Type TDaten = Record
     Name: Array [0..20] Of Char;
     Vorname: Array [0..20] Of Char;
     {usw.}
End;
[...]
Var person: TDaten;
Begin
   person.Name:='Gates';
Bei dieser Version muss man nun allerdings sicherstellen, dass in die Felder tatsächlich nur Namen mit maximal 20 Buchstaben geschrieben werden. Einen Char "Platz" braucht Delphi nämlich noch, um das Ende der Zeichenkette zu markieren.
Alles in allem sollte man sich genau überlegen, ob es bei den heutigen Prozessorleistungen und Speicherkapazitäten überhaupt sinnvoll ist, hier jedes Byte sparen zu müssen. Benutzt man den Typ String, so übernimmt Delphi die komplette Speicherverwaltung. Unter D2/D3 sind so auch beliebig lange Strings möglich. Dabei entsteht minimaler Verwaltungs-Overhead, der in den allermeisten Fällen kaum ins Gewicht fallen dürfte.
 

Was ist mit PChar-Parametern in Funktionsaufrufen?

von Sven Künzler 30. Mai 1998 [ab D1]

Um beispielsweise die Prozedur FindExecutable aufzurufen, benötigt man scheinbar zwei Parameter vom Typ PChar.

Procedure FindExecutable(file, dir: PChar; result: THandle);
Heißt das nun, dass man hier wieder umständlich mit Arrays hantieren müsste, um diese Funktion aufzurufen?
Nein. Auch hier kann man bei den bewährten Strings bleiben. Unter D2/D3 ist dies möglich durch Typecasting:
var fname, dir: String;
    res: THandle;
Begin
    fname:='SOL.EXE';
    dir:='WIN';
    FindExecutable(PChar(fname),PChar(dir),res);
Intern verwaltet Delphi Strings sehr ähnlich wie Arrays of Char. PChar(fName) tut nichts anderes als die Adresse zu berechnen, an der die Zeichenkette gespeichert ist, die durch die String-Variable fName repräsentiert wird.
Damit diese Methode klappt, muss allerdings der Compiler-Schalter {$H+} für lange Strings eingeschaltet sein (per Voreinstellung ist er das). Wenn man auf lange Strings verzichten will oder D1 benutzt, klappt das Typecasting nicht so einfach. Hier muss man entweder mit Arrays arbeiten oder die Funktion StrPCopy verwenden, die einen Pascal-String in ein Null-terminiertes Array of Char umwandelt.
 

Wie kann ich dynamische Arrays anlegen?

von Gunnar Scholz 30. Mai 1998 [D1-D3]

Seit Version 4.0 sind dynamische Arrays Bestandteil von Delphi. In älteren Versionen muss man ein wenig "tricksen", um ähnliche Funktionalität zu bekommen:

Um dynamische Arrays in D1-D3 zu deklarieren, gibt es zwei Methoden. Ob man bei der Deklaration Variante 1 oder 2 bevorzugen soll, darueber streiten sich die Experten. In beiden Fällen ist nur wichtig, dass man nicht auf Elemente zugreift, für die kein Speicherplatz allokiert worden ist.

Variante 1:

{$RANGECHECK OFF}
type
  TDynArray = array[0..0] of TIrgendwas;
  PDynArray = ^TDynArray;

Variante 2:

const
  MAXITEMS = 1000; { Maximale Anzahl der Einträge }
type
  TDynArray = Array[0..MAXITEMS] of TIrgendwas;
  PDynArray = ^TDynAray;
Die Benutzung funktioniert - unabhängig davon, welche der beiden Varianten in der Deklaration gewählt wurde - wie folgt:
var
  anzahl: Integer;
  DynArray: PDynArray;

  { Initialisierung }
  anzahl := 0;
  DynArray := nil;

  { Element hinzufuegen }
  Inc(anzahl);
  ReAllocMem(DynArray, anzahl * SizeOf(TIrgendwas));

  { letztes Element loeschen }
  Dec(anzahl);
  ReAllocMem(DynArray, anzahl * SizeOf(TIrgendwas));

  { alle Elemente loeschen bzw. gesamten Array-Speicher wieder freigeben }
  ReAllocMem(DynArray, 0);

  { Zugriff auf Element Nr. x (0 < x <= anzahl) }
  { Element schreiben }
  if x <= anzahl then DynArray^[x-1]:= irgendwas;

  { Element lesen }
  if x <= anzahl then irgendwas :=DynArray^[x-1];
Folgende Hinweise zu dem Beispiel-Source:
Bevor man Daten als dynamische Arrays programmiert, sollte man sich fragen, ob es nicht sinnvoller ist, von vornherein auf dynamische Strukturen zu setzen. Delphi bietet hierfür beispielsweise TList an. Dieser Listentyp erlaubt beliebig viele Einträge und übernimmt dabei automatisch Bereichsprüfungen. Darüber hinaus bietet er den Vorteil, auch nachträglich Einträge an beliebiger Stelle einzufügen oder zu entfernen.
TList verwaltet aber nur die Zeiger auf die Daten und kümmert sich nicht um deren Inhalt. Man muss selbst dafür sorgen, dass die an Add/Insert übergebenen Zeiger ordnungsgemäß initialisiert worden sind. Außerdem werden durch Delete/Remove nur die Verweise in TList gelöscht. Die Daten bleiben erhalten und müssen manuell entfernt werden.
Type
  Liste : TList;
  TIrgendwas = ...
  PIrgendwas = ^TIrgendwas;
  i: Integer;
Var
  Irgendwas: PIrgendwas;
...
  Liste := TList.Create;
...
  i := Liste.Add(New(Irgendwas));
  PIrgendwas(Liste[i]^) := ...;
...
  Dispose(PIrgendwas(Liste[i]^));
  Liste.Delete(i);
...
  Liste.Free;
 

Wieso bekomme ich Ausnahmefehler, wenn ich in einem TStrings-Objekt Zeichenketten zu speichern versuche?

von Sven Künzler 02. Juni 1998 [ab D1]

TStrings stellt nur das ,,Skelett'' für eine Stringliste bereit. Diese Klasse ist hauptsächlich für die Entwicklung weiterer Klassen gedacht, die mit TStrings dann ihre Stringverwaltung implementieren, indem sie die entsprechenden virtuellen Methoden von TStrings überschreiben.

Ein Beispiel für ein solches Objekt ist TStringList. Dieses ist von TStrings abgeleitet und implementiert tatsälich die benötigten Methoden. Wenn man also eine solche Stringverwaltung vorgefertigt haben möchte, benutze man TStringList statt TStrings.

 

Ich habe zwei Fließkommavariablen a und b, die laut Debugger den gleichen Wert haben. Aber die Vergleichsoperatoren a=b und a<>b liefern genau das umkehrte Ergebnis?

von Marian Aldenhövel 21. August 1999 [ab D1]

Fließkommawerte werden nur dann als gleich angesehen, wenn sie dasselbe Bitmuster aufweisen. Zwei Fließkommawerte, die sich um ein winziges Epsilon unterscheiden, haben verschiedene Bitmuster.

Hat man nun die beiden Werte auf verschiedene Weise ermittelt, wobei die Wege arithmetisch durchaus gleichwertig sein können, bekommt man mit hoher Wahrscheinlichkeit ein numerisch anderes Ergebnis.

Man sollte daher statt Gleichheit oder Ungleichheit auf "Ähnlichkeit" prüfen:
function equals(a,b:extended): boolean;
begin
  result:=(abs(a-b)<epsilon);
end;
epsilon setzt man auf einen kleinen Wert, wie klein hängt von den Anforderungen an der Anwendung ab.
 

Wie kann ich die Kalenderwoche ermitteln, in die ein bestimmtes Datum fällt?

von Achim Kalwa und Sven Künzler 18. April 2001

Irgendwann mal irgendwo abgeschrieben:

  function WeekOfYear(D : TDateTime) : Byte;
  var
    aDate        : Integer;
    MinDate      : tDateTime;
    MaxDate      : tDateTime;
    Day, Month, Year : Word;
    FirstDate    : Integer;
  begin
    aDate := Trunc(D);
    MinDate := EncodeDate(1900, 1, 1);
    MaxDate := EncodeDate(3999, 12, 31);
    if (aDate < MinDate) or (aDate > MaxDate) then begin
      Result := 0;
      exit;
    end;

    aDate  := aDate  + 3 - ((5 + DayOfWeek(aDate)) MOD 7);
    DecodeDate(aDate, Year, Month, Day);
    FirstDate := Trunc(EncodeDate(Year, 1, 1));

    Result := 1 + (aDate - FirstDate) DIV 7;
  end; 
Externe Quellen

Frequently Asked Questions about Calendars von Claus Tøndering
Die Kalender-FAQ enthält geschichtliche Informationen über die Zeitrechnung sowie die wichtigsten Kalender-Algorithmen.