[Unity] C#中级编程 - 06 - 隐藏/虚拟/抽象/覆写/密封
Posted L建豪 忄YH
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Unity] C#中级编程 - 06 - 隐藏/虚拟/抽象/覆写/密封相关的知识,希望对你有一定的参考价值。
[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也有效果,但会有警告
new public float num_0 = 30; // 屏蔽父类的属性,属性不论类型,就像方法不论返回值。
/* 如果去掉注释,实例化调用中,就会调用子类的函数,不会调用父类的。
public void get_num()
Debug.Log(num_1 +"子类"+ num_0); // 20子类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); // 20实例30
Debug.Log(Exercise_4_10.num +"静态"+ Exercise_4_9.num); // 102静态100,两个不共用
myClass.get_num(); // 100父类10
如果重名又没使用new,则会报警告
'属性或方法' hides inherited member '属性或方法'. Use the new keyword if hiding was intended
.(…隐藏继承的成员…如果要隐藏,请使用new关键字)。使用new
可以消去警告。
- 总结上面例子中的现象:
- 子类中有重名的属性或方法(属性的命名相同,方法的命名和参数相同),父类的就访问不了;静态属性和方法会相互独立,允许单独访问;局部属性和方法则被隐藏无法访问。
想起,为什么有一种编程习惯:在类中想查看一个属性的值时不会直接访问类属性,而是为它专门写一个方法,只有一条返回语句,返回该属性。
之前我还觉得这个操作是不是多此一举,现在想想,可能是避免这种被隐藏的问题,养成好习惯。而且单独写允许返回值作一定运算,就像c#中是set
关键字。
- 通过子类的实例化访问父类的方法,该方法属于父类作用域。父类方法内调用的重名属性是父类中的。
指定访问:this/base
上一个知识点讲到,因为 “访问顺序从内到外” ,子类无法访问到父类的重名属性或方法。
- c#有个两个关键字:
base
和this
,分别代表当前类的父类和当前类。如果想指定访问父类或子类的内容,可以在属性或方法前面加上这个前缀。
脚本②:(在上个例子的脚本②中添加如下)
/* 屏蔽父类的 get_num 方法 */
new public void get_num()
base.get_num(); // 100父类10,调用父类的重名方法
Debug.Log(this.num_1 +"子类"+ base.num_0); // 20子类10
// 当前类的 num_1 属性其实就算父类的,所以写不写,或写 this / base 都可以。
在unity中,经常用到
this
获取当前对象。在类的继承中,base也会经常用到。
base
更加常见的用法是调用父类的某些初始化函数。在unity中就是Awake()
之类的。
覆写:virtual/override
如果子类想隐藏、覆盖父类的属性或方法,有更加正式的方法。《C# virtual关键字详解 (biancheng.net)》
virtual
(虚拟)和override
(覆写),两个关键字搭配使用。前者写在父类中,后者写在子类中。
脚本①:(基于修改,省略其他)
// public virtual int num_0 = 100; // 报错,修饰 属性需要采用下面的写法
public virtual int num_10 get;set; // 也就是不能给初始化值了?
/*
public virtual int num_10 get;set; // 报错,不可修饰 静态
virtual public static void him() // 报错
*/
// public virtual void get_num() // 也行
virtual public void get_num() // virtual 关键字位于访问修饰符前后都可以
/* 略 */
脚本②:
// override public void get_num() // 也行
public override void get_num() // override 对应父类中的重名方法 virtual
/* 略 */
如果重名不写关键字
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
关键字:抽象。
脚本①:
// abstract public class Exercise_4_9 : MonoBehaviour 也可以
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 int num_10 get;set; // 报错,抽象只能修饰类或方法,不能修饰属性
// public abstract int num_10 = 10;
public abstract void get_num(); // 抽象方法
// abstract public void get_num(); // 也可以
/*
Debug.Log(num +"父类"+ num_0); // 不能释放注释,抽象方法不能写内容
*/
// 同样不可修饰 静态,
脚本②:
public class Exercise_4_10 : Exercise_4_9 // 继承抽象类,一般抽象类都是拿来继承的。
public override void get_num() // 必须有这个方法的实现,不然报错
// base.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();
// Exercise_4_9 myClass = new Exercise_4_9(); 报错,抽象类不可实例化,也就说只能继承……
myClass.get_num(); // 20子类10
-
总结特点:
- 不可修饰静态,(静态)不能将其标记为覆盖、虚拟或抽象。
- 不可修饰属性,只能修饰方法和类。若修饰了方法,那么该类也必须为抽象。抽象类和抽象方法共存。
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
- 当部分功能最后完成时,不会再改动时,将其设为密封。可以起到保护作用:密封类不可继承,密封方法不可覆写。
public sealed class myclass_1 : myclass_0 // 该类为密封类,不可用于继承
// sealed public override void get_num() // 也可以
public sealed override void get_num() // 该类为密封方法,不可再被覆写
Debug.Log("密封方法");
- 总结特点:
sealed
必须修饰override
使用。不然会报错:cannot be sealed because it is not an override
;不能被密封,因为它不是覆盖。- 密封方法可以存在普通类中,和抽象不一样,不一定类也要是密封。
- 密封类不可继承,密封方法不可覆写。
总结
- 即使被虚拟、抽象、密封后,如果不想使用父类的同名属性方法,还是可以直接隐藏掉。
以上是关于[Unity] C#中级编程 - 06 - 隐藏/虚拟/抽象/覆写/密封的主要内容,如果未能解决你的问题,请参考以下文章
[Unity] C#中级编程 - 10 - 命名空间/using