Tablica klas a ich konstruktor

dział ogólny

Re: Tablica klas a ich konstruktor

Nowy postprzez kinio » środa, 17 grudnia 2008, 16:26

Witam!

Witold napisał(a):Kinio dodałeś kostruktor TGlowna::TGlowna(AnsiString __path) i do rzutowania nie trzeba operatora dla AnsiString’a. (gdyby był byłaby chyba niejednoznaczność). "plik1.txt" nie jest typu AnsiString . A TGlowna("plik1.txt") też chyba można traktować jako rzutowanie (styl c funkcyjny tak jak int i = int(7.2))

Więc tak, po pierwsze trzeba sobie uświadomić czym się różni konwersja od rzutowania: rzutowanie jest jawną próbą zmiany typu, natomiast konwersja zachodzi automatycznie. Czyli rzutowanie jest wymuszoną konwersją.
Mając np.:
Kod: Zaznacz cały
int a = 12;
double d = 13.2;
double e = d+a;

najprawdopodobniej w ostatniej linijce nastąpi niejawne rzutowanie (konwersja) na typu int na typ double. Jednak my możemy chcieć czegoś innego: aby w zmiennej e była przechowywana wartość całkowita działania ale jako typ double wtedy trzeba wykonać rzutowanie:
Kod: Zaznacz cały
double e = (int)(d+a)

jednak zaraz po naszym rzutowaniu nastąpi konwersja na typ double, ale już z typu int więc e będzie równe 25.

Tak więc na tę drobnostkę którą znalazłeś mogę dopowiedzieć że aby rzutować, czyli użyć jawnie operatora rzutowania trzeba go zdefiniować w klasie AnsiString w sposób jaki pokazałem wcześniej, ponieważ dla klasy TGlowna raczej nie będzie go domyślnie zdefiniowanego:
Kod: Zaznacz cały
AnsiString operator TGlowna(void);

Dodatkowo to że dodałem konstruktor kopiujący: TGlowna::TGlowna(AnsiString __path) nie eliminuje konieczności definicji operatora rzutowania. Jak słusznie zauważyłeś "plik1.txt" nie jest typu AnsiString tylko typu char* a mimo wszystko istnieje możliwość zapisu:
Kod: Zaznacz cały
AnsiString a = "cos tam";

Ten zapis jest możliwy dzięki temu że gdzieś w klasie AnsiString jest zdefiniowany konstruktor typu: AnsiString(char*).
Tak więc podobny zapis z użyciem klasy TGlowna:
Kod: Zaznacz cały
TGlowna a[] = {"plik1.txt", "plik2.txt"};

nie będzie możliwy i podczas kompilacji pojawią się błędy ponieważ tutaj kompilator budując drzewo syntaktyczne musiałby zastosować dwie konwersje:
1. typu char* na AnsiString pewnie przy pomocy konstruktora
2. typu AnsiString na TGlowna też przy pomocy konstruktora
Jednak nie jest on tak inteligentny i nie potrafi tego zrobić ponieważ gdyby umiał zrobić dwie konwersje, wtedy powinien też umieć zrobić trzy i więcej. Jednak jeżeli nie było by możliwości konwersji jednego typu na drugi wtedy nasz biedny kompilator próbowałby dopasowywać konstruktory, operaotry rzutowania wszystkich znanych mu aktualnie typów w nieskończoność a i tak by nie znalazł odpowiedniej konwersji. Tak więc możliwa jest tylko jedna konwersja np. w przypadku kiedy mamy odpowiedni konstruktor.
Wobec powyższego zapis:
Kod: Zaznacz cały
TGlowna a[] = {TGlowna("plik1.txt"), TGlowna("plik2.txt")};

jest konieczny do takiej inicjalizacji tablicy ponieważ zapis:
Kod: Zaznacz cały
TGlowna a[] = {"plik1.txt", "plik2.txt"};

