Klasa jako tablica

0

Być może ten temat nadaje się do działu Newbie, jeżeli tak, to proszę przenieść.

Chciałbym uzyskać odpowiedź, czy w Delphi można stworzyć klasę, która zachowuje się jak tablica?

Zamiast:

TKlasa = class
Dane: array of Integer;
end;

var
Klasa: TKlasa

begin
Klasa:= TKlasa.Create;
Klasa.Dane[0]:= 120;
end;

chciałbym móc zrobić tak:

var
Klasa: TKlasa

begin
Klasa:= TKlasa.Create;
Klasa[0]:= 120;
end;

Tak to działa np. w TList z biblioteki Generics, ale nie wiem jak osiągnąć taki efekt.

2

@Crow: najpierw musisz zaalokować pamięć dla wewnętrznej macierzy – za pomocą SetLength – aby móc z niej korzystać. Wywołanie konstruktora klasy jedynie alokuje pamięć dla obiektu.

0

Dzięki za odpowiedzi.

A czy jest możliwe stworzenie indeksowanej właściwości wielowymiarowej, do której mógłbym się odwoływać przez

Klasa[0, 0]:= 123;

?

Ogólnie cel mam taki, że chcę sobie stworzyć typ stanowiący tablicę dwuwymiarową (a konkretniej macierz), która sama się wypełnia danymi, w zależności od podanych parametrów. Np. mam jakiś wektor [X, Y, Z, W] i chcę go przemnożyć przez jedną z wcześniej zaprojektowanych macierzy, powiedźmy macierz obrotu. Nie chcę tego robić tak:

var
MacierzObrotu: TMacierz; //jest to array of array of Double;

begin
WypełnijMacierzDanymi(MacierzObrotu, JakiesParametry);
NowaMacierz:= PomnozMacierze(MacierzObrotu, Wektor);
end;

Tylko tak:

NowaMacierz:= PomnozMacierze(TMacierz.Create(JakieśParametry), Wektor));

Czyli zamiast tworzyć zmienną wskazującą na TMacierz i wypełniać ją określonymi danymi (za pomocą odrębnej procedury), chciałbym móc stworzyć sobie taką macierz w locie, podając ją jako parametr funkcji (sama macierz byłaby wypełniana odpowiednimi danymi w konstruktorze Create).

Może w ogóle zabieram się za to od złej strony i można to rozwiązać inaczej?

0

Przynajmniej dwa wymiary chyba jest możliwe teraz nie mam czasu się w to zagłębiać jak ale o ile mnie pamięć nie myli to choćby TStringGrid ma ten sposób zaimplementowaną właściwość Cells gdzie podaje się index X i Y komórki.

4

Jeśli istnieją ograniczenia związane z liczbą indeksów w właściwości indeksowanej, to są to ograniczenia związane z liczbą parametrów zwykłej metody. Pewnie jakieś istnieją, ale jest to liczba dość duża, której w zwykłym kodzie się raczej nie osiągnie. No bo kto by potrzebował metody przyjmującej np. 50 parametrów? ;)

Przykład:

type
  TSome = class(TObject)
  private
    function GetFoo(A, B, C, D, E, F, G: Integer): Integer;
    procedure SetFoo(A, B, C, D, E, F, G: Integer; AValue: Integer);
  public
    property Foo[A, B, C, D, E, F, G: Integer]: Integer read GetFoo write SetFoo;
  end;

Użycie takiej właściwości:

var
  Some: TSome;
begin
  Some := TSome.Create();
  Some.Foo[0, 1, 2, 3, 4, 5, 6] := 100;

Jak widać, właściwość indeksowana siedmioma wartościami może istnieć (sens jej istnienia to inna bajka). Tutaj warto zaznaczyć, że indeksy właściwości indeksowanej nie mają nic wspólnego z wymiarami. Można powiedzieć, że właściwość Foo jest jedynie krótkim wrapperem na metody GetFoo i SetFoo, a nawet cukrem składniowym, a jej indeksy to po prostu parametry tych metod. Tak więc aby móc korzystać z takiej właściwości, nie musisz mieć wielowymiarowej macierzy, z której czyta getter i którą modyfikuje setter.


Druga sprawa to typy danych parametrów indeksowanej właściwości. Nie muszą to być indeksy tego samego typu, bo tak jak napisałem wyżej, właściwość indeksowana to tylko wrapper na konkretne metody. Skoro metoda może posiadać parametry różnego typu, to i właściwość może przyjmować indeksy różnego typu.

Drugi przykład:

