Zwracanie wskaźnika przez funkcję

0

Cześć.

Mam problem ze zwracaniem przez funkcję wskaźnika. Chciałbym generalnie w funkcjo tworzyć obiekt danej klasy, a potem zwracać wskaźnik na ten obiekt, żebym mógł go wpisać do globalnej kolekcji tych obiektów i usuwać je dopiero przy wyłączeniu programu w destruktorze głównego obiektu. Próbuję zadeklarować klase, typ wskaźnikowy na tą klasę i buduję funkcję która zwraca mi wskaźnik na obiekt tej klasy jako metodę innej klasy i niestety mi sie sypie. Zamieszczam uproszczony kod wraz z błędem. Proszę bardzo o pomoc i przepraszam, za lamerstwo.

type
  PName = ^TName;
  TName = class(TObject)
    public
      Name :string;
      Sex :string;
      Pupular :integer;
      constructor Create(pname:string;psex:string;ppopular:integer);
  end;      
 function TBaza.ParsLineToName(line: string): PName;
var
 ...
begin

  ...

  ParsLineToName := @TName.Create(name,sex,popular);
end;

Error:

 imiona.pas(102,21) Error: Incompatible types: got "<address of function(AnsiString,AnsiString,LongInt):TName of object;Register>" expected "PName"

Nie wklejam całości kodu więc powien, że linia 102 to dokładnie

ParsLineToName := @TName.Create(name,sex,popular);

Dziwi mnie to gdyż jak patrzę w Reference Guide to mam przykład:

 var  p1,p2 : ^Longint;  
     L : Longint;  
begin  
  P1 := @P2;  
  P2 := @L;  
  L := P1-P2;  
  P1 := P1-4;  
  P2 := P2+4;  
end. 

Proszę o diagnozę mojego problemu i pomoc. Z góry dziękuję za zainteresowanie.

1

Ale Ty robisz jakieś dziwne rzeczy z tym konstruktorem... W przykładzie który przytoczyłeś, jest pokazane jak bawić się zwykłymi wskaźnikami, a u Ciebie kombinujesz coś z pointerem na konstruktor, o czym powiadamia Cię błąd kompilacji;

Konstruktor klasy nie może być funkcją, więc i nie może nic zwracać, stąd pobierany jest adres metody (konstruktora), a nie tego co niejawnie zwraca; Więcej byś zdziałał, gdybyś normalnie obsługiwał referencje do obiektów, które de facto są wskaźnikami;

Poza tym Twoja klasa jest jakaś pokraczna... Wszystko jest publiczne, a nie powinno; Zapis klasy powinien wyglądać mniej więcej tak:

type
  TName = class(TObject)
  private
    FName: String;
    FSex: String;
    FPopular: Integer;
  public
    constructor Create(AName, ASex: String; APopular: Integer);
  public
    property Name: String read FName write FName;
    property Sex: String read FSex write FSex;
    property Popular: Integer read FPopular write FPopular;
  end;

ewentualnie z ograniczonym lub bardziej rozwiniętym dostępem do pól; No i kłania się nazewnictwo argumentów - przyjęło się, aby argumenty miały poprzedzającą literkę A;

A co do samej funkcji, to proponuję taką konstrukcję:

function TBaza.ParseLineToName(ALine: String): TName;
begin
  {...}

  Result := TName.Create({...});
end;

BTW: Po pierwsze Pars__e__, a po drugie jeśli już korzystasz z angielskich identyfikatorów, to wszędzie; Klasa ma identyfikator TBaza, czyli polski, a metody, argumenty czy składowe mają już nazwy angielskie.

2
result:= PName(TName.Create(name,sex,popular));

lub

ParsLineToName:= PName(TName.Create(name,sex,popular));
0

