• Perl的子程序


    子程序(subroutine)

    • perl中的子程序其实就是自定义函数。它使用sub关键字开头,表示声明一个子程序
    • 子程序名称有独立的名称空间,不会和其它名称冲突
    • Perl中的子程序中可以定义、引用、修改全局变量,这和几乎所有的语言都不同。当然,也可以定义局部变量
    • perl中使用&SUB_NAME()的方式调用SUB_NAME子程序,&有时候可以省略,括号有时候也可以省略。具体的规则见后面
    sub mysub {
        $n += 1;
        print "$n is: $n","
    ";
    }
    &mysub;
    print "$n is: $n","
    ";
    
    • perl的子程序最后一个执行的语句的结果或返回值就是子程序的返回值(是最后一个执行的语句,不是最后一行语句)。所以子程序总会有返回值,但返回值并不总是有用的返回值
    sub mysub {
        $n += 2;
        print "hello world
    ";
        $n + 3;         
    }
    $num = ⊂
    

    这里的&sub有返回值,它的返回值是最后执行的语句$n+3,并将返回值赋值给$num,所以$num的值为5。

    如果把上面的print作为最后一句:

    sub mysub {
        $n += 2;
        $n + 3;                 # 这不是返回值
        print "hello world
    ";  # 这是返回值
    }
    $num = ⊂
    

    上面的print才是子程序的返回值,这个返回值是什么?这是一个输出语句,它的返回值为1,表示成功打印。这个返回值显然没什么用。所以,子程序最后执行的语句一定要小心检查。

    那上面的$n+3的结果是什么?它的上下文是void context,相加的结果会被丢弃。

    子程序还可以返回列表。

    ($m,$n)=(3,5);
    sub mysub {
        $m..($n+1);
    }
    @arr=&mysub;
    print @arr;     # 输出3456
    
    • 如果想在子程序的中间退出子程序,就可以使用return子句来返回值。当然,如果这个返回语句是最后一行,就可以省略return
    # 返回一个偶数
    $n=20;
    sub mysub {
        if ($n % 2 == 0){
            return $n;
        }
        $n-1;         # 等价于 return $n-1;
    }
    $oushu=&mysub;
    print $oushu,"
    ";
    

    子程序参数

    Perl子程序除了可以直接操作全局变量,还可以传递参数。例如:

    &mysub(arg1,arg2...);
    &mysub arg1,arg2...;
    

    至于什么时候可以省略括号,后面再解释。

    • 调用子程序时传递的参数会传入子程序,它们存储在一个特殊的数组变量@_
    • 既然@_是数组,就可以使用$_[index]的方式引用数组中的各元素,也就是子程序的各个参数
    • @_数组只在每个子程序执行期间有效,每个子程序的@_都互不影响。子程序执行完成,@_将复原为原来的值
    # 返回最大值
    @_=qw(perl python shell);     # 测试:先定义数组@_
    sub mysub {
        if($_[0] > $_[1]){
            $_[0];
        }else{
            $_[1];
        }
    }
    $max = &mysub(10,20);
    print $max,"
    ";
    print @_;             # 子程序执行完,@_复原为qw(perl python shell)
    

    如果上面的子程序给的参数不是两个,而是3个或者1个(&mysub(10,20,30);&mysub(10);)会如何?因为参数是存在数组中的,所以给的参数多了,子程序用不到它,所以忽略多出的参数,如果给的参数少了,那么缺少的那些参数被引用时,值将是undef。

    所以,在逻辑上来说,参数多少不会影响子程序的错误,但结果可能会受到一些影响。但可以在子程序中判断子程序的参数个数:

    if(@_ != 2){     # 如果参数个数不是2个,就退出
        return 1;
    }
    

    这里的比较是标量上下文,@_返回的是参数个数。

    调用子程序的几种方式分析

    一般情况下,调用我们自定义的子程序时,都使用&符号,有时候还要带上括号传递参数。

    &mysub1();
    &mysub2;
    &mysub3 arg1,arg2;
    &mysub4(arg1,arg2);
    

    但有时候,&符号和括号是可以省略的。主要的规则是:

    • 只有子程序定义语句在子程序调用语句前面,才可以省略括号
    • 当使用了括号,perl已经知道这就是一个子程序调用,这时可以省略&也可以不省略&
    • 不能省略&的情况比较少。基本上,只要子程序名称不和内置函数同名,或者有特殊需求时(如需要明确子程序的名称时,如defined(&mysub)),都可以省略&
    • 不能在使用了&、有参数传递的情况下,省略括号
    • 最安全、最保险的调用方式是:
      • 有参数时:&subname(arg1,arg2),即不省略&和括号
      • 无参数时:&subname
      • 使用&的调用方式是比较古老的行为,虽然安全。但直接使用括号调用也基本无差别,但却更现代,所以建议用func()的方式调用自定义的子程序
    sub mysub{
        print @_,"
    ";
    }
                              # 先定义了子程序
    mysub;                      # 正常调用
    mysub();                    # 正常调用
    mysub("hello","world3");    # 正常调用
    mysub "hello","world4";     # 正常调用
    &mysub;                     # 安全的调用
    &mysub("hello","world6");   # 安全的调用
    &mysub "hello","world7";    # 本调用错误,因为使用了&,且有参数
    

    上面是先定义子程序,再调用子程序的。下面是先调用子程序,再定义子程序的。

    mysub;                      # 本调用无括号,不报错,当做内置函数执行,但无此内置函数,所以忽略
    mysub();                    # 有括号,不报错
    mysub("hello","world3");    # 有括号,不报错
    mysub "hello","world4";     # 无括号,本调用错误
    &mysub "hello","world7";    # 本调用错误
    &mysub;                     # 安全的调用
    &mysub("hello","world6");   # 安全的调用
    
    sub mysub{
        print @_,"HELLO","
    ";
    }
    

    如果子程序名称和内置函数同名,则不安全的调用方式总会优先调用内置函数。

    子程序中的私有变量:my关键字

    my关键字可以声明局部变量、局部数组。它可以用在任何类型的语句块内,而不限于sub子程序中。

    sub mysub {
        my $a=0;         # 一次只能声明一个局部目标
        my $b;           # 声明变量,初始undef
        my @arr;         # 声明空数组
        my($m,$n);       # 一次可以声明多个局部目标
        ($m,$n)=@_;      # 将函数参数赋值给$m,$n
        my($x,$y) = @_;  # 一步操作
    }
    
    foreach my $var (@arr) {   # 将控制变量定义为局部变量
        my($sq) = $_ * $_;     # 在foreach语句块内定义局部变量
    }
    

    在my定义局部变量的时候,需要注意列表上下文和标量上下文:

    @_=qw(perl shell python);
    my   $num = @_;              # 标量上下文
    my (@num) = @_;              # 列表上下文
    print $num,"
    ";             # 返回3
    print @num,"
    ";             # 返回perlshellpython
    

    在Perl中,除了my可以修饰作用域,还有local和our也可以修饰作用域,它们之间的区别参见:Perl的our、my、local的区别

    state关键字

    my关键字是让变量、数组、哈希私有化,state关键字则是让私有变量、数组、哈希持久化。注意两个关键字:私有,持久化。

    使用state关键字声明、初始化的变量对外不可见,但对其所在子程序是持久的:每次调用子程序后的变量值都保存着,下次再调用子程序会继承这个值。

    这个特性是perl 5.10版才引入的,所以必须加上use 5.010;语句才能使用该功能。

    use 5.010;
    $n=22;
    sub mysub {
        state $n += 1;
        print "Hello,$n
    ";
    }
    &mysub;           # 输出Hello,1
    print $n,"
    ";    # 输出22
    &mysub;           # 输出Hello,2
    print $n,"
    ";    # 输出22
    

    当然,如果在子程序中每次都用state将变量强制初始化,那么这个变量持久与否就无所谓了,这时用my关键字的效果是一样的。

    use 5.010;
    sub mysub {
        state $n=0;
        print "hello,$n","
    ";
    }
    &mysub;
    &mysub;
    &mysub;
    

    state除了可以初始化变量,还可以初始化数组和hash。但初始化数组和hash的时候有限制:不能在列表上下文初始化。

    use 5.010;
    sub mysub {
        state $n;        # 初始化为undef
        state @arr1;     # 初始化为空列表()
        state @arr2 = qw(perl shell);  # 错误,不能在列表上下文初始化
    }
    

    perl作用域初探

    因为perl中支持先定义子程序再调用,也支持先调用再定义的方式。不同的调用方式有可能会有区别。

    例如:

    #!/usr/bin/env perl -w
    use strict;
    
    # do_stuff(1);   # (1).在定义的前面
    {
        my $last = 1;
        sub do_stuff {
            my $arg = shift;
            print $arg + $last;
        }
    }
    do_stuff(1);     # (2).在定义的后面
    

    上面在不同的位置调用子程序do_stuff(1),但只有第二种方式是正确的,第一种方式是错误的。

    原因在于my定义的变量是词法作用域变量,先不用管词法作用域是什么。只需要知道my定义的变量是在程序编译期间定义的好的,但是赋值操作是在程序执行期间进行的。而子程序sub的名称do_stuff是无法加my关键字的,所以perl中所有的子程序都是全局范围可调用的。子程序的调用是程序执行期间的

    所以上面的程序中,整个过程是先在编译期间定义好词法变量$last(但未赋值初始化),子程序do_stuff。然后开始从程序头部往下执行:

    当执行到(1)的位置处也就是调用子程序,这个子程序中引用了变量$last,但$last至今未赋值,所以会报变量未初始化的错误。

    如果没有(1),那么从上往下执行将首先执行到my $last = 1,这表示为已在编译期间定义好的变量赋值。然后再继续执行到(2)调用子程序,但子程序引用的$last已经赋值初始化,所以一切正常。

    在perl中的子程序是在编译期间定义好的,还是执行期间临时去定义的,目前我个人还不是太确定,按照perl的作用域规则,它应该是在执行期间临时去定义的。但无论如何,它先定义还是后定义,都不影响对变量作用域的判断。

  • 相关阅读:
    程序员数学
    [topcoder]FlowerGarden
    [leetcode]Trapping Rain Water
    [leetcode]Gray Code
    [leetcode]Unique Paths II
    hdu 4112 Break the Chocolate(ceil floor)
    【转】博弈-翻硬币游戏
    POJ 3710 Christmas Game
    hdu 3590 PP and QQ
    博弈进阶
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/9512313.html
Copyright © 2020-2023  润新知