Einzelnen Beitrag anzeigen

Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.648 Beiträge
 
Delphi 11 Alexandria
 
#1082

AW: Was nervt euch so, während der Programmierung oder so allgemein

  Alt 13. Jul 2012, 06:26
Generisch hin, (un)typisiert her. Mir geht es einfach nur um eine doppelt verkettete Liste. Allein bei deren Deklaration bekam ich schon zu Turbo-Pascal-Zeiten einen dicken Hals: Wie kann etwas sich selbst vollständig als echte Teilmenge enthalten? Das ist mit mathematischer Logik nicht faßbar, das gibt es n.m.W. nur bei unendlichen Mengen. Inzwischen weiß ich, wie das gemeint ist.
Warum brauchst du denn für eine doppelt verkettete Liste explizite Pointer?

Wenn man diesen Unrat wenigstens vernünftig debuggen könnte, ist aber leider Fehlanzeige.
Ich habe mal kurz eine doppelt verkettete Liste geschrieben, die kann ich auch sehr gut debuggen (wobei ich die geschrieben und einmal testweise gestartet habe und es lief ):
Delphi-Quellcode:
type
  TDoubleLinkedListEntry<T> = class
  private
    var
      FValue: T;
      FPrevious: TDoubleLinkedListEntry<T>;
      FNext: TDoubleLinkedListEntry<T>;
    procedure SetValue(const Value: T);
    procedure SetNext(const Value: TDoubleLinkedListEntry<T>);
    procedure SetPrevious(const Value: TDoubleLinkedListEntry<T>);
  public
    constructor Create(const AValue: T; const APrevious, ANext: TDoubleLinkedListEntry<T>);
    property Value: T read FValue write SetValue;
    property Previous: TDoubleLinkedListEntry<T> read FPrevious write SetPrevious;
    property Next: TDoubleLinkedListEntry<T> read FNext write SetNext;
  end;

  TDoubleLinkedList<T> = class
  private
    var
      FHead, FTail: TDoubleLinkedListEntry<T>;
    type
      TListEnumerator = class
      private
        var
          FFirst: Boolean;
          FCurrent: TDoubleLinkedListEntry<T>;
      public
        constructor Create(AList: TDoubleLinkedList<T>);
        property Current: TDoubleLinkedListEntry<T> read FCurrent;
        function MoveNext: Boolean;
      end;
  public
    destructor Destroy; override;
    procedure Append(const Value: T);
    procedure Delete(const Value: T);
    procedure Remove(const Value: TDoubleLinkedListEntry<T>);
    function GetEnumerator: TListEnumerator;
    property Head: TDoubleLinkedListEntry<T> read FHead;
    property Tail: TDoubleLinkedListEntry<T> read FTail;
  end;

{ TDoubleLinkedListEntry<T> }

constructor TDoubleLinkedListEntry<T>.Create(const AValue: T; const APrevious, ANext: TDoubleLinkedListEntry<T>);
begin
  FValue := AValue;
  FPrevious := APrevious;
  FNext := ANext;
end;

procedure TDoubleLinkedListEntry<T>.SetNext(const Value: TDoubleLinkedListEntry<T>);
begin
  FNext := Value;
end;

procedure TDoubleLinkedListEntry<T>.SetPrevious(const Value: TDoubleLinkedListEntry<T>);
begin
  FPrevious := Value;
end;

procedure TDoubleLinkedListEntry<T>.SetValue(const Value: T);
begin
  FValue := Value;
end;

{ TDoubleLinkedList<T> }

destructor TDoubleLinkedList<T>.Destroy;
var
  CurrentEntry: TDoubleLinkedListEntry<T>;
begin
  if Assigned(FHead) then
  begin
    CurrentEntry := FHead;
    while Assigned(CurrentEntry.Next) do
    begin
      CurrentEntry := CurrentEntry.Next;
      CurrentEntry.Previous.Free;
    end;
    FTail.Free;
  end;
  inherited;
end;

