50 / 90

C++ Virtual Functions

C++ virtual funksiyalari

Virtual funksiya bu class bazasidagi function aʼzosi boʻlib, biz hosil boʻlgan sinflarda qayta belgilashni kutmoqdamiz.

Masalan,

example.cpp
class Base {
   public:
    void print() {
        // kod
    }
};

class Derived : public Base {
   public:
    void print() {
        // kod
    }
};

shadows Derived sinfidagi print() usuli Base sinfidagi print() usuli.

Biroq, agar biz Derived sinfidagi ob'ektga ishora qilish uchun Base tipidagi ko'rsatkich yaratsak va print() funksiyasini chaqirsak, u Base sinfining print() funksiyasini chaqiradi.

Boshqacha qilib aytganda, Base a'zo funksiyasi bekor qilinmaydi.

example.cpp
int main() {
    Derived derived1;
    Base* base1 = &derived1;

    // Base sinfining chaqiruv funksiyasi
    base1->print();

    return 0;
}

Bunga yo'l qo'ymaslik uchun virtual kalit so'zidan foydalanib, Base sinfining print() funksiyasini virtual deb e'lon qilamiz.

example.cpp
class Base {
   public:
    virtual void print() override {
        // kod
    }
};

Agar virtual funksiya olingan sinfda qayta aniqlangan bo'lsa, olingan sinfdagi funksiya, olingan sinf ob'ektiga ishora qiluvchi asosiy sinf ob'ektining pointer yordamida chaqirilgan taqdirda ham bajariladi. Bunday holda, funksiya bekor qilingan deb aytiladi.

Virtual funksiyalar C++ tilidagi polimorfizmning ajralmas qismidir. Qo'shimcha ma'lumot olish uchun C++ Polymorphism bo'yicha o'quv qo'llanmamizni tekshiring.

1-misol: C++ virtual funksiyasi

example.cpp
#include <iostream>
using namespace std;

class Base {
   public:
    virtual void print() {
        cout << "Base Function" << endl;
    }
};

class Derived : public Base {
   public:
    void print() override {
        cout << "Derived Function" << endl;
    }
};

int main() {
    Derived derived1;

    // olingan1 ga ishora qiluvchi Baza tipidagi ko'rsatkich
    Base* base1 = &derived1;

    // Derived sinfining a'zo funksiyasini chaqiradi
    base1->print();

    return 0;
}

Chiqish

kod
Derived Function

Bu erda biz Base ning print() usulini virtual deb e'lon qildik.

Shunday qilib, biz Derived obyektiga ishora qiluvchi Base tipidagi ko'rsatgichdan foydalansak ham, bu funksiya bekor qilinadi1.

Working of virtual functions in C++

C++ Working of Virtual Functions

C++ ni bekor qilish Spesifikatori

C++ 11 yangi override spetsifikatsiyasini taqdim etadi, bu virtual funksiyalardan foydalanishda keng tarqalgan xatolardan qochish uchun juda foydali.

Ushbu override spetsifikatsiyasi, asosiy sinfning a'zo funksiyasini bekor qiladigan hosila sinflarning a'zo funksiyalarini belgilaydi.

Masalan,

example.cpp
class Base {
   public:
    virtual void print() {
        // kod
    }
};

class Derived : public Base {
   public:
    void print() override {
        // kod
    }
};

Agar biz Derived sinfida funksiya prototipidan foydalansak va bu funksiyani sinfdan tashqarida aniqlasak, biz quyidagi koddan foydalanamiz:

example.cpp
class Derived : public Base {
   public:
    // funksiya prototipi
    void print() override;
};

// funksiya ta'rifi
void Derived::print() {
    // kod
}

Bu erda void print() override; Derived sinfidagi funksiya prototipidir. override specifier Base sinfidagi print() funksiyasi Derived sinfidagi print() funksiyasi tomonidan bekor qilinishini ta'minlaydi.

Agar biz Derived sinfida funksiya prototipidan foydalansak, u holda override spetsifikatsiyasidan ta'rifda emas, faqat funksiya deklaratsiyasida foydalanamiz.

C++ dan foydalanish

