跳转至内容

Perl 编程/函数

来自 Wikibooks,开放世界中的开放书籍
上一页:修饰符 索引 下一页:Perl 5.10 新增功能

Perl 函数是一组代码,允许它轻松地重复使用。函数是除最小的程序之外的所有程序的关键组织组件。

到目前为止,我们每次只编写了几行 Perl 代码。我们的示例程序从文件的顶部开始,一直执行到文件的底部,并使用诸如if, elseandwhile之类的控制流关键字进行了一些跳转。但是,在许多情况下,在程序中添加另一层组织很有用。

例如,当典型程序出错时,它会打印“错误消息”。打印错误消息的代码可能如下所示

print STDOUT "something went wrong!\n";

其他消息可能看起来略有不同,例如

print STDOUT "something ELSE went wrong!\n";

我们可以用数百行这样的代码来填充我们的代码,并且事情会一直正常运行……一段时间。但是,迟早,我们会希望将错误消息与报告程序有关的无害信息的“状态消息”分离开来。为此,我们可以用单词“ERROR”作为所有错误消息的前缀,并用“STATUS”作为所有状态消息的前缀。典型错误消息的代码将更改为

print STDOUT "ERROR: something went wrong!\n";

问题是,对于数百条错误消息,更改它们全部将是一件麻烦事。这就是子程序可以提供帮助的地方。

维基百科将子程序定义为“作为较大程序的一部分执行特定任务的一系列指令。可以从程序中的不同位置调用子程序,从而允许程序重复访问子程序,而无需多次编写子程序的代码。”

所有这些令人费解的话都意味着我们可以将错误消息代码包装在一个地方,如下所示

sub print_error_message {
  my($message) = @_;
  print STDOUT "ERROR: " . $message . "\n";
}

每当程序出错时,我们都可以激活或调用此子程序,并使用我们喜欢的任何消息

print_error_message("something bad happened");

print_error_message("something really horrible happened");

print_error_message("something sort of annoying happened");

并查看以下消息

ERROR: something bad happened

如果我们需要更改错误消息的格式,例如,包括一些感叹号,只需更改子程序即可

sub print_error_message {
  my($message) = @_;
  print STDOUT "ERROR: " . $message . "!!!\n";
}

这确实是一个简单的例子,子程序还有一些其他优点,但总而言之就是这样。在一个地方输入,在一个地方修复,在一个地方更改。

现在让我们更详细地了解一下子程序。以下内容大部分将使用以下子程序,如果还不明显,它将两个数字加在一起并返回它们的总和。

sub add_two_numbers {
  my($x, $y) = @_;
  my $sum = $x + $y;
  return $sum;
}

子程序的组成部分

[编辑 | 编辑源代码]
sub add_two_numbers {
  my($x, $y) = @_;
  my $sum = $x + $y;
  return $sum;
}

函数的第一行以关键字sub开头,后跟函数名。任何不是保留 Perl 单词(例如for, while, ifandelse)的字母和数字字符串都是有效的函数名。名称描述了子程序作用的子程序可以使程序更易于阅读。

原型(很少使用)

[编辑 | 编辑源代码]
sub add_two_numbers($$) {

可选的($$)指定此子程序期望多少个参数。($$)表示“此函数需要两个标量值”。Perl 原型并非大多数有其他语言经验的人所期望的那样:相反,原型会更改传递给子程序的参数的上下文。

可以通过在函数名前加&来禁用原型,但不建议这样做。

原型很少使用,因为使用正常参数传递方法更容易。

子程序的主体执行“工作”,并由三个主要部分组成。

读取参数

[编辑 | 编辑源代码]

传递给子程序的信息片段称为参数实际参数。例如,在

add_two_numbers(3, 4);

3and4是子程序的参数add_two_numbers.

Perl 通过一个由@_表示的数组将参数传递给子程序。通常,为这些参数指定有意义的名称会更方便,因此函数的第一行通常如下所示

sub add_two_numbers {
  my($x, $y) = @_; # reading parameters
  my $sum = $x + $y;
  return $sum;
}

将内容放入名为@_的两个变量中$xand$y. $xand$y被称为形式参数。形式参数(参数)和实际参数之间的区别很细微,在大多数情况下并不重要。在维基百科文章参数(计算机科学)中有对其进行了一些描述。请注意不要将特殊变量$_and@_(传递给函数的参数数组)混淆。

某些子程序不需要任何参数,例如

sub hello_world {
  print STDOUT "Hello World!\n";
}

将“Hello World”打印到 STDOUT。此子程序不需要任何有关如何执行其工作的额外信息,因此不需要任何参数。

大多数现代编程语言可以为程序员节省显式分解参数数组为变量的麻烦。不幸的是,Perl 没有。另一方面,这使得编写具有可变数量参数的子程序变得非常容易。

在编程上下文中,参数的含义与参数几乎相同(有关详细信息,请参阅参数)。这两个词经常被混淆,但不会造成理解上的损失。

重要说明:全局变量和局部变量
[编辑 | 编辑源代码]

与 C 或 Java 等编程语言不同,在 Perl 子程序中创建或使用的所有变量默认情况下都是全局变量。这意味着程序中子程序外部的任何部分都可能修改这些变量,并且您的子程序可能在不知情的情况下修改了它不应该修改的变量。在小型程序中,这通常很方便,但随着程序变得越来越长,这通常会导致复杂性,并且被认为是不好的做法。

避免此陷阱的最佳方法是在变量第一次出现时在其前面加上关键字my。这告诉 Perl 您只希望这些变量在最近的封闭花括号组内可用。实际上,这些局部变量充当子程序中使用的“临时空间”,当子程序返回时会消失。行use strict;在程序的顶部将指示 Perl强制您在变量前使用my,以防止您意外创建全局变量。

您可能在一些较旧的 Perl 程序中看到的my的替代方案是local关键字。localmy有点类似,但处理起来更复杂。最好在您自己的程序中坚持使用my

“作用域”描述了变量是局部变量还是全局变量,以及其他一些复杂性。有关技术讨论,请参阅作用域

子程序的有趣部分

[编辑 | 编辑源代码]

在子程序的中间,您可能会发现所有内容中更有趣的“核心”。在我们的 add_two_numbers 子程序中,这是实际执行加法的部分

sub add_two_numbers {
  my($x, $y) = @_;
  my $sum = $x + $y; # the interesting part
  return $sum;
}

在此中间部分,您可以做任何您想做的事情,算术运算、打印到文件,甚至调用其他子程序。

return语句

[编辑 | 编辑源代码]

最后,一些子程序使用return关键字。

sub add_two_numbers {
  my($x, $y) = @_;
  my $sum = $x + $y;
  return $sum; # the return statement
}

例如

$sum = add_two_numbers(4, 5);

将设置$sum为 9(4 和 5 的和)。

return也可以不用任何返回值作为快捷方式,在到达结束的}

