C# 虚函数

观前提示:

  1. 示例代码是我从程序中扒出来的,可以运行,但不能复制粘贴就直接运行。需要改一下命名空间之类的。主要是用来帮助解释代码运行过程。
  2. virtualTest.vt1A b = new virtualTest.vt1B();其中前面的类virtualTest.vt1A在下文被称为声明类;后面的virtualTest.vt1B()被称为实例类

基础示例

public class vt1A 
{
    public virtual void Fun() /* 使用修饰符virtual,表明这是一个虚函数 */
    {
        Console.WriteLine("Fun in Class vt1A");
    }

    public void AAA() 
    {

    }
}

public class vt1B: vt1A
{
    public override void Fun()/* 使用修饰符override重写父类的虚函数 */
    {
        Console.WriteLine("Fun in Class vt1B");
    }

    // public void AAA() {}
    // public override void AAA() {}
    // 报错,C#是不能随便在子类重写父类方法的。
}

public class vt1C : vt1B 
{
    /* 有继承关系,但没有重写虚函数 */
}

public class vt1D : vt1A 
{
    public new void Fun()/* 使用修饰符new创建函数,表明这是一个与父类函数同名的子类独立函数。并不是虚函数的重写 */
    {
        Console.WriteLine("Fun in Class vt1D");
    }
}

/// <summary>
/// virtual修饰符,基础验证
/// </summary>
private static void virtualTestNormal()
{
    Console.WriteLine("virtualTestNormal Start");

    /*
	* virtual修饰符基础知识
	* 用virtual修饰的函数表示为虚函数。
	* 类中的所有函数默认不是虚函数,只有用virtual修饰的才是虚函数。
	* 使用虚函数的注意事项:
	* 调用一个对象的虚函数时,系统首先检查该对象的声明类。判断调用的函数是否是虚函数。
	* 如果不是虚函数,就立刻执行函数。如果是虚函数,系统就不会执行该函数,而是去检查该对象的实例类。
	* 系统会检查该实例类中是否重写(override)该虚函数。
	* 如果实例类中重写了虚函数,那么立刻执行实例类中重写的函数。
	* 如果实例类没有重写该虚函数,系统就会去检查该实例类的父类。检查该实例类的父类有没有重写该虚函数。
	* 如果重写了,立刻执行。没重写,继续寻找父类的父类。
	* 直到找到第一个重写了该虚函数的类为止(也有可能找到最初的基类,执行的就是基类中virtual修饰的函数)。
	*/

    virtualTest.vt1A a = new virtualTest.vt1A();
    virtualTest.vt1A b = new virtualTest.vt1B();
    virtualTest.vt1A c = new virtualTest.vt1C();
    virtualTest.vt1A d = new virtualTest.vt1D();

    a.Fun();
    // 输出结果:Fun in Class vt1A。
    // 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
    // 2.寻找它的实例类,还是vt1A。
    // 3.执行vt1A中的方法
    b.Fun();
    // 输出结果:Fun in Class vt1B。
    // 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
    // 2.寻找它的实例类,也就是vt1B。
    // 3.执行vt1B中的方法
    c.Fun();
    // 输出结果:Fun in Class vt1B。
    // 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
    // 2.寻找它的实例类,也就是vt1C。
    // 3.vt1C中没有重写Fun方法(也就是没有override Fun)。
    // 4.寻找vt1C的父类,也就是vt1B,检查vt1B中有没有重写Fun
    // 5.vt1B中发现了重写Fun,执行。否则继续向vt1B的父类寻找
    d.Fun();
    // 输出结果:Fun in Class vt1A。
    // 1.先检查声明类vt1A,发现是virtual修饰的虚方法。
    // 2.寻找它的实例类,也就是vt1D。
    // 3.vt1D中虽然有Fun方法,但没有用override修饰,反而使用了new。说明该方法是与其父类同名的独立方法。所以还是没有重写
    // 4.寻找vt1D的父类,就是声明类vt1A本身,找到Fun方法,执行

    // 如何执行vt1D中使用new修饰的Fun方法?
    // 直接声明类型为vt1D
    virtualTest.vt1D newd = new virtualTest.vt1D();
    newd.Fun();


    Console.WriteLine("End virtualTestNormal");
}

