在 Java 中有不同的结果,C++在递归中使用 +=

Different results in Java and C++ using += in recursion

本文关键字:递归 C++ 结果 Java      更新时间:2023-10-16
下面

非常简单的Java代码有奇怪的输出,但C和C++中的相同逻辑代码有正确的输出。我尝试使用 JDK 1.7 和 JDK 1.3(相对 JRE(,奇怪的输出总是在那里。

public class Test {
    public static int sum=0;
    public static int fun(int n) {
        if (n == 1)
            return 1;
        else
            sum += fun(n - 1);  // this statement leads to weird output
        // { // the following block has right output
        //     int tmp = fun(n - 1);
        //     sum += tmp;
        // }
        return sum;
    }
    public static void main(String[] arg) {
        System.out.print(fun(5));
    }
}

输出为 1,应为 8。相对 C/C++ 代码如下:

#include<stdio.h>
int sum=0;
int fun(int n) {
        if (n == 1)
            return 1;
        else
            sum += fun(n - 1);
        return sum;
    }
int main()
{
    printf("%d",fun(5));
    return 0;
}

添加测试Java代码:

class A {
    public int sum = 0;
    public int fun(int n) {
        if(n == 1) {
            return 1;
        } else {
            sum += fun(n - 1);
            return sum;
        }
    }
}
public class Test {
    public static void main(String arg[]){
        A a = new A();
        System.out.print(a.fun(5));
    }
}

问题是这一行:

 sum += fun(n - 1);

正在更新变量sum.

假设您只是尝试将 1 到 N 的数字求和,那么它应该进行计算 根据f(N - 1)计算f(N) . 这不需要您参考sum...当然,它不需要您更新它。

(我小心不要告诉你答案是什么......因为如果你自己弄清楚,你会学到更多。


顺便说一句,您的算法中的缺陷没有任何特定于 Java 的内容......


值得注意的是,真正的问题与静态变量与实例变量无关。 真正的问题是,像这样的递归函数不应该使用任何一种变量。 现在在这个例子中,你可以侥幸逃脱,但如果递归涉及这样的事情:f(N) = f(N-1) + f(N-2)你很容易发现不同的调用树相互干扰。

在这种情况下,更正确的解决方案是将方法编写为:

int fun(int n) {
    if (n == 1)
        return 1;
    else
        return n + f(n - 1);
}

正如我所说,您不需要引用或更新sum变量。

为了

给出一个完整的答案,我将为了好玩(3(而运行这个。对于那些不感兴趣的人,为什么这对C++有效,但不适用于Java,请忽略我的回答。

以下是Java正在做的事情:

室内乐趣(3(

sum += sum + fn(n-1) // sum is 0

成为

sum = 0 + fun(2) // sum is 0

然后进入乐趣(2(

sum = 0 + fun(1) // sum is 0

然后进入乐趣(1(

return 1 // sum is 0

回到乐趣(2(

sum = 0 + 1; // sum is 0

成为

sum = 1; // sum will soon become 1

重回乐趣(3(

sum = 0 + 1; // sum is 1

成为

sum = 1; // sum gets reset to 1

以下是C++正在做的事情:

室内乐趣(3(

sum += fn(n-1) // sum is 0

成为

sum = sum + fn(2) // sum is 0

然后进入乐趣(2(

sum = sum + fn(1) // sum is 0

然后进入乐趣(1(

return 1 // sum is 0

回到乐趣(2(

sum = sum + 1 // sum is 0

成为

sum = 0 + 1 => sum = 1 // sum will soon become 1

重回乐趣(3(

sum = sum + 1 // sum is 1

成为

sum = 1 + 1 // sum will soon become 2

你应该做什么:我不知道为什么C++在进行函数调用后而不是之前评估sum。我不知道这是否在规格中。但我知道你不应该在任何语言中依赖这一点。正确的解决方案是:

int fun(int n) {
    if (n == 1)
        return 1;
    else
        return n + f(n - 1);
}

目前对这个问题的回答并没有触及问题的根源。Java 中的行为是由于以下事实:

sum += fun(n - 1); 

相当于:

sum = sum + fun(n - 1); 

其中sumfun(n - 1)之前计算并存储值。我们可以通过转到 JLS 第 15.26.2 节来了解这一点。复合赋值运算符说:

形式为 E1 op= E2 的

复合赋值表达式等效于 E1 = (T( ((E1( op (E2((

然后计算左操作数

,然后计算右操作数并执行操作。因此,sum在每次递归迭代之前都会被评估,并一直0回来。

这也意味着,如果您将行切换到以下内容:

sum = fun(n - 1) + sum ;

它将产生您想要的效果,因为首先会评估fun

在C++

sum += fun(n - 1);

也等效于:

sum = sum + fun(n - 1);  

这在C++草案标准部分5.17值和复合赋值运算符中有所涉及,其中说:

形式为 E1 op = E2

的表达式的行为等效于 E1 = E1 op E2,只是 E1 只计算一次。[...]

主要区别在于,左侧和右侧的评估顺序未指定,这在第1.9节程序执行15段中有所涉及,该段说:

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的计算是无序的。[...]

因此,18C++中都是有效的结果。我们可以看到这个实时 gcc 给了我们 8,clang 给了我们 1。

试试这个:

public class Test {
   public static int fun(int n) {
      System.out.println("processing n " + n );
      if (n == 1)
        return 1;
       else{
           return n + fun(n - 1);
      }
   }
   public static void main(String[] arg) {
      System.out.print(fun(5));
   }
}
public static int fun(int n) {
    if (n == 1)
        return 1;
    else
        return n +  fun(n - 1);
} 

顺便说一句,如果你想以与 C 代码相同的方式执行此操作,只需将 sum 定义为"整数"而不是"整数">

return n <= 1 ? n : n + fun(n-1);

试试这个

   static int getsum(int num){
        int sum = 0;
        if(num == 1){
            return num;
        }else{
             sum  = num+getsum(num-1);  
        }
        return sum ;
    }

你真的需要在那里逐步完成你的逻辑。这没有任何意义。f(5( = 8 的"奇怪"输出是吗?(你实际上不小心写了一个计算 2^(n-2( 的函数,但这似乎无关紧要(

我无法向您解释任何语法错误的地方 - 这是算法本身。它只是没有做你想要它做的事情。一个大红旗,应该让你印象深刻:变量 n 甚至没有直接添加到任何地方的总和!当你调用 fun(5( 时,甚至没有使用这个 5。它刚刚传递到 f(4( 中。

在我看来,你的逻辑是这样的:从 n->1 递归循环,并将其添加到总和中。在这种情况下,您的函数应该是:

void fun(int n){
    if(n == 0)
        return;
    sum += n;
    fun(n-1);
}

或类似的东西。但这并不是一个非常...递归的做事方式。完全没有变量会更好。非基本情况:返回 n+fun(n-1(。

同样在将来,当你说某些代码有"奇怪的输出"时,你可能应该同时提供 1( 奇怪的输出和 2( 你期望的输出。我们都只是在猜测你想写什么。