nie będzie poprawny z powodu opisanego wcześniej. Natomiast zapisy:
Kod: Zaznacz cały
TGlowna a[] = {(TGlowna)"plik1.txt", (TGlowna)"plik2.txt"};
TGlowna a[] = {TGlowna("plik1.txt"), TGlowna("plik2.txt")};

są obydwa poprawne i tutaj masz rację jeżeli w klasie AnsiString będzie zdefiniowany operator rzutowania oraz w klasie TGlowna konstruktor TGlowna(AnsiString) to wystąpi niejednoznaczność.

Czyli może tak na podsumowanie pierwszej drobnostki:
- o rzutowaniu mówimy jeżeli wykonujemy je przy pomocy operatora rzutowania, który mamy gdzieś zdefiniowany
- raczej nie powinniśmy mówić o rzutowaniu w przypadku kiedy uruchamiany jest konstrutkor
- zapis: TGlowna a = TGlowna("plik"); oraz TGlowna a = (TGlowna)"plik"; będą możliwe obydwa równocześnie przy definicji albo operatora w klasie AnsiString albo konstruktora w klasie TGlowna.
- Definicja operatora operator TGlowna w klasie AnsiString oraz konstruktora TGlowna(AnsiString) nie jest możliwa ze względu na wystąpienie niejednoznaczności!

Co do drugiej drobnostki:
Witold napisał(a):Sprawdziłem - brak wywołań konstruktora kopiującego, wiec chyba niema obiektów tymczasowych.

Tak masz racje, nie ma tam mowy o obiektach chwilowych - aż z ciekawości sprawdziłem

Pozdrawiam!
If a machine is expected to be infallible, it cannot also be intelligent.
-- A.Turing
Avatar użytkownika
kinio
Homos antropiczny
Homos antropiczny
 
Posty: 67
Dołączył(a): poniedziałek, 14 lipca 2008, 08:51
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez Witold » czwartek, 18 grudnia 2008, 11:31

Dzięki za odpowiedz. Trochę się namieszało w ostatnich postach , co innego rzutowanie char* na TGlowna, a co innego AnsiString na TGlowna.

Tego nie rozumiem:
1. TGlowna("plik1.txt") - to jest rzutowanie typu AnsiString na klasę TGlowna. (CB)
2. Nieprawda rzutowanie wyglądało by tak: TGlowna obiekt = (TGlowna)"plik1.txt"
3. i wymagało by takiej definicji w klasie AnsiString: AnsiString::operator TGlowna(void);


ad1: To napisał Cyfrowy Baron.
ad2: rzutowanie char* -> TGlowna, dla którego wystarczy konstruktor TGlowna(AnsiString), bo to chyba jest rzutowanie ? Trzeba podać konwersje jawnie. (char* -> AnsiStrng -> TGlowna(AnsiString))
ad3: nie rozumiem, to nie pasuje do punktu 2: do przeprowadzenia konwersji /rzutowania char* -> TGlowna, ten operator konwersji w AnsiString nie pomoże.

kinio napisał(a): raczej nie powinniśmy mówić o rzutowaniu w przypadku kiedy uruchamiany jest konstrutkor


W c++ używa się terminu: konstruktor konwertujący http://pl.wikipedia.org/wiki/Konstruktor_(programowanie_obiektowe)#Konstruktor_konwertuj.C4.85cy_.28C.2B.2B.29

Wydaję mi się że rzutowanie to jawna konwersja, czyli nawet gdy dana konwersja mogłaby być przeprowadzona niejawnie a zrobimy to jawnie będzie rzutowanie np.:
Kod: Zaznacz cały
  AnsiString Costam1 = "ciag znakow"; // konwersja niejawna
  AnsiString Costam2 = static_cast<AnsiString>("ciag znakow"); // rzutowanie
  AnsiString Costam3 = (AnsiString) "ciag znakow"; // rzutowanie
  AnsiString Costam4 = AnsiString("ciag znakow"); // rzutowanie

We wszystkich tych przypadkach konwersja będzie dokonana przy pomocy konstruktora (konwertującego) AnsiString(const char*);
Skoro są używane operatory rzutowania to musi być rzutowanie :)
Avatar użytkownika
Witold
Konstrukcjonista
Konstrukcjonista
 
