Perl入门学习笔记——子程序

Learning Perl 

第四章 子程序
系统内置函数,chomp,reverse和print等。Perl也可以创建子程序,就是用户自定义函数。
子程序属于独立的名字空间,不会将同一段代码中的子程序&fred和标量$fred混淆。

定义子程序
子程序由关键字sub,子程序名以及花括号封闭起来的代码块组成。
sub marine {
  $n += 1;
  print "hello, number $n!\n" ;
}
子程序被定义在程序中的任意位置,不需要事先声明,是全局的,不存在所谓的私有子程序。
定义了两个子程序,则后面的会覆盖前面的。

调用子程序
可以在表达式中使用子程序(前面加上与号)调用:
&marine;
&marine;

hello, number 1!
hello, number 2!

返回值
子程序被调用时一定是作为表达式的某个部分,即使该表达式的求指结果不会被用到。
需要调用子程序并对它的返回值做进一步的处理,所有的子程序都有返回值,但不是所有的子程序都包含有用的返回值。
Larry简化了"return",子程序执行过程中不断运算,而最后一次运算结果被自动当作子程序的返回值。
sub sum_of_fred_and_barnay {
  print "hi, you called the sum of fred and barnay!\n";
  $fred + $barnay;
}
$fred = 4;
$barnay = 5;
$wilma = &sum_of_fred_and_barnay;
print "\$wilma is $wilma.\n";
$betty = 3 * &sum_of_fred_and_barnay;
print "\$betty is $betty.\n";

hi, you called the sum of fred and barnay!
$wilma is 9.
hi, you called the sum of fred and barnay!
$betty is 27.

print语句只用于协助调试,程序完成后可以删除,假设在子程序结尾新增一条print语句
则表达式最后并非加法运算,而是print语句,返回值通常是1,表示成功输出信息。
这种情况术语是"void context"(空上下文),表示运算结果没有存储在变量里面,未被任何函数使用。

sub larger_of_fred_or_barnay {
  if ($fred > $barnay) {
    return $fred;
    } else {
    return $barnay;
    }
}

larger of fred and barnay is 5.

必须等到执行阶段得到变量内容后,才会知道返回值到底是$fred还是$barney。

参数
子程序不强制使用全局变量。
Perl子程序可以有参数,要传递参数列表到子程序里,只要在子程序调用的后面加上被括号圈引的列表表达式。
$n = &max(20, 15);
参数列表被传递到子程序中,可以随意调用,Perl会自动将参数列表化名为特殊的数组变量@_,该变量在子程序执行期间有效。子程序可以访问数组,以判断数组的个数以及参数的值。
子程序的第一个参数存储在$_[0],第二个参数存储在$_[1],以此类推。这些变量与$_毫无关联。参数列表总是存在某个数组变量里,可以让子程序调用,Perl将这个数组称为@_。
sub max {
  if ($_[0] > $_[1]) {
    $_[0];
    } else {
    $_[1];
    }
  }
$qing = &max(20, 12);
print "the larger argument is $qing.\n";

the larger argument is 20.

子程序只能接受两个参数,多余的参数会被忽略,参数如果不足也会被忽略——如果用到超过@_数组边界的参数,只会得到undef。
@_变量是子程序的私有变量。如果已经有了全局变量@_,则该变量在子程序调用是存起来,并在子程序返回时恢复原本的指。所以在当前的子程序调用中,@_总是包含了它的参数列表。

子程序中的私有变量
Perl里面所有的变量都是全局变量,但是随时可以使用my操作符来创建私有变量,称为词法变量(lexicak variable):
sub max {
  my($m, $n);
  ($m, $n) = @_;
  if ($m > $n)
    {$m}
  else {$n};
}
$a = &max(1,4);
print "$a";
这些变量属于封闭语法块的私有变量,可以称作有限作用域(scope)变量。
语法块之外的任意地方的$m和$n都完全不受这两个私有变量的影响。外部变量也无法影响内部的私有变量。
Perl允许省略语法块的最后一个分号,一般只有简单到整个语句块内只有一行时,才可以省略分号。

