#前言
嗨嗨嗨!来了。
本文将结合C#在Unity中的应用讲解,代码编辑器使用Visual Studio。讲解内容来自《C#高级编程 第10版》和我的理解,如果有错希望大佬指出(骂我,不要客气!)。
下面是《C#高级编程 第10版》的pdf文件分享
链接:
https://pan.baidu.com/s/1DfNICYLJ-zy17flRpzB7Cw?pwd=2233
提取码:2233、
#正文
#继承
-
继承的介绍
面对对象的特征:抽象,封装,继承,多态。足见继承的重要作用,接下里我们就先介绍一下C#中继承的一些特点吧:
-
单重继承:C#中一个类只可以派生自一个基类。(C++支持多重继承:一个类可以有多个基类。)
-
多层继承:多层继承允许继承有更大的层次结构。类B派生出自类A,类C又派生自类B。其中,类B也称为中间基类,C#支持它,也很常用。
-
接口继承:定义了接口继承,接口允许多重继承。即一个类最多继承一个类,但可以继承多个接口。
有两个重要的知识点哦(之前就提到过,你要问我有什么用,我也在思考有什么用,姑且像是1+1先背下来吧):
类的最大的基类是System.Object。
接口都继承自System.ValueType,而System.ValueType最终也是继承自System.Object。
-
实现继承
public
class
Shape
{
}
public
class
Square
:
Shape
{
}
public
class
Circular
:
Shape
{
}
类的继承就是上面这种写法,Square(正方形)和Circular(圆形)都继承自Shape(形状)。(Shape类没有写继承自哪个类时,会默认继承自System.Object。)
因为子类一定具有基类的所有的特点,并且可以具有自己的东西。形象的比喻一下,基类就是武器的强化原型,我们就像是铁匠,将需要强化的武器拿过来,然后注入自己的东西,然后升级成功!但有一点不一样的是我们是比较菜的铁匠,只能加不能减,武器原来的功能我们必须全部继承。
我们何时需要继承呢?
当我们将会制作几个类,这几个类有很多重复的属性和方法,就可以考虑制作一个基类,然后继承。也就是我们需要毒属性,火属性,冰属性的武器时,如果我们仅仅要改附魔,那武器的强化原型就有必要制作了。
-
虚方法
public
class
Shape
{
public
virtual
void
GetArea
(
)
{
}
}
关键字是virtual,属性也可以用virtual声明(我们之前说过,属性本质上就是方法。)
C#中的虚函数的概念与标准OOP(面向对象的程序设计)的概念相同:可以在派生类中重写虚函数。在调用方法时,会调用该类对象的合适方法。在C#中,函数在默认情况下不是虚拟的,但(除了构造函数以外)可以显示地声明为virtual。这遵循C ++的方式,即从性能的角度来看,除非显式指定,否则函数就不是虚拟的。而在Java中,所有函数都是虚拟的。但C#的语法与C ++的语法不同,因为C#要求在派生类的函数重写另一个函数时,要使用override关键字显示的声明。(这段摘自《C#高级编程》105p)
public
class
Cube
:
Shape
{
public
override
void
GetArea
(
)
{
base
.
GetArea
(
)
;
}
}
base即基类,base.GetArea()即调用基类中的GetArea()方法。
从使用上来讲,就是在你觉得哪个方法可以在它继承的子类里面重写,就可以这样写上,然后使用。
使用起来很简单啦,关键是为什么要这样写,毕竟一般情况下,你不使用virtual,你直接在子类里面写一个名字一模一样的方法,使用上基本没差。
-
首先是最简单的也是最重要的一点,VS(Visual Studio)编程是有代码提示的,所以你可以这样防止你代码命名出错,万一命名不一样效果可完全不同了。
-
其实第二点也是第一点的延伸,就是如果你在做一个项目,里面有继承,有一天你去把基类的方法名改了,这个时候如果你使用了virtual,你马上就会得到子类里面复写的方法名错了。但如果你没有,只是在子类中重新使用了与原来相同名字的函数,那你只能在运行错误中去找到那个函数了。
-
如果子类和基类有相同命名的方法,那么基类会对子类隐藏这个方法,在《C#高级编程》里有一小节讲解了这种隐藏方法的情况,这种情况下编译子类,会得到一个编译器警告,虽然程序员不管警告,但还是要注意一下。
总而言之,如果出现方法名子类和基类一致的情况,就使用virtual吧,这才是最推荐。
虚方法和实方法在编译期间是有区别的,实方法会被静态编译,写死了,而虚方法则是动态的。(C#一般都是实方法可能是为了省内存吧。)
-
多态
四个特征中的又一个特征:多态!这玩意可厉害了,但讲起来很简单啦。
public
void
MyFun
(
)
{
Shape
s1
=
new
Square
(
)
;
Shape
c1
=
new
Circular
(
)
;
}
就是这样,我之前在 扩展阅读_值类型和引用类型 里提到过,Shape S1的创建像是在栈上创建了一个可以存放堆上地址的内存,这个地址不止可以存放它自己声明的类型,还可以存放他的子类哦!
这就是多态!我们如果这个时候使用s1的去调用Shape中的函数(注意,在我们没有把Shape转换成Square()之前,我们不能去调用Square扩展的方法。Square中继承有所有的Shape中声明的方法。),实际上就像是直接调用Square的方法。(虚方法的用武之地到了!)
我们之前说过,Object类是全部类的基类,所以我们可以用 Object object = new 任何类型() ,但你也只能在这上面调用Object中声明的方法。
用法嘛,在函数的参数方面运用比较常见:
public
void
MyFun
(
Shape
s
)
{
s
.
GetArea
(
)
;
}
这样我可以在调用时放入Square类或者Circular类,这样就可以调用这个参数的GetArea()的类了。
-
抽象类和抽象方法
C#中可以使用abstract来把类和方法变为抽象类和抽象方法。
抽象类不能直接实现(也就是不能 new 抽象类() 这样),抽象方法的本身是虚方法,如果使用virtual关键字去修饰,会报错的。如果一个类包含一个抽象方法,那么这个类也必须声明为抽象类(不然就会报错。)。一个抽象类的类不一定全都是抽象方法。
public
abstract
class
Shape
{
public
virtual
void
GetArea
(
)
{
}
public
abstract
void
A
(
)
;
//注意看这里的方法以;结尾,不能实现它,不然会报错
}
派生类中必须实现虚方法,同样使用override来修饰方法。
至于使用场景嘛,只要你遇到了可以使用的场景就使用吧,毕竟使用方式已经讲了,这个不好举例场景诶。
-
密封类和密封方法
使用sealed进行修饰。
一个类如果不应该被继承,那么就可以把它设计成密封类。一个方法不应该被重写,那么就可以把它设计成应该密封方法。
以大家聪明才智一定以及理解了,因为用的不多,大家主要是要认识sealed这个关键字以及其作用。
介绍一下作用:首先是有些类和方法是内部的,任何尝试重写它的一些功能都可能导致代码不稳定。其次,对于密封类编译器知道其不能派生类,因此用于虚拟方法的虚拟表可以缩减或者消除,节省内存。
-
派生类的构造函数
这可是很有趣的点,有点绕,但我并不打算介绍,因为Unity的类大多继承自MonoBehaviour,有unity的生命周期,与构造函数打交道的机会较少。(如有兴趣可以自己百度看看。)
#接口
public
interface
Shape
{
void
GetArea
(
)
;
//这里不能有修饰符,不然会报错
}
之前提到C#中子类只能有应该基类,就像强化武器的武器原型只能有一个。那么接口就像是强化武器时的强化石,里面提前写好了你能强化哪些属性(应该重写哪些方法),并且可以放入的强化石的数量没有上限。
接口的使用方法和抽象类很像,我们重点先来看看两者的相同点和区别吧:
相同点:
-
都可以被继承。
-
都不能被实例化。
-
都可以包含方法声明。
-
子类都必须实现未实现的方法。
不同点
-
抽象类只能被单一继承(因为是类嘛),接口则可以多重继承。
-
抽象类中可以有方法被实现,也可以有抽象方法。接口只能有方法的声明,不能实现。
-
抽象类可以定义字段,属性和方法实现。接口则不能有字段。
接口和抽象类虽然不能实现,但可以用于实现多态哦。
还有就是接口可以继承自接口。
#其他
-
访问修饰符
下图截取自《C#高级编程》P113:
public,protected,private这三个老常用了,尤其是public和private平时都写麻了,这三个都是逻辑访问修饰符。
internal则是物理访问修饰符,边界是一个程序集。
-
其他修饰符
下图截取自《C#高级编程》P114:
extern可以在类外面实现类的方法,不常用,有兴趣可以自己搜一下看看。
-
is和as运算符
is和as都是用于类型转换的。
public
void
MyFun
(
Object
s
)
{
(
(
Shape
)
s
)
.
GetArea
(
)
;
}
我们把之前Shape的参数变为Object,根据多态,我们当然可以把Shape和它的子类作为参数传入方法中(因为Object是所有类的基类)。这里的 (Shape)s 是一种强制类型转换的写法,可以把s强行转换为Shape类型,但前提是这个s原来就是Shape类型或者其子类,不然这句就会返回一个InvalidCastException异常(报错了)。
很不安全呀,这该死的强制类型转换,当然你如果可以确定传入的类型是可以进行强制类型转换的话,也可以使用它,因为它的写法是最方便的。
为了防止上面这种不安全的情况,我们就需要使用is和as。
public
void
MyFun
(
Object
s
)
{
Shape
a
=
s
as
Shape
;
if
(
a
!=
null
)
{
}
}
首先是as,as会先检查参数是否能够转换为指定类型,可以转换,就转换,如果不能转换呢,就给你返回一个null。所以使用前需要检查一下,返回的数值是否是null。
public
void
MyFun
(
Object
s
)
{
if
(
s
is
Shape
)
{
(
(
Shape
)
s
)
.
GetArea
(
)
;
}
}
is则是仅仅进行判断,判断该类型可否转换为指定类型,返回true,或者false。在使用is判断可以转换之后,还需自己使用强制类型转换进行转换。
#结束语
-
建议
其实我讲的内容是简化的,很多地方可能还没有讲清楚。如果大家哪里没有看懂,可以去翻看一下《C#高级编程 第10版》,如果还是没有搞懂,欢迎在评论区询问,我会尽力回答的。以上提到的内容都是基础中的基础,是绝对不允许出现不理解的情况的。
如果害羞不好意思在评论区询问,欢迎大家加我QQ来讨论哦。QQ:2243211562
-
杂谈
有一丝丝伤感,今天中午午休的时候,我做了个梦,梦到了我大学的室友们。梦到了开学的时候,梦到了我们刚刚相识,梦到和室友们还有些生疏的时候。
梦里的感觉挺神奇的,我与眼前的几人明明是刚刚认识,就隐隐有一种感觉,大家以后会成为好友。
我醒来时有些懵,反应过来的时候只感觉想要再睡会,找找那种神奇的感觉(不是偷懒想继续睡。),可惜梦的肥皂泡已经碎了,找不见了。
我明明大学毕业才一个月多一点呀!怎么就做这种梦呀!原来我是这种容易想念别人的人嘛,诶。
谢谢大家听我讲我的梦。
考虑到重要程度,我觉得接下来不应该讲泛型,更应该讲数组相关的知识,所以第四节就讲数组的知识吧。
这篇文章就到这里,感谢大家的收看,大家再见!
1951

被折叠的 条评论
为什么被折叠?