调用子程序

[编辑 | 编辑源代码]

子程序可以在 Perl 程序的任何地方声明。它们可以像这样调用

add_two_numbers(4, 5); # the safest approach

add_two_numbers 4, 5; # only if predeclared

&add_two_numbers(4, 5); # older Perl syntax, but still valid

如果没有“&”前缀,除非子程序已预先声明,否则需要使用括号。

函数调用函数

[编辑 | 编辑源代码]

函数本身就为编写良好的代码提供了重要的基石,但将函数组合在一起才能真正发挥其威力。

正如您可能预期的那样,从另一个函数内部调用函数与从程序中位于任何花括号外部的部分调用函数没有什么区别。

此函数将两个数字相加,然后将它们乘以 3。请耐心等待这些函数的无用性。在您构建自己的程序以解决您独特的问题时,您会立即看到它们的用处。

sub add_two_numbers_and_mult_by_three {
  my($x, $y) = @_;    # read parameters
  my $sum = add_two_numbers($x, $y);   # add x and y, put result in sum
  my $sum_times_three = $sum*3;     # multiply by three
  return $sum_times_three;     # return result
}

这一行

my $sum = add_two_numbers($x, $y);   # add x and y, put result in sum

调用我们的函数add_two_numbers并将结果放入我们的$sum变量中。很简单,对吧?

在这个函数中,我们实际上编写了比我们需要的更多的代码。它可以简化为更小的东西,但同样易于阅读

sub add_two_numbers_and_mult_by_three {
  my($x, $y) = @_;    # read parameters
  return 3*add_two_numbers($x, $y);   # add x and y, mult by 3, return
}

函数调用自身 - 递归

[编辑 | 编辑源代码]

我们已经看到函数调用其他函数,但编程中一个简洁的概念是函数调用自身。这称为递归。起初,这似乎可能导致所谓的无限循环,但它实际上是相当标准的编程。

在数学中,阶乘函数将一个正整数乘以小于自身的所有正整数。例如,“5 的阶乘”(通常写成5!) 是通过将 5 乘以 4 乘以 3 乘以 2 乘以 1 计算得出的。当然,1 不会改变结果。阶乘函数可用于计算诸如在餐桌上安排亲属的不同方式的数量。

阶乘为递归提供了一个自然的例子,尽管它也可以用while循环轻松地编写。

sub factorial {
  my($num) = @_;
  if ($num == 1) {
    return 1;   # stop at 1, factorial doesn't multiply times zero
  } else {
    return $num*factorial($num - 1);   # call factorial function recursively
  }
}

这里的自引用行是

return $num*factorial($num - 1);   # call factorial function recursively

它从阶乘函数内部调用阶乘函数。这将永远持续下去,但我们有一种阻止它的停止标志

if ($num == 1) {
  return 1;   # stop at 1, factorial doesn't multiply times zero
}

这停止了对阶乘的调用序列,并防止了永无止境的无限循环。

用一种长篇大论的方式写出来

 factorial(5)
 = 5*factorial(4)
 = 5*4*factorial(3)
 = 5*4*3*factorial(2)
 = 5*4*3*2*factorial(1)
 = 5*4*3*2*1
 = 120

我们只是简单地接触了递归。对于某些编程问题,它是一个非常自然的解决方案。对于其他问题,它有点……不自然。可以肯定地说,这是每个程序员都应该随身携带的工具。

函数与过程与子程序

[编辑 | 编辑源代码]

在阅读编程文献并与程序员交谈时,您可能会遇到“函数”、“过程”和“子程序”这三个术语。大多数情况下,这些术语可以互换使用,但对于那些追求完美的人来说

函数始终返回一个值,并且在给定相同参数的情况下,始终返回相同的值。函数非常类似于您可能在数学课上学过的函数。

过程与函数不同,它可能根本不返回值。与函数不同,过程通常与其参数数组之外的外部环境进行交互。例如,过程可以读取和写入文件。

子程序指的是可以是函数或过程的一系列指令。

官方语法

[编辑 | 编辑源代码]

定义子程序的语法为

sub NAME PROTOTYPE ATTRIBUTES BLOCK

如果您理解这一点,您可能不需要阅读本书 ;)


上一页:修饰符 索引 下一页:Perl 5.10 新增功能
华夏公益教科书