Unity之C#学习笔记(8):虚函数,方法重写和方法隐藏 Virtual Methods, Override and New

前篇链接:Unity之C#学习笔记(7):类的继承 Inheritance

在这节,我们要重点讲解几个容易混淆的概念:虚函数(Virtual Methods),方法隐藏(New)和方法重写(Override)。

续接前篇,我们现在要解决的问题是:让基类和派生类同名同参的方法做出不同行为,也就是表现出“多态”(Polymorphism)。

Tips 1:在开始前,我们先把另一个名称很相似的概念解决掉:方法重载(Overload)。方法重载指的是在同一个类中编写多个同名不同参的函数。调用时,程序可以根据调用者提供的参数列表自动匹配适合的函数。当你在VS中调用一个函数时看到“+x 重载”的提示时,就说明这个函数有多个重载。函数重载也是多态性的体现之一。
Tips 2:这节介绍的虚函数是实现多态的一种方法,但之后的章节中我们还会介绍C#中实现多态的另一种强大特性:接口(Interface)。

我们先来看虚函数(Virtual Methods)的概念。虚函数用关键字virtual修饰,它标志着这个函数在派生类中可能被重写(Override)。与虚函数相对的是我们之前写的没有virtual修饰的实函数。实函数在编译时就静态地编译到了执行文件中,其相对地址在程序运行期间是不发生变化的。而虚函数的相对地址是不确定的,它会根据运行时期对象实例来动态判断要调用的函数。这就是虚函数能实现多态的原理。在基类函数加上virtual关键词,在派生类函数加上override关键词就能通过方法的重写实现多态。

我们将上一节中Student类的AskGPA函数改写为一个虚函数:

    public virtual void AskGPA()
    {
        Debug.Log("A student's GPA is " + gpa);
    }

然后在派生类中,使用override关键字对这一函数进行重写。

	public override void AskGPA()
    {
        Debug.Log("An undergraduate student's GPA is " + gpa);
    }

回到Unity,我们就能看到程序输出了派生类方法的输出。但是注意,实函数是不能被重写的,它没有虚函数动态判断的特性。

如果想让一个实的基类函数实现多态,就要另一种方式:方法隐藏(New)。方法隐藏要用到关键字new。注意这里new的作用不同于动态分配空间的new。我们将基类AskGPA函数的virtual修饰去掉,然后在派生类中,将override改成new:

    public new void AskGPA()
    {
        Debug.Log("An undergraduate student's GPA is " + gpa);
    }

回到Unity,可以看到程序输出了派生类方法的输出。在这里,我们本质上是将派生类中基类的AskGPA方法隐藏掉了,所以当我们调用AskGPA方法时,程序调用的就是派生类中的方法。

如果不写new,可能也能通过编译,但会收到警告。此时相当于是在隐式地隐藏基类方法。

Tips:成员变量也可以使用new隐藏,格式为new Typename xx。如果你在继承Monobehaviour的类中使用过名为name的变量,可能见到过绿色的下划线提示:“xx.name隐藏继承的成员Object.name”。这就是因为你声明的name变量正隐式地隐藏基类中的name变量,此时便可以使用new关键字。

这就是通过虚函数+方法重写或方法隐藏两种方式实现多态的过程。两种方式的区别在于,对于一个用基类引用的派生类变量:

	BaseClass bcdc = new DerivedClass();
	bcdc.SomeMethod();

使用Override方法,bcdc调用的是DerivedClass中的SomeMethod,而使用New方法,bcdc调用的是BaseClass的SomeMethod。更多例子可以参考微软官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/knowing-when-to-use-override-and-new-keywords

在实际使用中,我们大部分时间都应该选择第一种方式,除非基类函数已经不适合改写为虚函数。 熟悉Java语言的同学们可能会对这节比较疑惑:Java中函数大部分都是可以直接override的。这是因为在Java的机制中,普通的成员函数都是virtual的,反而如果你想禁止函数被重写,要使用final或private关键字。

最后考虑一个问题,如果我们的基类是一个比较抽象的概念,比如说我们有一个动物Animal的基类,有Dog、Duck等等派生类。在Animal中有一个“叫”的方法Bark。狗应该汪汪叫,鸭子应该嘎嘎叫,这些派生类函数都很好写。但Animal类中的Bark怎么写?我们实际上根本不知道Animal中的Bark怎么写,而只知道:应该有这么一个方法,继承我的派生类都应该具体实现它。这就是我们下一节的主题:抽象类和抽象函数。我们下节见。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值