Dziękuję za odpowiedź. Przyznam się, że faktycznie klasa jest pokraczna i w zasadzie to mógłby być record, ale nie wiem, czy podczas rozwoju nie będę musiał tam jakiejś funkcji jeszcze włożyć. Tak więc, dzięki za radę nt. klasy, jednak to nie rozwiązuje mojego problemu. Możesz w takim razie powiedzieć, jak mogę skonstruować obiekt i zwrócić na niego wskaźnik, żeby po zakończeniu metody nadal żył w programie ? (nie widziałem, że poprzednik edytował post). Teraz widzę, że odwoływałem się do wskaźnika na konstruktor, a chciałbym do wskaźnika na obiekt stworzony przez ów konstruktor.

Dzięki, za pomoc.

P.S.
Skąd wzięły się prefiksy A przed argumentami ? Bo przedrostki T podobno od Template, czy Type ? ... Swoją drogą, jaka jest konwencja zwracania wartości z funkcji ? Przez <NazwaFunkcji> , Result , czy może Exit(<wartość>) ??

--Edyta--

Jak widzę, to ubiegliście mój post nowym postem oraz edycją, poprzedniego, ale proszę jeszcze o wypowiedź nt. konwencji zwracania wartości. Dzięki za pomoc.

1

Przyznam się, że faktycznie klasa jest pokraczna i w zasadzie to mógłby być record, ale nie wiem, czy podczas rozwoju nie będę musiał tam jakiejś funkcji jeszcze włożyć.

Czyli kod nie jest zaprojektowany w ogóle :]

Wskaźniki na rekordy mają sens, ale wskaźniki na obiekty już nie bardzo; To co niejawnie zwraca konstruktor już jest wskaźnikiem, więc tworzyłbyś wskaźnik na wskaźnik, a to raczej w Twoim przypadku nie ma sensu, dlatego wnioskowałem o zastanowienie się i skorzystanie po prostu z listy obiektów, a nie pointerów na nie;

Skąd wzięły się prefiksy A przed argumentami ? Bo przedrostki T podobno od Template, czy Type ? ... Swoją drogą, jaka jest konwencja zwracania wartości z funkcji ? Przez <nazwafunkcji> , Result , czy może Exit(<wartość>) ??

Prefiksy wzięły się zapewne z praktyki; Służą one do zwiększania czytelności kodu i dają możliwość nazywania różnych elementów tak samo (coś jak notacja węgierska);

Weźmy na przykład nazwę Person - prywatne pole klasy będzie się nazywać FPerson, argument konstruktora czy innych metod APerson, a właściwość dająca dostęp do pola FPerson to po prostu Person; Wszędzie jest Person, ale każdy służy do czegoś innego i prefiksem informuje o swoim przeznaczeniu;

Co do konwencji modyfikacji rezultatu funkcji czy metod funkcyjnych, to wymienione przykłady nie są sobie równe; Przypisanie wartości do nazwy funkcji czy metody to stara technika rodem ze starego Pascala, gdzie nie było ukrytej zmiennej Result; Potem ją wprowadzono, aby dać możliwość bawienia się na dodatkowej zmiennej i uprościć zapis; Zobacz na poniższe przykłady:

function TSomeClass.ThisIsTheVeryLongMethodName(const AParam: Integer): Integer;
begin
  ThisIsTheVeryLongMethodName := SomeValue;
end;

function TSomeClass.ThisIsTheVeryLongMethodName(const AParam: Integer): Integer;
begin
  Result := SomeValue;
end;

Który jest czytelniejszy? Oczywiście drugi, a jeśli przypisanie wartości do rezultatu będzie mocno zagnieżdżone (z wielokrotnym wcięciem), to wykorzystanie zmiennej Result pozwoli skrócić taką linijkę; Ta zmienna przydaje się też w innych przypadkach, np. do inicjalizacji rezultatu; Dzięki temu nie musimy trzymać tymczasowego wyniku funkcji w dodatkowej zmiennej, jak ma to miejsce w językach z rodziny C;