Posty: 223
Dołączył(a): piątek, 29 sierpnia 2008, 10:53
Podziękował : 1
Otrzymał podziękowań: 14
Kompilator: bcb6, Turbo C++ Explorer
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez Cyfrowy Baron » czwartek, 18 grudnia 2008, 13:17

Wydaję mi się że rzutowanie to jawna konwersja, czyli nawet gdy dana konwersja mogłaby być przeprowadzona niejawnie a zrobimy to jawnie będzie rzutowanie np.:


Rzutowanie to nie konwersja lecz polimorfizm, chyba że rzutujesz jedną zmienną na drugą, wtedy zachodzi konwersja, ale tylko dlatego, że dany typ zmiennej zawiera procedury tej konwersji, czyli wynika to definicji zmiennej, a nie właściwości języka programowania.
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez Witold » piątek, 19 grudnia 2008, 01:12

Cyfrowy Baron napisał(a):Rzutowanie to nie konwersja lecz polimorfizm, chyba że rzutujesz jedną zmienną na drugą, wtedy zachodzi konwersja, ale tylko dlatego, że dany typ zmiennej zawiera procedury tej konwersji, czyli wynika to definicji zmiennej, a nie właściwości języka programowania.


Obawiam się że się mylisz:
Polimorfizm w programowaniu obiektowym
Polimorfizm w programowaniu obiektowym to wykazywanie różnych form działania podczas wywoływania metody w zależności od tego jakiego typu obiekt jest wskazywany przez wskaźnik lub referencję (pomijając typ wskaźnika lub referencji).

Polimorfizm statyczny
to inaczej programowanie generyczne. Jest to stosowalność tej samej procedury/funkcji do różnych typów argumentów, przy czym można statycznie (zazwyczaj w trakcie kompilacji) stwierdzić jaki będzie typ argumentów w trakcie wykonania. Przykładem są wzorce (template) w C++ oraz klasy generyczne w Javie.
http://pl.wikipedia.org/wiki/Polimorfizm_(informatyka)
Avatar użytkownika
Witold
Konstrukcjonista
Konstrukcjonista
 
Posty: 223
Dołączył(a): piątek, 29 sierpnia 2008, 10:53
Podziękował : 1
Otrzymał podziękowań: 14
Kompilator: bcb6, Turbo C++ Explorer
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez Cyfrowy Baron » piątek, 19 grudnia 2008, 10:22

Pisz co chcesz, ale rzutowanie to rodzaj polimorfizmu a nie konwersji.
W konwersji następuje zamiana jednego typu na inny, a w rzutowaniu następuje przypisanie jednego typu do innego, więc trudno mówić tutaj o konwersji.



Oto fragment z książki "Borland C++ Builder" autorstwa Jim Mischel'a i Jef Duntemann'a poświęcony polimorfizmowi:

[...]
Rzutowanie typów stoi za każdym polimorfizmem. Możliwe jest obejrzenie operacji rzutowania typów za pomocą prostego programu, który przekazuje wszystkie kliknięcia w programie za pośrednictwem jednej procedury obsługi zdarzenia OnClick. Ustawia on wówczas właściwość wspólną dla wszystkich obiektów sterujących wykorzystując odniesienie polimorficzne.
[...]
Przypisanie Polimorficzne