static void Main(string[] args)
{
    Console.WriteLine("Hello World!");

    virtualTestNormal();

    Console.ReadKey();
}

几个问题

1.virtual修饰的虚函数必须在子类重写吗?

答:不必须,可以不重写,完全不理会。

2.子类中override重写的函数就肯定是虚函数吗?

答:也不一定,还有可能是abstract

3.重写虚函数必须用override吗?

答:是的。C#重写虚函数必须virtual与override配套使用。否则你不能在子类中定义与父类名称相同的函数。除非你用new修饰子类中的同名方法。当然new就代表这是子类中的一个独立新函数,与父类中的虚函数就没有关系了。不存在所谓重写的关系。

4.virtual与abstract的区别?

答:其实他们区别还是很大的,只不过都涉及重写会容易让人混淆。几个重点。①虚函数必须有实现(也就是{ }),抽象函数必须没有实现。②虚函数被不被子类重写都可以,抽象函数必须被子类重写;③virtual只能修饰函数不能修饰类,abstract既能修饰函数也能修饰类。

进阶示例

class Base 
{
    public Base()
    {
        PrintFields();
    }

    public virtual void PrintFields() 
    {

    }
}

class vt2A: Base
{
    public vt2A()
    {
        PrintFields();
    }

    public vt2A(string context)
    {
        Console.WriteLine("para:" + context);
        PrintFields();
    }

    public override void PrintFields() 
    {
        Console.WriteLine("sssss");
    }
}

class vt2B : vt2A
{
    int x = 1;
    int y;// int y 默认为0;

    public vt2B()
    {
        y = -1;
    }

    public override void PrintFields()
    {
        Console.WriteLine("x={0},y={1}", x, y);
    }
}

class vt2B1 : vt2A
{
    int x = 1;
    int y;// int y 默认为0;

    public vt2B1()
    {
        y = -1;
    }
}

private static void virtualTestAdvanced() 
{
    Console.WriteLine("virtualTestAdvanced Start");

    new virtualTest.vt2B();
    // 输出结果:x=1,y=0 ; x=1,y=0 输出两遍
    // 1.创建子类vt2B的对象,先找到其父类vt2A。
    // 2.然而vt2A也有父类,所以继续向上找,Base,是一个基类,开始调用构造函数。
    // 3.调用Base的构造函数,执行其中的函数PrintFields();
    // 4.然而PrintFields()是一个虚函数,所以首先要查看实例类vt2B中有没有重写,
    // 5.找到了vt2B中的重写函数,执行,输出一遍x=1,y=0。
    // 6.之后再接着调用vt2A中的构造函数,也是PrintFields()
    // 7.重复刚才的虚函数相关的判断,会再输出一个x=1,y=0。
    // 8.然后执行vt2B的构造函数,完成对象创建。

    new virtualTest.vt2B1();
    // 输出结果:sssss ; sssss 输出两遍
    // 所有过程与上述创建vt2B相同。
    // 只不过检查实例类是否重写虚函数的时候,vt2B1类中没有重写虚函数,所以向上检查它的父类是否有重写虚函数。
    // vt2B的父类vt2A中重写了虚函数,执行。

    Console.WriteLine("End virtualTestAdvanced");
}

static void Main(string[] args)
{
    Console.WriteLine("Hello World!");

    virtualTestAdvanced();

    Console.ReadKey();
}

几个问题

1.这个进阶测试的难点

答:没有声明类。

2.没有声明类怎么办?

答:涉及知识点。创建子类对象时,会先调用父类的构造函数,然后才调用子类本身的构造函数。

3.如果父类有多个构造函数,我们有没有指定使用哪个怎么办?

答:该情况下,系统会自动隐式调用父类的无参构造函数。

4.如果创建该实例的类中没有重写虚函数怎么办?

答:虚函数的判断方式与以前一样,依次向上寻找它的父类中有没有重写就行。