为什么C++需要虚函数?

为什么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(); // 输出: "I'm eating generic food."
cat->eat(); // 输出: "I'm eating a rat."
animalCat->eat(); // 输出: "I'm eating a rat."

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; // 纯虚函数,使Animal成为抽象类
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关键字

如果在基类中忘记将函数声明为虚函数,那么通过基类指针调用该函数时,将不会发生动态绑定,而是调用基类的函数。

纯虚函数的使用

纯虚函数必须在派生类中实现,否则派生类将成为抽象类,不能实例化。

虚函数表的开销

每个包含虚函数的类都有一个虚函数表,每个对象都有一个指向虚函数表的指针,这会增加一定的内存开销。


为什么C++需要虚函数?
https://119291.xyz/posts/why-do-we-need-virtual-functions-in-cpp/
作者
ww
发布于
2025年5月28日
许可协议