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ỏ Base
kiểu để trỏ đến một đối tượng của Derived
lớp và gọi print()
hàm, nó sẽ gọi print()
hàm của Base
lớp.
Nói cách khác, hàm thành viên của Base
khô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 Base
lớp là ảo bằng cách sử dụng virtual
từ 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 Base
as virtual
.
Vì vậy, hàm này bị ghi đè ngay cả khi chúng ta sử dụng một con trỏ Base
kiể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 override
rấ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 Derived
lớ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 aspint()
. - Functions with different return types: If the virtual function is, say, of
void
type but the function in the derived class is ofint
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ở Animal
và các lớp dẫn xuất Dog
và 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 public
hàm cho mỗi lớp:
getType()
để trả về giá trị của kiểuprint()
để 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 Animal
lớp, sau đó tạo một hàm duy nhất, riêng biệt print()
chấp nhận một con trỏ Animal
kiể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 Animal
con 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 Animal
gợi ý để tự động tạo các đối tượng của Animal
, Dog
và Cat
cá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:
- Khi
print(animal1)
được gọi, con trỏ trỏ đến mộtAnimal
đối tượng. Vì vậy, hàm ảo trongAnimal
lớp được thực thi bên trongprint()
. - Khi
print(dog1)
được gọi, con trỏ trỏ đến mộtDog
đối tượng. Vì vậy, hàm ảo bị ghi đè và hàm củaDog
được thực thi bên trongprint()
. - Khi
print(cat1)
được gọi, con trỏ trỏ đến mộtCat
đối tượng. Vì vậy, hàm ảo bị ghi đè và hàm củaCat
được thực thi bên trongprint()
.