senline

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

女儿毕业学期选了门OOP的课程。作为一个统计专业和BA专业的学生,学这门课就是挑战自己。由于缺乏系统的编程学习,有些基础概念理解不是非常透彻,比如类,类实例,方法调用,递归调用等。

我结合她的一个日常作业,简单写下OO的方法调用过程,不是非常严谨,有些步骤(比如出入栈的内容)是原理性的,算是科普吧。

题目是在学习组合类(CompositeClass)的时候的练习题。

有两类形状(Shape):容器类Container 和 圆Circle,这两个形状都继承自Shape(抽象类)。每个容器类下可以有多个形状(可以是子容器或者圆),圆不是容器,不可以有子形状。

以下是部分代码:

abstract class Shape{
    protected Rectangle rect;
    protected String color;

    public Shape(int x, int y, int w, int h, String c){
        rect = new Rectangle(x, y, w, h);
        this.color = c;
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + ":(" + rect.x +"," + rect.y + ")," + color;
    }

    abstract void draw(String indent);
}

class Circle extends Shape{
    public Circle(int x, int y, int w, int h, String c){
        super(x, y, w, h, c);
    }

    @Override
    public void draw(String indent) {
        System.out.println(indent + toString());
    }
}

class Container extends Shape {
    private ArrayList<Shape> elements;

    public Container(int x, int y, int w, int h, String c) {
        super(x, y, w, h, c);
        elements = new ArrayList<Shape>();
    }

    public void add(Shape s) {
        elements.add(s);
        s.color = this.color;

    }

    public void remove(Shape s) {
        elements.remove(s);
        s.color = "black";
    }

    @Override
    public void draw(String indent) {
        System.out.println(indent + this.toString());
        Iterator<Shape> i = elements.iterator();
        while (i.hasNext()) {
            Shape d = i.next();
            d.draw(indent+" ");
        }
    }

  下面是调用类:

public class ShapeComposite {
    public static void main(String args[]) {
        Container Container1 = new Container(10,20,300,300, "yellow");
        Container Container2 = new Container(20,30,100,100, "red");
        Shape Circle1 = new Circle(25,35,20,20, "green");
        Container1.add(Container2);
        Container2.add(Circle1);

        Container1.draw("");
    }
}

  实例的关系如下图:Container1里包含Container2,Container2中包含Circle1。

 

 调用Container1的Draw()方法,打印container1 以及其下字形状的输出,如下样式:

Container:(10,20),yellow  --无缩进(container1)
 Container:(20,30),yellow --缩进1字符(container2)
  Circle:(25,35),yellow   --缩进2字符(circle1)

  通过缩进表示上下级关系。

方法的调用关系:Container1.Draw() ->Container2.Draw()->Circle1.Draw()。

用图表示出来:

 

下面详细说明。

1、调用 Container1.Draw()

缩进参数 indent=“”,表示不缩进。

首先把自己打印出来:System.out.println( indent + this.toString());

Container:(10,20),yellow -- 无缩进

因为container 有子形状,然后挨个拿出来,调用子形状的 draw。在这个例子中,container1下有一个 子container2,调用 其 draw:

d.draw( indent + " " );

这里d 就是 containier2。注意因为 container2 和 container1 类型相同,都是Container,因此draw方法是同一个方法。因此d.draw(indent+" " ) 会再次进入同一个方法(等同递归调用),只不过方法运行的上线文(context)不同了:

在第一次调用draw方法,类的实例时 container1,indent = “”,没有缩进。我们记为 container1.draw。

在第二次调用draw方法,实例时 container2,indent = “ ”,缩进增加了一个空格,我们记为 container2.draw。(因为container2 是 container1 的下级,因此缩进比container的缩进(“””)增加1个空格:indent+“ ”)。

在这里补充下方法调用的过程(假设M1调用M2),分以下几步:

(1)保存现场。就是保存M1当前的运行上下文,将M1的临时变量入栈(push stack),准确讲是将“寄存器”的值、返回地址入栈保存起来

(2)调用方法。将cp当前执行位置修改为被调用方法M2的入口地址

(3)执行被调用方法。顺序执行M2的指令,直到结束。这期间可能还会还会调用其他方法(调用过程一样)。执行完后,将M2的返回值压栈。

(4)返回。

         - cpu执行位置返回到M1方法中调用M2指令的下一条指令(从堆栈中取出调用前的执行位置+1)

         - 取出返回值。从堆栈中取出m2的返回值。

         - 恢复M1的上下文。从堆栈中恢复临时变量的值(寄存器的值),恢复调用前的“现场”。

         - 继续执行M1的剩余指令。

以上就是方法调用的大概过程。

在第二次调用container2.draw方法时,计算机会现将 container1.draw方法里的临时变量 indent 和 d 压到栈中(stack)暂存。此时堆栈的内容是:

 

 然后调用container2.draw(执行代码跳到方法的第一行代码)

2、调用 container2.draw()

container2.draw()和 container1.draw() 是同一个方法。只不过这次调用的indent = “ ”,是一个空格,表示缩进一个空格。

同样,因为container2仍然是个容器类,它会首先把自己打印出来:

Container:(10,20),yellow -- 缩进1字符。

  Container:(10,20),yellow -- 缩进了一个字符。

到这里,总的输出是:
Container:(10,20),yellow   -- 无缩进
  Container:(10,20),yellow -- 缩进1字符。

然后继续遍历其下的字形状,调用子形状的 draw方法。

这个例子中container2下只有一个字形状:Circle1。因此会调用circle1.draw()。

同样,计算机会把container2.draw中的临时变量 indent 和 d 压栈。栈的内容如下:

 

 3、调用 Circle1.draw()

第三次调用是调用 circle2的draw方法,indent 又加了个“ ” 空格,这时候的indent 是2个空格了:“  ”。也就是 circle的输出将缩进2个空格。

因为Circle不是容器类,其下没有子形状,以内draw方法只是把自己打印出来:

    Container:(10,20),yellow -- 缩进了2个空格。

  此时总的输出为:

Container:(10,20),yellow     -- 无缩进。
  Container:(10,20),yellow   -- 缩进1字符。
    Container:(10,20),yellow -- 缩进2字符。 

 

至此,Circle1.draw方法执行完毕,即将返回到 Container2.draw方法的第6行代码(循环体结束代码)。

4、返回container2.draw

Circle1.draw方法执行完毕后,返回到Container2.draw方法的第6行代码(循环体结束代码)。计算机将调用circle.draw方法前压栈的两个临时变量 indent=“ ”(1字符) 和 d = circle1 出栈,赋值给container2.draw 的indent 和 d(恢复现场),继续执行下面的指令。此时栈的状态为:

因为container2只有一个字形状,因此打印为 Circle1后,方法执行完毕。

执行完毕后,返回container1.draw 的 第6行代码。

5,返回container1.draw方法,继续执行。

container2.draw 执行完返回后,计算机将调用circle.draw方法前压栈的两个临时变量 indent=“” (0字符)和 d = Container2 出栈,赋值给container2.draw 的indent 和 d。此时栈的内容清空了。

继续执行container2.draw下面的代码。因为 container1下也只有一个 字形状:container1,因此循环结束,方法返回。

 

最终的输出结果为:

Container:(10,20),yellow  --无缩进
 Container:(20,30),yellow --缩进1字符
  Circle:(25,35),yellow   --缩进2字符

  

 

posted on 2022-06-15 16:57  森蓝2010  阅读(72)  评论(0编辑  收藏  举报