post on 12 May 2025 about 5843words require 20min
CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
2025-05-12-虚函数
参考资源
对 C++ 虚函数不了解?看完这篇文章掌握虚函数的原理和作用
虚函数是在父类中定义的一种特殊类型的函数,允许子类重写该函数以适应其自身需求。虚函数的调用取决于对象的实际类型,而不是指针或引用类型。通过将函数声明为虚函数,可以使继承层次结构中的每个子类都能够使用其自己的实现,从而提高代码的可扩展性和灵活性。在 C++ 中,使用关键字”virtual”来定义虚函数。
虚函数虚在所谓”推迟联编”或者”动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为”虚”函数。
虚函数可以让子类对象在运行时动态地继承和修改父类的成员函数,使得代码更加灵活、可重用,并且可以实现多态性和抽象类等高级特性。
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 形状基类,定义了计算面积和周长的虚函数
class Shape {
public:
// 计算面积的虚函数,提供默认实
// 使用virtual关键字,允许派生类重写
virtual double calculateArea() {
return 0.0; // 基类提供默认实现
}
// 计算周长的虚函数,提供默认实现
// 使用virtual关键字,允许派生类重写
virtual double calculatePerimeter() {
return 0.0; // 基类提供默认实现
}
// 虚析构函数,确保派生类对象正确释放
virtual ~Shape() {} // 虚析构函数
};
// 矩形类,继承自Shape基类
class Rectangle : public Shape {
private:
// 矩形的私有成员:宽和高
double width;
double height;
public:
// 构造函数,初始化矩形的宽和高
Rectangle(double w, double h) : width(w), height(h) {}
// 重写基类的calculateArea虚函数
// override关键字确保正确重写基类虚函数
double calculateArea() override {
return width * height; // 矩形面积计算
}
// 重写基类的calculatePerimeter虚函数
double calculatePerimeter() override {
return 2 * (width + height); // 矩形周长计算
}
};
// 圆形类,继承自Shape基类
class Circle : public Shape {
private:
// 圆形的私有成员:半径
double radius;
// 圆周率常量
const double PI = 3.14159;
public:
// 构造函数,初始化圆形的半径
Circle(double r) : radius(r) {}
// 重写基类的calculateArea虚函数
// override关键字确保正确重写基类虚函数
double calculateArea() override {
return PI * radius * radius; // 圆形面积计算
}
// 重写基类的calculatePerimeter虚函数
double calculatePerimeter() override {
return 2 * PI * radius; // 圆形周长计算
}
};
虚函数只能借助于指针或者引用来达到多态的效果。
多态的本质是“同一个函数调用,能够根据不同对象表现出不同的行为
从简单的例子开始
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal
{
public:
virtual void shout()
{
cout << "动物叫了一声\n";
}
};
class Cat : public Animal
{
public:
void shout() override
{
cout << "喵喵喵\n";
}
};
解释:
Animal
基类定义了一个虚函数 shout()
,为派生类提供了一个默认实现。Cat
类继承自 Animal
,使用 override
关键字重写了 shout()
方法,展示了虚函数允许派生类提供自己的特定实现。需要注意的是,在子类中重写虚函数时,其访问权限不能更严格(即不能由 public 变为 private 或 protected),否则编译器会报错。
纯虚函数是指在基类中定义的没有实现的虚函数。使用纯虚函数可以使该函数只有函数原型,而没有具体的实现。注:这里的“=0”表示该函数为纯虚函数。
纯虚函数的作用是让子类必须实现该函数,并且不能直接创建该类对象(即该类为抽象类)。
1
virtual void func() = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Shape {
public:
// 纯虚函数,没有默认实现
virtual double calculateArea() = 0;
// 包含纯虚函数的类成为抽象类
virtual void draw() = 0;
};
class Rectangle : public Shape {
public:
// 必须实现基类的纯虚函数
double calculateArea() override {
return width * height;
}
void draw() override {
// 绘制矩形的具体实现
}
private:
double width;
double height;
};
解释:
Shape
基类定义了两个纯虚函数 calculateArea()
和 draw()
,使 Shape
成为一个抽象类。= 0
)没有默认实现,强制派生类必须提供具体实现。Rectangle
类继承自 Shape
,必须实现所有纯虚函数,否则仍将是抽象类。抽象类是包含纯虚函数的类,它们不能被实例化,只能被继承。抽象类只能用作其他类的基类。如果一个类继承了抽象类,则必须实现所有的纯虚函数,否则该类也会成为抽象类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Shape
{
public:
_ // 纯虚函数_
virtual double getArea() = 0;
};
_// 继承自抽象类Shape_
class Rectangle : public Shape
{
public:
double **width**;
double **height**;
double getArea() { return **width** * **height**; }
};
_// 继承自抽象类Shape_
class Circle : public Shape
{
public:
double **radius**;
double getArea() { return 3.14 * **radius** * **radius**; }
};
Shape 为抽象类,其中包含纯虚函数 getArea(),Rectangle 和 Circle 均继承自 Shape,并且实现了 getArea()函数的具体内容。
在多重继承中,如果一个类同时继承了多个基类,而这些基类中都有同名的虚函数,那么子类必须对这些虚函数进行重写并实现。此时,需要使用作用域限定符来指明重写的是哪个基类的虚函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Base1
{
public:
virtual void func() { cout << "Base1::func()" << endl; }
};
class Base2
{
public:
virtual void func() { cout << "Base2::func()" << endl; }
};
class Derived : public Base1, public Base2
{
public:
virtual void func()
{
Base1::func();
Base2::func();
}
};
派生类 Derived 同时继承了 Base1 和 Base2,这两个基类中都有名为 func 的虚函数。在 Derived 中,我们通过使用作用域限定符 Base1::和 Base2::,分别调用了两个基类中的虚函数。
优点:
缺点:
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <string>
// 基类Entity,定义了一个虚函数GetName()
class Entity {
public:
// 虚函数,返回一个默认的实体名称
// 使用virtual关键字,允许派生类重写
virtual std::string GetName() { return "Entity"; }
};
// 派生类Player,继承自Entity
class Player : public Entity {
private:
// 私有成员,存储玩家名称
std::string m_Name;
public:
// 构造函数,使用传入的名称初始化玩家
Player(const std::string& name) :m_Name(name) {};
// 重写基类的GetName()虚函数
// override关键字确保正确重写基类虚函数
std::string GetName() override { return m_Name; };
// 设置玩家名称的成员函数
void SetName(std::string name) { m_Name = name; };
};
// 打印实体名称的函数
// 接受一个Entity指针作为参数,体现了多态性
void PrintName(Entity* entity) {
// 调用虚函数GetName(),实际执行的是对象的具体实现
std::cout << entity->GetName() << std::endl;
};
int main() {
// 创建一个基类Entity对象的指针
Entity* e = new Entity();
// 调用PrintName(),将打印"Entity"
PrintName(e);
// 创建一个Player对象,名称为"tanke"
Player* p = new Player("tanke");
// 调用PrintName(),将打印"tanke"
PrintName(p);
// 修改Player对象的名称为"wangjie"
p->SetName("wangjie");
// 将Player指针赋值给基类指针,体现了多态性
Entity* e1 = p;
// 调用PrintName(),将打印"wangjie"
PrintName(p);
// 等待用户输入,保持窗口打开
std::cin.get();
return 0;
}
解释:
Entity
基类定义了一个虚函数 GetName()
,为派生类提供了一个默认实现。Player
类继承自 Entity
,并使用 override
关键字重写了 GetName()
方法。PrintName()
函数接受一个 Entity
指针,展示了 C++ 多态性的关键特征 - 可以通过基类指针调用派生类的具体实现。Related posts