[PERL] 17-參照

URL Link //n.sfs.tw/11532

2017-07-29 00:11:46 By 張○○

PERL的參照一直是迷一樣的存在,就算不會也寫得嚇嚇叫。為了讓本人更了解參照,我寫這篇文章。

參照又稱參考(reference),讓一個變數保存另一個變數的「記憶體位址」

在變數前面加上一個反斜線 '\' 就會變成參照,主要有以下幾種:

1. 純量參照 $scalarref = \$foo;

2. 常數參照 $constref = \3.1415;

3. 陣列參照 $arrayref = \@ARGV;

4. 雜湊參照 $hashref = \%ENV;

5. 副程式參照 $coderef = \&handler;

6. 代號參照 $globref = \*STDOUT;

純量參照

$str = "一本書";
$ref = \$str;
print $ref; # 得到一個位址例如 SCALAR(0x243a7b0)
print $$ref; # 得到參照的值:一本書

說明

第2行 $ref 指向變數 $str的位址
第3行 印$ref 會印出一個位址,例如 SCALAR(0x243a7b0)
第4行 在參照前加上一個'$'字號,則印出該位址的值,這裡的兩個錢號要這樣看

   $$ref = 該位址變數型態+ 變數參照(位址)  = $ + $ref

想當然爾,當我們修改參照的目的變數時,一併會改變參照的值:

$str = "一本書";
$ref = \$str;
print $$ref; #一本書
$str= "一隻狗"; 
print $$ref; #一隻狗

第4~5行 改變了原本的$str變數,結果參照的$$ref值也跟著改。

我們也可以直接改參照的值,原字串也會跟著改:

$$ref= "一頭牛";
print $str; # 一頭牛

 

常數參照

常數參照指向一個常數的位址,這個位址的值都是唯讀的

$ref = \3.14159; # 數值常數
$ref2 = \"ABC123"; # 字串常數
print $$ref; # 3.14159

如果企圖想要修改參照的值,就會報錯,例如:

$$ref= 2.732;

Modification of a read-only value attempted at ...

 

陣列參照

@arr = qw(orange apple lemon);
$ref = \@arr;
print @$ref; #orangeapplelemon
print $arr[1];  #apple
print $ref->[1]; #apple

說明

第3行 用 @$ref 印出陣列內容
第4行 原本印出陣列的第1個項目的方法
第5行 用參照印出陣列的第1個項目的方法,注意寫法

值得注意的,在上面的第2行把參照指到現存的陣列 @arr,這裡也可以直接給定陣列的內容,省略指定 @arr本身,這個稱為匿名陣列

匿名陣列需要用中刮號 '[]' 來建立:

$ref = ['orange', 'apple', 'lemon' ];
print $ref->[1]; #apple

08-陣列 #2 --操作 中有提過,PERL的陣列都是一維結構,但是用參照的方法,可以建立多維的匿名陣列:

$ref = ['Mecury', 'Venus', ['Earth','Moon'], ['Mars', 'Phobos','Deimos']];
print "@$ref"; # Mecury Venus ARRAY(0x10d7a68) ARRAY(0x10f4030)
print $ref->[1]; # Venus
print $ref->[3][1]; # Phobos
print $ref->[2][2]; # 不存在的參照會印出空字串 ''
print $ref->[2]; # ARRAY(0x10d7a68)

第2行中陣列中的陣列會變成位址指標

注意第5行,如果該參照不存在,則印出空字串

 

雜湊參照

雜湊的參照和陣列參照大同小異,差別在於寫法:

%staff = ('name' => '王小明',  'age' => 33, 'job' => '工程師');
$ref = \%staff;
print $staff{'name'}; #王小明
print $ref->{'job'}; #工程師

$ref->{'age'}=40;
print $staff{'age'}; #40

同樣的,也可以直接用匿名雜湊,以大刮號 '{}' 建立

$ref = {'name' => '王小明',  'age' => 33, 'job' => '工程師'};
print $ref->{'job'}; #工程師