Ten rodzaj rzeczy wprawia tradycyjnych programistów piszących w języku C lub Pascal w stan napięcia. W programowaniu w starym stylu jeżłi parametr funkcji jest typu int, to funkcji można przekazywać tylko wartości tego samego typu. Ale jeśli nie jest to liczba całkowita, nie mżna tego zrobić. Zasady programowania obiektowego zmieniają nieco ten stan rzeczy: egzemplarz dowolnej klasy wywodzącej się od TObject można przypisać zmiennej lub parametrowi typu TObject. Przeczytaj to jeszcze raz, albo nawet dwa i trzy razy, bo to ważne.
Pomyśl również o tym: wszystkie obiekty C++ Builder pochodzą od klasy TObject, która jest jako ojciec (albo matka) dla wszystkich klas. Wszystkie więc obiekty są naprawdę obiektami TObject, jak również wszystkim tym, czym one mogą być, to znaczy modelami spłaty pożyczki hipotecznej lub obiektami StringGrid. Ponieważ na dole wewnątrz każdego obiektu są również obiekty TObject, dlatego możesz przypisać obiekt dowolnej klasy obiektowi albo parametrowi typu TObject. Nazywamy to przypisaniem polimorficznym.
[...]


Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez polymorphism » piątek, 19 grudnia 2008, 14:58

Oj baron baron, Witold ma rację, rzutowanie nie jest żadnym polimorfizmem. Jest tylko częścią tego mechanizmu, a nie nim samym. Bo gdyby tak było, to te dwa terminy nie istniałyby w nomenklaturze programistycznej - wystarczyłby jeden.

egzemplarz dowolnej klasy wywodzącej się od TObject można przypisać zmiennej lub parametrowi typu TObject. [...]
Wszystkie więc obiekty są naprawdę obiektami TObject

Tym cytatem (szczególnie ostatnie zdanie) podważyłeś to, o czym piszesz ;) Cytat w skrócie mówi o tym, że między obiektami pochodnymi od TObject a nim samym występuje relacja JEST, czyli możesz powiedzieć, że np. obiekt TForm JEST obiektem TObject. Ale już taki AnsiString nim nie jest i nie możesz go podać jako parametr funkcji, która oczekuje wskaźnika/referencji na TObject. Tak samo c-string nie jest AnsiStringiem, a ten nie jest TGlowna. Wszelkie rzutowania między tymi typami/klasami będą konwersją.

a w rzutowaniu następuje przypisanie jednego typu do innego, więc trudno mówić tutaj o konwersji.

To się nazywa reinterpretacja 8-)
C++ Reference - opis wszystkich klas STL-a i funkcji C.
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez Cyfrowy Baron » piątek, 19 grudnia 2008, 17:32

Jest tylko częścią tego mechanizmu, a nie nim samym.


Podczas gdy w literaturze programistycznej możemy przeczytać:


Rzutowanie typów stoi za każdym polimorfizmem.




Więc nie tyle jest częścią tego mechanizmu ile tworzy ten mechanizm. To tak jak z tą klasą TObject, każda nowa klasa tworzona w oparciu o tą klasę jest przede wszystkim typem TObject. Tak więc polimorfizm jest częścią mechanizmu zwanego rzutowaniem typów. Rzutowanie typów nie ma nic wspólnego z konwersją, ale ma wiele wspólnego z polimorfizmem.
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez polymorphism » piątek, 19 grudnia 2008, 19:56

Więc nie tyle jest częścią tego mechanizmu ile tworzy ten mechanizm.

Heh, jeśli jest jego częścią, to oczywistym jest, że go (współ)tworzy - w żaden sposób nie kłóci się to z tym, co napisałem wcześniej.

Tak więc polimorfizm jest częścią mechanizmu zwanego rzutowaniem typów.

Oczywiście, że nie. Jest dokładnie odwrotnie, bo nie każde rzutowanie ma związek z polimorfizmem. Przykład:

Kod: Zaznacz cały
zmienna_int = (int)zmienna_float; //<--- konwersja
zmienna_int = *((int*)&zmienna_float); //<--- reinterpretacja

Oba, jakby na to nie patrzeć, są rzutowaniami. Cytat, który podałeś, jest oczywiście prawdziwy, ale źle go interpretujesz.

To tak jak z tą klasą TObject, każda nowa klasa tworzona w oparciu o tą klasę jest przede wszystkim typem TObject.

No tak, pisałem o tym. Ale spróbuj to teraz odnieść do AnsiString'a i TGlowna 8-) Obie klasy niewiele mają ze sobą wspólnego, poza oczywiście konstruktorem konwertującym. Polimorfizm opiera się głównie na dziedziczeniu wspólnego interface'u.
C++ Reference - opis wszystkich klas STL-a i funkcji C.
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez kinio » sobota, 20 grudnia 2008, 02:48

