[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可以消去警告。

  • 总结上面例子中的现象:
  1. 子类中有重名的属性或方法(属性的命名相同,方法的命名和参数相同),父类的就访问不了;静态属性和方法会相互独立,允许单独访问;局部属性和方法则被隐藏无法访问

想起,为什么有一种编程习惯:在类中想查看一个属性的值时不会直接访问类属性,而是为它专门写一个方法,只有一条返回语句,返回该属性

之前我还觉得这个操作是不是多此一举,现在想想,可能是避免这种被隐藏的问题,养成好习惯。而且单独写允许返回值作一定运算,就像c#中是set关键字。

  1. 通过子类的实例化访问父类的方法,该方法属于父类作用域。父类方法内调用的重名属性是父类中的

指定访问:this/base

上一个知识点讲到,因为 “访问顺序从内到外” ,子类无法访问到父类的重名属性或方法。

  • c#有个两个关键字:basethis,分别代表当前类的父类当前类。如果想指定访问父类或子类的内容,可以在属性或方法前面加上这个前缀。

脚本②:(在上个例子的脚本②中添加如下)

/* 屏蔽父类的 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关键字)

  • 总结特点

    1. virtualoverride不可修饰静态,可修饰属性,但需要特殊定义方式。

    会报错,提示cannot be marked as override, virtual, or abstract;(静态)不能将其标记为覆盖、虚拟或抽象。

    1. 可位于访问修饰符前后,按照传统编程习惯,放后面

    2. 父类方法关键字virtual,子类没有对应的override重名方法,正常 使用父类方法。

    3. 父类方法没有关键字virtual,子类对应的override重名方法,直接 编译报错。

    4. 父类方法关键字virtual,子类没有对应的override重名方法,但是参数返回值不同,不会当做重载函数,会直接编译报错

  • 从使用结果来看是一样的,virtual/override用不用都可以,就算不用程序也不会报错。利用第一个知识点的new,可以方便快捷的隐藏父类的属性方法。对第二个知识点的base使用也没有障碍。

  • 但是!!!从总结可以看出,关键字virtual/override可以实现程序自检

    1. 命名:命名不对会编译错误,因为没有对应的virtual修饰的需要重写的名字。
    2. 返回值参数:不一样也会编译错误。
    3. 提示父类有同名:如果父类有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
    

  • 总结特点

    1. 不可修饰静态,(静态)不能将其标记为覆盖、虚拟或抽象。
    2. 不可修饰属性,只能修饰方法和类。若修饰了方法,那么该类也必须为抽象。抽象类和抽象方法共存
    3. abstract可放置访问修饰符前后,习惯性放后面,访问修饰符排第一。
    4. 抽象类不可实例化。通常只能用于继承了。
    5. 必须要有抽象方法的实现,不然报错。
    6. 组合关键字override使用。
  • 应用特点:实现程序自检,养成编程习惯。

    1. 避免模板类被实例化,会报错。
    2. 避免忘记实现抽象方法,会报错。
    3. 避免调用空的父类方法,会报错。
    4. 省去父类方法的实现,会报错。

覆写:override

  • 有时子类覆写了父类的方法后,该子类还可以成为下一个子类的父类,方法还可能再次被覆写。
  • override除了对应abstractvirtual外,还能对应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			// 该类为密封类,不可用于继承

	// sealed public override void get_num()		// 也可以
	public sealed override void get_num()			// 该类为密封方法,不可再被覆写
	
		Debug.Log("密封方法");
	

  • 总结特点
    1. sealed必须修饰override使用。不然会报错: cannot be sealed because it is not an override;不能被密封,因为它不是覆盖。
    2. 密封方法可以存在普通类中,和抽象不一样,不一定类也要是密封。
    3. 密封类不可继承,密封方法不可覆写。

总结

  • 即使被虚拟、抽象、密封后,如果不想使用父类的同名属性方法,还是可以直接隐藏掉。

以上是关于[Unity] C#中级编程 - 06 - 隐藏/虚拟/抽象/覆写/密封的主要内容,如果未能解决你的问题,请参考以下文章

[Unity] C#中级编程 - 08 - 接口

[Unity] C#中级编程 - 03 - 重载

[Unity] C#中级编程 - 07 - 多态

[Unity] C#中级编程 - 10 - 命名空间/using

[Unity] C#中级编程 - 10 - 命名空间/using

[Unity] C#中级编程 - 10 - 命名空间/using