Hàm ảo trong C++

Trong bài này bạn sẽ được học về hàm ảo và nơi sử dụng nó. Ngoài ra, bạn cũng sẽ được học về hàm ảo thuần và lớp trừu tượng.

Một hàm ảo là một hàm thành viên trong lớp chính mà bạn cần định nghĩa lại trong lớp kế thừa.

Trước khi tìm hiểu kỹ hơn, cùng tìm hiểu về lý do tại sao hàm ảo lại được cần đến.

Một ví dụ để bắt đầu

Giả sử rằng, chúng ta đang làm việc với một trò chơi (cụ thể là có vũ khí).

Chúng ta tạo ra lớp Weapon (Vũ khí) và hai lớp kế thừa Bomb (Bom) và Gun (Súng) để nạp các tính năng cho các vũ khí đó.

#include <iostream>
using namespace std;

class Weapon
{
    public:
       void loadFeatures()
         { cout << "Loading weapon features.n"; }
};

class Bomb : public Weapon
{
    public:
       void loadFeatures()
         { cout << "Loading bomb features.n"; }
};

class Gun : public Weapon
{
    public:
       void loadFeatures()
         { cout << "Loading gun features.n"; }
};

int main()
{
    Weapon *w = new Weapon;
    Bomb *b = new Bomb;
    Gun *g = new Gun;

    w->loadFeatures();
    b->loadFeatures();
    g->loadFeatures();

    return 0;
}

Đầu ra

Loading weapon features.

Loading bomb features.

Loading gun features.

Chúng ta định nghĩa ba đối tượng con trỏ w, bg của lớp Weapon, Bomb và Gun. Sau đó, chúng ta gọi hàm thành viên loadFeatures() của mỗi đối tượng sử dụng:

w->loadfeatures();
b->loadFeatures();
g->loadFeatures();

Chạy thành công!

Tuy nhiên, dự án trò chơi bắt đầu trở nên lớn hơn và lớn hơn nữa. Và chúng ta quyết định rằng phải tạo một lớp Loader riêng để nạp vào tính năng các loại vũ khí.

Lớp Loader này sẽ nạp các tính năng bổ sung của một loại vũ khi tùy thuộc vào việc vũ khí được chọn là gì.

class Loader
{
   public:
     void loadFeatures(Weapon *weapon)
     {
        weapon->features();
     }     
};

Hàm loadFeatures() sẽ nạp vào tính năng của một vũ khí xác định.

Cùng thử lập trình lớp Loader của chúng ta

#include <iostream>
using namespace std;

class Weapon
{
    public:
      void features()
         { cout << "Loading weapon features.n"; }
};

class Bomb : public Weapon
{
    public:
       void features()
         { cout << "Loading bomb features.n"; }
};

class Gun : public Weapon
{
    public:
       void features()
         { cout << "Loading gun features.n"; }
};

class Loader
{
   public:
     void loadFeatures(Weapon *weapon)
     {
        weapon->features();
     }     
};

int main()
{
    Loader *l = new Loader;
    Weapon *w;
    Bomb b;
    Gun g;

    w = &b;
    l->loadFeatures(w);

    w = &g;
    l->loadFeatures(w);

    return 0;
}

Đầu ra

Loading weapon features.

Loading weapon features.

Loading weapon features.

Đoạn mã lập trình của chúng ta có vẻ đã đúng. Tuy nhiên, tính năng vũ khí được nạp vào 3 lần.

Ban đầu, đối tượng Weapon w đang trỏ vào đối tượng b (thuộc lớp Bomb). Và sau đó chúng ta cố gắng nạp tính năng của đối tượng Bomb này bằng cách truyền nó vào hàm loadFeatures() sử dụng đối tượng con trỏ l (thuộc lớp Loader).

Tương tự, chúng ta đã thử nạp vào tính năng của đối tượng Gun.

Tuy nhiên, hàm loadFeatures() của lớp Loader nhận con trỏ trỏ tới đối tượng của lớp Weapon làm đối số:

void loadFeatures(Weapon *weapon)

Đó là lý do tại sao tính năng vũ khí được nạp tới 3 lần. Để giải quyết vấn đề này, bạn cần biến một hàm thuộc lớp chính (lớp Weapon) thành hàm ảo sử dụng từ khóa virtual.

class Weapon
{
    public:
      virtual void features()
         { cout << "Loading weapon features.n"; }
};

