关于Unity的C#基础学习(三)
面向对象
一、编码注意
1.每个C#的脚本只有唯一的一个类是继承自MonoBehaviour
2.类的名字要和我们的文件名字一样
3.代码的缩进
个人习惯:在类的内部来访问数据成员/成员函数的时候,用this来修饰,可以更清楚哪些变量是数据成员,哪些是局部变量,哪些是成员函数,哪些不是。其实this可以省略。
二、类的定义
//权限 public(外部),internal(内存) 修饰这个类
1.internal class Person{
}//Person类只能在程序集的内部使用,internal可以被省略
2.public class Person{
}//Person类可以在程序集的外部使用
三、数据成员和公共函数的定义
1.数据成员如果没有权限修饰,则默认是private
2.函数如果没有权限修饰,则默认是private
3.类的内部指的是类的大括号之间
所以的数据和函数,类的内部是可以直接调用的
public:类的内部,外部都可以直接调用。
protected:类的内部,类的子类可以直接调用。类的外部不能调用,
private:类的内部可以直接调用。类的外部不能调用。
四、多态
一个函数多种形态,函数名相同,参数不一样,实际上是两个函数。
private int xpos;
private int ypos;
void setPosition(int x,int y){
this.xpos=x;//个人习惯用this修饰
this.ypos=y;//个人习惯用this修饰
}
void setPosition(Point pt){
this.setPosition(pt.x,pt.y);//个人习惯用this修饰
}
比如有一个父类superClass,它有2个子类subClass1,subClass2。superClass有一个方法func(),两个子类都重写了这个方法。那么我们可以定义一个superClass的引用obj,让它指向一个子类的对象,比如superClass obj = new subClass1();那么我们调用obj.func()方法时候,会进行动态绑定,也就是obj它的实际类型的func()方法,即subClass1的func()方法。同样你写superClass obj = new subClass2();obj.func()其实调用的是subClass2的func()方法。这种由于子类重写父类方法,然后用父类引用指向子类对象,调用方法时候会进行动态绑定,这就是多态。多态对程序的扩展具有非常大的作用,比如你要再有一个subClass3,你需要改动的东西会少很多,要是使用了配置文件那就可以不动源代码了。
五、构造函数
1.在构造实例的时候调用,也就是实例化类的时候会调用,也就是new 的时候会调用。其实平时的语句Person person=new Person();的含义是:类名 引用变量名=new关键词 类的构造函数();
2.没有返回值,有权限修饰,一般权限都是public,如果是private的话就不能在别的类中new
3.对实例的数据成员进行初始化的一个地方
4.如果我们的类里面没有写构造函数,那么系统会自动生成一个默认的构造函数,如果有了构造函数,系统就不会为我们生成默认的构造函数了。
5.构造函数可以有多个,可以多态,根据new的时候构造函数的参数决定调用哪个构造函数
注意:继承自MonoBehaviour的对象千万不要去重载它的构造函数,写好入口内容就行了。
public Person(){
this.Sex=0;
this.name=“”;
this.age=0;
}
public Person(string name,int age,int sex){
this.sex=sex;
this.name=name;
this.age=age;
}
例子
Person person;//在栈上面定义一个局部的引用变量person
person=new Person();//实例化的时候,调用构造函数,这时候根据参数的形式,调用的是没有参数的构造函数
person=new Person("xiaoming",10,0);//这时候根据参数的形式,调用的是有参数的构造函数
六、继承
为什么要有继承?
男人类和女人类,都有一些公共的属性和方法,如果每个类都写一遍,就会造成代码的重复,很不好,这时候就需要继承的概念,就是先实现人类的逻辑属性,然后让男人类和女人类继承自人类,这样男人类不用写人类的代码,只要写好男人的代码就行了,女人类不用写人类的代码,只要写好女人的代码就行了。
父类或基类:男人类继承了/派生自人类,拥有了人类的所有的数据成员和方法
子类或派生类:人类是男人类的父类/基类
注意
1.继承未必可以使用,只能在子类里面使用父类的public,protected修饰的数据成员和方法。
2.继承的时候父类的权限如果是internal,子类也必须是internal。
3.子类的实例在构造的时候,首先会调用父类的构造函数,再调用子类的构造函数
七、重载
子类重新实现了基类有的方法,子类调用基类的同样名字的方法,调用的首先是自己的方法,可以用base.父类的方法名字来强行调用父类方法。这里的自己指的是Man m=new Man();前面那个Man。
1.base.init_person();
直接说明调用的是父类的方法init_person()方法,如果子类非要调用父类的方法,就用这个语句。只能在类的内部使用。
具体使用:
子类中
public void init_test(){
base.init_test();//先做完父类公共的init_test方法中的逻辑,再实现自己子类的相关逻辑。
......
}
2.子类中写this.init_person();
首先会检查子类中是不是有init_person方法,有的话就调用子类的这个方法,没有的话就去父类中去查找,看有没有init_person()方法,如果还没有,就去父类的父类中查找,直到找到为止。
八、虚函数
比喻1:管理者,有一套自己的规则,在这个规则下面有不同的人,遵守这些规则来做自己的事情。相当于A、B、C...继承了这个规则,在这个规则下去完成自己的事情,管理者在这套规则下做的事情,就是向A、B、C...要最终的结果。
管理者说:“我8点钟要你们两个人AB关于这个方案的结果”
A.getResult();
B.getResult();
如果有100个人话,就要一个for循环来一个一个的根据其名字调用相同的方法,很麻烦,而且可能会有新的成员加入,那样for循环的计数又要改变,更加麻烦了,这时候就需要用到虚函数来管理这些子类。
比喻2:Unity中,管理者,有一套自己的规则,就是组件的继承规则及组件父类,用户遵守这个规则来定义自己的组件类,这个组件类被规定几个接口,当用户扩展了这几个接口,写了自己的组件类。
管理者不知道用户有没有扩展,也不知道扩展了哪一个接口,但是他知道,用户遵守了他的规则,继承了他的组件基类,这时候就需要用到虚函数来管理这些子类。
怎么管理呢?
1.用基类的引用变量来保存子类的实例。用人来保存男人这个实例一样。
一个节点可能挂载很多的组件,比如挂很多的脚本组件,但是管理者不会管你到底是什么具体的脚本名字,内容。他只知道,脚本继承了MonoBehaviour,于是用一个基类的引用变量MonoBehaviour m去调用子类的实例。子类的实例其实也是基类的属性。
Person p=new Man();
p.getResult();
2.面对可能新加入的未知的子类,我们整个管理层要提出一种统一的管理方法,统一管理组件的方法,这个方法可以基于接口来进行提出,也就是说,基类定义这个函数的接口,子类自己去重载,然后做不同的实现。
父类里面写:
Person p=new Man();
p.getResult();//这里会看父类有没有这个函数,如果没有就去看它自己的父类,结果看到有这个函数,还是个虚函数。
public virtual void getResult(){
}//如果遇到一个函数是虚函数,那么就会基于这个实例来查找子类有没有重载这个虚函数,如果有,调用子类的重载函数
子类里面写:
public override void getResult(){
}//显示地表示这个函数是重写父类虚函数的。
这样,就可以用12点方法很好的管理子类以及新进来的子类,简单来说就是,先把所有人都看成一样的,让所有人来重写某个函数,我管理者只要用普遍实例来调用这个公共重写的函数就可以得到各个实例关于这个函数的各自的逻辑实现。事先不知道到底是哪些类的实例,但是我想执行他们各自的逻辑。
a.深入实例:
父类中:
void test_virtual_func(Person p){
p.getResut();
}
public virtual void getResult(){
}
this.test_virtual_func(new Man());
this.test_virtual_func(new Woman());
...
子类中:
public override void getResult(){
}
b.虚函数原理:
1.每个类一旦有重写什么虚函数,就会生成一个虚函数表,类的一个隐形的属性引用变量指向表
2.表内记载了每个类各个虚函数的重写函数代码的地址
3.每个类的实例都有一个隐形的属性引用变量,指向虚函数表
4.所以,在new的时候,就已经把表确定下来了
5.当我们去调用基类中的虚函数的时候,它就先找实例所指向的那个虚函数表,查到重写函数的地址
6.找到重写的函数代码并调用