变长参数列表
在Perl代码中,常常把任意长度的列表作为参数传递给子程序。
习惯于强类型子程序,只允许一定个数的参数并且预订限制它们的类型。不过子程序超过预期个数被调用时,也会造成问题。
检查@_数组的长度也很容易确定参数个数是否正确。可以写出这样检查参数列表:
sub max {
  if (@_ != 2) {
    print "WARNING! &max should get exactly two arguments!\n"};
  }
  .
  .
  .
}
if判断使用Perl中在标量上下文中直接使用数组名称来获取数组元素的个数。

改进&max子程序
$maxmum = &max(1, 12, 3, 24, 5, 36);
sub max {
  my($max_so_far) = shift @_;
  foreach (@_) {
    if ($_ > $max_so_far) {
      $max_so_far = $_;
    }
  }
  $max_so_far;
}
print $maxmum

36

上述程序代码使用称为高水线(high-watermark)算法。
第一行代码会对参数数组@_进行shift操作并返回1并存入$max_so_far变量,现在@_现在的内容为(12, 3, 24, 5, 36),因为1已经被移走,也就是第一个参数。
foreach循环会遍历参数列表@_里剩余的元素,循环的控制变量默认为$_。

空列表参数
假如传入的参数为@number为空列表。
第一行对参数数组@_进行shift操作,以此作为$max_so_far的值。因为数组为空,所以shift会返回undef。
foreach循环空数组,所以本身不会执行。
Perl会将$max_so_far的值undef作为子程序的返回值。

关于词法变量my
词法变量可以在任何语句块内,而不仅限于子程序的语法块。可以在if,while和foreach的语句块内使用。
foreach (1..10) {
  my ($squre) = $_ * $_;
  print "$_ squred is $squre.\n "
}

1 squred is 1.
 2 squred is 4.
 3 squred is 9.
 4 squred is 16.
 5 squred is 25.
 6 squred is 36.
 7 squred is 49.
 8 squred is 64.
 9 squred is 81.
 10 squred is 100.
 
 $squre对于所属的语句块来说是私有的。如果变量定义并未出现在任何语句块内,则该变量对于整个程序源文件来说是私有的。词法变量的作用域受限于定义它的最内层语句块或文件。只有语句块上下文作用域的程序代码才已$squre使用该变量。
 my操作符并不会改变变量赋值时的上下文:
 my($num) = @_;#列表上下文
 my $num = @_; #标量上下文
my操作符在不加括号时,只能用来声明单个词法变量:
my $cao ,$qing;        #erroe
my($cao, $qing);    #right
可以用my来定义私有数组:my @numbers;
标量被设定为undef,数组被设定为空数组。

use strict编译指令
编译指令,就是给编译器的某些提示,告诉它如何处理接下来的代码。
从Perl5.12开始,使用编译指令指定最低Perl版本号,就相当于隐式打开约束指令:
use 5.012;

return操作符
return操作符会立即停止执行并从子程序返回某个值:
my @names = qw/cao qing wang huan xiao bao/;
my $result = &which_element_is("huan", @names);
sub which_element_is {
  my($what, @array) = @_;
  foreach (0..$#array) {
    if ($what eq $array[$_]) {
      return $_;}
  }
  -1;
}
print "$result";

3

在子程序执行到中间部分就已经得到结论的时候,就可以使用return关键字立即返回.

省略与号
如果编译器知道在调用子程序前看到子程序的定义,或者Perl通过语法规则判断它只能是子程序调用,那么对待该子程序就可以像内置变量那种,在调用时省略与号。
只要将参数列表放在括号内,就一定是函数调用:
my @cards = shuffle(@deck_of_cards);
如果编译器已经见过子程序的定义,也可以省略与号。并且可以省略参数列表两边的括号。
sub division {
  $_[0] / $_[1];
}
my $quotient = division 20, 4;
print $quotient;

