Perl的子程序


子程序(subroutine)

  • perl中的子程序其實就是自定義函數。它使用sub關鍵字開頭,表示聲明一個子程序
  • 子程序名稱有獨立的名稱空間,不會和其它名稱沖突
  • Perl中的子程序中可以定義、引用、修改全局變量,這和幾乎所有的語言都不同。當然,也可以定義局部變量
  • perl中使用&SUB_NAME()的方式調用SUB_NAME子程序,&有時候可以省略,括號有時候也可以省略。具體的規則見后面
sub mysub {
    $n += 1;
    print "\$n is: $n","\n";
}
&mysub;
print "\$n is: $n","\n";
  • perl的子程序最后一個執行的語句的結果或返回值就是子程序的返回值(是最后一個執行的語句,不是最后一行語句)。所以子程序總會有返回值,但返回值並不總是有用的返回值
sub mysub {
    $n += 2;
    print "hello world\n";
    $n + 3;         
}
$num = ⊂

這里的&sub有返回值,它的返回值是最后執行的語句$n+3,並將返回值賦值給$num,所以$num的值為5。

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

sub mysub {
    $n += 2;
    $n + 3;                 # 這不是返回值
    print "hello world\n";  # 這是返回值
}
$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,"\n";

子程序參數

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,"\n";
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 @_,"\n";
}
                          # 先定義了子程序
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","\n";
}

如果子程序名稱和內置函數同名,則不安全的調用方式總會優先調用內置函數。

子程序中的私有變量: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,"\n";             # 返回3
print @num,"\n";             # 返回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\n";
}
&mysub;           # 輸出Hello,1
print $n,"\n";    # 輸出22
&mysub;           # 輸出Hello,2
print $n,"\n";    # 輸出22

當然,如果在子程序中每次都用state將變量強制初始化,那么這個變量持久與否就無所謂了,這時用my關鍵字的效果是一樣的。

use 5.010;
sub mysub {
    state $n=0;
    print "hello,$n","\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的作用域規則,它應該是在執行期間臨時去定義的。但無論如何,它先定義還是后定義,都不影響對變量作用域的判斷。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM