C++/Funkcje wirtualne: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
PG (dyskusja | edycje)
dopasowanie kodu do konwencji używanych w reszcie książki
Nie podano opisu zmian
 
Linia 8:
 
Mając klasę bazową wyprowadzamy od niej klasę pochodną:
<sourcesyntaxhighlight lang="cpp">
 
class Baza
Linia 28:
};
 
</syntaxhighlight>
</source>
Jeżeli teraz w funkcji main stworzymy wskaźnik do obiektu typu Baza, to możemy ten wskaźnik ustawiać na dowolne obiekty tego typu.
Można też ustawić go na obiekt typu pochodnego, czyli Baza2:
 
<sourcesyntaxhighlight lang="cpp">
int main()
{
Linia 50:
}
 
</syntaxhighlight>
</source>
Po skompilowaniu na ekranie zobaczymy dwa wypisy: "Tu funkcja pisz z klasy Baza".
Stało się tak dlatego, że wskaźnik jest do typu Baza. Gdy ustawiliśmy wskaźnik na obiekt typu pochodnego (wolno nam), a następnie wywołaliśmy funkcję składową, to kompilator sięgnął po funkcję pisz z klasy bazowej.
Linia 57:
Różnica polega tylko na dodaniu słowa kluczowego ''virtual'', co wygląda tak:
 
<sourcesyntaxhighlight lang="cpp">
class Baza
{
Linia 76:
};
 
</syntaxhighlight>
</source>
 
== Konsekwencje ==
Linia 90:
==Przykład==
Poniższy program zawiera deklaracje 3 klas: <code>Figura</code>, <code>Kwadrat</code> i <code>Kolo</code>. W klasie <code>Figura</code> została zadeklarowana metoda wirtualna (słowo kluczowe ''virtual'') <code>virtual float pole()</code>. Każda z klas pochodnych od klasy <code>Figura</code> ma zaimplementowane swoje metody <code>float pole()</code>. Następnie (w funkcji main) znajdują się deklaracje obiektów każdej z klas i wskaźnika mogącego pokazywać na obiekty klasy bazowej <code>Figura</code>.
<sourcesyntaxhighlight lang="cpp">
#include <iostream>
 
Linia 163:
return 0;
}
</syntaxhighlight>
</source>
 
Wywołanie metod składowych dla każdego z obiektów powoduje wykonanie metody odpowiedniej dla klasy danego obiektu. Następnie wskaźnikowi <code>wskJakasFigura</code> zostaje przypisany adres obiektu <code>jakasFigura</code> i zostaje wywołana metoda <code>float pole()</code>. Wynikiem jest ''"-1"'' zgodnie z treścią metody <code>float pole()</code> w klasie <code>Figura</code>. Następnie przypisujemy wskaźnikowi adres obiektu klasy Kwadrat - możemy tak zrobić ponieważ klasa <code>Kwadrat</code> jest klasą pochodną od klasy <code>Figura</code> - jest to tzw. [[w:rzutowanie w górę|rzutowanie w górę]]. Wywołanie teraz metody <code>float pole()</code> dla wskaźnika nie spowoduje wykonania metody zgodnej z typem wskaźnika - który jest typu <code>Figura*</code> lecz zgodnie z aktualnie wskazywanym obiektem, a więc wykonana zostanie metoda <code>float pole()</code> z klasy <code>Kwadrat</code> (gdyż ostatnie przypisanie wskaźnikowi wartości przypisywało mu adres obiektu klasy <code>Kwadrat</code>). Analogiczna sytuacja dzieje się gdy przypiszemy wskaźnikowi adres obiektu klasy <code>Kolo</code>. Następnie zostaje wykonana funkcja <code>void wyswietlPole(Figura&)</code> która przyjmuje jako parametr obiekt klasy <code>Figura</code> przez [[w:referencja|referencję]]. Tutaj również zostały wykonane odpowiednie metody dla obiektów klas pochodnych a nie metoda zgodna z obiektem jaki jest zadeklarowany jako parametr funkcji czyli <code>float Figura::pole()</code>. Takie działanie jest spowodowane przez przyjmowanie obiektu klasy <code>Figura</code> przez referencję. Gdyby obiekty były przyjmowane przez wartość (parametr bez ''&'') zostałaby wykonana 3 krotnie metoda <code>float Figura::pole()</code> i 3 krotnie wyświetlona wartość ''-1''.
Linia 175:
Jakie jest zastosowanie takiego rzutowania? Wyobraźmy, że posiadamy listę figur z przykładu. Figura jednak udostępnia jedynie swój interfejs, a my np. chcielibyśmy wykonać jakieś działanie wyłącznie na obiektach typu Kwadrat. Dzięki '''dynamic_cast''' możemy sprawdzić, czy figura jest odpowiedniego typu, dokonać konwersji i używać obiektu Kwadrat w żądany sposób.
 
<sourcesyntaxhighlight lang="cpp">
Figura* figura = new NazwaFigury(...);
 
Linia 187:
std::cout << "figura nie jest kwadratem" << '\n';
}
</syntaxhighlight>
</source>
 
Wynikiem poprawnego rzutowania wskaźników jest '''niepusty wskaźnik'''. Jeśli rzutowanie jest niemożliwe wskaźnik jest pusty.
Linia 201:
Przykład deklaracji:
 
<sourcesyntaxhighlight lang="cpp" highlight="3">
class KlasaAbstrakcyjna
{
virtual int wyswietl() = 0;
};
</syntaxhighlight>
</source>
 
==Nadpisywanie metod wirtualnych - override (C++11)==
Linia 214:
Jednak dodawanie metody o tej samej nazwie ma w 99% przypadków jeden cel - '''nadpisanie metody''' w klasie pochodnej. Problemem jest gdy lista parametrów się nie zgadza (na skutek pomyłki, zmian w klasie bazowej, itp.), wtedy '''wbrew intencjom''' wprowadzona jest nowa metoda. Aby zapobiec takim problemom od wersji C++11 dostępny jest nowy kwalifikator metod '''override''', który jasno mówi kompilatorowi, że metodę o podanej nazwie chcemy nadpisać. Jeśli metody o tej nazwie nie ma w klasie bazowej, bądź posiada inną sygnaturę, wówczas zgłaszany jest błąd kompilacji.
 
<sourcesyntaxhighlight lang="cpp" highlight="6">
class Bazowa
{
Linia 230:
// virtual void drukuj() override
};
</syntaxhighlight>
</source>
 
<noinclude>