Ostatni przykład, czyli procedura Exit ma zupełnie inne znaczenie; Różnica jest taka, że ze zmiennej Result można korzystać tak jak z każdej innej zmiennej (przypisanie wartości nie powoduje opuszczenia funkcji), a procedura Exit z parametrem powoduje przypisanie wartości do rezultatu oraz opuszczenie funkcji; Czyli ta procedura działa tak samo jak skorzystanie z cplusplusowatego return; W starym Delphi procedura Exit nie mogła posiadać parametru - FPC natomiast wspiera taki zapis (być może nowe Delphi też, ale nie pracuję z nimi);

Jak widzisz przeznaczenie jest różne, więc trzeba dobierać zapis do konkretnego przypadku, nie robiąc z tego reguły.

0

Ok, rozumiem. W takim razie Result skoro jest zmienną to czy nazwa funkcji też nią jest i czy różnią się jedynie nazwą ??

Co do samego problemu - jeżeli zastosuję Twoją propozyjce, czyli list obiektów, a nie listę wskaźników na obiekty, to muszę je później usuwać w destruktorze klasy zawierającej ową listę ??

Dodatkowo, czy poprawnym jest powołanie obiektu, zwracanie wskaźnika na ten obiekt, a potem wyłuskanie tego obiektu spod wskaźnika i dodanie go np. do listy czy tablicy ? Tak przynajmniej się uczyłem na kursie C++ na uczelni. Czy raczej od razu zwracać obiekt i go dodawać do listy ?

Co do projektu - przyjąłem że testować będę metodykę - zaimplementuj troszkę, sprawdź jak działa, czy działa i jaki jest kod. Jeżeli jeden z 3 etapów nie daje dobrego wyniku to refaktoryzuj, i tak teraz przerabiam już 4 raz mój program, przy czym na początku funkcja wczytująca obiekty z pliku miała ponad 200 lini, teraz rozbiłem ją na 3 inne i troszkę zapętliłem, więc metodyka chyba działa. Z reguły projektując, przy okazji rysując diagramy w WhiteStarUML, albo zbyt wiele czasu poświęcałem, na przemyślenie wszystkiego, albo podczas imolementacji okazywało sie, że to jest kiepskie i trzeba przeprojektować. Idąc takimi iteracjami, mam wrażenie, że szybciej i efektywniej koduję, przy czym mam jakieś tam założenia projektu, które są modyfikowane po każdej iteracji, jak i dokumentowane zmiany i kod są podczas implementacji, a raczej po testach implementacyjnych przed kolejną pętlą. Niemniej, chyba faktycznie zastosuje w tym meiejscu rekord zamiast obiektu. Z drugiej strony - czy mogę tworzyć recordy, tak jak obiekty i je zwracać z funkcji ?

1

W takim razie Result skoro jest zmienną to czy nazwa funkcji też nią jest i czy różnią się jedynie nazwą ??

To raczej jest kwestia interpretacji kodu przez kombilator;

Co do samego problemu - jeżeli zastosuję Twoją propozyjce, czyli list obiektów, a nie listę wskaźników na obiekty, to muszę je później usuwać w destruktorze klasy zawierającej ową listę ??

Oczywiście że tak, jeśli nie chcesz generować wycieków pamięci; Zasada jest prosta - to co alokujesz, na koniec dealokuj; Jeżeli tworzysz obiekty dynamicznie, to trzeba je w jakimś momencie zwolnić z pamięci (najlepiej w momencie gdy nie są już potrzebne, a nie sztywno przy zamykaniu aplikacji); Pamiętaj, że FPC nie posiada śmieciarki, więc ze zwalniania dynamicznie alokowanej pamięci nikt ani nic Cię nie wyręczy (i dobrze);

Dodatkowo, czy poprawnym jest powołanie obiektu, zwracanie wskaźnika na ten obiekt, a potem wyłuskanie tego obiektu spod wskaźnika i dodanie go np. do listy czy tablicy ?