Witam!

Nie wiem jak to najlepiej opisać czym jest polimorfizm w językach typu C++. Dobrze by było sporządzić do tego klika rysunków, ale nie chce mi się więc postaram się to mniej więcej opisać:

Więc tak, polimorfizm jest jednym z najważniejszych mechanizmów C++, jest on możliwy dzięki mechanizmowi dziedziczenia. O tym co to jest dziedziczenie nie będę pisał, jeżeli ktoś nie wie, to niech lepiej nie używa BCB, bo tam się z tym spotykamy non stop!!

Jeżeli ktoś ma braki i nie zrozumie niektórych dalszych pojęć to najpierw niech je sobie doczyta - ja nie będę wyjaśniał.

Postaram się wszystko opisać na przykładzie klasy TObject i pochodnych od niej.
Więc mamy gdzieś zdefiniowaną klasę TObject, zawiera ona zbiór pól i metod. Mamy też inne klasy które się od niej wywodzą bezpośrednio lub pośrednio np. TMemo. Co to znaczy że się wywodzi mam nadzieje że każdy wie!!

Po co nam dziedziczenie?
Otóż upraszczając, umożliwia nam ono zbudowanie nowej klasy na podstawie już istniejącej.
Co nam to daje?
Po pierwsze nie musimy pisać dwa razy tych samych metod i wykorzystać już istniejący kod, oraz
Umożliwia stosowanie pewnego specjalnego mechanizmu związanego z dziedziczeniem zwanego właśnie polimorfizm!!

Nie zagłębiamy się w polimorfizm statyczny - szablony. Dziedziczenie to można powiedzieć że jest to polimorfizm dynamiczny.

Dlaczego dynamiczny?
Jak wiemy kod C++ jest tłumaczony przez kompilator bezpośrednio na język maszynowy rozumiany przez procesor czyli Asembler. W asemblerze, jeżeli ktoś coś pisał to wie że tam trzeba już operować na konkretnych komórkach pamięci, rejestrach itp.
Każdy obiekt danej klasy ma taki sam schemat rozmieszczenia pól i metod w pamięci. Czyli powiedzmy: pierwsza komórka naszego obiektu ma adres #4, metoda o nazwie metoda1 ma adres #10 a pole o nazwie pole1 ma adres #20 to w następnym obiekcie którego pierwsza komórka pamięci ma adres #50 to adres metody metoda1 wynosi #56 a pola pole1 #66.
Czyli jednym słowem te same elementy są tak samo rozmieszczone względem siebie w każdym obiekcie.

I co z tego?
Teraz wyobraźmy sobie że definiujemy klasę pochodną. Klasa pochodna nie może być mniejsza od klasy bazowej, nie możemy z niej nic wyrzucić co było dziedziczone z klasy bazowej. Okazuje się że stara, bazowa część nowej klasy pochodnej jest tak samo zorganizowana w pamięci jak to jest w klasie bazowej!!

I co mamy dzięki temu?
Sprawa jest prosta:
Każda zmienna, obiekt mają swój adres w pamięci. Każdy obiekt posiada swoją kopię pól, natomiast jest tylko jedna instancja każdej metody (po co więcej jak zawsze robi to samo tylko na rzecz innego obiektu). Dla przykładu:
Kod: Zaznacz cały
A a1, a2;    // A jest klasa bazowa
B b;           // B jest klasa pochodna od A
a1.metoda1();
a2.metoda1();
b.metoda1();

W każdym wywołaniu jest uruchamiana dokładnie ta sama instancja metody metoda1!! (zakładamy że nie jest wirtualna).
Podobnie mamy ze wskaźnikami i referencjami:
Kod: Zaznacz cały
A a1,a2;
B b;
A* p = &a1;
p->metoda1();
a = &b;
a->metoda1();

