Các hàm ảo trong C++

Trong hướng dẫn này, chúng ta sẽ tìm hiểu về hàm ảo C ++ và cách sử dụng nó với sự trợ giúp của các ví dụ.

Một hàm ảo là một hàm thành viên trong lớp cơ sở mà chúng ta mong muốn xác định lại trong các lớp dẫn xuất.

Về cơ bản, một hàm ảo được sử dụng trong lớp cơ sở để đảm bảo rằng hàm được ghi đè . Điều này đặc biệt áp dụng cho các trường hợp con trỏ của lớp cơ sở trỏ đến một đối tượng của lớp dẫn xuất.

Ví dụ: hãy xem xét đoạn mã dưới đây:

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

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

Sau đó, nếu chúng ta tạo một con trỏ Basekiểu để trỏ đến một đối tượng của Derivedlớp và gọi print()hàm, nó sẽ gọi print()hàm của Baselớp.

Nói cách khác, hàm thành viên của Basekhông bị ghi đè.

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

    // calls function of Base class
    base1->print();

    return 0;
}

Để tránh điều này, chúng ta khai báo print()hàm của Baselớp là ảo bằng cách sử dụng virtualtừ khóa.

class Base {
   public:
    virtual void print() {
        // code
    }
};

Các hàm ảo là một phần không thể thiếu của tính đa hình trong C ++. Để tìm hiểu thêm, hãy xem hướng dẫn của chúng tôi về Đa hình C ++ .

Ví dụ 1: Hàm ảo C ++

#include <iostream>
using namespace std;

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

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

int main() {
    Derived derived1;

    // pointer of Base type that points to derived1
    Base* base1 = &derived1;

    // calls member function of Derived class
    base1->print();

    return 0;
}

Đầu ra

Derived Function

Ở đây, chúng tôi đã khai báo print()chức năng của Baseas virtual.

Vì vậy, hàm này bị ghi đè ngay cả khi chúng ta sử dụng một con trỏ Basekiểu trỏ đến Derivedđối tượng dẫn xuất1 .Hoạt động của các hàm ảo trong C ++

Ghi đè mã định danh C ++

C ++ 11 đã cung cấp cho chúng ta một mã định danh mới overriderất hữu ích để tránh lỗi khi sử dụng các hàm ảo.

Định danh này chỉ định các hàm thành viên của các lớp dẫn xuất ghi đè hàm thành viên của lớp cơ sở.

Ví dụ,

class Base {
   public:
    virtual void print() {
        // code
    }
};

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

Nếu chúng ta sử dụng một nguyên mẫu hàm trong Derivedlớp và xác định hàm đó bên ngoài lớp, thì chúng ta sử dụng đoạn mã sau:

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

// function definition
void Derived::print() {
    // code
}

Sử dụng ghi đè C ++

Khi sử dụng các hàm ảo, có thể mắc lỗi trong khi khai báo các hàm thành viên của các lớp dẫn xuất.

Việc sử dụng mã overrideđịnh danh sẽ nhắc trình biên dịch hiển thị thông báo lỗi khi những lỗi này được thực hiện.

Nếu không, chương trình sẽ biên dịch đơn giản nhưng hàm ảo sẽ không bị ghi đè.

Một số sai lầm có thể xảy ra là:

  • Functions with incorrect names: For example, if the virtual function in the base class is named print(), but we accidentally name the overriding function in the derived class as pint().
  • Functions with different return types: If the virtual function is, say, of void type but the function in the derived class is of int type.
  • Functions with different parameters: If the parameters of the virtual function and the functions in the derived classes don’t match.
  • No virtual function is declared in the base class.

Sử dụng các hàm ảo C ++

Giả sử chúng ta có một lớp cơ sở Animalvà các lớp dẫn xuất Dogvà Cat.

Giả sử mỗi lớp có một thành viên dữ liệu có tên là kiểu . Giả sử các biến này được khởi tạo thông qua các hàm tạo tương ứng của chúng.

class Animal {
   private:
    string type;
    ... .. ...
    public:
      Animal(): type("Animal") {}
    ... .. ...
};

class Dog : public Animal {
   private:
    string type;
    ... .. ...
    public:
      Animal(): type("Dog") {}
    ... .. ...
};

class Cat : public Animal {
   private:
    string type;
      ... .. ...
    public:
      Animal(): type("Cat") {}
    ... .. ...
};

Bây giờ, chúng ta hãy giả sử rằng chương trình của chúng ta yêu cầu chúng ta tạo hai publichàm cho mỗi lớp:

  1. getType()để trả về giá trị của kiểu
  2. print()để in giá trị của loại

Chúng ta có thể tạo cả hai hàm này trong mỗi lớp riêng biệt và ghi đè chúng, điều này sẽ rất lâu và tẻ nhạt.

Hoặc chúng ta có thể tạo getType() ảo trong Animallớp, sau đó tạo một hàm duy nhất, riêng biệt print()chấp nhận một con trỏ Animalkiểu làm đối số của nó. Sau đó, chúng ta có thể sử dụng một hàm này để ghi đè lên hàm ảo.

class Animal {
    ... .. ...
   public:
    ... .. ...
    virtual string getType {...}
};

... .. ...
... .. ...

void print(Animal* ani) {
    cout << "Animal: " << ani->getType() << endl;
}

Điều này sẽ làm cho mã ngắn hơn , rõ ràng hơn và ít lặp lại hơn .

Ví dụ 2: Trình diễn hàm ảo C ++

// C++ program to demonstrate the use of virtual function

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

class Animal {
   private:
    string type;

   public:
    // constructor to initialize type
    Animal() : type("Animal") {}

    // declare virtual function
    virtual string getType() {
        return type;
    }
};

class Dog : public Animal {
   private:
    string type;

   public:
    // constructor to initialize type
    Dog() : type("Dog") {}

    string getType() override {
        return type;
    }
};

class Cat : public Animal {
   private:
    string type;

   public:
    // constructor to initialize type
    Cat() : type("Cat") {}

    string getType() override {
        return type;
    }
};

void print(Animal* ani) {
    cout << "Animal: " << ani->getType() << endl;
}

int main() {
    Animal* animal1 = new Animal();
    Animal* dog1 = new Dog();
    Animal* cat1 = new Cat();

    print(animal1);
    print(dog1);
    print(cat1);

    return 0;
}

Đầu ra

Animal: Animal
Animal: Dog
Animal: Cat

Ở đây, chúng tôi đã sử dụng hàm ảo getType()và một Animalcon trỏ ani để tránh lặp lại print()hàm trong mọi lớp.

void print(Animal* ani) {
    cout << "Animal: " << ani->getType() << endl;
}

Trong main(), chúng tôi đã tạo ra 3 Animalgợi ý để tự động tạo các đối tượng của AnimalDogvà Catcác lớp học.

// dynamically create objects using Animal pointers
Animal* animal1 = new Animal();
Animal* dog1 = new Dog();
Animal* cat1 = new Cat();

Sau đó, chúng tôi gọi print()hàm bằng cách sử dụng các con trỏ sau:

  1. Khi print(animal1)được gọi, con trỏ trỏ đến một Animalđối tượng. Vì vậy, hàm ảo trong Animallớp được thực thi bên trong print().
  2. Khi print(dog1)được gọi, con trỏ trỏ đến một Dogđối tượng. Vì vậy, hàm ảo bị ghi đè và hàm của Dogđược thực thi bên trong print().
  3. Khi print(cat1)được gọi, con trỏ trỏ đến một Catđối tượng. Vì vậy, hàm ảo bị ghi đè và hàm của Catđược thực thi bên trong print().








Gõ tìm kiếm nhanh...