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。html

若是把上面的print做爲最後一句:python

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

上面的print纔是子程序的返回值,這個返回值是什麼?這是一個輸出語句,它的返回值爲1,表示成功打印。這個返回值顯然沒什麼用。因此,子程序最後執行的語句必定要當心檢查。shell

那上面的$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。code

因此,在邏輯上來講,參數多少不會影響子程序的錯誤,但結果可能會受到一些影響。但能夠在子程序中判斷子程序的參數個數:htm

if(@_ != 2){     # 若是參數個數不是2個,就退出
    return 1;
}

這裏的比較是標量上下文,@_返回的是參數個數。blog

調用子程序的幾種方式分析

通常狀況下,調用咱們自定義的子程序時,都使用&符號,有時候還要帶上括號傳遞參數。

&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的做用域規則,它應該是在執行期間臨時去定義的。但不管如何,它先定義仍是後定義,都不影響對變量做用域的判斷。

相關文章
相關標籤/搜索