Mam odpowiedź w formie rozmowy, aby lepiej przeczytać:
Dlaczego potrzebujemy funkcji wirtualnych?
Z powodu polimorfizmu.
Co to jest polimorfizm?
Fakt, że wskaźnik bazowy może również wskazywać na obiekty typu pochodnego.
Jak ta definicja polimorfizmu prowadzi do potrzeby funkcji wirtualnych?
Przez wczesne wiązanie .
Co jest wcześnie wiążące?
Wczesne wiązanie (wiązanie w czasie kompilacji) w C ++ oznacza, że wywołanie funkcji jest ustalone przed uruchomieniem programu.
Więc...?
Jeśli więc użyjesz typu podstawowego jako parametru funkcji, kompilator rozpozna tylko interfejs podstawowy, a jeśli wywołasz tę funkcję z dowolnymi argumentami z klas pochodnych, zostanie ona odcięta, co nie jest tym, co chcesz zrobić.
Jeśli nie to chcemy, dlaczego jest to dozwolone?
Ponieważ potrzebujemy polimorfizmu!
Jaka jest zatem korzyść z polimorfizmu?
Możesz użyć wskaźnika typu podstawowego jako parametru pojedynczej funkcji, a następnie w czasie wykonywania programu możesz uzyskać dostęp do każdego z pochodnych interfejsów typu (np. Ich funkcji składowych) bez żadnych problemów, korzystając z dereferencji tego singla wskaźnik bazowy.
Nadal nie wiem, jakie funkcje wirtualne są dobre dla ...! To było moje pierwsze pytanie!
to dlatego, że zadałeś zbyt wcześnie pytanie!
Dlaczego potrzebujemy funkcji wirtualnych?
Załóżmy, że wywołałeś funkcję ze wskaźnikiem podstawowym, który miał adres obiektu z jednej z jego klas pochodnych. Jak już mówiliśmy o tym powyżej, w czasie wykonywania wskaźnik ten zostaje zdereferencjonowany, jak na razie jednak spodziewamy się, że zostanie wykonana metoda (== funkcja członka) „z naszej klasy pochodnej”! Jednak ta sama metoda (ta, która ma ten sam nagłówek) jest już zdefiniowana w klasie bazowej, więc dlaczego twój program miałby zadawać sobie trud wyboru innej metody? Innymi słowy, jak możesz odróżnić ten scenariusz od tego, co normalnie zdarzało się wcześniej?
Krótka odpowiedź to „funkcja wirtualnego elementu członkowskiego w bazie”, a nieco dłuższa odpowiedź brzmi: „na tym etapie, jeśli program zobaczy funkcję wirtualną w klasie podstawowej, wie (zdaje sobie sprawę), że próbujesz użyć polimorfizm ”i tak idzie do klas pochodnych (przy użyciu tabeli v , formy późnego wiązania) w celu znalezienia innej metody z tym samym nagłówkiem, ale z - prawdopodobnie - inną implementacją.
Dlaczego inna implementacja?
Ty golicy! Idź przeczytać dobrą książkę !
OK, czekaj czekaj czekaj, dlaczego ktoś miałby zadawać sobie trud korzystania ze wskaźników bazowych, skoro mógł po prostu używać wskaźników pochodnych? Jesteś sędzią, czy ten cały ból głowy jest tego wart? Spójrz na te dwa fragmenty:
// 1:
Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();
// 2:
Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();
OK, chociaż myślę, że 1 jest wciąż lepszy niż 2 , możesz napisać 1 tak:
// 1:
Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();
a ponadto powinieneś zdawać sobie sprawę, że jest to tylko przemyślane wykorzystanie wszystkich rzeczy, które ci wyjaśniłem do tej pory. Zamiast tego załóżmy na przykład sytuację, w której miałeś w swoim programie funkcję, która korzystała z metod odpowiednio z każdej z klas pochodnych (getMonthBenefit ()):
double totalMonthBenefit = 0;
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
totalMonthBenefit += x -> getMonthBenefit();
}
Teraz spróbuj ponownie napisać to bez żadnych problemów!
double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();
I faktycznie może to być jeszcze wymyślony przykład!