2009-02-27 16 views
6

Ich habe mich seit einiger Zeit entwickelt, und ich habe Zeiger in meiner Entwicklung bisher nicht verwendet.Verwenden von Zeigern in Delphi

Was sind die Vorteile von Zeigern? Läuft eine Anwendung schneller oder benötigt sie weniger Ressourcen?

Da ich bin sicher, dass Zeiger wichtig sind, können Sie mich auf einige Artikel "Basis", aber gut, um Zeiger in Delphi zu verwenden? Google gibt mir zu viele, zu spezielle Ergebnisse.

Antwort

30

Ein Zeiger ist eine Variable, die auf ein Stück Speicher verweist. Die Vorteile sind:

  • Sie können das Stück Speicher die gewünschte Größe geben.
  • Sie müssen nur einen Zeiger ändern, um auf einen anderen Speicherbereich zu zeigen, der viel Zeit beim Kopieren spart.

Delphi verwendet viele versteckte Zeiger. Wenn Sie beispielsweise Folgendes verwenden:

var 
    myClass : TMyClass; 
begin 
    myClass := TMyClass.Create; 

myClass ist ein Zeiger auf das Objekt.

Ein anderes Beispiel ist das dynamische Array. Dies ist auch ein Zeiger.

Um mehr über Zeiger zu verstehen, müssen Sie mehr über Speicher wissen. Jedes Stück Daten kann in verschiedenen Datenstücken existieren.

Zum Beispiel globale Variablen:

unit X; 

interface 

var 
    MyVar: Integer; 

Eine globale Variable im Datensegment definiert ist. Das Datensegment ist festgelegt. Und während der Laufzeit des Programms sind diese Variablen verfügbar. Dies bedeutet, dass der Speicher nicht für andere Zwecke verwendet werden kann.

Lokale Variablen:

procedure Test; 
var 
    MyVar: Integer; 

Eine lokale Variable existiert auf dem Stapel. Dies ist ein Stück Speicher, das für den Haushalt verwendet wird. Es enthält die Parameter für die Funktion (ok einige sind in einem Register, aber das ist jetzt nicht wichtig). Es enthält die Rücksprungadresse, so dass die CPU weiß, wohin sie zurückkehren soll, wenn das Programm beendet ist. Und es enthält die lokalen Variablen, die in den Funktionen verwendet werden. Lokale Variablen existieren nur während der Lebensdauer einer Funktion. Wenn die Funktion beendet ist, können Sie nicht zuverlässig auf die lokale Variable zugreifen.

Heap Variablen:

procedure Test2; 
var 
    MyClass: TMyClass; 
begin 
    MyClass := TMyClass.Create; 

Die Variable MyClass ist ein Zeiger (die eine lokale Variable ist, die auf dem Stapel definiert ist). Indem Sie ein Objekt konstruieren, ordnen Sie dem Heap (dem großen Teil des "anderen" Speichers, der nicht für Programme und Stacks verwendet wird) ein Stück Speicher zu. Die Variable MyClass enthält die Adresse dieses Speicherbereichs. Heap-Variablen existieren, bis Sie sie freigeben. Das bedeutet, wenn Sie die Funktion Test2 verlassen, ohne das Objekt freizugeben, ist das Objekt immer noch auf dem Heap vorhanden. Sie können jedoch nicht darauf zugreifen, da die Adresse (Variable MyClass) nicht mehr vorhanden ist.

Best Practices

Es wird vorzugsweise fast immer zuzuweisen und eine Zeigervariable auf der gleichen Ebene freigeben.

Zum Beispiel:

var 
    myClass: TMyClass; 
begin 
    myClass := TMyClass.Create; 
    try 
    DoSomething(myClass); 
    DoSomeOtherthing(myClass); 
    finally 
    myClass.Free; 
    end; 
end; 

Wenn Sie können, versuchen Funktionen zu vermeiden, die eine Instanz eines Objekts zurück. Es ist nie sicher, ob der Anrufer das Objekt entsorgen muss. Und das schafft Speicherlecks oder Abstürze.

