一.前言
在使用C#开发的过程中,我们经常可以看到virtual关键字,这个关键字具体的使用场景是有类做实现继承,父类通过virtual设置虚方法,子类可以通过override来重写该方法。
二.编译原理
一般函数在编译时就静态地编译到执行文件中,其相对地址在程序运行期间是不发生变化。虚函数在编译期间是不被静态编译的,它的相对地址是不确定的,它在运行期间根据对象实例来判断要调用的函数。
三.实例讲解
1.一般情况
这是一般的类实现继承,A和B类,B类继承A类,在A类和B类中都同时有一个相同名字的方法,在执行时会有什么影响?
public class A { public void Func() { Console.WriteLine("A"); } } public class B : A { public void Func() { Console.WriteLine("B"); } } //调用 public static void Main(string[] args) { A a = new A(); B b = new B(); a.Func(); b.Func(); a = new B(); a.Func(); }
输出的结果是。
A
B
A
第一个输出的结果是A,这个是毫无疑问的。
第二个输出的结果是B,这是根据什么判断的?B类继承A类,如果两个类有同名的方法,那子类就会隐藏掉父类的同名方法。在编译器也会提醒是隐藏继承成员的方法,有意为之的话,可以使用关键字new。
第三个输出的结果是A。这里先讲一下类申明、实例类的定义,如A a = new B();A为申明类,B为实例类。一般情况下,所调用的方法都是在申明类中的。
2.使用virtual关键字
在上面的例子第三个输出得知,实例化的对象去调用方法,如A a = new B(),且A、B类都有同名方法,一般情况下是调用申明类的方法。那其它情况就是有使用virtual关键字的情况了,使用了virtual关键字修饰的方法叫做虚方法。
上面讲过使用了virtual关键字,编译器会对虚方法指向的地址做处理,具体检查流程如下。
1.在调用对象的方法时,首先会判断该对象的申明类中的方法是否为虚方法,如果不是虚方法,那么就会直接调用。
2.如果是虚方法,那么它就会检查它的实例类是否有重新实现这个虚方法(使用override关键字),如果有,就直接调用实例类这个重新实现的方法。如果没有,则依次上溯,按照同样步骤对父类进行检查,直到找到重新实现该虚方法的父类,并调用方法。
例子:准备要使用的类。
public class A { public virtual void Func() { Console.WriteLine("A"); } } public class B : A { public override void Func() { Console.WriteLine("B"); } } public class C : B { } public class D : A { public new void Func() { Console.WriteLine("D"); } }
//调用 public static void Main(string[] args) { A c = new C(); c.Func(); }
输出结果是B。检查到申明类A的是虚拟方法,转去检查实现类C。C类没有重写(override)方法,转为检查C类的父类B类,B类有重写该方法,调用该方法。
另外,没有使用override的情况。
//调用 public static void Main(string[] args) { A d = new D(); d.Func(); }
输出结果是A。虽然实现类有Func,但由于没有(重写)override,还是会转去检查父类,最终检查回A类的话,就会直接调用这个virtual方法。
四.总结
1.virtual关键字在实现继承的时候使用。
2.在申明类定义了虚方法,那么它就会去判断实现类是否有重写(override)该方法,并调用之。
3.延伸知识,在Java中,所有类的方法都是默认virtual。