function TDoubleLinkedList<T>.GetEnumerator: TListEnumerator;
begin
  Result := TListEnumerator.Create(Self);
end;

procedure TDoubleLinkedList<T>.Append(const Value: T);
begin
  if Assigned(FTail) then
  begin
    FTail.Next := TDoubleLinkedListEntry<T>.Create(Value, FTail, nil);
    FTail.Next.Previous := FTail;
    FTail := FTail.Next;
  end
  else
  begin
    FTail := TDoubleLinkedListEntry<T>.Create(Value, FTail, nil);
    FHead := FTail;
  end;
end;

procedure TDoubleLinkedList<T>.Remove(const Value: TDoubleLinkedListEntry<T>);
begin
  if Assigned(Value.Previous) then
    Value.Previous.Next := Value.Next;
  if Assigned(Value.Next) then
    Value.Next.Previous := Value.Previous;
end;

procedure TDoubleLinkedList<T>.Delete(const Value: T);
var
  CurrentEntry: TDoubleLinkedListEntry<T>;
begin
  CurrentEntry := FHead;
  while Assigned(CurrentEntry) do
    if TComparer<T>.Default.Compare(CurrentEntry.Value, Value) = 0 then
    begin
      Remove(CurrentEntry);
      CurrentEntry.Free;
      Break;
    end
    else
      CurrentEntry := CurrentEntry.Next;
end;

{ TDoubleLinkedList<T>.TListEnumerator }

constructor TDoubleLinkedList<T>.TListEnumerator.Create(AList: TDoubleLinkedList<T>);
begin
  FFirst := True;
  FCurrent := AList.Head;
end;

function TDoubleLinkedList<T>.TListEnumerator.MoveNext: Boolean;
begin
  Result := Assigned(FCurrent) and (Assigned(FCurrent.Next) or FFirst);
  if Result and not FFirst then
    FCurrent := FCurrent.Next;
  FFirst := False;
end;
Benutzung:
Delphi-Quellcode:
var
  Test: TDoubleLinkedList<Integer>;
  CurrentEntry: TDoubleLinkedListEntry<Integer>;
begin
  Test := TDoubleLinkedList<Integer>.Create;
  try
    Test.Append(100);
    Test.Append(300);
    Test.Append(200);
    for CurrentEntry in Test do
      ShowMessage(IntToStr(CurrentEntry.Value)); // 100, 300, 200
    Test.Delete(300);
    for CurrentEntry in Test do
      ShowMessage(IntToStr(CurrentEntry.Value)); // 100, 200
  finally
    Test.Free;
  end;
end;
Und debuggen kann ich das auch problemlos:

doublelinkedlistdebug.png

Sicher, explizite Zeiger braucht man an manchen Stellen natürlich. Solange man das ganze durchdacht strukturiert machen die aber auch nicht mehr Probleme als andere Sprachbestandteile.

Ein Beispiel, das ich beruflich vor einer Weile brauchte, war ein generischer Ringpuffer. Da dort auch z.B. Records hineingespeichert werden können sollten, brauchte ich einen Pointer auf die gespeicherten Daten um diese verändern zu können. Dies wird leider nicht komplett unterstützt, aber vom Prinzip her sah das am Ende so aus:
Delphi-Quellcode:
  TCircularBuffer<T> = class
  public
    type
      TBufferPointer = ^T;
  strict private
    var
      FElements: TArray<T>;
  // ...
  public
    type
      TBufferEnumerator = class(TEnumerator<TCircularBuffer<T>.TBufferPointer>)
      // ...
      public
        constructor Create(ABuffer: TCircularBuffer<T>);
        property Current: TCircularBuffer<T>.TBufferPointer read GetCurrent;
        function MoveNext: Boolean;
      end;
    function GetEnumerator: TBufferEnumerator; reintroduce;
    // ...
  end;
Ohne Generics und den Pointer in Kombination wäre das gar nicht möglich das typsicher so umzusetzen. Aber trotzdem ist das ganze immer noch typsicher.
Sebastian Jänicke
AppCentral