Metoda wirtualna (funkcja wirtualna) to metoda, która może zostać nadpisana w klasach dziedziczących[1][2][3]. Metody wirtualne umożliwiają polimorfizm[4].

Właściwości

edytuj
  • Są deklarowane przy użyciu słowa kluczowego virtual[5].
  • Metoda wirtualna nie może być zadeklarowana jako statyczna (static)[6].
  • Jeśli metoda wirtualna została zaimplementowana w jakimkolwiek wyższym poziomie dziedziczenia (w szczególności w klasie bazowej całej struktury dziedziczenia), nie jest konieczne podawanie implementacji w klasie pochodnej[7].
  • Słowo kluczowe virtual można pominąć w deklaracjach w klasach pochodnych.
  • Słowo kluczowe virtual określa się tylko w deklaracji metody, nie określa się tego w definicji (chyba że deklaracja jest jednocześnie definicją, co ma miejsce gdy implementacja metody zostanie określona w ciele klasy).
  • Jeśli w klasie jest zadeklarowana jakakolwiek metoda wirtualna, zaleca się aby destruktor w tej klasie również określić jako wirtualny.
  • W Javie domyślnie wszystkie metody są wirtualne.
  • W C# domyślnie metody nie są wirtualne[8]. Metody wirtualne muszą zostać zadeklarowane przy użyciu słowa kluczowego virtual.

Zastosowania

edytuj
  • Rozszerzalność kodu. Polimorfizm umożliwia rozszerzanie nawet skompilowanych fragmentów kodu.
    • Pozwala na rozszerzalność kodu również wtedy, gdy dostępna jest jedynie skompilowana wersja klasy bazowej.
  • Zwalnia programistę od niepotrzebnego wysiłku.
    • Programista nie musi przejmować się tym, którą z klas pochodnych aktualnie obsługuje, a jedynie tym, jakie operacje chce na tej klasie wykonać.
    • Programista myśli co ma wykonać a nie jak to coś wykonać - nie musi się przejmować szczegółami implementacyjnymi.

Czysta wirtualność

edytuj

Określa to, że metoda z klasy bazowej deklarująca metodę wirtualną nigdy nie powinna się wykonać. W efekcie klasa taka staje się klasą abstrakcyjną, a więc nie jest możliwe stworzenie obiektu tej klasy. Klasa taka służy zdefiniowaniu pewnego interfejsu i jest przeznaczona jedynie do tego, by od niej dziedziczyć.

W przykładzie poniżej, o ile mogą istnieć figury będące kwadratami, kołami itp. to nie powinien istnieć żaden obiekt klasy Figura. Figura jest tutaj pewnym abstrakcyjnym określeniem, natomiast dziedziczenie po tej klasie i rozszerzeniu jej o inne elementy (dane i metody) powoduje, że mamy do czynienia już z konkretną figurą geometryczną. Metodę czysto wirtualną w języku C++ deklaruje się tak:

 class Figura 
 {
   public:
     virtual float pole() = 0;
 };

Taka deklaracja metody wirtualnej zmusza jednocześnie do określenia metody float pole() na jednym z poziomów dziedziczenia. Nie jest możliwe pominięcie takiej implementacji. Jednocześnie taka deklaracja uniemożliwia stworzenie jakiegokolwiek obiektu klasy Figura np.: Figura mojObiekt;.

Przykład w C++

edytuj
 #include <iostream>

 const float pi = 3.14159;
 class Figura {
   public:
     virtual float pole() const {
       return -1.0;
     }
 };

 class Kwadrat : public Figura {
   public:
     Kwadrat( const float bok ) : a( bok ) {}

     float pole() const {
       return a * a;
     }

   private:
     float a; // bok kwadratu
 };

 class Kolo : public Figura {
   public:
     Kolo( const float promien ) : r( promien ) {}

     float pole() const {
       return pi * r * r;
     }

   private:
     float r; // promien kola
 };

 void wyswietlPole( Figura& figura ) {
   std::cout << figura.pole() << std::endl;
   return;
 }

 int main() {
   // deklaracje obiektow:
   Figura jakasFigura;
   Kwadrat jakisKwadrat( 5 );
   Kolo jakiesKolo( 3 );
   Figura* wskJakasFigura = 0; // deklaracja wskaźnika

   // obiekty -------------------------------
   std::cout << jakasFigura.pole() << std::endl; // wynik: -1
   std::cout << jakisKwadrat.pole() << std::endl; // wynik: 25
   std::cout << jakiesKolo.pole() << std::endl; // wynik: 28.274...

   // wskazniki -----------------------------
   wskJakasFigura = &jakasFigura;
   std::cout << wskJakasFigura->pole() << std::endl; // wynik: -1
   wskJakasFigura = &jakisKwadrat;
   std::cout << wskJakasFigura->pole() << std::endl; // wynik: 25
   wskJakasFigura = &jakiesKolo;
   std::cout << wskJakasFigura->pole() << std::endl; // wynik: 28.274...
 
   // referencje -----------------------------
   wyswietlPole( jakasFigura ); // wynik: -1
   wyswietlPole( jakisKwadrat ); // wynik: 25
   wyswietlPole( jakiesKolo ); // wynik: 28.274...

   return 0;
 }

