web-dev-qa-db-ja.com

再帰メソッド中にスタックを表示する方法はありますか?

再帰を学ぼうとしています。このコードを自分の本からコピーし、メソッドが何をいつ実行しているかを追跡できるように表示を追加しました。

public static void main(String[] args) {


    System.out.println(sum(4));

}//main

public static int sum(int n) {
    int sum;
    System.out.println("n = " + n + "\n");
    if (n == 1)
        sum = 1;
    else
        sum = sum(n - 1) + n;   
    System.out.println("n after function = " + n + "\n");
    System.out.println("sum after function = " + sum + "\n");
    return sum;
}    

結果は次のとおりです。

n = 4

n = 3

n = 2

n = 1

関数後のn = 1

関数後の合計= 1

関数の後のn = 2

関数後の合計= 3

関数の後のn = 3

関数後の合計= 6

関数の後のn = 4

関数後の合計= 10

10

スタックに何が保存されているのか、おそらくそれがどのように保存されているのかがわからなくなってきています。

Nが1までカウントダウンしている間にスタックの内容を表示する方法はありますか?

これは奇妙な概念です。これは、理解できない方法で値を格納する目に見えないループのようなものです。

6
MayNotBe

Javaコードからスタックの内容に直接アクセスすることはできませんが、デバッガ(すべての最新のIDEに統合されています)を使用すると、さらに優れたものが得られます。メソッド呼び出しのスタックがリストされます。そして、各レベルで、それを選択すると、スタック上のローカル変数の値。

これが Eclipseでデバッガーを使用する方法に関するチュートリアル です。

8

Eclipseなどのデバッガーを使用して、いつでもスタックを表示できますが、ループを再帰として想定することは、ループを理解するための最良の方法ではありません。スタックを下っていくと、スタックの一番下に到達するまで問題の小さな部分を分解します。問題が解決するのは簡単です。次に、スタックを元に戻すときに、ピースを再び元に戻します。

_sum(4)
sum(3) + 4
sum(2) + 3
sum(1) + 2
1
_

sum(4)sum(3) + 4に分解されます。 sum(3)sum(2) + 3に分解されます。やがてsum(1)に到達します。これには、_1_という簡単な答えがあります(ifステートメントの最初の選択肢)。

さらに分解する方法はないので、スタックに戻って_2_、_3_、および_4_を追加します。同じ計算を行うことができますが、それはループとは非常に異なるもので、スタックを上下に移動します。

5
Karl Bielefeldt

スタックにはアクセスできますが、スタックの値にはアクセスできません。

public class StackTrace {

    public static void main(String[] args) {
        recurse(10);
    }

    static void recurse(int depth) {
        for (StackTraceElement each: new Exception().getStackTrace()) System.out.println(each);
        if (depth > 0) recurse(depth - 1);
    }

}
1
akuhn

もちろん、独自のCスタイルのスタックを維持するプログラムを作成することもできます。この場合、すべての引数とローカルをプッシュし、スタックに値を返します。

public class Main {

    public static void main(String[] args) {
        new Main().run();
    }

    int[] stack = new int[100];
    int base = 0;

    public void run() {

        // Push arguments on stack
        stack[base] = 4;
        // increase base pointer and call method
        base += 1;
        call();
        // retrieve result
        int result = stack[base];
        // print it
        System.out.println(result);

    }

    public void call() {
        // Push locals on stack and increase base, now arg n
        // is at base - 2 and local sum is at base - 1
        stack[base] = 0;
        base += 1;
        // test if n equals 1
        if (stack[base - 2] == 1) {
            // if yes set sum to 1
            stack[base - 1] = 1;
        } else {
            // Push new argument on stack
            stack[base] = stack[base - 2] - 1;
            // increase base pointer and call method
            base += 1;
            call();
            // add result to sum
            stack[base - 1] += stack[base];
            // add n to sum
            stack[base - 1] += stack[base - 2];
        }
        // cache return value
        int result = stack[base - 1];
        // decrease base pointer by size of both args and locals
        base -= 2;
        // Push result on stack
        stack[base] = result;
    }
}

プログラムでは、呼び出し元が引数のプッシュとベースポインターの増加を担当し、レシーバーがベースポインターをリセットしてスタックに戻り値を残すという呼び出し規約を使用しています。

これは、メソッド呼び出しがマシンレベルで行われる方法です。

1
akuhn