Virtual funksiyalardan foydalanganda hosila sinflarning a'zo funksiyalarini e'lon qilishda xatolikka yo'l qo'yish mumkin.

override spetsifikatsiyasidan foydalanish kompilyatorni ushbu xatolar sodir bo'lganda xato xabarlarini ko'rsatishni taklif qiladi.

Aks holda, dastur shunchaki kompilyatsiya qilinadi, lekin virtual funksiya bekor qilinmaydi.

Ushbu mumkin bo'lgan xatolardan ba'zilari:

  • Noto'g'ri nomli funksiyalar: Masalan, agar asosiy sinfdagi virtual funksiya print() deb nomlangan bo'lsa, lekin biz tasodifan olingan sinfdagi bekor qiluvchi funksiyani pint() deb nomlaymiz.
  • Qaytishning har xil turlariga ega funksiyalar: Agar virtual funksiya, aytaylik, void tipidagi bo‘lsa, lekin olingan sinfdagi funksiya int tipidagi bo‘lsa.
  • Turli parametrlarga ega funksiyalar: virtual funksiya parametrlari va olingan sinflardagi funksiyalar mos kelmasa.
  • Asosiy sinfda virtual funksiya e'lon qilinmaydi.

Virtual destruktor

Agar olingan sinf dinamik xotira ajratishni o'z ichiga olgan bo'lsa, biz uning destruktoridagi xotirani ajratishimiz kerak.

example.cpp
#include <iostream>

using namespace std;

class Shape {
    public:
        Shape() = default;

        virtual double get_area() const {
            return 0.0;
        }

        ~Shape() = default;
};

class Square: public Shape {
    private:
        double* length;

    public:
        Square(double len = 1.0): length(new double{len}) {
        }

        double get_area() const override {
            return *length * *length;
        }

        ~Square() {
            delete length;
             cout << "deleted length." << endl << endl;
        }
};

int main() {
    // Square ob'ektiga ishora qiluvchi Square sinfiga ko'rsatkich
    Square* shape1 = new Square(1.5);
    cout << "area of square: " << shape1->get_area() << endl;
    // Square sinf destruktorini chaqiradi
    delete shape1;

    // Square ob'ektiga ishora qiluvchi Shape sinfiga ko'rsatkich
    Shape* shape2 = new Square(2.5);
    cout << "area of square: " << shape2->get_area() << endl;
    // Shakl sinfi destruktorini chaqiradi
    delete shape2;
    return 0;
}

Chiqish:

kod
area of square: 2.25
deleted length.

area of square: 6.25

Kvadrat ob'ektga ishora qiluvchi Square* bo'lgan shape1 ni o'chirsak, Square klassining destruktori chaqiriladi, bu Square ob'ekti tomonidan olingan dinamik xotirani o'chiradi.

Biroq, agar biz Square ob'ektiga ishora qiluvchi Shape* bo'lgan shape2 ni o'chirsak, Shape sinfining destruktori chaqiriladi, bu Square ob'ekti tomonidan olingan dinamik xotirani ajratmaydi.

Agar Shape sinf destruktorini virtual deb e'lon qilsak, Square sinf destruktori Square ob'ektiga ishora qiluvchi Shape sinfiga ko'rsatgichni o'chirganimizda chaqiriladi.

example.cpp
#include <iostream>

using namespace std;

class Shape {
    public:
        Shape() = default;

        virtual double get_area() const {
            return 0.0;
        }

        virtual ~Shape() = default;
};

class Square: public Shape {
    private:
        double* length;

    public:
        Square(double len = 1.0): length(new double{len}) {
        }

        double get_area() const override {
            return *length * *length;
        }

        ~Square() {
            delete length;
             cout << "deleted length." << endl << endl;
        }
};

int main() {
    // Square ob'ektiga ishora qiluvchi Square sinfiga ko'rsatkich
    Square* shape1 = new Square(1.5);
    cout << "area of square: " << shape1->get_area() << endl;
    // Square sinf destruktorini chaqiradi
    delete shape1;

    // Square ob'ektiga ishora qiluvchi Shape sinfiga ko'rsatkich
    Shape* shape2 = new Square(2.5);
    cout << "area of square: " << shape2->get_area() << endl;
    // kvadrat sinf destruktorini chaqiradi
    delete shape2;
    return 0;
}