Ví dụ: Sử dụng hàm ảo để giải quyết vấn đề

#include <iostream>
using namespace std;

class Weapon
{
    public:
      virtual void features()
         { cout << "Loading weapon features.n"; }
};

class Bomb : public Weapon
{
    public:
       void features()
         { cout << "Loading bomb features.n"; }
};

class Gun : public Weapon
{
    public:
       void features()
         { cout << "Loading gun features.n"; }
};

class Loader
{
   public:
     void loadFeatures(Weapon *weapon)
     {
        weapon->features();
     }     
};

int main()
{
    Loader *l = new Loader;
    Weapon *w;
    Bomb b;
    Gun g;

    w = &b;
    l->loadFeatures(w);

    w = &g;
    l->loadFeatures(w);

    return 0;
}

Đầu ra

Loading weapon features.

Loading bomb features.

Loading gun features.

Ngoài ra, cũng nên chú ý rằng hàm l->loadFeatures(w) sẽ gọi hàm của các lớp khác nhau phụ thuộc vào đối tượng mà đối tượng l trỏ tới.

Sử dụng hàm ảo khiến đoạn mã của chúng ta không chỉ rõ ràng hơn mà còn khả chuyển hơn.

Nếu chúng ta muốn thêm vào một vũ khí khác (ví dụ như dao), chúng ta có thể dễ dàng thêm vào và nạp tính năng của nó. Bằng cách nào ư?

class Knife : public Weapon
{
    public:
       void features()
         { cout << "Loading knife features.n"; }
};

Và trong hàm main()

Knife k; 
w = &k; 
l->loadFeatures(w);

Cần chú ý rằng chúng ta không hề thay đổi thứ gì trong lớp Loader để nạp tính năng của dao.

Lớp trừu tượng và hàm ảo thuần trong C++

Mục đích của lập trình hướng đối tượng là chia một vấn đề phức tạp thành các tập nhỏ hơn. Điều này giúp hiểu và giải quyết vấn đề theo một cách hiệu quả.

Đôi khi, cần thiết phải sử dụng kế thừa chỉ để có cái nhìn rõ ràng hơn về vấn đề.

Trong C++, bạn có thể tạo một lớp trừu tượng mà không thể được khởi tạo (bạn không thể tạo một đối tượng của lớp này). Tuy nhiên, bạn có thể kế thừa một lớp từ lớp này, và khởi tạo đối tượng của lớp kế thừa đó.

Lớp trừu tượng là lớp chính mà không thể được khởi tạo.

Một lớp chứa hàm ảo thuần cũng được gọi là lớp trừu tượng.

Hàm ảo thuần

Một hàm ảo có lời khai báo kết thúc bằng =0 được gọi là hàm ảo thuần. Ví dụ:

class Weapon
{
    public:
      virtual void features() = 0;
};

Ở đây, hàm ảo thuần là:

virtual void features() = 0

Và lớp Weapon là một lớp trừu tượng.

Ví dụ: Lớp trừu tượng và hàm ảo thuần

#include <iostream>
using namespace std;

// lớp trừu tượng
class Shape                   
{
    protected:
       float l;
    public:
       void getData()       
       {
           cin >> l;
       }
       
       // hàm ảo
       virtual float calculateArea() = 0;
};

class Square : public Shape
{
    public:
       float calculateArea()
       {   return l*l;  }
};

class Circle : public Shape
{
    public:
       float calculateArea()
       { return 3.14*l*l; }
};

int main()
{
    Square s;
    Circle c;

    cout << "Enter length to calculate the area of a square: ";
    s.getData();
    cout<<"Area of square: " << s.calculateArea();
    cout<<"nEnter radius to calculate the area of a circle: ";
    c.getData();
    cout << "Area of circle: " << c.calculateArea();

    return 0;
}

Đầu ra

Enter length to calculate the area of a square: 4

Area of square: 16

Enter radius to calculate the area of a circle: 5

Area of circle: 78.5

Trong chương trình này, hàm ảo thuần virtual float area() = 0; được định nghĩa trong lớp Shape.

Một điều quan trọng cần lưu ý đó là, bạn nên ghi đè hàm ảo thuần của lớp chính trong lớp kế thừa. Nếu bạn không làm vậy, lớp kế thừa cũng sẽ trở thành một lớp trừu tượng.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *