面向对象语言中的虚函数

前段时间在写C#脚本时遇到了一些问题,具体是啥问题我现在也忘了,但是我还是发现了出现问题的原因,就是C#中虚函数和实函数是存在区别的。

何为虚函数

在编程语言中,一个函数的相对地址在运行过程中是不会改变的。但是面向对象语言为了多态性,通常希望基类的函数在调用时能够灵活根据自己的实例类来灵活的执行函数,所以加入了虚函数这个东西。参考下列C#代码:

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
using System;

namespace CsharpLearning
{
class Animal
{
static void Main(string[] args)
{
Animal animal_1 = new Dog();
Animal animal_2 = new Cat();
animal_1.Call();
animal_2.Call();
}
public void Call()
{
Console.WriteLine("Do nothing");
}
}

class Dog:Animal
{
public new void Call()
{
Console.WriteLine("Wah Wah!");
}
}
class Cat : Animal
{
public new void Call()
{
Console.WriteLine("Miao~");
}
}
}
/*
输出:
Do nothing
Do nothing
*/

这里调用了DogCat 两个实例类方法,可是调用的却是基类中的方法,所以输出的是两行Do nothing。原因是C#中的函数均为实函数(除了抽象函数),所以在通过声明类(基类)的方法调用时,只会调用这个基类中的方法。这也是为什么编译器要求在派生类的同签名函数前需要添加new关键字,否则会有警告的原因。

C#提供了virtualoverride关键字来声明和继承虚函数。在上面代码中做以下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[...]
//Animal类中
public virtual void Call()
{
[...]
}

//Dog类中
public override void Call()
{
[...]
}

//Cat类不变
public new void Call()
{

}
[...]
/*输出结果:
Wah Wah!
Do nothing
*/

基类中标记Call函数为虚函数,在通过声明类(基类)调用时会自动地向类继承树的深处寻找,直到找到对应的由override标记的函数为止,所以Dog类调用产生的结果符合我们的预期。但是这里Cat类使用了new关键字,表明这个函数与父类中的函数的不同的函数(即使它们签名相同),所以调用的结果是Do nothing。如果在继承类中没有添加new或者override关键字,编译器会产生一个警告,且默认该方法为new的方法。

其他语言

在C/C++中,和C#一样,默认函数为实函数,需要使用virtual关键字来声明虚函数。而Java中除final以外所有函数均为虚函数,直接继承就可以实现多态,不需要额外的声明。之前一直在用Java写MC mod,转到C#一下有点不适应,可能这就是遇到问题的原因吧。

我本来想探究一下python有没有虚函数和实函数的区别,但是打开之后发现Python是弱类型的语言,不存在声明类和实例类的区别,所以不需要关键字,直接继承自父类即可实现多态。


面向对象语言中的虚函数
http://zhouhf.top/2022/09/03/面向对象语言中的虚函数/
作者
周洪锋
发布于
2022年9月3日
许可协议