Ale masz w tym jakiś szczególny cel, czy po prostu tak chcesz zrobić, bo tak było w C++? Zauważ, że te dwa języki wiele różni, więc raczej nie brałbym przykładu z C++, pisząc w Pascalu, Obiect Pascalu czy Delphi;

Sprawa jest prosta - dostęp do utworzonej instancji klasy w pamięci uzyskujesz przez niejawny wskaźnik, więc to powinno Ci wystarczyć; Zawsze jednak są wyjątki - nawet w źródłach biblioteki standardowej dla FPC można znaleźć wskaźniki na wskaźniki na wskaźniki - nie dociekałem po co to;

Czy raczej od razu zwracać obiekt i go dodawać do listy ?

Jeśli nie pracujesz nad jakimś turbo WTFem, to wystarczy trzymać to, co niejawnie zwraca konstruktor klasy;

W mojej bibliotece do obsługi plików TreeStructInfo korzystam z klasy przechowującej obiekty elementów drzew; Typ TTSInfoElementsList jest to w skrócie zwykła macierz typu array of TObject, gdzie każdy element listy zajmuje SizeOf(TObject) bajtów (na moim systemie 4 bajty); Nie ma żadnego sensu w tworzeniu wskaźników na te elementy list, skoro one same w sobie są wskaźnikami na zaalokowaną pamięć dla obiektów;

W takim przypadku w destruktorze wystarczy zwalniać pamięć po elementach list, czyli wywołując metody Free, bez innych zabaw; Jeżeli trzymałbyś wskaźniki na miejsca, do których przekazywano utworzone obiekty, to wskaźników nie musiałbyś zwalniać (to zrobi automat), ale to co znajduje się pod wskaźnikami już tak;

[...] i tak teraz przerabiam już 4 raz mój program [...]

I nie martwi Cię to, że już czwarty raz podchodzisz do tego samego algorytmu? Ja rozumiem, że w parktyce wszystko wychodzi najlepiej, ale dobrze by było się najpierw zastanowić nad kodem, a dopiero później zacząć klepać; A tak to robisz jeden krok do przodu, a następnie cztery do tyłu;

Z reguły projektując, przy okazji rysując diagramy w WhiteStarUML, albo zbyt wiele czasu poświęcałem, na przemyślenie wszystkiego, albo podczas imolementacji okazywało sie, że to jest kiepskie i trzeba przeprojektować.

To raczej normalne, że coś się projektuje, a następnie w praktyce trzeba stosować nieco obejść i implementować po części niezgodnie z projektem; Tym raczej się nie przejmuj i nie twórz diagramów zbyt skrupulatnie, bo stracisz tylko czas; Diagramy mają pomagać w stworzeniu programu, a nie być jego wierną graficzną reprezentacją;

Niemniej, chyba faktycznie zastosuje w tym meiejscu rekord zamiast obiektu.

Jeżeli masz dużą pewność, że obiekty nie będą konieczne w późniejszych etapach, np. w końcowym stadiom implementacji lub w już procesie utrzymywania kodu, to śmiało możesz skorzystać z rekordów; Jednak korzystanie z klas wcale nie jest nie wiadomo jak trudniejsze czy wolniejsze w porównaniu do rekordów, więc raczej nic nie stracisz, jeśli od razu zajmiesz się klasami; Wręcz przeciwnie - więcej możesz zyskać niż stracić;

Z drugiej strony - czy mogę tworzyć recordy, tak jak obiekty i je zwracać z funkcji ?

Zależy co masz na myśli;

Tworzyć rekordy i zwracać je w funkcji oczywiście możesz, nie tylko jako wskaźniki, ale także jako całe struktury; Możesz zwrócić za pomocą funkcji pointer na rekord, np. wspomnianego typu PName, albo cały rekord typu TName; Natomiast w przypadku obiektów takiego wyboru już nie masz - zwracasz referencję do zaalokowanej pamięci dla obiektu, czyli niejawny wskaźnik;

