0%

浅谈继承中函数有无virtual的影响

本文讨论了一下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
#include<iostream>

using namespace std;

class A
{
public:
/* virtual */ void func()
{
cout<<"I'm A!";
}
};

class B:public A
{
public:
void func()
{
cout<<"I'm B!";
}
};

int main(int argc, char const *argv[])
{
A a;
B b;
A* p = &b;
p->func();

// a.func();
// b.func();

return 0;
}

运行的输出结果是:I’m A!。如果p的声明类型是B*,那么输出结果是:I’m B!。这里基类A的func方法并没有加virtual关键字。如果我们加上virtual,那么结果就会变成,无论p的声明是A*还是B*,输出结果都是:I’m B!。

但是如果我们在这里直接通过对象本身a或b调用他们的func方法,那么结果就是调用它们各自类型中定义的func,也就是a.func()输出“I’m A!”。b.func()输出“I’m B!”。

于是我们可以总结一下,virtual只是对于通过指针引用也行,大家可以自行实验)来调用父类子类的方法有影响。在父类的func前加上virtual的情况下,通过指针(或引用)调用该方法,实际调用的是该指针所指向的实际的类中定义的方法。这也就是动态绑定的意思,也就是在运行的过程中动态确定指针(或引用)所指向的实际对象是什么(是父类还是子类)并调用实际对象的方法。相对的,静态绑定就是仅仅根据声明的指针或引用的类型来决定调用的方法,即静态绑定到了声明对象的类型上。

因此,无论父类的方法加不加virtual,通过对象本身调用重载过的方法都是对象本身定义的方法。

至于为什么动态绑定和静态绑定只是针对使用指针和引用的情况,个人觉得这是因为指针/引用在实现多态、模板的时候具有很大的用处。

这里有一条建议,来自《Effective C++ 》:

绝对不要重新定义继承而来的非虚(non-virtual)函数

也就是说,本次实验的原始代码中B类重载A的非虚函数func的做法是不好的,这里仅仅是为了实验。具体原因嘛,由于篇幅限制,就不展开了,请各位自行搜索。

PS: 这里所说的静态绑定、动态绑定的含义仅仅是针对程序运行的结果来说的(主要是便于理解其实际影响),这两个概念的背后其实还有一些更深入的知识点,例如:静态类型、动态类型、编译期、运行期。想要了解的话也请各位自行搜索。