Dlaczego wskaźnik typu A może pokazywać na obiekt klasy B?
Ponieważ dla tego wskaźnika niczym on się nie różni od normalnego obiektu klasy A. Tak jak pisałem obiekt klasy B jest tak samo zorganizowany w pamięci jak obiekt klasy A (ponieważ pochodzi od klasy A), z tą różnicą że rozmiar obiektu klasy B jest równy bądź większy od obiektu klasy A, ta pozostała część, wskaźnikowi nie przeszkadza bo jest typu A!!
Taki przypisanie jak powyżej:
Kod: Zaznacz cały
p = &b;

możemy nazwać polimorficznym!!

Co to ten polimorfizm?
Krótko: wykazywanie różnego rodzaju zachowania przez te same metody w zależności o typu obiektu na rzecz którego zostały wywołane.
Dłużej: Aby mówić o polimorfizmie musimy się zapoznać z terminem funkcja wirtualna. W powyższym przykładzie wskaźnik p wywoływał zawsze metodę metoda1 z klasy A. Po prostu sięgał na ślepo do klasy A. Jeżeli jednak zdefiniujemy metodę metoda1 w klasie A jako wirtualna i w klasie pochodnej B będziemy starać się ją przysłonić, poprzez zdefiniowanie jej innej wersji to okaże się że w tym przykładzie wskaźnik p w przypadku:
Kod: Zaznacz cały
p = &b;
p->metoda1();

wywoła metodę z klasy B zamiast z klasy A!!
I to jest polimorfizm, na tym to polega, nie na czymś innym.

Teraz dlaczego jest on dynamiczny?
Mamy takie dwa pojęcia: wczesne i późne wiązanie - co to znaczy?
Kompilator analizując kod napotyka na wiele wywołań funkcji i metod. Natrafiając na funkcję nie wstawia jej zawartości w dane miejsce tylko wstawia instrukcę skoku pod adres gdzie funkcja jest zdefiniowana!!
Podobnie jest z niewirtualnymi metodami, jeżeli kompilator widzi wywołanie nie wirtualnej metody to w dane miejsce wstawia instrukcje skoku pod dany adres gdzie metoda jest zdefiniowana! Jeżeli jednak metoda jest wirtualna to co on tam może wstawić?
A no kod który już w czasie działania programu podejmie decyzję w które miejsce ma skoczyć i którą metodę wywołać - to jest ten dynamizm i dlatego ten polimorfizm jest nazwany danaymicznym!!

Mam nadzieje że udało mi się jakoś wytłumaczyć co to jest polimorfizm i jak i dlaczego działa!!
Kto się nie zgadza nie sobie jednak coś jeszcze poczyta :)

Oczywiście w wielu dociekliwych umysłach pewnie zrodziło się kilka pytań, najczęstsze to:
- Czy funkcja wirtualna może być inline?
- Czy funkcja wirtualna może być static?
- Czy klasa pochodna musi definiować swoją wersję funkcji wirtualnej?
Odpowiedzi nie podam, chociaż są krótkie bo tylko tak/nie ale dłuższe jest wyjaśnianie dlaczego tak lub dlaczego nie a spać mi się już chce!

Aha, no i krótko w odniesieniu do poprzednich postów:
Przypisanie:
Kod: Zaznacz cały
TObject* obj;
TMemo* memo;
obj = memo;

To bedzie przypisanie polimorficzne bo klasa TMemo wywodzi się od TObject. Natomiast:
Kod: Zaznacz cały
TGlowna a;
AnsiString b;
a = (TGlowna)b;

nie będzie przypisaniem polimorficznym ponieważ pomiędzy klasami TGlowna i AnsiString nie ma mowy o relacji pochodna-bazowa!!
Taki przypisanie nazwijmy tym nieszczęsnym rzutowaniem - jak ktoś nie chce niech się nie zgadza, ale niech broń Boże nie nazywa tego przypisaniem polimorficznym!!

Pozdrawiam!
If a machine is expected to be infallible, it cannot also be intelligent.
-- A.Turing
Avatar użytkownika
kinio
Homos antropiczny
Homos antropiczny
 