5

子程序子所以可以正确运行,是因为符合加不加括号都不会产生歧义的原则。
但是不要把子程序定义放在语句调用的后面。
加入子程序与Perl内置函数重名,必须使用&号,为了避免歧义。

非标量返回值
子程序不仅可以返回标量值,如果在列表上下文使用时,还可以返回列表值。
sub list_from_cao_to_qing {
  if ($cao < $qing) {
    return $cao..$qing;
    }
  else {
  reverse $qing..$cao;
  }
}
$cao = 5;
$qing = 1;
@c = &list_from_cao_to_qing;
print "@c";

5 4 3 2 1

单写一个return不加任何参数时,在标量上下文的返回值就是undef,在列表上下文返回空列表。

持久性私有变量
在子程序中使用my操作符来创建私有变量,每次调用子程序时,私有变量都会被重新定义。而使用state操作符声明变量,就可以在子程序调用期间保留变量之前的值,并将变量作用域局限于子程序内部。
use 5.012;
sub cao {
  state $n = 0;
  $n += 1;
  print "Hello $n.\n";
}
&cao;
&cao;
&cao;

Hello 1.
Hello 2.
Hello 3.

类似标量变量,其他任意类型的变量都可以被声明为state变量。
use 5.012;

sum(5, 6);
sum(1..4);
sum(3);
sub sum {
  state $sum = 0;
  state @numbers;
  foreach my $number (@_) {
    push @numbers, $number;
    $sum += $number;
  }
  say "The sum of (@numbers) is $sum";
}

The sum of (5 6) is 11
The sum of (5 6 1 2 3 4) is 21
The sum of (5 6 1 2 3 4 3) is 24

在使用数组和哈希的state变量时,还是有限制的,无法在列表上下文初始化这两种类型的state变量。

习题
1. 写一个名字为total的子程序,可以返回给定列表中数字相加的和。
2. 使用之前的程序,计算1到1000的值。
3. 写一个名字为above_average的子程序,给定一个包含多个数字的列表,返回大于这些数平均值的数。
4. 写一个名为greet的子程序,给定人名为参数,打印欢迎信息。
5. 修改子程序,告诉新来的人,之前已经来过的人。

答案
1.
use 5.012;
sub total {
    my $sum;
    foreach (@_) {
        $sum += $_;
    }
    return $sum;
}

$a = &total(qw \1 2 3 4 5\);
print "$a";
2.
use 5.012;
sub total {
    my $sum;
    foreach (@_) {
        $sum += $_;
    }
    return $sum;
}
print "The sum of number form 1 to 1000", &total(1..1000),".\n";
3.
use 5.012;
sub total {
    my $sum;
    foreach (@_) {
        $sum += $_;
    }
    return $sum;
}
sub average {
  if (@_ == 0) { return}
  my $count = @_;
  my $sum = &total(@_);
  $sum / $count;
}
sub above_average {
  my $average = average(@_);
  my @list;
  foreach my $element (@_){
    if ($element > $average) {
      push @list, $element;
    }
  }
  @list;
}
my @fred = above_average(1..10);
print "\@myfred is @fred.\n";
4.
use 5.012;

&greet("caoqing");
&greet("xiaohuan");
&greet("baobao");
sub greet {
  state $last_person;
  my $name = shift;
  print "Hi, $name! ";
  if (defined $last_person) {
    print "$last_person is also here!\n";
  }
  else {
    print "You are the first one here!\n";
  }
   $last_person = $name;
   
5.
use 5.012;
&greet("caoqing");
&greet("xiaohuan");
&greet("baobao");
sub greet {
  state @names;
  my $name = shift;
  print "Hi, $name! ";
  if ( @names ) {
    print "I have seen: @names!\n";
  }
  else {
    print "You are the first one here!\n";
  }
   push @names, $name;
}


你可能感兴趣的:(Perl入门学习笔记——子程序)