使用 Eclipse 的调试器了解 Java 递归
Understanding Java recursion using Eclipse's debugger
我正在 Java 中进行一些简单的递归练习,以理解这个概念(我很难理解)。对于到目前为止的所有研究,我都严重依赖 Eclipse 的调试器来准确理解我的代码在做什么。然而,当谈到递归时,我发现情况并非如此,因为很难准确跟踪正在发生的事情。
考虑以下代码,returns第n
个斐波那契数的方法:
public int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
使用此代码的调试器时,很难准确跟踪正在发生的事情 where/when。只有一个变量,它每一步都在变化,并且对于较小的 n
值,例如 7,它已经变得难以跟踪,因为在 13 之前执行的步骤太多了终于到了。
我想知道:
- 如何以更好的方式调试我的递归代码(通常),以便更好地理解递归?
- 考虑到
return fibonacci(n - 1) + fibonacci(n - 2)
这个概念很容易理解,我是不是太专注于调试这类事情了?
您可以在打印 n
值及其 fibonnacci
值的每条指令中使用简单的 System.out.prinln()
对其进行调试。
这是一个示例代码:
public int fibonacci(int n) {
if (n == 0 || n == 1) {
System.out.println("your value is: " +n+ " and its Fibonacci value is: "+n);
return n;
} else {
System.out.println("your value is: " +n+ " and its Fibonacci value is: "+fibonacci(n - 1) + fibonacci(n - 2));
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
你可以测试DEMO here.
我花了一段时间才掌握递归,出于某种原因,我从未发现调试器有用。我将尝试向您解释我是如何做的,它不涉及调试器(免责声明:这是个人方法,可能不正确或不通用)。
在递归代码中,你总是至少有一个终止块和一个
递归块。在精神上隔离这两个部分。
return n;
-> 终止块
return fibonacci(n - 1) + fibonacci(n - 2);
-> 递归块
递归块表示递归的抽象规则。您可以使用相同的函数获取这些值,而不是将这些值保存在变量 Fn1
和 Fn2
中。想想一堵砖墙:你的递归函数创建了一面墙,在现有的墙上增加了一块砖。在递归块内部,在某个步骤中,您不介意现有墙由谁创建以及如何创建,您只需向其中添加一块新砖。碰巧墙是由同一个函数创建的,当时是一块砖。
在终止块处调用带有一些值的代码。在这个值的过程结束时应该发生什么?谈到斐波那契,在过程结束时(n = 1
或 n = 0
)我必须再次将这些数字添加到总数中。这是由递归块完成的。换句话说,终止块将具体值(而不是如何获取它们的过程)提供给递归块。
当我必须进行故障排除时,我会打印每一步的值,这是我为我找到的最佳解决方案。然后我检查它们是否符合预期。对于您的斐波那契数列,我希望看到类似
的输出
代码:
public static int fibonacci( int n ) {
System.out.println( "\nInput value: " + n );
if( n == 0 || n == 1 ) {
System.out.println( "Terminating block value: " + n );
return n;
}
else {
System.out.println( "Recursion block value: fibonacci(" + (n - 1) + ") + fibonacci(" + (n - 2) + ")" );
int result = fibonacci( n - 1 ) + fibonacci( n - 2 );
System.out.println( "Recursion block return value: " + result );
return result;
}
}
输出:
Input value: 4
Recursion block value: fibonacci(3) + fibonacci(2)
Input value: 3
Recursion block value: fibonacci(2) + fibonacci(1)
Input value: 2
Recursion block value: fibonacci(1) + fibonacci(0)
Input value: 1
Terminating block value: 1
Input value: 0
Terminating block value: 0
Recursion block return value: 1
Input value: 1
Terminating block value: 1
Recursion block return value: 2
Input value: 2
Recursion block value: fibonacci(1) + fibonacci(0)
Input value: 1
Terminating block value: 1
Input value: 0
Terminating block value: 0
Recursion block return value: 1
Recursion block return value: 3
您还可以阅读有关 Induction 的有用信息,它与递归密切相关。
如何调试我的递归代码?
首先,确保您已切换到“调试”透视图,并且您看到的是正确的 windows(Variables
、Expressions
、Debug
和您的源代码) 例如像这样:
接下来,请注意在 Debug
中您可以看到该方法当前被调用的频率。此列表会根据调用和尚未返回的方法的数量而增长和缩小。
您可以单击其中一种方法来更改范围。查看更改范围时 Variables
的内容如何变化。
最后,要检查任意事物,请在 Expressions
window 中输入表达式。这几乎就像实时编码。您几乎可以检查任何东西。
我是不是太专注于调试了?
没有。学会正确地做事,它会在以后为你节省很多时间。
添加一个System.out.println()
需要重新编译,你需要重现情况,这并不总是那么简单。
"Inline" 代码使使用 Eclipse 调试器变得更加困难,因为它非常注重显示不存在的局部变量。您可以通过使事情更详细并保存到变量来使其更容易逐步完成。通过这种方式,您可以更轻松地查看正在发生的事情以及结果。例如,按如下方式修改您的代码将使调试器在以下位置更容易使用:
public int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
} else {
int nMinus1 = fibonacci(n - 1);
int nMinus2 = fibonacci(n - 2);
int retValue = nMinus1 + nMinus2;
return retValue;
}
}
免责声明:我没有尝试编译此代码。
我正在 Java 中进行一些简单的递归练习,以理解这个概念(我很难理解)。对于到目前为止的所有研究,我都严重依赖 Eclipse 的调试器来准确理解我的代码在做什么。然而,当谈到递归时,我发现情况并非如此,因为很难准确跟踪正在发生的事情。
考虑以下代码,returns第n
个斐波那契数的方法:
public int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
使用此代码的调试器时,很难准确跟踪正在发生的事情 where/when。只有一个变量,它每一步都在变化,并且对于较小的 n
值,例如 7,它已经变得难以跟踪,因为在 13 之前执行的步骤太多了终于到了。
我想知道:
- 如何以更好的方式调试我的递归代码(通常),以便更好地理解递归?
- 考虑到
return fibonacci(n - 1) + fibonacci(n - 2)
这个概念很容易理解,我是不是太专注于调试这类事情了?
您可以在打印 n
值及其 fibonnacci
值的每条指令中使用简单的 System.out.prinln()
对其进行调试。
这是一个示例代码:
public int fibonacci(int n) {
if (n == 0 || n == 1) {
System.out.println("your value is: " +n+ " and its Fibonacci value is: "+n);
return n;
} else {
System.out.println("your value is: " +n+ " and its Fibonacci value is: "+fibonacci(n - 1) + fibonacci(n - 2));
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
你可以测试DEMO here.
我花了一段时间才掌握递归,出于某种原因,我从未发现调试器有用。我将尝试向您解释我是如何做的,它不涉及调试器(免责声明:这是个人方法,可能不正确或不通用)。
在递归代码中,你总是至少有一个终止块和一个 递归块。在精神上隔离这两个部分。
return n;
-> 终止块return fibonacci(n - 1) + fibonacci(n - 2);
-> 递归块递归块表示递归的抽象规则。您可以使用相同的函数获取这些值,而不是将这些值保存在变量
Fn1
和Fn2
中。想想一堵砖墙:你的递归函数创建了一面墙,在现有的墙上增加了一块砖。在递归块内部,在某个步骤中,您不介意现有墙由谁创建以及如何创建,您只需向其中添加一块新砖。碰巧墙是由同一个函数创建的,当时是一块砖。在终止块处调用带有一些值的代码。在这个值的过程结束时应该发生什么?谈到斐波那契,在过程结束时(
n = 1
或n = 0
)我必须再次将这些数字添加到总数中。这是由递归块完成的。换句话说,终止块将具体值(而不是如何获取它们的过程)提供给递归块。
当我必须进行故障排除时,我会打印每一步的值,这是我为我找到的最佳解决方案。然后我检查它们是否符合预期。对于您的斐波那契数列,我希望看到类似
的输出代码:
public static int fibonacci( int n ) {
System.out.println( "\nInput value: " + n );
if( n == 0 || n == 1 ) {
System.out.println( "Terminating block value: " + n );
return n;
}
else {
System.out.println( "Recursion block value: fibonacci(" + (n - 1) + ") + fibonacci(" + (n - 2) + ")" );
int result = fibonacci( n - 1 ) + fibonacci( n - 2 );
System.out.println( "Recursion block return value: " + result );
return result;
}
}
输出:
Input value: 4
Recursion block value: fibonacci(3) + fibonacci(2)
Input value: 3
Recursion block value: fibonacci(2) + fibonacci(1)
Input value: 2
Recursion block value: fibonacci(1) + fibonacci(0)
Input value: 1
Terminating block value: 1
Input value: 0
Terminating block value: 0
Recursion block return value: 1
Input value: 1
Terminating block value: 1
Recursion block return value: 2
Input value: 2
Recursion block value: fibonacci(1) + fibonacci(0)
Input value: 1
Terminating block value: 1
Input value: 0
Terminating block value: 0
Recursion block return value: 1
Recursion block return value: 3
您还可以阅读有关 Induction 的有用信息,它与递归密切相关。
如何调试我的递归代码?
首先,确保您已切换到“调试”透视图,并且您看到的是正确的 windows(Variables
、Expressions
、Debug
和您的源代码) 例如像这样:
接下来,请注意在 Debug
中您可以看到该方法当前被调用的频率。此列表会根据调用和尚未返回的方法的数量而增长和缩小。
您可以单击其中一种方法来更改范围。查看更改范围时 Variables
的内容如何变化。
最后,要检查任意事物,请在 Expressions
window 中输入表达式。这几乎就像实时编码。您几乎可以检查任何东西。
我是不是太专注于调试了?
没有。学会正确地做事,它会在以后为你节省很多时间。
添加一个System.out.println()
需要重新编译,你需要重现情况,这并不总是那么简单。
"Inline" 代码使使用 Eclipse 调试器变得更加困难,因为它非常注重显示不存在的局部变量。您可以通过使事情更详细并保存到变量来使其更容易逐步完成。通过这种方式,您可以更轻松地查看正在发生的事情以及结果。例如,按如下方式修改您的代码将使调试器在以下位置更容易使用:
public int fibonacci(int n) {
if (n == 0 || n == 1) {
return n;
} else {
int nMinus1 = fibonacci(n - 1);
int nMinus2 = fibonacci(n - 2);
int retValue = nMinus1 + nMinus2;
return retValue;
}
}
免责声明:我没有尝试编译此代码。