furious programming
2017-10-12 01:21

Co jakiś czas ktoś – celowo lub przy okazji – pyta się na forum, czy referencja w Pascalu to wskaźnik, czy nie wskaźnik. Uściślijmy więc.

Tak, referencja instancji klasy to wskaźnik, co prawda traktowany jest w odmienny sposób, jednak to nie zmienia faktu bycia wskaźnikiem. Aby sprawdzić to w praktyce, wystarczy referencję zrzutować na pointer, a ten następnie z powrotem na referencję konkretnej klasy.

Przykład testowej aplikacji poniżej:

uses
  Classes;
var
  ListObj: TStringList;
  ListPtr: Pointer;
begin
  ListObj := TStringList.Create();
  try
    ListObj.Add('free pascal');
 
    ListPtr := Pointer(ListObj);      // przerobienie referencji na wskaźnik
    ListObj := TStringList(ListPtr);  // przerobienie wskaźnika na referencję
 
    Write(ListObj.Text);
  finally
    ListObj.Free();
  end;
end.

Dwa rzutowania i wszystko jasne. Miszung ze wskazaniami nie sprawi, że w linijce z wyświetleniem zawartości listy dostaniemy wyjątek.

Gdzieniegdzie, raczej w starych kodach pisanych np. w Delphi 7, można spotkać konwersję referencji na zwykłą liczbę, w celu jej przesłania w parametrze razem z komunikatem.

Druga sprawa – czasem ktoś wpada na pomysł, aby wykorzystać zwykłą procedurę jako zdarzenie jakiegoś obiektu. I też pojawiają się różne zdania na temat tego, czy da się, czy jednak się nie da. Otóż, bezpośrednio nie da się – zmienna zdarzeniowa nie jest pointerem, więc nie można do niej przypisać adresu zwykłej procedury. A czym jest? Nie jest to jeden wskaźnik, a dwa – jeden przechowuje adres kodu do wykonania, a drugi wskazuje na dodatkowe dane.

Aby móc użyć zwykłej procedury jako zdarzenia, należy przypisać adres tej procedury do pierwszego wskaźnika. A co z drugim, tym na dane? Cóż, można w nim przesłać adres czegokolwiek – wyślijmy więc referencję obiektu, aby dopełnić pierwszą część tego wpisu. ;)

Prosty przykład:

uses
  Classes;
 
  procedure OnListChange(ASender: TObject);  // zwykła procedura, zgodna z TNotifyEvent
  var
    List: TStringList absolute ASender;
  begin
    Write(List.Text);
  end;
 
var
  List: TStringList;
  Method: TMethod;  // TMethod reprezentuje wymaganą strukturę wskaźników
begin
  List := TStringList.Create();
  try
    Method.Code := @OnListChange;  // przypisanie adresu zwykłej procedury
    Method.Data := List;           // przypisanie referencji (ta podana zostanie w parametrze ASender)
 
    List.OnChange := TNotifyEvent(Method);  // ustawienie przygotowanego zdarzenia (rzutowanie konieczne)
    List.Add('free pascal');
  finally
    List.Free();
  end;
end.

Po wywołaniu metody Add, wartość jej parametru zostanie dodana do listy, po czym instancja odpali zdarzenie OnChange, o ile zostało ustawione. My to zrobiliśmy, więc zostanie wykonana procedurka OnListChange – na ekranie konsoli pojawi się zawartość listy, czyli w tym przypadku napis free pascal.

#free-pascal #lazarus #delphi

Azarien

Referencja to referencja. To czy referencja jest samym tylko wskaźnikiem, czy jakimś opakowanym, czy czymś więcej - to szczegół implementacyjny.

furious programming

Owszem, jednak aby wiedzieć z czym ma się do czynienia i co niestandardowego można z referencjami zrobić, warto zagłębić się w temat i podłubać. W razie kolejnego ciekawskiego, będę mógł podlinkować ten wpis, zamiast znów tłumaczyć to samo. :P