Posty: 67
Dołączył(a): poniedziałek, 14 lipca 2008, 08:51
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez polymorphism » sobota, 20 grudnia 2008, 11:33

Jak wiemy kod C++ jest tłumaczony przez kompilator bezpośrednio na język maszynowy rozumiany przez procesor czyli Asembler.

Kod maszynowy to nie assembler, który jest językiem programowania jak każdy inny, wymagający kompilacji język.

Jeżeli jednak metoda jest wirtualna to co on tam może wstawić?

Pobierze adres metody z tzw. vtable, którą posiada każda klasa, która zawiera choćby jedną metodę wirtualną - jest to niejawny wskaźnik na początku klasy. Oczywiście klasa może posiadać kilka takich tablic, w zależności od złożoności dziedziczenia.

A no kod który już w czasie działania programu podejmie decyzję w które miejsce ma skoczyć i którą metodę wywołać - to jest ten dynamizm i dlatego ten polimorfizm jest nazwany danaymicznym!!

Precyzując: dynamizm polega na tym, że można poszerzyć spektrum klas pochodnych od klasy polimorficznej bez konieczności rekompilacji kodu, który wykorzystuje mechanizmy polimorfizmu.
C++ Reference - opis wszystkich klas STL-a i funkcji C.
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez kinio » sobota, 20 grudnia 2008, 14:45

polymorphism napisał(a):Kod maszynowy to nie assembler, który jest językiem programowania jak każdy inny, wymagający kompilacji język.

OK, możemy się umówić że asembler to program dokonujący asemblacji kodu języka asemblerowego na kod maszynowy. Język asemblera jest językiem niższego poziomu niż C++. Instrukcje asemblera bezpośrednio reprezentują kod binarny rozumiany i wykonywany przez procesor. Powstał on w celu ułatwienia pisana programów, które wcześniej były ciągami zer i jedynek. Tak więc kompilacja języka asemblera znacznie się różni od kompilacji C++ i nie można powiedzieć że jest językiem programowania jak każdy inny!! Bo jest mnóstwo języków programowania, które nie są podobne do asemblera czy też C++ np. PROLOG, który jest w pełni deklaratywny i jest jednym z języków sztucznej inteligencji.

polymorphism napisał(a):Pobierze adres metody z tzw. vtable, którą posiada każda klasa, która zawiera choćby jedną metodę wirtualną - jest to niejawny wskaźnik na początku klasy. Oczywiście klasa może posiadać kilka takich tablic, w zależności od złożoności dziedziczenia.

Tak, każda klasa posiadająca chociaż jedną metodę wirtualną posiada taką tablicę, która zawiera wskaźniki do wszystkich implementacji danej metody wirtualnej i rozpoznając typ obiektu uruchamia metodę spod odpowiedniego adresu. Jest to realizowane w trakcie działania programu i nazywa się późnym lub dynamicznym wiązaniem. Ilość takich tablic w klasie zależy od ilości funkcji wirtualnych w niej zdefiniowanych.

polymorphism napisał(a):Oczywiście klasa może posiadać kilka takich tablic, w zależności od złożoności dziedziczenia.

Tutaj się nie zgodzę, ponieważ taka tablica nie jest dziedziczona, a jest umieszczona w klasie która jako pierwsza deklaruje daną metodę jako wirtualną. Ilość tych tablic nie zależy od złożoności dziedziczenia tylko od ilości metod wirtualnych deklarowanych w tej klasie po raz pierwszy.

kinio napisał(a):dynamizm polega na tym, że można poszerzyć spektrum klas pochodnych od klasy polimorficznej bez konieczności rekompilacji kodu, który wykorzystuje mechanizmy polimorfizmu.

Zgodzę się że nie trzeba rekompilacji kodu do wykorzystania polimorfizmu w klasach pochodnych. Jednak według mnie późne wiązanie najlepiej pokazuje dynamizm polimorfizmu, ponieważ w trakcie działania programu podejmowana jest decyzja którą implementacje metody wirtualnej należy uruchomić!

