首页 C++ 虚函数
文章
取消

C++ 虚函数

虚函数

C ++ 虚函数是一种实现多态的机制,它可以让基类的指针或引用调用派生类的成员函数。虚函数的作用是实现运行时决议,即根据指针或引用所指向或绑定的对象的实际类型动态的确定调用哪个函数。虚函数依赖于虚函数表工作,虚函数表用来保存虚函数地址,当我们用基类指针指向派生类时,虚表指针指向派生类的虚函数表

定义虚函数的方法实在函数声明前加上关键字 virtual :

1
2
3
4
5
class Animal{
public:
    virtual void makeSound();  // 虚函数
    //virtual void makeSound() = 0;  // 纯虚函数
}

重写虚函数的方法是在派生类中使用相同的函数名和参数列表,并且可以加上关键字 override 来检查是否正确重写了基类的虚函数

如果定义成纯虚函数,则派生类必须使用 override 来实现,且基类不能被实例化

1
2
3
4
class Dog : public Animal{
public:
    void makeSound() override;
}

调用虚函数的方法有三种:对象名、指针和引用

1
2
3
4
5
6
7
8
9
Animal a; // 基类对象
Dog d; // 派生类对象
Animal *pa = &a; // 基类指针指向基类对象
Animal *pd = &d; // 基类指针指向派生类对象

a.makeSound(); // 调用基类的makeSound()
d.makeSound(); // 调用派生类的makeSound()
pa->makeSound(); // 调用基类的makeSound()
pd->makeSound(); // 调用派生类的makeSound()

通过对象名调用虚函数时,会根据对象的类型确定调用哪个函数,这是静态关联。通过指针或引用调用虚函数时,会根据指针或引用所指向或绑定的对象确定调用哪个函数,这是动态关联。

为了实现动态关联,C++ 编译器会为每个含有虚函数的类生成一个虚函数表(virtual function table),并为每个该类的对象分配一个虚指针(virtual pointer),指向该表。虚函数表中存放了该类所有虚函数的地址,而虚指针则在运行时根据对象所属的类型进行更新。当通过指针或引用调用虚函数时,编译器会先通过该指针或引用找到对应对象的虚指针,然后通过该虚指针找到对应类的虚函数表,最后通过该表找到对应虚函数并执行

下面是一个例子:

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
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void makeSound() { cout << "Animal sound" << endl; }
};

class Dog : public Animal {
public:
    void makeSound() override { cout << "Woof" << endl; }
};

int main() {
    Animal a;
    Dog d;
    Animal *pa = &a;
    Animal *pd = &d;

    cout << "sizeof(Animal) = " << sizeof(Animal) << endl;
    cout << "sizeof(Dog) = " << sizeof(Dog) << endl;

    a.makeSound();
    d.makeSound();
    pa->makeSound();
    pd->makeSound();

    return 0;
}

输出结果:

1
2
3
4
5
6
sizeof(Animal) = 8
sizeof(Dog) = 8
Animal sound
Woof
Animal sound
Woof

从输出结果可以看出,Animal 和 Dog 都占 8 个字节,其中 4 个字节是虚指针,另外 4 个字节是对象的其他数据(如果有的话)。虚指针在运行时会指向对象所属类的虚函数表,而虚函数表中存放了该类所有虚函数的地址 。

当通过对象名调用虚函数时,编译器会根据对象的类型确定调用哪个函数,这是静态关联。例如, a.makeSound() 会调用 Animal 类的 makeSound() 函数,而 d.makeSound() 会调用 Dog 类的 makeSound() 函数。

当通过指针或引用调用虚函数时,编译器会先通过该指针或引用找到对应对象的虚指针,然后通过该虚指针找到对应类的虚函数表,最后通过该表找到对应虚函数并执行 。

例如,pa->makeSound() 会先通过 pa 找到 a 对象的虚指针,然后通过该虚指针找到 Animal 类的虚函数表,最后通过该表找到 Animal 类的 makeSound() 函数并执行。同理,pd->makeSound() 会先通过 pd 找到 d 对象的虚指针,然后通过该虚指针找到 Dog 类的虚函数表,最后通过该表找到 Dog 类的 makeSound() 函数并执行。

虚函数表如何找到对应的虚函数

虚函数表是一个存放虚函数地址的数组,每个虚函数在虚函数表中都有一个固定的位置,这个位置由编译器在编译时确定。当通过指针或引用调用虚函数时,编译器会根据指针或引用的类型和虚函数的名字和参数列表,计算出虚函数在虚函数表中的偏移量,然后通过该偏移量找到对应的虚函数地址,并执行该地址所指向的代码。

例如,如果 Animal 类有两个虚函数 makeSound() 和 eat() ,并且 makeSound() 在虚函数表中的位置是 0,eat() 在虚函数表中的位置是 1 ,那么当通过 Animal* 类型的指针 pa 调用 pa->makeSound() 时,编译器会计算出 makeSound() 在虚函数表中的偏移量是 0,然后通过 pa 找到对象的虚指针,再通过该虚指针找到对象所属类的虚函数表,最后通过该表加上偏移量 0 找到对应的虚函数地址,并执行该地址所指向的代码。

虚函数与纯虚函数

虚函数和纯虚函数是 C++ 中的重要概念,它们都可以实现多态性,但是有一些区别:

  • 虚函数是在基类中用 virtual 关键字声明的函数,它可以在派生类中被重写(override),从而实现动态绑定。虚函数既有定义,也有实现的代码。
  • 纯虚函数是在基类中用 virtual 关键字声明,并且在后面加上 = 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
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;

// 基类
class Animal {
public:
    // 虚函数
    virtual void eat() {
        cout << "Animal is eating." << endl;
    }
    // 纯虚函数
    virtual void speak() = 0;
};

// 派生类
class Dog : public Animal {
public:
    // 重写虚函数
    void eat() override {
        cout << "Dog is eating." << endl;
    }
    // 实现纯虚函数
    void speak() override {
        cout << "Dog is barking." << endl;
    }
};

int main() {
    // Animal a; // 错误,不能创建抽象类的对象
    Animal* p = new Dog(); // 正确,可以创建指向派生类的指针
    p->eat(); // 输出 Dog is eating.
    p->speak(); // 输出 Dog is barking.
    delete p;
    return 0;
}

写在最后

感谢你在茫茫人海中找到我🕵🏼

🎉你是第 个读者

㊗️ 你平安喜乐,顺遂无忧!

希望你读完有所收获~

🥂🥂🥂

本文由作者按照 CC BY 4.0 进行授权

TinyWebServer 相关函数使用与样例 [日志]

C++ 封装、继承 与 多态