雜湊中也能建立雜湊,多維雜湊:

$ref = {
  1=>{'name' => '王小明',  'age' => 33, 'job' => '工程師'},
  2=>{'name' => '李小華',  'age' => 44, 'job' => '業務'},
  3=>{'name' => '張大膽',  'age' => 22, 'job' => '實習生'},
  4=>['Sunday', 'Monday', 'Tuesday' ]
};

print $ref->{3}->{'job'}; #實習生
print $ref->{3}{'job'}; #實習生 可以省略第二個箭頭
print $ref->{2}->{'addr'}; # 不存在的參照會印出空字串 ''
print $ref->{4}->[2]; #Tuesday 雜湊中帶有陣列
print $ref->{4}[2]; #Tuesday 可以省略第二個箭頭

注意上面的範例中,雜湊中帶有陣列,第10行注意寫法。

 

副程式參照

print &power(2,4); #16

$ref = \&power;
print &$ref(2,4); #16

sub power {
  $x = shift;
  $y = shift;
  return $x ** $y;
}

上面的範列中撰寫了一個副程式 power來計算指數,雖然他放在下面,但不必擔心在前面叫用會出錯,因為perl會全部讀入編譯再執行。

第1行 傳統的副程式叫用方法
第3行 設定參照到此副程式
第4行 參照的叫用方法,注意寫法

副程式參照主要用於匿名副程式參照寫法,這樣可以直接把副程式指定給一個變數,在javascript中也有這樣的寫法。

$ref = sub {
  $x = shift;
  $y = shift;
  return $x ** $y;
};

print &$ref(2,4);
print &{$ref}(2,4); #結果同上,把參照用大刮號包起來

值得注意的就是如果這樣子寫,順序就很重要,先得要指定參照才能執行,像上面的第7行叫用放在 $ref指定前的話就會出錯。

 

代號參照

這參照很難理解,主要是對PERL中常用到的代號作參照,舉個例來說,執行時我要讓使用者輸入姓名:

print "輸入姓名:";
my $name = <STDIN>;
chomp $name; #移除行尾換行

print "$name 您好!\n";

執行結果

輸入姓名:<等待輸入,輸入王大明>
王大明 您好!

用參照的寫法第2行改為:

my $in = \*STDIN;
$name=  <$in>;

這個參照真是迷一樣的存在,我也不太理解他存在的意義。

 

副程式用變數傳參方法

接下來用範例來說明副程式用變數傳參方法。

假設某學校數學考試太難,老師討論後決定分數都開根號乘以10作為最後的成績,需要寫一個函數來回傳學生的最後的成績是否及格,同時也要把最後的成績回傳。

當然這個作法有很多種,例如把結果包成陣列回傳,這個範例使用傳參照變數的方法:

$math= 40;

$pass= &adjscore(\$math);
print $math . "\n";  #63.2455532033676
print "及格" if $pass ==1;
print "不及格" if $pass ==0;

sub adjscore {
  local $score = shift;
  $$score = sqrt($$score)*10;
  return 1 if $$score >=60;
  return 0;
}

第3~4行 傳入的是分數參照,經過副程式演算後直接運算成新的成績

副程式中的變數 $score 接到的是 $math的參照,當然運算後直接就改成新的成績。

這就是參照好用的地方!

補充

foreach my $onekey ( keys $arr_ref) { .. }

當把參照放到迴圈中,新版的PERL出現這樣的錯誤:

Experimental values on scalar is now forbidden

如果是陣例參照,改成這樣之一:

foreach my $onekey ( keys @{$arr_ref}) { .. }
foreach my $onekey ( keys @arr_ref) { .. }

如果是雜湊參照,改成這樣

foreach my $onekey ( keys %{$hash_ref}) { .. }

 

上一篇 16-字串取代和置換
回到目錄 01-撰寫第一隻PERL程式
下一篇 18-套件及模組

參考資料

[1] https://perldoc.perl.org/perlref.html