Chiqish:

kod
area of square: 2.25
deleted length.

area of square: 6.25
deleted length.

C++ virtual funksiyalaridan foydalanish

Virtual funksiyalar biz bir guruh ob'ektlarni saqlaganimizda foydalidir.

Faraz qilaylik, bizda asosiy toifa Xodim va olingan sinflar HourlyEmployee va RegularEmployee. Biz ikkala hosila sinf ob'ektlariga ishora qiluvchi Xodim* ko'rsatkichlarini Xodim* to'plamida saqlashimiz mumkin.

Keyin biz Employee* ko'rsatkichlari yordamida virtual funksiyani chaqirishimiz mumkin. Biz virtual funksiyani Employee* ko'rsatkichlari yordamida chaqirganimizda, kompilyator tegishli olingan sinfda belgilangan funksiyani chaqiradi.

example.cpp
// Virtual funksiyadan foydalanishni ko'rsatish uchun C++ dasturi

#include <iostream>
#include <vector>
using namespace std;

class Employee {
    private:
        string first_name;
        string last_name;
    public:
        Employee(string fname, string lname): first_name(fname), last_name(lname) {
        }
        string get_full_name() {
            return first_name + " " + last_name;
        }
        // bekor qilishga ruxsat berish uchun virtual funksiya
        virtual void print_wage() {
            cout << "The employee's wage structure is not declared yet" << endl;
        }
        // virtual destruktor
        virtual ~Employee() {
        }
};

class HourlyEmployee: public Employee {
    private:
        double hourly_wage;
    public:
        HourlyEmployee(string fname, string lname, double wage_per_hour): Employee(fname, lname), hourly_wage(wage_per_hour) {
            
        }
        // funksiyani bekor qiladi va to'g'ri bajarilishini ta'minlaydi
        // HourlyEmployee ish haqi tarkibiga ko'ra
        void print_wage() override {
            cout << "The hourly wage of " << get_full_name() << " is " << hourly_wage << endl;
        }
};

class RegularEmployee: public Employee {
    private:
        double monthly_wage;
    public:
        RegularEmployee(string fname, string lname, double wage_per_month): Employee(fname, lname), monthly_wage(wage_per_month) {
            
        }
        // funksiyani bekor qiladi va to'g'ri bajarilishini ta'minlaydi
        // Muntazam xodimning ish haqi tarkibiga ko'ra
        void print_wage() override {
            cout << "The monthly wage of " << get_full_name() << " is " << monthly_wage << endl;
        }
};

int main() {
    // Dervied sinf ob'ektlariga ishora qiluvchi Employee* ko'rsatkichlarini saqlash uchun vektorni e'lon qiling
    vector<Employee*> employees {new HourlyEmployee("John", "Doe", 13.5), new RegularEmployee("Jane", "Smith", 3000.7)};
    for(Employee* employee: employees) {
        employee->print_wage();
    }
    return 0;
}

Chiqish

kod
The hourly wage of John Doe is 13.5
The monthly wage of Jane Smith is 3000.7

Bu yerda “Xodim”ning asosiy klassi print_wage() virtual funksiyasiga ega. HourlyEmployee va RegularEmployee hosila sinflari print_wage() funksiyasini o'z amalga oshirishiga ega.

Biz vektorda HourlyEmployee va RegularEmployee obyektlariga ishora qiluvchi Employee* ko‘rsatkichlarini saqladik.

example.cpp
vector<Employee*> employees {new HourlyEmployee("John", "Doe", 13.5), new RegularEmployee("Jane", "Smith", 3000.7)};

Keyin vektordagi ko'rsatkichlar bo'ylab takrorlash uchun intervalli for loop dan foydalandik.

example.cpp
for(Employee* employee: employees) {
    employee->print_wage();
}

Bu erda employee->print_wage() print_wage() funksiyasini ko'rsatuvchi xodim ko'rsatadigan ob'ekt turiga qarab chaqiradi.