+0

Hoffe, das ist genug Informationen, ich kann mehr hinzufügen, wenn Sie möchten. –

+0

+1 Hervorragende Erklärung. Gute Arbeit! –

+0

Danke, ich helfe gerne. Aber es ist immer schön, wenn es geschätzt wird. –

2

Sie haben wahrscheinlich Zeiger verwendet, aber Sie wissen es einfach nicht. Eine Klassenvariable ist ein Zeiger, eine Zeichenfolge ist ein Zeiger, ein dynamisches Array ist ein Zeiger, Delphi versteckt es nur für Sie. Sie werden sie sehen, wenn Sie API-Aufrufe durchführen (Zeichenfolgen an PChar übergeben), aber selbst dann kann Delphi viel verbergen.

Siehe Gamecats Antwort für Vorteile von Zeigern.

In diesem About.com article finden Sie eine grundlegende Erklärung der Zeiger in Delphi.

2

Zeiger sind für einige Datenstrukturen notwendig. Das einfachste Beispiel ist eine verkettete Liste. Der Vorteil solcher Strukturen ist, dass Sie Elemente rekombinieren können, ohne sie im Speicher zu verschieben. Zum Beispiel können Sie eine verknüpfte Liste von großen komplexen Objekten haben und zwei von ihnen sehr schnell austauschen, da Sie wirklich zwei Zeiger anpassen müssen, anstatt diese Objekte zu verschieben.

Dies gilt für viele Sprachen einschließlich Object Pascal (Delphi).

10

Sie haben bis jetzt viele gute Antworten gegeben worden, aber beginnend mit der Antwort, die Sie bereits mit Zeigern zu tun haben, wenn Sie lange Strings verwenden, dynamische Arrays und Objektreferenzen sollten Sie beginnen sich fragen, warum Sie würde Einsatz Zeiger anstelle von langen Strings, dynamischen Arrays und Objektreferenzen. Gibt es einen Grund, immer noch Zeiger zu benutzen, da Delphi sie in vielen Fällen gut versteckt?

Lassen Sie mich Ihnen zwei Beispiele für die Verwendung von Pointer in Delphi geben. Sie werden sehen, dass dies für Sie wahrscheinlich überhaupt nicht relevant ist, wenn Sie hauptsächlich Business-Apps schreiben. Es kann jedoch wichtig werden, wenn Sie jemals Windows- oder API-Funktionen von Drittanbietern verwenden müssen, die von keiner der Standard-Delphi-Einheiten importiert werden und für die keine Importeinheiten in (beispielsweise) den JEDI-Bibliotheken gefunden werden. Und es kann der Schlüssel sein, um das notwendige letzte Stück Geschwindigkeit in dem Code zur Verarbeitung von Strings zu erreichen.

Pointers können verwendet werden, mit Datentypen in unterschiedlichen Größen (unbekannt bei der Kompilierung) beschäftigen

den Bitmap-Datentyp Windows-Betrachten. Jedes Bild kann unterschiedliche Breite und Höhe haben, und es gibt verschiedene Formate, die von Schwarz und Weiß (1 Bit pro Pixel) über 2^4, 2^8, 2^16, 2^24 oder sogar 2^32 Grauwerte oder Farben reichen . Das bedeutet, dass zur Kompilierungszeit nicht bekannt ist, wie viel Speicher eine Bitmap belegen wird.

In Windows.pas gibt es die TBitmapInfo Typ:

type 
    PBitmapInfo = ^TBitmapInfo; 
    tagBITMAPINFO = packed record 
    bmiHeader: TBitmapInfoHeader; 
    bmiColors: array[0..0] of TRGBQuad; 
    end; 
    TBitmapInfo = tagBITMAPINFO; 

Das TRGBQuad Element ein einzelnes Pixel beschreibt, aber die Bitmap hat natürlich mehr als ein Pixel enthalten.Daher würde man nie eine lokale Variable vom Typ TBitmapInfo, aber immer einen Zeiger darauf verwenden:

var 
    BmpInfo: PBitmapInfo; 
begin 
    // some other code determines width and height... 
    ... 
    BmpInfo := AllocMem(SizeOf(TBitmapInfoHeader) 
    + BmpWidth * BmpHeight * SizeOf(TRGBQuad)); 
    ... 
end; 

nun den Zeiger verwenden, können Sie alle Pixel zuzugreifen, obwohl TBitmapInfo nur ein einziges hat. Beachten Sie, dass Sie für einen solchen Code die Bereichsprüfung deaktivieren müssen.

Stuff wie das kann natürlich auch mit der TMemoryStream Klasse behandelt werden, die im Grunde eine freundliche Wrapper um einen Zeiger auf einen Speicherblock ist.

Und natürlich ist es viel einfacher, einfach ein TBitmap zu erstellen und seine Breite, Höhe und Pixelformat zuzuweisen. Um dies erneut zu sagen, eliminiert die Delphi-VCL die meisten Fälle, in denen Zeiger sonst notwendig wären.

Zeiger auf Zeichen verwendet werden String-Operationen zu beschleunigen

Dies ist, wie die meisten Mikro-Optimierungen, verwendet werden, etwas, das nur in extremen Fällen, nachdem Sie profiliert haben und fand den Code Strings zu konsumieren viel Zeit.

Eine nette Eigenschaft von Strings ist, dass sie als Referenz gezählt werden. Das Kopieren kopiert nicht den Speicher, den sie belegen, sondern erhöht nur den Referenzzähler. Nur wenn der Code versucht, eine Zeichenfolge zu ändern, deren Referenzzahl größer als 1 ist, wird der Speicher kopiert, um eine Zeichenfolge mit einer Referenzzählung von 1 zu erstellen, die dann sicher geändert werden kann.

Eine nicht so nette Eigenschaft von Strings ist, dass sie als Referenz gezählt werden. Jede Operation, die möglicherweise die Zeichenfolge ändern könnte, muss sicherstellen, dass die Referenzanzahl 1 ist, da andernfalls Änderungen an der Zeichenfolge gefährlich wären. Das Ersetzen eines Zeichens in einer Zeichenfolge ist eine solche Änderung. Um sicherzustellen, dass die Referenzzählung 1 ist, wird ein Aufruf an UniqueString() vom Compiler hinzugefügt, wenn ein Zeichen in einer Zeichenfolge geschrieben wird. Jetzt schreibt n Zeichen einer Zeichenkette in einer Schleife verursacht Unique()-n mal aufgerufen werden, auch wenn nach dem ersten Mal ist sichergestellt, dass der Referenzzähler 1. Das bedeutet im Wesentlichen n - 1 Aufrufe von UniqueString() werden unnötigerweise ausgeführt.

Die Verwendung eines Zeigers auf die Zeichen ist eine gängige Methode, um Zeichenfolgenoperationen zu beschleunigen, die Schleifen enthalten. Stellen Sie sich vor, Sie möchten (für Anzeigezwecke) alle Leerzeichen in einer Zeichenfolge durch einen kleinen Punkt ersetzen. Verwenden Sie die CPU Ansicht des Debuggers und vergleicht den Code für diesen Code ausgeführt

procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString; 
var 
    i: integer; 
begin 
    Result := AValue; 
    for i := 1 to Length(Result) do begin 
    if Result[i] = ' ' then 
     Result[i] := $B7; 
    end; 
end; 

mit diesem Code

procedure MakeSpacesVisible(const AValue: AnsiString): AnsiString; 
var 
    P: PAnsiChar; 
begin 
    Result := AValue; 
    P := PAnsiChar(Result); 
    while P[0] <> #0 do begin 
    if P[0] = ' ' then 
     P[0] := $B7; 
    Inc(P); 
    end; 
end; 

In der zweiten Funktion wird es nur ein Anruf zu Unique(), wenn Die Adresse des ersten Zeichens wird dem Char-Zeiger zugewiesen.