polimorfim i dziedziczenie a dostęp do pól składowych

0

Witam wszystkich,

Trochę zgłupiałam troszeczkę. Zagadnienie dość banalne, a jednak byłam przekonana, że działa to inaczej... Stąd fajnie by było, gdyby ktoś mi podpowiedział jak to działa (chociaż to już raczej wiem, bardziej DLACZEGO to tak działa). Załóżmy prosty przypadek kodu:

class Zwierze{
public:
int paszcza;
virtual void dajGlos(){cout<<"klapanie paszcza"<<endl;}
};

class Pies : public Zwierze{
public:
int nogi;
void dajGlos(){cout<<"hau hau"<<endl;}
};

int main(){
Zwierze* z1 = new Pies();
z1->dajGlos();
z1->nogi = 4;
}

Oczywiście* dajGlos()* działa jak trzeba, ale dostać się do * nóg" * już nie mogę... Czy nie taka właśnie jest idea dziedziczenia, aby "rozszerzać" klasy bazowe? Oczywiście, mogę napisać bez problemu:

Pies p1 = new Pies();
p1->dajGlos();
p1->nogi = 4;

Ale w ten sposób nie mogę skorzystać z dobrodziejstw polimorfizmu... Czy naprawdę jedno wyklucza drugie?

Może mi ktoś powiedzieć, gdzie w moim rozumowaniu jest błąd?

Pozdrawiam!
Malvinka

0

Polimorfizm działa dla METOD a nie dla pól. Co jest chyba dość oczywiste... Bo idea jest taka że pewne typy mogą różnić się zachowaniem, ale przejawiać takie same rodzaje zachowań. Tzn np. Pies może dać głos i krowa też. Ale strukturalnie takiej zależności nie ma. Krowa ma wymiona a pies nie ma!
A bardziej realnie: na przykład List (taka struktura danych) ma metody add() czy remove() i dziedziczące z niej LinkedList (lista wiązana) i ArrayList (lista na tablicy) też mają takie zachowania. Ale nie możesz wymagać żeby LinkedList miała tablicę z elementami!

Twój błąd polega też na tym że masz wskaźnik do Zwierzęcia i możesz w efekcie korzystać tylko z rzeczy które deklaruje Zwierze. Możesz korzystać z jego metod i jego pól. Ale nawet jeśli przypiszesz do wskaźnika na Zwierze Psa to nadal masz dostęp tylko do pól i metod Zwierzęcia (chociaż polimorfizm sprawia że wywoływane metody pochodzą z klasy dziedziczącej, ale zauważ że działa to tylko dla metod deklarowanych przez Zwierze!)

0

Dziękuję za szybką odpowiedź!

To, że wskaźnik był na Zwierzę miało być właśnie celowe! Chciałabym móc dynamicznie zdecydować jakiego typu jest mój obiekt i korzystać z pól typowych dla niego właśnie, a jednocześnie korzystać z polimorfizmu. Na przykładzie z listami:
Zadeklarować sobie listę (tudzież wskaźnik na listę) List lista;
Dopiero w trakcie trwania programu zdecydować, że będzie to akurat ArrayList (czyli dynamicznie), a nie LinkedList (albo na odwrót)
Odwołać się do elementów tablicy z ArrayList właśnie (a jednocześnie móc skorzystać z metody add() właściwej również dla ArrayList).

Jak to rozwiązać? Muszę stworzyć po prostu nowy obiekt ArrayList?

0

@kjut nic nie rozumiesz. Możesz odnosić się tylko do implementacji METOD specyficznych dla klasy pochodnej. Koniec, kropka. Możesz odnosić się do DANYCH w liście za pomocą METOD na przykład metody get(x) albo pop().

0

No nie rozumiem, dlatego pytam! :)

Przecież mogę zrobić tak (wracając do przykładu z mojego pierwszego postu, ze Zwierzem i Psem)

Pies p1;
p1.paszcza = PASZCZA_Z_KLAMI;    //to wzięłam z klasy bazowej
p1.nogi = 4;      //to wzięłam z klasy pochodnej

Ale chciałabym móc zdecydować o tym, że to akurat Pies, a nie np. Wąż dynamicznie! A więc chciałabym móc napisać:

Zwierze* z1;
//dalej w programie
//...
//
z1 = new Pies();
z1->paszcza = PASZCZA_Z_KLAMI;   //to wziąć z klasy bazowej Zwierze
z1->nogi = 4;    //a to z Psa

Rozumiem, że nie mogę. Teraz chciałabym wiedzieć DLACZEGO.

0

Zresztą zakładając, że klasa Zwierze NIE MA metody ileNog(), a klasa Pies takową posiada, również NIE MOGĘ napisać:

Zwierze* z1 = new Pies();
z1->ileNog();

Chodzi mi o mechanizm, nie wiem... kompilatora? O to jak to działa od strony komputera ;)

0

Właściwie to możesz: ((Pies*)z1)->nogi=4;
A nie możesz bezpośrednio ponieważ z1 to wskaźnik na zwierze które z kolei niekoniecznie może mieć te nogi.

0

Czyli mechanizm działa tak:

Kompilator stwierdza, że z1 to Zwierze i że chcę sprawdzić mu nogi, bo to, że dynamicznie sobie do nich przypiszę Psa to się okaże potem i kompilator o tym nie wie. Kompilator analizuje moje z1->nogi jako coś co musi działać zawsze, bez względu na to, co się dzieje w programie.

Jeśli się nie mylę w powyższym to teraz rozumiem. Dziękuję wszystkim!

0

Zasadniczo tak. Kompilator nie jest genialny. On jeśli w klasach masz metody wirtualne to robi sobie takie specjalne tablice metod wirtualnych. W efekcie nawet jeśli używasz wskaźnika na klasę bazową to on widzi w tej klasie tablicę z metodami które ma wywołać (i w klasie pochodnej są to metody z klasy pochodnej).
Zresztą MYŚL! Co by się miało stać gdybyś zrobiła tak:

Zwierze* zwierze = new Waz();
zwierze->nogi = 4;

? albo tak:

Zwierze* zwierze = new Waz();
zwierze->pokazNogi();

?
Jakiego byś sie spodziewała efektu?

0

Nie odbierajcie tego znowu jako propagandę. Ale jeśli autorka tematu tego nie wie i chciałaby się dowiedzieć, to informuję ją o tym ;)

Otóż, spróbuj sobie ten przykład napisać w języku Python. Masz tam dużo bardziej intuicyjną i elastyczną składnię, która pozwala na to, na co C++ Ci teraz nie pozwala ;) I skorzystasz w pełni z dobrodziejstw polimorfizmu.
"Nieczytelne" (nieumiejętne) korzystanie może prowadzić do wielkiego zamotania kodu, ale cóż... coś za coś ;)

0

Polimorfizm wiąże się ze słowem kluczowym virtual. Słowo to można zastosować względem metod a nie zmiennych klasy. Co za tym idzie w klasach pochodnych można przesłaniać jedynie rzeczy, które są opatrzone słowem virtual - czyli tylko i wyłącznie metody.

Drugą sprawą są zmienne w zakresie public - nie powinno się tego robić. Zmienne powinny być w sekcji private lub protected, pobieranie tych zmiennych powinno być udostępnione za pomocą metod umieszczonych w public.

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