下面,我就“多态”的一些体验写下来,至于对象的又生到死的过程,这里就不讲了。
生物界,生命形式多姿多彩,无奇不有,无处不体现着多态,生物界的多种生态形式,归根到底是因为生物具有一定的“遗传性”,也许你长得比你弟弟要矮些,但不一定是你弟弟吃的东西比你好,很有可能,是”遗传“在作怪,或者某个“长得高的变量基因”没有遗传给你,或许是你的访问权限不够,那也是听天又命的事情。那么程序的世界里会是什么样子呢?具体的实现机制又是怎样的呢?
在此之前,我们得给程序世界的"多态性",定一个量,要不然就会太泛,当然,有部分概论是借鉴王涛先生的,请大家体谅。
多态指,同一类事物,同一方法,能表现不同的形式,比如抽象方法覆写,方法重写等等,都是多态的表现,我们来看一个简单的例子。
例一
namespace _1_2_Inheritance
{
public abstract class A
{
public string name = "A_name";
public abstract void DoAnythings();
}
public class B : A
{
public string name = "B_name";
public override void DoAnythings()
{
Console.WriteLine("B name is {0}", name);
}
}
public class C : B
{
public string name = "C_name";
public override void DoAnythings()
{
Console.WriteLine("C name is {0}", name);
}
}
public class TestInher
{
public static void Main()
{
A c = new C();
c.DoAnythings();
Console.WriteLine(c.name);
Console.ReadLine();
}
}
}
代码实在太简单,我都不好意思讲,说实话,在我没有看《你必须知道的.net》之前,我一直都认为c.name等于“C_name”,而且深信不疑。
还是先简单的介绍一下.net的堆和栈机制,栈一般存放“值类型”,当然包括指针pointer,而且栈内存又系统负责回收,Clr的Gc也不用管,栈主要保存一些方法的调用指令,以及一些局部变量。也就是说,程序代码中的所有方法都会被“压栈”,就好比我们经常吃的“柠檬口味的乐事”,都是一片一片,整整齐齐又上往下压的。
“引用类型” 的对象分配比较有意思,先在栈中,分配一个指针空间,此时为null,然后再堆中任意开辟一块内存空间,保持实实在在的数据,然后把堆的地址赋值给栈中的空指针,所以我们要明白一点,”值类型“是和指针完全不同的两个概念,只是他俩都存在栈中而已。堆中的数据需要Gc负责回收,所以我们能少用“引用类型”就要少用,少装点箱子,少开点箱子。
我们来看看“栈内存”中的真实生活
请大家注意左上角第一行数据“0012FFC4”,请大家记住他的位置是第一行top one,我们来单步调试一下,再看下一张图。
单步调试后的“0012FFC4”已经排在top two了,第一行的栈地址是“0012FFC0”,也就是0012FFC4减去4,所以“压栈“是真实的,我绝不会忽悠大家。
大家对堆和栈有一个比较直观的认识后,我们来看看C,B类型对象的变量和方法的分配情况,因为A是抽象的,所以不能直接构造对象,所以不在讨论范围,通过调试,获得一张B和C类型对象的变量和方法的分布图
同名变量情况, 从上到下,也就是压栈
抽象和虚方法,非抽象和虚方法
从图中,我们可以看出,从上到下,从子类到父类的变量依次被初始化,而且子类中都继承和分配了父类的变量,如果方法是抽象的,那么子类直接拷贝父类抽象方法的签名,并根据子类不同的方法实现,来重写父类的抽象方法。
你必须知道的.net中,处理同名变量和抽象方法,有两个原则,一是”对象创建原则“,和“就近原则”,其实都可以归纳为"就近原则”,再联想一下这张图,还有什么不可以搞定呢?
总结
继承,封装,多态等这些概念,只有类比到现实世界中的种种,才可能理解得更透彻,希望对大家有些帮助,欢迎大家讨论,指教...
补充和纠正
晚上看到了大家的评语,很贴切,也很有建设性,谢谢。
就在刚刚,我和王涛先生聊了一些关于,virtual方法和abstract方法和普通方法的内存分布情况,谢谢王涛先生指正一个非常普通,但又容易犯错误的问题。
纠正1,非virtual方法和非abstract方法,在子类中都没有父类的拷贝。
纠正2, 只要是virtual方法和abstract方法,不管子类中有没有override父类的方法,子类中都有父类的virtual方法和abstract方法的拷贝。