Cpp中的虚函数与纯虚函数

虚函数

1
2
3
4
5
6
class Animal {
public:
virtual void speak() {
std::cout << "Animal speaks" << std::endl;
}
};

虚函数在基类中使用virtual关键字声明成员函数,并允许子类重写该函数,以提供特定于子类的实现。通俗一点的讲:

  • 如果子类没有重写该函数(即提供虚函数的实现),将会自动调用基类的缺省虚函数实现
  • 如果子类重写了该函数,则调用重写后的实现

创建虚函数时,虚函数所在类在申请内存时会同时生成虚函数指针vptr,该指针指向“虚函数表”vtbl,表中数据为函数指针,存储了对应虚函数具体实现所对应的位置。

因此,虚函数的实现过程是:

  1. 通过对象内存中的虚函数指针vptr找到虚函数表vtbl
  2. 通过vtbl中的函数指针找到对应虚函数的实现区域并进行调用

示例

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
#include <iostream>

class Animal {
public:
//声明虚函数
virtual void speak() {
std::cout << "Animal speaks" << std::endl;
}
};

class Cat : public Animal {
public:
//重写基类中声明的虚函数
void speak() override {
std::cout << "Meow" << std::endl;
}
};

class Dog : public Animal {
public:
//重写基类中声明的虚函数
void speak() override {
std::cout << "Bark" << std::endl;
}
};


int main() {
Animal* cat = new Cat();
cat->speak(); //这里的speak()来源是Cat类,而非Animal类

Animal* dog = new Dog();
dog->speak(); //这里的speak()来源是Dog类,而非Animal类

return 0;
}

由于speak()为虚函数,cat->speak()会调用Cat::speak()而非Animal::speak()。因为虚函数是动态绑定(运行时多态)的,其根据对象的实际类型(Cat)来决定调用哪个版本的speak()

纯虚函数

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

在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象(“一个动物”是什么?)明显不合常理。而针对每种动物的方法又有所不同,此时需要使用多态特性,也就需要在基类中定义虚函数。

纯虚函数是在基类中声明的虚函数,它要求任何派生类都要定义自己的实现方法,以实现多态性。实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数。

定义纯虚函数是为了实现一个接口,用来规范派生类的行为,也即规范继承这个类的程序员必须实现这个函数。派生类仅仅只是继承函数的接口。纯虚函数的意义在于,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但基类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。对于动物这个基类而言,它必须是某一个种类,可以是鸟、狗、猫,但不能什么都不是。这个“种类”就可以作为纯虚函数在基类Animal中声明。

注意:含有纯虚函数的类称之为抽象类,它不能生成对象(创建实例),只能创建它的派生类的实例。抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。

抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。

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
#include <iostream>

class Animal {
public:
virtual void speak() {
std::cout << "Animal speaks" << std::endl;
}

//一个动物要么是活的,要么是死的,子类中必须定义这个属性,因此是纯虚函数
virtual void isalive() = 0;
};

class Cat : public Animal {
public:
void speak() override {
std::cout << "Meow" << std::endl;
}

//重写基类Animal中的isalive()纯虚函数
void isalive() override {
std::cout << "Cat is alive" << std::endl;
}
};

class Dog : public Animal {
public:
void speak() override {
std::cout << "Bark" << std::endl;
}

//重写基类Animal中的isalive()纯虚函数
void isalive() override {
std::cout << "Dog is alive" << std::endl;
}
};


int main() {
Animal* cat = new Cat();
cat->speak();
cat->isalive();

Animal* dog = new Dog();
dog->speak();
dog->isalive();

return 0;
}

如果这里使用Animal* animal = new Animal(),编译器将报错,因为Animal包含纯虚函数,已经是一个抽象类。


Cpp中的虚函数与纯虚函数
http://akichen891.github.io/2025/02/15/Cpp中的虚函数与纯虚函数/
作者
Aki
发布于
2025年2月15日
更新于
2025年2月18日
许可协议