为什么C++需要虚函数?
技术背景
在C++中,继承是一个重要的特性,它允许一个类(派生类)继承另一个类(基类)的属性和方法。然而,当通过基类指针或引用调用派生类对象的方法时,可能会出现一些问题。为了解决这些问题,C++引入了虚函数的概念。虚函数是实现运行时多态的关键,它允许在运行时根据对象的实际类型来决定调用哪个函数。
实现步骤
1. 定义基类和派生类
首先,定义一个基类,并在其中声明虚函数。然后,定义派生类,并重写基类的虚函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> using namespace std;
class Animal { public: virtual void eat() { cout << "I'm eating generic food." << endl; } };
class Cat : public Animal { public: void eat() override { cout << "I'm eating a rat." << endl; } };
CPP
|
2. 通过基类指针调用虚函数
在主函数中,创建基类和派生类的对象,并使用基类指针指向派生类对象,然后调用虚函数。
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main() { Animal* animal = new Animal(); Cat* cat = new Cat(); Animal* animalCat = cat;
animal->eat(); cat->eat(); animalCat->eat();
delete animal; delete cat; return 0; }
CPP
|
核心代码
虚函数实现运行时多态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <iostream> using namespace std;
class Animal { public: virtual void MakeTypicalNoise() = 0; virtual ~Animal() {}; };
class Cat : public Animal { public: void MakeTypicalNoise() override { cout << "Meow!" << endl; } };
class Dog : public Animal { public: void MakeTypicalNoise() override { cout << "Woof!" << endl; } };
class Doberman : public Dog { public: void MakeTypicalNoise() override { cout << "Woo, woo, woow! ... "; Dog::MakeTypicalNoise(); } };
int main() { Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };
const int cnAnimals = sizeof(apObject) / sizeof(Animal*); for (int i = 0; i < cnAnimals; i++) { apObject[i]->MakeTypicalNoise(); } for (int i = 0; i < cnAnimals; i++) { delete apObject[i]; } return 0; }
CPP
|
虚析构函数的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream> using namespace std;
class Animal { public: virtual ~Animal() { cout << "Deleting an Animal" << endl; } };
class Cat : public Animal { public: ~Cat() { cout << "Deleting an Animal name Cat" << endl; } };
int main() { Animal* a = new Cat(); delete a; return 0; }
CPP
|
最佳实践
设计抽象基类
将基类设计为抽象基类,包含纯虚函数,这样可以强制派生类实现这些函数,确保派生类具有必要的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Shape { public: virtual double area() const = 0; virtual ~Shape() {} };
class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14 * radius * radius; } };
CPP
|
使用override关键字
在派生类中重写虚函数时,使用override
关键字,这样可以确保重写的函数与基类的虚函数签名一致,避免因拼写错误等原因导致的问题。
合理使用虚析构函数
当基类指针指向派生类对象时,为了确保在删除基类指针时能够正确调用派生类的析构函数,避免内存泄漏,应将基类的析构函数声明为虚析构函数。
常见问题
虚函数的效率问题
虚函数的调用涉及到运行时的动态绑定,因此比普通函数调用稍微慢一些。但是,在现代编译器和硬件的支持下,这种性能损失通常是可以接受的。
忘记使用virtual关键字
如果在基类中忘记将函数声明为虚函数,那么通过基类指针调用该函数时,将不会发生动态绑定,而是调用基类的函数。
纯虚函数的使用
纯虚函数必须在派生类中实现,否则派生类将成为抽象类,不能实例化。
虚函数表的开销
每个包含虚函数的类都有一个虚函数表,每个对象都有一个指向虚函数表的指针,这会增加一定的内存开销。