【问题标题】:Error when factorial recursion reaches zero阶乘递归达到零时出错
【发布时间】:2017-02-23 21:00:14
【问题描述】:

为什么以下递归阶乘在 Perl 中会失败,即使它适用于 C++ 和 Java?

Java:

public static long factorial(int n) {
  if (n == 0) {
    return 1;
  } else {
    return factorial(n-1) * n;
  }
}

C++:

long factorial(int n) {
  if (n == 0)
    return 1;
  else
    return n * factorial(n-1);
}

但是这个在 Perl 中失败了(use Carp;):

sub factorial {
    my $n = shift || croak "null value for argument";
    return 1 if $n == 0;  # base case
    return  $n * factorial($n-1);
}

错误信息(第 10 行是在 main() 中调用函数的位置,第 16 行是第二个 return 语句):

 null value for argument at ./fact1.pl line 14.
        main::factorial(0) called at ./fact1.pl line 16
        main::factorial(1) called at ./fact1.pl line 10

(以下内容已被编辑,感谢 Matt Jacob 指出我在原始版本中的遗漏。)

原来的版本,没有鲤鱼,但有|| return,错误信息是:

Use of uninitialized value in multiplication (*) at ./fact1.pl line 16.

这是在没有命令行参数时尝试使脚本静默的结果。 (没有投诉uninitialized value。)

对基本情况的小修改使其也可以在 Perl 中工作:

sub factorial {
    my $n = shift || croak "null value for argument";
    return 1 if $n == 1;  # base case
    return $n * factorial($n-1);
}

(当然,以取消处理0(零)输入为代价。)


经验教训

  1. 问题出在我认为的不同位置
    • Perl 代码的错误在于它试图比 C++ 或 Java 代码 sn-ps 做更多的事情(一种非常粗略的方法)
    • 不同的错误信息应该给了我提示
  2. 以下内容可以帮助我定位问题:
    • 删除shift;之后的所有内容
    • 记住0(零)表示false
  3. Logical Defined-Or 从 Perl 5.10 开始就存在
  4. 至于正确的错误处理,this page 提供了很好的概述。

【问题讨论】:

  • 回复“why not the same if/else”:java来自一个课程示例。 (所以大括号)。 C++,我在 Perl 之后的测试失败了。 (但我喜欢在简单的表达式中去掉大括号)。在 Perl 中,等效的“无括号”解决方案是条件子句最后出现。
  • 您在使用croak 之前关于错误消息的陈述与您问题中的代码不匹配。如果您调用factorial()$n 将是undef,并且警告将是关于“未初始化的 $n 在数字 eq”。 (但是undef 会隐式转换为 0,并且该函数仍将返回 1。)真正的问题是:为什么您要使用未定义/空值调用该函数并期望它在 Perl 中不能工作用其他语言工作!
  • @MattJacob 你是绝对正确的。它不仅仅是“没有”,而是带有“|| return”。当没有命令行参数时,它是为了帮助我。 (我将编辑问题并添加您的评论。)

标签: perl function recursion error-handling parameter-passing


【解决方案1】:

Perl 有一些相当非正统的规则来判断什么是正确的。

特别是,数字 0 在用于布尔表达式时被认为是假的。所以当你这么说的时候

my $n = shift || croak "null value for argument";

如果shift 将 0 作为参数,则它被视为 false,这意味着 || 运算符需要计算其第二个操作数。

幸运的是,Perl 的作者预见到了这种用例,因此他们定义了一个替代的或运算符。 || 检查第一个值是否为真,// 检查第一个值是否已定义。

my $n = shift // croak "null value for argument";

这将按照您的意图进行,除非$n 可能是文字值undef。在这种情况下,您将需要一些更复杂的自省来检查参数是否存在,但对于这个阶乘函数,这应该足够了。

【讨论】:

  • 你可能还补充说//||or有更高的绑定,所以实际上$n = shift || croak实际上将croak的返回值分配给$n。由于croakdie,因此没有返回值,因为它永远不会返回,但这仍然很重要。
  • @simbabque,你警告我们 Perl 实际上正在做我们希望它做的事情。当使用or 时,赋值不必要地发生,我们通过检查赋值结果间接检查我们想要检查的内容。当使用||// 时,需要检查的实际内容会被检查,并且赋值不会or 那样不必要地发生。这实际上是理想的情况,不需要警告!
  • 在我使用的教程和资料中似乎奇怪的遗漏了他们没有这个运算符,所以我做了一点历史搜索。 defined-or 运算符 (//),正如我在本书 (Learning Perl, 5th Edition) 中所说的那样,我首先设法找到了它,它是在 Perl 5.10 中引入的。
  • 引用logical defined-or operator名称下的相关帮助页面(和书签)。
猜你喜欢
  • 1970-01-01
  • 2016-01-28
  • 1970-01-01
  • 2015-04-19
  • 2019-08-06
  • 2019-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多