type
  TSome = class(TObject)
  private
    function GetFoo(A: Integer; B: Boolean; C: String; D: TClass): Double;
    procedure SetFoo(A: Integer; B: Boolean; C: String; D: TClass; AValue: Double);
  public
    property Foo[A: Integer; B: Boolean; C: String; D: TClass]: Double read GetFoo write SetFoo;
  end;

Indeksy właściwości są cztery, każdy innego typu – nie ma problemu. I przykładowe wywołanie:

var
  Some: TSome;
begin
  Some := TSome.Create();
  Some.Foo[8, False, 'furious', TStringList] := 3.14;

Parametr indeksowanej właściwości może być też rekordem, a nawet macierzą (a co!):

type
  TCaptions = array of String;

type
  TSome = class(TObject)
  private
    function GetFoo(A: String; const B: TCaptions): Double;
    procedure SetFoo(A: String; const B: TCaptions; AValue: Double);
  public
    property Foo[A: String; const B: TCaptions]: Double read GetFoo write SetFoo;
  end;

Przykład użycia:

var
  Some: TSome;
begin
  Some := TSome.Create();
  Some.Foo['furious', TCaptions.Create('foo', 'bar', 'baz')] := 3.14;

Do tej pory trzeba było pisać nazwę właściwości – tu: Foo – aby móc z niej skorzystać. Żeby tego nie robić, wystarczy dodać słówko default na końcu deklaracji właściwości, np.:

property Foo[A, B, C, D, E, F, G: Integer]: Integer read GetFoo write SetFoo; default;

Dzięki temu nawiasy do podawania indeksów mogą teraz być pisane tuż po identyfikatorze zmiennej:

var
  Some: TSome;
begin
  Some := TSome.Create();
  Some[0, 1, 2, 3, 4, 5, 6] := 100;

Należy pamiętać, że tylko jedna właściwość w klasie może być domyślną, a także o tym, że default ma więcej znaczeń, co zależy od kontekstu. Dzięki niemu możliwe jest też definiowanie domyślnej wartości parametru metody czy właściwości upublicznionej (co ma związek z zasobami formularzy, ale nie ma związku z problemem poruszanym w tym wątku, więc o tym kiedy indziej).


Można powiedzieć, że właściwości w obiektowym Pascalu są jednymi z najbardziej (jeśli nie najbardziej) rozbudowanymi i funkcjonalnymi konstrukcjami spośród wszystkich języków programowania. Indeksowanie właściwości to tylko jedna z wielu fajnych i przydatnych rzeczy – jest jeszcze wiele innych ciekawych konstrukcji.

Trzeba jednak pamiętać, że rozbudowane właściwości są możliwością, a nie koniecznością. Nikt nie lubi skoplikowanego kodu, więc należy unikać przesadyzmu.

0

Ponownie dzięki za pomoc.

A jak to wygląda z odśmiecaniem w Delphi? @babubabu słusznie zwrócił uwagę na wyciek, bo takie każdorazowe wywoływanie klasy chyba faktycznie niepotrzebnie będzie zajmować pamięć... czy nie? Delphi ma jakiś odśmiecacz automatycznie czyszczący takie nieużytki, czy trzeba to robić ręcznie?

Dla pewności przerobiłem kod, dzięki czemu zamiast klasy, w parametrze funkcji podaję po prostu inną funkcję (tworzącą potrzebną mi w danym momencie macierz). Z tego co wiem, po zakończeniu działania funkcji result powinien automatycznie zostać wywalony z pamięci, czyli nie zostanie syf. Trochę inaczej, ale mam co chciałem :).

Tak swoją drogą, przydałoby się dodać do kompendium coś o właściwościach indeksowanych, bo nie ma tam ani słowa.

0
Crow napisał(a):

A jak to wygląda z odśmiecaniem w Delphi? […] Delphi ma jakiś odśmiecacz automatycznie czyszczący takie nieużytki, czy trzeba to robić ręcznie?

Zasada jest prosta – jeśli sam zaalokowałeś pamięć, to musisz ją też sam zwolnić. Wyjątkiem są typy proste, macierze, struktury, interfejsy oraz komponenty posiadające rodzica. Natomiast tworzone instancje klas muszą być zwalniane za pomocą Free (lub Destroy, ale tego się nie stosuje).

Dla pewności przerobiłem kod […]

Unikaj tworzenia instancji klas wewnątrz metod i ich zwracania na zewnątrz – łatwo się w tym pogubić. Staraj się zawsze tworzyć obiekty i przekazywać referencje do metod. W ten sposób unikniesz wycieków pamięci i problemów z czytelnością kodu.

