[Unity中文课堂教程] C#中级编程 - 06 - 隐藏/虚拟/抽象/覆写/密封
参考《c语言中文网》有很多实用的知识点
《C# base关键字:调用父类成员方法 (biancheng.net)》
《C# virtual关键字详解 (biancheng.net)》
继续上一篇笔记——继承没补充的细节。
隐藏:new
上一篇笔记讲到,子类继承父类后,子类可以直接访问父类的属性和方法,作出“宛如父类的内容根据访问修饰符直接拷贝到了子类中”的比喻。其实这是不恰当的思路,因为这就暗示子类和父类是同一个作用域,但不是的。
写代码的都应该知道,一个代码块一个作用域,最常见的划分区域的方法是花括号{} ,python是缩进。而代码寻找内容时会从内到外,从小到大的遍历作用域,寻找可用的内容,变量或函数等。
脚本①:
public class Exercise_4_9 : MonoBehaviour
{
public static int num = 100;
public int num_0 = 10;
public int num_1 = 20;
public void get_num()
{
Debug.Log(num +"父类"+ num_0);
}
}
脚本②:
public class Exercise_4_10 : Exercise_4_9
{
new public static int num = 102;
new public float num_0 = 30;
public void get_num(int x)
{}
}
脚本③:
public class Exercise_4_11 : MonoBehaviour
{
void Start()
{
Exercise_4_10 myClass = new Exercise_4_10();
Debug.Log(myClass.num_1 +"实例"+ myClass.num_0);
Debug.Log(Exercise_4_10.num +"静态"+ Exercise_4_9.num);
myClass.get_num();
}
}
如果重名又没使用new,则会报警告'属性或方法' hides inherited member '属性或方法'. Use the new keyword if hiding was intended .(…隐藏继承的成员…如果要隐藏,请使用new关键字)。使用new 可以消去警告。
- 子类中有重名的属性或方法(属性的命名相同,方法的命名和参数相同),父类的就访问不了;静态属性和方法会相互独立,允许单独访问;局部属性和方法则被隐藏无法访问。
想起,为什么有一种编程习惯:在类中想查看一个属性的值时不会直接访问类属性,而是为它专门写一个方法,只有一条返回语句,返回该属性。
之前我还觉得这个操作是不是多此一举,现在想想,可能是避免这种被隐藏的问题,养成好习惯。而且单独写允许返回值作一定运算,就像c#中是set 关键字。
- 通过子类的实例化访问父类的方法,该方法属于父类作用域。父类方法内调用的重名属性是父类中的。
指定访问:this/base
上一个知识点讲到,因为 “访问顺序从内到外” ,子类无法访问到父类的重名属性或方法。
- c#有个两个关键字:
base 和this ,分别代表当前类的父类和当前类。如果想指定访问父类或子类的内容,可以在属性或方法前面加上这个前缀。
脚本②:(在上个例子的脚本②中添加如下)
new public void get_num()
{
base.get_num();
Debug.Log(this.num_1 +"子类"+ base.num_0);
}
在unity中,经常用到this 获取当前对象。在类的继承中,base也会经常用到。
base 更加常见的用法是调用父类的某些初始化函数。在unity中就是Awake() 之类的。
覆写:virtual/override
如果子类想隐藏、覆盖父类的属性或方法,有更加正式的方法。《C# virtual关键字详解 (biancheng.net)》
virtual (虚拟)和override (覆写),两个关键字搭配使用。前者写在父类中,后者写在子类中。
脚本①:(基于修改,省略其他)
public virtual int num_10 {get;set;}
virtual public void get_num()
{}
脚本②:
public override void get_num()
{}
如果重名不写关键字override ,则会报警告,和本篇第一个知识点类似,只要加上就可以消去了; '属性或方法' hides inherited member '属性或方法'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword .(…隐藏继承的成员…要使当前成员重写该实现,请添加override 关键字。否则添加new 关键字)
-
总结特点:
virtual 和override 都不可修饰静态,可修饰属性,但需要特殊定义方式。
会报错,提示cannot be marked as override, virtual, or abstract ;(静态)不能将其标记为覆盖、虚拟或抽象。
-
可位于访问修饰符前后,按照传统编程习惯,放后面。 -
父类方法有关键字virtual ,子类没有对应的override 重名方法,正常 使用父类方法。 -
父类方法没有关键字virtual ,子类有对应的override 重名方法,直接 编译报错。 -
父类方法有关键字virtual ,子类没有对应的override 重名方法,但是参数或返回值不同,不会当做重载函数,会直接编译报错。 -
从使用结果来看是一样的,virtual/override 用不用都可以,就算不用程序也不会报错。利用第一个知识点的new ,可以方便快捷的隐藏父类的属性方法。对第二个知识点的base 使用也没有障碍。 -
但是!!!从总结可以看出,关键字virtual/override 可以实现程序自检:
- 命名:命名不对会编译错误,因为没有对应的
virtual 修饰的需要重写的名字。 - 返回值和参数:不一样也会编译错误。
- 提示父类有同名:如果父类有
virtual ,子类有重名但没override ,会报警告。 -
为了程序的可读性和封装性,使用正确的关键字修饰是很有必要的!!!
抽象:abstract/override
查覆写时看到类似的知识点,简单列举《C# abstract:声明抽象类或抽象方法 (biancheng.net)》
- 有时父类的同名属性方法只是为了提示子类覆写用,并没有实际内容。此时可使用
abstract 关键字:抽象。
脚本①:
public abstract class Exercise_4_9 : MonoBehaviour
{
public static int num = 100;
public int num_0 = 10;
public int num_1 = 20;
public virtual int num_10 {get;set;}
public abstract void get_num();
}
脚本②:
public class Exercise_4_10 : Exercise_4_9
{
public override void get_num()
{
Debug.Log(this.num_1 +"子类"+ base.num_0);
}
}
脚本③:
public class Exercise_4_11 : MonoBehaviour
{
void Start()
{
Exercise_4_10 myClass = new Exercise_4_10();
myClass.get_num();
}
}
-
总结特点:
- 不可修饰静态,(静态)不能将其标记为覆盖、虚拟或抽象。
- 不可修饰属性,只能修饰方法和类。若修饰了方法,那么该类也必须为抽象。抽象类和抽象方法共存。
abstract 可放置访问修饰符前后,习惯性放后面,访问修饰符排第一。- 抽象类不可实例化。通常只能用于继承了。
- 必须要有抽象方法的实现,不然报错。
- 组合关键字
override 使用。 -
应用特点:实现程序自检,养成编程习惯。
- 避免模板类被实例化,会报错。
- 避免忘记实现抽象方法,会报错。
- 避免调用空的父类方法,会报错。
- 省去父类方法的实现,会报错。
覆写:override
- 有时子类覆写了父类的方法后,该子类还可以成为下一个子类的父类,方法还可能再次被覆写。
override 除了对应abstract 和virtual 外,还能对应override 。
从unity编译报错也能看出来,如果只有override 时,会显示:because it is not marked virtual, abstract, or override ;因为它没有被标记为虚拟、抽象或覆盖。
public class myclass_0 : MonoBehaviour
{
public virtual void get_num()
{
}
}
public class myclass_1 : myclass_0
{
public override void get_num()
{
}
}
public class myclass_2 : myclass_1
{
public override void get_num()
{
}
}
- 不过有时候又不希望被再覆写,就会用到下一个知识点——密封。
密封:sealed override
快速带过,《C# sealed:声明密封类或密封方法 (biancheng.net)》
- 当部分功能最后完成时,不会再改动时,将其设为密封。可以起到保护作用:密封类不可继承,密封方法不可覆写。
public sealed class myclass_1 : myclass_0
{
public sealed override void get_num()
{
Debug.Log("密封方法");
}
}
- 总结特点:
sealed 必须修饰override 使用。不然会报错: cannot be sealed because it is not an override ;不能被密封,因为它不是覆盖。- 密封方法可以存在普通类中,和抽象不一样,不一定类也要是密封。
- 密封类不可继承,密封方法不可覆写。
总结
- 即使被虚拟、抽象、密封后,如果不想使用父类的同名属性方法,还是可以直接隐藏掉。
|