尾递归,我们如何去掉return语句呢?

Tail recursion, how do we eliminate the return statement?

这是一个尾递归 C# 程序,它对 1 到 10 的平方求和。除了 AddSquares2() 中的最后一个 else 之外,它可以工作。它不起作用,因为 C# 似乎在 return 值的方法中的每个路径中都需要一个 return 语句。但是,我不需要 return 像这样的尾递归方法中的语句。见评论。

这只是 C# 的一个特性吗?或者有没有办法告诉编译器我知道我在做什么,我不希望每条路径都 return 一个值?

这是我的程序:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Recursive
{
    class Program
    {
        static void Main(string[] args)
        {
            int start = 1;
            int end = 10;
            int sum = 0;
            AddSquares(start, end, sum);
            sum = AddSquares2(start, end, sum);
        }

        private static int AddSquares2(int start, int end, int sum)
        {
            Console.WriteLine($"AddSquares2({start}, {end}, {sum})");
            if (start > end)
                return (sum);
            else
                //should not have to return anything here
                return(AddSquares2(start + 1, end, sum + start * start));
        }

        private static void AddSquares(int start, int end, int sum)
        {
            Console.WriteLine($"AddSquares({start}, {end}, {sum})");
            if (start > end)
                Console.WriteLine($"  The total sum is {sum}");
            else
                AddSquares(start + 1, end, sum + start * start);
        }
    }
}

这是输出:

 AddSquares(1, 10, 0)
    AddSquares(2, 10, 1)
    AddSquares(3, 10, 5)
    AddSquares(4, 10, 14)
    AddSquares(5, 10, 30)
    AddSquares(6, 10, 55)
    AddSquares(7, 10, 91)
    AddSquares(8, 10, 140)
    AddSquares(9, 10, 204)
    AddSquares(10, 10, 285)
    AddSquares(11, 10, 385)
      The total sum is 385
    AddSquares2(1, 10, 0)
    AddSquares2(2, 10, 1)
    AddSquares2(3, 10, 5)
    AddSquares2(4, 10, 14)
    AddSquares2(5, 10, 30)
    AddSquares2(6, 10, 55)
    AddSquares2(7, 10, 91)
    AddSquares2(8, 10, 140)
    AddSquares2(9, 10, 204)
    AddSquares2(10, 10, 285)
    AddSquares2(11, 10, 385)
      The total sum is 385

简而言之,C# 中的方法必须在所有代码执行路径上都有一个 return,您可能不喜欢它,但它确实如此。同样在数学中,所有函数都有一个结果,这就是函数的作用。

说到这里,结果可以是未定义的、Infinity、0、null,或者在 C# 的情况下 return 不可为 null 的类型 int 它可以是 0 . 鉴于签名,这非常有意义。

我们假装一下,我们可以在您的 else 点消失代码执行,怎么样?如果调用方法依赖于一个结果(他们通常这样做)并且即使该方法按照您建议的方式结束,那么调用者会怎么想?好吧,它需要能够处理值的缺失,或者不存在。

如果您真的需要知道您的 else 不产生值,请将您的签名设为 int?(可空)和 return null,以便调用者知道要做什么做。如果它只是缺少总和的数字,则将其设为 0,没有坏处

您可以 "get away with" 不使用 return 的唯一方法是抛出错误。在你的情况下这样做没有意义。

您也可以通过不提供代码路径来避免使用 return(在这种情况下没有 else 子句)

但是,您做对了,因为您已经 return编辑了在您的 else 中递归调用 AddSquares2 的结果。如果你不这样做,你的 AddSquares2 代码将永远不会递归并且不会工作(所以你毕竟必须 return 东西!啊)。

递归方法必须在它们自身内部有一个点,它们 return 不会调用它们自己(以阻止递归永远进行),并且还有一个它们调用它们自己的点(以保持递归进行一段时间)

可能是一个学术练习,但也许值得一提的是,递归是一种循环形式,它(ab)使用调用堆栈来提供循环结构。递归方法可以(并且应该,恕我直言)使用普通循环来完成

问题断言它不需要 "return" 是正确的,但这取决于你对 "return" 的理解。在低级意义上,如果执行尾调用,则此时不需要执行"ret"指令(某种标准堆栈清理类型指令),因为尾调用将使实际执行行为等同于一个简单的循环。

然而,C# 并没有那么接近金属。当我们编写 C# 时,我们指定 intent 并让编译器 (and/or JITter) 负责尾调用、方法内联等优化。 C# return 关键字的存在并不一定意味着或强制执行某些堆栈清理。相反,它表示程序员的意图

如果我们允许您省略 "return",这会使编译器理解您的意图的工作变得更加困难。程序员是否意识到将要发生的优化并在概念上打算将方法变为 "return",或者他的意思是明确不对结果做任何事情。 C# 假定后者。

现在,如果您担心不能省略 return 这意味着无法执行尾调用,请不要担心。编译器和 JITter 会自动处理这些类型的优化。他们以最优化的方式接受您指定的 intentimplement 。他们几乎总是比程序员更好地决定应用这些优化的内容和时间。