代码改变世界

一个关于继承和多态的问题(思索篇)

2005-01-06 22:55  FantasySoft  阅读(1006)  评论(2编辑  收藏

        在上一篇Blog中给大家留了一个问题,如果大家有上机去尝试一下,就会知道答案是1了。虽然代码用的是Java,但是这个问题是具有同性的,即使换了C#去写,也会让我们思索一番。下面先从Java的角度去阐述一下这个问题。
        对于这个答案的解释,我们可以从两个方面去看:一是从语义的角度;首先,g这个方法对于类C而言是继承自类B,那么就应该与类B中的方法g的行为完全一致,而方法g调用了方法f,其访问修饰符是private,也就是说不可以被覆盖(override)了,从这里就可以知道方法g的行为(调用类B中的方法f)就已经确定了。所以类C继承了类B,使用方法g就不能乱来了,就得乖乖的按照类B定义好的方法g办事了;
        二是从底层实现的角度;如果大家觉得上面的说法不够说服力的话,还是让我们来点更加细节的吧。我们都知道创建一个实例的时候,会根据继承层次相应地调用不同的构造函数。当类C实例化的时候,先调用类A的构造函数,接着是类B的,最后是类C的,由于我们这里并没有为这三个类定义特殊的构造函数,默认不带参数的构造函数就会被调用了。那么在类C的一个实例被创建的时候,到底发生了什么呢?首先是类A的构造函数被调用,也就是在heap中为这个实例分配了内存以存放实例变量(instance variable)和方法的指针,由于类A中没有实例变量,就只为存放方法g的指针分配了空间,同时这个指针的内容是可以被修改的;接着类B的构造函数被调用,由于方法g被override了,那么原来存放着方法g指针的地址就会被修改,指向类B的方法g,而方法g中调用的方法f由于其访问修饰符是private,所以这个被调用的方法f的指针值就已经固定了;接着类C的构造函数被调用,只是增加了方法f的指针,注意是增加。综上所述,由于在Java中,所有类方法都是虚函数,都是可以被继承的,因此,如果访问修饰符不为private,都会在类构造函数被调用的时候分配空间以初始化这些方法的指针。
        以下是上一篇blog中Java代码所对应的C#代码:

using System;
namespace
 DefaultNamespace
{
    
public class Test 
{
        
public static void Main(string[] args) 
{
            A ref1 
= new
 C();
            B ref2 
=
 (B)ref1;
            Console.WriteLine(ref1.g());
            Console.WriteLine(ref2.g());
        }

    }

    
    
public class A {
       
private int f() 
{
            
return 0
;
        }

       
public virtual int g() {
             
return 3
;
        }

    }

    
    
public class B: A {
       
private int f() 
{
            
return 1

        }

       
public override int g() {
            
return
 f();
        }

    }

    
    
public class C: B {
       
public int f() 
{
            
return 2
;
        }

    }

}


        从以上代码,我们可以发现,C#对于继承体系有着更为严格和具体的定义。譬如需要通过virtual关键字来说明该方法是虚函数,是可以被override的以及override的关键字的使用等。
        呼,终于都说完了这一串让自己战战兢兢的文字。还是一句老话,自己明白容易,要讲清楚就难了。关键还是认识不够深刻啊,请各位多多指点了。