W przykładzie znajdują się deklaracje 3 klas: Figura, Kwadrat i Kolo. W klasie Figura została zadeklarowana metoda wirtualna (słowo kluczowe virtual) virtual float pole(). Każda z klas pochodnych od klasy Figura ma zaimplementowane swoje metody float pole(). Następnie (w funkcji main) znajdują się deklaracje obiektów każdej z klas i wskaźnika mogącego pokazywać na obiekty klasy bazowej Figura.

Wywołanie metod składowych dla każdego z obiektów powoduje wykonanie metody odpowiedniej dla klasy danego obiektu. Następnie wskaźnikowi wskJakasFigura zostaje przypisany adres obiektu jakasFigura i zostaje wywołana metoda float pole(). Wynikiem jest "-1" zgodnie z treścią metody float pole() w klasie Figura. Następnie przypisujemy wskaźnikowi adres obiektu klasy Kwadrat - możemy tak zrobić ponieważ klasa Kwadrat jest klasą pochodną od klasy Figura - jest to tzw. rzutowanie w górę. Wywołanie teraz metody float pole() dla wskaznika nie spowoduje wykonania metody zgodnej z typem wskaźnika - który jest typu Figura* lecz zgodnie z aktualnie wskazywanym obiektem, a więc wykonana zostanie metoda float pole() z klasy Kwadrat (gdyż ostatnie przypisanie wskaźnikowi wartości przypisywało mu adres obiektu klasy Kwadrat). Analogiczna sytuacja dzieje się gdy przypiszemy wskaźnikowi adres obiektu klasy Kolo. Następnie zostaje wykonana funkcja void wyswietlPole(Figura&) która przyjmuje jako parametr obiekt klasy Figura przez 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 float Figura::pole(). Takie działanie jest spowodowane przez przyjmowanie obiektu klasy Figura przez referencję. Gdyby obiekty były przyjmowane przez wartość (parametr bez &) zostałaby wykonana 3 krotnie metoda float Figura::pole() i 3 krotnie wyświetlona wartość -1.

Wyżej opisane działanie zostało spowodowane przez określenie metody w klasie bazowej jako wirtualnej. Gdyby zostało usunięte słowo kluczowe virtual w deklaracji metody w klasie bazowej, zostałyby wykonane metody zgodne z typem wskaźnika lub referencji, a więc za każdym razem zostałaby wykonana metoda float pole() z klasy Figura.

Zobacz też

edytuj

Bibliografia

edytuj

Przypisy

edytuj
  1. What are Virtual Methods? [online], Stackoverflow, 12 października 2024 [dostęp 2024-10-12] (ang.).
  2. C# language documentation: virtual [online], Microsoft, 12 października 2024 [dostęp 2024-10-12], Cytat: The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class (ang.).
  3. Virtual Function in C++ [online], Geeks for geeks, 12 października 2024 [dostęp 2024-10-12], Cytat: A virtual function (also known as virtual methods) is a member function that is declared within a base class and is re-defined (overridden) by a derived class (ang.).
  4. Virtual Function in C++ [online], Geeks for geeks, 12 października 2024 [dostęp 2024-10-12], Cytat: They are mainly used to achieve Runtime polymorphism (ang.).
  5. Virtual Function in C++ [online], Geeks for geeks, 12 października 2024 [dostęp 2024-10-12], Cytat: Functions are declared with a virtual keyword in a base class (ang.).
  6. Virtual Function in C++ [online], Geeks for geeks, 12 października 2024 [dostęp 2024-10-12], Cytat: Virtual functions cannot be static (ang.).
  7. Virtual Function in C++ [online], Geeks for geeks, 12 października 2024 [dostęp 2024-10-12], Cytat: It is not mandatory for the derived class to override (or re-define the virtual function), in that case, the base class version of the function is used (ang.).
  8. C# language documentation: virtual (C# Reference) [online], Microsoft, 12 października 2024 [dostęp 2024-10-12], Cytat: By default, methods are non-virtual. You cannot override a non-virtual method (ang.).