Pozdrawiam!
If a machine is expected to be infallible, it cannot also be intelligent.
-- A.Turing
Avatar użytkownika
kinio
Homos antropiczny
Homos antropiczny
 
Posty: 67
Dołączył(a): poniedziałek, 14 lipca 2008, 08:51
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: Tablica klas a ich konstruktor

Nowy postprzez polymorphism » sobota, 20 grudnia 2008, 16:27

Tak więc kompilacja języka asemblera znacznie się różni od kompilacji C++ i nie można powiedzieć że jest językiem programowania jak każdy inny!!

Heh, to chyba oczywiste, że się różni, wszak te dwa języki posiadają zupełnie rożne gramatyki ;) Co nie zmienia faktu, że assembler musi być skompilowany tak jak inne języki, które wymagają kompilacji do kodu natywnego (C\C++, Pascal\Delphi, itd). Nawet taki C# czy Java jest kompilowana (wprawdzie do bytecode'u, ale jest). Assembler, tak jak inne języki, posiada swoją składnie, pewne reguły itd. Oczywiście nie jest tak przenośny jak np. C, ale jak sam napisałeś jest to język niskiego poziomu, a tu nie uciekniesz od specyfiki sprzętu, pod który piszesz (w zasadzie C miał być/jest takim przenośnym assemblerem).

Bo jest mnóstwo języków programowania, które nie są podobne do asemblera czy też C++ np. PROLOG

No OK, ale co to za argument, że nie jest podobny?

Tutaj się nie zgodzę, ponieważ taka tablica nie jest dziedziczona, a jest umieszczona w klasie która jako pierwsza deklaruje daną metodę jako wirtualną. Ilość tych tablic nie zależy od złożoności dziedziczenia tylko od ilości metod wirtualnych deklarowanych w tej klasie po raz pierwszy.

Ilość metod nie ma związku z ilością tablic vtable. Zrób sobie małe doświadczenie:
Kod: Zaznacz cały
class A
{
public:
   virtual void foo() { cout << "A::foo" << endl; }
};

class B
{
public:
   virtual void foo() { cout << "B::foo" << endl; }
};

class C:public A,public B
{
public:
   void foo() { cout << "C::foo" << endl; }
};

...

cout << sizeof(A) << endl;   //<--- 4
cout << sizeof(B) << endl;   //<--- 4
cout << sizeof(C) << endl;   //<--- ile? :P


podejmowana jest decyzja która implementacje metody wirtualnej należy uruchomić!

No, w zasadzie mówimy o tym samym, tylko ja nie powiedziałbym, że ta decyzja jest podejmowana w trakcie działania programu (w każdym razie nie w tym, który jest skompilowany do kodu natywnego). Decyzja podejmowana jest w trakcie budowania klasy, która dziedziczy interface i implementuje własną wersję (niektórych) metod wirtualnych. Od strony kodu, wywołanie metod wirtualnych jest tak samo mechaniczne jak wywołanie zwykłej metody, tyle że adres metody nie jest wpisany "na sztywno" w kod. Ale nie drążmy tego tematu dalej, bo jak pisałem mówimy o tym samym, tylko inaczej dobieramy słowa/zdania...
C++ Reference - opis wszystkich klas STL-a i funkcji C.
Avatar użytkownika
polymorphism
Doświadczony Programista ● Moderator
Doświadczony Programista ● Moderator
 
Posty: 2156
Dołączył(a): piątek, 19 grudnia 2008, 13:04
Podziękował : 0
Otrzymał podziękowań: 200
System operacyjny: Windows 8.1
Windows 10
Linux Mint 21.1
Kompilator: Visual Studio
Visual Studio Code
MSYS2 (MinGW, clang)
g++
clang
Gadu Gadu: 0
    NieznanyNieznana

Poprzednia strona

  • Podobne tematy
    Odpowiedzi
    Wyświetlone
    Ostatni post

Powrót do Ogólne problemy z programowaniem

Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zalogowanych użytkowników i 6 gości