Jak bardzo Cię to interesuje to możesz nawet zwracać blok pamięci jaki okupuje utworzony obiekt, ale to raczej w ramach ćwiczeń czy zaspokojenia ciekawości, a nie jako warta stosowania technika.

1

Konwencja zwracania wyniku funkcji przez jej nazwę, jest konieczna dla FPC bez dyrektywy kompilatora $Mode Delphi. Nazwa funkcji możę być zmieną wymikową, tylko w jej ciele. Nakwygodniej stosować Result i nie wnikać. Bo co jak zechcesz poprawić nazwę funkcji, będziesz za kazdym razem robił znajdź i zamień w tekstcie?

Co do zwalniania w destruktorze, oczywiście powiniwneś zwolnić wcześniej tworzone obiekty "pomocnicze". Generalnie zasada jest taka, to co utworzyłeś, powinieneś zwolnić, gdy już jest niepotrzebne.

Edit: FP był szybszy, nie umiem szybko pisać na dotykowej :-)

1
olesio napisał(a)

Nakwygodniej stosować Result i nie wnikać. Bo co jak zechcesz poprawić nazwę funkcji, będziesz za kazdym razem robił znajdź i zamień w tekstcie?

Heh, miałem ten sam problem podczas pracy w Delphi7...

Ale tu niespodzianka - w IDE Lazarusa jest opcja Rename Identifier pod klawiszem F2; Wystarczy wcisnąć, zmienić nazwę i zatwierdzić, a wszelkie wystąpienia danego identyfikatora zostaną poprawione; Oczywiście brana jest pod uwagę widoczność danego elementu, więc ta opcja raczej szkód nie narobi :]

0

Dzięki kolegom za wyczerpujące odpowiedzi. Mam jeszcze jakieś nawyki z C/C++, jak widać, te języki nieco inaczej traktują operacje na obiektach. à propos wskaźnika na wskaźnik na wskaźnik, od razu co mi przyszło do głowy to lista macierzy - sztampowe zadanie z C++ na studiach ;) Ciekawa uwaga à propos konieczności zmiany nazw funkcji późniejszych problemów z refaktoryzacją. Co to kiepsko zaprojektowanej aplikacji - nie zmieniam całego, kodu czy nawet zamysłu tylko refaktoryzuje najbardziej badziewne kawałki. Program napisany pod wpływem emocji. Działa. Co do trzymania obiektów w pamięci przez całe życie programu - niestety jest to program mający spełniać jedną funkcjonalność i obiekty te są niezbędne. Kolejne programy będę bardziej projektował, ten chce doprowadzić do stanu, że będę z niego zadowolony. Generalnie czeka mnie jeszcze kompilacja na Androida, ale to dopiero jak otrzymam zadowalającą jakość kodu i działania.

Możecie coś napisać, jeszcze o zwracaniu bloku pamięci jako obiekt klasy ? Domyślam, się, że pewnie będzie to jakiś wskaźnik typu void i późniejsze rzutowanie pasma pamięci ??

0

à propos wskaźnika na wskaźnik na wskaźnik, od razu co mi przyszło do głowy to lista macierzy - sztampowe zadanie z C++ na studiach ;)

Ale to było zadanie z C++; Widziałem gdzieś w bibliotece standardowej "potrójny" wskaźnik, ale nie mogę teraz tego znaleźć ani przypomnieć sobie do czego on służył i gdzie był wykorzystywany; Coś na tej zasadzie:

type
  TSomeType = SimpleType; // nie pamiętam jaki był typ...

type
  PSomeType   = ^TSomeType;
  PPSomeType  = ^PSomeType;
  PPPSomeType = ^PPSomeType;

Program napisany pod wpływem emocji.

Brzmi dość egzotycznie :]

Co do trzymania obiektów w pamięci przez całe życie programu - niestety jest to program mający spełniać jedną funkcjonalność i obiekty te są niezbędne.

Dlaczego "niestety"? Skoro są one potrzebne bo program z nich korzysta, to nie możesz ich zwolnić zbyt wcześnie.

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