加深记忆:先建了三个类,父亲 ,儿子,孙子;
class Dad
{
public Dad()
{
Console.WriteLine("dad构造函数");
}
public void Sing()
{
Console.WriteLine("父亲唱歌");
}
public virtual void Say()
{
Console.WriteLine("这是dad说的");
}
}
class Child:Dad
{
public Child()
{
Console.WriteLine("子类构造函数");
}
void Eat()
{
Console.WriteLine("儿子吃");
}
new void Sing()
{
Console.WriteLine("儿子唱歌");
}
public override void Say()
{
Console.WriteLine("这是儿子说的");
}
}
class Childchild:Child
{
public Childchild()
{
Console.WriteLine("孙子构造函数");
}
}
先看new
child类new了一个sing方法,但是父类也有这个sing方法
走的是父类的sing方法;
因为这时候声明的对象还是父类的对象,你在子类里new了一个方法出来,对于父类来说,并不认识你这个方法,所以我父类还是执行自己的父类方法而已;New的初衷就是为了隐藏基类中包含的定义,但是也只是在声明子类对象时才会生效,通过父类引用指向子类对象,实际上就是一个向上转型的过程。
再看override
首先要知道 普通类能被重写的条件是 父类的方法要是虚方法,或者父类是抽象类,重写抽象方法;
如上代码 父类的say方法时虚方法,子类重写了say方法;
可以看到,走的就是子类的say方法了;
对于虚方法 大概流程看一下;
1、当调用一个对象的函数时,系统会直接去检查这个对象申明定义的类,即申明类,看所调用的函数是否为虚方法;
2、如果不是虚方法,那么它就直接执行该函数。而如果有virtual关键字,也就是一个虚方法,那么这个时候它就不会立刻执行该函数了,而是转去检查对象的实例类。
3、在这个实例类里,他会检查这个实例类的定义中是否有重新实现该虚方法(通过override关键字),如果是有,那么OK,它就不会再找了,而马上执行该实例类中的这个重新实现的函数。而如果没有的话,系统就会不停地往上找实例类的父类,并对父类重复刚才在实例类里的检查,直到找到第一个重载了该虚方法的父类为止,然后执行该父类里重载后的函数。
类似上图,孙子类中没有重写这个虚方法,但是在声明的时候,就会检查出这个方法是虚方法,于是,转去检查实例类ChildChild,发现没有重写这个方法,又去检查ChildChild的父类 也就是Child,发现重写了,执行该方法,并且不在向上找。假如儿子类也没有重写,那么会继续找到最上面的类,执行该虚方法。
在内存中 大概发生了这么个事
声明的这个Dad 引用的是Child实例,但是也仅仅指向了子类中继承的父类的非私有成员的那一部分,这也就是我们用Item调用不出来Child类里Eat方法的原因
再从内存角度说一下:
如果一个父类,变量占用了0.5M内存, 他的子类,变量占用了1M内存, 在实例化父类的时候 系统只需要分配0.5的内存 但是在实例化子类的时候,执行子类的构造函数之前,会先去执行她的父类的构造函数(如上面截图), 也就会实例化一个父类 所以需要分配子类加父类的内存 也就是1.5M;(如果是还有一个类继承了子类 那么实例化的时候 也会一级一级向上查找 把她的父类,父类的父类,都实例化出来)