Tak swoją drogą, przydałoby się dodać do kompendium coś o właściwościach indeksowanych, bo nie ma tam ani słowa.

Używaj dokumentacji dostarczonej wraz ze środowiskiem, bo ta zawiera wszystkie potrzebne informacje i są one aktualne dla używanej wersji języka i bibliotek. Kompendium niestety ale jest stare i zapomniane.

0

Trochę niezwiązane z tematem, ale nawiązujące do naszej rozmowy. Jak wygląda kwestia zarządzania pamięcią przy użyciu zmiennych prostych (class var) w rekordach? Mam coś takiego (to tylko przykład):

type
TData = record
class var Value: Integer;
class function Create(const zValue: Integer): TData; static;
end;

class function TData.Create;
begin
Value:= zValue;
end;

var
Data: TData;

begin
Data:= TData.Create(150);
end;

Gdy już taki rekord przestanie mi być potrzebny, to jak mam się go pozbyć z pamięci? Albo jak pozbywać się z pamięci nieużywanych już zmiennych class var? Powinienem napisać jakaś procedurę czyszczącą, która wszystko wyzeruje ("wyniluje")? A może lepiej korzystać z klas? Nie ukrywam, że w tej konstrukcji wzorowałem się na System.Math.Vectors, w którym zastosowano takie właśnie rozwiązanie (to znaczy rekordy z metodami i zmiennymi prostymi), ale może to nie jest rozwiązanie optymalne?

Bez class var się nie obejdzie, bo metody proste w rekordach muszą być statyczne, a static nie ma dostępu do elementów swojej klasy. Metoda z kolei musi być prosta, bo rekordy nie pozwalają na tworzenie zwykłych metod z referencją do samych siebie...

0
Crow napisał(a):

Jak wygląda kwestia zarządzania pamięcią przy użyciu zmiennych prostych (class var) w rekordach?

Tak samo jak w przypadku innych typów zarządzanych – samo się alokuje i dealokuje. Standardowe rekodry są zarządzane, typy proste jak Integer też.

Gdy już taki rekord przestanie mi być potrzebny, to jak mam się go pozbyć z pamięci?

Nie możesz się go pozbyć – on sobie będzie istnieć w pamięci dotąd, aż wykonanie kodu wyjdzie poza zakres widoczności takiej zmiennej (lokalnej). Jeśli owa zmienna jest globalna, to istnieje w pamięci przez cały czas życia procesu. W końcu po to jest zarządzana, aby się nią nie przejmować.

Albo jak pozbywać się z pamięci nieużywanych już zmiennych class var?

Też nijak. Typ danych – a raczej informacje o nim – istnieje przez cały czas życia procesu. Jeśli taki typ zawiera zmienną klasową, to ona też żyje sobie razem z nim – w końcu takie są założenia. Po co chcesz je usuwać?

Powinienem napisać jakaś procedurę czyszczącą, która wszystko wyzeruje ("wyniluje")?

Jeśli używasz referencji lub wskaźników przechowujących dane alokowane ręcznie, to tak – w przeciwnym razie spowodujesz wycieki pamięci. To co chcesz użyć (te dziwne struktury) nie wymagają czyszczenia czy dealokacji.

A może lepiej korzystać z klas?

Do tej pory nie pytałem o to w jakim celu upychasz te dane w rekordach, ale jeśli nie musisz ich używać, to skorzystaj z normalnych klas. A jeśli z jakiegoś powodu nie chcesz samemu tworzyć i zwalniać instancji, to skorzystaj z klasycznych obiektów.

Te obiekty też są zarządane, więc odpada konieczność ich tworzenia i zwalniania. Tak jak struktury standardowo alokowane są na stosie, a dodatkowo wspierają dziedziczenie, hermetyzację i polimorfizm. Stare obiekty są protoplastą współczesnych klas i posiadają większą funkcjonalność niż rekordy (nawet te zaawansowane).

Szczerze pisząc koncepcja zaawansowanego rekordu to jakiś wypierd (po mojemu) będący rozpaczliwą próbą przeniesienia funkcjonalności starych obiektów do rekordów i odseparowania terminu ”obiekt” od jego pierwotnej postaci. Ten smutny żart ze strony twórców komplikuje relatywnie proste rzeczy i niepotrzebnie wprowadzaja dodatkowe zależności.

Metoda z kolei musi być prosta, bo rekordy nie pozwalają na tworzenie zwykłych metod z referencją do samych siebie...

Nie za bardzo rozumiem o czym piszesz, więc przykład jakiś by się przydał.

1 użytkowników online, w tym zalogowanych: 0, gości: 1